httpclient 2.1.5 → 2.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +85 -0
- data/bin/httpclient +77 -0
- data/bin/jsonclient +85 -0
- data/lib/hexdump.rb +50 -0
- data/lib/http-access2.rb +6 -4
- data/lib/httpclient/auth.rb +575 -173
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/cacert1024.pem +3866 -0
- data/lib/httpclient/connection.rb +6 -2
- data/lib/httpclient/cookie.rb +162 -504
- data/lib/httpclient/http.rb +334 -119
- data/lib/httpclient/include_client.rb +85 -0
- data/lib/httpclient/jruby_ssl_socket.rb +588 -0
- data/lib/httpclient/session.rb +385 -288
- data/lib/httpclient/ssl_config.rb +195 -155
- data/lib/httpclient/ssl_socket.rb +150 -0
- data/lib/httpclient/timeout.rb +14 -10
- data/lib/httpclient/util.rb +142 -6
- data/lib/httpclient/version.rb +3 -0
- data/lib/httpclient/webagent-cookie.rb +459 -0
- data/lib/httpclient.rb +509 -202
- data/lib/jsonclient.rb +63 -0
- data/lib/oauthclient.rb +111 -0
- data/sample/async.rb +8 -0
- data/sample/auth.rb +11 -0
- data/sample/cookie.rb +18 -0
- data/sample/dav.rb +103 -0
- data/sample/howto.rb +49 -0
- data/sample/jsonclient.rb +67 -0
- data/sample/oauth_buzz.rb +57 -0
- data/sample/oauth_friendfeed.rb +59 -0
- data/sample/oauth_twitter.rb +61 -0
- data/sample/ssl/0cert.pem +22 -0
- data/sample/ssl/0key.pem +30 -0
- data/sample/ssl/1000cert.pem +19 -0
- data/sample/ssl/1000key.pem +18 -0
- data/sample/ssl/htdocs/index.html +10 -0
- data/sample/ssl/ssl_client.rb +22 -0
- data/sample/ssl/webrick_httpsd.rb +29 -0
- data/sample/stream.rb +21 -0
- data/sample/thread.rb +27 -0
- data/sample/wcat.rb +21 -0
- data/test/ca-chain.pem +44 -0
- data/test/ca.cert +23 -0
- data/test/client-pass.key +18 -0
- data/test/client.cert +19 -0
- data/test/client.key +15 -0
- data/test/helper.rb +131 -0
- data/test/htdigest +1 -0
- data/test/htpasswd +2 -0
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/runner.rb +2 -0
- data/test/server.cert +19 -0
- data/test/server.key +15 -0
- data/test/sslsvr.rb +65 -0
- data/test/subca.cert +21 -0
- data/test/test_auth.rb +492 -0
- data/test/test_cookie.rb +309 -0
- data/test/test_hexdump.rb +14 -0
- data/test/test_http-access2.rb +508 -0
- data/test/test_httpclient.rb +2145 -0
- data/test/test_include_client.rb +52 -0
- data/test/test_jsonclient.rb +80 -0
- data/test/test_ssl.rb +559 -0
- data/test/test_webagent-cookie.rb +465 -0
- metadata +85 -44
- data/lib/httpclient/auth.rb.orig +0 -513
- data/lib/httpclient/cacert.p7s +0 -1579
- data/lib/httpclient.rb.orig +0 -1020
- data/lib/tags +0 -908
data/lib/httpclient/http.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
4
|
+
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
5
|
#
|
4
6
|
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
7
|
# redistribute it and/or modify it under the same terms of Ruby's license;
|
@@ -7,6 +9,10 @@
|
|
7
9
|
|
8
10
|
|
9
11
|
require 'time'
|
12
|
+
if defined?(Encoding::ASCII_8BIT)
|
13
|
+
require 'open-uri' # for encoding
|
14
|
+
end
|
15
|
+
require 'httpclient/util'
|
10
16
|
|
11
17
|
|
12
18
|
# A namespace module for HTTP Message definitions used by HTTPClient.
|
@@ -90,12 +96,13 @@ module HTTP
|
|
90
96
|
# p res.header['last-modified'].first
|
91
97
|
#
|
92
98
|
class Message
|
99
|
+
include HTTPClient::Util
|
93
100
|
|
94
101
|
CRLF = "\r\n"
|
95
102
|
|
96
103
|
# Represents HTTP message header.
|
97
104
|
class Headers
|
98
|
-
# HTTP version in a HTTP header.
|
105
|
+
# HTTP version in a HTTP header. String.
|
99
106
|
attr_accessor :http_version
|
100
107
|
# Size of body. nil when size is unknown (e.g. chunked response).
|
101
108
|
attr_reader :body_size
|
@@ -109,7 +116,7 @@ module HTTP
|
|
109
116
|
# Request only. Requested query.
|
110
117
|
attr_accessor :request_query
|
111
118
|
# Request only. Requested via proxy or not.
|
112
|
-
attr_accessor :
|
119
|
+
attr_accessor :request_absolute_uri
|
113
120
|
|
114
121
|
# Response only. HTTP status
|
115
122
|
attr_reader :status_code
|
@@ -122,6 +129,8 @@ module HTTP
|
|
122
129
|
attr_accessor :body_charset # :nodoc:
|
123
130
|
# Used for dumping response.
|
124
131
|
attr_accessor :body_date # :nodoc:
|
132
|
+
# Used for keeping content encoding.
|
133
|
+
attr_reader :body_encoding # :nodoc:
|
125
134
|
|
126
135
|
# HTTP response status code to reason phrase mapping definition.
|
127
136
|
STATUS_CODE_MAP = {
|
@@ -151,14 +160,14 @@ module HTTP
|
|
151
160
|
# Creates a Message::Headers. Use init_request, init_response, or
|
152
161
|
# init_connect_request for acutual initialize.
|
153
162
|
def initialize
|
154
|
-
@http_version = 1.1
|
163
|
+
@http_version = '1.1'
|
155
164
|
@body_size = nil
|
156
165
|
@chunked = false
|
157
166
|
|
158
167
|
@request_method = nil
|
159
168
|
@request_uri = nil
|
160
169
|
@request_query = nil
|
161
|
-
@
|
170
|
+
@request_absolute_uri = nil
|
162
171
|
|
163
172
|
@status_code = nil
|
164
173
|
@reason_phrase = nil
|
@@ -166,6 +175,7 @@ module HTTP
|
|
166
175
|
@body_type = nil
|
167
176
|
@body_charset = nil
|
168
177
|
@body_date = nil
|
178
|
+
@body_encoding = nil
|
169
179
|
|
170
180
|
@is_request = nil
|
171
181
|
@header_item = []
|
@@ -178,24 +188,31 @@ module HTTP
|
|
178
188
|
@request_method = 'CONNECT'
|
179
189
|
@request_uri = uri
|
180
190
|
@request_query = nil
|
181
|
-
@http_version = 1.0
|
191
|
+
@http_version = '1.0'
|
182
192
|
end
|
183
193
|
|
184
194
|
# Placeholder URI object for nil uri.
|
185
|
-
NIL_URI =
|
195
|
+
NIL_URI = HTTPClient::Util.urify('http://nil-uri-given/')
|
186
196
|
# Initialize this instance as a general request.
|
187
197
|
def init_request(method, uri, query = nil)
|
188
198
|
@is_request = true
|
189
199
|
@request_method = method
|
190
200
|
@request_uri = uri || NIL_URI
|
191
201
|
@request_query = query
|
192
|
-
@
|
202
|
+
@request_absolute_uri = false
|
203
|
+
self
|
193
204
|
end
|
194
205
|
|
195
206
|
# Initialize this instance as a response.
|
196
|
-
def init_response(status_code)
|
207
|
+
def init_response(status_code, req = nil)
|
197
208
|
@is_request = false
|
198
209
|
self.status_code = status_code
|
210
|
+
if req
|
211
|
+
@request_method = req.request_method
|
212
|
+
@request_uri = req.request_uri
|
213
|
+
@request_query = req.request_query
|
214
|
+
end
|
215
|
+
self
|
199
216
|
end
|
200
217
|
|
201
218
|
# Sets status code and reason phrase.
|
@@ -205,14 +222,31 @@ module HTTP
|
|
205
222
|
end
|
206
223
|
|
207
224
|
# Returns 'Content-Type' header value.
|
208
|
-
def
|
225
|
+
def content_type
|
209
226
|
self['Content-Type'][0]
|
210
227
|
end
|
211
228
|
|
212
229
|
# Sets 'Content-Type' header value. Overrides if already exists.
|
213
|
-
def
|
230
|
+
def content_type=(content_type)
|
214
231
|
delete('Content-Type')
|
215
|
-
self['Content-Type'] =
|
232
|
+
self['Content-Type'] = content_type
|
233
|
+
end
|
234
|
+
|
235
|
+
alias contenttype content_type
|
236
|
+
alias contenttype= content_type=
|
237
|
+
|
238
|
+
if defined?(Encoding::ASCII_8BIT)
|
239
|
+
def set_body_encoding
|
240
|
+
if type = self.content_type
|
241
|
+
OpenURI::Meta.init(o = '')
|
242
|
+
o.meta_add_field('content-type', type)
|
243
|
+
@body_encoding = o.encoding
|
244
|
+
end
|
245
|
+
end
|
246
|
+
else
|
247
|
+
def set_body_encoding
|
248
|
+
@body_encoding = nil
|
249
|
+
end
|
216
250
|
end
|
217
251
|
|
218
252
|
# Sets byte size of message body.
|
@@ -235,6 +269,11 @@ module HTTP
|
|
235
269
|
}.join
|
236
270
|
end
|
237
271
|
|
272
|
+
# Set Date header
|
273
|
+
def set_date_header
|
274
|
+
set('Date', Time.now.httpdate)
|
275
|
+
end
|
276
|
+
|
238
277
|
# Adds a header. Addition order is preserved.
|
239
278
|
def add(key, value)
|
240
279
|
if value.is_a?(Array)
|
@@ -285,11 +324,45 @@ module HTTP
|
|
285
324
|
get(key).collect { |item| item[1] }
|
286
325
|
end
|
287
326
|
|
327
|
+
def set_headers(headers)
|
328
|
+
headers.each do |key, value|
|
329
|
+
add(key, value)
|
330
|
+
end
|
331
|
+
set_body_encoding
|
332
|
+
end
|
333
|
+
|
334
|
+
def create_query_uri()
|
335
|
+
if @request_method == 'CONNECT'
|
336
|
+
return "#{@request_uri.host}:#{@request_uri.port}"
|
337
|
+
end
|
338
|
+
path = @request_uri.path
|
339
|
+
path = '/' if path.nil? or path.empty?
|
340
|
+
if query_str = create_query_part()
|
341
|
+
path += "?#{query_str}"
|
342
|
+
end
|
343
|
+
path
|
344
|
+
end
|
345
|
+
|
346
|
+
def create_query_part()
|
347
|
+
query_str = nil
|
348
|
+
if @request_uri.query
|
349
|
+
query_str = @request_uri.query
|
350
|
+
end
|
351
|
+
if @request_query
|
352
|
+
if query_str
|
353
|
+
query_str += "&#{Message.create_query_part_str(@request_query)}"
|
354
|
+
else
|
355
|
+
query_str = Message.create_query_part_str(@request_query)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
query_str
|
359
|
+
end
|
360
|
+
|
288
361
|
private
|
289
362
|
|
290
363
|
def request_line
|
291
|
-
path = create_query_uri(
|
292
|
-
if @
|
364
|
+
path = create_query_uri()
|
365
|
+
if @request_absolute_uri
|
293
366
|
path = "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ path }"
|
294
367
|
end
|
295
368
|
"#{ @request_method } #{ path } HTTP/#{ @http_version }#{ CRLF }"
|
@@ -323,12 +396,12 @@ module HTTP
|
|
323
396
|
elsif @body_size and (keep_alive or @body_size != 0)
|
324
397
|
set('Content-Length', @body_size.to_s)
|
325
398
|
end
|
326
|
-
if @http_version >= 1.1
|
399
|
+
if @http_version >= '1.1' and get('Host').empty?
|
327
400
|
if @request_uri.port == @request_uri.default_port
|
328
401
|
# GFE/1.3 dislikes default port number (returns 404)
|
329
|
-
set('Host', "#{@request_uri.
|
402
|
+
set('Host', "#{@request_uri.hostname}")
|
330
403
|
else
|
331
|
-
set('Host', "#{@request_uri.
|
404
|
+
set('Host', "#{@request_uri.hostname}:#{@request_uri.port}")
|
332
405
|
end
|
333
406
|
end
|
334
407
|
end
|
@@ -337,7 +410,7 @@ module HTTP
|
|
337
410
|
return if @dumped
|
338
411
|
@dumped = true
|
339
412
|
if defined?(Apache) && self['Date'].empty?
|
340
|
-
|
413
|
+
set_date_header
|
341
414
|
end
|
342
415
|
keep_alive = Message.keep_alive_enabled?(@http_version)
|
343
416
|
if @chunked
|
@@ -351,35 +424,17 @@ module HTTP
|
|
351
424
|
set('Last-Modified', @body_date.httpdate)
|
352
425
|
end
|
353
426
|
if self['Content-Type'].empty?
|
354
|
-
set('Content-Type', "#{ @body_type || 'text/html' }; charset=#{ charset_label
|
427
|
+
set('Content-Type', "#{ @body_type || 'text/html' }; charset=#{ charset_label }")
|
355
428
|
end
|
356
429
|
end
|
357
430
|
|
358
|
-
def charset_label
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
return "#{uri.host}:#{uri.port}"
|
365
|
-
end
|
366
|
-
path = uri.path
|
367
|
-
path = '/' if path.nil? or path.empty?
|
368
|
-
query_str = nil
|
369
|
-
if uri.query
|
370
|
-
query_str = uri.query
|
371
|
-
end
|
372
|
-
if query
|
373
|
-
if query_str
|
374
|
-
query_str += "&#{Message.create_query_part_str(query)}"
|
375
|
-
else
|
376
|
-
query_str = Message.create_query_part_str(query)
|
377
|
-
end
|
378
|
-
end
|
379
|
-
if query_str
|
380
|
-
path += "?#{query_str}"
|
431
|
+
def charset_label
|
432
|
+
# TODO: should handle response encoding for 1.9 correctly.
|
433
|
+
if RUBY_VERSION > "1.9"
|
434
|
+
CHARSET_MAP[@body_charset] || 'us-ascii'
|
435
|
+
else
|
436
|
+
CHARSET_MAP[@body_charset || $KCODE] || 'us-ascii'
|
381
437
|
end
|
382
|
-
path
|
383
438
|
end
|
384
439
|
end
|
385
440
|
|
@@ -390,6 +445,8 @@ module HTTP
|
|
390
445
|
attr_reader :size
|
391
446
|
# maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.
|
392
447
|
attr_accessor :chunk_size
|
448
|
+
# Hash that keeps IO positions
|
449
|
+
attr_accessor :positions
|
393
450
|
|
394
451
|
# Default value for chunk_size
|
395
452
|
DEFAULT_CHUNK_SIZE = 1024 * 16
|
@@ -409,16 +466,20 @@ module HTTP
|
|
409
466
|
@positions = {}
|
410
467
|
set_content(body, boundary)
|
411
468
|
@chunk_size = DEFAULT_CHUNK_SIZE
|
469
|
+
self
|
412
470
|
end
|
413
471
|
|
414
472
|
# Initialize this instance as a response.
|
415
473
|
def init_response(body = nil)
|
416
474
|
@body = body
|
417
|
-
if @body.respond_to?(:
|
475
|
+
if @body.respond_to?(:bytesize)
|
476
|
+
@size = @body.bytesize
|
477
|
+
elsif @body.respond_to?(:size)
|
418
478
|
@size = @body.size
|
419
479
|
else
|
420
480
|
@size = nil
|
421
481
|
end
|
482
|
+
self
|
422
483
|
end
|
423
484
|
|
424
485
|
# Dumps message body to given dev.
|
@@ -428,20 +489,23 @@ module HTTP
|
|
428
489
|
# reason. (header is dumped to dev, too)
|
429
490
|
# If no dev (the second argument) given, this method returns a dumped
|
430
491
|
# String.
|
492
|
+
#
|
493
|
+
# assert: @size is not nil
|
431
494
|
def dump(header = '', dev = '')
|
432
495
|
if @body.is_a?(Parts)
|
433
496
|
dev << header
|
434
|
-
buf = ''
|
435
497
|
@body.parts.each do |part|
|
436
498
|
if Message.file?(part)
|
437
499
|
reset_pos(part)
|
438
|
-
|
439
|
-
dev << buf
|
440
|
-
end
|
500
|
+
dump_file(part, dev, @body.sizes[part])
|
441
501
|
else
|
442
502
|
dev << part
|
443
503
|
end
|
444
504
|
end
|
505
|
+
elsif Message.file?(@body)
|
506
|
+
dev << header
|
507
|
+
reset_pos(@body)
|
508
|
+
dump_file(@body, dev, @size)
|
445
509
|
elsif @body
|
446
510
|
dev << header + @body
|
447
511
|
else
|
@@ -485,30 +549,41 @@ module HTTP
|
|
485
549
|
private
|
486
550
|
|
487
551
|
def set_content(body, boundary = nil)
|
488
|
-
if
|
489
|
-
# uses Transfer-Encoding: chunked
|
490
|
-
# support it.
|
552
|
+
if Message.file?(body)
|
553
|
+
# uses Transfer-Encoding: chunked if body does not respond to :size.
|
554
|
+
# bear in mind that server may not support it. at least ruby's CGI doesn't.
|
491
555
|
@body = body
|
492
556
|
remember_pos(@body)
|
493
|
-
@size = nil
|
557
|
+
@size = body.respond_to?(:size) ? body.size - body.pos : nil
|
494
558
|
elsif boundary and Message.multiparam_query?(body)
|
495
559
|
@body = build_query_multipart_str(body, boundary)
|
496
560
|
@size = @body.size
|
497
561
|
else
|
498
562
|
@body = Message.create_query_part_str(body)
|
499
|
-
@size = @body.
|
563
|
+
@size = @body.bytesize
|
500
564
|
end
|
501
565
|
end
|
502
566
|
|
503
567
|
def remember_pos(io)
|
504
568
|
# IO may not support it (ex. IO.pipe)
|
505
|
-
@positions[io] = io.pos
|
569
|
+
@positions[io] = io.pos if io.respond_to?(:pos)
|
506
570
|
end
|
507
571
|
|
508
572
|
def reset_pos(io)
|
509
573
|
io.pos = @positions[io] if @positions.key?(io)
|
510
574
|
end
|
511
575
|
|
576
|
+
def dump_file(io, dev, sz)
|
577
|
+
buf = ''
|
578
|
+
rest = sz
|
579
|
+
while rest > 0
|
580
|
+
n = io.read([rest, @chunk_size].min, buf)
|
581
|
+
raise ArgumentError.new("Illegal size value: #size returns #{sz} but cannot read") if n.nil?
|
582
|
+
dev << buf
|
583
|
+
rest -= n.bytesize
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
512
587
|
def dump_chunks(io, dev)
|
513
588
|
buf = ''
|
514
589
|
while !io.read(@chunk_size, buf).nil?
|
@@ -517,7 +592,7 @@ module HTTP
|
|
517
592
|
end
|
518
593
|
|
519
594
|
def dump_chunk(str)
|
520
|
-
dump_chunk_size(str.
|
595
|
+
dump_chunk_size(str.bytesize) + (str + CRLF)
|
521
596
|
end
|
522
597
|
|
523
598
|
def dump_last_chunk
|
@@ -530,10 +605,12 @@ module HTTP
|
|
530
605
|
|
531
606
|
class Parts
|
532
607
|
attr_reader :size
|
608
|
+
attr_reader :sizes
|
533
609
|
|
534
610
|
def initialize
|
535
611
|
@body = []
|
536
|
-
@
|
612
|
+
@sizes = {}
|
613
|
+
@size = 0 # total
|
537
614
|
@as_stream = false
|
538
615
|
end
|
539
616
|
|
@@ -541,24 +618,27 @@ module HTTP
|
|
541
618
|
if Message.file?(part)
|
542
619
|
@as_stream = true
|
543
620
|
@body << part
|
544
|
-
if part.respond_to?(:
|
621
|
+
if part.respond_to?(:lstat)
|
622
|
+
sz = part.lstat.size
|
623
|
+
add_size(part, sz)
|
624
|
+
elsif part.respond_to?(:size)
|
545
625
|
if sz = part.size
|
546
|
-
|
626
|
+
add_size(part, sz)
|
547
627
|
else
|
628
|
+
@sizes.clear
|
548
629
|
@size = nil
|
549
630
|
end
|
550
|
-
elsif part.respond_to?(:lstat)
|
551
|
-
@size += part.lstat.size
|
552
631
|
else
|
553
632
|
# use chunked upload
|
633
|
+
@sizes.clear
|
554
634
|
@size = nil
|
555
635
|
end
|
556
636
|
elsif @body[-1].is_a?(String)
|
557
637
|
@body[-1] += part.to_s
|
558
|
-
@size += part.to_s.
|
638
|
+
@size += part.to_s.bytesize if @size
|
559
639
|
else
|
560
640
|
@body << part.to_s
|
561
|
-
@size += part.to_s.
|
641
|
+
@size += part.to_s.bytesize if @size
|
562
642
|
end
|
563
643
|
end
|
564
644
|
|
@@ -569,12 +649,20 @@ module HTTP
|
|
569
649
|
[@body.join]
|
570
650
|
end
|
571
651
|
end
|
652
|
+
|
653
|
+
private
|
654
|
+
|
655
|
+
def add_size(part, sz)
|
656
|
+
if @size
|
657
|
+
@sizes[part] = sz
|
658
|
+
@size += sz
|
659
|
+
end
|
660
|
+
end
|
572
661
|
end
|
573
662
|
|
574
663
|
def build_query_multipart_str(query, boundary)
|
575
664
|
parts = Parts.new
|
576
665
|
query.each do |attr, value|
|
577
|
-
value ||= ''
|
578
666
|
headers = ["--#{boundary}"]
|
579
667
|
if Message.file?(value)
|
580
668
|
remember_pos(value)
|
@@ -583,13 +671,24 @@ module HTTP
|
|
583
671
|
}.join("; ")
|
584
672
|
if value.respond_to?(:mime_type)
|
585
673
|
content_type = value.mime_type
|
674
|
+
elsif value.respond_to?(:content_type)
|
675
|
+
content_type = value.content_type
|
586
676
|
else
|
587
|
-
|
677
|
+
path = value.respond_to?(:path) ? value.path : nil
|
678
|
+
content_type = Message.mime_type(path)
|
588
679
|
end
|
589
680
|
headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
|
590
681
|
headers << %{Content-Type: #{content_type}}
|
682
|
+
elsif attr.is_a?(Hash)
|
683
|
+
h = attr
|
684
|
+
value = h[:content]
|
685
|
+
h.each do |h_key, h_val|
|
686
|
+
headers << %{#{h_key}: #{h_val}} if h_key != :content
|
687
|
+
end
|
688
|
+
remember_pos(value) if Message.file?(value)
|
591
689
|
else
|
592
690
|
headers << %{Content-Disposition: form-data; name="#{attr}"}
|
691
|
+
value = value.to_s
|
593
692
|
end
|
594
693
|
parts.add(headers.join(CRLF) + CRLF + CRLF)
|
595
694
|
parts.add(value)
|
@@ -601,7 +700,9 @@ module HTTP
|
|
601
700
|
|
602
701
|
def params_from_file(value)
|
603
702
|
params = {}
|
604
|
-
|
703
|
+
original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil
|
704
|
+
path = value.respond_to?(:path) ? value.path : nil
|
705
|
+
params['filename'] = original_filename || File.basename(path || '')
|
605
706
|
# Creation time is not available from File::Stat
|
606
707
|
if value.respond_to?(:mtime)
|
607
708
|
params['modification-date'] = value.mtime.rfc822
|
@@ -622,8 +723,8 @@ module HTTP
|
|
622
723
|
# uri:: an URI that need to connect. Only uri.host and uri.port are used.
|
623
724
|
def new_connect_request(uri)
|
624
725
|
m = new
|
625
|
-
m.
|
626
|
-
m.
|
726
|
+
m.http_header.init_connect_request(uri)
|
727
|
+
m.http_header.body_size = nil
|
627
728
|
m
|
628
729
|
end
|
629
730
|
|
@@ -642,26 +743,26 @@ module HTTP
|
|
642
743
|
# a multipart/form-data using this boundary String.
|
643
744
|
def new_request(method, uri, query = nil, body = nil, boundary = nil)
|
644
745
|
m = new
|
645
|
-
m.
|
646
|
-
m.
|
647
|
-
m.
|
746
|
+
m.http_header.init_request(method, uri, query)
|
747
|
+
m.http_body = Body.new
|
748
|
+
m.http_body.init_request(body || '', boundary)
|
648
749
|
if body
|
649
|
-
m.
|
650
|
-
m.
|
750
|
+
m.http_header.body_size = m.http_body.size
|
751
|
+
m.http_header.chunked = true if m.http_body.size.nil?
|
651
752
|
else
|
652
|
-
m.
|
753
|
+
m.http_header.body_size = nil
|
653
754
|
end
|
654
755
|
m
|
655
756
|
end
|
656
757
|
|
657
758
|
# Creates a Message instance of response.
|
658
759
|
# body:: a String or an IO of response message body.
|
659
|
-
def new_response(body)
|
760
|
+
def new_response(body, req = nil)
|
660
761
|
m = new
|
661
|
-
m.
|
662
|
-
m.
|
663
|
-
m.
|
664
|
-
m.
|
762
|
+
m.http_header.init_response(Status::OK, req)
|
763
|
+
m.http_body = Body.new
|
764
|
+
m.http_body.init_response(body)
|
765
|
+
m.http_header.body_size = m.http_body.size || 0
|
665
766
|
m
|
666
767
|
end
|
667
768
|
|
@@ -708,6 +809,8 @@ module HTTP
|
|
708
809
|
case path
|
709
810
|
when /\.txt$/i
|
710
811
|
'text/plain'
|
812
|
+
when /\.xml$/i
|
813
|
+
'text/xml'
|
711
814
|
when /\.(htm|html)$/i
|
712
815
|
'text/html'
|
713
816
|
when /\.doc$/i
|
@@ -724,9 +827,9 @@ module HTTP
|
|
724
827
|
end
|
725
828
|
|
726
829
|
# Returns true if the given HTTP version allows keep alive connection.
|
727
|
-
# version::
|
830
|
+
# version:: String
|
728
831
|
def keep_alive_enabled?(version)
|
729
|
-
version >= 1.1
|
832
|
+
version >= '1.1'
|
730
833
|
end
|
731
834
|
|
732
835
|
# Returns true if the given query (or body) has a multiple parameter.
|
@@ -736,15 +839,14 @@ module HTTP
|
|
736
839
|
|
737
840
|
# Returns true if the given object is a File. In HTTPClient, a file is;
|
738
841
|
# * must respond to :read for retrieving String chunks.
|
739
|
-
# * must respond to :path and returns a path for Content-Disposition.
|
740
842
|
# * must respond to :pos and :pos= to rewind for reading.
|
741
843
|
# Rewinding is only needed for following HTTP redirect. Some IO impl
|
742
844
|
# defines :pos= but raises an Exception for pos= such as StringIO
|
743
845
|
# but there's no problem as far as using it for non-following methods
|
744
846
|
# (get/post/etc.)
|
745
847
|
def file?(obj)
|
746
|
-
obj.respond_to?(:read) and obj.respond_to?(:
|
747
|
-
obj.respond_to?(:pos
|
848
|
+
obj.respond_to?(:read) and obj.respond_to?(:pos) and
|
849
|
+
obj.respond_to?(:pos=)
|
748
850
|
end
|
749
851
|
|
750
852
|
def create_query_part_str(query) # :nodoc:
|
@@ -757,50 +859,107 @@ module HTTP
|
|
757
859
|
end
|
758
860
|
end
|
759
861
|
|
862
|
+
def Array.try_convert(value)
|
863
|
+
return value if value.instance_of?(Array)
|
864
|
+
return nil if !value.respond_to?(:to_ary)
|
865
|
+
converted = value.to_ary
|
866
|
+
return converted if converted.instance_of?(Array)
|
867
|
+
|
868
|
+
cname = value.class.name
|
869
|
+
raise TypeError, "can't convert %s to %s (%s#%s gives %s)" %
|
870
|
+
[cname, Array.name, cname, :to_ary, converted.class.name]
|
871
|
+
end unless Array.respond_to?(:try_convert)
|
872
|
+
|
760
873
|
def escape_query(query) # :nodoc:
|
761
|
-
|
762
|
-
|
763
|
-
|
874
|
+
pairs = []
|
875
|
+
query.each { |attr, value|
|
876
|
+
left = escape(attr.to_s) << '='
|
877
|
+
if values = Array.try_convert(value)
|
878
|
+
values.each { |v|
|
879
|
+
if v.respond_to?(:read)
|
880
|
+
v = v.read
|
881
|
+
end
|
882
|
+
pairs.push(left + escape(v.to_s))
|
883
|
+
}
|
884
|
+
else
|
885
|
+
if value.respond_to?(:read)
|
886
|
+
value = value.read
|
887
|
+
end
|
888
|
+
pairs.push(left << escape(value.to_s))
|
764
889
|
end
|
765
|
-
|
766
|
-
|
890
|
+
}
|
891
|
+
pairs.join('&')
|
767
892
|
end
|
768
893
|
|
769
894
|
# from CGI.escape
|
770
|
-
|
771
|
-
str
|
772
|
-
|
773
|
-
|
895
|
+
if defined?(Encoding::ASCII_8BIT)
|
896
|
+
def escape(str) # :nodoc:
|
897
|
+
str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(/([^ a-zA-Z0-9_.-]+)/) {
|
898
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
899
|
+
}.tr(' ', '+')
|
900
|
+
end
|
901
|
+
else
|
902
|
+
def escape(str) # :nodoc:
|
903
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
904
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
905
|
+
}.tr(' ', '+')
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
# from CGI.parse
|
910
|
+
def parse(query)
|
911
|
+
params = Hash.new([].freeze)
|
912
|
+
query.split(/[&;]/n).each do |pairs|
|
913
|
+
key, value = pairs.split('=',2).collect{|v| unescape(v) }
|
914
|
+
if params.has_key?(key)
|
915
|
+
params[key].push(value)
|
916
|
+
else
|
917
|
+
params[key] = [value]
|
918
|
+
end
|
919
|
+
end
|
920
|
+
params
|
921
|
+
end
|
922
|
+
|
923
|
+
# from CGI.unescape
|
924
|
+
def unescape(string)
|
925
|
+
string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
|
926
|
+
[$1.delete('%')].pack('H*')
|
927
|
+
end
|
774
928
|
end
|
775
929
|
end
|
776
930
|
|
777
931
|
|
778
932
|
# HTTP::Message::Headers:: message header.
|
779
|
-
attr_accessor :
|
933
|
+
attr_accessor :http_header
|
780
934
|
|
781
935
|
# HTTP::Message::Body:: message body.
|
782
|
-
attr_reader :
|
936
|
+
attr_reader :http_body
|
783
937
|
|
784
938
|
# OpenSSL::X509::Certificate:: response only. server certificate which is
|
785
939
|
# used for retrieving the response.
|
786
940
|
attr_accessor :peer_cert
|
787
941
|
|
942
|
+
# The other Message object when this Message is generated instead of
|
943
|
+
# the Message because of redirection, negotiation, or format conversion.
|
944
|
+
attr_accessor :previous
|
945
|
+
|
788
946
|
# Creates a Message. This method should be used internally.
|
789
947
|
# Use Message.new_connect_request, Message.new_request or
|
790
948
|
# Message.new_response instead.
|
791
949
|
def initialize # :nodoc:
|
792
|
-
@
|
793
|
-
@
|
950
|
+
@http_header = Headers.new
|
951
|
+
@http_body = @peer_cert = nil
|
952
|
+
@previous = nil
|
794
953
|
end
|
795
954
|
|
796
955
|
# Dumps message (header and body) to given dev.
|
797
956
|
# dev needs to respond to <<.
|
798
957
|
def dump(dev = '')
|
799
|
-
str =
|
800
|
-
if
|
801
|
-
dev =
|
802
|
-
elsif
|
803
|
-
dev =
|
958
|
+
str = @http_header.dump + CRLF
|
959
|
+
if @http_header.chunked
|
960
|
+
dev = @http_body.dump_chunked(str, dev)
|
961
|
+
elsif @http_body
|
962
|
+
dev = @http_body.dump(str, dev)
|
804
963
|
else
|
805
964
|
dev << str
|
806
965
|
end
|
@@ -808,24 +967,36 @@ module HTTP
|
|
808
967
|
end
|
809
968
|
|
810
969
|
# Sets a new body. header.body_size is updated with new body.size.
|
811
|
-
def
|
812
|
-
@
|
813
|
-
@
|
970
|
+
def http_body=(body)
|
971
|
+
@http_body = body
|
972
|
+
@http_header.body_size = @http_body.size if @http_header
|
973
|
+
end
|
974
|
+
alias body= http_body=
|
975
|
+
|
976
|
+
# Returns HTTP version in a HTTP header. String.
|
977
|
+
def http_version
|
978
|
+
@http_header.http_version
|
979
|
+
end
|
980
|
+
|
981
|
+
# Sets HTTP version in a HTTP header. String.
|
982
|
+
def http_version=(http_version)
|
983
|
+
@http_header.http_version = http_version
|
814
984
|
end
|
815
985
|
|
816
|
-
#
|
986
|
+
VERSION_WARNING = 'Message#version (Float) is deprecated. Use Message#http_version (String) instead.'
|
817
987
|
def version
|
818
|
-
|
988
|
+
warning(VERSION_WARNING)
|
989
|
+
@http_header.http_version.to_f
|
819
990
|
end
|
820
991
|
|
821
|
-
# Sets HTTP version in a HTTP header. Float.
|
822
992
|
def version=(version)
|
823
|
-
|
993
|
+
warning(VERSION_WARNING)
|
994
|
+
@http_header.http_version = version
|
824
995
|
end
|
825
996
|
|
826
997
|
# Returns HTTP status code in response. Integer.
|
827
998
|
def status
|
828
|
-
@
|
999
|
+
@http_header.status_code
|
829
1000
|
end
|
830
1001
|
|
831
1002
|
alias code status
|
@@ -834,34 +1005,78 @@ module HTTP
|
|
834
1005
|
# Sets HTTP status code of response. Integer.
|
835
1006
|
# Reason phrase is updated, too.
|
836
1007
|
def status=(status)
|
837
|
-
@
|
1008
|
+
@http_header.status_code = status
|
838
1009
|
end
|
839
1010
|
|
840
1011
|
# Returns HTTP status reason phrase in response. String.
|
841
1012
|
def reason
|
842
|
-
@
|
1013
|
+
@http_header.reason_phrase
|
843
1014
|
end
|
844
1015
|
|
845
1016
|
# Sets HTTP status reason phrase of response. String.
|
846
1017
|
def reason=(reason)
|
847
|
-
@
|
1018
|
+
@http_header.reason_phrase = reason
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
# Returns 'Content-Type' header value.
|
1022
|
+
def content_type
|
1023
|
+
@http_header.content_type
|
848
1024
|
end
|
849
1025
|
|
850
1026
|
# Sets 'Content-Type' header value. Overrides if already exists.
|
851
|
-
def
|
852
|
-
@
|
1027
|
+
def content_type=(content_type)
|
1028
|
+
@http_header.content_type = content_type
|
853
1029
|
end
|
1030
|
+
alias contenttype content_type
|
1031
|
+
alias contenttype= content_type=
|
854
1032
|
|
855
|
-
# Returns
|
856
|
-
def
|
857
|
-
@
|
1033
|
+
# Returns content encoding
|
1034
|
+
def body_encoding
|
1035
|
+
@http_header.body_encoding
|
858
1036
|
end
|
859
1037
|
|
860
1038
|
# Returns a content of message body. A String or an IO.
|
861
1039
|
def content
|
862
|
-
@
|
1040
|
+
@http_body.content
|
863
1041
|
end
|
864
|
-
end
|
865
1042
|
|
1043
|
+
alias header http_header
|
1044
|
+
alias body content
|
1045
|
+
|
1046
|
+
# Returns Hash of header. key and value are both String. Each key has a
|
1047
|
+
# single value so you can't extract exact value when a message has multiple
|
1048
|
+
# headers like 'Set-Cookie'. Use header['Set-Cookie'] for that purpose.
|
1049
|
+
# (It returns an Array always)
|
1050
|
+
def headers
|
1051
|
+
Hash[*http_header.all.flatten]
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
# Extracts cookies from 'Set-Cookie' header.
|
1055
|
+
# Supports 'Set-Cookie' in response header only.
|
1056
|
+
# Do we need 'Cookie' support in request header?
|
1057
|
+
def cookies
|
1058
|
+
set_cookies = http_header['set-cookie']
|
1059
|
+
unless set_cookies.empty?
|
1060
|
+
uri = http_header.request_uri
|
1061
|
+
set_cookies.map { |str|
|
1062
|
+
WebAgent::Cookie.parse(str, uri)
|
1063
|
+
}.flatten
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
# Convenience method to return boolean of whether we had a successful request
|
1068
|
+
def ok?
|
1069
|
+
HTTP::Status.successful?(status)
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def redirect?
|
1073
|
+
HTTP::Status.redirect?(status)
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
# SEE_OTHER is a redirect, but it should sent as GET
|
1077
|
+
def see_other?
|
1078
|
+
status == HTTP::Status::SEE_OTHER
|
1079
|
+
end
|
1080
|
+
end
|
866
1081
|
|
867
1082
|
end
|