binary_plist-parser 0.1.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: 87d5a1762931655a435d08575f31156cc2d1eb2650436bcf720416a12040f475
4
+ data.tar.gz: be07141444b9e3fd0a57d5c87a422dab28b282bb7cd80c1fb04f36b8ce6f2c17
5
+ SHA512:
6
+ metadata.gz: e1e1636803309cd985f125b3f308624551def8117d0d23ff2ee9965f537788d59060537b71c93644439f8669e2998f980261d0f4eb8185176b889433e8b3287c
7
+ data.tar.gz: 99e37bed5f348bac4805918227c9cf8ae8477f1ec15292a8345c736ea33d82fa3332e729c796bcb6f39955a0d7ba24fb567a96050cf3ef117677684328aa228e
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format Fuubar
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,12 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - spec/**/*_spec.rb
4
+
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/TrailingCommaInHashLiteral:
9
+ EnforcedStyleForMultiline: consistent_comma
10
+
11
+ Style/TrailingCommaInArrayLiteral:
12
+ EnforcedStyleForMultiline: consistent_comma
@@ -0,0 +1,20 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - "2.6"
6
+ - "2.7"
7
+ before_install: gem install bundler -v 2.0.2
8
+
9
+ script:
10
+ - bundle exec rspec
11
+
12
+ deploy:
13
+ provider: rubygems
14
+ api_key:
15
+ secure: h7iodoUUGjFc94eGydyJcAnkACbjxNJuXXSjMPmw2OwM6x/l4+QqzEBZ52QwqYU0ly0gnSPZeNjkSzvZW6yu2ane53/gZmigrxmAiCo0ClCalpGxH0FDHh2p/8xLvLSzemHdy23yGWI31G/oUIZ5yebUAci2lFps6859Jbyyl9QiqR0ew/e26M5JQl4lG2MeTNCwnYujWBWer8+IPMNV1Jb+BZB3y2fhvCBxtQW1wVawJ1BKwF5mquqrB4stITnhLZl8m3XKQXDFIWbtf/uFKcAnM6cHq7PVUQrTWeeNAYvC359KWxkKe8nILWu14rFchsgRK6XHGmzV+dkTDzDaWTHSqsaqTSfT9reB1EjB5IgKn6Iix+NZfolariNtV70m/ZZf0Gl/Igt9F2LdkPPd5zFvkcGaNM2skp8d95yewCLY2iGrsigXFl7SGxKWvZQn+1kiN1f5wP1y6osB1iTxwXm8WpQHzD4I4rhMOdi3uAgkAJLJelYxH125IF59+iZZI1qsud30qVCLdkYm41iyL+FlSEocJ0v1/ooL5GTrusBoYhGvASMs6R9HDha50iqvfKKQqpBR9jgHijDmmZqSTyVX7y+VyK8oK91OWCxsfpxBr64iJVqNOOiXSpAiR4igpCWP3iQFHV3SNUbMw/fMI5KwYkiL8jDLLNKkTjp80Sc=
16
+ gem: binary_plist-parser
17
+ on:
18
+ tags: true
19
+ repo: telyn/binary_plist
20
+ skip_cleanup: 'true'
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in binary_plist_parser.gemspec
4
+ gemspec
5
+
6
+ gem "guard-rspec", "~> 4.7"
7
+
8
+ gem "fuubar", "~> 2.5"
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ binary_plist-parser (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.3)
10
+ diff-lcs (1.3)
11
+ ffi (1.13.1)
12
+ formatador (0.2.5)
13
+ fuubar (2.5.0)
14
+ rspec-core (~> 3.0)
15
+ ruby-progressbar (~> 1.4)
16
+ guard (2.16.2)
17
+ formatador (>= 0.2.4)
18
+ listen (>= 2.7, < 4.0)
19
+ lumberjack (>= 1.0.12, < 2.0)
20
+ nenv (~> 0.1)
21
+ notiffany (~> 0.0)
22
+ pry (>= 0.9.12)
23
+ shellany (~> 0.0)
24
+ thor (>= 0.18.1)
25
+ guard-compat (1.2.1)
26
+ guard-rspec (4.7.3)
27
+ guard (~> 2.1)
28
+ guard-compat (~> 1.1)
29
+ rspec (>= 2.99.0, < 4.0)
30
+ listen (3.2.1)
31
+ rb-fsevent (~> 0.10, >= 0.10.3)
32
+ rb-inotify (~> 0.9, >= 0.9.10)
33
+ lumberjack (1.2.5)
34
+ method_source (1.0.0)
35
+ nenv (0.3.0)
36
+ notiffany (0.1.3)
37
+ nenv (~> 0.1)
38
+ shellany (~> 0.0)
39
+ pry (0.13.1)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
42
+ rake (13.0.1)
43
+ rb-fsevent (0.10.4)
44
+ rb-inotify (0.10.1)
45
+ ffi (~> 1.0)
46
+ rspec (3.9.0)
47
+ rspec-core (~> 3.9.0)
48
+ rspec-expectations (~> 3.9.0)
49
+ rspec-mocks (~> 3.9.0)
50
+ rspec-core (3.9.2)
51
+ rspec-support (~> 3.9.3)
52
+ rspec-expectations (3.9.2)
53
+ diff-lcs (>= 1.2.0, < 2.0)
54
+ rspec-support (~> 3.9.0)
55
+ rspec-mocks (3.9.1)
56
+ diff-lcs (>= 1.2.0, < 2.0)
57
+ rspec-support (~> 3.9.0)
58
+ rspec-support (3.9.3)
59
+ ruby-progressbar (1.10.1)
60
+ shellany (0.0.1)
61
+ thor (1.0.1)
62
+
63
+ PLATFORMS
64
+ ruby
65
+
66
+ DEPENDENCIES
67
+ binary_plist-parser!
68
+ bundler (~> 2.0)
69
+ fuubar (~> 2.5)
70
+ guard-rspec (~> 4.7)
71
+ rake (~> 13.0)
72
+ rspec (~> 3.0)
73
+
74
+ BUNDLED WITH
75
+ 2.0.2
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.call("routing/#{m[1]}_routing"),
51
+ rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ rspec.spec.call("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Telyn Z.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ # binary_plist
2
+
3
+ A parser (and maybe someday dumper) for Apple's bplist data format
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'binary_plist-parser'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ ## Usage
18
+
19
+
20
+ ```ruby
21
+ require "binary_plist/parser"
22
+
23
+ BinaryPList::Parser::BPList00.new(IO.read("spec/support/test-data/0")).parse
24
+
25
+ # => ["Cool Idea\n7", "abandoned\n1"]  
26
+ ```
27
+
28
+ You can also pass in an IO object instead of a string:
29
+
30
+ ```
31
+ require "binary_plist/parser"
32
+
33
+ File.open("spec/support/test-data/0", "r") do |fh|
34
+ BinaryPList::Parser::BPList00.new(fh).parse
35
+ end
36
+
37
+ # => ["Cool Idea\n7", "abandoned\n1"]  
38
+ ```
39
+
40
+ ## Development
41
+
42
+ After checking out the repo, run `bundle` to install dependencies. Then, run
43
+ `rspec` to run the tests.
44
+
45
+ ## Contributing
46
+
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/telyn/binary_plist.
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
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 "binary_plist_parser"
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require "binary_plist/parser/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "binary_plist-parser"
8
+ spec.version = BinaryPlistParser::VERSION
9
+ spec.authors = ["Telyn Z."]
10
+ spec.email = ["175827+telyn@users.noreply.github.com"]
11
+
12
+ spec.summary = ""
13
+ spec.description = ""
14
+ spec.homepage = "https://github.com/telyn/binary_plist"
15
+ spec.license = "MIT"
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added
25
+ # into git.
26
+ spec.files = `git ls-files -z`.split("\x0")
27
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 2.0"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ end
@@ -0,0 +1,9 @@
1
+ class BinaryPList
2
+ def self.load
3
+
4
+ end
5
+
6
+ def self.dump
7
+ raise "Sorry - I haven't implemented a dump function. Check out the binary_plist gem"
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ require "binary_plist/parser/version"
2
+
3
+ module BinaryPlistParser
4
+ class Error < StandardError; end
5
+ class UnsupportedMarkerError < Error; end
6
+ end
7
+
8
+ require "binary_plist/parser/bplist00"
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binary_plist/trailer"
4
+ require "binary_plist/parser/offset_table"
5
+
6
+ require "binary_plist/parser/object_readers/base"
7
+ require "binary_plist/parser/object_readers/array"
8
+ require "binary_plist/parser/object_readers/ascii_string"
9
+ require "binary_plist/parser/object_readers/int"
10
+ require "binary_plist/parser/object_readers/null"
11
+ require "binary_plist/parser/object_readers/utf16_string"
12
+
13
+ module BinaryPList
14
+ module Parser
15
+ class BPList00 < ObjectReaders::Base
16
+ class << self
17
+ def readers
18
+ @readers ||= [
19
+ ObjectReaders::ASCIIString,
20
+ ObjectReaders::Array,
21
+ ObjectReaders::Int,
22
+ ObjectReaders::Null,
23
+ ObjectReaders::UTF16String,
24
+ ]
25
+ end
26
+ end
27
+
28
+ MAGIC = "bplist00"
29
+ NotBPList00 = Class.new(StandardError)
30
+
31
+ def initialize(io)
32
+ io = StringIO.new(io) if io.is_a? String
33
+ raise NotBPList00 unless io.read(MAGIC.length).eql? MAGIC
34
+
35
+ @io = io
36
+ super(self.class, io, nil, nil)
37
+ end
38
+
39
+ def parse
40
+ object(trailer.top_object)
41
+ end
42
+
43
+ private
44
+
45
+ def offset_table
46
+ @offset_table ||= OffsetTable.new(@io,
47
+ trailer.offset_table_offset,
48
+ trailer.offset_int_size)
49
+ end
50
+
51
+ def trailer
52
+ @trailer ||=
53
+ begin
54
+ @io.seek(-32, File::SEEK_END)
55
+ Trailer.load(io)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ require "binary_plist/parser/object_readers/base"
2
+ require "binary_plist/parser/object_readers/int"
3
+
4
+ module BinaryPList
5
+ module Parser
6
+ module ObjectReaders
7
+ class Array < Base
8
+ def self.reads?(marker)
9
+ return true if (0b1010_0000..0b1010_1111).include?(marker)
10
+
11
+ false
12
+ end
13
+
14
+ def read(marker)
15
+ raise UnsupportedMarkerError, marker unless self.class.reads?(marker)
16
+
17
+ @marker = marker
18
+
19
+ object_refs.map do |object_ref|
20
+ object(object_ref)
21
+ end
22
+ end
23
+
24
+ def object_refs
25
+ array_length.times.map do
26
+ read_objref
27
+ end
28
+ end
29
+
30
+ def array_length
31
+ if @marker == 0b1010_1111
32
+ Int.new(nil, io, offset_table, trailer).read(io.getbyte)
33
+ else
34
+ @marker - 0b1010_0000
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binary_plist/parser/object_readers/base"
4
+ require "binary_plist/parser/object_readers/int"
5
+
6
+ module BinaryPList
7
+ module Parser
8
+ module ObjectReaders
9
+ class ASCIIString < Base
10
+ def self.reads?(marker)
11
+ (0b0101_0000..0b0101_1111).include?(marker)
12
+ end
13
+
14
+ def read(marker)
15
+ raise UnsupportedMarkerError, marker unless self.class.reads?(marker)
16
+
17
+ @marker = marker
18
+
19
+ read_bytes(string_length)
20
+ .force_encoding("ASCII-8BIT")
21
+ .encode("UTF-8")
22
+ end
23
+
24
+ def string_length
25
+ @string_length ||= if (@marker & 0xF) == 0xF
26
+ Int.new(nil, io, offset_table, trailer)
27
+ .read(read_byte)
28
+ else
29
+ @marker & 0xF
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,84 @@
1
+ module BinaryPList
2
+ module Parser
3
+ module ObjectReaders
4
+ class UnsupportedMarkerError < StandardError; end
5
+
6
+ # In order for a subclass to be used by Base#read_object, it must do three
7
+ # things:
8
+ # 1) Be included in the main_class.readers
9
+ # 2) Implement .reads?(marker) - which returns true when it can read the
10
+ # given marker
11
+ # 3) Implement #read(marker) - which reads the given marker.
12
+ class Base
13
+ def initialize(main_class, io, offset_table, trailer)
14
+ @main_class = main_class
15
+ @io = io
16
+ @offset_table = offset_table
17
+ @trailer = trailer
18
+ end
19
+
20
+ # called in subclasses by #read_object.
21
+ # at the beginning of #read,, the next byte read by any read calls to
22
+ # #io will be the byte immediately following the marker byte.
23
+ # @param [Integer] marker the marker byte
24
+ # @return some object
25
+ def read(marker)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ private
30
+
31
+ def object(ref)
32
+ trailer.check_object_reference!(ref)
33
+
34
+ read_object(offset_table.object_offset(ref))
35
+ end
36
+
37
+ def read_object(offset)
38
+ trailer.check_object_offset!(offset)
39
+
40
+ io.seek(offset)
41
+ marker = io.getbyte
42
+
43
+ reader = reader_for(marker)
44
+ raise UnsupportedMarkerError, marker if reader.nil?
45
+
46
+ reader.read(marker)
47
+ end
48
+
49
+ def reader_for(marker)
50
+ @main_class.readers.find do |klass|
51
+ klass.reads? marker
52
+ end&.new(@main_class, io, offset_table, trailer)
53
+ end
54
+
55
+ def read_objref
56
+ read_int(trailer.object_ref_size)
57
+ end
58
+
59
+ def read_int(size)
60
+ size.times
61
+ .map { read_byte }
62
+ .reduce(0) do |acc, byte|
63
+ (acc << 8) | byte
64
+ end
65
+ end
66
+
67
+ def read_byte
68
+ trailer.check_object_offset!(io.tell)
69
+ io.getbyte
70
+ end
71
+
72
+ def read_bytes(bytes)
73
+ return "" if bytes.zero?
74
+
75
+ trailer.check_object_offset!(io.tell + bytes - 1)
76
+ io.read(bytes)
77
+ end
78
+
79
+
80
+ attr_reader :offset_table, :trailer, :io
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binary_plist/parser/object_readers/base"
4
+
5
+ module BinaryPList
6
+ module Parser
7
+ module ObjectReaders
8
+ class Int < Base
9
+ def self.reads?(marker)
10
+ (0b0001_0000..0b0001_1111).include?(marker)
11
+ end
12
+
13
+ def read(marker)
14
+ raise UnsupportedMarkerError, marker unless self.class.reads?(marker)
15
+
16
+ marker_length = marker & 0xF
17
+
18
+ bytes_count = (1 << marker_length)
19
+ # raise OffsetOutOfRangeError if outside_object_table?(io.tell + bytes_count)
20
+
21
+ read_int(bytes_count)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binary_plist/parser/object_readers/base"
4
+
5
+ module BinaryPList
6
+ module Parser
7
+ module ObjectReaders
8
+ class Null < Base
9
+ def self.reads?(marker)
10
+ (0..0).include?(marker)
11
+ end
12
+
13
+ def read(marker)
14
+ raise UnsupportedMarkerError, marker unless self.class.reads?(marker)
15
+
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binary_plist/parser/object_readers/base"
4
+ require "binary_plist/parser/object_readers/int"
5
+
6
+ module BinaryPList
7
+ module Parser
8
+ module ObjectReaders
9
+ class UTF16String < Base
10
+ def self.reads?(marker)
11
+ (0b0110_0000..0b0110_1111).include?(marker)
12
+ end
13
+
14
+ def read(marker)
15
+ raise UnsupportedMarkerError, marker unless self.class.reads?(marker)
16
+
17
+ @marker = marker
18
+
19
+ read_bytes(string_length * 2)
20
+ .force_encoding("UTF-16BE")
21
+ .encode("UTF-8")
22
+ end
23
+
24
+ def string_length
25
+ @string_length ||= if (@marker & 0xF) == 0xF
26
+ Int.new(nil, io, offset_table, trailer)
27
+ .read(read_byte)
28
+ else
29
+ @marker & 0xF
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryPList
4
+ module Parser
5
+ OutOfBoundsError = Class.new(StandardError)
6
+
7
+ class OffsetTable
8
+ def initialize(io, offset, int_size)
9
+ @io = io
10
+ @int_size = int_size
11
+ @offset = offset
12
+
13
+ pos = io.tell
14
+ io.seek(-32, File::SEEK_END)
15
+ @last_int_loc = io.tell - @int_size
16
+ io.seek(pos)
17
+ end
18
+
19
+ def object_offset(num)
20
+ seek(object_offset_location(num))
21
+ read_arbitrary_int(int_size)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :io, :offset, :int_size
27
+
28
+ def object_offset_location(num)
29
+ offset + num * int_size
30
+ end
31
+
32
+ # TODO: refactor into helper module
33
+ def read_arbitrary_int(len)
34
+ bytes = io.read(len)
35
+ bytes.split("").reduce(0) do |int, byte|
36
+ (int << 8) | byte.unpack("C").first
37
+ end
38
+ end
39
+
40
+ def seek(pos)
41
+ raise OutOfBoundsError if @last_int_loc < pos
42
+
43
+ @io.seek(pos)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryPlistParser
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryPList
4
+ # Error raised when attempting to reference an object.
5
+ # When the reference number is larger than the number of objects specified in
6
+ # the trailer.
7
+ class ObjectOutOfRangeError < StandardError; end
8
+ # Error raised when attempting to read an offset from outside the range it
9
+ # should be in.
10
+ # For example, if an array object says it is 12 bytes, but there're only 6
11
+ # more bytes in the Object Table, then this error will be raised.
12
+ class OffsetOutOfRangeError < StandardError; end
13
+
14
+ Trailer = Struct.new(:sort_version,
15
+ :offset_int_size,
16
+ :object_ref_size,
17
+ :num_objects,
18
+ :top_object,
19
+ :offset_table_offset) do
20
+ def self.load(io)
21
+ bytes = io.is_a?(String) ? bytes : io.read(32)
22
+
23
+ sort_version, offset_int_size, object_ref_size,
24
+ num_objects, top_object, offset_table_offset =
25
+ bytes.unpack("@5 CCC Q>3")
26
+
27
+ Trailer.new(sort_version,
28
+ offset_int_size,
29
+ object_ref_size,
30
+ num_objects,
31
+ top_object,
32
+ offset_table_offset)
33
+ end
34
+
35
+ def pack
36
+ [0, 0, 0, 0, 0, sort_version, offset_int_size, object_ref_size,
37
+ num_objects, top_object, offset_table_offset].pack("C8Q>3")
38
+ end
39
+
40
+ def check_object_offset!(offset)
41
+ return if object_table_range.include?(offset)
42
+
43
+ raise OffsetOutOfRangeError, offset: offset, range: object_table_range
44
+ end
45
+
46
+ def check_object_reference!(ref)
47
+ return unless num_objects < ref
48
+
49
+ raise ObjectOutOfRangeError, num: num, max: trailer.num_objects
50
+ end
51
+
52
+ def object_table_range
53
+ (8...offset_table_offset)
54
+ end
55
+
56
+ def offset_table_range
57
+ (offset_table_offset..offset_table_offset + num_objects * offset_int_size)
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binary_plist-parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Telyn Z.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-06-19 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: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.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.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: ''
56
+ email:
57
+ - 175827+telyn@users.noreply.github.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".rubocop.yml"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - Guardfile
69
+ - LICENSE.txt
70
+ - README.md
71
+ - Rakefile
72
+ - bin/console
73
+ - bin/setup
74
+ - binary_plist-parser.gemspec
75
+ - lib/binary_plist.rb
76
+ - lib/binary_plist/parser.rb
77
+ - lib/binary_plist/parser/bplist00.rb
78
+ - lib/binary_plist/parser/object_readers/array.rb
79
+ - lib/binary_plist/parser/object_readers/ascii_string.rb
80
+ - lib/binary_plist/parser/object_readers/base.rb
81
+ - lib/binary_plist/parser/object_readers/int.rb
82
+ - lib/binary_plist/parser/object_readers/null.rb
83
+ - lib/binary_plist/parser/object_readers/utf16_string.rb
84
+ - lib/binary_plist/parser/offset_table.rb
85
+ - lib/binary_plist/parser/version.rb
86
+ - lib/binary_plist/trailer.rb
87
+ homepage: https://github.com/telyn/binary_plist
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://github.com/telyn/binary_plist
92
+ source_code_uri: https://github.com/telyn/binary_plist
93
+ changelog_uri: https://github.com/telyn/binary_plist/blob/master/CHANGELOG.md
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubygems_version: 3.0.4
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: ''
113
+ test_files: []