Class: HTTP::CookieJar::MozillaStore

Inherits:
AbstractStore show all
Defined in:
lib/http/cookie_jar/mozilla_store.rb

Overview

A store class that uses Mozilla compatible SQLite3 database as backing store.

Session cookies are stored separately on memory and will not be stored persistently in the SQLite3 database.

Defined Under Namespace

Classes: Database

Constant Summary collapse

SCHEMA_VERSION =

:stopdoc:

7
ALL_COLUMNS =
%w[
  baseDomain
  originAttributes
  name value
  host path
  expiry creationTime lastAccessed
  isSecure isHttpOnly
  appId inBrowserElement
]
SQL =
{}
Callable =
proc { |obj, meth, *args|
  proc {
    obj.__send__(meth, *args)
  }
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractStore

class_to_symbol, #empty?, implementation, inherited

Constructor Details

#initialize(options = nil) ⇒ MozillaStore

:call-seq:

new(**options)

Generates a Mozilla cookie store. If the file does not exist, it is created. If it does and its schema is old, it is automatically upgraded with a new schema keeping the existing data.

Available option keywords are as below:

:filename : A file name of the SQLite3 database to open. This option is mandatory.

:gc_threshold : GC threshold; A GC happens when this many times cookies have been stored (default: ‘HTTP::Cookie::MAX_COOKIES_TOTAL / 20`)

:app_id : application ID (default: ‘0`) to have per application jar.

:in_browser_element : a flag to tell if cookies are stored in an in browser element. (default: ‘false`)



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/http/cookie_jar/mozilla_store.rb', line 92

def initialize(options = nil)
  super

  @origin_attributes = encode_www_form({}.tap { |params|
    params['appId'] = @app_id if @app_id.nonzero?
    params['inBrowserElement'] = 1 if @in_browser_element
  })

  @filename = options[:filename] or raise ArgumentError, ':filename option is missing'

  @sjar = HTTP::CookieJar::HashStore.new

  @db = Database.new(@filename)

  @stmt = Hash.new { |st, key|
    st[key] = @db.prepare(SQL[key])
  }

  ObjectSpace.define_finalizer(self, Callable[@db, :close])

  upgrade_database

  @gc_index = 0
end

Instance Attribute Details

#filenameObject (readonly)

The file name of the SQLite3 database given in initialization.



123
124
125
# File 'lib/http/cookie_jar/mozilla_store.rb', line 123

def filename
  @filename
end

Instance Method Details

#add(cookie) ⇒ Object



424
425
426
427
428
429
430
431
432
# File 'lib/http/cookie_jar/mozilla_store.rb', line 424

def add(cookie)
  if cookie.session?
    @sjar.add(cookie)
    db_delete(cookie)
  else
    @sjar.delete(cookie)
    db_add(cookie)
  end
end

#cleanup(session = false) ⇒ Object



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/http/cookie_jar/mozilla_store.rb', line 553

def cleanup(session = false)
  synchronize {
    break if @gc_index == 0

    @stmt[:delete_expired].execute({ 'expiry' => Time.now.to_i })

    @stmt[:overusing_domains].execute({
        'count' => HTTP::Cookie::MAX_COOKIES_PER_DOMAIN
      }).each { |row|
      domain, count = row['domain'], row['count']

      @stmt[:delete_per_domain_overuse].execute({
          'domain' => domain,
          'limit' => count - HTTP::Cookie::MAX_COOKIES_PER_DOMAIN,
        })
    }

    overrun = count - HTTP::Cookie::MAX_COOKIES_TOTAL

    if overrun > 0
      @stmt[:delete_total_overuse].execute({ 'limit' => overrun })
    end

    @gc_index = 0
  }
  self
end

#clearObject



522
523
524
525
526
# File 'lib/http/cookie_jar/mozilla_store.rb', line 522

def clear
  @db.execute("DELETE FROM moz_cookies")
  @sjar.clear
  self
end

#closeObject

Closes the SQLite3 database. After closing, any operation may raise an error.



127
128
129
130
# File 'lib/http/cookie_jar/mozilla_store.rb', line 127

def close
  @db.closed? || @db.close
  self
end

#closed?Boolean

Tests if the SQLite3 database is closed.

Returns:

  • (Boolean)


133
134
135
# File 'lib/http/cookie_jar/mozilla_store.rb', line 133

def closed?
  @db.closed?
end

#default_optionsObject



15
16
17
18
19
20
21
# File 'lib/http/cookie_jar/mozilla_store.rb', line 15

def default_options
  {
    :gc_threshold => HTTP::Cookie::MAX_COOKIES_TOTAL / 20,
    :app_id => 0,
    :in_browser_element => false,
  }
end

#delete(cookie) ⇒ Object



434
435
436
437
# File 'lib/http/cookie_jar/mozilla_store.rb', line 434

def delete(cookie)
  @sjar.delete(cookie)
  db_delete(cookie)
end

#each(uri = nil, &block) ⇒ Object

:yield: cookie



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/http/cookie_jar/mozilla_store.rb', line 460

def each(uri = nil, &block) # :yield: cookie
  now = Time.now
  if uri
    thost = DomainName.new(uri.host)

    @stmt[:cookies_for_domain].execute({
        :baseDomain => thost.domain || thost.hostname,
        :appId => @app_id,
        :inBrowserElement => @in_browser_element ? 1 : 0,
        :expiry => now.to_i,
      }).each { |row|
      if secure = row['isSecure'] != 0
        next unless URI::HTTPS === uri
      end

      cookie = HTTP::Cookie.new({}.tap { |attrs|
          attrs[:name]        = row['name']
          attrs[:value]       = row['value']
          attrs[:domain]      = row['host']
          attrs[:path]        = row['path']
          attrs[:expires_at]  = Time.at(row['expiry'])
          attrs[:accessed_at] = deserialize_usectime(row['lastAccessed'])
          attrs[:created_at]  = deserialize_usectime(row['creationTime'])
          attrs[:secure]      = secure
          attrs[:httponly]    = row['isHttpOnly'] != 0
        })

      if cookie.valid_for_uri?(uri)
        cookie.accessed_at = now
        @stmt[:update_lastaccessed].execute({
            'lastAccessed' => serialize_usectime(now),
            'id' => row['id'],
          })
        yield cookie
      end
    }
    @sjar.each(uri, &block)
  else
    @stmt[:all_cookies].execute({
        :appId => @app_id,
        :inBrowserElement => @in_browser_element ? 1 : 0,
        :expiry => now.to_i,
      }).each { |row|
      cookie = HTTP::Cookie.new({}.tap { |attrs|
          attrs[:name]        = row['name']
          attrs[:value]       = row['value']
          attrs[:domain]      = row['host']
          attrs[:path]        = row['path']
          attrs[:expires_at]  = Time.at(row['expiry'])
          attrs[:accessed_at] = deserialize_usectime(row['lastAccessed'])
          attrs[:created_at]  = deserialize_usectime(row['creationTime'])
          attrs[:secure]      = row['isSecure'] != 0
          attrs[:httponly]    = row['isHttpOnly'] != 0
        })

      yield cookie
    }
    @sjar.each(&block)
  end
  self
end

#initialize_copy(other) ⇒ Object

Raises TypeError. Cloning is inhibited in this store class.

Raises:

  • (TypeError)


118
119
120
# File 'lib/http/cookie_jar/mozilla_store.rb', line 118

def initialize_copy(other)
  raise TypeError, 'can\'t clone %s' % self.class
end

#schema_versionObject

Returns the schema version of the database.



138
139
140
141
142
143
# File 'lib/http/cookie_jar/mozilla_store.rb', line 138

def schema_version
  @schema_version ||= @db.execute("PRAGMA user_version").first["user_version"]
rescue SQLite3::SQLException
  @logger.warn "couldn't get schema version!" if @logger
  return nil
end