From d28f51fb1ea027faf72a3dd2ad64d76209d57e89 Mon Sep 17 00:00:00 2001 From: kjnash Date: Fri, 9 Sep 2022 08:04:55 +0000 Subject: Fix bug [2927221] - revised http::meta, new http::metaValue, header names are case-insensitive so convert to lower case. --- doc/http.n | 71 ++++++++++++++++++++++++++++++++++----------------- library/http/http.tcl | 41 ++++++++++++++++++++++++++--- tests/http.test | 8 +++--- tests/http11.test | 22 ++++++++-------- 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/doc/http.n b/doc/http.n index f0018e3..135774d 100644 --- a/doc/http.n +++ b/doc/http.n @@ -38,7 +38,9 @@ http \- Client-side implementation of the HTTP/1.1 protocol .sp \fB::http::reason \fIcode\fR .sp -\fB::http::meta \fItoken\fR +\fB::http::meta \fItoken\fR ?\fIheaderName\fR? +.sp +\fB::http::metaValue\fR \fItoken\fR \fIheaderName\fR .sp \fB::http::data \fItoken\fR .sp @@ -58,7 +60,8 @@ Namespace \fBhttp\fR exports the commands \fBconfig\fR, \fBformatQuery\fR, \fBreset\fR, \fBunregister\fR, and \fBwait\fR. .PP It does not export the commands \fBcleanup\fR, \fBcode\fR, \fBdata\fR, -\fBerror\fR, \fBmeta\fR, \fBncode\fR, \fBsize\fR, or \fBstatus\fR. +\fBerror\fR, \fBmeta\fR, \fBmetaValue\fR, \fBncode\fR, \fBreason\fR, +\fBsize\fR, or \fBstatus\fR. .BE .SH DESCRIPTION .PP @@ -357,7 +360,7 @@ multiple interfaces are available. The \fIcallback\fR is made after each transfer of data from the URL. The callback gets three additional arguments: the \fItoken\fR from \fB::http::geturl\fR, the expected total size of the contents from the -\fBContent-Length\fR meta-data, and the current number of bytes +\fBContent-Length\fR metadata, and the current number of bytes transferred so far. The expected total size may be unknown, in which case zero is passed to the callback. Here is a template for the progress callback: @@ -547,11 +550,31 @@ This is a convenience procedure that returns the \fBcurrentsize\fR element of the state array, which represents the number of bytes received from the URL in the \fB::http::geturl\fR call. .TP -\fB::http::meta\fR \fItoken\fR -. -This is a convenience procedure that returns the \fBmeta\fR -element of the state array which contains the HTTP response -headers. See below for an explanation of this element. +\fB::http::meta\fR \fItoken\fR ?\fIheaderName\fR? +. +This command returns a list of HTTP response header names and values, in the +order that they were received from the server: a Tcl list of the form +?name value ...? Header names are case-insensitive and are converted to lower +case. The return value is not a \fBdict\fR because some header names may occur +more than once, notably \fIset-cookie\fR. If one argument is supplied, all +response headers are returned: the value is that of the \fBmeta\fR element +of the state array (described below). If two arguments are supplied, the +second provides the value of a header name. Only headers with the requested +name (converted to lower case) are returned. If no such headers are found, +an empty list is returned. +.TP +\fB::http::metaValue\fR \fItoken\fR \fIheaderName\fR +. +This command returns the value of the HTTP response header named +\fIheaderName\fR. Header names are case-insensitive and are converted to +lower case. If no such header exists, the return value is the empty string. +If there are multiple headers named \fIheaderName\fR, the result is obtained +by joining the individual values with the string ", " (comma and space), +preserving their order. Multiple headers with the same name may be processed +in this manner, except \fIset-cookie\fR which does not conform to the +comma-separated-list syntax and cannot be combined into a single value. +Each \fIset-cookie\fR header must be treated individually, e.g. by processing +the return value of \fB::http::meta\fR \fIset-cookie\fR. .TP \fB::http::cleanup\fR \fItoken\fR . @@ -713,14 +736,14 @@ command. .TP \fBcharset\fR . -The value of the charset attribute from the \fBContent-Type\fR meta-data +The value of the charset attribute from the \fBContent-Type\fR metadata value. If none was specified, this defaults to the RFC standard \fBiso8859-1\fR, or the value of \fB$::http::defaultCharset\fR. Incoming text data will be automatically converted from this charset to utf-8. .TP \fBcoding\fR . -A copy of the \fBContent-Encoding\fR meta-data value. +A copy of the \fBContent-Encoding\fR metadata value. .TP \fBcurrentsize\fR . @@ -745,23 +768,23 @@ is returned by the \fB::http::code\fR command. The format of this value is: The \fIcode\fR is a three-digit number defined in the HTTP standard. A code of 200 is OK. Codes beginning with 4 or 5 indicate errors. Codes beginning with 3 are redirection errors. In this case the -\fBLocation\fR meta-data specifies a new URL that contains the +\fBLocation\fR metadata specifies a new URL that contains the requested information. .RE .TP \fBmeta\fR . -The HTTP protocol returns meta-data that describes the URL contents. -The \fBmeta\fR element of the state array is a list of the keys and -values of the meta-data. This is in a format useful for initializing -an array that just contains the meta-data: -.RS +The response from a HTTP server includes metadata headers that describe the +response body and the message from the server. The \fBmeta\fR element of the +state array is a list of the keys (header names) and values (header values) of +the metadata. Header names are case-insensitive and are converted to lower +case. The value of meta is not a \fBdict\fR because some header names may +occur more than once, notably "set-cookie". If the value \fBmeta\fR is read +into a dict or into an array (using array set), only the last header with each +name will be preserved. .PP -.CS -array set meta $state(meta) -.CE -.PP -Some of the meta-data keys are listed below, but the HTTP standard defines +.RS +Some of the metadata keys are listed below, but the HTTP standard defines more, and servers are free to add their own. .TP \fBContent-Type\fR @@ -793,11 +816,11 @@ During the transaction this value is the empty string. .TP \fBtotalsize\fR . -A copy of the \fBContent-Length\fR meta-data value. +A copy of the \fBContent-Length\fR metadata value. .TP \fBtype\fR . -A copy of the \fBContent-Type\fR meta-data value. +A copy of the \fBContent-Type\fR metadata value. .TP \fBurl\fR . @@ -1057,7 +1080,7 @@ The peer thread can transfer the socket only to the main interpreter of the scri .SH EXAMPLE .PP This example creates a procedure to copy a URL to a file while printing a -progress meter, and prints the meta-data associated with the URL. +progress meter, and prints the metadata associated with the URL. .PP .CS proc httpcopy { url file {chunk 4096} } { diff --git a/library/http/http.tcl b/library/http/http.tcl index 67f0309..ba8e1ab 100644 --- a/library/http/http.tcl +++ b/library/http/http.tcl @@ -2962,11 +2962,45 @@ proc http::size {token} { upvar 0 $token state return $state(currentsize) } -proc http::meta {token} { +proc http::meta {token args} { + set lenny [llength $args] + if {$lenny > 1} { + return -code error {usage: ::http::meta token ?headerName?} + } else { + return [Meta $token {*}$args] + } +} +proc http::metaValue {token header} { + Meta $token $header VALUE +} +proc http::Meta {token args} { variable $token upvar 0 $token state - return $state(meta) + + set header [string tolower [lindex $args 0]] + set how [string tolower [lindex $args 1]] + set lenny [llength $args] + if {$lenny == 0} { + return $state(meta) + } elseif {($lenny > 2) || (($lenny == 2) && ($how ne {value}))} { + return -code error {usage: ::http::Meta token ?headerName ?VALUE??} + } else { + set result {} + set combined {} + foreach {key value} $state(meta) { + if {$key eq $header} { + lappend result $key $value + append combined $value {, } + } + } + if {$lenny == 1} { + return $result + } else { + return [string range $combined 0 end-2] + } + } } + proc http::error {token} { variable $token upvar 0 $token state @@ -3445,7 +3479,8 @@ proc http::Event {sock token} { # Process header lines. ##Log header - token $token - $line if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} { - switch -- [string tolower $key] { + set key [string tolower $key] + switch -- $key { content-type { set state(type) [string trim [string tolower $value]] # Grab the optional charset information. diff --git a/tests/http.test b/tests/http.test index 26ba710..08f6311 100644 --- a/tests/http.test +++ b/tests/http.test @@ -390,7 +390,7 @@ test http-3.25 {http::meta} -setup { } -cleanup { http::cleanup $token unset -nocomplain m token -} -result {Content-Length Content-Type Date} +} -result {content-length content-type date} test http-3.26 {http::meta} -setup { unset -nocomplain m token } -body { @@ -400,7 +400,7 @@ test http-3.26 {http::meta} -setup { } -cleanup { http::cleanup $token unset -nocomplain m token -} -result {Content-Length Content-Type Date X-Check} +} -result {content-length content-type date x-check} test http-3.27 {http::geturl: -headers override -type} -body { set token [http::geturl $url/headers -type "text/plain" -query dummy \ -headers [list "Content-Type" "text/plain;charset=utf-8"]] @@ -485,7 +485,7 @@ test http-4.1 {http::Event} -body { set token [http::geturl $url -keepalive 0] upvar #0 $token data array set meta $data(meta) - expr {($data(totalsize) == $meta(Content-Length))} + expr {($data(totalsize) == $meta(content-length))} } -cleanup { http::cleanup $token } -result 1 @@ -493,7 +493,7 @@ test http-4.2 {http::Event} -body { set token [http::geturl $url] upvar #0 $token data array set meta $data(meta) - string compare $data(type) [string trim $meta(Content-Type)] + string compare $data(type) [string trim $meta(content-type)] } -cleanup { http::cleanup $token } -result 0 diff --git a/tests/http11.test b/tests/http11.test index 346e334..912e069 100644 --- a/tests/http11.test +++ b/tests/http11.test @@ -51,15 +51,11 @@ proc halt_httpd {} { } proc meta {tok {key ""}} { - set meta [http::meta $tok] - if {$key ne ""} { - if {[dict exists $meta $key]} { - return [dict get $meta $key] - } else { - return "" - } + if {$key eq ""} { + return [http::meta $tok] + } else { + return [http::metaValue $tok $key] } - return $meta } proc state {tok {key ""}} { @@ -128,11 +124,12 @@ test http11-1.1 "normal,gzip,non-chunked" -setup { -timeout 10000 -headers {accept-encoding gzip}] http::wait $tok list [http::status $tok] [http::code $tok] [check_crc $tok] \ - [meta $tok content-encoding] [meta $tok transfer-encoding] + [meta $tok content-encoding] [meta $tok transfer-encoding] \ + [http::meta $tok content-encoding] [http::meta $tok transfer-encoding] } -cleanup { http::cleanup $tok halt_httpd -} -result {ok {HTTP/1.1 200 OK} ok gzip {}} +} -result {ok {HTTP/1.1 200 OK} ok gzip {} {content-encoding gzip} {}} test http11-1.2 "normal,deflated,non-chunked" -setup { variable httpd [create_httpd] @@ -193,11 +190,12 @@ test http11-1.6 "normal, specify 1.1 " -setup { -protocol 1.1 -timeout 10000] http::wait $tok list [http::status $tok] [http::code $tok] [check_crc $tok] \ - [meta $tok connection] [meta $tok transfer-encoding] + [meta $tok connection] [meta $tok transfer-encoding] \ + [http::meta $tok connection] [http::meta $tok transfer-encoding] } -cleanup { http::cleanup $tok halt_httpd -} -result {ok {HTTP/1.1 200 OK} ok close chunked} +} -result {ok {HTTP/1.1 200 OK} ok close chunked {connection close} {transfer-encoding chunked}} test http11-1.7 "normal, 1.1 and keepalive " -setup { variable httpd [create_httpd] -- cgit v0.12