Compare commits
18 Commits
Author | SHA1 | Date |
---|---|---|
Jeff Dallien | 115d986044 | |
Jeff Dallien | 4f581183df | |
Jeff Dallien | 36ad9f11b2 | |
Jeff Dallien | eda67c7ea8 | |
Ben Eills | e8a4ab5f22 | |
Ben Eills | 3217c96dd6 | |
Jeff Dallien | 6423b9bcb5 | |
Jeff Dallien | 74b464b40c | |
Jeff Dallien | 54e805f17f | |
Jeff Dallien | ea196c2e4a | |
Jeff Dallien | dbd7ca9299 | |
Jeff Dallien | c8d8879153 | |
Jeff Dallien | 58775f8e06 | |
Jeff Dallien | 7e9e160f17 | |
Jeff Dallien | f764e35095 | |
Jeff Dallien | 2856bd60fe | |
Jeff Dallien | 61d8f6e13b | |
Jeff Dallien | 64d118cc85 |
|
@ -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.
|
17
README.md
17
README.md
|
@ -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 ###
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module CookieExtractor
|
||||
VERSION = "0.0.1"
|
||||
VERSION = "0.2.0"
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue