Tcl Source Code

Artifact [20cc2f9c34]
Login

Artifact 20cc2f9c347fd1147f8075fef5a4a8de7bc7e483:

Attachment "diff.dat" to ticket [1063703fff] added by hobbs 2004-11-12 04:00:34.
--- ./http.tcl.disabled	2004-10-02 02:57:49.000000000 +0200
+++ ./http.tcl.new	2004-11-11 09:51:54.017173956 +0100
@@ -9,7 +9,7 @@
 # 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.4 2004/05/25 22:50:47 hobbs Exp $
+# RCS: @(#) $Id: http.tcl,v 1.43 2002/10/03 13:34:32 dkf Exp $
 
 # Rough version history:
 # 1.0	Old http_get interface
@@ -19,13 +19,14 @@
 # 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.
+# 3.0   Added HTTP/1.1 extensions: HTTP 100 (continue) and Chunked Transfer-Encoding
+# 3.1   Added -binary option to http::geturl and charset element
+#       to the state array.
 
 package require Tcl 8.2
 # keep this in sync with pkgIndex.tcl
 # and with the install directories in Makefiles
-package provide http 2.5.0
+package provide http 3.1
 
 namespace eval http {
     variable http
@@ -56,11 +57,11 @@
     array set urlTypes {
 	http	{80 ::socket}
     }
-
+    
     variable encodings [string tolower [encoding names]]
     # This can be changed, but iso8859-1 is the RFC standard.
     variable defaultCharset "iso8859-1"
-
+    
     namespace export geturl config reset wait formatQuery register unregister
     # Useful, but not exported: data size status code
 }
@@ -120,7 +121,7 @@
 	}
 	return $result
     }
-    set options [string map {- ""} $options]
+    regsub -all -- - $options {} options
     set pat ^-([join $options |])$
     if {[llength $args] == 1} {
 	set flag [lindex $args 0]
@@ -165,6 +166,7 @@
     }
     catch {close $state(sock)}
     catch {after cancel $state(after)}
+
     if {[info exists state(-command)] && !$skipCB} {
 	if {[catch {eval $state(-command) {$token}} err]} {
 	    if {[string length $errormsg] == 0} {
@@ -246,9 +248,9 @@
 	-queryprogress	{}
 	state		header
 	meta		{}
-	coding		{}
 	currentsize	0
 	totalsize	0
+	coding  	{}
 	querylength	0
 	queryoffset	0
         type            text/html
@@ -256,28 +258,21 @@
 	status		""
 	http            ""
     }
-    # These flags have their types verified [Bug 811170]
-    array set type {
-	-binary		boolean
-	-blocksize	integer
-	-queryblocksize integer
-	-validate	boolean
-	-timeout	integer
-    }
     set state(charset)	$defaultCharset
     set options {-binary -blocksize -channel -command -handler -headers \
 	    -progress -query -queryblocksize -querychannel -queryprogress\
 	    -validate -timeout -type}
     set usage [join $options ", "]
-    set options [string map {- ""} $options]
+    regsub -all -- - $options {} options
     set pat ^-([join $options |])$
     foreach {flag value} $args {
 	if {[regexp $pat $flag]} {
 	    # Validate numbers
-	    if {[info exists type($flag)] && \
-		    ![string is $type($flag) -strict $value]} {
+	    if {[info exists state($flag)] && \
+		    [string is integer -strict $state($flag)] && \
+		    ![string is integer -strict $value]} {
 		unset $token
-		return -code error "Bad value for $flag ($value), must be $type($flag)"
+		return -code error "Bad value for $flag ($value), must be integer"
 	    }
 	    set state($flag) $value
 	} else {
@@ -296,11 +291,9 @@
     }
 
     # 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.
 
-    set exp {^(([^:]*)://)?([^@]+@)?([^/:]+)(:([0-9]+))?(/.*)?$}
-    if {![regexp -nocase $exp $url x prefix proto user host y port srvurl]} {
+    if {![regexp -nocase {^(([^:]*)://)?([^/:]+)(:([0-9]+))?(/.*)?$} $url \
+	    x prefix proto host y port srvurl]} {
 	unset $token
 	return -code error "Unsupported URL: $url"
     }
@@ -413,7 +406,7 @@
     }
 
     if {[catch {
-	puts $s "$how $srvurl HTTP/1.0"
+	puts $s "$how $srvurl HTTP/1.1"
 	puts $s "Accept: $http(-accept)"
 	if {$port == $defport} {
 	    # Don't add port in this case, to handle broken servers.
@@ -423,15 +416,16 @@
 	    puts $s "Host: $host:$port"
 	}
 	puts $s "User-Agent: $http(-useragent)"
+	puts $s "Connection: close"
 	foreach {key value} $state(-headers) {
-	    set value [string map [list \n "" \r ""] $value]
+	    regsub -all \[\n\r\]  $value {} value
 	    set key [string trim $key]
 	    if {[string equal $key "Content-Length"]} {
-		set contDone 1
-		set state(querylength) $value
+	    set contDone 1
+	    set state(querylength) $value
 	    }
 	    if {[string length $key]} {
-		puts $s "$key: $value"
+	    puts $s "$key: $value"
 	    }
 	}
 	if {$isQueryChannel && $state(querylength) == 0} {
@@ -461,7 +455,7 @@
 	# (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.
-
+		
 	if {$isQuery || $isQueryChannel} {
 	    puts $s "Content-Type: $state(-type)"
 	    if {!$contDone} {
@@ -486,8 +480,9 @@
 		# Something went wrong, so throw the exception, and the
 		# enclosing catch will do cleanup.
 		return -code error [lindex $state(error) 0]
-	    }
+	    }		
 	}
+	
     } err]} {
 	# The socket probably was never connected,
 	# or the connection dropped later.
@@ -543,6 +538,26 @@
     upvar 0 $token state
     return $state(currentsize)
 }
+proc http::charset {token} {
+    variable $token
+    upvar 0 $token state
+    return $state(charset)
+}
+
+proc http::meta {token} {
+    variable $token
+    upvar #0 $token state
+    set max 0
+    foreach {name value} $state(meta) {
+        if {[string length $name] > $max} {
+            set max [string length $name]
+        }
+    }
+    incr max
+    foreach {name value} $state(meta) {
+        puts [format "%-*s %s" $max $name: $value]
+    }
+}
 
 proc http::error {token} {
     variable $token
@@ -595,7 +610,6 @@
     }
     return
 }
-
 # http::Write
 #
 #	Write POST query data to the socket
@@ -610,13 +624,16 @@
     variable $token
     upvar 0 $token state
     set s $state(sock)
-
+    
     # Output a block.  Tcl will buffer this if the socket blocks
+    
     set done 0
     if {[catch {
+	
 	# Catch I/O errors on dead sockets
 
 	if {[info exists state(-query)]} {
+	    
 	    # Chop up large query strings so queryprogress callback
 	    # can give smooth feedback
 
@@ -629,6 +646,7 @@
 		set done 1
 	    }
 	} else {
+	    
 	    # Copy blocks from the query channel
 
 	    set outStr [read $state(-querychannel) $state(-queryblocksize)]
@@ -674,7 +692,7 @@
     upvar 0 $token state
     set s $state(sock)
 
-     if {[eof $s]} {
+    if {[eof $s]} {
 	Eof $token
 	return
     }
@@ -682,33 +700,33 @@
 	if {[catch {gets $s line} n]} {
 	    Finish $token $n
 	} elseif {$n == 0} {
-	    variable encodings
-	    set state(state) body
-	    if {$state(-binary) || ![string match -nocase text* $state(type)]
-		    || [string match *gzip* $state(coding)]
-		    || [string match *compress* $state(coding)]} {
-		# Turn off conversions for non-text data
-		fconfigure $s -translation binary
-		if {[info exists state(-channel)]} {
-		    fconfigure $state(-channel) -translation binary
+	    # empty line marks end of response headers
+	    # http 1.1 allows status 100 (continue)
+	    if {[ncode $token] != "100"} {
+		variable encodings
+		set state(state) body
+		if {$state(-binary) || ![regexp -nocase ^text $state(type)] || [regexp chunked|gzip|compress $state(coding)]} {
+		    # Turn off conversions for non-text data or chunked transfers
+		    fconfigure $s -translation binary
+		    if {[info exists state(-channel)]} {
+			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.
+		    set idx [lsearch -exact $encodings [string tolower $state(charset)]]
+		    if {$idx >= 0} {
+			fconfigure $s -encoding [lindex $encodings $idx]
+		    }
 		}
-	    } 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.
-		set idx [lsearch -exact $encodings \
-			[string tolower $state(charset)]]
-		if {$idx >= 0} {
-		    fconfigure $s -encoding [lindex $encodings $idx]
+		if {[info exists state(-channel)] && ![info exists state(-handler)]} {
+		    # Initiate a sequence of background fcopies
+		    fileevent $s readable {}
+		    CopyStart $s $token
 		}
-	    }
-	    if {[info exists state(-channel)] && \
-		    ![info exists state(-handler)]} {
-		# Initiate a sequence of background fcopies
-		fileevent $s readable {}
-		CopyStart $s $token
-	    }
+	    }	    
 	} elseif {$n > 0} {
 	    if {[regexp -nocase {^content-type:(.+)$} $line x type]} {
 		set state(type) [string trim $type]
@@ -718,37 +736,78 @@
 	    if {[regexp -nocase {^content-length:(.+)$} $line x length]} {
 		set state(totalsize) [string trim $length]
 	    }
-	    if {[regexp -nocase {^content-encoding:(.+)$} $line x coding]} {
-		set state(coding) [string trim $coding]
+	    if {[regexp -nocase {^transfer-encoding:(.+)$} $line x coding]} {
+		set state(coding) [string trim [string tolower $coding]]
+		set state(-chunksize) 0
 	    }
 	    if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
 		lappend state(meta) $key [string trim $value]
-	    } elseif {[string match HTTP* $line]} {
+	    } elseif {[regexp ^HTTP $line]} {
 		set state(http) $line
 	    }
 	}
     } else {
-	if {[catch {
-	    if {[info exists state(-handler)]} {
-		set n [eval $state(-handler) {$s $token}]
-	    } else {
-		set block [read $s $state(-blocksize)]
-		set n [string length $block]
-		if {$n >= 0} {
-		    append state(body) $block
-		}
-	    }
-	    if {$n >= 0} {
-		incr state(currentsize) $n
-	    }
-	} err]} {
-	    Finish $token $err
-	} else {
-	    if {[info exists state(-progress)]} {
-		eval $state(-progress) \
-			{$token $state(totalsize) $state(currentsize)}
-	    }
-	}
+	    if {[catch {
+            if {[info exists state(-handler)]} {
+                set n [eval $state(-handler) {$s $token}]
+            } else {
+
+                # Sonderbehandlung von chunked Transfer-Encoding -> chunk data einlesen
+                if {($state(coding) == "chunked") && ($state(-chunksize) > 0)} {
+                    set block [read $s $state(-chunksize)]
+                    set n [string length $block]
+                    if {$n >= 0} {
+                        append state(body) $block
+                        set state(-chunksize) [expr $state(-chunksize) - $n]
+                    }
+                    # Chunk-Ende? -> dann \r\n entfernen
+                    if {$state(-chunksize) == 0} {
+                        gets $s
+                    }
+                # -> chunk size einlesen
+                } elseif {($state(coding) == "chunked") && ($state(-chunksize) == 0)} {
+                    set state(-chunksize) [gets $s]
+                    # keine Ahnung warum sich das einmal nicht ausgegangen ist, wurde um ein zeichnen zu wenig eingelesen...
+                    if {($state(-chunksize) != "") && ([string trim $state(-chunksize)] == "")} {
+                        set state(-chunksize) [gets $s]
+                    }
+                    if {($state(-chunksize) != "") && ($state(-chunksize) != 0)} {
+                        set state(-chunksize) [expr "0x$state(-chunksize)"]
+                        # Rest des Blocks einlesen
+                        set block [read $s $state(-chunksize)]
+                        set n [string length $block]
+                        if {$n >= 0} {
+                            append state(body) $block
+                            set state(-chunksize) [expr $state(-chunksize) - $n]
+                        }
+                        # Chunk-Ende? -> dann \r\n entfernen
+                        if {$state(-chunksize) == 0} {
+                            gets $s
+                        }
+                    # letzter Transfer
+                    } else {
+                        gets $s
+                        set n 0
+                    }
+                # kein chunk
+                } else {
+                    set block [read $s $state(-blocksize)]
+                    set n [string length $block]
+                    if {$n >= 0} {
+                        append state(body) $block
+                    }
+                }
+            }
+            if {$n >= 0} {
+                incr state(currentsize) $n
+            }
+        } err]} {
+	        Finish $token $err
+        } else {
+	        if {[info exists state(-progress)]} {
+	            eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
+	        }
+        }
     }
 }
 
@@ -767,10 +826,27 @@
     variable $token
     upvar 0 $token state
     if {[catch {
-	fcopy $s $state(-channel) -size $state(-blocksize) -command \
-	    [list http::CopyDone $token]
+         # Sonderbehandlung von chunked Transfer-Encoding -> chunk data einlesen
+        if {($state(coding) == "chunked") && ($state(-chunksize) > 0)} {
+            fcopy $s $state(-channel) -size $state(-chunksize) -command [list http::CopyDone $token]
+        # -> chunk size einlesen
+        } elseif {($state(coding) == "chunked") && ($state(-chunksize) == 0)} {
+            set state(-chunksize) [gets $s]
+            if {($state(-chunksize) != "") && ($state(-chunksize) != 0)} {
+                set state(-chunksize) [expr "0x$state(-chunksize)"]
+                # Rest des Blocks einlesen
+                fcopy $s $state(-channel) -size $state(-chunksize) -command [list http::CopyDone $token]
+            # letzter Transfer
+            } else {
+                gets $s
+                CopyDone $token 0
+            }
+        # kein chunk
+        } else {
+            fcopy $s $state(-channel) -size $state(-blocksize) -command [list http::CopyDone $token]
+        }
     } err]} {
-	Finish $token $err
+	    Finish $token $err
     }
 }
 
@@ -790,16 +866,26 @@
     upvar 0 $token state
     set s $state(sock)
     incr state(currentsize) $count
+    if {$state(coding) == "chunked"} {
+        if {$count >= 0} {
+            set state(-chunksize) [expr $state(-chunksize) - $count]
+        }
+        # Chunk-Ende? -> dann \r\n entfernen
+        if {$state(-chunksize) == 0} {
+            gets $s
+        }
+    }
+		
     if {[info exists state(-progress)]} {
-	eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
+        eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
     }
     # At this point the token may have been reset
     if {[string length $error]} {
-	Finish $token $error
+        Finish $token $error
     } elseif {[catch {eof $s} iseof] || $iseof} {
-	Eof $token
+        Eof $token
     } else {
-	CopyStart $s $token
+        CopyStart $s $token
     }
 }
 
@@ -866,10 +952,10 @@
     set sep ""
     foreach i $args {
 	append result $sep [mapReply $i]
-	if {[string equal $sep "="]} {
-	    set sep &
-	} else {
+	if {[string compare $sep "="]} {
 	    set sep =
+	} else {
+	    set sep &
 	}
     }
     return $result
@@ -886,19 +972,15 @@
 #       The encoded string
 
 proc http::mapReply {string} {
-    variable http
     variable formMap
     variable alphanumeric
-
+    
     # The spec says: "non-alphanumeric characters are replaced by '%HH'"
     # 1 leave alphanumerics characters alone
     # 2 Convert every other character to an array lookup
     # 3 Escape constructs that are "special" to the tcl parser
     # 4 "subst" the result, doing all the array substitutions
 
-    if {$http(-urlencoding) ne ""} {
-	set string [encoding convertto $http(-urlencoding) $string]
-    }
     regsub -all \[^$alphanumeric\] $string {$formMap(&)} string
     regsub -all {[][{})\\]\)} $string {\\&} string
     return [subst -nocommand $string]
@@ -916,10 +998,11 @@
 proc http::ProxyRequired {host} {
     variable http
     if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
-	if {![info exists http(-proxyport)] || \
-		![string length $http(-proxyport)]} {
+	if {![info exists http(-proxyport)] || ![string length $http(-proxyport)]} {
 	    set http(-proxyport) 8080
 	}
 	return [list $http(-proxyhost) $http(-proxyport)]
+    } else {
+	return {}
     }
 }