From 64d118cc850874b3822bdb513ae86334cf137ffc Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 21 Feb 2012 19:22:56 -0500 Subject: [PATCH 01/18] Add Chrome/Chromium cookie extractor. Same as the Firefox one, but with different table and field names. Will refactor them both to remove duplication. --- .../chrome_cookie_extractor.rb | 43 +++++++ spec/chrome_cookie_extractor_spec.rb | 106 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 lib/cookie_extractor/chrome_cookie_extractor.rb create mode 100644 spec/chrome_cookie_extractor_spec.rb diff --git a/lib/cookie_extractor/chrome_cookie_extractor.rb b/lib/cookie_extractor/chrome_cookie_extractor.rb new file mode 100644 index 0000000..ba94331 --- /dev/null +++ b/lib/cookie_extractor/chrome_cookie_extractor.rb @@ -0,0 +1,43 @@ +require 'sqlite3' + +module CookieExtractor + class ChromeCookieExtractor + + 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 + @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 + end + end +end diff --git a/spec/chrome_cookie_extractor_spec.rb b/spec/chrome_cookie_extractor_spec.rb new file mode 100644 index 0000000..98c343f --- /dev/null +++ b/spec/chrome_cookie_extractor_spec.rb @@ -0,0 +1,106 @@ +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) + SQLite3::Database.should_receive(:new). + with('filename'). + and_return(@fake_cookie_db) + 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 From 61d8f6e13b7f0cfab5caabb72c6d7ec4fd3f341e Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 21 Feb 2012 19:24:07 -0500 Subject: [PATCH 02/18] Need to include the new file here. --- lib/cookie_extractor.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cookie_extractor.rb b/lib/cookie_extractor.rb index 20f68f4..013c4ba 100644 --- a/lib/cookie_extractor.rb +++ b/lib/cookie_extractor.rb @@ -1,5 +1,6 @@ require "cookie_extractor/version" require "cookie_extractor/firefox_cookie_extractor" +require "cookie_extractor/chrome_cookie_extractor" module CookieExtractor end From 2856bd60fe184d0bc4384c67f20d82b4cc392351 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 21 Feb 2012 19:53:53 -0500 Subject: [PATCH 03/18] Move the two exactly duplicated methods into a module. --- lib/cookie_extractor.rb | 1 + .../chrome_cookie_extractor.rb | 17 +---------------- lib/cookie_extractor/common.rb | 19 +++++++++++++++++++ .../firefox_cookie_extractor.rb | 17 +---------------- 4 files changed, 22 insertions(+), 32 deletions(-) create mode 100644 lib/cookie_extractor/common.rb diff --git a/lib/cookie_extractor.rb b/lib/cookie_extractor.rb index 013c4ba..b9cd79e 100644 --- a/lib/cookie_extractor.rb +++ b/lib/cookie_extractor.rb @@ -1,4 +1,5 @@ require "cookie_extractor/version" +require "cookie_extractor/common" require "cookie_extractor/firefox_cookie_extractor" require "cookie_extractor/chrome_cookie_extractor" diff --git a/lib/cookie_extractor/chrome_cookie_extractor.rb b/lib/cookie_extractor/chrome_cookie_extractor.rb index ba94331..61901f5 100644 --- a/lib/cookie_extractor/chrome_cookie_extractor.rb +++ b/lib/cookie_extractor/chrome_cookie_extractor.rb @@ -2,6 +2,7 @@ require 'sqlite3' module CookieExtractor class ChromeCookieExtractor + include Common def initialize(cookie_file) @cookie_file = cookie_file @@ -23,21 +24,5 @@ module CookieExtractor 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 - end end end diff --git a/lib/cookie_extractor/common.rb b/lib/cookie_extractor/common.rb new file mode 100644 index 0000000..a1af08f --- /dev/null +++ b/lib/cookie_extractor/common.rb @@ -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 diff --git a/lib/cookie_extractor/firefox_cookie_extractor.rb b/lib/cookie_extractor/firefox_cookie_extractor.rb index 5072464..e665849 100644 --- a/lib/cookie_extractor/firefox_cookie_extractor.rb +++ b/lib/cookie_extractor/firefox_cookie_extractor.rb @@ -2,6 +2,7 @@ require 'sqlite3' module CookieExtractor class FirefoxCookieExtractor + include Common def initialize(cookie_file) @cookie_file = cookie_file @@ -23,21 +24,5 @@ module CookieExtractor 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 - end end end From f764e35095ea8e7aefd1171177fa97e6690b8a1d Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:05:07 -0500 Subject: [PATCH 04/18] Detector browser that created the supplied cookie db by table names present. --- bin/cookie_extractor | 5 +-- lib/cookie_extractor.rb | 1 + lib/cookie_extractor/browser_detector.rb | 29 ++++++++++++++++ spec/browser_detector_spec.rb | 43 ++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 lib/cookie_extractor/browser_detector.rb create mode 100644 spec/browser_detector_spec.rb diff --git a/bin/cookie_extractor b/bin/cookie_extractor index 16b36a4..daacc64 100755 --- a/bin/cookie_extractor +++ b/bin/cookie_extractor @@ -2,10 +2,11 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cookie_extractor")) -# TODO: detect firefox or chrome input file and/or locate it automatically +# TODO: Locate cookie dbs automatically filename = ARGV.first if filename - puts CookieExtractor::FirefoxCookieExtractor.new(filename).extract.join("\n") + extractor = CookieExtractor::BrowserDetector.new_extractor(filename) + puts extractor.extract.join("\n") else puts "Usage: cookie_extractor /path/to/cookies.sqlite" end diff --git a/lib/cookie_extractor.rb b/lib/cookie_extractor.rb index b9cd79e..8752ca1 100644 --- a/lib/cookie_extractor.rb +++ b/lib/cookie_extractor.rb @@ -2,6 +2,7 @@ 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 diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb new file mode 100644 index 0000000..48e870d --- /dev/null +++ b/lib/cookie_extractor/browser_detector.rb @@ -0,0 +1,29 @@ +module CookieExtractor + class BrowserDetector + + def self.new_extractor(db_filename) + browser = detect_browser(db_filename) + if browser + CookieExtractor.const_get("#{browser}CookieExtractor").new(db_filename) + else + raise "Could not detect browser type." + end + 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 + end +end diff --git a/spec/browser_detector_spec.rb b/spec/browser_detector_spec.rb new file mode 100644 index 0000000..179cd5b --- /dev/null +++ b/spec/browser_detector_spec.rb @@ -0,0 +1,43 @@ +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 From 7e9e160f179d98671a7260661336bcab4bda5392 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:19:01 -0500 Subject: [PATCH 05/18] Should close the db after extracting the cookies from it. --- .../chrome_cookie_extractor.rb | 7 +++--- .../firefox_cookie_extractor.rb | 7 +++--- spec/chrome_cookie_extractor_spec.rb | 22 ++++++++++++++++++- spec/firefox_cookie_extractor_spec.rb | 22 ++++++++++++++++++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/lib/cookie_extractor/chrome_cookie_extractor.rb b/lib/cookie_extractor/chrome_cookie_extractor.rb index 61901f5..6e11004 100644 --- a/lib/cookie_extractor/chrome_cookie_extractor.rb +++ b/lib/cookie_extractor/chrome_cookie_extractor.rb @@ -11,9 +11,9 @@ module CookieExtractor def extract db = SQLite3::Database.new @cookie_file db.results_as_hash = true - @result = [] + result = [] db.execute("SELECT * FROM cookies") do |row| - @result << [ row['host_key'], + result << [ row['host_key'], true_false_word(is_domain_wide(row['host_key'])), row['path'], true_false_word(row['secure']), @@ -22,7 +22,8 @@ module CookieExtractor row['value'] ].join("\t") end - @result + db.close + result end end end diff --git a/lib/cookie_extractor/firefox_cookie_extractor.rb b/lib/cookie_extractor/firefox_cookie_extractor.rb index e665849..68d1252 100644 --- a/lib/cookie_extractor/firefox_cookie_extractor.rb +++ b/lib/cookie_extractor/firefox_cookie_extractor.rb @@ -11,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']), @@ -22,7 +22,8 @@ module CookieExtractor row['value'] ].join("\t") end - @result + db.close + result end end end diff --git a/spec/chrome_cookie_extractor_spec.rb b/spec/chrome_cookie_extractor_spec.rb index 98c343f..d9a13a4 100644 --- a/spec/chrome_cookie_extractor_spec.rb +++ b/spec/chrome_cookie_extractor_spec.rb @@ -2,12 +2,32 @@ 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) + @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( diff --git a/spec/firefox_cookie_extractor_spec.rb b/spec/firefox_cookie_extractor_spec.rb index fb86adf..8fd4ae5 100644 --- a/spec/firefox_cookie_extractor_spec.rb +++ b/spec/firefox_cookie_extractor_spec.rb @@ -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( From 58775f8e06cf09bc23b63d33c6184ff34822e3b2 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:33:25 -0500 Subject: [PATCH 06/18] Add some more useful error messages in the common situations (file doesn't exist, isn't a sqlite db) --- bin/cookie_extractor | 18 ++++++++++++++---- lib/cookie_extractor/browser_detector.rb | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bin/cookie_extractor b/bin/cookie_extractor index daacc64..7b9128c 100755 --- a/bin/cookie_extractor +++ b/bin/cookie_extractor @@ -4,9 +4,19 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cookie_ # TODO: Locate cookie dbs automatically filename = ARGV.first -if filename - extractor = CookieExtractor::BrowserDetector.new_extractor(filename) - puts extractor.extract.join("\n") -else +unless filename puts "Usage: cookie_extractor /path/to/cookies.sqlite" + exit +end + +if File.exists?(filename) + begin + extractor = CookieExtractor::BrowserDetector.new_extractor(filename) + puts extractor.extract.join("\n") + rescue SQLite3::NotADatabaseException, + CookieExtractor::BrowserNotDetectedException + puts "Error: File '#{filename}' is not a Firefox or Chrome cookie database" + end +else + puts "Error: File '#{filename}' does not exist" end diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb index 48e870d..dcf9468 100644 --- a/lib/cookie_extractor/browser_detector.rb +++ b/lib/cookie_extractor/browser_detector.rb @@ -1,4 +1,6 @@ module CookieExtractor + class BrowserNotDetectedException < Exception; end + class BrowserDetector def self.new_extractor(db_filename) From c8d8879153c258cf30ae63ac4aa3d7bc4676551b Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:35:52 -0500 Subject: [PATCH 07/18] Need to actually raise custom exception class --- lib/cookie_extractor/browser_detector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb index dcf9468..b64dfd9 100644 --- a/lib/cookie_extractor/browser_detector.rb +++ b/lib/cookie_extractor/browser_detector.rb @@ -8,7 +8,7 @@ module CookieExtractor if browser CookieExtractor.const_get("#{browser}CookieExtractor").new(db_filename) else - raise "Could not detect browser type." + raise BrowserNotDetectedException, "Could not detect browser type." end end From dbd7ca92997eb07df605e166ccfcbbe33c71fb5b Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:45:17 -0500 Subject: [PATCH 08/18] Update 'usage' message to reflect Chrome support and increase version number to 0.1.0. --- bin/cookie_extractor | 3 ++- lib/cookie_extractor/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/cookie_extractor b/bin/cookie_extractor index 7b9128c..6f808b8 100755 --- a/bin/cookie_extractor +++ b/bin/cookie_extractor @@ -5,7 +5,8 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cookie_ # TODO: Locate cookie dbs automatically filename = ARGV.first unless filename - puts "Usage: cookie_extractor /path/to/cookies.sqlite" + puts "Usage: (Firefox) cookie_extractor /path/to/cookies.sqlite" + puts " (Chrome) cookie_extractor /path/to/Cookies" exit end diff --git a/lib/cookie_extractor/version.rb b/lib/cookie_extractor/version.rb index 56fb8d9..c35d403 100644 --- a/lib/cookie_extractor/version.rb +++ b/lib/cookie_extractor/version.rb @@ -1,3 +1,3 @@ module CookieExtractor - VERSION = "0.0.1" + VERSION = "0.1.0" end From ea196c2e4a1e818fdda2f0e103dd44639a58112c Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 19:55:21 -0500 Subject: [PATCH 09/18] Update gemspec to reflect Chrome support --- cookie_extractor.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cookie_extractor.gemspec b/cookie_extractor.gemspec index a53359a..54c7227 100644 --- a/cookie_extractor.gemspec +++ b/cookie_extractor.gemspec @@ -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 From 54e805f17f0367f1eef4f015709b838a8a40995d Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 20:05:13 -0500 Subject: [PATCH 10/18] Update README to mention Chrome. --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fff51ee..65e4057 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ 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 ### @@ -11,6 +11,11 @@ gem install cookie_extractor cookie_extractor /path/to/firefox/cookies.sqlite > cookies.txt +Typical locations for the cookies file on Linux are: + * Firefox: ~/.mozilla/firefox//cookies.sqlite + * Chrome: ~/.config/google-chrome/Default/Cookies + * Chromium: ~/.config/chromium/Default/Cookies + ### License ### Copyright (c) 2012 Jeff Dallien From 74b464b40c1d4632ab960be0ceec3c49e9262972 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 20:07:02 -0500 Subject: [PATCH 11/18] Fix stupid markdown in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 65e4057..9a21b91 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ gem install cookie_extractor cookie_extractor /path/to/firefox/cookies.sqlite > cookies.txt Typical locations for the cookies file on Linux are: + * Firefox: ~/.mozilla/firefox//cookies.sqlite * Chrome: ~/.config/google-chrome/Default/Cookies * Chromium: ~/.config/chromium/Default/Cookies From 6423b9bcb547551214976d95279b163e9ba49d41 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 22 Feb 2012 20:09:36 -0500 Subject: [PATCH 12/18] Again, fix markdown in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a21b91..420b434 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ cookie_extractor /path/to/firefox/cookies.sqlite > cookies.txt Typical locations for the cookies file on Linux are: - * Firefox: ~/.mozilla/firefox//cookies.sqlite + * Firefox: ~/.mozilla/firefox/(profile directory)/cookies.sqlite * Chrome: ~/.config/google-chrome/Default/Cookies * Chromium: ~/.config/chromium/Default/Cookies From 3217c96dd6d07bde15e4f5a471c96c1ffe523d5f Mon Sep 17 00:00:00 2001 From: Ben Eills Date: Thu, 27 Jun 2013 20:51:41 +0100 Subject: [PATCH 13/18] 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 --- README.md | 15 ++++--- bin/cookie_extractor | 54 +++++++++++++++++------- lib/cookie_extractor/browser_detector.rb | 43 +++++++++++++++++++ 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 420b434..2ba69f5 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,22 @@ Extract cookies from Firefox, Chrome or Chromium sqlite cookie stores into a wge ### 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 + * Firefox: *~/.mozilla/firefox/(profile directory)/cookies.sqlite* + * Chrome: *~/.config/google-chrome/Default/Cookies* + * Chromium: *~/.config/chromium/Default/Cookies* ### License ### diff --git a/bin/cookie_extractor b/bin/cookie_extractor index 6f808b8..95a25d6 100755 --- a/bin/cookie_extractor +++ b/bin/cookie_extractor @@ -2,22 +2,46 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cookie_extractor")) -# TODO: Locate cookie dbs automatically -filename = ARGV.first -unless filename - puts "Usage: (Firefox) cookie_extractor /path/to/cookies.sqlite" - puts " (Chrome) cookie_extractor /path/to/Cookies" + +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 -if File.exists?(filename) - begin - extractor = CookieExtractor::BrowserDetector.new_extractor(filename) - puts extractor.extract.join("\n") - rescue SQLite3::NotADatabaseException, - CookieExtractor::BrowserNotDetectedException - puts "Error: File '#{filename}' is not a Firefox or Chrome cookie database" - end -else - puts "Error: File '#{filename}' does not exist" + +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 + + diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb index b64dfd9..de67f05 100644 --- a/lib/cookie_extractor/browser_detector.rb +++ b/lib/cookie_extractor/browser_detector.rb @@ -1,7 +1,46 @@ 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 + full_cookie_locations = @cookie_locations.select { |browser, path| + !Dir.glob(File.expand_path(path)).empty? + }.sort_by { |browser, path| + File.mtime(Dir.glob(File.expand_path(path)).first) + }.reverse.each { |tuple| + browser = tuple.first + 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) @@ -12,6 +51,10 @@ module CookieExtractor end end + def self.supported_browsers + @cookie_locations.keys + end + def self.detect_browser(db_filename) db = SQLite3::Database.new(db_filename) browser = From e8a4ab5f22aa5d332fc072ed8795953993f45749 Mon Sep 17 00:00:00 2001 From: Ben Eills Date: Mon, 1 Jul 2013 16:59:25 +0100 Subject: [PATCH 14/18] Added RSpec tests, although these are untested and should be checked over by someone who actually knows anything about RSpec before being pulled --- lib/cookie_extractor/browser_detector.rb | 3 ++- spec/browser_detector_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb index de67f05..17cef26 100644 --- a/lib/cookie_extractor/browser_detector.rb +++ b/lib/cookie_extractor/browser_detector.rb @@ -4,13 +4,14 @@ module CookieExtractor class NoCookieFileFoundException < Exception; end class BrowserDetector + attr_reader :cookie_locations + @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 diff --git a/spec/browser_detector_spec.rb b/spec/browser_detector_spec.rb index 179cd5b..c01cd4c 100644 --- a/spec/browser_detector_spec.rb +++ b/spec/browser_detector_spec.rb @@ -40,4 +40,26 @@ describe CookieExtractor::BrowserDetector, "determining the correct extractor to extractor.instance_of?(CookieExtractor::ChromeCookieExtractor).should be_true end end + + 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 + Dir.stub!(:glob).and_return(CookieExtractor::BrowserDetector.cookie_locations.values) + end + + it "should return a ChromeCookieExtractor or FirefoxCookieExtractor" do + lambda { CookieExtractor::BrowserDetector.guess }. + should be_kind_of(CookieExtractor::Common) + end + end end From eda67c7ea8b059dfbf6517d401c20cb43d9c6977 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 2 Jul 2013 23:07:52 -0400 Subject: [PATCH 15/18] Fix load path. --- lib/cookie_extractor.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cookie_extractor.rb b/lib/cookie_extractor.rb index 8752ca1..99a1e71 100644 --- a/lib/cookie_extractor.rb +++ b/lib/cookie_extractor.rb @@ -1,3 +1,6 @@ +$:.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" From 36ad9f11b28d45512c3bfe0ff52375c24eaea805 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 2 Jul 2013 23:13:22 -0400 Subject: [PATCH 16/18] Fix up browser detector spec and do some refactoring for clarity. --- lib/cookie_extractor/browser_detector.rb | 25 +++++++++++++----------- spec/browser_detector_spec.rb | 25 +++++++++++++++++++----- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/cookie_extractor/browser_detector.rb b/lib/cookie_extractor/browser_detector.rb index 17cef26..8700344 100644 --- a/lib/cookie_extractor/browser_detector.rb +++ b/lib/cookie_extractor/browser_detector.rb @@ -4,9 +4,7 @@ module CookieExtractor class NoCookieFileFoundException < Exception; end class BrowserDetector - attr_reader :cookie_locations - - @cookie_locations = { + COOKIE_LOCATIONS = { "chrome" => "~/.config/google-chrome/Default/Cookies", "chromium" => "~/.config/chromium/Default/Cookies", "firefox" => "~/.mozilla/firefox/*.default/cookies.sqlite" @@ -15,12 +13,7 @@ module CookieExtractor # Returns the extractor of the most recently used browser's cookies # or raise NoCookieFileFoundException if there are no cookies def self.guess - full_cookie_locations = @cookie_locations.select { |browser, path| - !Dir.glob(File.expand_path(path)).empty? - }.sort_by { |browser, path| - File.mtime(Dir.glob(File.expand_path(path)).first) - }.reverse.each { |tuple| - browser = tuple.first + most_recently_used_detected_browsers.each { |browser, path| begin extractor = self.browser_extractor(browser) rescue BrowserNotDetectedException, NoCookieFileFoundException @@ -36,7 +29,7 @@ module CookieExtractor # 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])) + 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 @@ -53,7 +46,7 @@ module CookieExtractor end def self.supported_browsers - @cookie_locations.keys + COOKIE_LOCATIONS.keys end def self.detect_browser(db_filename) @@ -71,5 +64,15 @@ module CookieExtractor 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 diff --git a/spec/browser_detector_spec.rb b/spec/browser_detector_spec.rb index c01cd4c..788a371 100644 --- a/spec/browser_detector_spec.rb +++ b/spec/browser_detector_spec.rb @@ -40,7 +40,9 @@ describe CookieExtractor::BrowserDetector, "determining the correct extractor to 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([]) @@ -54,12 +56,25 @@ describe CookieExtractor::BrowserDetector, "determining the correct extractor to describe "when multiple cookie files are found in the standard locations" do before :each do - Dir.stub!(:glob).and_return(CookieExtractor::BrowserDetector.cookie_locations.values) + cookie_locations = CookieExtractor::BrowserDetector::COOKIE_LOCATIONS + Dir.stub!(:glob).and_return([cookie_locations['chrome']], + [], + [cookie_locations['firefox']]) end - it "should return a ChromeCookieExtractor or FirefoxCookieExtractor" do - lambda { CookieExtractor::BrowserDetector.guess }. - should be_kind_of(CookieExtractor::Common) + 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 end From 4f581183df7a5323d55069a861e27aa9bc8d7ac3 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Tue, 2 Jul 2013 23:25:47 -0400 Subject: [PATCH 17/18] Increment version now that browser auto detection is merged in. --- lib/cookie_extractor/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cookie_extractor/version.rb b/lib/cookie_extractor/version.rb index c35d403..424c034 100644 --- a/lib/cookie_extractor/version.rb +++ b/lib/cookie_extractor/version.rb @@ -1,3 +1,3 @@ module CookieExtractor - VERSION = "0.1.0" + VERSION = "0.2.0" end From 115d986044d9d1fca4fdb52a4aa7d16c9fac1570 Mon Sep 17 00:00:00 2001 From: Jeff Dallien Date: Wed, 3 Jul 2013 22:09:56 -0400 Subject: [PATCH 18/18] Add CHANGES file to record differences between versions --- CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..0900169 --- /dev/null +++ b/CHANGES.md @@ -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.