firefox-data 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 69f8af3ca8b398607350277c4525297d73cf77c0
4
+ data.tar.gz: 68217687204f43a15abc0d1db16404c0b820fecf
5
+ SHA512:
6
+ metadata.gz: b4796e5e185a385c37052bb3d6f07c312c44c7f73029d8fd8e93dee677a44ffdd3df6800c3f2da5b870a5a5cf47d40f1db559ac13d2d070a0867dca72c8cc5ea
7
+ data.tar.gz: 7ca0eff0b21d59cb366d4f228bd6fec66d3111b8c6935f9665f39889ad5ab8ac1f6f52c6ee058ae4521c43c0706e55ed94fbdebba616a8abcff933e5cc6e44d3
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+
3
+ Permission to use, copy, modify, and distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
4
+ #
5
+ # Permission to use, copy, modify, and distribute this software for any
6
+ # purpose with or without fee is hereby granted, provided that the above
7
+ # copyright notice and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ require 'bundler/setup'
18
+
19
+ require 'firefox'
20
+ require 'nss'
21
+ require 'termios'
22
+ require 'thor'
23
+
24
+ class FirefoxData < Thor
25
+ class_option :profile,
26
+ desc: 'the name of the profile',
27
+ banner: 'NAME',
28
+ type: :string,
29
+ default: 'default',
30
+ aliases: ['p']
31
+
32
+ desc "search-logins REGEXP", "Search for logins in the password database"
33
+ option :password,
34
+ desc: 'ask for the master password',
35
+ type: :boolean,
36
+ aliases: ['w']
37
+ def search_logins(re_string)
38
+ re = Regexp.new(re_string, Regexp::IGNORECASE)
39
+
40
+ index = Firefox::ProfileIndex.new()
41
+ index.load()
42
+
43
+ profile = index.profiles[options[:profile]]
44
+
45
+ password = ''
46
+ if options[:password]
47
+ password = ask_password()
48
+ end
49
+
50
+ NSS.init(profile.path)
51
+ NSS.authenticate(password)
52
+
53
+ profile.load_logins(decrypt: true)
54
+ matches = profile.logins.select {|l| l.hostname.match? re}
55
+ renders = matches.map do |login|
56
+ "hostname #{login.hostname}\n" + \
57
+ "username #{login.username}\n" + \
58
+ "password #{login.password}\n"
59
+ end
60
+ puts renders.join("\n")
61
+ end
62
+
63
+ no_commands do
64
+ def without_term_echo(&block)
65
+ attr = Termios.tcgetattr($stdin)
66
+
67
+ nattr = attr.dup
68
+ nattr.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
69
+ Termios.tcsetattr($stdin, Termios::TCSANOW, nattr)
70
+
71
+ begin
72
+ yield
73
+ ensure
74
+ Termios.tcsetattr($stdin, Termios::TCSANOW, attr)
75
+ end
76
+ end
77
+
78
+ def ask_password()
79
+ printf('Password: ')
80
+
81
+ password = ''
82
+
83
+ without_term_echo() do
84
+ password = $stdin.gets().chomp()
85
+ puts ''
86
+ end
87
+
88
+ password
89
+ end
90
+ end
91
+ end
92
+
93
+ FirefoxData.start(ARGV)
@@ -0,0 +1,21 @@
1
+
2
+ require 'rake'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'firefox-data'
6
+ s.version = '1.0.0'
7
+ s.date = '2017-02-11'
8
+ s.summary = 'A library to extract data from firefox profiles.'
9
+ s.description = 'The firefox-data library extracts various types of ' + \
10
+ 'data from firefox profiles.'
11
+ s.homepage = 'https://github.com/galdor/rb-firefox-data'
12
+ s.license = 'ISC'
13
+ s.author = 'Nicolas Martyanoff'
14
+ s.email = 'khaelin@gmail.com'
15
+
16
+ s.required_ruby_version = '>= 2.4.0'
17
+
18
+ s.files = FileList['firefox-data.gemspec', 'LICENSE',
19
+ 'bin/*.rb', 'lib/**/*.rb']
20
+ s.executables = ['firefox-data']
21
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'bundler/setup'
16
+
17
+ require 'pathname'
18
+
19
+ module Firefox
20
+ ROOT_PATH = Pathname.new("#{Dir.home}/.mozilla/firefox")
21
+ end
22
+
23
+ require 'firefox/profile_index'
24
+ require 'firefox/profile'
25
+ require 'firefox/login'
@@ -0,0 +1,109 @@
1
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'date'
16
+
17
+ require 'json-schema'
18
+
19
+ require 'nss'
20
+
21
+ module Firefox
22
+ class InvalidLogin < StandardError
23
+ end
24
+
25
+ class Login
26
+ JSON_SCHEMA = {
27
+ 'type' => 'object',
28
+ 'required' => ['id', 'hostname',
29
+ 'encryptedUsername', 'encryptedPassword'],
30
+ 'properties' => {
31
+ 'id' => {'type': 'integer'},
32
+ 'hostname' => {'type': 'string'},
33
+ 'httpRealm' => {'type': ['string', 'null']},
34
+ 'formSubmitURL' => {'type': ['string', 'null']},
35
+ 'usernameField' => {'type': ['string', 'null']},
36
+ 'passwordField' => {'type': ['string', 'null']},
37
+ 'encryptedUsername' => {'type': 'string'},
38
+ 'encryptedPassword' => {'type': 'string'},
39
+ 'guid' => {'type': ['string', 'null']},
40
+ 'encType' => {'type': 'integer'},
41
+ 'timeCreated' => {'type': 'integer'},
42
+ 'timeLastUsed' => {'type': 'integer'},
43
+ 'timePasswordChanged' => {'type': 'integer'},
44
+ 'timesUsed' => {'type': 'integer'},
45
+ },
46
+ }
47
+
48
+ attr_accessor :id, :hostname, :http_realm, :form_submit_url,
49
+ :username_field, :password_field,
50
+ :encrypted_username, :encrypted_password, :enc_type,
51
+ :username, :password,
52
+ :guid,
53
+ :time_created, :time_last_used, :time_password_changed,
54
+ :times_used
55
+
56
+ def initialize()
57
+ end
58
+
59
+ def to_s()
60
+ "#<Firefox::Login #{@hostname}>"
61
+ end
62
+
63
+ def inspect()
64
+ to_s()
65
+ end
66
+
67
+ def self.from_json(data)
68
+ # In firefox (checked in the mercurial repository on 2017-02-11),
69
+ # logins.json is updated in
70
+ # toolkit/components/passwordmgr/storage-json.js
71
+
72
+ begin
73
+ JSON::Validator.validate!(JSON_SCHEMA, data)
74
+ rescue JSON::Schema::ValidationError => err
75
+ raise InvalidLogin, "invalid login data: #{err.message}"
76
+ end
77
+
78
+ login = Login.new()
79
+
80
+ to_date = lambda do |timestamp|
81
+ seconds = timestamp / 1000
82
+ milliseconds = timestamp % 1000
83
+ Time.at(seconds, milliseconds).utc()
84
+ end
85
+
86
+ login.id = data['id']
87
+ login.hostname = data['hostname']
88
+ login.http_realm = data['httpRealm']
89
+ login.form_submit_url = data['formSubmitURL']
90
+ login.username_field = data['usernameField']
91
+ login.password_field = data['passwordField']
92
+ login.encrypted_username = data['encryptedUsername']
93
+ login.encrypted_password = data['encryptedPassword']
94
+ login.enc_type = data['encType']
95
+ login.guid = data['guid']
96
+ login.time_created = to_date.(data['timeCreated'])
97
+ login.time_last_used = to_date.(data['timeLastUsed'])
98
+ login.time_password_changed = to_date.(data['timePasswordChanged'])
99
+ login.times_used = data['timesUsed']
100
+
101
+ login
102
+ end
103
+
104
+ def decrypt()
105
+ @username = NSS.decrypt(@encrypted_username)
106
+ @password = NSS.decrypt(@encrypted_password)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'json'
16
+
17
+ module Firefox
18
+ class InvalidProfile < StandardError
19
+ end
20
+
21
+ class Profile
22
+ attr_reader :name, :path, :logins
23
+
24
+ def initialize(name, path)
25
+ @name = name
26
+ @path = path
27
+ @logins = nil
28
+ end
29
+
30
+ def to_s()
31
+ "#<Firefox::Profile #{@name}>"
32
+ end
33
+
34
+ def inspect()
35
+ to_s()
36
+ end
37
+
38
+ def load_logins(decrypt: false)
39
+ path = @path.join('logins.json')
40
+ data = JSON.parse(File.read(path))
41
+ unless data.key? 'logins'
42
+ raise InvalidProfile, "missing 'logins' entry in #{path}"
43
+ end
44
+
45
+ logins = []
46
+ data['logins'].each do |login_data|
47
+ login = Login.from_json(login_data)
48
+ login.decrypt() if decrypt
49
+ logins << login
50
+ end
51
+ @logins = logins
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ module Firefox
16
+ class ProfileIndex
17
+ DEFAULT_PATH = ROOT_PATH.join('profiles.ini')
18
+
19
+ attr_reader :path, :profiles
20
+
21
+ def initialize(path: DEFAULT_PATH)
22
+ @path = path
23
+ @profiles = {}
24
+ end
25
+
26
+ def load()
27
+ sections = []
28
+ section = nil
29
+
30
+ File.open(@path).each do |line|
31
+ if line.match(/^\[([^\]]+)\]/)
32
+ title = $1
33
+ next if title == 'General'
34
+
35
+ section = {}
36
+ sections << section
37
+ elsif !section.nil? && line.match(/^([^=]+)\s*=\s*(.*)/)
38
+ key = $1
39
+ value = $2
40
+
41
+ section[key] = value
42
+ end
43
+ end
44
+
45
+ profiles = {}
46
+ sections.each do |section|
47
+ name = section['Name']
48
+ path = Pathname.new(section['Path'])
49
+ is_relative = section['IsRelative']
50
+
51
+ if is_relative == '1'
52
+ path = ROOT_PATH.join(path)
53
+ end
54
+
55
+ profile = Profile.new(name, path)
56
+ profiles[name] = profile
57
+ end
58
+ @profiles = profiles
59
+ end
60
+
61
+ def profile?(name)
62
+ return @profiles.key? name
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,123 @@
1
+ # Copyright (c) 2017 Nicolas Martyanoff <khaelin@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'ffi'
16
+
17
+ module NSSFFI
18
+ extend FFI::Library
19
+
20
+ class SecItemStr < FFI::Struct
21
+ layout :type, :int,
22
+ :data, :pointer,
23
+ :len, :uint
24
+
25
+ def string()
26
+ self[:data].read_string(self[:len])
27
+ end
28
+ end
29
+
30
+ ffi_lib 'nss3'
31
+
32
+ enum :sec_status, [:wouldblock, -2,
33
+ :failure, -1,
34
+ :success, 0]
35
+
36
+ typedef :int, :pr_bool
37
+ typedef SecItemStr.ptr(), :sec_item
38
+ typedef :int, :sec_item_type
39
+ typedef :pointer, :pl_arena_pool
40
+ typedef :pointer, :pk11_slot_info
41
+
42
+ attach_function :nss_init, 'NSS_Init', [:string], :sec_status
43
+ attach_function :nss_base64_decode_buffer, 'NSSBase64_DecodeBuffer',
44
+ [:pl_arena_pool, :sec_item, :string, :uint], :sec_item
45
+
46
+ attach_function :pk11_get_internal_key_slot, 'PK11_GetInternalKeySlot',
47
+ [], :pk11_slot_info
48
+ attach_function :pk11_free_slot, 'PK11_FreeSlot', [:pk11_slot_info], :void
49
+ attach_function :pk11_check_user_password, 'PK11_CheckUserPassword',
50
+ [:pk11_slot_info, :string], :sec_status
51
+ attach_function :pk11sdr_decrypt, 'PK11SDR_Decrypt',
52
+ [:sec_item, :sec_item, :pointer], :sec_status
53
+
54
+ attach_function :secitem_alloc_item, 'SECITEM_AllocItem',
55
+ [:pl_arena_pool, :sec_item, :uint], :sec_item
56
+ attach_function :secitem_free_item, 'SECITEM_FreeItem',
57
+ [:sec_item, :pr_bool], :void
58
+ end
59
+
60
+ module NSS
61
+ class Error < StandardError
62
+ end
63
+
64
+ def self.init(profile_path)
65
+ res = NSSFFI.nss_init(profile_path.to_s())
66
+ raise NSS::Error, "cannot initialize nss" unless res == :success
67
+ end
68
+
69
+ def self.with_internal_key_slot(&block)
70
+ slot = NSSFFI.pk11_get_internal_key_slot()
71
+ raise NSS::Error, "cannot retrieve internal key slot" if slot.nil?
72
+
73
+ begin
74
+ yield slot
75
+ ensure
76
+ NSSFFI.pk11_free_slot(slot)
77
+ end
78
+ end
79
+
80
+ def self.check_user_password(slot, password)
81
+ res = NSSFFI.pk11_check_user_password(slot, password)
82
+ raise NSS::Error, "authentication failed" unless res == :success
83
+ end
84
+
85
+ def self.authenticate(password)
86
+ with_internal_key_slot do |slot|
87
+ check_user_password(slot, password)
88
+ end
89
+ end
90
+
91
+ def self.base64_decode(str, &block)
92
+ str_item = NSSFFI.nss_base64_decode_buffer(nil, nil, str, str.bytesize())
93
+ raise NSS::Error, "cannot decode base64 string" if str_item.nil?
94
+
95
+ begin
96
+ yield str_item
97
+ ensure
98
+ NSSFFI.secitem_free_item(str_item, 1)
99
+ end
100
+ end
101
+
102
+ def self.decrypt(b64str)
103
+ base64_decode(b64str) do |str_item|
104
+ with_sec_item do |res_item|
105
+ res = NSSFFI.pk11sdr_decrypt(str_item, res_item, nil)
106
+ raise NSS::Error, "cannot decrypt string" unless res == :success
107
+
108
+ res_item.string()
109
+ end
110
+ end
111
+ end
112
+
113
+ def self.with_sec_item(&block)
114
+ item = NSSFFI.secitem_alloc_item(nil, nil, 0)
115
+ raise NSS::Error, "cannot allocate sec item" if item.nil?
116
+
117
+ begin
118
+ yield item
119
+ ensure
120
+ NSSFFI.secitem_free_item(item, 1)
121
+ end
122
+ end
123
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firefox-data
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicolas Martyanoff
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: The firefox-data library extracts various types of data from firefox
14
+ profiles.
15
+ email: khaelin@gmail.com
16
+ executables:
17
+ - firefox-data
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - bin/firefox-data
23
+ - firefox-data.gemspec
24
+ - lib/firefox.rb
25
+ - lib/firefox/login.rb
26
+ - lib/firefox/profile.rb
27
+ - lib/firefox/profile_index.rb
28
+ - lib/nss.rb
29
+ homepage: https://github.com/galdor/rb-firefox-data
30
+ licenses:
31
+ - ISC
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.4.0
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.6.8
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: A library to extract data from firefox profiles.
53
+ test_files: []