net-http 0.1.1 → 0.4.1

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.
@@ -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}