firefox-json 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 134532c5c458ea1344c83744b89887468b180a160407d60d1a2f55ab9fa10bee
4
+ data.tar.gz: 4154350cbcc37b82a240be051c930e61479e7a6cfcc5a938d36d47a980983902
5
+ SHA512:
6
+ metadata.gz: 5389d7be95eae2054691c170202ea8f847888e10986a9c64d3d39b73e0450ac517d7322fb2afb151cf63527ef5a9a09898cb3f2fd0966830f42244506ccc17b2
7
+ data.tar.gz: f8147fd5b4751f080ca6178e6495473337ce8019aa302032226fa105b7614bd7767ea1664da44a6220297e6d3f18ee012fd0607423b71704dcdcaf6430c34b06
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2018 Boris Peterbarg <reist.sa@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,76 @@
1
+ # FirefoxJson
2
+
3
+ `firefox-json` is a library to view and/or manipulate the json files in Firefox profiles.
4
+
5
+ It's capable of reading both the older .js and the new .jsonlz4 files.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'firefox-json'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install firefox-json
22
+
23
+ ## Usage
24
+
25
+ For now, all the library can do is manipulate sessions - the window and tab collections.
26
+
27
+ ### Getting a session
28
+
29
+ ```ruby
30
+ FirefoxJson.available_profiles
31
+ => ["default", "some-other-profile"]
32
+ session = FirefoxJson.load_profile('default').session
33
+ => #<Firefox::Session# windows=1 closed=1>
34
+ ```
35
+
36
+ ### Digging in
37
+
38
+ ```ruby
39
+ session.closed_windows
40
+ => [#<Firefox::Window closed! tabs=1 selected="Some Title">]
41
+ _[0].tabs
42
+ => [#<Firefox::Tab entries=1 selected="Some Title">]
43
+ e = _[0].entries[0]
44
+ => #<Firefox::Entry http://www.site.com/a-page>
45
+ e.public_methods - Object.methods
46
+ => [:url, :title, :referrer, :domain, :dump, :path, :path=, :save, :required_key, :reload]
47
+ e.referrer
48
+ => "https://www.google.com/long-search-string"
49
+ e.domain
50
+ => "www.site.com"
51
+ ```
52
+
53
+ ### Recovering a closed window
54
+
55
+ ```ruby
56
+ session.windows << session.closed_windows.shift # you can add `.tap { |w| w.is_closed = false }` to remove the `closed!` part
57
+ => [#<Firefox::Window tabs=15 closed=2 selected="Google">, #<Firefox::Window closed! tabs=3 selected="Ruby Programming Language">]
58
+ session
59
+ => #<Firefox::Session# windows=2>
60
+ session.save # or session.save('some_other_file.js')
61
+ ```
62
+
63
+ ## Development
64
+
65
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+
67
+ To install this gem onto your local machine, run `bundle exec rake install`.
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/reist/firefox-json.
72
+
73
+ ## License
74
+
75
+ The gem is available as open source under the terms of the [ISC License](https://opensource.org/licenses/ISC).
76
+
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "firefox-json"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'firefox-json/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'firefox-json'
7
+ spec.version = FirefoxJson::VERSION
8
+ spec.date = '2018-03-20'
9
+ spec.summary = %q{Read and manipulate Firefox's json files.}
10
+ spec.homepage = 'https://github.com/reist/firefox-json'
11
+ spec.license = 'ISC'
12
+ spec.author = 'Boris Peterbarg'
13
+ spec.email = 'boris.sa@gmail.com'
14
+
15
+ spec.required_ruby_version = '>= 2.3.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.16'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.2'
27
+ spec.add_runtime_dependency 'oj', '~> 3.5'
28
+ spec.add_runtime_dependency 'inifile', '~> 3.0'
29
+ spec.add_runtime_dependency 'extlz4', '~> 0.2.5'
30
+ end
@@ -0,0 +1,13 @@
1
+ require "firefox-json/version"
2
+ require "firefox-json/profiles"
3
+
4
+ module FirefoxJson
5
+ def self.available_profiles
6
+ Profiles.new.list
7
+ end
8
+
9
+ def self.load_profile(name)
10
+ profiles = Profiles.new
11
+ profiles[name]
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ require 'extlz4'
2
+ require 'oj'
3
+
4
+ module FirefoxJson
5
+ # Saves and restores files
6
+ module JsFile
7
+ MOZ_ID = "mozLz40\0"
8
+ MOZ_PREFIX = MOZ_ID.size + 4
9
+ MAX_SIZE = 1 << 32 - 1
10
+ OFFSETS = [1 << 24, 1 << 16, 1 << 8, 1]
11
+
12
+ class FileTooLarge < RuntimeError; end
13
+
14
+ def self.load_file(path)
15
+ load(IO.read(path))
16
+ end
17
+
18
+ def self.load(string)
19
+ if string[0, MOZ_ID.size] == MOZ_ID
20
+ string = decompress(string)
21
+ end
22
+ Oj.load(string, mode: :strict)
23
+ end
24
+
25
+ def self.save(path, data)
26
+ string = Oj.dump(data, mode: :strict)
27
+ if path.end_with?('jsonlz4')
28
+ File.open(path, 'wb') do |file|
29
+ file.write(compress(string))
30
+ end
31
+ else
32
+ File.open(path, 'w') do |file|
33
+ file.write(string)
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.decompress(string)
39
+ string.force_encoding(Encoding::BINARY)
40
+ size = string[8, 4].bytes.zip(OFFSETS.reverse).map do |byte, offset|
41
+ byte * offset
42
+ end.sum
43
+ string = LZ4.block_decode(string[MOZ_PREFIX..-1])
44
+ if string.bytesize != size
45
+ raise "Expected size #{size} != #{string.bytesize}"
46
+ end
47
+ string.force_encoding(Encoding::UTF_8)
48
+ end
49
+
50
+ def self.compress(string)
51
+ size = string.bytesize
52
+ if size > MAX_SIZE
53
+ raise FileTooLarge, 'Content over 4GB!'
54
+ end
55
+ sstr = OFFSETS.map do |offset|
56
+ part = size / offset
57
+ size %= offset
58
+ part
59
+ end.reverse.pack('c*')
60
+ MOZ_ID + sstr + LZ4.block_encode(string)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,48 @@
1
+ require 'inifile'
2
+ require 'firefox-json/session'
3
+
4
+ module FirefoxJson
5
+ # Access to the profiles.ini file that links to all defined profiles and their locations
6
+ class Profiles
7
+ # Collects methods to access a single profile's session file
8
+ class Profile
9
+ def initialize(data, ff_path)
10
+ @data = data
11
+ @ff_path = ff_path
12
+ end
13
+
14
+ def path
15
+ @path ||= @data['IsRelative'] == 1 ? File.join(@ff_path, @data['Path']) : @data['Path']
16
+ end
17
+
18
+ def session
19
+ Session.default(path)
20
+ end
21
+
22
+ def recovery_session
23
+ Session.recovery(path)
24
+ end
25
+ end
26
+
27
+ def initialize(path = File.join(Dir.home, '.mozilla/firefox'))
28
+ @ff_path = path
29
+ data = IniFile.load(File.join(@ff_path, 'profiles.ini'))
30
+ p_sections = data.sections.select {|section| section.start_with?('Profile')}
31
+ @profile_map = p_sections.reduce({}) do |hash, section|
32
+ profile = data[section]
33
+ hash[profile['Name'].freeze] = profile.freeze
34
+ hash
35
+ end
36
+ end
37
+
38
+ def list
39
+ @profile_map.keys.dup
40
+ end
41
+
42
+ def [](name)
43
+ if @profile_map.key?(name)
44
+ Profile.new(@profile_map[name], @ff_path)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,302 @@
1
+ require 'firefox-json/js_file'
2
+
3
+ module FirefoxJson
4
+ module Session
5
+ class Base
6
+ attr_accessor :path
7
+ @@inherited = []
8
+
9
+ def initialize data, path: nil
10
+ setup data
11
+ @path = path
12
+ end
13
+
14
+ def inspect
15
+ to_s
16
+ end
17
+
18
+ def eql? object
19
+ object.is_a?(self.class) && hash == object.hash
20
+ end
21
+
22
+ def setup data
23
+ unless data && data[required_key]
24
+ raise ArgumentError, "Not a Firefox #{self.class.name.downcase} - missing #{required_key} key"
25
+ end
26
+ @data = data
27
+ end
28
+ protected :setup
29
+
30
+ def reload
31
+ raise ArgumentError, 'Path not given' unless @path
32
+ @data = JsFile.load_file(@path)
33
+ end
34
+
35
+ def dump
36
+ @data
37
+ end
38
+
39
+ def save path=nil
40
+ path ||= @path
41
+ raise ArgumentError, 'Path not given' unless path
42
+ JsFile.save(path, dump)
43
+ end
44
+
45
+ def self.inherited klass
46
+ @@inherited << klass
47
+ end
48
+
49
+ def self.children
50
+ @@inherited
51
+ end
52
+
53
+ def self.choose_for data
54
+ children.find { |klass| data.key? klass.required_key }
55
+ end
56
+
57
+ def self.mattr_accessor name
58
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
59
+ def self.#{name}
60
+ @#{name}
61
+ end
62
+ def #{name}
63
+ self.class.#{name}
64
+ end
65
+ CODE
66
+ end
67
+
68
+ def self.required_key= key
69
+ @required_key = key.freeze
70
+ end
71
+ mattr_accessor :required_key
72
+
73
+ def self.set_collection item_class, index_key, with_closed = false
74
+ include Collection
75
+ @index_key = index_key
76
+ @item_class = item_class
77
+ base_key_name = item_class.name.split('::')[-1].sub(/y$/, 'ie') + 's'
78
+ self.required_key = base_key_name.downcase
79
+ define_method @required_key do
80
+ @collection
81
+ end
82
+ if with_closed
83
+ @closed_key = "_closed#{base_key_name}"
84
+ define_method "closed_#{required_key}" do
85
+ @closed_collection
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Management of collections, closed items and the item in focus
92
+ module Collection
93
+ def self.included target
94
+ target.send(:attr_reader, :selected_idx)
95
+ [:item_class, :closed_key, :index_key].each do |accessor|
96
+ target.mattr_accessor accessor
97
+ end
98
+ end
99
+
100
+ def setup data
101
+ super
102
+ @collection = convert required_key, false
103
+ if closed_key
104
+ @closed_collection = convert closed_key, true
105
+ end
106
+ @selected_idx = @data[index_key]
107
+ sanify_selected_idx
108
+ end
109
+
110
+ def convert key, is_closed
111
+ @data[key].map {|hash| item_class.new(hash, is_closed)}
112
+ end
113
+
114
+ def selected_idx= idx
115
+ if send(required_key).size >= idx
116
+ @selected_idx = idx
117
+ else
118
+ @selected_idx
119
+ end
120
+ end
121
+
122
+ def sanify_selected_idx
123
+ if !@selected_idx || @selected_idx > send(required_key).size
124
+ reset_selected_idx
125
+ else
126
+ @selected_idx
127
+ end
128
+ end
129
+
130
+ def reset_selected_idx
131
+ @selected_idx = send(required_key).size
132
+ end
133
+
134
+ def selected
135
+ send(required_key)[@selected_idx-1]
136
+ end
137
+
138
+ def dump
139
+ @data[index_key] = sanify_selected_idx
140
+ @data[required_key] = @collection.map(&:dump)
141
+ if closed_key
142
+ @data[closed_key] = @closed_collection.map(&:dump)
143
+ end
144
+ super
145
+ end
146
+ end
147
+
148
+ # The base of the session - a specific viewed site
149
+ class Entry < Base
150
+ attr_reader :url, :title, :referrer
151
+ self.required_key = 'url'
152
+
153
+ def initialize data, _closed
154
+ setup data
155
+ @url = data['url']
156
+ @title = data['title']
157
+ @referrer = data['referrer']
158
+ @id = data['id']
159
+ @docshell_id = data['docshellID']
160
+ @doc_identifier = data['docIdentifier']
161
+ end
162
+
163
+ def domain
164
+ url.split('/')[2]
165
+ end
166
+
167
+ def hash
168
+ url.hash
169
+ end
170
+
171
+ def to_s
172
+ "#<FirefoxJson::Entry #{url}>"
173
+ end
174
+ end
175
+
176
+ # A tab collects all its history and knows whether it's closed or not
177
+ class Tab < Base
178
+ attr_reader :is_closed
179
+ set_collection Entry, 'index'
180
+
181
+ # is_closed passed from Window and means the real data is inside the 'state' key
182
+ def initialize data, is_closed
183
+ @is_closed = is_closed
184
+ if is_closed
185
+ @closed_data = data.reject {|key,_v| 'state' == key}
186
+ end
187
+ tab_state = is_closed ? data['state'] : data
188
+ setup tab_state
189
+ end
190
+
191
+ def hash
192
+ selected_url.hash
193
+ end
194
+
195
+ def dump
196
+ is_closed ? @closed_data.merge('state' => super) : super
197
+ end
198
+
199
+ def selected_title
200
+ selected&.title
201
+ end
202
+
203
+ def selected_url
204
+ selected&.url
205
+ end
206
+
207
+ def selected_domain
208
+ selected&.domain
209
+ end
210
+
211
+ def to_s
212
+ "#<FirefoxJson::Tab#{' closed!' if is_closed} entries=#{entries.size} selected=\"#{selected_title}\">"
213
+ end
214
+ end
215
+
216
+ # A collection of tabs, both current and previous
217
+ class Window < Base
218
+ attr_reader :is_closed
219
+ set_collection Tab, 'selected', true
220
+
221
+ def initialize data, is_closed = false
222
+ @is_closed = is_closed
223
+ setup data
224
+ end
225
+
226
+ def hash
227
+ tabs.hash
228
+ end
229
+
230
+ def current_urls
231
+ tabs.map(&:selected_url)
232
+ end
233
+
234
+ def selected_title
235
+ selected.selected.title
236
+ end
237
+
238
+ def by_domain
239
+ tabs.map(&:selected_domain).reduce(Hash.new(0)) {|h,host| h[host]+=1; h}.sort_by {|_,v| -v}
240
+ end
241
+
242
+ def to_s
243
+ "#<FirefoxJson::Window#{' closed!' if is_closed} tabs=#{tabs.size}#{' closed='+closed_tabs.size.to_s if closed_tabs.size>0} selected=\"#{selected_title}\">"
244
+ end
245
+ end
246
+
247
+ # A collection of windows, both current and previous
248
+ class Session < Base
249
+ set_collection Window, 'selectedWindow', true
250
+
251
+ def current_urls
252
+ windows.map(&:current_urls)
253
+ end
254
+
255
+ def to_s
256
+ closed_text = ' closed='+closed_windows.size.to_s if closed_windows.size>0
257
+ fname = File.basename(path).split('.')[0..-2].join('.')
258
+ warning = fname if fname != 'sessionstore'
259
+ "#<FirefoxJson::Session##{warning} windows=#{windows.size}#{closed_text}>"
260
+ end
261
+
262
+ def self.default_file(path)
263
+ Dir["#{path}/sessionstore.jsonlz4",
264
+ "#{path}/sessionstore.js"][0]
265
+ end
266
+
267
+ def self.recovery_file(path)
268
+ Dir["#{path}/sessionstore-backups/recovery.jsonlz4",
269
+ "#{path}/sessionstore-backups/recovery.js"][0]
270
+ end
271
+
272
+ def self.file(path)
273
+ default_file(path) || recovery_file(path)
274
+ end
275
+ end
276
+
277
+ BAD_ARG = 'Not Firefox session data'.freeze
278
+
279
+ def self.load string, path=nil
280
+ data = JsFile.load(string)
281
+ raise ArgumentError, BAD_ARG unless data.is_a?(Hash)
282
+ klass = Base.choose_for(data)
283
+ raise RuntimeError, BAD_ARG unless klass
284
+ klass.new(data, path: path)
285
+ end
286
+
287
+ def self.load_file(path)
288
+ load IO.read(path), path
289
+ end
290
+
291
+ def self.default(path)
292
+ file = Session.file(path) || Session.recovery_file(path)
293
+ raise "No session file found under #{path}" if !file
294
+
295
+ load_file file
296
+ end
297
+
298
+ def self.recovery(path)
299
+ load_file Session.recovery_file(path)
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,3 @@
1
+ module FirefoxJson
2
+ VERSION = '0.2.0'
3
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firefox-json
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Boris Peterbarg
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: oj
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: inifile
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: extlz4
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.5
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.5
97
+ description:
98
+ email: boris.sa@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - ".rspec"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - bin/console
110
+ - bin/rake
111
+ - firefox-json.gemspec
112
+ - lib/firefox-json.rb
113
+ - lib/firefox-json/js_file.rb
114
+ - lib/firefox-json/profiles.rb
115
+ - lib/firefox-json/session.rb
116
+ - lib/firefox-json/version.rb
117
+ homepage: https://github.com/reist/firefox-json
118
+ licenses:
119
+ - ISC
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 2.3.0
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.7.6
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Read and manipulate Firefox's json files.
141
+ test_files: []