httpclient 2.1.5 → 2.8.3
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 +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
|