http2 0.0.32 → 0.0.33
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://img.shields.io/shippable/540e7b9b3479c5ea8f9ec21d.svg)](https://app.shippable.com/projects/540e7b9b3479c5ea8f9ec21d/builds/latest)
|
2
1
|
[![Code Climate](https://codeclimate.com/github/kaspernj/http2.png)](https://codeclimate.com/github/kaspernj/http2)
|
3
|
-
[![
|
2
|
+
[![Build Status](https://www.peakflow.io/en/projects/http2/branch-statuses/master.svg
|
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
|