home *** CD-ROM | disk | FTP | other *** search
/ Enter 2003: The Beautiful Scenery / enter-parhaat-2003.iso / files / Python-2.2.1.exe / HTTP.TCL < prev    next >
Encoding:
Text File  |  2000-05-30  |  21.7 KB  |  882 lines

  1. # http.tcl --
  2. #
  3. #    Client-side HTTP for GET, POST, and HEAD commands.
  4. #    These routines can be used in untrusted code that uses 
  5. #    the Safesock security policy.  These procedures use a 
  6. #    callback interface to avoid using vwait, which is not 
  7. #    defined in the safe base.
  8. #
  9. # See the file "license.terms" for information on usage and
  10. # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  11. #
  12. # RCS: @(#) $Id: http.tcl,v 1.32.2.1 2000/05/31 01:28:57 sandeep Exp $
  13.  
  14. # Rough version history:
  15. # 1.0    Old http_get interface
  16. # 2.0    http:: namespace and http::geturl
  17. # 2.1    Added callbacks to handle arriving data, and timeouts
  18. # 2.2    Added ability to fetch into a channel
  19. # 2.3    Added SSL support, and ability to post from a channel
  20. #    This version also cleans up error cases and eliminates the
  21. #    "ioerror" status in favor of raising an error
  22.  
  23. package provide http 2.3
  24.  
  25. namespace eval http {
  26.     variable http
  27.     array set http {
  28.     -accept */*
  29.     -proxyhost {}
  30.     -proxyport {}
  31.     -useragent {Tcl http client package 2.3}
  32.     -proxyfilter http::ProxyRequired
  33.     }
  34.  
  35.     variable formMap
  36.     variable alphanumeric a-zA-Z0-9
  37.     variable c
  38.     variable i 0
  39.     for {} {$i <= 256} {incr i} {
  40.     set c [format %c $i]
  41.     if {![string match \[$alphanumeric\] $c]} {
  42.         set formMap($c) %[format %.2x $i]
  43.     }
  44.     }
  45.     # These are handled specially
  46.     array set formMap {
  47.     " " +   \n %0d%0a
  48.     }
  49.  
  50.     variable urlTypes
  51.     array set urlTypes {
  52.     http    {80 ::socket}
  53.     }
  54.  
  55.     namespace export geturl config reset wait formatQuery register unregister
  56.     # Useful, but not exported: data size status code
  57. }
  58.  
  59. # http::register --
  60. #
  61. #     See documentaion for details.
  62. #
  63. # Arguments:
  64. #     proto           URL protocol prefix, e.g. https
  65. #     port            Default port for protocol
  66. #     command         Command to use to create socket
  67. # Results:
  68. #     list of port and command that was registered.
  69.  
  70. proc http::register {proto port command} {
  71.     variable urlTypes
  72.     set urlTypes($proto) [list $port $command]
  73. }
  74.  
  75. # http::unregister --
  76. #
  77. #     Unregisters URL protocol handler
  78. #
  79. # Arguments:
  80. #     proto           URL protocol prefix, e.g. https
  81. # Results:
  82. #     list of port and command that was unregistered.
  83.  
  84. proc http::unregister {proto} {
  85.     variable urlTypes
  86.     if {![info exists urlTypes($proto)]} {
  87.     return -code error "unsupported url type \"$proto\""
  88.     }
  89.     set old $urlTypes($proto)
  90.     unset urlTypes($proto)
  91.     return $old
  92. }
  93.  
  94. # http::config --
  95. #
  96. #    See documentaion for details.
  97. #
  98. # Arguments:
  99. #    args        Options parsed by the procedure.
  100. # Results:
  101. #        TODO
  102.  
  103. proc http::config {args} {
  104.     variable http
  105.     set options [lsort [array names http -*]]
  106.     set usage [join $options ", "]
  107.     if {[llength $args] == 0} {
  108.     set result {}
  109.     foreach name $options {
  110.         lappend result $name $http($name)
  111.     }
  112.     return $result
  113.     }
  114.     regsub -all -- - $options {} options
  115.     set pat ^-([join $options |])$
  116.     if {[llength $args] == 1} {
  117.     set flag [lindex $args 0]
  118.     if {[regexp -- $pat $flag]} {
  119.         return $http($flag)
  120.     } else {
  121.         return -code error "Unknown option $flag, must be: $usage"
  122.     }
  123.     } else {
  124.     foreach {flag value} $args {
  125.         if {[regexp -- $pat $flag]} {
  126.         set http($flag) $value
  127.         } else {
  128.         return -code error "Unknown option $flag, must be: $usage"
  129.         }
  130.     }
  131.     }
  132. }
  133.  
  134. # http::Finish --
  135. #
  136. #    Clean up the socket and eval close time callbacks
  137. #
  138. # Arguments:
  139. #    token        Connection token.
  140. #    errormsg    (optional) If set, forces status to error.
  141. #       skipCB      (optional) If set, don't call the -command callback.  This
  142. #                   is useful when geturl wants to throw an exception instead
  143. #                   of calling the callback.  That way, the same error isn't
  144. #                   reported to two places.
  145. #
  146. # Side Effects:
  147. #        Closes the socket
  148.  
  149. proc http::Finish { token {errormsg ""} {skipCB 0}} {
  150.     variable $token
  151.     upvar 0 $token state
  152.     global errorInfo errorCode
  153.     if {[string length $errormsg] != 0} {
  154.     set state(error) [list $errormsg $errorInfo $errorCode]
  155.     set state(status) error
  156.     }
  157.     catch {close $state(sock)}
  158.     catch {after cancel $state(after)}
  159.     if {[info exists state(-command)] && !$skipCB} {
  160.     if {[catch {eval $state(-command) {$token}} err]} {
  161.         if {[string length $errormsg] == 0} {
  162.         set state(error) [list $err $errorInfo $errorCode]
  163.         set state(status) error
  164.         }
  165.     }
  166.     if {[info exist state(-command)]} {
  167.         # Command callback may already have unset our state
  168.         unset state(-command)
  169.     }
  170.     }
  171. }
  172.  
  173. # http::reset --
  174. #
  175. #    See documentaion for details.
  176. #
  177. # Arguments:
  178. #    token    Connection token.
  179. #    why    Status info.
  180. #
  181. # Side Effects:
  182. #       See Finish
  183.  
  184. proc http::reset { token {why reset} } {
  185.     variable $token
  186.     upvar 0 $token state
  187.     set state(status) $why
  188.     catch {fileevent $state(sock) readable {}}
  189.     catch {fileevent $state(sock) writable {}}
  190.     Finish $token
  191.     if {[info exists state(error)]} {
  192.     set errorlist $state(error)
  193.     unset state
  194.     eval error $errorlist
  195.     }
  196. }
  197.  
  198. # http::geturl --
  199. #
  200. #    Establishes a connection to a remote url via http.
  201. #
  202. # Arguments:
  203. #       url        The http URL to goget.
  204. #       args        Option value pairs. Valid options include:
  205. #                -blocksize, -validate, -headers, -timeout
  206. # Results:
  207. #    Returns a token for this connection.
  208. #    This token is the name of an array that the caller should
  209. #    unset to garbage collect the state.
  210.  
  211. proc http::geturl { url args } {
  212.     variable http
  213.     variable urlTypes
  214.  
  215.     # Initialize the state variable, an array.  We'll return the
  216.     # name of this array as the token for the transaction.
  217.  
  218.     if {![info exists http(uid)]} {
  219.     set http(uid) 0
  220.     }
  221.     set token [namespace current]::[incr http(uid)]
  222.     variable $token
  223.     upvar 0 $token state
  224.     reset $token
  225.  
  226.     # Process command options.
  227.  
  228.     array set state {
  229.     -blocksize     8192
  230.     -queryblocksize 8192
  231.     -validate     0
  232.     -headers     {}
  233.     -timeout     0
  234.     -type           application/x-www-form-urlencoded
  235.     -queryprogress    {}
  236.     state        header
  237.     meta        {}
  238.     currentsize    0
  239.     totalsize    0
  240.     querylength    0
  241.     queryoffset    0
  242.         type            text/html
  243.         body            {}
  244.     status        ""
  245.     http            ""
  246.     }
  247.     set options {-blocksize -channel -command -handler -headers \
  248.         -progress -query -queryblocksize -querychannel -queryprogress\
  249.         -validate -timeout -type}
  250.     set usage [join $options ", "]
  251.     regsub -all -- - $options {} options
  252.     set pat ^-([join $options |])$
  253.     foreach {flag value} $args {
  254.     if {[regexp $pat $flag]} {
  255.         # Validate numbers
  256.         if {[info exists state($flag)] && \
  257.             [string is integer -strict $state($flag)] && \
  258.             ![string is integer -strict $value]} {
  259.         unset $token
  260.         return -code error "Bad value for $flag ($value), must be integer"
  261.         }
  262.         set state($flag) $value
  263.     } else {
  264.         unset $token
  265.         return -code error "Unknown option $flag, can be: $usage"
  266.     }
  267.     }
  268.  
  269.     # Make sure -query and -querychannel aren't both specified
  270.  
  271.     set isQueryChannel [info exists state(-querychannel)]
  272.     set isQuery [info exists state(-query)]
  273.     if {$isQuery && $isQueryChannel} {
  274.     unset $token
  275.     return -code error "Can't combine -query and -querychannel options!"
  276.     }
  277.  
  278.     # Validate URL, determine the server host and port, and check proxy case
  279.  
  280.     if {![regexp -nocase {^(([^:]*)://)?([^/:]+)(:([0-9]+))?(/.*)?$} $url \
  281.         x prefix proto host y port srvurl]} {
  282.     unset $token
  283.     return -code error "Unsupported URL: $url"
  284.     }
  285.     if {[string length $proto] == 0} {
  286.     set proto http
  287.     set url ${proto}://$url
  288.     }
  289.     if {![info exists urlTypes($proto)]} {
  290.     unset $token
  291.     return -code error "Unsupported URL type \"$proto\""
  292.     }
  293.     set defport [lindex $urlTypes($proto) 0]
  294.     set defcmd [lindex $urlTypes($proto) 1]
  295.  
  296.     if {[string length $port] == 0} {
  297.     set port $defport
  298.     }
  299.     if {[string length $srvurl] == 0} {
  300.     set srvurl /
  301.     }
  302.     if {[string length $proto] == 0} {
  303.     set url http://$url
  304.     }
  305.     set state(url) $url
  306.     if {![catch {$http(-proxyfilter) $host} proxy]} {
  307.     set phost [lindex $proxy 0]
  308.     set pport [lindex $proxy 1]
  309.     }
  310.  
  311.     # If a timeout is specified we set up the after event
  312.     # and arrange for an asynchronous socket connection.
  313.  
  314.     if {$state(-timeout) > 0} {
  315.     set state(after) [after $state(-timeout) \
  316.         [list http::reset $token timeout]]
  317.     set async -async
  318.     } else {
  319.     set async ""
  320.     }
  321.  
  322.     # If we are using the proxy, we must pass in the full URL that
  323.     # includes the server name.
  324.  
  325.     if {[info exists phost] && [string length $phost]} {
  326.     set srvurl $url
  327.     set conStat [catch {eval $defcmd $async {$phost $pport}} s]
  328.     } else {
  329.     set conStat [catch {eval $defcmd $async {$host $port}} s]
  330.     }
  331.     if {$conStat} {
  332.  
  333.     # something went wrong while trying to establish the connection
  334.     # Clean up after events and such, but DON'T call the command callback
  335.     # (if available) because we're going to throw an exception from here
  336.     # instead.
  337.     Finish $token "" 1
  338.     cleanup $token
  339.     return -code error $s
  340.     }
  341.     set state(sock) $s
  342.  
  343.     # Wait for the connection to complete
  344.  
  345.     if {$state(-timeout) > 0} {
  346.     fileevent $s writable [list http::Connect $token]
  347.     http::wait $token
  348.  
  349.     if {[string equal $state(status) "error"]} {
  350.         # something went wrong while trying to establish the connection
  351.         # Clean up after events and such, but DON'T call the command
  352.         # callback (if available) because we're going to throw an 
  353.         # exception from here instead.
  354.         set err [lindex $state(error) 0]
  355.         cleanup $token
  356.         return -code error $err
  357.     } elseif {![string equal $state(status) "connect"]} {
  358.         # Likely to be connection timeout
  359.         return $token
  360.     }
  361.     set state(status) ""
  362.     }
  363.  
  364.     # Send data in cr-lf format, but accept any line terminators
  365.  
  366.     fconfigure $s -translation {auto crlf} -buffersize $state(-blocksize)
  367.  
  368.     # The following is disallowed in safe interpreters, but the socket
  369.     # is already in non-blocking mode in that case.
  370.  
  371.     catch {fconfigure $s -blocking off}
  372.     set how GET
  373.     if {$isQuery} {
  374.     set state(querylength) [string length $state(-query)]
  375.     if {$state(querylength) > 0} {
  376.         set how POST
  377.         set contDone 0
  378.     } else {
  379.         # there's no query data
  380.         unset state(-query)
  381.         set isQuery 0
  382.     }
  383.     } elseif {$state(-validate)} {
  384.     set how HEAD
  385.     } elseif {$isQueryChannel} {
  386.     set how POST
  387.     # The query channel must be blocking for the async Write to
  388.     # work properly.
  389.     fconfigure $state(-querychannel) -blocking 1 -translation binary
  390.     set contDone 0
  391.     }
  392.  
  393.     if {[catch {
  394.     puts $s "$how $srvurl HTTP/1.0"
  395.     puts $s "Accept: $http(-accept)"
  396.     puts $s "Host: $host"
  397.     puts $s "User-Agent: $http(-useragent)"
  398.     foreach {key value} $state(-headers) {
  399.         regsub -all \[\n\r\]  $value {} value
  400.         set key [string trim $key]
  401.         if {[string equal $key "Content-Length"]} {
  402.         set contDone 1
  403.         set state(querylength) $value
  404.         }
  405.         if {[string length $key]} {
  406.         puts $s "$key: $value"
  407.         }
  408.     }
  409.     if {$isQueryChannel && $state(querylength) == 0} {
  410.         # Try to determine size of data in channel
  411.         # If we cannot seek, the surrounding catch will trap us
  412.  
  413.         set start [tell $state(-querychannel)]
  414.         seek $state(-querychannel) 0 end
  415.         set state(querylength) \
  416.             [expr {[tell $state(-querychannel)] - $start}]
  417.         seek $state(-querychannel) $start
  418.     }
  419.  
  420.     # Flush the request header and set up the fileevent that will
  421.     # either push the POST data or read the response.
  422.     #
  423.     # fileevent note:
  424.     #
  425.     # It is possible to have both the read and write fileevents active
  426.     # at this point.  The only scenario it seems to affect is a server
  427.     # that closes the connection without reading the POST data.
  428.     # (e.g., early versions TclHttpd in various error cases).
  429.     # Depending on the platform, the client may or may not be able to
  430.     # get the response from the server because of the error it will
  431.     # get trying to write the post data.  Having both fileevents active
  432.     # changes the timing and the behavior, but no two platforms
  433.     # (among Solaris, Linux, and NT)  behave the same, and none 
  434.     # behave all that well in any case.  Servers should always read thier
  435.     # POST data if they expect the client to read their response.
  436.         
  437.     if {$isQuery || $isQueryChannel} {
  438.         puts $s "Content-Type: $state(-type)"
  439.         if {!$contDone} {
  440.         puts $s "Content-Length: $state(querylength)"
  441.         }
  442.         puts $s ""
  443.         fconfigure $s -translation {auto binary}
  444.         fileevent $s writable [list http::Write $token]
  445.     } else {
  446.         puts $s ""
  447.         flush $s
  448.         fileevent $s readable [list http::Event $token]
  449.     }
  450.  
  451.     if {! [info exists state(-command)]} {
  452.  
  453.         # geturl does EVERYTHING asynchronously, so if the user
  454.         # calls it synchronously, we just do a wait here.
  455.  
  456.         wait $token
  457.         if {[string equal $state(status) "error"]} {
  458.         # Something went wrong, so throw the exception, and the
  459.         # enclosing catch will do cleanup.
  460.         return -code error [lindex $state(error) 0]
  461.         }        
  462.     }
  463.     } err]} {
  464.     # The socket probably was never connected,
  465.     # or the connection dropped later.
  466.  
  467.     # Clean up after events and such, but DON'T call the command callback
  468.     # (if available) because we're going to throw an exception from here
  469.     # instead.
  470.     
  471.     # if state(status) is error, it means someone's already called Finish
  472.     # to do the above-described clean up.
  473.     if {[string equal $state(status) "error"]} {
  474.         Finish $token $err 1
  475.     }
  476.     cleanup $token
  477.     return -code error $err
  478.     }
  479.  
  480.     return $token
  481. }
  482.  
  483. # Data access functions:
  484. # Data - the URL data
  485. # Status - the transaction status: ok, reset, eof, timeout
  486. # Code - the HTTP transaction code, e.g., 200
  487. # Size - the size of the URL data
  488.  
  489. proc http::data {token} {
  490.     variable $token
  491.     upvar 0 $token state
  492.     return $state(body)
  493. }
  494. proc http::status {token} {
  495.     variable $token
  496.     upvar 0 $token state
  497.     return $state(status)
  498. }
  499. proc http::code {token} {
  500.     variable $token
  501.     upvar 0 $token state
  502.     return $state(http)
  503. }
  504. proc http::ncode {token} {
  505.     variable $token
  506.     upvar 0 $token state
  507.     if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
  508.     return $numeric_code
  509.     } else {
  510.     return $state(http)
  511.     }
  512. }
  513. proc http::size {token} {
  514.     variable $token
  515.     upvar 0 $token state
  516.     return $state(currentsize)
  517. }
  518.  
  519. proc http::error {token} {
  520.     variable $token
  521.     upvar 0 $token state
  522.     if {[info exists state(error)]} {
  523.     return $state(error)
  524.     }
  525.     return ""
  526. }
  527.  
  528. # http::cleanup
  529. #
  530. #    Garbage collect the state associated with a transaction
  531. #
  532. # Arguments
  533. #    token    The token returned from http::geturl
  534. #
  535. # Side Effects
  536. #    unsets the state array
  537.  
  538. proc http::cleanup {token} {
  539.     variable $token
  540.     upvar 0 $token state
  541.     if {[info exist state]} {
  542.     unset state
  543.     }
  544. }
  545.  
  546. # http::Connect
  547. #
  548. #    This callback is made when an asyncronous connection completes.
  549. #
  550. # Arguments
  551. #    token    The token returned from http::geturl
  552. #
  553. # Side Effects
  554. #    Sets the status of the connection, which unblocks
  555. #     the waiting geturl call
  556.  
  557. proc http::Connect {token} {
  558.     variable $token
  559.     upvar 0 $token state
  560.     global errorInfo errorCode
  561.     if {[eof $state(sock)] ||
  562.     [string length [fconfigure $state(sock) -error]]} {
  563.         Finish $token "connect failed [fconfigure $state(sock) -error]" 1
  564.     } else {
  565.     set state(status) connect
  566.     fileevent $state(sock) writable {}
  567.     }
  568.     return
  569. }
  570.  
  571. # http::Write
  572. #
  573. #    Write POST query data to the socket
  574. #
  575. # Arguments
  576. #    token    The token for the connection
  577. #
  578. # Side Effects
  579. #    Write the socket and handle callbacks.
  580.  
  581. proc http::Write {token} {
  582.     variable $token
  583.     upvar 0 $token state
  584.     set s $state(sock)
  585.     
  586.     # Output a block.  Tcl will buffer this if the socket blocks
  587.     
  588.     set done 0
  589.     if {[catch {
  590.     
  591.     # Catch I/O errors on dead sockets
  592.  
  593.     if {[info exists state(-query)]} {
  594.         
  595.         # Chop up large query strings so queryprogress callback
  596.         # can give smooth feedback
  597.  
  598.         puts -nonewline $s \
  599.             [string range $state(-query) $state(queryoffset) \
  600.             [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
  601.         incr state(queryoffset) $state(-queryblocksize)
  602.         if {$state(queryoffset) >= $state(querylength)} {
  603.         set state(queryoffset) $state(querylength)
  604.         set done 1
  605.         }
  606.     } else {
  607.         
  608.         # Copy blocks from the query channel
  609.  
  610.         set outStr [read $state(-querychannel) $state(-queryblocksize)]
  611.         puts -nonewline $s $outStr
  612.         incr state(queryoffset) [string length $outStr]
  613.         if {[eof $state(-querychannel)]} {
  614.         set done 1
  615.         }
  616.     }
  617.     } err]} {
  618.     # Do not call Finish here, but instead let the read half of
  619.     # the socket process whatever server reply there is to get.
  620.  
  621.     set state(posterror) $err
  622.     set done 1
  623.     }
  624.     if {$done} {
  625.     catch {flush $s}
  626.     fileevent $s writable {}
  627.     fileevent $s readable [list http::Event $token]
  628.     }
  629.  
  630.     # Callback to the client after we've completely handled everything
  631.  
  632.     if {[string length $state(-queryprogress)]} {
  633.     eval $state(-queryprogress) [list $token $state(querylength)\
  634.         $state(queryoffset)]
  635.     }
  636. }
  637.  
  638. # http::Event
  639. #
  640. #    Handle input on the socket
  641. #
  642. # Arguments
  643. #    token    The token returned from http::geturl
  644. #
  645. # Side Effects
  646. #    Read the socket and handle callbacks.
  647.  
  648.  proc http::Event {token} {
  649.     variable $token
  650.     upvar 0 $token state
  651.     set s $state(sock)
  652.  
  653.      if {[eof $s]} {
  654.     Eof $token
  655.     return
  656.     }
  657.     if {[string equal $state(state) "header"]} {
  658.     if {[catch {gets $s line} n]} {
  659.         Finish $token $n
  660.     } elseif {$n == 0} {
  661.         set state(state) body
  662.         if {![regexp -nocase ^text $state(type)]} {
  663.         # Turn off conversions for non-text data
  664.         fconfigure $s -translation binary
  665.         if {[info exists state(-channel)]} {
  666.             fconfigure $state(-channel) -translation binary
  667.         }
  668.         }
  669.         if {[info exists state(-channel)] &&
  670.             ![info exists state(-handler)]} {
  671.         # Initiate a sequence of background fcopies
  672.         fileevent $s readable {}
  673.         CopyStart $s $token
  674.         }
  675.     } elseif {$n > 0} {
  676.         if {[regexp -nocase {^content-type:(.+)$} $line x type]} {
  677.         set state(type) [string trim $type]
  678.         }
  679.         if {[regexp -nocase {^content-length:(.+)$} $line x length]} {
  680.         set state(totalsize) [string trim $length]
  681.         }
  682.         if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
  683.         lappend state(meta) $key [string trim $value]
  684.         } elseif {[regexp ^HTTP $line]} {
  685.         set state(http) $line
  686.         }
  687.     }
  688.     } else {
  689.     if {[catch {
  690.         if {[info exists state(-handler)]} {
  691.         set n [eval $state(-handler) {$s $token}]
  692.         } else {
  693.         set block [read $s $state(-blocksize)]
  694.         set n [string length $block]
  695.         if {$n >= 0} {
  696.             append state(body) $block
  697.         }
  698.         }
  699.         if {$n >= 0} {
  700.         incr state(currentsize) $n
  701.         }
  702.     } err]} {
  703.         Finish $token $err
  704.     } else {
  705.         if {[info exists state(-progress)]} {
  706.         eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
  707.         }
  708.     }
  709.     }
  710. }
  711.  
  712. # http::CopyStart
  713. #
  714. #    Error handling wrapper around fcopy
  715. #
  716. # Arguments
  717. #    s    The socket to copy from
  718. #    token    The token returned from http::geturl
  719. #
  720. # Side Effects
  721. #    This closes the connection upon error
  722.  
  723.  proc http::CopyStart {s token} {
  724.     variable $token
  725.     upvar 0 $token state
  726.     if {[catch {
  727.     fcopy $s $state(-channel) -size $state(-blocksize) -command \
  728.         [list http::CopyDone $token]
  729.     } err]} {
  730.     Finish $token $err
  731.     }
  732. }
  733.  
  734. # http::CopyDone
  735. #
  736. #    fcopy completion callback
  737. #
  738. # Arguments
  739. #    token    The token returned from http::geturl
  740. #    count    The amount transfered
  741. #
  742. # Side Effects
  743. #    Invokes callbacks
  744.  
  745.  proc http::CopyDone {token count {error {}}} {
  746.     variable $token
  747.     upvar 0 $token state
  748.     set s $state(sock)
  749.     incr state(currentsize) $count
  750.     if {[info exists state(-progress)]} {
  751.     eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
  752.     }
  753.     # At this point the token may have been reset
  754.     if {[string length $error]} {
  755.     Finish $token $error
  756.     } elseif {[catch {eof $s} iseof] || $iseof} {
  757.     Eof $token
  758.     } else {
  759.     CopyStart $s $token
  760.     }
  761. }
  762.  
  763. # http::Eof
  764. #
  765. #    Handle eof on the socket
  766. #
  767. # Arguments
  768. #    token    The token returned from http::geturl
  769. #
  770. # Side Effects
  771. #    Clean up the socket
  772.  
  773.  proc http::Eof {token} {
  774.     variable $token
  775.     upvar 0 $token state
  776.     if {[string equal $state(state) "header"]} {
  777.     # Premature eof
  778.     set state(status) eof
  779.     } else {
  780.     set state(status) ok
  781.     }
  782.     set state(state) eof
  783.     Finish $token
  784. }
  785.  
  786. # http::wait --
  787. #
  788. #    See documentaion for details.
  789. #
  790. # Arguments:
  791. #    token    Connection token.
  792. #
  793. # Results:
  794. #        The status after the wait.
  795.  
  796. proc http::wait {token} {
  797.     variable $token
  798.     upvar 0 $token state
  799.  
  800.     if {![info exists state(status)] || [string length $state(status)] == 0} {
  801.     # We must wait on the original variable name, not the upvar alias
  802.     vwait $token\(status)
  803.     }
  804.  
  805.     return $state(status)
  806. }
  807.  
  808. # http::formatQuery --
  809. #
  810. #    See documentaion for details.
  811. #    Call http::formatQuery with an even number of arguments, where 
  812. #    the first is a name, the second is a value, the third is another 
  813. #    name, and so on.
  814. #
  815. # Arguments:
  816. #    args    A list of name-value pairs.
  817. #
  818. # Results:
  819. #        TODO
  820.  
  821. proc http::formatQuery {args} {
  822.     set result ""
  823.     set sep ""
  824.     foreach i $args {
  825.     append result $sep [mapReply $i]
  826.     if {[string compare $sep "="]} {
  827.         set sep =
  828.     } else {
  829.         set sep &
  830.     }
  831.     }
  832.     return $result
  833. }
  834.  
  835. # http::mapReply --
  836. #
  837. #    Do x-www-urlencoded character mapping
  838. #
  839. # Arguments:
  840. #    string    The string the needs to be encoded
  841. #
  842. # Results:
  843. #       The encoded string
  844.  
  845.  proc http::mapReply {string} {
  846.     variable formMap
  847.  
  848.     # The spec says: "non-alphanumeric characters are replaced by '%HH'"
  849.     # 1 leave alphanumerics characters alone
  850.     # 2 Convert every other character to an array lookup
  851.     # 3 Escape constructs that are "special" to the tcl parser
  852.     # 4 "subst" the result, doing all the array substitutions
  853.  
  854.     set alphanumeric    a-zA-Z0-9
  855.     regsub -all \[^$alphanumeric\] $string {$formMap(&)} string
  856.     regsub -all \n $string {\\n} string
  857.     regsub -all \t $string {\\t} string
  858.     regsub -all {[][{})\\]\)} $string {\\&} string
  859.     return [subst $string]
  860. }
  861.  
  862. # http::ProxyRequired --
  863. #    Default proxy filter. 
  864. #
  865. # Arguments:
  866. #    host    The destination host
  867. #
  868. # Results:
  869. #       The current proxy settings
  870.  
  871.  proc http::ProxyRequired {host} {
  872.     variable http
  873.     if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
  874.     if {![info exists http(-proxyport)] || ![string length $http(-proxyport)]} {
  875.         set http(-proxyport) 8080
  876.     }
  877.     return [list $http(-proxyhost) $http(-proxyport)]
  878.     } else {
  879.     return {}
  880.     }
  881. }
  882.