http2 0.0.32 → 0.0.33
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.
- checksums.yaml +5 -5
- data/README.md +2 -2
- data/Rakefile +2 -4
- data/lib/http2.rb +22 -18
- data/lib/http2/base_request.rb +2 -2
- data/lib/http2/connection.rb +11 -1
- data/lib/http2/errors.rb +12 -5
- data/lib/http2/post_data_generator.rb +2 -2
- data/lib/http2/post_multipart_request.rb +4 -8
- data/lib/http2/post_request.rb +1 -5
- data/lib/http2/response.rb +17 -29
- data/lib/http2/response_reader.rb +46 -34
- data/lib/http2/url_builder.rb +1 -1
- data/lib/http2/utils.rb +5 -3
- data/spec/helpers.rb +4 -6
- data/spec/http2/post_data_generator_spec.rb +2 -1
- data/spec/http2_spec.rb +17 -6
- data/spec/spec_helper.rb +1 -4
- data/spec/spec_root/unauthorized.rhtml +3 -0
- data/spec/spec_root/unsupported_media_type.rhtml +3 -0
- metadata +39 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 856e9e6edf6d355dc73912489a6263a6a213abbaa1ec87f6fa4b5547f727f336
|
4
|
+
data.tar.gz: f4062dda7f2970331ab68a9e38095fe7bc729f61a27305e7eaea4ddb9a2d28fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ca5c943ae78a977a5253487670755830ea8225b417f79238cbaff5cf6d4f3adabe1e572b6480325c19e6cb317f35e10d2ef491de79df9d15cc7f6f8014f2b13
|
7
|
+
data.tar.gz: aead6eadd6b8569bab500e3ebf64bea0188ebf06739c83ba78d1f1fcd70b3b3ac0a6ad2516610bdefd2751bc63acb53c352fbbc1e3e310931d41ab0b6bf72f18
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
[](https://app.shippable.com/projects/540e7b9b3479c5ea8f9ec21d/builds/latest)
|
2
1
|
[](https://codeclimate.com/github/kaspernj/http2)
|
3
|
-
[](https://www.peakflow.io/en/projects/http2/build-groups)
|
4
4
|
|
5
5
|
# http2
|
6
6
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require "rubygems"
|
4
2
|
require "bundler"
|
5
3
|
begin
|
6
4
|
Bundler.setup(:default, :development)
|
7
5
|
rescue Bundler::BundlerError => e
|
8
|
-
|
9
|
-
|
6
|
+
warn e.message
|
7
|
+
warn "Run `bundle install` to install missing gems"
|
10
8
|
exit e.status_code
|
11
9
|
end
|
12
10
|
require "rake"
|
data/lib/http2.rb
CHANGED
@@ -3,7 +3,8 @@ require "uri"
|
|
3
3
|
require "monitor" unless ::Kernel.const_defined?(:Monitor)
|
4
4
|
require "string-cases"
|
5
5
|
|
6
|
-
# This class tries to emulate a browser in Ruby without any visual stuff.
|
6
|
+
# This class tries to emulate a browser in Ruby without any visual stuff.
|
7
|
+
# Remember cookies, keep sessions alive, reset connections according to keep-alive rules and more.
|
7
8
|
#===Examples
|
8
9
|
# Http2.new(host: "www.somedomain.com", port: 80, ssl: false, debug: false) do |http|
|
9
10
|
# res = http.get("index.rhtml?show=some_page")
|
@@ -17,28 +18,28 @@ require "string-cases"
|
|
17
18
|
class Http2
|
18
19
|
# Autoloader for subclasses.
|
19
20
|
def self.const_missing(name)
|
20
|
-
|
21
|
-
Http2.const_get(name)
|
22
|
-
end
|
21
|
+
file_path = "#{File.dirname(__FILE__)}/http2/#{::StringCases.camel_to_snake(name)}.rb"
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
resp = http.get("api.php?longurl=#{url}")
|
28
|
-
return resp.body
|
23
|
+
if File.exist?(file_path)
|
24
|
+
require file_path
|
25
|
+
return Http2.const_get(name) if Http2.const_defined?(name)
|
29
26
|
end
|
27
|
+
|
28
|
+
super
|
30
29
|
end
|
31
30
|
|
32
31
|
attr_reader :autostate, :connection, :cookies, :args, :debug, :mutex, :resp, :raise_errors, :nl
|
33
32
|
attr_accessor :keepalive_max, :keepalive_timeout
|
34
33
|
|
35
|
-
VALID_ARGUMENTS_INITIALIZE = [
|
34
|
+
VALID_ARGUMENTS_INITIALIZE = [
|
35
|
+
:host, :port, :skip_port_in_host_header, :ssl, :ssl_skip_verify, :nl, :user_agent, :raise_errors,
|
36
|
+
:follow_redirects, :debug, :encoding_gzip, :autostate, :basic_auth, :extra_headers, :proxy
|
37
|
+
].freeze
|
36
38
|
def initialize(args = {})
|
37
39
|
@args = parse_init_args(args)
|
38
40
|
set_default_values
|
39
41
|
@cookies = {}
|
40
42
|
@mutex = Monitor.new
|
41
|
-
|
42
43
|
@connection = ::Http2::Connection.new(self)
|
43
44
|
|
44
45
|
if block_given?
|
@@ -124,9 +125,9 @@ class Http2
|
|
124
125
|
# Proxies the request to another method but forces the method to be "DELETE".
|
125
126
|
def delete(args)
|
126
127
|
if args[:json]
|
127
|
-
|
128
|
+
post(args.merge(method: :delete))
|
128
129
|
else
|
129
|
-
|
130
|
+
get(args.merge(method: :delete))
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
@@ -147,17 +148,18 @@ class Http2
|
|
147
148
|
|
148
149
|
if @args[:basic_auth]
|
149
150
|
require "base64" unless ::Kernel.const_defined?(:Base64)
|
150
|
-
headers["Authorization"] = "Basic #{Base64.
|
151
|
+
headers["Authorization"] = "Basic #{Base64.strict_encode64("#{@args[:basic_auth][:user]}:#{@args[:basic_auth][:passwd]}").strip}"
|
151
152
|
end
|
152
153
|
|
153
154
|
if @args[:proxy] && @args[:proxy][:user] && @args[:proxy][:passwd] && !@connection.proxy_connect?
|
154
155
|
require "base64" unless ::Kernel.const_defined?(:Base64)
|
155
156
|
puts "Http2: Adding proxy auth header to request" if @debug
|
156
|
-
headers["Proxy-Authorization"] = "Basic #{Base64.
|
157
|
+
headers["Proxy-Authorization"] = "Basic #{Base64.strict_encode64("#{@args[:proxy][:user]}:#{@args[:proxy][:passwd]}").strip}"
|
157
158
|
end
|
158
159
|
|
159
160
|
headers.merge!(@args[:extra_headers]) if @args[:extra_headers]
|
160
161
|
headers.merge!(args[:headers]) if args[:headers]
|
162
|
+
|
161
163
|
headers
|
162
164
|
end
|
163
165
|
|
@@ -177,7 +179,7 @@ class Http2
|
|
177
179
|
|
178
180
|
# Returns a header-string which normally would be used for a request in the given state.
|
179
181
|
def header_str(headers_hash)
|
180
|
-
headers_hash["Cookie"] = cookie_header_string
|
182
|
+
headers_hash["Cookie"] = cookie_header_string unless cookie_header_string.empty?
|
181
183
|
|
182
184
|
headers_str = ""
|
183
185
|
headers_hash.each do |key, val|
|
@@ -204,6 +206,7 @@ class Http2
|
|
204
206
|
def cookie(name)
|
205
207
|
name = name.to_s
|
206
208
|
return @cookies.fetch(name) if @cookies.key?(name)
|
209
|
+
|
207
210
|
raise "No cookie by that name: '#{name}' in '#{@cookies.keys.join(", ")}'"
|
208
211
|
end
|
209
212
|
|
@@ -239,7 +242,7 @@ private
|
|
239
242
|
host = args[:host] || self.host
|
240
243
|
port = args[:port] || self.port
|
241
244
|
|
242
|
-
host_header_string =
|
245
|
+
host_header_string = host.dup # Copy host string to avoid changing the original string if port has been given!
|
243
246
|
host_header_string << ":#{port}" if port && ![80, 443].include?(port.to_i) && !@args[:skip_port_in_host_header]
|
244
247
|
host_header_string
|
245
248
|
end
|
@@ -269,13 +272,14 @@ private
|
|
269
272
|
args = {host: args} if args.is_a?(String)
|
270
273
|
raise "Arguments wasnt a hash." unless args.is_a?(Hash)
|
271
274
|
|
272
|
-
args.
|
275
|
+
args.each_key do |key|
|
273
276
|
raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_INITIALIZE.include?(key)
|
274
277
|
end
|
275
278
|
|
276
279
|
args[:proxy][:connect] = true if args[:proxy] && !args[:proxy].key?(:connect) && args[:ssl]
|
277
280
|
|
278
281
|
raise "No host was given." unless args[:host]
|
282
|
+
|
279
283
|
args
|
280
284
|
end
|
281
285
|
|
data/lib/http2/base_request.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Http2::BaseRequest
|
2
2
|
attr_reader :http2, :args, :debug
|
3
3
|
|
4
|
-
VALID_ARGUMENTS_POST = [:post, :url, :default_headers, :headers, :json, :method, :cookies, :on_content, :content_type]
|
4
|
+
VALID_ARGUMENTS_POST = [:post, :url, :default_headers, :headers, :json, :method, :cookies, :on_content, :content_type].freeze
|
5
5
|
|
6
6
|
def initialize(http2, args)
|
7
7
|
@http2 = http2
|
@@ -9,7 +9,7 @@ class Http2::BaseRequest
|
|
9
9
|
@debug = http2.debug
|
10
10
|
@nl = http2.nl
|
11
11
|
|
12
|
-
@args.
|
12
|
+
@args.each_key do |key|
|
13
13
|
raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_POST.include?(key)
|
14
14
|
end
|
15
15
|
|
data/lib/http2/connection.rb
CHANGED
@@ -33,6 +33,7 @@ class Http2::Connection
|
|
33
33
|
def sock_write(str)
|
34
34
|
str = str.to_s
|
35
35
|
return if str.empty?
|
36
|
+
|
36
37
|
count = @sock.write(str)
|
37
38
|
raise "Couldnt write to socket: '#{count}', '#{str}'." if count <= 0
|
38
39
|
end
|
@@ -49,6 +50,7 @@ class Http2::Connection
|
|
49
50
|
|
50
51
|
begin
|
51
52
|
raise Errno::EPIPE, "The socket is closed." if !@sock || @sock.closed?
|
53
|
+
|
52
54
|
sock_write(str)
|
53
55
|
rescue Errno::EPIPE # this can also be thrown by puts.
|
54
56
|
reconnect
|
@@ -131,6 +133,7 @@ class Http2::Connection
|
|
131
133
|
|
132
134
|
res = @sock_plain.gets.to_s
|
133
135
|
raise "Couldn't connect through proxy: #{res}" unless res.match(/^http\/1\.(0|1)\s+200/i)
|
136
|
+
|
134
137
|
@sock_plain.gets
|
135
138
|
|
136
139
|
@proxy_connect = true
|
@@ -150,8 +153,15 @@ class Http2::Connection
|
|
150
153
|
require "openssl" unless ::Kernel.const_defined?(:OpenSSL)
|
151
154
|
|
152
155
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
153
|
-
|
156
|
+
|
157
|
+
if @args[:ssl_skip_verify]
|
158
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
159
|
+
else
|
160
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
161
|
+
end
|
162
|
+
|
154
163
|
@sock_ssl = OpenSSL::SSL::SSLSocket.new(@sock_plain, ssl_context)
|
164
|
+
@sock_ssl.hostname = @http2.host
|
155
165
|
@sock_ssl.sync_close = true
|
156
166
|
@sock_ssl.connect
|
157
167
|
|
data/lib/http2/errors.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
# This class holds various classes for error-handeling.
|
2
2
|
class Http2::Errors
|
3
|
-
class
|
3
|
+
class BaseError < RuntimeError
|
4
4
|
attr_accessor :response
|
5
5
|
end
|
6
6
|
|
7
|
-
class Noaccess <
|
8
|
-
|
9
|
-
class
|
10
|
-
|
7
|
+
class Noaccess < BaseError; end
|
8
|
+
|
9
|
+
class Internalserver < BaseError; end
|
10
|
+
|
11
|
+
class Notfound < BaseError; end
|
12
|
+
|
13
|
+
class Badrequest < BaseError; end
|
14
|
+
|
15
|
+
class Unauthorized < BaseError; end
|
16
|
+
|
17
|
+
class UnsupportedMediaType < BaseError; end
|
11
18
|
end
|
@@ -54,10 +54,10 @@ private
|
|
54
54
|
|
55
55
|
def generate_key_value(key, value)
|
56
56
|
if value.is_a?(Hash) || value.is_a?(Array)
|
57
|
-
|
57
|
+
::Http2::PostDataGenerator.new(value, orig_key: key).generate
|
58
58
|
else
|
59
59
|
data = ::Http2::PostDataGenerator.new(value).generate
|
60
|
-
|
60
|
+
"#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(data)}"
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
@@ -56,15 +56,11 @@ private
|
|
56
56
|
|
57
57
|
def read_file(path, praw)
|
58
58
|
File.open(path, "r") do |fp|
|
59
|
-
|
60
|
-
|
61
|
-
praw << data
|
62
|
-
end
|
63
|
-
# rubocop:disable Lint/HandleExceptions
|
64
|
-
rescue EOFError
|
65
|
-
# rubocop:enable Lint/HandleExceptions
|
66
|
-
# Happens when done.
|
59
|
+
while (data = fp.sysread(4096))
|
60
|
+
praw << data
|
67
61
|
end
|
62
|
+
rescue EOFError
|
63
|
+
# Happens when done.
|
68
64
|
end
|
69
65
|
end
|
70
66
|
|
data/lib/http2/post_request.rb
CHANGED
@@ -60,11 +60,7 @@ private
|
|
60
60
|
"Content-Type" => content_type
|
61
61
|
}
|
62
62
|
headers_hash.merge! @http2.default_headers(@args)
|
63
|
-
|
64
|
-
unless headers_hash["Accept"]
|
65
|
-
headers_hash["Accept"] = "application/json" if @args[:json]
|
66
|
-
end
|
67
|
-
|
63
|
+
headers_hash["Accept"] = "application/json" if @args[:json] && !headers_hash["Accept"]
|
68
64
|
headers_hash
|
69
65
|
end
|
70
66
|
end
|
data/lib/http2/response.rb
CHANGED
@@ -1,38 +1,34 @@
|
|
1
1
|
# This object will be returned as the response for each request.
|
2
2
|
class Http2::Response
|
3
3
|
# All the data the response contains. Headers, body, cookies, requested URL and more.
|
4
|
-
attr_reader :
|
5
|
-
attr_accessor :body, :charset, :code, :
|
4
|
+
attr_reader :headers, :request, :request_args, :requested_url
|
5
|
+
attr_accessor :body, :charset, :code, :http_version
|
6
|
+
attr_writer :content_type
|
6
7
|
|
7
8
|
# This method should not be called manually.
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns headers given from the host for the result.
|
17
|
-
#===Examples
|
18
|
-
# headers_hash = res.headers
|
19
|
-
def headers
|
20
|
-
@args.fetch(:headers)
|
9
|
+
def initialize(body: "", debug: false, headers: {}, request:)
|
10
|
+
@body = body
|
11
|
+
@debug = debug
|
12
|
+
@headers = headers
|
13
|
+
@request = request
|
14
|
+
@requested_url = request.path
|
21
15
|
end
|
22
16
|
|
23
17
|
# Returns a certain header by name or false if not found.
|
24
18
|
#===Examples
|
25
19
|
# val = res.header("content-type")
|
26
20
|
def header(key)
|
27
|
-
return false unless
|
28
|
-
|
21
|
+
return false unless headers.key?(key)
|
22
|
+
|
23
|
+
headers.fetch(key).first.to_s
|
29
24
|
end
|
30
25
|
|
31
26
|
# Returns true if a header of the given string exists.
|
32
27
|
#===Examples
|
33
28
|
# print "No content-type was given." if !http.header?("content-type")
|
34
29
|
def header?(key)
|
35
|
-
return true if
|
30
|
+
return true if headers.key?(key) && !headers[key].first.to_s.empty?
|
31
|
+
|
36
32
|
false
|
37
33
|
end
|
38
34
|
|
@@ -40,7 +36,7 @@ class Http2::Response
|
|
40
36
|
if header?("content-length")
|
41
37
|
header("content-length").to_i
|
42
38
|
elsif @body
|
43
|
-
|
39
|
+
@body.bytesize
|
44
40
|
else
|
45
41
|
raise "Couldn't calculate content-length."
|
46
42
|
end
|
@@ -48,20 +44,12 @@ class Http2::Response
|
|
48
44
|
|
49
45
|
def content_type
|
50
46
|
if header?("content-type")
|
51
|
-
|
47
|
+
header("content-type")
|
52
48
|
else
|
53
49
|
raise "No content-type was given."
|
54
50
|
end
|
55
51
|
end
|
56
52
|
|
57
|
-
# Returns the requested URL as a string.
|
58
|
-
#===Examples
|
59
|
-
# res.requested_url #=> "?show=status&action=getstatus"
|
60
|
-
def requested_url
|
61
|
-
raise "URL could not be detected." unless @args[:request_args][:url]
|
62
|
-
@args[:request_args][:url]
|
63
|
-
end
|
64
|
-
|
65
53
|
# Checks the data that has been sat on the object and raises various exceptions, if it does not validate somehow.
|
66
54
|
def validate!
|
67
55
|
puts "Http2: Validating response length." if @debug
|
@@ -105,7 +93,7 @@ private
|
|
105
93
|
|
106
94
|
# Checks that the length of the body is the same as the given content-length if given.
|
107
95
|
def validate_body_versus_content_length!
|
108
|
-
unless
|
96
|
+
unless header?("content-length")
|
109
97
|
puts "Http2: No content length given - skipping length validation." if @debug
|
110
98
|
return nil
|
111
99
|
end
|
@@ -1,20 +1,21 @@
|
|
1
1
|
class Http2::ResponseReader
|
2
2
|
attr_reader :response
|
3
3
|
|
4
|
-
def initialize(args)
|
4
|
+
def initialize(args:, http2:, sock:, request:)
|
5
5
|
@mode = "headers"
|
6
6
|
@transfer_encoding = nil
|
7
|
-
@
|
7
|
+
@request = request
|
8
|
+
@response = Http2::Response.new(debug: http2.debug, request: request)
|
8
9
|
@rec_count = 0
|
9
|
-
@args = args
|
10
|
-
@debug =
|
11
|
-
@http2 =
|
12
|
-
@sock =
|
10
|
+
@args = args
|
11
|
+
@debug = http2.debug
|
12
|
+
@http2 = http2
|
13
|
+
@sock = sock
|
13
14
|
@nl = @http2.nl
|
14
15
|
@conn = @http2.connection
|
15
16
|
|
16
17
|
read_headers
|
17
|
-
read_body if @length == nil || @length
|
18
|
+
read_body if @length == nil || @length.positive?
|
18
19
|
finish
|
19
20
|
end
|
20
21
|
|
@@ -26,6 +27,7 @@ class Http2::ResponseReader
|
|
26
27
|
if line == "\n" || line == "\r\n" || line == @nl
|
27
28
|
puts "Http2: Changing mode to body!" if @debug
|
28
29
|
raise "No headers was given at all? Possibly corrupt state after last request?" if @response.headers.empty?
|
30
|
+
|
29
31
|
@mode = "body"
|
30
32
|
@http2.on_content_call(@args, @nl)
|
31
33
|
break
|
@@ -53,16 +55,12 @@ class Http2::ResponseReader
|
|
53
55
|
|
54
56
|
def finish
|
55
57
|
# Check if we should reconnect based on keep-alive-max.
|
56
|
-
if @keepalive_max == 1 || @connection == "close"
|
57
|
-
@conn.close unless @conn.closed?
|
58
|
-
end
|
58
|
+
@conn.close if !@conn.closed? && (@keepalive_max == 1 || @connection == "close")
|
59
59
|
|
60
60
|
# Validate that the response is as it should be.
|
61
61
|
puts "Http2: Validating response." if @debug
|
62
62
|
|
63
|
-
unless @response.code
|
64
|
-
raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{@response.body}'."
|
65
|
-
end
|
63
|
+
raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{@response.body}'." unless @response.code
|
66
64
|
|
67
65
|
@response.validate!
|
68
66
|
check_and_decode
|
@@ -81,31 +79,35 @@ private
|
|
81
79
|
url, args = url_and_args_from_location
|
82
80
|
|
83
81
|
if redirect_using_same_connection?(args)
|
84
|
-
|
82
|
+
@http2.get(url)
|
85
83
|
else
|
86
84
|
::Http2.new(args).get(url)
|
87
85
|
end
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
91
|
-
REDIRECT_CODES = [302, 303, 307]
|
89
|
+
REDIRECT_CODES = [301, 302, 303, 307, 308].freeze
|
92
90
|
def redirect_response?
|
93
|
-
REDIRECT_CODES.include?(
|
91
|
+
REDIRECT_CODES.include?(response.code.to_i) && response.header?("location") && @http2.args[:follow_redirects]
|
94
92
|
end
|
95
93
|
|
96
94
|
def redirect_using_same_connection?(args)
|
97
95
|
if !args[:host] || args[:host] == @args[:host]
|
98
|
-
|
96
|
+
true
|
99
97
|
else
|
100
|
-
|
98
|
+
false
|
101
99
|
end
|
102
100
|
end
|
103
101
|
|
102
|
+
def url
|
103
|
+
@url ||= response.header("location")
|
104
|
+
end
|
105
|
+
|
104
106
|
def url_and_args_from_location
|
105
|
-
uri = URI.parse(
|
107
|
+
uri = URI.parse(url)
|
106
108
|
|
107
109
|
url = uri.path
|
108
|
-
url << "?#{uri.query}"
|
110
|
+
url << "?#{uri.query}" unless uri.query.to_s.empty?
|
109
111
|
url = url.gsub(/\A\//, "")
|
110
112
|
|
111
113
|
args = @http2.args
|
@@ -130,7 +132,7 @@ private
|
|
130
132
|
|
131
133
|
begin
|
132
134
|
valid_string = ic.encode("UTF-8")
|
133
|
-
rescue
|
135
|
+
rescue StandardError
|
134
136
|
valid_string = untrusted_str.force_encoding("UTF-8").encode("UTF-8", invalid: :replace, replace: "").encode("UTF-8")
|
135
137
|
end
|
136
138
|
|
@@ -141,14 +143,19 @@ private
|
|
141
143
|
def handle_errors
|
142
144
|
return unless @http2.raise_errors
|
143
145
|
|
144
|
-
|
146
|
+
case @response.code
|
147
|
+
when "500"
|
145
148
|
err = Http2::Errors::Internalserver.new("A internal server error occurred")
|
146
|
-
|
149
|
+
when "403"
|
147
150
|
err = Http2::Errors::Noaccess.new("No access")
|
148
|
-
|
151
|
+
when "400"
|
149
152
|
err = Http2::Errors::Badrequest.new("Bad request")
|
150
|
-
|
153
|
+
when "401"
|
154
|
+
err = Http2::Errors::Unauthorized.new("Unauthorized")
|
155
|
+
when "404"
|
151
156
|
err = Http2::Errors::Notfound.new("Not found")
|
157
|
+
when "415"
|
158
|
+
err = Http2::Errors::UnsupportedMediaType.new("Unsupported media type")
|
152
159
|
end
|
153
160
|
|
154
161
|
if err
|
@@ -161,7 +168,13 @@ private
|
|
161
168
|
if line
|
162
169
|
@rec_count += line.length
|
163
170
|
elsif !line && @rec_count <= 0
|
164
|
-
|
171
|
+
parts = [
|
172
|
+
"KeepAliveMax: '#{@http2.keepalive_max}'",
|
173
|
+
"Connection: '#{@connection}'",
|
174
|
+
"PID: '#{Process.pid}'"
|
175
|
+
]
|
176
|
+
|
177
|
+
raise Errno::ECONNABORTED, "Server closed the connection before being able to read anything (#{parts.join(", ")})."
|
165
178
|
end
|
166
179
|
end
|
167
180
|
|
@@ -201,7 +214,7 @@ private
|
|
201
214
|
key = match[1].downcase
|
202
215
|
set_header_special_values(key, match[2])
|
203
216
|
parse_normal_header(line, key, match[1], match[2])
|
204
|
-
elsif (match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/))
|
217
|
+
elsif (match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)) # rubocop:disable Style/RedundantRegexpEscape
|
205
218
|
@response.code = match[2]
|
206
219
|
@response.http_version = match[1]
|
207
220
|
@http2.on_content_call(@args, line)
|
@@ -232,18 +245,16 @@ private
|
|
232
245
|
@response.headers[key] = [] unless @response.headers.key?(key)
|
233
246
|
@response.headers[key] << value
|
234
247
|
|
235
|
-
if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
|
236
|
-
@http2.on_content_call(@args, line)
|
237
|
-
end
|
248
|
+
@http2.on_content_call(@args, line) if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
|
238
249
|
end
|
239
250
|
|
240
251
|
# Parses the body based on given headers and saves it to the result-object.
|
241
252
|
# http.parse_body(str)
|
242
253
|
def parse_body(line)
|
243
|
-
return :break if @length
|
254
|
+
return :break if @length&.zero?
|
244
255
|
|
245
256
|
if @transfer_encoding == "chunked"
|
246
|
-
|
257
|
+
parse_body_chunked(line)
|
247
258
|
else
|
248
259
|
puts "Http2: Adding #{line.to_s.bytesize} to the body." if @debug
|
249
260
|
@response.body << line
|
@@ -255,15 +266,16 @@ private
|
|
255
266
|
def parse_body_chunked(line)
|
256
267
|
len = line.strip.hex
|
257
268
|
|
258
|
-
if len
|
269
|
+
if len.positive?
|
259
270
|
read = @conn.read(len)
|
260
271
|
return :break if read == "" || read == "\n" || read == "\r\n"
|
272
|
+
|
261
273
|
@response.body << read
|
262
274
|
@http2.on_content_call(@args, read)
|
263
275
|
end
|
264
276
|
|
265
277
|
nl = @conn.gets
|
266
|
-
if len
|
278
|
+
if len.zero?
|
267
279
|
if nl == "\n" || nl == "\r\n"
|
268
280
|
return :break
|
269
281
|
else
|
data/lib/http2/url_builder.rb
CHANGED
data/lib/http2/utils.rb
CHANGED
@@ -4,7 +4,7 @@ class Http2::Utils
|
|
4
4
|
def self.urlenc(string)
|
5
5
|
# Thanks to CGI framework
|
6
6
|
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
7
|
-
"
|
7
|
+
"%#{Regexp.last_match(1).unpack("H2" * Regexp.last_match(1).bytesize).join("%").upcase}"
|
8
8
|
end.tr(" ", "+")
|
9
9
|
end
|
10
10
|
|
@@ -12,18 +12,20 @@ class Http2::Utils
|
|
12
12
|
def self.urldec(string)
|
13
13
|
# Thanks to CGI framework
|
14
14
|
string.to_s.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/) do
|
15
|
-
[
|
15
|
+
[Regexp.last_match(1).delete("%")].pack("H*")
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
# Parses a cookies-string and returns them in an array.
|
20
20
|
def self.parse_set_cookies(str)
|
21
|
-
str =
|
21
|
+
str = str.to_s
|
22
22
|
return [] if str.empty?
|
23
|
+
|
23
24
|
cookie_start_regex = /^(.+?)=(.*?)(;\s*|$)/
|
24
25
|
|
25
26
|
match = str.match(cookie_start_regex)
|
26
27
|
raise "Could not match cookie: '#{str}'" unless match
|
28
|
+
|
27
29
|
str.gsub!(cookie_start_regex, "")
|
28
30
|
|
29
31
|
cookie_data = {
|
data/spec/helpers.rb
CHANGED
@@ -17,13 +17,11 @@ module Helpers
|
|
17
17
|
def with_http(args = {})
|
18
18
|
with_webserver do |hayabusa|
|
19
19
|
Http2.new({host: "localhost", port: hayabusa.port, encoding_gzip: false}.merge(args)) do |http|
|
20
|
-
|
21
|
-
yield http
|
22
|
-
rescue Http2::Errors::Internalserver => e
|
23
|
-
puts "Body of error-response: #{e.response.body}"
|
24
|
-
raise e
|
25
|
-
end
|
20
|
+
yield http
|
26
21
|
end
|
22
|
+
rescue Http2::Errors::Internalserver => e
|
23
|
+
puts "Body of error-response: #{e.response.body}"
|
24
|
+
raise e
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
@@ -19,6 +19,7 @@ describe Http2::PostDataGenerator do
|
|
19
19
|
}
|
20
20
|
}
|
21
21
|
).generate
|
22
|
-
|
22
|
+
|
23
|
+
expect(res).to eq "test1%5Border%5D%5B%5B%3ABnet_profile%2C+%22profile_id%22%5D%5D=5"
|
23
24
|
end
|
24
25
|
end
|
data/spec/http2_spec.rb
CHANGED
@@ -26,7 +26,7 @@ describe "Http2" do
|
|
26
26
|
}
|
27
27
|
]
|
28
28
|
},
|
29
|
-
"val9" => [
|
29
|
+
"val9" => %w[a b d]
|
30
30
|
}
|
31
31
|
)
|
32
32
|
res = JSON.parse(resp.body)
|
@@ -100,11 +100,6 @@ describe "Http2" do
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
it "should be able to convert URL's to 'is.gd'-short-urls" do
|
104
|
-
isgd = Http2.isgdlink("https://github.com/kaspernj/http2")
|
105
|
-
raise "Expected isgd-var to be valid but it wasnt: '#{isgd}'." unless isgd.match(/^http:\/\/is\.gd\/([A-z\d]+)$/)
|
106
|
-
end
|
107
|
-
|
108
103
|
it "should raise exception when something is not found" do
|
109
104
|
with_http do |http|
|
110
105
|
expect { http.get("something_that_does_not_exist.rhtml") }.to raise_error(::Http2::Errors::Notfound)
|
@@ -161,4 +156,20 @@ describe "Http2" do
|
|
161
156
|
expect(resp.code).to eq "200"
|
162
157
|
end
|
163
158
|
end
|
159
|
+
|
160
|
+
it "throws errors on unauhtorized" do
|
161
|
+
with_http do |http|
|
162
|
+
expect do
|
163
|
+
http.get("unauthorized.rhtml")
|
164
|
+
end.to raise_error(Http2::Errors::Unauthorized)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "throws errors on unsupported media type" do
|
169
|
+
with_http do |http|
|
170
|
+
expect do
|
171
|
+
http.get("unsupported_media_type.rhtml")
|
172
|
+
end.to raise_error(Http2::Errors::UnsupportedMediaType)
|
173
|
+
end
|
174
|
+
end
|
164
175
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require "codeclimate-test-reporter"
|
2
|
-
CodeClimate::TestReporter.start
|
3
|
-
|
4
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
5
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
3
|
require "rspec"
|
@@ -9,7 +6,7 @@ require "helpers"
|
|
9
6
|
|
10
7
|
# Requires supporting files with custom matchers and macros, etc,
|
11
8
|
# in ./support/ and its subdirectories.
|
12
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }
|
13
10
|
|
14
11
|
RSpec.configure do |config|
|
15
12
|
config.expect_with :rspec do |c|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.33
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kasper Johansen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: string-cases
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: best_practice_project
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,63 +39,49 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 1.0.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 1.0.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: hayabusa
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.0.28
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.0.28
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: hayabusa
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 0.0.25
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 0.0.25
|
82
|
+
version: '0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
84
|
+
name: rdoc
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
87
|
- - ">="
|
@@ -109,7 +95,7 @@ dependencies:
|
|
109
95
|
- !ruby/object:Gem::Version
|
110
96
|
version: '0'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
98
|
+
name: rspec
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
101
|
- - ">="
|
@@ -123,33 +109,33 @@ dependencies:
|
|
123
109
|
- !ruby/object:Gem::Version
|
124
110
|
version: '0'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
112
|
+
name: rubocop
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
115
|
- - ">="
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0
|
117
|
+
version: '0'
|
132
118
|
type: :development
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
122
|
- - ">="
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: 0
|
124
|
+
version: '0'
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
126
|
+
name: sqlite3
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- - "
|
129
|
+
- - ">="
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version: 0
|
131
|
+
version: '0'
|
146
132
|
type: :development
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
|
-
- - "
|
136
|
+
- - ">="
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version: 0
|
138
|
+
version: '0'
|
153
139
|
description: A lightweight framework for doing http-connections in Ruby. Supports
|
154
140
|
cookies, keep-alive, compressing and much more.
|
155
141
|
email: k@spernj.org
|
@@ -191,6 +177,8 @@ files:
|
|
191
177
|
- spec/spec_root/json_test.rhtml
|
192
178
|
- spec/spec_root/multipart_test.rhtml
|
193
179
|
- spec/spec_root/redirect_test.rhtml
|
180
|
+
- spec/spec_root/unauthorized.rhtml
|
181
|
+
- spec/spec_root/unsupported_media_type.rhtml
|
194
182
|
homepage: http://github.com/kaspernj/http2
|
195
183
|
licenses:
|
196
184
|
- MIT
|
@@ -210,26 +198,27 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
198
|
- !ruby/object:Gem::Version
|
211
199
|
version: '0'
|
212
200
|
requirements: []
|
213
|
-
|
214
|
-
rubygems_version: 2.4.0
|
201
|
+
rubygems_version: 3.0.4
|
215
202
|
signing_key:
|
216
203
|
specification_version: 4
|
217
204
|
summary: A lightweight framework for doing http-connections in Ruby. Supports cookies,
|
218
205
|
keep-alive, compressing and much more.
|
219
206
|
test_files:
|
220
207
|
- spec/http2_spec.rb
|
208
|
+
- spec/spec_root/multipart_test.rhtml
|
209
|
+
- spec/spec_root/unauthorized.rhtml
|
210
|
+
- spec/spec_root/cookie_test.rhtml
|
211
|
+
- spec/spec_root/json_test.rhtml
|
212
|
+
- spec/spec_root/unsupported_media_type.rhtml
|
213
|
+
- spec/spec_root/content_type_test.rhtml
|
214
|
+
- spec/spec_root/redirect_test.rhtml
|
215
|
+
- spec/helpers.rb
|
216
|
+
- spec/spec_helper.rb
|
217
|
+
- spec/http2/cookies_spec.rb
|
221
218
|
- spec/http2/get_request_spec.rb
|
222
|
-
- spec/http2/
|
219
|
+
- spec/http2/response_reader_spec.rb
|
223
220
|
- spec/http2/post_data_generator_spec.rb
|
224
|
-
- spec/http2/cookies_spec.rb
|
225
221
|
- spec/http2/url_builder_spec.rb
|
226
|
-
- spec/http2/response_reader_spec.rb
|
227
222
|
- spec/http2/post_multipart_request_spec.rb
|
228
223
|
- spec/http2/response_spec.rb
|
229
|
-
- spec/
|
230
|
-
- spec/spec_root/json_test.rhtml
|
231
|
-
- spec/spec_root/multipart_test.rhtml
|
232
|
-
- spec/spec_root/content_type_test.rhtml
|
233
|
-
- spec/spec_root/cookie_test.rhtml
|
234
|
-
- spec/helpers.rb
|
235
|
-
- spec/spec_helper.rb
|
224
|
+
- spec/http2/post_request_spec.rb
|