firefox-json 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []