summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--library/http/http.tcl296
-rw-r--r--tests/http.test129
3 files changed, 260 insertions, 170 deletions
diff --git a/ChangeLog b/ChangeLog
index ee32ad7..14df576 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2005-11-18 Donal K. Fellows <donal.k.fellows@manchester.ac.uk>
+
+ * library/http/http.tcl (http::geturl): Improved syntactic validation
+ of URLs, and better error messages in some cases. [Bug 1358369]
+
2005-11-16 Don Porter <dgp@users.sourceforge.net>
* README: Bump version number to 8.4.12
diff --git a/library/http/http.tcl b/library/http/http.tcl
index 08a0888..6c7e636 100644
--- a/library/http/http.tcl
+++ b/library/http/http.tcl
@@ -1,30 +1,29 @@
# http.tcl --
#
-# Client-side HTTP for GET, POST, and HEAD commands.
-# These routines can be used in untrusted code that uses
-# the Safesock security policy. These procedures use a
-# callback interface to avoid using vwait, which is not
+# Client-side HTTP for GET, POST, and HEAD commands. These routines can
+# be used in untrusted code that uses the Safesock security policy. These
+# procedures use a callback interface to avoid using vwait, which is not
# defined in the safe base.
#
-# See the file "license.terms" for information on usage and
-# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# See the file "license.terms" for information on usage and redistribution of
+# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: http.tcl,v 1.43.2.8 2005/11/15 22:58:13 dgp Exp $
+# RCS: @(#) $Id: http.tcl,v 1.43.2.9 2005/11/18 15:20:47 dkf Exp $
# Rough version history:
-# 1.0 Old http_get interface
-# 2.0 http:: namespace and http::geturl
-# 2.1 Added callbacks to handle arriving data, and timeouts
-# 2.2 Added ability to fetch into a channel
-# 2.3 Added SSL support, and ability to post from a channel
-# This version also cleans up error cases and eliminates the
-# "ioerror" status in favor of raising an error
-# 2.4 Added -binary option to http::geturl and charset element
-# to the state array.
+# 1.0 Old http_get interface.
+# 2.0 http:: namespace and http::geturl.
+# 2.1 Added callbacks to handle arriving data, and timeouts.
+# 2.2 Added ability to fetch into a channel.
+# 2.3 Added SSL support, and ability to post from a channel. This version
+# also cleans up error cases and eliminates the "ioerror" status in
+# favor of raising an error
+# 2.4 Added -binary option to http::geturl and charset element to the state
+# array.
package require Tcl 8.4
-# keep this in sync with pkgIndex.tcl
-# and with the install directories in Makefiles
+# Keep this in sync with pkgIndex.tcl and with the install directories
+# in Makefiles
package provide http 2.5.2
namespace eval http {
@@ -39,12 +38,11 @@ namespace eval http {
set http(-useragent) "Tcl http client package [package provide http]"
proc init {} {
- # Set up the map for quoting chars
- # RFC3986 Section 2.3 say percent encode all except:
- # "... percent-encoded octets in the ranges of ALPHA
- # (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
- # period (%2E), underscore (%5F), or tilde (%7E) should
- # not be created by URI producers ..."
+ # Set up the map for quoting chars. RFC3986 Section 2.3 say percent
+ # encode all except: "... percent-encoded octets in the ranges of ALPHA
+ # (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
+ # underscore (%5F), or tilde (%7E) should not be created by URI
+ # producers ..."
for {set i 0} {$i <= 256} {incr i} {
set c [format %c $i]
if {![string match {[-._~a-zA-Z0-9]} $c]} {
@@ -152,9 +150,9 @@ proc http::config {args} {
# Arguments:
# token Connection token.
# errormsg (optional) If set, forces status to error.
-# skipCB (optional) If set, don't call the -command callback. This
+# skipCB (optional) If set, don't call the -command callback. This
# is useful when geturl wants to throw an exception instead
-# of calling the callback. That way, the same error isn't
+# of calling the callback. That way, the same error isn't
# reported to two places.
#
# Side Effects:
@@ -218,17 +216,16 @@ proc http::reset { token {why reset} } {
# args Option value pairs. Valid options include:
# -blocksize, -validate, -headers, -timeout
# Results:
-# Returns a token for this connection.
-# This token is the name of an array that the caller should
-# unset to garbage collect the state.
+# Returns a token for this connection. This token is the name of an array
+# that the caller should unset to garbage collect the state.
proc http::geturl { url args } {
variable http
variable urlTypes
variable defaultCharset
- # Initialize the state variable, an array. We'll return the
- # name of this array as the token for the transaction.
+ # Initialize the state variable, an array. We'll return the name of this
+ # array as the token for the transaction.
if {![info exists http(uid)]} {
set http(uid) 0
@@ -301,17 +298,118 @@ proc http::geturl { url args } {
}
# Validate URL, determine the server host and port, and check proxy case
- # Recognize user:pass@host URLs also, although we do not do anything
- # with that info yet.
+ # Recognize user:pass@host URLs also, although we do not do anything with
+ # that info yet.
+
+ # URLs have basically four parts.
+ # First, before the colon, is the protocol scheme (e.g. http)
+ # Second, for HTTP-like protocols, is the authority
+ # The authority is preceded by // and lasts up to (but not including)
+ # the following / and it identifies up to four parts, of which only one,
+ # the host, is required (if an authority is present at all). All other
+ # parts of the authority (user name, password, port number) are optional.
+ # Third is the resource name, which is split into two parts at a ?
+ # The first part (from the single "/" up to "?") is the path, and the
+ # second part (from that "?" up to "#") is the query. *HOWEVER*, we do
+ # not need to separate them; we send the whole lot to the server.
+ # Fourth is the fragment identifier, which is everything after the first
+ # "#" in the URL. The fragment identifier MUST NOT be sent to the server
+ # and indeed, we don't bother to validate it (it could be an error to
+ # pass it in here, but it's cheap to strip).
+ #
+ # An example of a URL that has all the parts:
+ # http://jschmoe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes
+ # The "http" is the protocol, the user is "jschmoe", the password is
+ # "xyzzy", the host is "www.bogus.net", the port is "8000", the path is
+ # "/foo/bar.tml", the query is "q=foo", and the fragment is "changes".
+ #
+ # Note that the RE actually combines the user and password parts, as
+ # recommended in RFC 3986. Indeed, that RFC states that putting passwords
+ # in URLs is a Really Bad Idea, something with which I would agree utterly.
+ # Also note that we do not currently support IPv6 addresses.
+ #
+ # From a validation perspective, we need to ensure that the parts of the
+ # URL that are going to the server are correctly encoded.
+
+ set URLmatcher {(?x) # this is _expanded_ syntax
+ ^
+ (?: (\w+) : ) ? # <protocol scheme>
+ (?: //
+ (?:
+ (
+ [^@/\#?]+ # <userinfo part of authority>
+ ) @
+ )?
+ ( [^/:\#?]+ ) # <host part of authority>
+ (?: : (\d+) )? # <port part of authority>
+ )?
+ ( / [^\#?]* (?: \? [^\#?]* )?)? # <path> (including query)
+ (?: \# (.*) )? # <fragment>
+ $
+ }
- set exp {^(([^:]*)://)?([^@]+@)?([^/:]+)(:([0-9]+))?(/.*)?$}
- if {![regexp -nocase $exp $url x prefix proto user host y port srvurl]} {
+ # Phase one: parse
+ if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
unset $token
return -code error "Unsupported URL: $url"
}
+ # Phase two: validate
+ if {$host eq ""} {
+ # Caller has to provide a host name; we do not have a "default host"
+ # that would enable us to handle relative URLs.
+ unset $token
+ return -code error "Missing host part: $url"
+ # Note that we don't check the hostname for validity here; if it's
+ # invalid, we'll simply fail to resolve it later on.
+ }
+ if {$port ne "" && $port>65535} {
+ unset $token
+ return -code error "Invalid port number: $port"
+ }
+ # The user identification and resource identification parts of the URL can
+ # have encoded characters in them; take care!
+ if {$user ne ""} {
+ # Check for validity according to RFC 3986, Appendix A
+ set validityRE {(?xi)
+ ^
+ (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
+ $
+ }
+ if {![regexp -- $validityRE $user]} {
+ unset $token
+ # Provide a better error message in this error case
+ if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
+ return -code error \
+ "Illegal encoding character usage \"$bad\" in URL user"
+ }
+ return -code error "Illegal characters in URL user"
+ }
+ }
+ if {$srvurl ne ""} {
+ # Check for validity according to RFC 3986, Appendix A
+ set validityRE {(?xi)
+ ^
+ # Path part (already must start with / character)
+ (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )*
+ # Query part (optional, permits ? characters)
+ (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
+ $
+ }
+ if {![regexp -- $validityRE $srvurl]} {
+ unset $token
+ # Provide a better error message in this error case
+ if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
+ return -code error \
+ "Illegal encoding character usage \"$bad\" in URL path"
+ }
+ return -code error "Illegal characters in URL path"
+ }
+ } else {
+ set srvurl /
+ }
if {[string length $proto] == 0} {
set proto http
- set url ${proto}://$url
+ set url ${proto}:$url
}
if {![info exists urlTypes($proto)]} {
unset $token
@@ -323,20 +421,27 @@ proc http::geturl { url args } {
if {[string length $port] == 0} {
set port $defport
}
- if {[string length $srvurl] == 0} {
- set srvurl /
- }
- if {[string length $proto] == 0} {
- set url http://$url
- }
- set state(url) $url
if {![catch {$http(-proxyfilter) $host} proxy]} {
set phost [lindex $proxy 0]
set pport [lindex $proxy 1]
}
- # If a timeout is specified we set up the after event
- # and arrange for an asynchronous socket connection.
+ # OK, now reassemble into a full URL
+ set url ${proto}://
+ if {$user ne ""} {
+ append url $user
+ append url @
+ }
+ append url $host
+ if {$port != $defport} {
+ append url : $port
+ }
+ append url $srvurl
+ # Don't append the fragment!
+ set state(url) $url
+
+ # If a timeout is specified we set up the after event and arrange for an
+ # asynchronous socket connection.
if {$state(-timeout) > 0} {
set state(after) [after $state(-timeout) \
@@ -346,8 +451,8 @@ proc http::geturl { url args } {
set async ""
}
- # If we are using the proxy, we must pass in the full URL that
- # includes the server name.
+ # If we are using the proxy, we must pass in the full URL that includes
+ # the server name.
if {[info exists phost] && [string length $phost]} {
set srvurl $url
@@ -355,11 +460,11 @@ proc http::geturl { url args } {
} else {
set conStat [catch {eval $defcmd $async {$host $port}} s]
}
- if {$conStat} {
- # something went wrong while trying to establish the connection
- # Clean up after events and such, but DON'T call the command callback
- # (if available) because we're going to throw an exception from here
+ if {$conStat} {
+ # Something went wrong while trying to establish the connection. Clean
+ # up after events and such, but DON'T call the command callback (if
+ # available) because we're going to throw an exception from here
# instead.
Finish $token "" 1
cleanup $token
@@ -367,16 +472,16 @@ proc http::geturl { url args } {
}
set state(sock) $s
- # Wait for the connection to complete
+ # Wait for the connection to complete.
if {$state(-timeout) > 0} {
fileevent $s writable [list http::Connect $token]
http::wait $token
if {$state(status) eq "error"} {
- # something went wrong while trying to establish the connection
+ # Something went wrong while trying to establish the connection.
# Clean up after events and such, but DON'T call the command
- # callback (if available) because we're going to throw an
+ # callback (if available) because we're going to throw an
# exception from here instead.
set err [lindex $state(error) 0]
cleanup $token
@@ -392,8 +497,8 @@ proc http::geturl { url args } {
fconfigure $s -translation {auto crlf} -buffersize $state(-blocksize)
- # The following is disallowed in safe interpreters, but the socket
- # is already in non-blocking mode in that case.
+ # The following is disallowed in safe interpreters, but the socket is
+ # already in non-blocking mode in that case.
catch {fconfigure $s -blocking off}
set how GET
@@ -403,7 +508,7 @@ proc http::geturl { url args } {
set how POST
set contDone 0
} else {
- # there's no query data
+ # There's no query data.
unset state(-query)
set isQuery 0
}
@@ -421,8 +526,8 @@ proc http::geturl { url args } {
puts $s "$how $srvurl HTTP/1.0"
puts $s "Accept: $http(-accept)"
if {$port == $defport} {
- # Don't add port in this case, to handle broken servers.
- # [Bug #504508]
+ # Don't add port in this case, to handle broken servers. [Bug
+ # 504508]
puts $s "Host: $host"
} else {
puts $s "Host: $host:$port"
@@ -440,8 +545,8 @@ proc http::geturl { url args } {
}
}
if {$isQueryChannel && $state(querylength) == 0} {
- # Try to determine size of data in channel
- # If we cannot seek, the surrounding catch will trap us
+ # Try to determine size of data in channel. If we cannot seek, the
+ # surrounding catch will trap us
set start [tell $state(-querychannel)]
seek $state(-querychannel) 0 end
@@ -450,22 +555,21 @@ proc http::geturl { url args } {
seek $state(-querychannel) $start
}
- # Flush the request header and set up the fileevent that will
- # either push the POST data or read the response.
+ # Flush the request header and set up the fileevent that will either
+ # push the POST data or read the response.
#
# fileevent note:
#
- # It is possible to have both the read and write fileevents active
- # at this point. The only scenario it seems to affect is a server
- # that closes the connection without reading the POST data.
- # (e.g., early versions TclHttpd in various error cases).
- # Depending on the platform, the client may or may not be able to
- # get the response from the server because of the error it will
- # get trying to write the post data. Having both fileevents active
- # changes the timing and the behavior, but no two platforms
- # (among Solaris, Linux, and NT) behave the same, and none
- # behave all that well in any case. Servers should always read thier
- # POST data if they expect the client to read their response.
+ # It is possible to have both the read and write fileevents active at
+ # this point. The only scenario it seems to affect is a server that
+ # closes the connection without reading the POST data. (e.g., early
+ # versions TclHttpd in various error cases). Depending on the platform,
+ # the client may or may not be able to get the response from the server
+ # because of the error it will get trying to write the post data.
+ # Having both fileevents active changes the timing and the behavior,
+ # but no two platforms (among Solaris, Linux, and NT) behave the same,
+ # and none behave all that well in any case. Servers should always read
+ # their POST data if they expect the client to read their response.
if {$isQuery || $isQueryChannel} {
puts $s "Content-Type: $state(-type)"
@@ -482,9 +586,8 @@ proc http::geturl { url args } {
}
if {! [info exists state(-command)]} {
-
- # geturl does EVERYTHING asynchronously, so if the user
- # calls it synchronously, we just do a wait here.
+ # geturl does EVERYTHING asynchronously, so if the user calls it
+ # synchronously, we just do a wait here.
wait $token
if {$state(status) eq "error"} {
@@ -494,8 +597,8 @@ proc http::geturl { url args } {
}
}
} err]} {
- # The socket probably was never connected,
- # or the connection dropped later.
+ # The socket probably was never connected, or the connection dropped
+ # later.
# Clean up after events and such, but DON'T call the command callback
# (if available) because we're going to throw an exception from here
@@ -622,8 +725,8 @@ proc http::Write {token} {
# Catch I/O errors on dead sockets
if {[info exists state(-query)]} {
- # Chop up large query strings so queryprogress callback
- # can give smooth feedback
+ # Chop up large query strings so queryprogress callback can give
+ # smooth feedback.
puts -nonewline $s \
[string range $state(-query) $state(queryoffset) \
@@ -644,8 +747,8 @@ proc http::Write {token} {
}
}
} err]} {
- # Do not call Finish here, but instead let the read half of
- # the socket process whatever server reply there is to get.
+ # Do not call Finish here, but instead let the read half of the socket
+ # process whatever server reply there is to get.
set state(posterror) $err
set done 1
@@ -656,7 +759,7 @@ proc http::Write {token} {
fileevent $s readable [list http::Event $token]
}
- # Callback to the client after we've completely handled everything
+ # Callback to the client after we've completely handled everything.
if {[string length $state(-queryprogress)]} {
eval $state(-queryprogress) [list $token $state(querylength)\
@@ -698,10 +801,10 @@ proc http::Event {token} {
fconfigure $state(-channel) -translation binary
}
} else {
- # If we are getting text, set the incoming channel's
- # encoding correctly. iso8859-1 is the RFC default, but
- # this could be any IANA charset. However, we only know
- # how to convert what we have encodings for.
+ # If we are getting text, set the incoming channel's encoding
+ # correctly. iso8859-1 is the RFC default, but this could be
+ # any IANA charset. However, we only know how to convert what
+ # we have encodings for.
set idx [lsearch -exact $encodings \
[string tolower $state(charset)]]
if {$idx >= 0} {
@@ -855,16 +958,15 @@ proc http::wait {token} {
# http::formatQuery --
#
-# See documentaion for details.
-# Call http::formatQuery with an even number of arguments, where
-# the first is a name, the second is a value, the third is another
-# name, and so on.
+# See documentaion for details. Call http::formatQuery with an even
+# number of arguments, where the first is a name, the second is a value,
+# the third is another name, and so on.
#
# Arguments:
# args A list of name-value pairs.
#
# Results:
-# TODO
+# TODO
proc http::formatQuery {args} {
set result ""
@@ -894,9 +996,9 @@ proc http::mapReply {string} {
variable http
variable formMap
- # The spec says: "non-alphanumeric characters are replaced by '%HH'"
- # Use a pre-computed map and [string map] to do the conversion
- # (much faster than [regsub]/[subst]). [Bug 1020491]
+ # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use
+ # a pre-computed map and [string map] to do the conversion (much faster
+ # than [regsub]/[subst]). [Bug 1020491]
if {$http(-urlencoding) ne ""} {
set string [encoding convertto $http(-urlencoding) $string]
@@ -913,7 +1015,7 @@ proc http::mapReply {string} {
}
# http::ProxyRequired --
-# Default proxy filter.
+# Default proxy filter.
#
# Arguments:
# host The destination host
diff --git a/tests/http.test b/tests/http.test
index b0020e7..773b7b3 100644
--- a/tests/http.test
+++ b/tests/http.test
@@ -12,7 +12,7 @@
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
#
-# RCS: @(#) $Id: http.test,v 1.33.2.3 2005/10/05 05:01:37 hobbs Exp $
+# RCS: @(#) $Id: http.test,v 1.33.2.4 2005/11/18 15:20:47 dkf Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2
@@ -82,19 +82,15 @@ if {[info commands testthread] == "testthread" && [file exists $httpdFile]} {
}
}
-
test http-1.1 {http::config} {
http::config
} [list -accept */* -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -urlencoding utf-8 -useragent "Tcl http client package $version"]
-
test http-1.2 {http::config} {
http::config -proxyfilter
} http::ProxyRequired
-
test http-1.3 {http::config} {
catch {http::config -junk}
} 1
-
test http-1.4 {http::config} {
set savedconf [http::config]
http::config -proxyhost nowhere.come -proxyport 8080 \
@@ -104,11 +100,9 @@ test http-1.4 {http::config} {
eval http::config $savedconf
set x
} {-accept */* -proxyfilter myFilter -proxyhost nowhere.come -proxyport 8080 -urlencoding iso8859-1 -useragent {Tcl Test Suite}}
-
test http-1.5 {http::config} {
list [catch {http::config -proxyhost {} -junk 8080} msg] $msg
} {1 {Unknown option -junk, must be: -accept, -proxyfilter, -proxyhost, -proxyport, -urlencoding, -useragent}}
-
test http-1.6 {http::config} {
set enc [list [http::config -urlencoding]]
http::config -urlencoding iso8859-1
@@ -117,7 +111,6 @@ test http-1.6 {http::config} {
set enc
} {utf-8 iso8859-1}
-
test http-2.1 {http::reset} {
catch {http::reset http#1}
} 0
@@ -125,14 +118,12 @@ test http-2.1 {http::reset} {
test http-3.1 {http::geturl} {
list [catch {http::geturl -bogus flag} msg] $msg
} {1 {Unknown option flag, can be: -binary, -blocksize, -channel, -command, -handler, -headers, -progress, -query, -queryblocksize, -querychannel, -queryprogress, -validate, -timeout, -type}}
-
test http-3.2 {http::geturl} {
catch {http::geturl http:junk} err
set err
} {Unsupported URL: http:junk}
-
-set url [info hostname]:$port
-set badurl [info hostname]:6666
+set url //[info hostname]:$port
+set badurl //[info hostname]:6666
test http-3.3 {http::geturl} {
set token [http::geturl $url]
http::data $token
@@ -140,14 +131,12 @@ test http-3.3 {http::geturl} {
<h1>Hello, World!</h1>
<h2>GET /</h2>
</body></html>"
-
set tail /a/b/c
-set url [info hostname]:$port/a/b/c
+set url //[info hostname]:$port/a/b/c
set fullurl http://user:pass@[info hostname]:$port/a/b/c
-set binurl [info hostname]:$port/binary
-set posturl [info hostname]:$port/post
-set badposturl [info hostname]:$port/droppost
-
+set binurl //[info hostname]:$port/binary
+set posturl //[info hostname]:$port/post
+set badposturl //[info hostname]:$port/droppost
test http-3.4 {http::geturl} {
set token [http::geturl $url]
http::data $token
@@ -155,7 +144,6 @@ test http-3.4 {http::geturl} {
<h1>Hello, World!</h1>
<h2>GET $tail</h2>
</body></html>"
-
proc selfproxy {host} {
global port
return [list [info hostname] $port]
@@ -167,9 +155,8 @@ test http-3.5 {http::geturl} {
http::data $token
} "<html><head><title>HTTP/1.0 TEST</title></head><body>
<h1>Hello, World!</h1>
-<h2>GET http://$url</h2>
+<h2>GET http:$url</h2>
</body></html>"
-
test http-3.6 {http::geturl} {
http::config -proxyfilter bogus
set token [http::geturl $url]
@@ -179,7 +166,6 @@ test http-3.6 {http::geturl} {
<h1>Hello, World!</h1>
<h2>GET $tail</h2>
</body></html>"
-
test http-3.7 {http::geturl} {
set token [http::geturl $url -headers {Pragma no-cache}]
http::data $token
@@ -187,7 +173,6 @@ test http-3.7 {http::geturl} {
<h1>Hello, World!</h1>
<h2>GET $tail</h2>
</body></html>"
-
test http-3.8 {http::geturl} {
set token [http::geturl $url -query Name=Value&Foo=Bar -timeout 2000]
http::data $token
@@ -200,12 +185,10 @@ test http-3.8 {http::geturl} {
<dt>Foo<dd>Bar
</dl>
</body></html>"
-
test http-3.9 {http::geturl} {
set token [http::geturl $url -validate 1]
http::code $token
} "HTTP/1.0 200 OK"
-
test http-3.10 {http::geturl queryprogress} {
set query foo=bar
set sep ""
@@ -227,7 +210,6 @@ test http-3.10 {http::geturl queryprogress} {
http::wait $t
list [http::status $t] [string length $query] $postProgress [http::data $t]
} {ok 122879 {16384 32768 49152 65536 81920 98304 114688 122879} {Got 122879 bytes}}
-
test http-3.11 {http::geturl querychannel with -command} {
set query foo=bar
set sep ""
@@ -263,14 +245,11 @@ test http-3.11 {http::geturl querychannel with -command} {
removeFile outdata
set testRes
} {ok 122879 {Got 122880 bytes} ok {PostStart {Got 122880 bytes}}}
-
-# On Linux platforms when the client and server are on the same
-# host, the client is unable to read the server's response one
-# it hits the write error. The status is "eof"
-
-# On Windows, the http::wait procedure gets a
-# "connection reset by peer" error while reading the reply
-
+# On Linux platforms when the client and server are on the same host, the
+# client is unable to read the server's response one it hits the write error.
+# The status is "eof".
+# On Windows, the http::wait procedure gets a "connection reset by peer" error
+# while reading the reply.
test http-3.12 {http::geturl querychannel with aborted request} {nonPortable} {
set query foo=bar
set sep ""
@@ -308,21 +287,49 @@ test http-3.12 {http::geturl querychannel with aborted request} {nonPortable} {
removeFile outdata
list [http::status $t] [http::code $t]
} {ok {HTTP/1.0 200 Data follows}}
-
test http-3.13 {http::geturl socket leak test} {
set chanCount [llength [file channels]]
for {set i 0} {$i < 3} {incr i} {
- catch {http::geturl $badurl -timeout 5000}
+ catch {http::geturl $badurl -timeout 5000}
}
# No extra channels should be taken
expr {[llength [file channels]] == $chanCount}
} 1
-
test http-3.14 "http::geturl $fullurl" {
set token [http::geturl $fullurl -validate 1]
http::code $token
} "HTTP/1.0 200 OK"
+test http-3.15 {http::geturl parse failures} -body {
+ http::geturl "{invalid}:url"
+} -returnCodes error -result {Unsupported URL: {invalid}:url}
+test http-3.16 {http::geturl parse failures} -body {
+ http::geturl http:relative/url
+} -returnCodes error -result {Unsupported URL: http:relative/url}
+test http-3.17 {http::geturl parse failures} -body {
+ http::geturl /absolute/url
+} -returnCodes error -result {Missing host part: /absolute/url}
+test http-3.18 {http::geturl parse failures} -body {
+ http::geturl http://somewhere:123456789/
+} -returnCodes error -result {Invalid port number: 123456789}
+test http-3.19 {http::geturl parse failures} -body {
+ http::geturl http://{user}@somewhere
+} -returnCodes error -result {Illegal characters in URL user}
+test http-3.20 {http::geturl parse failures} -body {
+ http::geturl http://%user@somewhere
+} -returnCodes error -result {Illegal encoding character usage "%us" in URL user}
+test http-3.21 {http::geturl parse failures} -body {
+ http::geturl http://somewhere/{path}
+} -returnCodes error -result {Illegal characters in URL path}
+test http-3.22 {http::geturl parse failures} -body {
+ http::geturl http://somewhere/%path
+} -returnCodes error -result {Illegal encoding character usage "%pa" in URL path}
+test http-3.23 {http::geturl parse failures} -body {
+ http::geturl http://somewhere/path?{query}
+} -returnCodes error -result {Illegal characters in URL path}
+test http-3.24 {http::geturl parse failures} -body {
+ http::geturl http://somewhere/path?%query
+} -returnCodes error -result {Illegal encoding character usage "%qu" in URL path}
test http-4.1 {http::Event} {
set token [http::geturl $url]
@@ -330,19 +337,16 @@ test http-4.1 {http::Event} {
array set meta $data(meta)
expr ($data(totalsize) == $meta(Content-Length))
} 1
-
test http-4.2 {http::Event} {
set token [http::geturl $url]
upvar #0 $token data
array set meta $data(meta)
string compare $data(type) [string trim $meta(Content-Type)]
} 0
-
test http-4.3 {http::Event} {
set token [http::geturl $url]
http::code $token
} {HTTP/1.0 200 Data follows}
-
test http-4.4 {http::Event} {
set testfile [makeFile "" testfile]
set out [open $testfile w]
@@ -357,7 +361,6 @@ test http-4.4 {http::Event} {
<h1>Hello, World!</h1>
<h2>GET $tail</h2>
</body></html>"
-
test http-4.5 {http::Event} {
set testfile [makeFile "" testfile]
set out [open $testfile w]
@@ -367,7 +370,6 @@ test http-4.5 {http::Event} {
removeFile $testfile
expr $data(currentsize) == $data(totalsize)
} 1
-
test http-4.6 {http::Event} {
set testfile [makeFile "" testfile]
set out [open $testfile w]
@@ -379,8 +381,7 @@ test http-4.6 {http::Event} {
close $in
removeFile $testfile
set x
-} "$bindata$binurl"
-
+} "$bindata[string trimleft $binurl /]"
proc myProgress {token total current} {
global progress httpLog
if {[info exists httpLog] && $httpLog} {
@@ -391,7 +392,7 @@ proc myProgress {token total current} {
if 0 {
# This test hangs on Windows95 because the client never gets EOF
set httpLog 1
- test http-4.6 {http::Event} {
+ test http-4.6.1 {http::Event} knownBug {
set token [http::geturl $url -blocksize 50 -progress myProgress]
set progress
} {111 111}
@@ -412,38 +413,29 @@ test http-4.10 {http::Event} {
set token [http::geturl $url -progress myProgress]
http::size $token
} {111}
-
# Timeout cases
-
-# Short timeout to working server (the test server)
-# This lets us try a reset during the connection
-
+# Short timeout to working server (the test server). This lets us try a
+# reset during the connection.
test http-4.11 {http::Event} {
set token [http::geturl $url -timeout 1 -command {#}]
http::reset $token
http::status $token
} {reset}
-
-# Longer timeout with reset
-
+# Longer timeout with reset.
test http-4.12 {http::Event} {
set token [http::geturl $url/?timeout=10 -command {#}]
http::reset $token
http::status $token
} {reset}
-
-# Medium timeout to working server that waits even longer
-# The timeout hits while waiting for a reply
-
+# Medium timeout to working server that waits even longer. The timeout
+# hits while waiting for a reply.
test http-4.13 {http::Event} {
set token [http::geturl $url?timeout=30 -timeout 10 -command {#}]
http::wait $token
http::status $token
} {timeout}
-
-# Longer timeout to good host, bad port, gets an error
-# after the connection "completes" but the socket is bad
-
+# Longer timeout to good host, bad port, gets an error after the
+# connection "completes" but the socket is bad.
test http-4.14 {http::Event} {
set code [catch {
set token [http::geturl $badurl/?timeout=10 -timeout 10000 -command {#}]
@@ -456,14 +448,12 @@ test http-4.14 {http::Event} {
# error code varies among platforms.
list $code [regexp {(connect failed|couldn't open socket)} $err]
} {1 1}
-
# Bogus host
-
test http-4.15 {http::Event} {
# This test may fail if you use a proxy server. That is to be
# expected and is not a problem with Tcl.
set code [catch {
- set token [http::geturl not_a_host.tcl.tk -timeout 1000 -command {#}]
+ set token [http::geturl //not_a_host.tcl.tk -timeout 1000 -command {#}]
http::wait $token
http::status $token
} err]
@@ -474,17 +464,13 @@ test http-4.15 {http::Event} {
test http-5.1 {http::formatQuery} {
http::formatQuery name1 value1 name2 "value two"
} {name1=value1&name2=value+two}
-
-# test http-5.2 obsoleted by 5.4 and 5.4 with http 2.5
-
+# test http-5.2 obsoleted by 5.4 and 5.5 with http 2.5
test http-5.3 {http::formatQuery} {
http::formatQuery lines "line1\nline2\nline3"
} {lines=line1%0d%0aline2%0d%0aline3}
-
test http-5.4 {http::formatQuery} {
http::formatQuery name1 ~bwelch name2 \xa1\xa2\xa2
} {name1=~bwelch&name2=%c2%a1%c2%a2%c2%a2}
-
test http-5.5 {http::formatQuery} {
set enc [http::config -urlencoding]
http::config -urlencoding iso8859-1
@@ -502,19 +488,17 @@ test http-6.1 {http::ProxyRequired} {
set data(body)
} "<html><head><title>HTTP/1.0 TEST</title></head><body>
<h1>Hello, World!</h1>
-<h2>GET http://$url</h2>
+<h2>GET http:$url</h2>
</body></html>"
test http-7.1 {http::mapReply} {
http::mapReply "abc\$\[\]\"\\()\}\{"
} {abc%24%5b%5d%22%5c%28%29%7d%7b}
-
test http-7.2 {http::mapReply} {
# RFC 2718 specifies that we pass urlencoding on utf-8 chars by default,
# so make sure this gets converted to utf-8 then urlencoded.
http::mapReply "\u2208"
} {%e2%88%88}
-
test http-7.3 {http::formatQuery} {
set enc [http::config -urlencoding]
# this would be reverting to http <=2.4 behavior
@@ -523,7 +507,6 @@ test http-7.3 {http::formatQuery} {
http::config -urlencoding $enc
set res
} [list 1 "can't read \"formMap(\u2208)\": no such element in array"]
-
test http-7.4 {http::formatQuery} {
set enc [http::config -urlencoding]
# this would be reverting to http <=2.4 behavior w/o errors