CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/lib/rex/proto/http/client_spec.rb
Views: 11789
1
# -*- coding:binary -*-
2
3
# Note: Some of these tests require a failed
4
# connection to 127.0.0.1:1. If you have some crazy local
5
# firewall that is dropping packets to this, your tests
6
# might be slow.
7
RSpec.describe Rex::Proto::Http::Client do
8
9
class << self
10
11
# Set a standard excuse that indicates that the method
12
# under test needs to be first examined to figure out
13
# what's sane and what's not.
14
def excuse_lazy(test_method=nil)
15
ret = "need to determine pass/fail criteria"
16
test_method ? ret << " for #{test_method.inspect}" : ret
17
end
18
19
# Complain about not having a "real" connection (can be mocked)
20
def excuse_needs_connection
21
"need to actually set up an HTTP server to test"
22
end
23
24
# Complain about not having a real auth server (can be mocked)
25
def excuse_needs_auth
26
"need to set up an HTTP authentication challenger"
27
end
28
29
end
30
31
let(:ip) { "1.2.3.4" }
32
33
subject(:cli) do
34
Rex::Proto::Http::Client.new(ip)
35
end
36
37
describe "#set_config" do
38
39
it "should respond to #set_config" do
40
expect(cli.set_config).to eq({})
41
end
42
43
end
44
45
it "should respond to initialize" do
46
expect(cli).to be
47
end
48
49
it "should have a set of default instance variables" do
50
expect(cli.instance_variable_get(:@hostname)).to eq ip
51
expect(cli.instance_variable_get(:@port)).to eq 80
52
expect(cli.instance_variable_get(:@context)).to eq({})
53
expect(cli.instance_variable_get(:@ssl)).to be_falsey
54
expect(cli.instance_variable_get(:@proxies)).to be_nil
55
expect(cli.instance_variable_get(:@username)).to be_empty
56
expect(cli.instance_variable_get(:@password)).to be_empty
57
expect(cli.config).to be_a_kind_of Hash
58
end
59
60
it "should produce a raw HTTP request" do
61
expect(cli.request_raw).to be_a_kind_of Rex::Proto::Http::ClientRequest
62
end
63
64
it "should produce a CGI HTTP request" do
65
req = cli.request_cgi
66
expect(req).to be_a_kind_of Rex::Proto::Http::ClientRequest
67
end
68
69
context "with authorization" do
70
subject(:cli) do
71
cli = Rex::Proto::Http::Client.new(ip)
72
cli.set_config({"authorization" => "Basic base64dstuffhere"})
73
cli
74
end
75
let(:user) { "user" }
76
let(:pass) { "pass" }
77
let(:base64) { ["user:pass"].pack('m').chomp }
78
79
context "and an Authorization header" do
80
before do
81
cli.set_config({"headers" => { "Authorization" => "Basic #{base64}" } })
82
end
83
it "should have one Authorization header" do
84
req = cli.request_cgi
85
match = req.to_s.match("Authorization: Basic")
86
expect(match).to be
87
expect(match.length).to eq 1
88
end
89
it "should prefer the value in the header" do
90
req = cli.request_cgi
91
match = req.to_s.match(/Authorization: Basic (.*)$/)
92
expect(match).to be
93
expect(match.captures.length).to eq 1
94
expect(match.captures[0].chomp).to eq base64
95
end
96
end
97
end
98
99
context "with credentials" do
100
subject(:cli) do
101
cli = Rex::Proto::Http::Client.new(ip)
102
cli
103
end
104
let(:first_response) {
105
"HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\nWWW-Authenticate: Basic realm=\"foo\"\r\n\r\n"
106
}
107
let(:authed_response) {
108
"HTTP/1.1 200 Ok\r\nContent-Length: 0\r\n\r\n"
109
}
110
let(:user) { "user" }
111
let(:pass) { "pass" }
112
113
it "should not send creds on the first request in order to induce a 401" do
114
req = subject.request_cgi
115
expect(req.to_s).not_to match("Authorization:")
116
end
117
118
it "should send creds after receiving a 401" do
119
conn = double
120
allow(conn).to receive(:put)
121
allow(conn).to receive(:peerinfo)
122
allow(conn).to receive(:shutdown)
123
allow(conn).to receive(:close)
124
allow(conn).to receive(:closed?).and_return(false)
125
126
expect(conn).to receive(:get_once).and_return(first_response, authed_response)
127
expect(conn).to receive(:put) do |str_request|
128
expect(str_request).not_to include("Authorization")
129
nil
130
end
131
expect(conn).to receive(:put) do |str_request|
132
expect(str_request).to include("Authorization")
133
nil
134
end
135
136
expect(cli).to receive(:_send_recv).twice.and_call_original
137
138
allow(Rex::Socket::Tcp).to receive(:create).and_return(conn)
139
140
opts = { "username" => user, "password" => pass}
141
req = cli.request_cgi(opts)
142
cli.send_recv(req)
143
144
# Make sure it didn't modify the argument
145
expect(opts).to eq({ "username" => user, "password" => pass})
146
end
147
148
end
149
150
it "should attempt to connect to a server" do
151
this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1)
152
expect { this_cli.connect(1) }.to raise_error ::Rex::ConnectionRefused
153
end
154
155
it "should be able to close a connection" do
156
expect(cli.close).to be_nil
157
end
158
159
it "should send a request and receive a response", :skip => excuse_needs_connection do
160
161
end
162
163
it "should send a request and receive a response without auth handling", :skip => excuse_needs_connection do
164
165
end
166
167
it "should send a request", :skip => excuse_needs_connection do
168
169
end
170
171
it "should test for credentials" do
172
skip "Should actually respond to :has_creds" do
173
expect(cli).not_to have_creds
174
this_cli = described_class.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" )
175
expect(this_cli).to have_creds
176
end
177
end
178
179
it "should send authentication", :skip => excuse_needs_connection
180
181
it "should produce a basic authentication header" do
182
u = "user1"
183
p = "pass1"
184
b64 = ["#{u}:#{p}"].pack("m*").strip
185
expect(cli.basic_auth_header("user1","pass1")).to eq "Basic #{b64}"
186
end
187
188
it "should perform digest authentication", :skip => excuse_needs_auth do
189
190
end
191
192
it "should perform negotiate authentication", :skip => excuse_needs_auth do
193
194
end
195
196
it "should get a response", :skip => excuse_needs_connection do
197
198
end
199
200
it "should end a connection with a stop" do
201
expect(cli.stop).to be_nil
202
end
203
204
it "should test if a connection is valid" do
205
expect(cli.conn?).to be_falsey
206
end
207
208
it "should tell if pipelining is enabled" do
209
expect(cli).not_to be_pipelining
210
this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1)
211
this_cli.pipeline = true
212
expect(this_cli).to be_pipelining
213
end
214
215
it "should respond to its various accessors" do
216
expect(cli).to respond_to :config
217
expect(cli).to respond_to :config_types
218
expect(cli).to respond_to :pipeline
219
expect(cli).to respond_to :local_host
220
expect(cli).to respond_to :local_port
221
expect(cli).to respond_to :conn
222
expect(cli).to respond_to :context
223
expect(cli).to respond_to :proxies
224
expect(cli).to respond_to :username
225
expect(cli).to respond_to :password
226
expect(cli).to respond_to :junk_pipeline
227
end
228
# Not super sure why these are protected...
229
# Me either...
230
# Same here...
231
it "should refuse access to its protected accessors" do
232
expect {cli.ssl}.to raise_error NoMethodError
233
expect {cli.ssl_version}.to raise_error NoMethodError
234
expect {cli.hostname}.to raise_error NoMethodError
235
expect {cli.port}.to raise_error NoMethodError
236
end
237
238
context 'with vars_form_data' do
239
subject(:cli) do
240
cli = Rex::Proto::Http::Client.new(ip)
241
cli.config['data'] = ''
242
cli.config['method'] = 'POST'
243
cli
244
end
245
let(:file_path) do
246
::File.join(::Msf::Config.install_root, 'spec', 'file_fixtures', 'string_list.txt')
247
end
248
let(:file) do
249
::File.open(file_path, 'rb')
250
end
251
let(:mock_boundary_suffix) do
252
'MockBoundary1234'
253
end
254
before(:each) do
255
allow(Rex::Text).to receive(:rand_text_numeric).with(30).and_return(mock_boundary_suffix)
256
end
257
258
it 'should not include any form boundary metadata by default' do
259
request = cli.request_cgi({ })
260
261
expected = <<~EOF
262
POST / HTTP/1.1\r
263
Host: #{ip}\r
264
User-Agent: #{request.opts['agent']}\r
265
Content-Type: application/x-www-form-urlencoded\r
266
Content-Length: 0\r
267
\r
268
EOF
269
expect(request.to_s).to eq(expected)
270
end
271
it 'should parse field name and file object as data' do
272
vars_form_data = [
273
{ 'name' => 'field1', 'data' => file, 'content_type' => 'text/plain' }
274
]
275
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
276
# We are gsub'ing here as HttpClient does this gsub to non-binary file data
277
file_contents = file.read.gsub("\r", '').gsub("\n", "\r\n")
278
expected = <<~EOF
279
POST / HTTP/1.1\r
280
Host: #{ip}\r
281
User-Agent: #{request.opts['agent']}\r
282
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
283
Content-Length: 214\r
284
\r
285
-----------------------------MockBoundary1234\r
286
Content-Disposition: form-data; name="field1"; filename="string_list.txt"\r
287
Content-Type: text/plain\r
288
\r
289
#{file_contents}\r
290
-----------------------------MockBoundary1234--\r
291
EOF
292
expect(request.to_s).to eq(expected)
293
end
294
it 'should parse field name and binary file object as data' do
295
vars_form_data = [
296
{ 'name' => 'field1', 'data' => file, 'encoding' => 'binary' }
297
]
298
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
299
expected = <<~EOF
300
POST / HTTP/1.1\r
301
Host: #{ip}\r
302
User-Agent: #{request.opts['agent']}\r
303
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
304
Content-Length: 221\r
305
\r
306
-----------------------------MockBoundary1234\r
307
Content-Disposition: form-data; name="field1"; filename="string_list.txt"\r
308
Content-Transfer-Encoding: binary\r
309
\r
310
#{file.read}\r
311
-----------------------------MockBoundary1234--\r
312
EOF
313
expect(request.to_s).to eq(expected)
314
end
315
it 'should parse field name and binary file object as data with filename override' do
316
vars_form_data = [
317
{ 'name' => 'field1', 'data' => file, 'encoding' => 'binary', 'content_type' => 'text/plain', 'filename' => 'my_file.txt' }
318
]
319
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
320
expected = <<~EOF
321
POST / HTTP/1.1\r
322
Host: #{ip}\r
323
User-Agent: #{request.opts['agent']}\r
324
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
325
Content-Length: 243\r
326
\r
327
-----------------------------MockBoundary1234\r
328
Content-Disposition: form-data; name="field1"; filename="my_file.txt"\r
329
Content-Type: text/plain\r
330
Content-Transfer-Encoding: binary\r
331
\r
332
#{file.read}\r
333
-----------------------------MockBoundary1234--\r
334
EOF
335
expect(request.to_s).to eq(expected)
336
end
337
it 'should parse data correctly when provided with a string' do
338
data = 'hello world'
339
vars_form_data = [
340
{ 'name' => 'file1', 'data' => data, 'filename' => 'file1' }
341
]
342
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
343
expected = <<~EOF
344
POST / HTTP/1.1\r
345
Host: #{ip}\r
346
User-Agent: #{request.opts['agent']}\r
347
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
348
Content-Length: 175\r
349
\r
350
-----------------------------MockBoundary1234\r
351
Content-Disposition: form-data; name="file1"; filename="file1"\r
352
\r
353
#{data}\r
354
-----------------------------MockBoundary1234--\r
355
EOF
356
expect(request.to_s).to eq(expected)
357
end
358
it 'should parse data correctly when provided with a string and content type' do
359
data = 'hello world'
360
vars_form_data = [
361
{ 'name' => 'file1', 'data' => data, 'filename' => 'file1', 'content_type' => 'text/plain' }
362
]
363
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
364
expected = <<~EOF
365
POST / HTTP/1.1\r
366
Host: #{ip}\r
367
User-Agent: #{request.opts['agent']}\r
368
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
369
Content-Length: 201\r
370
\r
371
-----------------------------MockBoundary1234\r
372
Content-Disposition: form-data; name="file1"; filename="file1"\r
373
Content-Type: text/plain\r
374
\r
375
#{data}\r
376
-----------------------------MockBoundary1234--\r
377
EOF
378
expect(request.to_s).to eq(expected)
379
end
380
it 'should parse data correctly when provided with a string, content type and filename' do
381
data = 'hello world'
382
vars_form_data = [
383
{ 'name' => 'file1', 'data' => data, 'content_type' => 'text/plain', 'filename' => 'my_file.txt' }
384
]
385
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
386
expected = <<~EOF
387
POST / HTTP/1.1\r
388
Host: #{ip}\r
389
User-Agent: #{request.opts['agent']}\r
390
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
391
Content-Length: 207\r
392
\r
393
-----------------------------MockBoundary1234\r
394
Content-Disposition: form-data; name="file1"; filename="my_file.txt"\r
395
Content-Type: text/plain\r
396
\r
397
#{data}\r
398
-----------------------------MockBoundary1234--\r
399
EOF
400
expect(request.to_s).to eq(expected)
401
end
402
it 'should parse data correctly when provided with a number' do
403
data = 123
404
vars_form_data = [
405
{ 'name' => 'file1', 'data' => data, 'content_type' => 'text/plain' }
406
]
407
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
408
expected = <<~EOF
409
POST / HTTP/1.1\r
410
Host: #{ip}\r
411
User-Agent: #{request.opts['agent']}\r
412
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
413
Content-Length: 175\r
414
\r
415
-----------------------------MockBoundary1234\r
416
Content-Disposition: form-data; name="file1"\r
417
Content-Type: text/plain\r
418
\r
419
#{data}\r
420
-----------------------------MockBoundary1234--\r
421
EOF
422
expect(request.to_s).to eq(expected)
423
end
424
it 'should parse dat correctly when provided with an IO object' do
425
require 'stringio'
426
str = 'Hello World!'
427
vars_form_data = [
428
{ 'name' => 'file1', 'data' => ::StringIO.new(str), 'content_type' => 'text/plain', 'filename' => 'my_file.txt' }
429
]
430
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
431
expected = <<~EOF
432
POST / HTTP/1.1\r
433
Host: #{ip}\r
434
User-Agent: #{request.opts['agent']}\r
435
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
436
Content-Length: 208\r
437
\r
438
-----------------------------MockBoundary1234\r
439
Content-Disposition: form-data; name="file1"; filename="my_file.txt"\r
440
Content-Type: text/plain\r
441
\r
442
#{str}\r
443
-----------------------------MockBoundary1234--\r
444
EOF
445
expect(request.to_s).to eq(expected)
446
end
447
it 'should handle nil data values correctly' do
448
vars_form_data = [
449
{ 'name' => 'nil_value', 'data' => nil }
450
]
451
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
452
# This could potentially return one less '\r'.
453
expected = <<~EOF
454
POST / HTTP/1.1\r
455
Host: #{ip}\r
456
User-Agent: #{request.opts['agent']}\r
457
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
458
Content-Length: 150\r
459
\r
460
-----------------------------MockBoundary1234\r
461
Content-Disposition: form-data; name="nil_value"\r
462
\r
463
\r
464
-----------------------------MockBoundary1234--\r
465
EOF
466
expect(request.to_s).to eq(expected)
467
end
468
it 'should handle nil field values correctly' do
469
vars_form_data = [
470
{ 'name' => nil, 'data' => '123' },
471
{ 'data' => '456' },
472
]
473
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
474
expected = <<~EOF
475
POST / HTTP/1.1\r
476
Host: #{ip}\r
477
User-Agent: #{request.opts['agent']}\r
478
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
479
Content-Length: 221\r
480
\r
481
-----------------------------MockBoundary1234\r
482
Content-Disposition: form-data\r
483
\r
484
123\r
485
-----------------------------MockBoundary1234\r
486
Content-Disposition: form-data\r
487
\r
488
456\r
489
-----------------------------MockBoundary1234--\r
490
EOF
491
expect(request.to_s).to eq(expected)
492
end
493
it 'should handle nil field values and data correctly' do
494
vars_form_data = [
495
{ 'name' => nil, 'data' => nil }
496
]
497
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
498
expected = <<~EOF
499
POST / HTTP/1.1\r
500
Host: #{ip}\r
501
User-Agent: #{request.opts['agent']}\r
502
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
503
Content-Length: 132\r
504
\r
505
-----------------------------MockBoundary1234\r
506
Content-Disposition: form-data\r
507
\r
508
\r
509
-----------------------------MockBoundary1234--\r
510
EOF
511
expect(request.to_s).to eq(expected)
512
end
513
it 'should raise an error on non-string field names' do
514
invalid_names = [
515
false,
516
true,
517
123,
518
['hello'],
519
{ k: 'val' }
520
]
521
invalid_names.each do |name|
522
vars_form_data = [
523
{ 'name' => name, 'data' => '123' }
524
]
525
526
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
527
expect { request.to_s }.to raise_error /The provided field `name` option is not valid. Expected: String/
528
end
529
end
530
531
it 'should handle binary correctly' do
532
binary_data = (0..255).map { |x| x.chr }.join
533
vars_form_data = [
534
{ 'name' => 'field1', 'data' => binary_data, 'encoding' => 'binary' }
535
]
536
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
537
expected = <<~EOF
538
POST / HTTP/1.1\r
539
Host: #{ip}\r
540
User-Agent: #{request.opts['agent']}\r
541
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
542
Content-Length: 438\r
543
\r
544
-----------------------------MockBoundary1234\r
545
Content-Disposition: form-data; name="field1"\r
546
Content-Transfer-Encoding: binary\r
547
\r
548
#{binary_data}\r
549
-----------------------------MockBoundary1234--\r
550
EOF
551
expect(request.to_s).to eq(expected)
552
end
553
it 'should handle duplicate file and field names correctly' do
554
vars_form_data = [
555
{ 'name' => 'file', 'data' => 'file1_content', 'filename' => 'duplicate.txt' },
556
{ 'name' => 'file', 'data' => 'file2_content', 'filename' => 'duplicate.txt' },
557
{ 'name' => 'file', 'data' => 'file2_content', 'filename' => 'duplicate.txt' },
558
# Note, this won't actually attempt to read a file - the content will be set to 'file.txt'
559
{ 'name' => 'file', 'data' => 'file.txt', 'filename' => 'duplicate.txt' }
560
]
561
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
562
expected = <<~EOF
563
POST / HTTP/1.1\r
564
Host: #{ip}\r
565
User-Agent: #{request.opts['agent']}\r
566
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
567
Content-Length: 584\r
568
\r
569
-----------------------------MockBoundary1234\r
570
Content-Disposition: form-data; name="file"; filename="duplicate.txt"\r
571
\r
572
file1_content\r
573
-----------------------------MockBoundary1234\r
574
Content-Disposition: form-data; name="file"; filename="duplicate.txt"\r
575
\r
576
file2_content\r
577
-----------------------------MockBoundary1234\r
578
Content-Disposition: form-data; name="file"; filename="duplicate.txt"\r
579
\r
580
file2_content\r
581
-----------------------------MockBoundary1234\r
582
Content-Disposition: form-data; name="file"; filename="duplicate.txt"\r
583
\r
584
file.txt\r
585
-----------------------------MockBoundary1234--\r
586
EOF
587
expect(request.to_s).to eq(expected)
588
end
589
it 'does not encode special characters in file name by default as it may be used as part of an exploit' do
590
vars_form_data = [
591
{ 'name' => 'file', 'data' => 'abc', 'content_type' => 'text/plain', 'encoding' => '8bit', 'filename' => "'t \"e 'st.txt'" }
592
]
593
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
594
expected = <<~EOF
595
POST / HTTP/1.1\r
596
Host: #{ip}\r
597
User-Agent: #{request.opts['agent']}\r
598
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
599
Content-Length: 234\r
600
\r
601
-----------------------------MockBoundary1234\r
602
Content-Disposition: form-data; name="file"; filename="'t \"e 'st.txt'"\r
603
Content-Type: text/plain\r
604
Content-Transfer-Encoding: 8bit\r
605
\r
606
abc\r
607
-----------------------------MockBoundary1234--\r
608
EOF
609
expect(request.to_s).to eq(expected)
610
end
611
it 'should handle nil filename values correctly' do
612
vars_form_data = [
613
{ 'name' => 'example_name', 'data' => 'example_data', 'filename' => nil }
614
]
615
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
616
expected = <<~EOF
617
POST / HTTP/1.1\r
618
Host: #{ip}\r
619
User-Agent: #{request.opts['agent']}\r
620
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
621
Content-Length: 165\r
622
\r
623
-----------------------------MockBoundary1234\r
624
Content-Disposition: form-data; name="example_name"\r
625
\r
626
example_data\r
627
-----------------------------MockBoundary1234--\r
628
EOF
629
expect(request.to_s).to eq(expected)
630
end
631
it 'should handle nil encoding values correctly' do
632
vars_form_data = [
633
{ 'name' => 'example_name', 'data' => 'example_data', 'encoding' => nil }
634
]
635
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
636
expected = <<~EOF
637
POST / HTTP/1.1\r
638
Host: #{ip}\r
639
User-Agent: #{request.opts['agent']}\r
640
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
641
Content-Length: 165\r
642
\r
643
-----------------------------MockBoundary1234\r
644
Content-Disposition: form-data; name="example_name"\r
645
\r
646
example_data\r
647
-----------------------------MockBoundary1234--\r
648
EOF
649
expect(request.to_s).to eq(expected)
650
end
651
it 'should handle nil content type values correctly' do
652
vars_form_data = [
653
{ 'name' => 'example_name', 'data' => 'example_data', 'content_type' => nil }
654
]
655
request = cli.request_cgi({ 'vars_form_data' => vars_form_data })
656
expected = <<~EOF
657
POST / HTTP/1.1\r
658
Host: #{ip}\r
659
User-Agent: #{request.opts['agent']}\r
660
Content-Type: multipart/form-data; boundary=---------------------------MockBoundary1234\r
661
Content-Length: 165\r
662
\r
663
-----------------------------MockBoundary1234\r
664
Content-Disposition: form-data; name="example_name"\r
665
\r
666
example_data\r
667
-----------------------------MockBoundary1234--\r
668
EOF
669
expect(request.to_s).to eq(expected)
670
end
671
672
it 'should not hang when parsing a HEAD response' do
673
response = <<~EOF
674
HTTP/1.1 200 OK
675
Date: Thu, 15 Dec 2022 02:52:42 GMT
676
Server: Apache
677
Expires: Thu, 19 Nov 1981 08:52:00 GMT
678
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
679
Pragma: no-cache
680
Vary: Accept-Encoding
681
Access-Control-Allow-Origin: *
682
Connection: close
683
Content-Type: text/html; charset=UTF-8
684
Content-Length: 1000
685
686
687
EOF
688
689
conn = double
690
allow(conn).to receive(:put)
691
allow(conn).to receive(:peerinfo)
692
allow(conn).to receive(:closed?).and_return(false)
693
694
expect(conn).to receive(:get_once).at_least(:once).and_return(response, nil)
695
allow(Rex::Socket::Tcp).to receive(:create).and_return(conn)
696
697
request = cli.request_cgi('method' => 'HEAD')
698
resp = cli.send_recv(request,5)
699
700
expect(resp.headers['Content-Length']).to eq('1000')
701
end
702
end
703
end
704
705