diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb index 6a74486..7f02980 100644 --- a/lib/cgi/core.rb +++ b/lib/cgi/core.rb @@ -72,91 +72,267 @@ def stdoutput private :env_table, :stdinput, :stdoutput - # Create an HTTP header block as a string. - # # :call-seq: - # http_header(content_type_string="text/html") - # http_header(headers_hash) - # - # Includes the empty line that ends the header block. + # http_header(content_type = 'text/html') -> string + # http_header(headers) -> string + # + # Creates and returns an HTTP header section as a multi-line string. + # + # The string always includes: + # + # - Header +Content-Type+ (with a default value if none given). + # - A trailing newline, which delimits the header block; + # that last line is omitted from the examples below. + # + # In Brief + # + # headers = { + # 'charset' => 'iso-2022-jp', + # 'connection' => 'keep-alive', + # 'cookie' => 'foo=0', + # 'expires' => Time.now + (60 * 60 * 24 * 365), + # 'language' => 'en-US, en-CA', + # 'length' => 4096, + # 'nph' => true, + # 'server' => 'Apache/2.4.1 (Unix)', + # 'status' => 'OK', + # 'type' => 'text/xml', + # MyHeader: true + # } + # + # puts cgi.http_header(headers) + # HTTP/1.0 200 OK + # Date: Mon, 01 Dec 2025 22:08:22 GMT + # Server: Apache/2.4.1 (Unix) + # Connection: keep-alive + # Content-Type: text/xml; charset=iso-2022-jp + # Content-Length: 4096 + # Content-Language: en-US, en-CA + # Expires: Tue, 01 Dec 2026 22:05:30 GMT + # Set-Cookie: foo=0 + # MyHeader: true + # + # headers.delete('nph') + # + # puts cgi.http_header(headers) + # Status: 200 OK + # Server: Apache/2.4.1 (Unix) + # Connection: keep-alive + # Content-Type: text/xml; charset=iso-2022-jp + # Content-Length: 4096 + # Content-Language: en-US, en-CA + # Expires: Tue, 01 Dec 2026 22:05:30 GMT + # Set-Cookie: foo=0 + # MyHeader: true + # + # Arguments + # + # With no argument given, + # includes only header +Content-Type+ with its default value 'text/html': + # + # puts cgi.http_header + # Content-Type: text/html + # + # With string argument +content_type+ given, + # includes header +Content-Type+ with its default value 'text/html': # - # +content_type_string+:: - # If this form is used, this string is the Content-Type - # +headers_hash+:: - # A Hash of header values. The following header keys are recognized: - # - # type:: The Content-Type header. Defaults to "text/html" - # charset:: The charset of the body, appended to the Content-Type header. - # nph:: A boolean value. If true, prepend protocol string and status - # code, and date; and sets default values for "server" and - # "connection" if not explicitly set. - # status:: - # The HTTP status code as a String, returned as the Status header. The - # values are: - # - # OK:: 200 OK - # PARTIAL_CONTENT:: 206 Partial Content - # MULTIPLE_CHOICES:: 300 Multiple Choices - # MOVED:: 301 Moved Permanently - # REDIRECT:: 302 Found - # NOT_MODIFIED:: 304 Not Modified - # BAD_REQUEST:: 400 Bad Request - # AUTH_REQUIRED:: 401 Authorization Required - # FORBIDDEN:: 403 Forbidden - # NOT_FOUND:: 404 Not Found - # METHOD_NOT_ALLOWED:: 405 Method Not Allowed - # NOT_ACCEPTABLE:: 406 Not Acceptable - # LENGTH_REQUIRED:: 411 Length Required - # PRECONDITION_FAILED:: 412 Precondition Failed - # SERVER_ERROR:: 500 Internal Server Error - # NOT_IMPLEMENTED:: 501 Method Not Implemented - # BAD_GATEWAY:: 502 Bad Gateway - # VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates - # - # server:: The server software, returned as the Server header. - # connection:: The connection type, returned as the Connection header (for - # instance, "close". - # length:: The length of the content that will be sent, returned as the - # Content-Length header. - # language:: The language of the content, returned as the Content-Language - # header. - # expires:: The time on which the current content expires, as a +Time+ - # object, returned as the Expires header. - # cookie:: - # A cookie or cookies, returned as one or more Set-Cookie headers. The - # value can be the literal string of the cookie; a CGI::Cookie object; - # an Array of literal cookie strings or Cookie objects; or a hash all of - # whose values are literal cookie strings or Cookie objects. - # - # These cookies are in addition to the cookies held in the - # @output_cookies field. - # - # Other headers can also be set; they are appended as key: value. - # - # Examples: - # - # http_header - # # Content-Type: text/html + # puts cgi.http_header('text/xml') + # Content-Type: text/xml + # + # With hash argument +headers+ given, + # includes a header for hash entry, whose name is based on the entry's key, + # and whose value is the entry's value. + # + # Recognized Keys + # + # The following keys are recognized; + # each is a lowercase string: + # + # 'charset':: + # The character set of the body; appended to the +Content-Type+ header: + # + # puts cgi.http_header('charset' => 'iso-2022-jp') + # Content-Type: text/html; charset=iso-2022-jp + # + # 'connection':: + # Sets header +Connection+ to the given string: + # + # puts cgi.http_header('connection' => 'keep-alive') + # Connection: keep-alive + # Content-Type: text/html + # + # 'cookie':: + # Sets one or more +Set-Cookie+ headers to the given value, which may be: + # + # - String cookie. + # - CGI::Cookie object. + # - Array of string cookies and CGI::Cookie objects. + # - A hash whose values are string cookies and CGI::Cookie objects + # (the keys are not used). + # + # Examples: + # + # foo_string = 'foo=0' + # bar_string = 'bar=1' + # foo_cookie = CGI::Cookie.new('foo', '0') + # bar_cookie = CGI::Cookie.new('bar', '1') + # + # puts cgi.http_header('cookie' => foo_string) + # Content-Type: text/html + # Set-Cookie: foo=0 + # + # puts cgi.http_header('cookie' => foo_cookie) + # Content-Type: text/html + # Set-Cookie: foo=0; path= + # + # puts cgi.http_header('cookie' => [foo_cookie, bar_string]) + # Content-Type: text/html + # Set-Cookie: foo=0; path= + # Set-Cookie: bar=1 + # + # puts cgi.http_header('cookie' => {foo: foo_cookie, bar: bar_string}) + # Content-Type: text/html + # Set-Cookie: foo=0; path= + # Set-Cookie: bar=1 + # + # These cookies are in addition to the cookies held + # in the @output_cookies variable. + # + # 'expires':: + # Sets header +Expires+ to the given time, + # which must be a {Time}[https://docs.ruby-lang.org/en/master/Time.html] object: + # + # puts cgi.http_header('expires' => Time.now + (60 * 60 * 24 * 365)) + # Content-Type: text/html + # Expires: Tue, 01 Dec 2026 23:42:37 GMT + # + # 'language':: + # Sets header +Content-Language+ to the given string: + # + # puts cgi.http_header('language' => 'en-US, en-CA') + # Content-Type: text/html + # Content-Language: en-US, en-CA + # + # 'length':: + # Sets header +Content-Length+ the given value, + # which may be an integer or a string: + # + # puts cgi.http_header('length' => 4096) + # Content-Type: text/html + # Content-Length: 4096 # - # http_header("text/plain") - # # Content-Type: text/plain + # puts cgi.http_header('length' => '4096') + # Content-Type: text/html + # Content-Length: 4096 # - # http_header("nph" => true, - # "status" => "OK", # == "200 OK" - # # "status" => "200 GOOD", - # "server" => ENV['SERVER_SOFTWARE'], - # "connection" => "close", - # "type" => "text/html", - # "charset" => "iso-2022-jp", - # # Content-Type: text/html; charset=iso-2022-jp - # "length" => 103, - # "language" => "ja", - # "expires" => Time.now + 30, - # "cookie" => [cookie1, cookie2], - # "my_header1" => "my_value", - # "my_header2" => "my_value") + # 'nph':: + # If +true+: + # + # - Adds protocol string and status code as first line, + # - Adds date as second line. + # - Adds headers +Server+ with no value, + # and +Connection+ with default value 'close'; + # either or both values may be overridden with explicit values. + # + # Examples: + # + # puts cgi.http_header('nph' => true) + # HTTP/1.0 200 OK + # Date: Mon, 01 Dec 2025 19:42:22 GMT + # Server: + # Connection: close + # Content-Type: text/html + # + # puts cgi.http_header('nph' => true, 'server' => 'Apache/2.4.1 (Unix)', 'connection' => 'keep-alive') + # HTTP/1.0 200 OK + # Date: Mon, 01 Dec 2025 20:00:41 GMT + # Server: Apache/2.4.1 (Unix) + # Connection: keep-alive + # Content-Type: text/html + # + # 'server':: + # Sets header +Server+ to the given string: + # + # puts cgi.http_header('server' => 'Apache/2.4.1 (Unix)') + # Server: Apache/2.4.1 (Unix) + # Content-Type: text/html + # + # 'status':: + # Sets header +Status+ to the given string: + # + # puts cgi.http_header('status' => '666 MyVeryOwnStatus') + # Status: 666 MyVeryOwnStatus + # Content-Type: text/html + # + # If the given string is a key in the hash constant +CGI::HTTP_STATUS+, + # the status becomes the value for that key: + # + # CGI::HTTP_STATUS + # # => + # {"OK" => "200 OK", + # "PARTIAL_CONTENT" => "206 Partial Content", + # "MULTIPLE_CHOICES" => "300 Multiple Choices", + # "MOVED" => "301 Moved Permanently", + # "REDIRECT" => "302 Found", + # "NOT_MODIFIED" => "304 Not Modified", + # "BAD_REQUEST" => "400 Bad Request", + # "AUTH_REQUIRED" => "401 Authorization Required", + # "FORBIDDEN" => "403 Forbidden", + # "NOT_FOUND" => "404 Not Found", + # "METHOD_NOT_ALLOWED" => "405 Method Not Allowed", + # "NOT_ACCEPTABLE" => "406 Not Acceptable", + # "LENGTH_REQUIRED" => "411 Length Required", + # "PRECONDITION_FAILED" => "412 Precondition Failed", + # "SERVER_ERROR" => "500 Internal Server Error", + # "NOT_IMPLEMENTED" => "501 Method Not Implemented", + # "BAD_GATEWAY" => "502 Bad Gateway", + # "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"} + # + # puts cgi.http_header('status' => 'OK') + # Status: 200 OK + # Content-Type: text/html + # + # puts cgi.http_header('status' => 'NOT_FOUND') + # Status: 404 Not Found + # Content-Type: text/html + # + # 'type':: + # Sets +Content-Type+, overriding the default value 'text/html': + # + # puts cgi.http_header('type' => 'text/xml') + # Content-Type: text/xml + # + # Unrecognized Keys + # + # Headers may also be set for unrecognized keys; + # an unrecognized key becomes a header name with the given value: + # + # puts cgi.http_header('length' => 0) # Recognized key (lowercase string). + # Content-Type: text/html + # Content-Length: 0 + # + # puts cgi.http_header('Length' => 0) # Unrecognized key (string key not lowercase). + # Content-Type: text/html + # Length: 0 + # + # puts cgi.http_header(length: 0) # Unrecognized key (symbol key not string) + # Content-Type: text/html + # length: 0 # # This method does not perform charset conversion. + # + # It's best to use this method (CGI#http_header), not its aliased method +header+, + # which is provided only for backward compatibility. + # + # Method CGI#http_header is preferred because when +tag_maker+ is 'html5', + # calling method +header+ generates an HTML +header+ element: + # + # cgi = CGI.new(tag_maker: 'html5') + # puts cgi.http_header # Works as expected. + # Content-Type: text/html + # puts cgi.header # Maybe a surprise. + #
+ # def http_header(options='text/html') if options.is_a?(String) content_type = options