* http2.6/http.tcl: Fixed some bugs (0 length body with chunked
authorPat Thoyts <patthoyts@users.sourceforge.net>
Mon, 12 Mar 2007 15:45:39 +0000 (15:45 +0000)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Mon, 12 Mar 2007 15:45:39 +0000 (15:45 +0000)
transfer) and added support for gzip encoding if zlib is
available. Tested continued operation with Tcl 8.2.
* library/webdavvfs.tcl: Encoding is now in the http package.
* http2.6/pkgIndex.tcl: version to 2.6.6

ChangeLog
http2.6/http.tcl
http2.6/pkgIndex.tcl
library/webdavvfs.tcl

index bf2d601fe17df40a00e4ce1eee990d8a65ae4752..babea3361ff93ddadf58583bf9cad7089c188a64 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2007-03-12  Pat Thoyts  <patthoyts@users.sourceforge.net>
+
+       * http2.6/http.tcl: Fixed some bugs (0 length body with chunked
+       transfer) and added support for gzip encoding if zlib is
+       available. Tested continued operation with Tcl 8.2.
+       * library/webdavvfs.tcl: Encoding is now in the http package.
+       * http2.6/pkgIndex.tcl: version to 2.6.6
+
 2007-03-07  Pat Thoyts  <patthoyts@users.sourceforge.net>
 
        * library/webdavvfs.tcl: silence debug output
index 0388411f3b3090f3bdab95a053b47a960fb84a5d..b57cf335f83549eaef97ce413079013c08ea11e0 100644 (file)
 # 2.6.3 Added support for chunked encoding.
 # 2.6.4 Merged in jcw's webdav mods to fix the chunked transfer
 # 2.6.5 Merged up to 2.5.3 from tcl cvs (formMap, url decomposition)
+# 2.6.6 Support content-encoding gzip. Handle 0 length body in chunked.
 
 package require Tcl 8.2
 # keep this in sync with pkgIndex.tcl
-package provide http 2.6.5
+package provide http 2.6.6
 
 namespace eval http {
     variable http
@@ -48,9 +49,9 @@ namespace eval http {
         # Use a Mozilla compatible useragent header to avoid problems with
         # some web sites.
         set http(-useragent) \
-            "Mozilla/5.0 ([string totitle $::tcl_platform(platform)];\
-             $::tcl_platform(os)) http/[package provide http]\
-             Tcl/[package provide Tcl]"
+            "Mozilla/5.0 ([string totitle $::tcl_platform(platform)]; U;\
+             $::tcl_platform(os) $::tcl_platform(osVersion))\
+             http/[package provide http] Tcl/[package provide Tcl]"
     }
 
     proc init {} {
@@ -82,8 +83,10 @@ namespace eval http {
     init
 
     variable urlTypes
-    array set urlTypes {
-       http    {80 ::socket}
+    if {![info exists urlTypes]} {
+        array set urlTypes {
+            http       {80 ::socket}
+        }
     }
 
     variable encodings [string tolower [encoding names]]
@@ -201,12 +204,14 @@ proc http::Finish { token {errormsg ""} {skipCB 0}} {
        set state(error) [list $errormsg $errorInfo $errorCode]
        set state(status) error
     }
-    if {$state(status) == "timeout"
+    if {[string equal $state(status) "timeout"]
+        || [string equal $state(status) "error"]
         || ([info exists state(connection)] 
-            && $state(connection) == "close")} {
+            && $state(connection) == "close")
+    } then {
         CloseSocket $state(sock) $token
     }
-    catch {after cancel $state(after)}
+    if {[info exists state(after)]} { after cancel $state(after) }
     if {[info exists state(-command)] && !$skipCB} {
        if {[catch {eval $state(-command) {$token}} err]} {
            if {[string length $errormsg] == 0} {
@@ -256,23 +261,6 @@ proc ::http::CloseSocket {s {token {}}} {
 
 # -------------------------------------------------------------------------
 
-proc ::http::OpenConnection {host port {socketcmd socket} {async ""}} {
-    variable socketmap
-    if {![info exists socketmap($host:$port)]} {
-        set sock [eval $socketcmd $async $host $port]
-        set id [string map {sock conn} $sock]
-        variable $id
-        upvar 0 $id conn
-        set conn(sock) $sock
-        set id [namespace which -variable $id]
-        set socketmap($host:$port) $id
-        Log "Connection $id used for $host:$port"
-    } else {
-        set id $socketmap($host:$port)
-    }
-    return $id
-}
-
 proc ::http::CloseConnection {id} {
     variable socketmap
     if {[info exists socketmap($id)]} {
@@ -469,7 +457,7 @@ proc http::geturl { url args } {
        return -code error "Unsupported URL: $url"
     }
     # Phase two: validate
-    if {$host eq ""} {
+    if {[string length $host] < 1} {
        # Caller has to provide a host name; we do not have a "default host"
        # that would enable us to handle relative URLs.
        unset $token
@@ -477,13 +465,13 @@ proc http::geturl { url args } {
        # 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} {
+    if {[string length $port] != 0 && $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 ""} {
+    if {![string equal $user ""]} {
        # Check for validity according to RFC 3986, Appendix A
        set validityRE {(?xi)
            ^
@@ -500,7 +488,7 @@ proc http::geturl { url args } {
            return -code error "Illegal characters in URL user"
        }
     }
-    if {$srvurl ne ""} {
+    if {![string equal $srvurl ""]} {
        # Check for validity according to RFC 3986, Appendix A
        set validityRE {(?xi)
            ^
@@ -542,7 +530,7 @@ proc http::geturl { url args } {
 
     # OK, now reassemble into a full URL
     set url ${proto}://
-    if {$user ne ""} {
+    if {![string equal $user ""]} {
        append url $user
        append url @
     }
@@ -570,8 +558,11 @@ proc http::geturl { url args } {
 
     if {[info exists phost] && [string length $phost]} {
         set srvurl $url
-        set state(socketinfo) $phost:$pport
+        set targetAddr [list $phost $pport]
+        # Don't share proxy connections among different hosts.
+        set state(socketinfo) ${host}:${port}
     } else {
+        set targetAddr [list $host $port]
         set state(socketinfo) $host:$port
     }
 
@@ -585,7 +576,7 @@ proc http::geturl { url args } {
                 unset socketmap($state(socketinfo))
             } else {
                 set s $socketmap($state(socketinfo))
-                #Log "reusing socket $s for $state(socketinfo)"
+                Log "reusing socket $s for $state(socketinfo)"
                 catch {fileevent $s writable {}}
                 catch {fileevent $s readable {}}
             }
@@ -596,7 +587,7 @@ proc http::geturl { url args } {
     if {![info exists s] || $s == {}} {
 
         set conStat [catch {
-            eval $defcmd $async [split $state(socketinfo) :]
+            eval $defcmd $async $targetAddr
         } s]
         if {$conStat} {
             
@@ -611,7 +602,7 @@ proc http::geturl { url args } {
         }
     }
     set state(sock) $s
-    #Log "Using $s for $state(socketinfo)"
+    Log "Using $s for $state(socketinfo)"
     set socketmap($state(socketinfo)) $s
 
     # Wait for the connection to complete.
@@ -687,7 +678,11 @@ proc http::geturl { url args } {
                 && $state(-keepalive)} {
             puts $s "Proxy-Connection: Keep-Alive"
         }
+        set accept_encoding_seen 0
        foreach {key value} $state(-headers) {
+            if {[string equal accept-encoding [string tolower $key]]} {
+                set accept_encoding_seen 1
+            }
            set value [string map [list \n "" \r ""] $value]
            set key [string trim $key]
            if {[string equal $key "Content-Length"]} {
@@ -698,6 +693,13 @@ proc http::geturl { url args } {
                puts $s "$key: $value"
            }
        }
+        if {!$accept_encoding_seen
+            && [llength [package provide zlib]] > 0 
+            && !([info exists state(-channel)] 
+                 || [info exists state(-handler)])
+        } then {
+            puts $s "Accept-Encoding: gzip, identity, *;q=0.1"
+        }
        if {$isQueryChannel && $state(querylength) == 0} {
            # Try to determine size of data in channel
            # If we cannot seek, the surrounding catch will trap us
@@ -977,14 +979,28 @@ proc http::Event {s token} {
             variable encodings
            set state(state) body
 
+            # For non-chunked transfer we may have no body -- in this case we may get
+            # no further file event if the connection doesn't close and no more data
+            # is sent. We can tell and must finish up now - not later.
+            if {!(([info exists state(connection)] 
+                   && [string equal $state(connection) "close"])
+                  || [info exists state(transfer)])
+                &&  $state(totalsize) == 0
+            } then {
+                Log "body size is 0 and no events likely - complete."
+                Eof $token
+                return
+            }
+
             # We have to use binary translation to count bytes properly.
             fconfigure $s -translation binary
 
-           if {$state(-binary) || ![string match -nocase text* $state(type)]
-                   || [string match *gzip* $state(coding)]
-                   || [string match *compress* $state(coding)]} {
+           if {$state(-binary) || ![string match -nocase text* $state(type)]} {
                # Turn off conversions for non-text data
                 set state(binary) 1
+            }
+            if {$state(binary) || [string match *gzip* $state(coding)]
+                || [string match *compress* $state(coding)]} {
                if {[info exists state(-channel)]} {
                    fconfigure $state(-channel) -translation binary
                }
@@ -995,6 +1011,7 @@ proc http::Event {s token} {
                fileevent $s readable {}
                CopyStart $s $token
            }
+            http::Log [array get state]
        } elseif {$n > 0} {
             # Process header lines
             if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
@@ -1004,6 +1021,7 @@ proc http::Event {s token} {
                         # grab the optional charset information
                         regexp -nocase {charset\s*=\s*(\S+)} $state(type) \
                             x state(charset)
+                        http::Log "Received Content-Type $state(type) and $state(charset) ($x)"
                     }
                     content-length {
                         set state(totalsize) [string trim $value]
@@ -1068,11 +1086,12 @@ proc http::Event {s token} {
                     }
                 }
            } else {
-               set block [read $s $state(-blocksize)]
-               set n [string length $block]
-               if {$n >= 0} {
-                   append state(body) $block
-               }
+                Log "read non-chunk $state(currentsize) of $state(totalsize)"
+                set block [read $s $state(-blocksize)]
+                set n [string length $block]
+                if {$n >= 0} {
+                    append state(body) $block
+                }
            }
             if {[info exists state]} {
                 if {$n >= 0} {
@@ -1185,6 +1204,14 @@ proc http::Eof {token {force 0}} {
        set state(status) ok
     }
 
+    if {[string equal $state(coding) "gzip"] && [string length $state(body)] > 0} {
+        if {[catch {
+            set state(body) [Gunzip $state(body)]
+        } err]} {
+            return [Finish $token $err]
+        }
+    }
+
     if { ! $state(binary) } {
 
         # If we are getting text, set the incoming channel's
@@ -1271,7 +1298,7 @@ proc http::mapReply {string} {
     # Use a pre-computed map and [string map] to do the conversion
     # (much faster than [regsub]/[subst]). [Bug 1020491]
 
-    if {$http(-urlencoding) ne ""} {
+    if {![string equal $http(-urlencoding) ""]} {
        set string [encoding convertto $http(-urlencoding) $string]
        return [string map $formMap $string]
     }
@@ -1344,3 +1371,56 @@ proc http::CharsetToEncoding {charset} {
        return "binary"
     }
 }
+
+# http::Gunzip --
+#
+#      Decompress data transmitted using the gzip transfer coding.
+#
+
+proc http::Gunzip {data} {
+    binary scan $data Scb5icc magic method flags time xfl os
+    set pos 10
+    if {$magic != 0x1f8b} {
+        return -code error "invalid data: supplied data is not in gzip format"
+    }
+    if {$method != 8} {
+        return -code error "invalid compression method"
+    }
+
+    foreach {f_text f_crc f_extra f_name f_comment} [split $flags ""] break
+    set extra ""
+    if { $f_extra } {
+       binary scan $data @${pos}S xlen
+        incr pos 2
+        set extra [string range $data $pos $xlen]
+        set pos [incr xlen]
+    }
+
+    set name ""
+    if { $f_name } {
+        set ndx [string first \0 $data $pos]
+        set name [string range $data $pos $ndx]
+        set pos [incr ndx]
+    }
+
+    set comment ""
+    if { $f_comment } {
+        set ndx [string first \0 $data $pos]
+        set comment [string range $data $pos $ndx]
+        set pos [incr ndx]
+    }
+
+    set fcrc ""
+    if { $f_crc } {
+       set fcrc [string range $pos [incr pos]]
+        incr pos
+    }
+
+    binary scan [string range $data end-7 end] ii crc size
+    set inflated [zlib inflate [string range $data $pos end-8]]
+
+    if { $crc != [set chk [zlib crc32 $inflated]] } {
+       return -code error "invalid data: checksum mismatch $crc != $chk"
+    }
+    return $inflated
+}
index ed24b3c8d3c223d0f98d8ac1ae0f8ad5c15ae91f..ce34abab8fcb082b878176a2887390ed34489a35 100644 (file)
@@ -4,5 +4,5 @@
 # package ifneeded http 2.6 [list tclPkgSetup $dir http 2.6 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister}}}]
 #
 if {![package vsatisfies [package provide Tcl] 8.2]} {return}
-package ifneeded http 2.6.5 [list source [file join $dir http.tcl]]
+package ifneeded http 2.6.6 [list source [file join $dir http.tcl]]
 
index ff151d3b7c97e648fc91cccd65557283c094fda9..d22d67a7b7d047b3efe0c01881625b9fcfbde492 100644 (file)
@@ -153,12 +153,8 @@ proc vfs::webdav::open {dirurl extraHeadersList name mode permissions} {
            upvar #0 $token state
 
            set filed [vfs::memchan]
-           
-           fconfigure $filed -encoding $state(charset)
-           
+            fconfigure $filed -encoding binary -translation binary
            puts -nonewline $filed [::http::data $token]
-
-           fconfigure $filed -translation auto
            seek $filed 0
            ::http::cleanup $token
            return [list $filed]