net-http 0.1.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,188 @@
1
- # frozen_string_literal: false
2
- # The HTTPHeader module defines methods for reading and writing
3
- # HTTP headers.
1
+ # frozen_string_literal: true
4
2
  #
5
- # It is used as a mixin by other classes, to provide hash-like
6
- # access to HTTP header values. Unlike raw hash access, HTTPHeader
7
- # provides access via case-insensitive keys. It also provides
8
- # methods for accessing commonly-used HTTP header values in more
9
- # convenient formats.
3
+ # The \HTTPHeader module provides access to \HTTP headers.
4
+ #
5
+ # The module is included in:
6
+ #
7
+ # - Net::HTTPGenericRequest (and therefore Net::HTTPRequest).
8
+ # - Net::HTTPResponse.
9
+ #
10
+ # The headers are a hash-like collection of key/value pairs called _fields_.
11
+ #
12
+ # == Request and Response Fields
13
+ #
14
+ # Headers may be included in:
15
+ #
16
+ # - A Net::HTTPRequest object:
17
+ # the object's headers will be sent with the request.
18
+ # Any fields may be defined in the request;
19
+ # see {Setters}[rdoc-ref:Net::HTTPHeader@Setters].
20
+ # - A Net::HTTPResponse object:
21
+ # the objects headers are usually those returned from the host.
22
+ # Fields may be retrieved from the object;
23
+ # see {Getters}[rdoc-ref:Net::HTTPHeader@Getters]
24
+ # and {Iterators}[rdoc-ref:Net::HTTPHeader@Iterators].
25
+ #
26
+ # Exactly which fields should be sent or expected depends on the host;
27
+ # see:
28
+ #
29
+ # - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
30
+ # - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields].
31
+ #
32
+ # == About the Examples
33
+ #
34
+ # :include: doc/net-http/examples.rdoc
35
+ #
36
+ # == Fields
37
+ #
38
+ # A header field is a key/value pair.
39
+ #
40
+ # === Field Keys
41
+ #
42
+ # A field key may be:
43
+ #
44
+ # - A string: Key <tt>'Accept'</tt> is treated as if it were
45
+ # <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>.
46
+ # - A symbol: Key <tt>:Accept</tt> is treated as if it were
47
+ # <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>.
48
+ #
49
+ # Examples:
50
+ #
51
+ # req = Net::HTTP::Get.new(uri)
52
+ # req[:accept] # => "*/*"
53
+ # req['Accept'] # => "*/*"
54
+ # req['ACCEPT'] # => "*/*"
55
+ #
56
+ # req['accept'] = 'text/html'
57
+ # req[:accept] = 'text/html'
58
+ # req['ACCEPT'] = 'text/html'
59
+ #
60
+ # === Field Values
61
+ #
62
+ # A field value may be returned as an array of strings or as a string:
63
+ #
64
+ # - These methods return field values as arrays:
65
+ #
66
+ # - #get_fields: Returns the array value for the given key,
67
+ # or +nil+ if it does not exist.
68
+ # - #to_hash: Returns a hash of all header fields:
69
+ # each key is a field name; its value is the array value for the field.
70
+ #
71
+ # - These methods return field values as string;
72
+ # the string value for a field is equivalent to
73
+ # <tt>self[key.downcase.to_s].join(', '))</tt>:
74
+ #
75
+ # - #[]: Returns the string value for the given key,
76
+ # or +nil+ if it does not exist.
77
+ # - #fetch: Like #[], but accepts a default value
78
+ # to be returned if the key does not exist.
79
+ #
80
+ # The field value may be set:
81
+ #
82
+ # - #[]=: Sets the value for the given key;
83
+ # the given value may be a string, a symbol, an array, or a hash.
84
+ # - #add_field: Adds a given value to a value for the given key
85
+ # (not overwriting the existing value).
86
+ # - #delete: Deletes the field for the given key.
87
+ #
88
+ # Example field values:
89
+ #
90
+ # - \String:
91
+ #
92
+ # req['Accept'] = 'text/html' # => "text/html"
93
+ # req['Accept'] # => "text/html"
94
+ # req.get_fields('Accept') # => ["text/html"]
95
+ #
96
+ # - \Symbol:
97
+ #
98
+ # req['Accept'] = :text # => :text
99
+ # req['Accept'] # => "text"
100
+ # req.get_fields('Accept') # => ["text"]
101
+ #
102
+ # - Simple array:
103
+ #
104
+ # req[:foo] = %w[bar baz bat]
105
+ # req[:foo] # => "bar, baz, bat"
106
+ # req.get_fields(:foo) # => ["bar", "baz", "bat"]
107
+ #
108
+ # - Simple hash:
109
+ #
110
+ # req[:foo] = {bar: 0, baz: 1, bat: 2}
111
+ # req[:foo] # => "bar, 0, baz, 1, bat, 2"
112
+ # req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"]
113
+ #
114
+ # - Nested:
115
+ #
116
+ # req[:foo] = [%w[bar baz], {bat: 0, bam: 1}]
117
+ # req[:foo] # => "bar, baz, bat, 0, bam, 1"
118
+ # req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"]
119
+ #
120
+ # req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}}
121
+ # req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1"
122
+ # req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"]
123
+ #
124
+ # == Convenience Methods
125
+ #
126
+ # Various convenience methods retrieve values, set values, query values,
127
+ # set form values, or iterate over fields.
128
+ #
129
+ # === Setters
130
+ #
131
+ # \Method #[]= can set any field, but does little to validate the new value;
132
+ # some of the other setter methods provide some validation:
133
+ #
134
+ # - #[]=: Sets the string or array value for the given key.
135
+ # - #add_field: Creates or adds to the array value for the given key.
136
+ # - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>.
137
+ # - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>.
138
+ # - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>.
139
+ # - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>.
140
+ # - #set_range: Sets the value for field <tt>'Range'</tt>.
141
+ #
142
+ # === Form Setters
143
+ #
144
+ # - #set_form: Sets an HTML form data set.
145
+ # - #set_form_data: Sets header fields and a body from HTML form data.
146
+ #
147
+ # === Getters
148
+ #
149
+ # \Method #[] can retrieve the value of any field that exists,
150
+ # but always as a string;
151
+ # some of the other getter methods return something different
152
+ # from the simple string value:
153
+ #
154
+ # - #[]: Returns the string field value for the given key.
155
+ # - #content_length: Returns the integer value of field <tt>'Content-Length'</tt>.
156
+ # - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>.
157
+ # - #content_type: Returns the string value of field <tt>'Content-Type'</tt>.
158
+ # - #fetch: Returns the string field value for the given key.
159
+ # - #get_fields: Returns the array field value for the given +key+.
160
+ # - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>.
161
+ # - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>.
162
+ # - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+.
163
+ # - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>.
164
+ # - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>.
165
+ #
166
+ # === Queries
167
+ #
168
+ # - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>.
169
+ # - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>.
170
+ # - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>.
171
+ # - #key?: Returns whether a given key exists.
172
+ #
173
+ # === Iterators
174
+ #
175
+ # - #each_capitalized: Passes each field capitalized-name/value pair to the block.
176
+ # - #each_capitalized_name: Passes each capitalized field name to the block.
177
+ # - #each_header: Passes each field name/value pair to the block.
178
+ # - #each_name: Passes each field name to the block.
179
+ # - #each_value: Passes each string field value to the block.
10
180
  #
11
181
  module Net::HTTPHeader
182
+ MAX_KEY_LENGTH = 1024
183
+ MAX_FIELD_LENGTH = 65536
12
184
 
13
- def initialize_http_header(initheader)
185
+ def initialize_http_header(initheader) #:nodoc:
14
186
  @header = {}
15
187
  return unless initheader
16
188
  initheader.each do |key, value|
@@ -19,6 +191,12 @@ module Net::HTTPHeader
19
191
  warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE
20
192
  else
21
193
  value = value.strip # raise error for invalid byte sequences
194
+ if key.to_s.bytesize > MAX_KEY_LENGTH
195
+ raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
196
+ end
197
+ if value.to_s.bytesize > MAX_FIELD_LENGTH
198
+ raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
199
+ end
22
200
  if value.count("\r\n") > 0
23
201
  raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF"
24
202
  end
@@ -33,14 +211,32 @@ module Net::HTTPHeader
33
211
 
34
212
  alias length size #:nodoc: obsolete
35
213
 
36
- # Returns the header field corresponding to the case-insensitive key.
37
- # For example, a key of "Content-Type" might return "text/html"
214
+ # Returns the string field value for the case-insensitive field +key+,
215
+ # or +nil+ if there is no such key;
216
+ # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
217
+ #
218
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
219
+ # res['Connection'] # => "keep-alive"
220
+ # res['Nosuch'] # => nil
221
+ #
222
+ # Note that some field values may be retrieved via convenience methods;
223
+ # see {Getters}[rdoc-ref:Net::HTTPHeader@Getters].
38
224
  def [](key)
39
225
  a = @header[key.downcase.to_s] or return nil
40
226
  a.join(', ')
41
227
  end
42
228
 
43
- # Sets the header field corresponding to the case-insensitive key.
229
+ # Sets the value for the case-insensitive +key+ to +val+,
230
+ # overwriting the previous value if the field exists;
231
+ # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
232
+ #
233
+ # req = Net::HTTP::Get.new(uri)
234
+ # req['Accept'] # => "*/*"
235
+ # req['Accept'] = 'text/html'
236
+ # req['Accept'] # => "text/html"
237
+ #
238
+ # Note that some field values may be set via convenience methods;
239
+ # see {Setters}[rdoc-ref:Net::HTTPHeader@Setters].
44
240
  def []=(key, val)
45
241
  unless val
46
242
  @header.delete key.downcase.to_s
@@ -49,20 +245,18 @@ module Net::HTTPHeader
49
245
  set_field(key, val)
50
246
  end
51
247
 
52
- # [Ruby 1.8.3]
53
- # Adds a value to a named header field, instead of replacing its value.
54
- # Second argument +val+ must be a String.
55
- # See also #[]=, #[] and #get_fields.
248
+ # Adds value +val+ to the value array for field +key+ if the field exists;
249
+ # creates the field with the given +key+ and +val+ if it does not exist.
250
+ # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
56
251
  #
57
- # request.add_field 'X-My-Header', 'a'
58
- # p request['X-My-Header'] #=> "a"
59
- # p request.get_fields('X-My-Header') #=> ["a"]
60
- # request.add_field 'X-My-Header', 'b'
61
- # p request['X-My-Header'] #=> "a, b"
62
- # p request.get_fields('X-My-Header') #=> ["a", "b"]
63
- # request.add_field 'X-My-Header', 'c'
64
- # p request['X-My-Header'] #=> "a, b, c"
65
- # p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
252
+ # req = Net::HTTP::Get.new(uri)
253
+ # req.add_field('Foo', 'bar')
254
+ # req['Foo'] # => "bar"
255
+ # req.add_field('Foo', 'baz')
256
+ # req['Foo'] # => "bar, baz"
257
+ # req.add_field('Foo', %w[baz bam])
258
+ # req['Foo'] # => "bar, baz, baz, bam"
259
+ # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"]
66
260
  #
67
261
  def add_field(key, val)
68
262
  stringified_downcased_key = key.downcase.to_s
@@ -101,16 +295,13 @@ module Net::HTTPHeader
101
295
  end
102
296
  end
103
297
 
104
- # [Ruby 1.8.3]
105
- # Returns an array of header field strings corresponding to the
106
- # case-insensitive +key+. This method allows you to get duplicated
107
- # header fields without any processing. See also #[].
298
+ # Returns the array field value for the given +key+,
299
+ # or +nil+ if there is no such field;
300
+ # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
108
301
  #
109
- # p response.get_fields('Set-Cookie')
110
- # #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23",
111
- # "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"]
112
- # p response['Set-Cookie']
113
- # #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"
302
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
303
+ # res.get_fields('Connection') # => ["keep-alive"]
304
+ # res.get_fields('Nosuch') # => nil
114
305
  #
115
306
  def get_fields(key)
116
307
  stringified_downcased_key = key.downcase.to_s
@@ -118,24 +309,58 @@ module Net::HTTPHeader
118
309
  @header[stringified_downcased_key].dup
119
310
  end
120
311
 
121
- # Returns the header field corresponding to the case-insensitive key.
122
- # Returns the default value +args+, or the result of the block, or
123
- # raises an IndexError if there's no header field named +key+
124
- # See Hash#fetch
312
+ # call-seq:
313
+ # fetch(key, default_val = nil) {|key| ... } -> object
314
+ # fetch(key, default_val = nil) -> value or default_val
315
+ #
316
+ # With a block, returns the string value for +key+ if it exists;
317
+ # otherwise returns the value of the block;
318
+ # ignores the +default_val+;
319
+ # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
320
+ #
321
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
322
+ #
323
+ # # Field exists; block not called.
324
+ # res.fetch('Connection') do |value|
325
+ # fail 'Cannot happen'
326
+ # end # => "keep-alive"
327
+ #
328
+ # # Field does not exist; block called.
329
+ # res.fetch('Nosuch') do |value|
330
+ # value.downcase
331
+ # end # => "nosuch"
332
+ #
333
+ # With no block, returns the string value for +key+ if it exists;
334
+ # otherwise, returns +default_val+ if it was given;
335
+ # otherwise raises an exception:
336
+ #
337
+ # res.fetch('Connection', 'Foo') # => "keep-alive"
338
+ # res.fetch('Nosuch', 'Foo') # => "Foo"
339
+ # res.fetch('Nosuch') # Raises KeyError.
340
+ #
125
341
  def fetch(key, *args, &block) #:yield: +key+
126
342
  a = @header.fetch(key.downcase.to_s, *args, &block)
127
343
  a.kind_of?(Array) ? a.join(', ') : a
128
344
  end
129
345
 
130
- # Iterates through the header names and values, passing in the name
131
- # and value to the code block supplied.
346
+ # Calls the block with each key/value pair:
132
347
  #
133
- # Returns an enumerator if no block is given.
348
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
349
+ # res.each_header do |key, value|
350
+ # p [key, value] if key.start_with?('c')
351
+ # end
134
352
  #
135
- # Example:
353
+ # Output:
136
354
  #
137
- # response.header.each_header {|key,value| puts "#{key} = #{value}" }
355
+ # ["content-type", "application/json; charset=utf-8"]
356
+ # ["connection", "keep-alive"]
357
+ # ["cache-control", "max-age=43200"]
358
+ # ["cf-cache-status", "HIT"]
359
+ # ["cf-ray", "771d17e9bc542cf5-ORD"]
138
360
  #
361
+ # Returns an enumerator if no block is given.
362
+ #
363
+ # Net::HTTPHeader#each is an alias for Net::HTTPHeader#each_header.
139
364
  def each_header #:yield: +key+, +value+
140
365
  block_given? or return enum_for(__method__) { @header.size }
141
366
  @header.each do |k,va|
@@ -145,10 +370,24 @@ module Net::HTTPHeader
145
370
 
146
371
  alias each each_header
147
372
 
148
- # Iterates through the header names in the header, passing
149
- # each header name to the code block.
373
+ # Calls the block with each field key:
374
+ #
375
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
376
+ # res.each_key do |key|
377
+ # p key if key.start_with?('c')
378
+ # end
379
+ #
380
+ # Output:
381
+ #
382
+ # "content-type"
383
+ # "connection"
384
+ # "cache-control"
385
+ # "cf-cache-status"
386
+ # "cf-ray"
150
387
  #
151
388
  # Returns an enumerator if no block is given.
389
+ #
390
+ # Net::HTTPHeader#each_name is an alias for Net::HTTPHeader#each_key.
152
391
  def each_name(&block) #:yield: +key+
153
392
  block_given? or return enum_for(__method__) { @header.size }
154
393
  @header.each_key(&block)
@@ -156,12 +395,23 @@ module Net::HTTPHeader
156
395
 
157
396
  alias each_key each_name
158
397
 
159
- # Iterates through the header names in the header, passing
160
- # capitalized header names to the code block.
398
+ # Calls the block with each capitalized field name:
399
+ #
400
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
401
+ # res.each_capitalized_name do |key|
402
+ # p key if key.start_with?('C')
403
+ # end
404
+ #
405
+ # Output:
161
406
  #
162
- # Note that header names are capitalized systematically;
163
- # capitalization may not match that used by the remote HTTP
164
- # server in its response.
407
+ # "Content-Type"
408
+ # "Connection"
409
+ # "Cache-Control"
410
+ # "Cf-Cache-Status"
411
+ # "Cf-Ray"
412
+ #
413
+ # The capitalization is system-dependent;
414
+ # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html].
165
415
  #
166
416
  # Returns an enumerator if no block is given.
167
417
  def each_capitalized_name #:yield: +key+
@@ -171,8 +421,18 @@ module Net::HTTPHeader
171
421
  end
172
422
  end
173
423
 
174
- # Iterates through header values, passing each value to the
175
- # code block.
424
+ # Calls the block with each string field value:
425
+ #
426
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
427
+ # res.each_value do |value|
428
+ # p value if value.start_with?('c')
429
+ # end
430
+ #
431
+ # Output:
432
+ #
433
+ # "chunked"
434
+ # "cf-q-config;dur=6.0000002122251e-06"
435
+ # "cloudflare"
176
436
  #
177
437
  # Returns an enumerator if no block is given.
178
438
  def each_value #:yield: +value+
@@ -182,32 +442,45 @@ module Net::HTTPHeader
182
442
  end
183
443
  end
184
444
 
185
- # Removes a header field, specified by case-insensitive key.
445
+ # Removes the header for the given case-insensitive +key+
446
+ # (see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]);
447
+ # returns the deleted value, or +nil+ if no such field exists:
448
+ #
449
+ # req = Net::HTTP::Get.new(uri)
450
+ # req.delete('Accept') # => ["*/*"]
451
+ # req.delete('Nosuch') # => nil
452
+ #
186
453
  def delete(key)
187
454
  @header.delete(key.downcase.to_s)
188
455
  end
189
456
 
190
- # true if +key+ header exists.
457
+ # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise:
458
+ #
459
+ # req = Net::HTTP::Get.new(uri)
460
+ # req.key?('Accept') # => true
461
+ # req.key?('Nosuch') # => false
462
+ #
191
463
  def key?(key)
192
464
  @header.key?(key.downcase.to_s)
193
465
  end
194
466
 
195
- # Returns a Hash consisting of header names and array of values.
196
- # e.g.
197
- # {"cache-control" => ["private"],
198
- # "content-type" => ["text/html"],
199
- # "date" => ["Wed, 22 Jun 2005 22:11:50 GMT"]}
467
+ # Returns a hash of the key/value pairs:
468
+ #
469
+ # req = Net::HTTP::Get.new(uri)
470
+ # req.to_hash
471
+ # # =>
472
+ # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
473
+ # "accept"=>["*/*"],
474
+ # "user-agent"=>["Ruby"],
475
+ # "host"=>["jsonplaceholder.typicode.com"]}
476
+ #
200
477
  def to_hash
201
478
  @header.dup
202
479
  end
203
480
 
204
- # As for #each_header, except the keys are provided in capitalized form.
481
+ # Like #each_header, but the keys are returned in capitalized form.
205
482
  #
206
- # Note that header names are capitalized systematically;
207
- # capitalization may not match that used by the remote HTTP
208
- # server in its response.
209
- #
210
- # Returns an enumerator if no block is given.
483
+ # Net::HTTPHeader#canonical_each is an alias for Net::HTTPHeader#each_capitalized.
211
484
  def each_capitalized
212
485
  block_given? or return enum_for(__method__) { @header.size }
213
486
  @header.each do |k,v|
@@ -222,8 +495,17 @@ module Net::HTTPHeader
222
495
  end
223
496
  private :capitalize
224
497
 
225
- # Returns an Array of Range objects which represent the Range:
226
- # HTTP header field, or +nil+ if there is no such header.
498
+ # Returns an array of Range objects that represent
499
+ # the value of field <tt>'Range'</tt>,
500
+ # or +nil+ if there is no such field;
501
+ # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
502
+ #
503
+ # req = Net::HTTP::Get.new(uri)
504
+ # req['Range'] = 'bytes=0-99,200-299,400-499'
505
+ # req.range # => [0..99, 200..299, 400..499]
506
+ # req.delete('Range')
507
+ # req.range # # => nil
508
+ #
227
509
  def range
228
510
  return nil unless @header['range']
229
511
 
@@ -266,14 +548,31 @@ module Net::HTTPHeader
266
548
  result
267
549
  end
268
550
 
269
- # Sets the HTTP Range: header.
270
- # Accepts either a Range object as a single argument,
271
- # or a beginning index and a length from that index.
272
- # Example:
551
+ # call-seq:
552
+ # set_range(length) -> length
553
+ # set_range(offset, length) -> range
554
+ # set_range(begin..length) -> range
555
+ #
556
+ # Sets the value for field <tt>'Range'</tt>;
557
+ # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
558
+ #
559
+ # With argument +length+:
560
+ #
561
+ # req = Net::HTTP::Get.new(uri)
562
+ # req.set_range(100) # => 100
563
+ # req['Range'] # => "bytes=0-99"
273
564
  #
274
- # req.range = (0..1023)
275
- # req.set_range 0, 1023
565
+ # With arguments +offset+ and +length+:
276
566
  #
567
+ # req.set_range(100, 100) # => 100...200
568
+ # req['Range'] # => "bytes=100-199"
569
+ #
570
+ # With argument +range+:
571
+ #
572
+ # req.set_range(100..199) # => 100..199
573
+ # req['Range'] # => "bytes=100-199"
574
+ #
575
+ # Net::HTTPHeader#range= is an alias for Net::HTTPHeader#set_range.
277
576
  def set_range(r, e = nil)
278
577
  unless r
279
578
  @header.delete 'range'
@@ -305,8 +604,15 @@ module Net::HTTPHeader
305
604
 
306
605
  alias range= set_range
307
606
 
308
- # Returns an Integer object which represents the HTTP Content-Length:
309
- # header field, or +nil+ if that field was not provided.
607
+ # Returns the value of field <tt>'Content-Length'</tt> as an integer,
608
+ # or +nil+ if there is no such field;
609
+ # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]:
610
+ #
611
+ # res = Net::HTTP.get_response(hostname, '/nosuch/1')
612
+ # res.content_length # => 2
613
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
614
+ # res.content_length # => nil
615
+ #
310
616
  def content_length
311
617
  return nil unless key?('Content-Length')
312
618
  len = self['Content-Length'].slice(/\d+/) or
@@ -314,6 +620,20 @@ module Net::HTTPHeader
314
620
  len.to_i
315
621
  end
316
622
 
623
+ # Sets the value of field <tt>'Content-Length'</tt> to the given numeric;
624
+ # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]:
625
+ #
626
+ # _uri = uri.dup
627
+ # hostname = _uri.hostname # => "jsonplaceholder.typicode.com"
628
+ # _uri.path = '/posts' # => "/posts"
629
+ # req = Net::HTTP::Post.new(_uri) # => #<Net::HTTP::Post POST>
630
+ # req.body = '{"title": "foo","body": "bar","userId": 1}'
631
+ # req.content_length = req.body.size # => 42
632
+ # req.content_type = 'application/json'
633
+ # res = Net::HTTP.start(hostname) do |http|
634
+ # http.request(req)
635
+ # end # => #<Net::HTTPCreated 201 Created readbody=true>
636
+ #
317
637
  def content_length=(len)
318
638
  unless len
319
639
  @header.delete 'content-length'
@@ -322,53 +642,99 @@ module Net::HTTPHeader
322
642
  @header['content-length'] = [len.to_i.to_s]
323
643
  end
324
644
 
325
- # Returns "true" if the "transfer-encoding" header is present and
326
- # set to "chunked". This is an HTTP/1.1 feature, allowing
327
- # the content to be sent in "chunks" without at the outset
328
- # stating the entire content length.
645
+ # Returns +true+ if field <tt>'Transfer-Encoding'</tt>
646
+ # exists and has value <tt>'chunked'</tt>,
647
+ # +false+ otherwise;
648
+ # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]:
649
+ #
650
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
651
+ # res['Transfer-Encoding'] # => "chunked"
652
+ # res.chunked? # => true
653
+ #
329
654
  def chunked?
330
655
  return false unless @header['transfer-encoding']
331
656
  field = self['Transfer-Encoding']
332
657
  (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
333
658
  end
334
659
 
335
- # Returns a Range object which represents the value of the Content-Range:
336
- # header field.
337
- # For a partial entity body, this indicates where this fragment
338
- # fits inside the full entity body, as range of byte offsets.
660
+ # Returns a Range object representing the value of field
661
+ # <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
662
+ # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
663
+ #
664
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
665
+ # res['Content-Range'] # => nil
666
+ # res['Content-Range'] = 'bytes 0-499/1000'
667
+ # res['Content-Range'] # => "bytes 0-499/1000"
668
+ # res.content_range # => 0..499
669
+ #
339
670
  def content_range
340
671
  return nil unless @header['content-range']
341
- m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or
672
+ m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or
342
673
  raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format'
343
- m[1].to_i .. m[2].to_i
674
+ return unless m[1] == 'bytes'
675
+ m[2].to_i .. m[3].to_i
344
676
  end
345
677
 
346
- # The length of the range represented in Content-Range: header.
678
+ # Returns the integer representing length of the value of field
679
+ # <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
680
+ # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
681
+ #
682
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
683
+ # res['Content-Range'] # => nil
684
+ # res['Content-Range'] = 'bytes 0-499/1000'
685
+ # res.range_length # => 500
686
+ #
347
687
  def range_length
348
688
  r = content_range() or return nil
349
689
  r.end - r.begin + 1
350
690
  end
351
691
 
352
- # Returns a content type string such as "text/html".
353
- # This method returns nil if Content-Type: header field does not exist.
692
+ # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type]
693
+ # from the value of field <tt>'Content-Type'</tt>,
694
+ # or +nil+ if no such field exists;
695
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
696
+ #
697
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
698
+ # res['content-type'] # => "application/json; charset=utf-8"
699
+ # res.content_type # => "application/json"
700
+ #
354
701
  def content_type
355
- return nil unless main_type()
356
- if sub_type()
357
- then "#{main_type()}/#{sub_type()}"
358
- else main_type()
702
+ main = main_type()
703
+ return nil unless main
704
+
705
+ sub = sub_type()
706
+ if sub
707
+ "#{main}/#{sub}"
708
+ else
709
+ main
359
710
  end
360
711
  end
361
712
 
362
- # Returns a content type string such as "text".
363
- # This method returns nil if Content-Type: header field does not exist.
713
+ # Returns the leading ('type') part of the
714
+ # {media type}[https://en.wikipedia.org/wiki/Media_type]
715
+ # from the value of field <tt>'Content-Type'</tt>,
716
+ # or +nil+ if no such field exists;
717
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
718
+ #
719
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
720
+ # res['content-type'] # => "application/json; charset=utf-8"
721
+ # res.main_type # => "application"
722
+ #
364
723
  def main_type
365
724
  return nil unless @header['content-type']
366
725
  self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
367
726
  end
368
727
 
369
- # Returns a content type string such as "html".
370
- # This method returns nil if Content-Type: header field does not exist
371
- # or sub-type is not given (e.g. "Content-Type: text").
728
+ # Returns the trailing ('subtype') part of the
729
+ # {media type}[https://en.wikipedia.org/wiki/Media_type]
730
+ # from the value of field <tt>'Content-Type'</tt>,
731
+ # or +nil+ if no such field exists;
732
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
733
+ #
734
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
735
+ # res['content-type'] # => "application/json; charset=utf-8"
736
+ # res.sub_type # => "json"
737
+ #
372
738
  def sub_type
373
739
  return nil unless @header['content-type']
374
740
  _, sub = *self['Content-Type'].split(';').first.to_s.split('/')
@@ -376,9 +742,14 @@ module Net::HTTPHeader
376
742
  sub.strip
377
743
  end
378
744
 
379
- # Any parameters specified for the content type, returned as a Hash.
380
- # For example, a header of Content-Type: text/html; charset=EUC-JP
381
- # would result in type_params returning {'charset' => 'EUC-JP'}
745
+ # Returns the trailing ('parameters') part of the value of field <tt>'Content-Type'</tt>,
746
+ # or +nil+ if no such field exists;
747
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
748
+ #
749
+ # res = Net::HTTP.get_response(hostname, '/todos/1')
750
+ # res['content-type'] # => "application/json; charset=utf-8"
751
+ # res.type_params # => {"charset"=>"utf-8"}
752
+ #
382
753
  def type_params
383
754
  result = {}
384
755
  list = self['Content-Type'].to_s.split(';')
@@ -390,29 +761,54 @@ module Net::HTTPHeader
390
761
  result
391
762
  end
392
763
 
393
- # Sets the content type in an HTTP header.
394
- # The +type+ should be a full HTTP content type, e.g. "text/html".
395
- # The +params+ are an optional Hash of parameters to add after the
396
- # content type, e.g. {'charset' => 'iso-8859-1'}
764
+ # Sets the value of field <tt>'Content-Type'</tt>;
765
+ # returns the new value;
766
+ # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]:
767
+ #
768
+ # req = Net::HTTP::Get.new(uri)
769
+ # req.set_content_type('application/json') # => ["application/json"]
770
+ #
771
+ # Net::HTTPHeader#content_type= is an alias for Net::HTTPHeader#set_content_type.
397
772
  def set_content_type(type, params = {})
398
773
  @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
399
774
  end
400
775
 
401
776
  alias content_type= set_content_type
402
777
 
403
- # Set header fields and a body from HTML form data.
404
- # +params+ should be an Array of Arrays or
405
- # a Hash containing HTML form data.
406
- # Optional argument +sep+ means data record separator.
778
+ # Sets the request body to a URL-encoded string derived from argument +params+,
779
+ # and sets request header field <tt>'Content-Type'</tt>
780
+ # to <tt>'application/x-www-form-urlencoded'</tt>.
781
+ #
782
+ # The resulting request is suitable for HTTP request +POST+ or +PUT+.
783
+ #
784
+ # Argument +params+ must be suitable for use as argument +enum+ to
785
+ # {URI.encode_www_form}[https://docs.ruby-lang.org/en/master/URI.html#method-c-encode_www_form].
786
+ #
787
+ # With only argument +params+ given,
788
+ # sets the body to a URL-encoded string with the default separator <tt>'&'</tt>:
789
+ #
790
+ # req = Net::HTTP::Post.new('example.com')
791
+ #
792
+ # req.set_form_data(q: 'ruby', lang: 'en')
793
+ # req.body # => "q=ruby&lang=en"
794
+ # req['Content-Type'] # => "application/x-www-form-urlencoded"
407
795
  #
408
- # Values are URL encoded as necessary and the content-type is set to
409
- # application/x-www-form-urlencoded
796
+ # req.set_form_data([['q', 'ruby'], ['lang', 'en']])
797
+ # req.body # => "q=ruby&lang=en"
410
798
  #
411
- # Example:
412
- # http.form_data = {"q" => "ruby", "lang" => "en"}
413
- # http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"}
414
- # http.set_form_data({"q" => "ruby", "lang" => "en"}, ';')
799
+ # req.set_form_data(q: ['ruby', 'perl'], lang: 'en')
800
+ # req.body # => "q=ruby&q=perl&lang=en"
415
801
  #
802
+ # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']])
803
+ # req.body # => "q=ruby&q=perl&lang=en"
804
+ #
805
+ # With string argument +sep+ also given,
806
+ # uses that string as the separator:
807
+ #
808
+ # req.set_form_data({q: 'ruby', lang: 'en'}, '|')
809
+ # req.body # => "q=ruby|lang=en"
810
+ #
811
+ # Net::HTTPHeader#form_data= is an alias for Net::HTTPHeader#set_form_data.
416
812
  def set_form_data(params, sep = '&')
417
813
  query = URI.encode_www_form(params)
418
814
  query.gsub!(/&/, sep) if sep != '&'
@@ -422,53 +818,108 @@ module Net::HTTPHeader
422
818
 
423
819
  alias form_data= set_form_data
424
820
 
425
- # Set an HTML form data set.
426
- # +params+ :: The form data to set, which should be an enumerable.
427
- # See below for more details.
428
- # +enctype+ :: The content type to use to encode the form submission,
429
- # which should be application/x-www-form-urlencoded or
430
- # multipart/form-data.
431
- # +formopt+ :: An options hash, supporting the following options:
432
- # :boundary :: The boundary of the multipart message. If
433
- # not given, a random boundary will be used.
434
- # :charset :: The charset of the form submission. All
435
- # field names and values of non-file fields
436
- # should be encoded with this charset.
437
- #
438
- # Each item of params should respond to +each+ and yield 2-3 arguments,
439
- # or an array of 2-3 elements. The arguments yielded should be:
440
- # * The name of the field.
441
- # * The value of the field, it should be a String or a File or IO-like.
442
- # * An options hash, supporting the following options, only
443
- # used for file uploads:
444
- # :filename :: The name of the file to use.
445
- # :content_type :: The content type of the uploaded file.
446
- #
447
- # Each item is a file field or a normal field.
448
- # If +value+ is a File object or the +opt+ hash has a :filename key,
449
- # the item is treated as a file field.
450
- #
451
- # If Transfer-Encoding is set as chunked, this sends the request using
452
- # chunked encoding. Because chunked encoding is HTTP/1.1 feature,
453
- # you should confirm that the server supports HTTP/1.1 before using
454
- # chunked encoding.
455
- #
456
- # Example:
457
- # req.set_form([["q", "ruby"], ["lang", "en"]])
458
- #
459
- # req.set_form({"f"=>File.open('/path/to/filename')},
460
- # "multipart/form-data",
461
- # charset: "UTF-8",
462
- # )
463
- #
464
- # req.set_form([["f",
465
- # File.open('/path/to/filename.bar'),
466
- # {filename: "other-filename.foo"}
467
- # ]],
468
- # "multipart/form-data",
469
- # )
470
- #
471
- # See also RFC 2388, RFC 2616, HTML 4.01, and HTML5
821
+ # Stores form data to be used in a +POST+ or +PUT+ request.
822
+ #
823
+ # The form data given in +params+ consists of zero or more fields;
824
+ # each field is:
825
+ #
826
+ # - A scalar value.
827
+ # - A name/value pair.
828
+ # - An IO stream opened for reading.
829
+ #
830
+ # Argument +params+ should be an
831
+ # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
832
+ # (method <tt>params.map</tt> will be called),
833
+ # and is often an array or hash.
834
+ #
835
+ # First, we set up a request:
836
+ #
837
+ # _uri = uri.dup
838
+ # _uri.path ='/posts'
839
+ # req = Net::HTTP::Post.new(_uri)
840
+ #
841
+ # <b>Argument +params+ As an Array</b>
842
+ #
843
+ # When +params+ is an array,
844
+ # each of its elements is a subarray that defines a field;
845
+ # the subarray may contain:
846
+ #
847
+ # - One string:
848
+ #
849
+ # req.set_form([['foo'], ['bar'], ['baz']])
850
+ #
851
+ # - Two strings:
852
+ #
853
+ # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]])
854
+ #
855
+ # - When argument +enctype+ (see below) is given as
856
+ # <tt>'multipart/form-data'</tt>:
857
+ #
858
+ # - A string name and an IO stream opened for reading:
859
+ #
860
+ # require 'stringio'
861
+ # req.set_form([['file', StringIO.new('Ruby is cool.')]])
862
+ #
863
+ # - A string name, an IO stream opened for reading,
864
+ # and an options hash, which may contain these entries:
865
+ #
866
+ # - +:filename+: The name of the file to use.
867
+ # - +:content_type+: The content type of the uploaded file.
868
+ #
869
+ # Example:
870
+ #
871
+ # req.set_form([['file', file, {filename: "other-filename.foo"}]]
872
+ #
873
+ # The various forms may be mixed:
874
+ #
875
+ # req.set_form(['foo', %w[bar 1], ['file', file]])
876
+ #
877
+ # <b>Argument +params+ As a Hash</b>
878
+ #
879
+ # When +params+ is a hash,
880
+ # each of its entries is a name/value pair that defines a field:
881
+ #
882
+ # - The name is a string.
883
+ # - The value may be:
884
+ #
885
+ # - +nil+.
886
+ # - Another string.
887
+ # - An IO stream opened for reading
888
+ # (only when argument +enctype+ -- see below -- is given as
889
+ # <tt>'multipart/form-data'</tt>).
890
+ #
891
+ # Examples:
892
+ #
893
+ # # Nil-valued fields.
894
+ # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil})
895
+ #
896
+ # # String-valued fields.
897
+ # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2})
898
+ #
899
+ # # IO-valued field.
900
+ # require 'stringio'
901
+ # req.set_form({'file' => StringIO.new('Ruby is cool.')})
902
+ #
903
+ # # Mixture of fields.
904
+ # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file})
905
+ #
906
+ # Optional argument +enctype+ specifies the value to be given
907
+ # to field <tt>'Content-Type'</tt>, and must be one of:
908
+ #
909
+ # - <tt>'application/x-www-form-urlencoded'</tt> (the default).
910
+ # - <tt>'multipart/form-data'</tt>;
911
+ # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578].
912
+ #
913
+ # Optional argument +formopt+ is a hash of options
914
+ # (applicable only when argument +enctype+
915
+ # is <tt>'multipart/form-data'</tt>)
916
+ # that may include the following entries:
917
+ #
918
+ # - +:boundary+: The value is the boundary string for the multipart message.
919
+ # If not given, the boundary is a random string.
920
+ # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1].
921
+ # - +:charset+: Value is the character set for the form submission.
922
+ # Field names and values of non-file fields should be encoded with this charset.
472
923
  #
473
924
  def set_form(params, enctype='application/x-www-form-urlencoded', formopt={})
474
925
  @body_data = params
@@ -484,12 +935,24 @@ module Net::HTTPHeader
484
935
  end
485
936
  end
486
937
 
487
- # Set the Authorization: header for "Basic" authorization.
938
+ # Sets header <tt>'Authorization'</tt> using the given
939
+ # +account+ and +password+ strings:
940
+ #
941
+ # req.basic_auth('my_account', 'my_password')
942
+ # req['Authorization']
943
+ # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
944
+ #
488
945
  def basic_auth(account, password)
489
946
  @header['authorization'] = [basic_encode(account, password)]
490
947
  end
491
948
 
492
- # Set Proxy-Authorization: header for "Basic" authorization.
949
+ # Sets header <tt>'Proxy-Authorization'</tt> using the given
950
+ # +account+ and +password+ strings:
951
+ #
952
+ # req.proxy_basic_auth('my_account', 'my_password')
953
+ # req['Proxy-Authorization']
954
+ # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
955
+ #
493
956
  def proxy_basic_auth(account, password)
494
957
  @header['proxy-authorization'] = [basic_encode(account, password)]
495
958
  end
@@ -499,6 +962,7 @@ module Net::HTTPHeader
499
962
  end
500
963
  private :basic_encode
501
964
 
965
+ # Returns whether the HTTP session is to be closed.
502
966
  def connection_close?
503
967
  token = /(?:\A|,)\s*close\s*(?:\z|,)/i
504
968
  @header['connection']&.grep(token) {return true}
@@ -506,6 +970,7 @@ module Net::HTTPHeader
506
970
  false
507
971
  end
508
972
 
973
+ # Returns whether the HTTP session is to be kept alive.
509
974
  def connection_keep_alive?
510
975
  token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
511
976
  @header['connection']&.grep(token) {return true}