Compare commits

...

18 Commits

Author SHA1 Message Date
Jeff Dallien 115d986044 Add CHANGES file to record differences between versions 2013-07-03 22:09:56 -04:00
Jeff Dallien 4f581183df Increment version now that browser auto detection is merged in. 2013-07-02 23:25:47 -04:00
Jeff Dallien 36ad9f11b2 Fix up browser detector spec and do some refactoring for clarity. 2013-07-02 23:13:22 -04:00
Jeff Dallien eda67c7ea8 Fix load path. 2013-07-02 23:07:52 -04:00
Ben Eills e8a4ab5f22 Added RSpec tests, although these are untested and should be checked over by someone who actually knows anything about RSpec before being pulled 2013-07-01 16:59:25 +01:00
Ben Eills 3217c96dd6 Added cookie-file location guessing
* User may now specify a browser to use
* Or he may choose to guess the best, based on mtime of cookie files
* Command line application accepts flags for this functionality
2013-06-27 20:51:41 +01:00
Jeff Dallien 6423b9bcb5 Again, fix markdown in README. 2012-02-22 20:09:36 -05:00
Jeff Dallien 74b464b40c Fix stupid markdown in README 2012-02-22 20:07:02 -05:00
Jeff Dallien 54e805f17f Update README to mention Chrome. 2012-02-22 20:05:13 -05:00
Jeff Dallien ea196c2e4a Update gemspec to reflect Chrome support 2012-02-22 19:55:21 -05:00
Jeff Dallien dbd7ca9299 Update 'usage' message to reflect Chrome support and increase version number to 0.1.0. 2012-02-22 19:45:17 -05:00
Jeff Dallien c8d8879153 Need to actually raise custom exception class 2012-02-22 19:35:52 -05:00
Jeff Dallien 58775f8e06 Add some more useful error messages in the common situations (file doesn't exist, isn't a sqlite db) 2012-02-22 19:33:25 -05:00
Jeff Dallien 7e9e160f17 Should close the db after extracting the cookies from it. 2012-02-22 19:19:01 -05:00
Jeff Dallien f764e35095 Detector browser that created the supplied cookie db by table names present. 2012-02-22 19:05:07 -05:00
Jeff Dallien 2856bd60fe Move the two exactly duplicated methods into a module. 2012-02-21 19:53:53 -05:00
Jeff Dallien 61d8f6e13b Need to include the new file here. 2012-02-21 19:24:07 -05:00
Jeff Dallien 64d118cc85 Add Chrome/Chromium cookie extractor. Same as the Firefox one, but with different table and field names. Will refactor them both to remove duplication. 2012-02-21 19:22:56 -05:00
13 changed files with 441 additions and 34 deletions

16
CHANGES.md Normal file
View File

@ -0,0 +1,16 @@
CHANGES: cookie_extractor
-------------------------
### v0.2.0 2013-07-02
- Browser guessing code contributed by Ben Eills (github.com/beneills):
- Added --guess flag to automatically choose most recently used cookie file
- Added --browser flag to specify browser by name
### v0.1.0 2012-02-23
- Updated to include Chrome/Chromium support.
### v0.0.1 2012-02-20
- Initial version. Supports extracting Firefox cookies.

View File

@ -1,15 +1,26 @@
cookie_extractor
----------------
Extract cookies from Firefox sqlite cookie store (eventually Chrome also) into a wget-compatible cookies.txt file.
Extract cookies from Firefox, Chrome or Chromium sqlite cookie stores into a wget-compatible cookies.txt file.
### Install ###
gem install cookie_extractor
gem install cookie_extractor
### Usage ###
cookie_extractor /path/to/firefox/cookies.sqlite > cookies.txt
cookie_extractor /path/to/firefox/cookies.sqlite > cookies.txt
cookie_extractor --guess # Guess which browser to use and open corresponding file
cookie_extractor --browser chrome|chromium|firefox # Open corresponding DB
Typical locations for the cookies file on Linux are:
* Firefox: *~/.mozilla/firefox/(profile directory)/cookies.sqlite*
* Chrome: *~/.config/google-chrome/Default/Cookies*
* Chromium: *~/.config/chromium/Default/Cookies*
### License ###

View File

@ -2,10 +2,46 @@
require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cookie_extractor"))
# TODO: detect firefox or chrome input file and/or locate it automatically
filename = ARGV.first
if filename
puts CookieExtractor::FirefoxCookieExtractor.new(filename).extract.join("\n")
else
puts "Usage: cookie_extractor /path/to/cookies.sqlite"
def usage msg=nil
puts msg if msg
puts "Usage: cookie_extractor /path/to/cookies.sqlite Open a DB file"
puts " cookie_extractor --guess Guess which browser to use and open corresponding file"
puts " cookie_extractor --browser chrome|chromium|firefox Open DB corresponding to a particular browser"
exit
end
begin
extractor =
case ARGV.first
when '--guess', '-g'
begin
CookieExtractor::BrowserDetector.guess()
rescue CookieExtractor::NoCookieFileFoundException
abort "Error: we couldn't find any supported broswer's cookies"
end
when '--browser', '-b'
browser = ARGV[1]
usage "Error: Please supply a browser name" unless browser
begin
CookieExtractor::BrowserDetector.browser_extractor(browser)
rescue CookieExtractor::InvalidBrowserNameException
abort "Error: '#{browser}' is not a valid browser name"
rescue CookieExtractor::NoCookieFileFoundException
abort "Error: Could not locate cookie file for browser #{browser}"
end
when nil
usage
else
filename = ARGV.first
abort "Error: #{filename} does not exist" unless File.exists?(filename)
CookieExtractor::BrowserDetector.new_extractor(filename)
end
puts extractor.extract.join("\n")
rescue SQLite3::NotADatabaseException,
CookieExtractor::BrowserNotDetectedException
abort "Error: File '#{filename}' is not a Firefox or Chrome cookie database"
end

View File

@ -8,8 +8,8 @@ Gem::Specification.new do |s|
s.authors = ["Jeff Dallien"]
s.email = ["jeff@dallien.net"]
s.homepage = "http://github.com/jdallien/cookie_extractor"
s.summary = %q{Create cookies.txt from Firefox cookies}
s.description = %q{Extract cookies from Firefox sqlite databases into a wget-compatible cookies.txt file.}
s.summary = %q{Create cookies.txt from Firefox or Chrome/Chromium cookies}
s.description = %q{Extract cookies from Firefox, Chrome or Chromium sqlite databases into a wget-compatible cookies.txt file.}
s.rubyforge_project = "cookie_extractor"
@ -18,6 +18,6 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_development_dependency "rspec"
s.add_runtime_dependency "sqlite3-ruby"
s.add_development_dependency "rspec", "~> 2.8"
s.add_runtime_dependency "sqlite3-ruby", "~> 1.3"
end

View File

@ -1,5 +1,11 @@
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
require "cookie_extractor/version"
require "cookie_extractor/common"
require "cookie_extractor/firefox_cookie_extractor"
require "cookie_extractor/chrome_cookie_extractor"
require "cookie_extractor/browser_detector"
module CookieExtractor
end

View File

@ -0,0 +1,78 @@
module CookieExtractor
class BrowserNotDetectedException < Exception; end
class InvalidBrowserNameException < Exception; end
class NoCookieFileFoundException < Exception; end
class BrowserDetector
COOKIE_LOCATIONS = {
"chrome" => "~/.config/google-chrome/Default/Cookies",
"chromium" => "~/.config/chromium/Default/Cookies",
"firefox" => "~/.mozilla/firefox/*.default/cookies.sqlite"
}
# Returns the extractor of the most recently used browser's cookies
# or raise NoCookieFileFoundException if there are no cookies
def self.guess
most_recently_used_detected_browsers.each { |browser, path|
begin
extractor = self.browser_extractor(browser)
rescue BrowserNotDetectedException, NoCookieFileFoundException
# better try the next one...
else
return extractor
end
}
# If we make it here, we've failed...
raise NoCookieFileFoundException, "Couldn't find any browser's cookies"
end
# Open a browser's cookie file using intelligent guesswork
def self.browser_extractor(browser)
raise InvalidBrowserNameException, "Browser must be one of: #{self.supported_browsers.join(', ')}" unless self.supported_browsers.include?(browser)
paths = Dir.glob(File.expand_path(COOKIE_LOCATIONS[browser]))
if paths.length < 1 or not File.exists?(paths.first)
raise NoCookieFileFoundException, "File #{paths.first} does not exist!"
end
self.new_extractor(paths.first)
end
def self.new_extractor(db_filename)
browser = detect_browser(db_filename)
if browser
CookieExtractor.const_get("#{browser}CookieExtractor").new(db_filename)
else
raise BrowserNotDetectedException, "Could not detect browser type."
end
end
def self.supported_browsers
COOKIE_LOCATIONS.keys
end
def self.detect_browser(db_filename)
db = SQLite3::Database.new(db_filename)
browser =
if has_table?(db, 'moz_cookies')
'Firefox'
elsif has_table?(db, 'cookies')
'Chrome'
end
db.close
browser
end
def self.has_table?(db, table_name)
db.table_info(table_name).size > 0
end
def self.most_recently_used_detected_browsers
COOKIE_LOCATIONS.select { |browser, path|
Dir.glob(File.expand_path(path)).any?
}.sort_by { |browser, path|
File.mtime(Dir.glob(File.expand_path(path)).first)
}.reverse
end
private_class_method :most_recently_used_detected_browsers
end
end

View File

@ -0,0 +1,29 @@
require 'sqlite3'
module CookieExtractor
class ChromeCookieExtractor
include Common
def initialize(cookie_file)
@cookie_file = cookie_file
end
def extract
db = SQLite3::Database.new @cookie_file
db.results_as_hash = true
result = []
db.execute("SELECT * FROM cookies") do |row|
result << [ row['host_key'],
true_false_word(is_domain_wide(row['host_key'])),
row['path'],
true_false_word(row['secure']),
row['expires_utc'],
row['name'],
row['value']
].join("\t")
end
db.close
result
end
end
end

View File

@ -0,0 +1,19 @@
module CookieExtractor
module Common
private
def is_domain_wide(hostname)
hostname[0..0] == "."
end
def true_false_word(value)
if value == "1" || value == 1 || value == true
"TRUE"
elsif value == "0" || value == 0 || value == false
"FALSE"
else
raise "Invalid value passed to true_false_word: #{value.inspect}"
end
end
end
end

View File

@ -2,6 +2,7 @@ require 'sqlite3'
module CookieExtractor
class FirefoxCookieExtractor
include Common
def initialize(cookie_file)
@cookie_file = cookie_file
@ -10,9 +11,9 @@ module CookieExtractor
def extract
db = SQLite3::Database.new @cookie_file
db.results_as_hash = true
@result = []
result = []
db.execute("SELECT * FROM moz_cookies") do |row|
@result << [ row['host'],
result << [ row['host'],
true_false_word(is_domain_wide(row['host'])),
row['path'],
true_false_word(row['isSecure']),
@ -21,23 +22,8 @@ module CookieExtractor
row['value']
].join("\t")
end
@result
end
private
def is_domain_wide(hostname)
hostname[0..0] == "."
end
def true_false_word(value)
if value == "1" || value == 1 || value == true
"TRUE"
elsif value == "0" || value == 0 || value == false
"FALSE"
else
raise "Invalid value passed to true_false_word: #{value.inspect}"
end
db.close
result
end
end
end

View File

@ -1,3 +1,3 @@
module CookieExtractor
VERSION = "0.0.1"
VERSION = "0.2.0"
end

View File

@ -0,0 +1,80 @@
require File.join(File.dirname(__FILE__), "spec_helper")
describe CookieExtractor::BrowserDetector, "determining the correct extractor to use" do
before :each do
@fake_cookie_db = double("cookie database", :close => true)
SQLite3::Database.should_receive(:new).
with('filename').
and_return(@fake_cookie_db)
end
describe "given a sqlite database with a 'moz_cookies' table" do
before :each do
@fake_cookie_db.should_receive(:table_info).
with("moz_cookies").
and_return(
{ 'name' => 'some_field',
'type' => "some_type" })
end
it "should return a firefox extractor instance" do
extractor = CookieExtractor::BrowserDetector.new_extractor('filename')
extractor.instance_of?(CookieExtractor::FirefoxCookieExtractor).should be_true
end
end
describe "given a sqlite database with a 'cookies' table" do
before :each do
@fake_cookie_db.should_receive(:table_info).
with("moz_cookies").
and_return([])
@fake_cookie_db.should_receive(:table_info).
with("cookies").
and_return(
[{ 'name' => 'some_field',
'type' => "some_type" }])
end
it "should return a chrome extractor instance" do
extractor = CookieExtractor::BrowserDetector.new_extractor('filename')
extractor.instance_of?(CookieExtractor::ChromeCookieExtractor).should be_true
end
end
end
describe CookieExtractor::BrowserDetector, "guessing the location of the cookie file" do
describe "when no cookie files are found in the standard locations" do
before :each do
Dir.stub!(:glob).and_return([])
end
it "should raise NoCookieFileFoundException" do
lambda { CookieExtractor::BrowserDetector.guess }.
should raise_error(CookieExtractor::NoCookieFileFoundException)
end
end
describe "when multiple cookie files are found in the standard locations" do
before :each do
cookie_locations = CookieExtractor::BrowserDetector::COOKIE_LOCATIONS
Dir.stub!(:glob).and_return([cookie_locations['chrome']],
[],
[cookie_locations['firefox']])
end
describe "and chrome was the most recently used" do
before :each do
File.should_receive(:mtime).twice.and_return(
Time.parse("July 2 2013 00:00:00"),
Time.parse("July 1 2013 00:00:00"))
end
it "should build a ChromeCookieExtractor" do
CookieExtractor::BrowserDetector.
should_receive(:browser_extractor).
once.with("chrome")
CookieExtractor::BrowserDetector.guess
end
end
end
end

View File

@ -0,0 +1,126 @@
require File.join(File.dirname(__FILE__), "spec_helper")
describe CookieExtractor::ChromeCookieExtractor do
before :each do
@fake_cookie_db = double("cookie database",
:results_as_hash= => true,
:close => true)
SQLite3::Database.should_receive(:new).
with('filename').
and_return(@fake_cookie_db)
end
describe "opening and closing a sqlite db" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{ 'host_key' => '.dallien.net',
'path' => '/',
'secure' => '0',
'expires_utc' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::ChromeCookieExtractor.new('filename')
end
it "should close the db when finished" do
@fake_cookie_db.should_receive(:close)
@extractor.extract
end
end
describe "with a cookie that has a host starting with a dot" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{ 'host_key' => '.dallien.net',
'path' => '/',
'secure' => '0',
'expires_utc' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::ChromeCookieExtractor.new('filename')
@result = @extractor.extract
end
it "should return one cookie string" do
@result.size.should == 1
end
it "should put TRUE in the domain wide field" do
cookie_string = @result.first
cookie_string.split("\t")[1].should == "TRUE"
end
it "should build the correct cookie string" do
cookie_string = @result.first
cookie_string.should ==
".dallien.net\tTRUE\t/\tFALSE\t1234567890\tNAME\tVALUE"
end
end
describe "with a cookie that has a host not starting with a dot" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{ 'host_key' => 'jeff.dallien.net',
'path' => '/path',
'secure' => '1',
'expires_utc' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::ChromeCookieExtractor.new('filename')
@result = @extractor.extract
end
it "should return one cookie string" do
@result.size.should == 1
end
it "should put FALSE in the domain wide field" do
cookie_string = @result.first
cookie_string.split("\t")[1].should == "FALSE"
end
it "should build the correct cookie string" do
cookie_string = @result.first
cookie_string.should ==
"jeff.dallien.net\tFALSE\t/path\tTRUE\t1234567890\tNAME\tVALUE"
end
end
describe "with a cookie that is not marked as secure" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{ 'host_key' => '.dallien.net',
'path' => '/',
'secure' => '0',
'expires_utc' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::ChromeCookieExtractor.new('filename')
@result = @extractor.extract
end
it "should put FALSE in the secure field" do
cookie_string = @result.first
cookie_string.split("\t")[3].should == "FALSE"
end
end
describe "with a cookie that is marked as secure" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{ 'host_key' => '.dallien.net',
'path' => '/',
'secure' => '1',
'expires_utc' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::ChromeCookieExtractor.new('filename')
@result = @extractor.extract
end
it "should put TRUE in the secure field" do
cookie_string = @result.first
cookie_string.split("\t")[3].should == "TRUE"
end
end
end

View File

@ -2,12 +2,32 @@ require File.join(File.dirname(__FILE__), "spec_helper")
describe CookieExtractor::FirefoxCookieExtractor do
before :each do
@fake_cookie_db = double("cookie database", :results_as_hash= => true)
@fake_cookie_db = double("cookie database",
:results_as_hash= => true,
:close => true)
SQLite3::Database.should_receive(:new).
with('filename').
and_return(@fake_cookie_db)
end
describe "opening and closing a sqlite db" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(
{'host' => '.dallien.net',
'path' => '/',
'isSecure' => '0',
'expiry' => '1234567890',
'name' => 'NAME',
'value' => 'VALUE'})
@extractor = CookieExtractor::FirefoxCookieExtractor.new('filename')
end
it "should close the db when finished" do
@fake_cookie_db.should_receive(:close)
@extractor.extract
end
end
describe "with a cookie that has a host starting with a dot" do
before :each do
@fake_cookie_db.should_receive(:execute).and_yield(