mhtml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bd65b32102f1862282c6980695730474a60011f
4
+ data.tar.gz: 96c49f4a7a9e1132d93c89dc7cb27b03f746b179
5
+ SHA512:
6
+ metadata.gz: c2c2effc9cac20e3c6284f5c61137b553c8b0115bbc6540bd9c15ce97f2cefd541cba9cd9e1a0ede97010e2f7465074897233ee2d3e736172839f27dd29fc5d1
7
+ data.tar.gz: e4015b53919c0d6a29caf8c11df08ef6a8188eaea2459ca07460ff127df0d287b40eadc4e39e3c5985ef7c1d938ae35c69d5b8b7a0a5897ecf01d437f9167c68
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ .byebug_history
12
+
13
+ # rspec failure tracking
14
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mhtml.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Mhtml
2
+ A ruby gem for parsing MHTML.
3
+
4
+ Uses the NodeJS C HTTP Parser under the hood (thanks to @cotag for the gem).
5
+
6
+ ## Installation
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'mhtml'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install mhtml
20
+
21
+ ## Usage
22
+ Two interfaces are provided - all at once, or chunked.
23
+
24
+ ### All at once
25
+ For when you have all of the data in memory.
26
+
27
+ ```ruby
28
+ source = File.open('/file/path.mht').read
29
+ doc = Mhtml::RootDocument.new(source)
30
+
31
+ doc.headers.each { |h| puts h }
32
+
33
+ # body is decoded from printed quotable, and encoded according to charset header
34
+ puts doc.body
35
+
36
+ doc.sub_docs.each { |s| puts subdoc }
37
+ ```
38
+
39
+ ### Chunked
40
+ For when source data is being streamed, or when concerned about memory usage.
41
+
42
+ ```ruby
43
+ doc = Mhtml::RootDocument.new
44
+
45
+ doc.on_header { |h| handle_header(h) } # yields each header
46
+
47
+ # yields body, possibly in parts
48
+ doc.on_body do |b|
49
+ encoding = doc.encoding
50
+ handle_body(b)
51
+ end
52
+
53
+ doc.on_subdoc_begin { handle_subdoc_begin } # yields nil on each subdoc begin
54
+ doc.on_subdoc_header { |h| handle_subdoc_header(h) } # yields each subdoc header
55
+ doc.on_subdoc_body { |b| handle_subdoc_body(b) } # yields each subdoc's body, possibly in parts
56
+ doc.on_subdoc_complete { handle_subdoc_begin } # yields nil on each subdoc complete
57
+
58
+ File.open('/file/path.mht').read.scan(/.{128}/).each do |chunk|
59
+ doc << chunk
60
+ end
61
+ ```
62
+
63
+ ### Headers
64
+ The header class looks like this (portayed as a hash):
65
+
66
+ ```ruby
67
+ # Content-Type: multipart/related; charset="windows-1252"; boundary="----=_NextPart_01C74319.B7EA56A0"
68
+ {
69
+ key: 'Content-Type',
70
+ values: [
71
+ { key: nil, value: 'multipart/related' },
72
+ { key: 'charset', value: 'windows-1252' },
73
+ { key: 'boundary', value: '----=_NextPart_01C74319.B7EA56A0' }
74
+ ]
75
+ }
76
+ ```
77
+
78
+ ## TODO
79
+ - Revisit spec fixtures - either use existing solution or break out to separate
80
+ gem
81
+ - Build up body of fixtures using MHTML from various sources
82
+
83
+ ## Development
84
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
85
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
86
+ prompt that will allow you to experiment.
87
+
88
+ To install this gem onto your local machine, run `bundle exec rake install`.
89
+ To release a new version, update the version number in `version.rb`, and then
90
+ run `bundle exec rake release`, which will create a git tag for the version,
91
+ push git commits and tags, and push the `.gem` file to
92
+ [rubygems.org](https://rubygems.org).
93
+
94
+
95
+ ## Contributing
96
+ Bug reports and pull requests are welcome on GitHub at
97
+ https://github.com/benjineering/mhtml_rb.
data/Rakefile ADDED
@@ -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
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'mhtml'
5
+
6
+ require 'byebug'
7
+ require 'irb'
8
+
9
+ Dir.glob('spec/support/**/*.rb') do |path|
10
+ mod = path.gsub(/\.rb\Z/, '')
11
+ require_relative "../#{mod}"
12
+ end
13
+
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -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,135 @@
1
+ require 'http-parser'
2
+
3
+ module Mhtml
4
+ class Document
5
+ attr_reader :chunked, :parser
6
+ attr_accessor :headers, :body, :is_quoted_printable, :encoding
7
+
8
+ def initialize(str = nil)
9
+ @chunked = !str.is_a?(String)
10
+ @header_key = nil
11
+ @header_value_lines = nil
12
+ @is_quoted_printable = false
13
+ @encoding = nil
14
+
15
+ @request = HttpParser::Parser.new_instance { |inst| inst.type = :response }
16
+
17
+ @parser = HttpParser::Parser.new do |parser|
18
+ parser.on_header_field { |inst, data| handle_header_field(inst, data) }
19
+ parser.on_header_value { |inst, data| handle_header_value(inst, data) }
20
+ parser.on_body { |inst, data| handle_body(inst, data) }
21
+ parser.on_message_begin { |inst| handle_message_begin(inst) }
22
+ parser.on_message_complete { |inst| handle_message_complete(inst) }
23
+ end
24
+
25
+ @parser.parse(@request, Mhtml::STATUS_LINE)
26
+
27
+ unless @chunked
28
+ @headers = []
29
+ @body = ''
30
+ @parser.parse(@request, str)
31
+ end
32
+ end
33
+
34
+ def <<(chunk)
35
+ @parser.parse(@request, chunk)
36
+ end
37
+
38
+ def ==(other)
39
+ @headers == other.headers &&
40
+ @body.gsub(/\r\n/, "\n").strip == other.body.gsub(/\r\n/, "\n").strip
41
+ end
42
+
43
+ def on_header
44
+ @headers_proc = Proc.new
45
+ end
46
+
47
+ def on_body
48
+ @body_proc = Proc.new
49
+ end
50
+
51
+ def header(key)
52
+ header = nil
53
+
54
+ @headers.each do |h|
55
+ if h.key == key
56
+ header = h
57
+ break
58
+ end
59
+ end
60
+
61
+ header
62
+ end
63
+
64
+ # for testing only = no spec implemented
65
+ def to_s
66
+ @headers.join(LINE_BREAK) + Mhtml::DOUBLE_LINE_BREAK + @body
67
+ end
68
+
69
+ private
70
+
71
+ def handle_header_field(inst, data)
72
+ maybe_create_header
73
+ @header_key = data
74
+ @header_value_lines = []
75
+ end
76
+
77
+ def handle_header_value(inst, data)
78
+ @header_value_lines << data
79
+ end
80
+
81
+ def handle_body(inst, data)
82
+ maybe_create_header
83
+ decoded = decode(data)
84
+
85
+ if @chunked
86
+ @body_proc.call(decoded) unless @body_proc.nil?
87
+ else
88
+ @body.force_encoding(@encoding) if @body.empty? && !@encoding.nil?
89
+ @body += decoded
90
+ end
91
+ end
92
+
93
+ def handle_message_begin(inst)
94
+ end
95
+
96
+ def handle_message_complete(inst)
97
+ end
98
+
99
+ def maybe_create_header
100
+ unless @header_key.nil?
101
+ header = HttpHeader.new(@header_key, @header_value_lines)
102
+ @headers << header unless @chunked
103
+
104
+ if header.key == 'Content-Type'
105
+ boundary = header.value('boundary')
106
+ @boundary = boundary.value unless boundary.nil?
107
+
108
+ charset = header.value('charset')
109
+
110
+ unless charset.nil?
111
+ @encoding = Encoding.find(charset.value) rescue nil
112
+ end
113
+
114
+ elsif header.key == 'Content-Transfer-Encoding'
115
+ value = header.values.first
116
+
117
+ if !value.nil? && value.value == 'quoted-printable'
118
+ @is_quoted_printable = true
119
+ end
120
+ end
121
+
122
+ @headers_proc.call(header) unless @headers_proc.nil?
123
+
124
+ @header_key = nil
125
+ @header_value_lines = []
126
+ end
127
+ end
128
+
129
+ def decode(str)
130
+ str = str.unpack1('M*') if @is_quoted_printable
131
+ str = str.force_encoding(@encoding) unless @encoding.nil?
132
+ str
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,103 @@
1
+ module Mhtml
2
+ class HttpHeader
3
+ require 'string'
4
+
5
+ attr_accessor :key, :values
6
+
7
+ KEY_VALUE_SEP = ':'.freeze
8
+ VALUE_SEP = ';'.freeze
9
+
10
+ def initialize(key_or_hash, value_lines = nil)
11
+ if key_or_hash.is_a?(Hash)
12
+ @key = key_or_hash[:key]
13
+ @values = key_or_hash[:values]
14
+ return
15
+ end
16
+
17
+ @key = key_or_hash
18
+ @values = []
19
+ values_str = value_lines.join('')
20
+
21
+ values_str.split(VALUE_SEP).each do |val_str|
22
+ val_str.strip!
23
+ val = Value.new(val_str)
24
+
25
+ if val.nil?
26
+ raise "Invalid value:\n#{val_str}\n\nFrom string:\n#{val_str}"
27
+ end
28
+
29
+ @values << val
30
+ end
31
+ end
32
+
33
+ def ==(other)
34
+ @key == other.key && @values == other.values
35
+ end
36
+
37
+ def value(key)
38
+ value = nil
39
+
40
+ @values.each do |v|
41
+ if v.key == key
42
+ value = v
43
+ break
44
+ end
45
+ end
46
+
47
+ value
48
+ end
49
+
50
+ # following methods are for debugging only - no spec implemented
51
+ def to_s
52
+ "#{@key}#{KEY_VALUE_SEP} #{@values.join(VALUE_SEP + ' ')}"
53
+ end
54
+
55
+ def clone
56
+ vals = @values.collect { |v| v.clone }
57
+ HttpHeader.new(key: @key.clone, values: vals)
58
+ end
59
+
60
+ class Value
61
+ attr_reader :key, :value
62
+
63
+ # str examples:
64
+ # value
65
+ # key="value"
66
+ def initialize(str_or_hash)
67
+ if str_or_hash.is_a?(Hash)
68
+ @key = str_or_hash[:key]
69
+ @value = str_or_hash[:value]
70
+ return
71
+ end
72
+
73
+ str = str_or_hash
74
+ split_i = str.index('=')
75
+ @key = str[0, split_i].strip unless split_i.nil?
76
+
77
+ @value =
78
+ if split_i.nil?
79
+ str.strip
80
+ else
81
+ str[split_i + 1, str.length - 1].strip.strip_other('"')
82
+ end
83
+ end
84
+
85
+ def ==(other)
86
+ @key == other.key && @value == other.value
87
+ end
88
+
89
+ # following methods are for debugging only - no spec implemented
90
+ def to_s
91
+ if @key.nil?
92
+ @value
93
+ else
94
+ %Q[#{@key}="#{@value}"]
95
+ end
96
+ end
97
+
98
+ def clone
99
+ Value.new(key: @key.clone, value: @value.clone)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,123 @@
1
+ module Mhtml
2
+ class RootDocument < Document
3
+ BOUNDARY_PREFIX = '--'.freeze
4
+
5
+ attr_accessor :boundary, :sub_docs
6
+
7
+ def initialize(str = nil)
8
+ @sub_docs = []
9
+ super(str)
10
+ end
11
+
12
+ def ==(other)
13
+ super(other) && @boundary == other.boundary && @sub_docs == other.sub_docs
14
+ end
15
+
16
+ def on_subdoc_begin
17
+ @subdoc_begin_proc = Proc.new
18
+ end
19
+
20
+ def on_subdoc_header
21
+ @subdoc_header_proc = Proc.new
22
+ end
23
+
24
+ def on_subdoc_body
25
+ @subdoc_body_proc = Proc.new
26
+ end
27
+
28
+ def on_subdoc_complete
29
+ @subdoc_complete_proc = Proc.new
30
+ end
31
+
32
+ def boundary_str
33
+ "#{Mhtml::LINE_BREAK}#{BOUNDARY_PREFIX}#{@boundary}#{Mhtml::LINE_BREAK}"
34
+ end
35
+
36
+ def last_boundary_str
37
+ "#{Mhtml::LINE_BREAK}#{BOUNDARY_PREFIX}#{@boundary}#{BOUNDARY_PREFIX}#{Mhtml::LINE_BREAK}"
38
+ end
39
+
40
+ # for testing only = no spec implemented
41
+ def to_s
42
+ doc_sep = Mhtml::DOUBLE_LINE_BREAK + BOUNDARY_PREFIX + @boundary +
43
+ Mhtml::LINE_BREAK
44
+ super + doc_sep + @sub_docs.join(doc_sep)
45
+ end
46
+
47
+ private
48
+
49
+ def handle_body(inst, data)
50
+ maybe_create_header
51
+ boundary = boundary_str
52
+
53
+ unless @split.nil?
54
+ data = @split + data
55
+ @split = nil
56
+ end
57
+
58
+ parts = data.split(boundary)
59
+
60
+ unless @body_read
61
+ @body_read = parts.length > 1
62
+ super(inst, parts.shift)
63
+ end
64
+
65
+ parts.each_with_index do |part, i|
66
+ end_boundary_pos = part.rindex(last_boundary_str)
67
+ is_last_subdoc = !end_boundary_pos.nil?
68
+ part = part[0..(end_boundary_pos - 1)] if is_last_subdoc
69
+
70
+ if @chunked
71
+ is_last_part = i + 1 == parts.length
72
+ handle_chunked_body(part, is_last_part, is_last_subdoc)
73
+ else
74
+ @sub_docs << Document.new(part)
75
+ end
76
+ end
77
+ end
78
+
79
+ def handle_chunked_body(chunk, is_last_part, is_last_subdoc)
80
+ if @chunked_sub_doc.nil?
81
+ create_chunked_subdoc
82
+ @subdoc_begin_proc.call unless @subdoc_begin_proc.nil?
83
+ end
84
+
85
+ if is_last_part
86
+ split_idx = chunk.rindex_of_split(boundary_str)
87
+
88
+ if split_idx.nil?
89
+ quoted_matches = chunk.match(/=[0-9A-F\r\n]{0,2}\Z/)
90
+
91
+ unless quoted_matches.nil?
92
+ split_idx = chunk.length - quoted_matches[0].length + 1
93
+ end
94
+ end
95
+
96
+ unless split_idx.nil?
97
+ @split = chunk[split_idx..(chunk.length - 1)]
98
+ chunk = chunk[0..(split_idx - 1)]
99
+ end
100
+ end
101
+
102
+ @chunked_sub_doc << chunk
103
+
104
+ unless is_last_part && !is_last_subdoc
105
+ @sub_docs << @chunked_sub_doc
106
+ @chunked_sub_doc = nil
107
+ @subdoc_complete_proc.call unless @subdoc_complete_proc.nil?
108
+ end
109
+ end
110
+
111
+ def create_chunked_subdoc
112
+ @chunked_sub_doc = Document.new
113
+
114
+ @chunked_sub_doc.on_header do |header|
115
+ @subdoc_header_proc.call(header) unless @subdoc_header_proc.nil?
116
+ end
117
+
118
+ @chunked_sub_doc.on_body do |body|
119
+ @subdoc_body_proc.call(body) unless @subdoc_body_proc.nil?
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ module Mhtml
2
+ VERSION = '0.1.0'
3
+ end
data/lib/mhtml.rb ADDED
@@ -0,0 +1,11 @@
1
+
2
+ module Mhtml
3
+ LINE_BREAK = "\r\n".freeze
4
+ DOUBLE_LINE_BREAK = "#{LINE_BREAK}#{LINE_BREAK}".freeze
5
+ STATUS_LINE = "HTTP/1.1 200 OK#{LINE_BREAK}".freeze
6
+ end
7
+
8
+ require 'mhtml/document'
9
+ require 'mhtml/http_header'
10
+ require 'mhtml/root_document'
11
+ require 'mhtml/version'
data/lib/string.rb ADDED
@@ -0,0 +1,64 @@
1
+ class String
2
+
3
+ def each_index(x)
4
+ raise 'Block required' unless block_given?
5
+ return if empty? || x.nil?
6
+
7
+ i = 0
8
+ while true
9
+ i = index(x, i)
10
+ return if i.nil?
11
+
12
+ yield i
13
+ i += 1
14
+
15
+ return if i + 1 == length
16
+ end
17
+ end
18
+
19
+ def strip_other(str)
20
+ start_i = 0
21
+ new_length = length
22
+
23
+ if start_with?(str)
24
+ start_i += str.length
25
+ new_length -= str.length
26
+ end
27
+
28
+ if end_with?(str)
29
+ new_length -= str.length
30
+ end
31
+
32
+ self[start_i, new_length]
33
+ end
34
+
35
+ def underscore
36
+ self.gsub(/::/, '/').
37
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
38
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
39
+ tr("-", "_").
40
+ downcase
41
+ end
42
+
43
+ def index_of_split(other)
44
+ last_idx = (other.length - 1)
45
+
46
+ (0..last_idx).step do |i|
47
+ part = other[i..last_idx]
48
+ return part.length - 1 if start_with?(part)
49
+ end
50
+
51
+ nil
52
+ end
53
+
54
+ def rindex_of_split(other)
55
+ last_idx = (other.length - 1)
56
+
57
+ (0..last_idx).step do |i|
58
+ part = other[0..(last_idx - i)]
59
+ return length - part.length if end_with?(part)
60
+ end
61
+
62
+ nil
63
+ end
64
+ end
data/mhtml.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'mhtml/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mhtml'
7
+ spec.version = Mhtml::VERSION
8
+ spec.authors = [ 'Ben Williams' ]
9
+ spec.email = [ '8enwilliams@gmail.com' ]
10
+
11
+ spec.summary = 'A Ruby gem for reading and extracting MHTML files'
12
+ spec.description = 'A Ruby gem for reading and extracting MHTML files'
13
+ spec.homepage = 'https://github.com/benjineering/mhtml_rb'
14
+ spec.licenses = [ 'MIT', 'GPL-2' ]
15
+
16
+ spec.metadata[ 'allowed_push_host' ] = 'https://rubygems.org'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+
22
+ spec.require_paths = [ 'lib' ]
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.14'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'byebug'
28
+
29
+ spec.add_dependency 'http-parser'
30
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mhtml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-15 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.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
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.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
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: http-parser
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A Ruby gem for reading and extracting MHTML files
84
+ email:
85
+ - 8enwilliams@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - lib/mhtml.rb
99
+ - lib/mhtml/document.rb
100
+ - lib/mhtml/http_header.rb
101
+ - lib/mhtml/root_document.rb
102
+ - lib/mhtml/version.rb
103
+ - lib/string.rb
104
+ - mhtml.gemspec
105
+ homepage: https://github.com/benjineering/mhtml_rb
106
+ licenses:
107
+ - MIT
108
+ - GPL-2
109
+ metadata:
110
+ allowed_push_host: https://rubygems.org
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.6.11
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: A Ruby gem for reading and extracting MHTML files
131
+ test_files: []