http_decoders 0.1.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
+ SHA1:
3
+ metadata.gz: 3800af55baeac0e36064f9837b9c0aab1bdf0268
4
+ data.tar.gz: ac21d6225c4d5c3275e2ec0d58868b3f7dcf49bc
5
+ SHA512:
6
+ metadata.gz: c0141f3482e8ed76c47caf50f33fb82312adb393c2c7f22828f8c327d05c439cb429eccb3812e42ad90dd4ac2b912395d0e133cb1d33357cdd74541032faabf0
7
+ data.tar.gz: 6b7b1d5c70b8add6369ff3fba33647c573e2353586f37acdeb7485b2e5e238a38ac4857f9c68a914d105d0069902e1a5493e541ae2c9223aa044c3749dc8d196
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.3
6
+ - 2.4.0
7
+ before_install: gem install bundler -v 1.14.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in http_decoders.gemspec
4
+ gemspec
@@ -0,0 +1,44 @@
1
+ Copyright (c) 2017 Smartly.io Solutions Oy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+
23
+ This license applies to parts originating from the https://github.com/igrigorik/em-http-request repository:
24
+
25
+ Copyright (c) 2011 Ilya Grigorik
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining
28
+ a copy of this software and associated documentation files (the
29
+ "Software"), to deal in the Software without restriction, including
30
+ without limitation the rights to use, copy, modify, merge, publish,
31
+ distribute, sublicense, and/or sell copies of the Software, and to
32
+ permit persons to whom the Software is furnished to do so, subject to
33
+ the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be
36
+ included in all copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
39
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
40
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
41
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
42
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
43
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
44
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # HttpDecoders
2
+
3
+ Decompress HTTP responses. Supports deflate and gzip. Extracted from [em-http-request](https://github.com/igrigorik/em-http-request).
4
+
5
+ [![Build Status](https://travis-ci.org/smartlyio/http_decoders.svg?branch=master)](https://travis-ci.org/smartlyio/http_decoders)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'http_decoders'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install http_decoders
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ decoder = HttpDecoders.decoder_for_encoding('gzip')
27
+ decompressed = decoder << data
28
+ decompressed += decoder.finalize!
29
+
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` 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.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at https://github.com/smartlyio/http_decoders.
@@ -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 'http_decoders'
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
@@ -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,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'http_decoders/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'http_decoders'
8
+ spec.version = HttpDecoders::VERSION
9
+ spec.authors = ['Ville Lautanala']
10
+ spec.email = ['lautis@gmail.com']
11
+ spec.license = 'MIT'
12
+
13
+ spec.summary = 'Unified callback interface to decompression libraries.'
14
+ spec.description = 'Unified callback interface to decompression libraries.'
15
+ spec.homepage = 'https://github.com/smartlyio/http_decoders'
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.14'
25
+ spec.add_development_dependency 'rake', '~> 12.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.5'
27
+ end
@@ -0,0 +1,306 @@
1
+ require 'http_decoders/version'
2
+
3
+ require 'zlib'
4
+ require 'stringio'
5
+
6
+ # Unified callback interface to decompression libraries.
7
+ module HttpDecoders
8
+
9
+ class DecoderError < StandardError
10
+ end
11
+
12
+ class << self
13
+ def accepted_encodings
14
+ DECODERS.inject([]) { |r, d| r + d.encoding_names }
15
+ end
16
+
17
+ def decoder_for_encoding(encoding)
18
+ DECODERS.each { |d|
19
+ return d if d.encoding_names.include? encoding
20
+ }
21
+ nil
22
+ end
23
+ end
24
+
25
+ class Base
26
+ def self.encoding_names
27
+ name = to_s.split('::').last.downcase
28
+ [name]
29
+ end
30
+
31
+ ##
32
+ # chunk_callback:: [Block] To handle a decompressed chunk
33
+ def initialize(&chunk_callback)
34
+ @chunk_callback = chunk_callback
35
+ end
36
+
37
+ def <<(compressed)
38
+ return unless compressed && compressed.size > 0
39
+
40
+ decompressed = decompress(compressed)
41
+ receive_decompressed decompressed
42
+ end
43
+
44
+ def finalize!
45
+ decompressed = finalize
46
+ receive_decompressed decompressed
47
+ end
48
+
49
+ private
50
+
51
+ def receive_decompressed(decompressed)
52
+ if decompressed && decompressed.size > 0
53
+ @chunk_callback.call(decompressed)
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ ##
60
+ # Must return a part of decompressed
61
+ def decompress(compressed)
62
+ nil
63
+ end
64
+
65
+ ##
66
+ # May return last part
67
+ def finalize
68
+ nil
69
+ end
70
+ end
71
+
72
+ class Deflate < Base
73
+ def decompress(compressed)
74
+ begin
75
+ @zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
76
+ @zstream.inflate(compressed)
77
+ rescue Zlib::Error
78
+ raise DecoderError
79
+ end
80
+ end
81
+
82
+ def finalize
83
+ return nil unless @zstream
84
+
85
+ begin
86
+ r = @zstream.inflate(nil)
87
+ @zstream.close
88
+ r
89
+ rescue Zlib::Error
90
+ raise DecoderError
91
+ end
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Partial implementation of RFC 1952 to extract the deflate stream from a gzip file
97
+ class GZipHeader
98
+ def initialize
99
+ @state = :begin
100
+ @data = ""
101
+ @pos = 0
102
+ end
103
+
104
+ def finished?
105
+ @state == :finish
106
+ end
107
+
108
+ def read(n, buffer)
109
+ if (@pos + n) <= @data.size
110
+ buffer << @data[@pos..(@pos + n - 1)]
111
+ @pos += n
112
+ return true
113
+ else
114
+ return false
115
+ end
116
+ end
117
+
118
+ def readbyte
119
+ if (@pos + 1) <= @data.size
120
+ @pos += 1
121
+ @data.getbyte(@pos - 1)
122
+ end
123
+ end
124
+
125
+ def eof?
126
+ @pos >= @data.size
127
+ end
128
+
129
+ def extract_stream(compressed)
130
+ @data << compressed
131
+
132
+ while !eof? && !finished?
133
+ buffer = ""
134
+
135
+ case @state
136
+ when :begin
137
+ break if !read(10, buffer)
138
+
139
+ if buffer.getbyte(0) != 0x1f || buffer.getbyte(1) != 0x8b
140
+ raise DecoderError.new("magic header not found")
141
+ end
142
+
143
+ if buffer.getbyte(2) != 0x08
144
+ raise DecoderError.new("unknown compression method")
145
+ end
146
+
147
+ @flags = buffer.getbyte(3)
148
+ if (@flags & 0xe0).nonzero?
149
+ raise DecoderError.new("unknown header flags set")
150
+ end
151
+
152
+ # We don't care about these values, I'm leaving the code for reference
153
+ # @time = buffer[4..7].unpack("V")[0] # little-endian uint32
154
+ # @extra_flags = buffer.getbyte(8)
155
+ # @os = buffer.getbyte(9)
156
+
157
+ @state = :extra_length
158
+
159
+ when :extra_length
160
+ if (@flags & 0x04).nonzero?
161
+ break if !read(2, buffer)
162
+ @extra_length = buffer.unpack("v")[0] # little-endian uint16
163
+ @state = :extra
164
+ else
165
+ @state = :extra
166
+ end
167
+
168
+ when :extra
169
+ if (@flags & 0x04).nonzero?
170
+ break if read(@extra_length, buffer)
171
+ @state = :name
172
+ else
173
+ @state = :name
174
+ end
175
+
176
+ when :name
177
+ if (@flags & 0x08).nonzero?
178
+ while !(buffer = readbyte).nil?
179
+ if buffer == 0
180
+ @state = :comment
181
+ break
182
+ end
183
+ end
184
+ else
185
+ @state = :comment
186
+ end
187
+
188
+ when :comment
189
+ if (@flags & 0x10).nonzero?
190
+ while !(buffer = readbyte).nil?
191
+ if buffer == 0
192
+ @state = :hcrc
193
+ break
194
+ end
195
+ end
196
+ else
197
+ @state = :hcrc
198
+ end
199
+
200
+ when :hcrc
201
+ if (@flags & 0x02).nonzero?
202
+ break if !read(2, buffer)
203
+ @state = :finish
204
+ else
205
+ @state = :finish
206
+ end
207
+ end
208
+ end
209
+
210
+ if finished?
211
+ compressed[(@pos - (@data.length - compressed.length))..-1]
212
+ else
213
+ ""
214
+ end
215
+ end
216
+ end
217
+
218
+ class GZip < Base
219
+ MAGIC_STRING = "\x1F\x8B".force_encoding(Encoding::ASCII_8BIT).freeze
220
+
221
+ def self.encoding_names
222
+ %w(gzip compressed)
223
+ end
224
+
225
+ def initialize
226
+ super
227
+ @buffer = nil
228
+ end
229
+
230
+ def decompress(compressed)
231
+ compressed
232
+ .force_encoding(Encoding::ASCII_8BIT)
233
+ .each_line(MAGIC_STRING)
234
+ .map { |chunk| decompress_chunk(chunk) }
235
+ .join('')
236
+ end
237
+
238
+ def finalize
239
+ if @zstream
240
+ if !@zstream.finished?
241
+ r = @zstream.finish
242
+ end
243
+ @zstream.close
244
+ r
245
+ else
246
+ nil
247
+ end
248
+ rescue Zlib::Error
249
+ raise DecoderError
250
+ end
251
+
252
+ private
253
+
254
+ def decompress_chunk(compressed)
255
+ @header ||= GZipHeader.new
256
+ if !@header.finished?
257
+ compressed = @header.extract_stream(compressed)
258
+ end
259
+
260
+ if @buffer
261
+ @buffer << compressed
262
+ decompress_buffer
263
+ else
264
+ @zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
265
+ decompressed = @zstream.inflate(compressed)
266
+
267
+ # Gzip tailer with CRC32 and length is included after deflate stream.
268
+ # Buffer input including magic byte
269
+ if @header.finished? && @zstream.finished?
270
+ @buffer = []
271
+ @buffer << MAGIC_STRING if compressed.end_with?(MAGIC_STRING)
272
+ end
273
+ decompressed.to_s
274
+ end
275
+ rescue Zlib::Error
276
+ raise DecoderError
277
+ end
278
+
279
+ def decompress_buffer
280
+ next_stream = find_stream(@buffer.join) if @buffer
281
+ return unless next_stream
282
+
283
+ @buffer = nil
284
+ decompressed = finalize.to_s
285
+ reset
286
+ decompressed + decompress(next_stream)
287
+ end
288
+
289
+ def find_stream(decompressed)
290
+ index = decompressed.force_encoding(Encoding::ASCII_8BIT).index(MAGIC_STRING)
291
+ if index
292
+ decompressed[index..-1]
293
+ else
294
+ nil
295
+ end
296
+ end
297
+
298
+ def reset
299
+ @header = nil
300
+ @zstream = nil
301
+ end
302
+ end
303
+
304
+ DECODERS = [Deflate, GZip]
305
+
306
+ end
@@ -0,0 +1,3 @@
1
+ module HttpDecoders
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: http_decoders
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ville Lautanala
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-05 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: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.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.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ description: Unified callback interface to decompression libraries.
56
+ email:
57
+ - lautis@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - http_decoders.gemspec
72
+ - lib/http_decoders.rb
73
+ - lib/http_decoders/version.rb
74
+ homepage: https://github.com/smartlyio/http_decoders
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.5.1
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Unified callback interface to decompression libraries.
98
+ test_files: []