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:

5
ALL_COLUMNS =
%w[
baseDomain
appId inBrowserElement
name value
host path
expiry creationTime lastAccessed
isSecure isHttpOnly
UK_COLUMNS =
%w[
name host path
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`)


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

def initialize(options = nil)
  super

  @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.


121
122
123
# File 'lib/http/cookie_jar/mozilla_store.rb', line 121

def filename
  @filename
end

Instance Method Details

#add(cookie) ⇒ Object


300
301
302
303
304
305
306
307
308
# File 'lib/http/cookie_jar/mozilla_store.rb', line 300

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


430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/http/cookie_jar/mozilla_store.rb', line 430

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


399
400
401
402
403
# File 'lib/http/cookie_jar/mozilla_store.rb', line 399

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.


125
126
127
128
# File 'lib/http/cookie_jar/mozilla_store.rb', line 125

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

#closed?Boolean

Tests if the SQLite3 database is closed.

Returns:

  • (Boolean)

131
132
133
# File 'lib/http/cookie_jar/mozilla_store.rb', line 131

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


310
311
312
313
# File 'lib/http/cookie_jar/mozilla_store.rb', line 310

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

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

:yield: cookie


336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/http/cookie_jar/mozilla_store.rb', line 336

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

    @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] = Time.at(row['lastAccessed'] || 0)
          attrs[:created_at]  = Time.at(row['creationTime'] || 0)
          attrs[:secure]      = secure
          attrs[:httponly]    = row['isHttpOnly'] != 0
        })

      if cookie.valid_for_uri?(uri)
        cookie.accessed_at = now
        @stmt[:update_lastaccessed].execute({
            'lastAccessed' => now.to_i,
            '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] = Time.at(row['lastAccessed'] || 0)
          attrs[:created_at]  = Time.at(row['creationTime'] || 0)
          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)

116
117
118
# File 'lib/http/cookie_jar/mozilla_store.rb', line 116

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

#schema_versionObject

Returns the schema version of the database.


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

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