http2 0.0.31 → 0.0.35
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 +15 -2
- data/Rakefile +18 -17
- data/lib/http2/base_request.rb +22 -0
- data/{include → lib/http2}/connection.rb +21 -10
- data/{include → lib/http2}/cookie.rb +4 -1
- data/lib/http2/errors.rb +18 -0
- data/lib/http2/get_request.rb +33 -0
- data/{include → lib/http2}/post_data_generator.rb +7 -6
- data/{include → lib/http2}/post_multipart_request.rb +20 -20
- data/{include → lib/http2}/post_request.rb +13 -29
- data/{include → lib/http2}/response.rb +48 -35
- data/{include → lib/http2}/response_reader.rb +61 -41
- data/{include → lib/http2}/url_builder.rb +5 -5
- data/{include → lib/http2}/utils.rb +12 -10
- data/lib/http2.rb +43 -37
- data/spec/helpers.rb +4 -6
- data/spec/http2/cookies_spec.rb +6 -6
- data/spec/http2/get_request_spec.rb +11 -0
- data/spec/http2/post_data_generator_spec.rb +2 -1
- data/spec/http2/post_multipart_request_spec.rb +11 -0
- data/spec/http2/post_request_spec.rb +11 -0
- data/spec/http2/response_reader_spec.rb +12 -0
- data/spec/http2/response_spec.rb +80 -4
- data/spec/http2/url_builder_spec.rb +1 -1
- data/spec/http2_spec.rb +81 -66
- data/spec/spec_helper.rb +7 -9
- data/spec/spec_root/redirect_test.rhtml +1 -1
- data/spec/spec_root/unauthorized.rhtml +3 -0
- data/spec/spec_root/unsupported_media_type.rhtml +3 -0
- metadata +82 -42
- data/include/errors.rb +0 -11
- data/include/get_request.rb +0 -34
@@ -1,17 +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
|
10
|
+
@args = args
|
11
|
+
@debug = http2.debug
|
12
|
+
@http2 = http2
|
13
|
+
@sock = sock
|
10
14
|
@nl = @http2.nl
|
11
15
|
@conn = @http2.connection
|
12
16
|
|
13
17
|
read_headers
|
14
|
-
read_body if @length == nil || @length
|
18
|
+
read_body if @length == nil || @length.positive?
|
15
19
|
finish
|
16
20
|
end
|
17
21
|
|
@@ -23,6 +27,7 @@ class Http2::ResponseReader
|
|
23
27
|
if line == "\n" || line == "\r\n" || line == @nl
|
24
28
|
puts "Http2: Changing mode to body!" if @debug
|
25
29
|
raise "No headers was given at all? Possibly corrupt state after last request?" if @response.headers.empty?
|
30
|
+
|
26
31
|
@mode = "body"
|
27
32
|
@http2.on_content_call(@args, @nl)
|
28
33
|
break
|
@@ -49,24 +54,20 @@ class Http2::ResponseReader
|
|
49
54
|
end
|
50
55
|
|
51
56
|
def finish
|
52
|
-
#Check if we should reconnect based on keep-alive-max.
|
53
|
-
if @keepalive_max == 1 || @connection == "close"
|
54
|
-
@conn.close unless @conn.closed?
|
55
|
-
end
|
57
|
+
# Check if we should reconnect based on keep-alive-max.
|
58
|
+
@conn.close if !@conn.closed? && (@keepalive_max == 1 || @connection == "close")
|
56
59
|
|
57
60
|
# Validate that the response is as it should be.
|
58
61
|
puts "Http2: Validating response." if @debug
|
59
62
|
|
60
|
-
unless @response.code
|
61
|
-
raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{@response.body}'."
|
62
|
-
end
|
63
|
+
raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{@response.body}'." unless @response.code
|
63
64
|
|
64
65
|
@response.validate!
|
65
66
|
check_and_decode
|
66
67
|
@http2.autostate_register(@response) if @http2.args[:autostate]
|
67
68
|
handle_errors
|
68
69
|
|
69
|
-
if response = check_and_follow_redirect
|
70
|
+
if (response = check_and_follow_redirect)
|
70
71
|
@response = response
|
71
72
|
end
|
72
73
|
end
|
@@ -78,36 +79,45 @@ private
|
|
78
79
|
url, args = url_and_args_from_location
|
79
80
|
|
80
81
|
if redirect_using_same_connection?(args)
|
81
|
-
|
82
|
+
@http2.get(url)
|
82
83
|
else
|
83
84
|
::Http2.new(args).get(url)
|
84
85
|
end
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
REDIRECT_CODES = [302, 303, 307]
|
89
|
+
REDIRECT_CODES = [301, 302, 303, 307, 308].freeze
|
89
90
|
def redirect_response?
|
90
|
-
REDIRECT_CODES.include?(
|
91
|
+
REDIRECT_CODES.include?(response.code.to_i) && response.header?("location") && @http2.args[:follow_redirects]
|
91
92
|
end
|
92
93
|
|
93
94
|
def redirect_using_same_connection?(args)
|
94
95
|
if !args[:host] || args[:host] == @args[:host]
|
95
|
-
|
96
|
+
true
|
96
97
|
else
|
97
|
-
|
98
|
+
false
|
98
99
|
end
|
99
100
|
end
|
100
101
|
|
102
|
+
def url
|
103
|
+
@url ||= response.header("location")
|
104
|
+
end
|
105
|
+
|
101
106
|
def url_and_args_from_location
|
102
|
-
uri = URI.parse(
|
107
|
+
uri = URI.parse(url)
|
108
|
+
|
103
109
|
url = uri.path
|
104
|
-
url << "?#{uri.query}"
|
110
|
+
url << "?#{uri.query}" unless uri.query.to_s.empty?
|
111
|
+
url = url.gsub(/\A\//, "")
|
112
|
+
|
113
|
+
args = @http2.args
|
114
|
+
.reject { |k, _v| [:ssl, :port].include? k }
|
115
|
+
.merge(host: uri.host)
|
105
116
|
|
106
|
-
args = {host: uri.host}
|
107
117
|
args[:ssl] = true if uri.scheme == "https"
|
108
118
|
args[:port] = uri.port if uri.port
|
109
119
|
|
110
|
-
|
120
|
+
[url, args]
|
111
121
|
end
|
112
122
|
|
113
123
|
def check_and_decode
|
@@ -122,8 +132,8 @@ private
|
|
122
132
|
|
123
133
|
begin
|
124
134
|
valid_string = ic.encode("UTF-8")
|
125
|
-
rescue
|
126
|
-
valid_string = untrusted_str.force_encoding("UTF-8").encode("UTF-8", :
|
135
|
+
rescue StandardError
|
136
|
+
valid_string = untrusted_str.force_encoding("UTF-8").encode("UTF-8", invalid: :replace, replace: "").encode("UTF-8")
|
127
137
|
end
|
128
138
|
|
129
139
|
@response.body = valid_string
|
@@ -131,16 +141,21 @@ private
|
|
131
141
|
end
|
132
142
|
|
133
143
|
def handle_errors
|
134
|
-
return unless
|
144
|
+
return unless @http2.raise_errors
|
135
145
|
|
136
|
-
|
146
|
+
case @response.code
|
147
|
+
when "500"
|
137
148
|
err = Http2::Errors::Internalserver.new("A internal server error occurred")
|
138
|
-
|
149
|
+
when "403"
|
139
150
|
err = Http2::Errors::Noaccess.new("No access")
|
140
|
-
|
151
|
+
when "400"
|
141
152
|
err = Http2::Errors::Badrequest.new("Bad request")
|
142
|
-
|
153
|
+
when "401"
|
154
|
+
err = Http2::Errors::Unauthorized.new("Unauthorized")
|
155
|
+
when "404"
|
143
156
|
err = Http2::Errors::Notfound.new("Not found")
|
157
|
+
when "415"
|
158
|
+
err = Http2::Errors::UnsupportedMediaType.new("Unsupported media type")
|
144
159
|
end
|
145
160
|
|
146
161
|
if err
|
@@ -153,7 +168,13 @@ private
|
|
153
168
|
if line
|
154
169
|
@rec_count += line.length
|
155
170
|
elsif !line && @rec_count <= 0
|
156
|
-
|
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(", ")})."
|
157
178
|
end
|
158
179
|
end
|
159
180
|
|
@@ -176,7 +197,7 @@ private
|
|
176
197
|
end
|
177
198
|
|
178
199
|
def parse_content_type(content_type_line)
|
179
|
-
if match_charset = content_type_line.match(/\s*;\s*charset=(.+)/i)
|
200
|
+
if (match_charset = content_type_line.match(/\s*;\s*charset=(.+)/i))
|
180
201
|
@charset = match_charset[1].downcase
|
181
202
|
@response.charset = @charset
|
182
203
|
content_type_line.gsub!(match_charset[0], "")
|
@@ -185,15 +206,15 @@ private
|
|
185
206
|
@response.content_type = content_type_line
|
186
207
|
end
|
187
208
|
|
188
|
-
#Parse a header-line and saves it on the object.
|
209
|
+
# Parse a header-line and saves it on the object.
|
189
210
|
#===Examples
|
190
211
|
# http.parse_header("Content-Type: text/html\r\n")
|
191
212
|
def parse_header(line)
|
192
|
-
if match = line.match(/^(.+?):\s*(.+)#{@nl}$/)
|
213
|
+
if (match = line.match(/^(.+?):\s*(.+)#{@nl}$/))
|
193
214
|
key = match[1].downcase
|
194
215
|
set_header_special_values(key, match[2])
|
195
216
|
parse_normal_header(line, key, match[1], match[2])
|
196
|
-
elsif match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)
|
217
|
+
elsif (match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)) # rubocop:disable Style/RedundantRegexpEscape
|
197
218
|
@response.code = match[2]
|
198
219
|
@response.http_version = match[1]
|
199
220
|
@http2.on_content_call(@args, line)
|
@@ -224,18 +245,16 @@ private
|
|
224
245
|
@response.headers[key] = [] unless @response.headers.key?(key)
|
225
246
|
@response.headers[key] << value
|
226
247
|
|
227
|
-
if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
|
228
|
-
@http2.on_content_call(@args, line)
|
229
|
-
end
|
248
|
+
@http2.on_content_call(@args, line) if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
|
230
249
|
end
|
231
250
|
|
232
|
-
#Parses the body based on given headers and saves it to the result-object.
|
251
|
+
# Parses the body based on given headers and saves it to the result-object.
|
233
252
|
# http.parse_body(str)
|
234
253
|
def parse_body(line)
|
235
|
-
return :break if @length
|
254
|
+
return :break if @length&.zero?
|
236
255
|
|
237
256
|
if @transfer_encoding == "chunked"
|
238
|
-
|
257
|
+
parse_body_chunked(line)
|
239
258
|
else
|
240
259
|
puts "Http2: Adding #{line.to_s.bytesize} to the body." if @debug
|
241
260
|
@response.body << line
|
@@ -247,15 +266,16 @@ private
|
|
247
266
|
def parse_body_chunked(line)
|
248
267
|
len = line.strip.hex
|
249
268
|
|
250
|
-
if len
|
269
|
+
if len.positive?
|
251
270
|
read = @conn.read(len)
|
252
271
|
return :break if read == "" || read == "\n" || read == "\r\n"
|
272
|
+
|
253
273
|
@response.body << read
|
254
274
|
@http2.on_content_call(@args, read)
|
255
275
|
end
|
256
276
|
|
257
277
|
nl = @conn.gets
|
258
|
-
if len
|
278
|
+
if len.zero?
|
259
279
|
if nl == "\n" || nl == "\r\n"
|
260
280
|
return :break
|
261
281
|
else
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Http2::UrlBuilder
|
2
2
|
attr_accessor :host, :port, :protocol, :path, :params
|
3
3
|
|
4
|
-
def initialize
|
4
|
+
def initialize
|
5
5
|
@params = {}
|
6
6
|
end
|
7
7
|
|
@@ -24,18 +24,18 @@ class Http2::UrlBuilder
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
url_params
|
28
28
|
end
|
29
29
|
|
30
30
|
def build_path_and_params
|
31
|
-
url =
|
31
|
+
url = path.to_s
|
32
32
|
|
33
33
|
if params?
|
34
34
|
url << "?"
|
35
35
|
url << build_params
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
url
|
39
39
|
end
|
40
40
|
|
41
41
|
def build
|
@@ -49,7 +49,7 @@ class Http2::UrlBuilder
|
|
49
49
|
|
50
50
|
url << build_path_and_params
|
51
51
|
|
52
|
-
|
52
|
+
url
|
53
53
|
end
|
54
54
|
|
55
55
|
def params?
|
@@ -2,36 +2,38 @@
|
|
2
2
|
class Http2::Utils
|
3
3
|
# URL-encodes a string.
|
4
4
|
def self.urlenc(string)
|
5
|
-
#Thanks to CGI framework
|
5
|
+
# Thanks to CGI framework
|
6
6
|
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
7
|
-
|
8
|
-
end.tr(
|
7
|
+
"%#{Regexp.last_match(1).unpack("H2" * Regexp.last_match(1).bytesize).join("%").upcase}"
|
8
|
+
end.tr(" ", "+")
|
9
9
|
end
|
10
10
|
|
11
11
|
# URL-decodes a string.
|
12
12
|
def self.urldec(string)
|
13
|
-
#Thanks to CGI framework
|
14
|
-
|
15
|
-
[
|
13
|
+
# Thanks to CGI framework
|
14
|
+
string.to_s.tr("+", " ").gsub(/((?:%[0-9a-fA-F]{2})+)/) do
|
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 = {
|
30
32
|
name: urldec(match[1].to_s),
|
31
|
-
value:
|
33
|
+
value: urldec(match[2].to_s)
|
32
34
|
}
|
33
35
|
|
34
|
-
while match = str.match(/(.+?)=(.*?)(;\s*|$)/)
|
36
|
+
while (match = str.match(/(.+?)=(.*?)(;\s*|$)/))
|
35
37
|
str = str.gsub(match[0], "")
|
36
38
|
key = match[1].to_s.downcase
|
37
39
|
value = match[2].to_s
|
@@ -45,6 +47,6 @@ class Http2::Utils
|
|
45
47
|
|
46
48
|
cookie = Http2::Cookie.new(cookie_data)
|
47
49
|
|
48
|
-
|
50
|
+
[cookie]
|
49
51
|
end
|
50
52
|
end
|
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,45 +18,49 @@ require "string-cases"
|
|
17
18
|
class Http2
|
18
19
|
# Autoloader for subclasses.
|
19
20
|
def self.const_missing(name)
|
20
|
-
|
21
|
-
return 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?
|
45
46
|
begin
|
46
47
|
yield(self)
|
47
48
|
ensure
|
48
|
-
|
49
|
+
destroy
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
54
|
def host
|
54
|
-
@args
|
55
|
+
@args.fetch(:host)
|
55
56
|
end
|
56
57
|
|
57
58
|
def port
|
58
|
-
@args
|
59
|
+
@args.fetch(:port)
|
60
|
+
end
|
61
|
+
|
62
|
+
def ssl?
|
63
|
+
@args[:ssl] ? true : false
|
59
64
|
end
|
60
65
|
|
61
66
|
def reconnect
|
@@ -68,7 +73,7 @@ class Http2
|
|
68
73
|
builder.port = port
|
69
74
|
builder.protocol = @args[:protocol]
|
70
75
|
|
71
|
-
|
76
|
+
builder
|
72
77
|
end
|
73
78
|
|
74
79
|
# Closes current connection if any, changes the arguments on the object and reconnects keeping all cookies and other stuff intact.
|
@@ -82,7 +87,6 @@ class Http2
|
|
82
87
|
#===Examples
|
83
88
|
# http.destroy
|
84
89
|
def destroy
|
85
|
-
@args = nil
|
86
90
|
@cookies = nil
|
87
91
|
@debug = nil
|
88
92
|
@mutex = nil
|
@@ -104,9 +108,9 @@ class Http2
|
|
104
108
|
raise "Invalid arguments: '#{args.class.name}'"
|
105
109
|
end
|
106
110
|
|
107
|
-
raise "Invalid URL: '#{args[:url]}'"
|
111
|
+
raise "Invalid URL: '#{args[:url]}'" if args[:url] != "" && args[:url].to_s.split("\n").length != 1
|
108
112
|
|
109
|
-
|
113
|
+
args
|
110
114
|
end
|
111
115
|
|
112
116
|
# Returns a result-object based on the arguments.
|
@@ -120,9 +124,9 @@ class Http2
|
|
120
124
|
# Proxies the request to another method but forces the method to be "DELETE".
|
121
125
|
def delete(args)
|
122
126
|
if args[:json]
|
123
|
-
|
127
|
+
post(args.merge(method: :delete))
|
124
128
|
else
|
125
|
-
|
129
|
+
get(args.merge(method: :delete))
|
126
130
|
end
|
127
131
|
end
|
128
132
|
|
@@ -143,18 +147,19 @@ class Http2
|
|
143
147
|
|
144
148
|
if @args[:basic_auth]
|
145
149
|
require "base64" unless ::Kernel.const_defined?(:Base64)
|
146
|
-
headers["Authorization"] = "Basic #{Base64.
|
150
|
+
headers["Authorization"] = "Basic #{Base64.strict_encode64("#{@args[:basic_auth][:user]}:#{@args[:basic_auth][:passwd]}").strip}"
|
147
151
|
end
|
148
152
|
|
149
153
|
if @args[:proxy] && @args[:proxy][:user] && @args[:proxy][:passwd] && !@connection.proxy_connect?
|
150
154
|
require "base64" unless ::Kernel.const_defined?(:Base64)
|
151
155
|
puts "Http2: Adding proxy auth header to request" if @debug
|
152
|
-
headers["Proxy-Authorization"] = "Basic #{Base64.
|
156
|
+
headers["Proxy-Authorization"] = "Basic #{Base64.strict_encode64("#{@args[:proxy][:user]}:#{@args[:proxy][:passwd]}").strip}"
|
153
157
|
end
|
154
158
|
|
155
159
|
headers.merge!(@args[:extra_headers]) if @args[:extra_headers]
|
156
160
|
headers.merge!(args[:headers]) if args[:headers]
|
157
|
-
|
161
|
+
|
162
|
+
headers
|
158
163
|
end
|
159
164
|
|
160
165
|
# Posts to a certain page.
|
@@ -172,34 +177,35 @@ class Http2
|
|
172
177
|
end
|
173
178
|
|
174
179
|
# Returns a header-string which normally would be used for a request in the given state.
|
175
|
-
def header_str(headers_hash
|
176
|
-
headers_hash["Cookie"] = cookie_header_string
|
180
|
+
def header_str(headers_hash)
|
181
|
+
headers_hash["Cookie"] = cookie_header_string unless cookie_header_string.empty?
|
177
182
|
|
178
183
|
headers_str = ""
|
179
184
|
headers_hash.each do |key, val|
|
180
185
|
headers_str << "#{key}: #{val}#{@nl}"
|
181
186
|
end
|
182
187
|
|
183
|
-
|
188
|
+
headers_str
|
184
189
|
end
|
185
190
|
|
186
191
|
def cookie_header_string
|
187
192
|
cstr = ""
|
188
193
|
|
189
194
|
first = true
|
190
|
-
@cookies.each do |
|
195
|
+
@cookies.each do |_cookie_name, cookie|
|
191
196
|
cstr << "; " unless first
|
192
197
|
first = false if first
|
193
198
|
ensure_single_lines([cookie.name, cookie.value])
|
194
199
|
cstr << "#{Http2::Utils.urlenc(cookie.name)}=#{Http2::Utils.urlenc(cookie.value)}"
|
195
200
|
end
|
196
201
|
|
197
|
-
|
202
|
+
cstr
|
198
203
|
end
|
199
204
|
|
200
205
|
def cookie(name)
|
201
206
|
name = name.to_s
|
202
207
|
return @cookies.fetch(name) if @cookies.key?(name)
|
208
|
+
|
203
209
|
raise "No cookie by that name: '#{name}' in '#{@cookies.keys.join(", ")}'"
|
204
210
|
end
|
205
211
|
|
@@ -216,8 +222,8 @@ class Http2
|
|
216
222
|
# Reads the response after posting headers and data.
|
217
223
|
#===Examples
|
218
224
|
# res = http.read_response
|
219
|
-
def read_response(args = {})
|
220
|
-
::Http2::ResponseReader.new(http2: self, sock: @sock, args: args).response
|
225
|
+
def read_response(request, args = {})
|
226
|
+
::Http2::ResponseReader.new(http2: self, sock: @sock, args: args, request: request).response
|
221
227
|
end
|
222
228
|
|
223
229
|
def to_s
|
@@ -231,13 +237,13 @@ class Http2
|
|
231
237
|
private
|
232
238
|
|
233
239
|
def host_header
|
234
|
-
#Possible to give custom host-argument.
|
240
|
+
# Possible to give custom host-argument.
|
235
241
|
host = args[:host] || self.host
|
236
242
|
port = args[:port] || self.port
|
237
243
|
|
238
|
-
host_header_string =
|
244
|
+
host_header_string = host.dup # Copy host string to avoid changing the original string if port has been given!
|
239
245
|
host_header_string << ":#{port}" if port && ![80, 443].include?(port.to_i) && !@args[:skip_port_in_host_header]
|
240
|
-
|
246
|
+
host_header_string
|
241
247
|
end
|
242
248
|
|
243
249
|
# Registers the states from a result.
|
@@ -247,7 +253,6 @@ private
|
|
247
253
|
|
248
254
|
res.body.to_s.scan(/<input type="hidden" name="__(EVENTTARGET|EVENTARGUMENT|VIEWSTATE|LASTFOCUS)" id="(.*?)" value="(.*?)" \/>/) do |match|
|
249
255
|
name = "__#{match[0]}"
|
250
|
-
id = match[1]
|
251
256
|
value = match[2]
|
252
257
|
|
253
258
|
puts "Http2: Registered autostate-value with name '#{name}' and value '#{value}'." if @debug
|
@@ -266,14 +271,15 @@ private
|
|
266
271
|
args = {host: args} if args.is_a?(String)
|
267
272
|
raise "Arguments wasnt a hash." unless args.is_a?(Hash)
|
268
273
|
|
269
|
-
args.
|
274
|
+
args.each_key do |key|
|
270
275
|
raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_INITIALIZE.include?(key)
|
271
276
|
end
|
272
277
|
|
273
278
|
args[:proxy][:connect] = true if args[:proxy] && !args[:proxy].key?(:connect) && args[:ssl]
|
274
279
|
|
275
280
|
raise "No host was given." unless args[:host]
|
276
|
-
|
281
|
+
|
282
|
+
args
|
277
283
|
end
|
278
284
|
|
279
285
|
def set_default_values
|
@@ -281,7 +287,7 @@ private
|
|
281
287
|
@autostate_values = {} if autostate
|
282
288
|
@nl = @args[:nl] || "\r\n"
|
283
289
|
|
284
|
-
|
290
|
+
unless @args[:port]
|
285
291
|
if @args[:ssl]
|
286
292
|
@args[:port] = 443
|
287
293
|
else
|
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
|
data/spec/http2/cookies_spec.rb
CHANGED
@@ -5,14 +5,14 @@ describe Http2 do
|
|
5
5
|
|
6
6
|
it "should parse cookies and let them be read" do
|
7
7
|
with_http do |http|
|
8
|
-
|
9
|
-
http.cookies.length.
|
8
|
+
http.get("cookie_test.rhtml")
|
9
|
+
expect(http.cookies.length).to eq 2
|
10
10
|
|
11
11
|
cookie = http.cookie("TestCookie")
|
12
|
-
cookie.name.
|
13
|
-
cookie.value.
|
14
|
-
cookie.path.
|
15
|
-
cookie.expires.
|
12
|
+
expect(cookie.name).to eq "TestCookie"
|
13
|
+
expect(cookie.value).to eq "TestValue"
|
14
|
+
expect(cookie.path).to eq "/"
|
15
|
+
expect(cookie.expires).to be > Time.now
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Http2::GetRequest do
|
4
|
+
it "reads the headers from the request" do
|
5
|
+
with_http do |http|
|
6
|
+
res = http.get("content_type_test.rhtml")
|
7
|
+
headers = res.request.headers_string
|
8
|
+
expect(headers).to include "GET /content_type_test.rhtml"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
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
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Http2::PostMultipartRequest do
|
4
|
+
it "reads the headers from the request" do
|
5
|
+
with_http do |http|
|
6
|
+
res = http.post_multipart(url: "json_test.rhtml", post: {"test1" => "test2"})
|
7
|
+
headers = res.request.headers_string
|
8
|
+
expect(headers).to include "POST /json_test.rhtml"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Http2::PostRequest do
|
4
|
+
it "reads the headers from the request" do
|
5
|
+
with_http do |http|
|
6
|
+
res = http.post(url: "json_test.rhtml", json: {testkey: "testvalue"})
|
7
|
+
headers = res.request.headers_string
|
8
|
+
expect(headers).to include "POST /json_test.rhtml"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Http2::ResponseReader do
|
4
|
+
it "#url_and_args_from_location" do
|
5
|
+
with_http(ssl_skip_verify: true, follow_redirects: true) do |http|
|
6
|
+
response = http.get("redirect_test.rhtml?redirect_to=https://www.google.dk")
|
7
|
+
expect(response.host).to eq "www.google.dk"
|
8
|
+
expect(response.ssl?).to eq true
|
9
|
+
expect(response.port).to eq 443
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|