http.tcl
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:28k
源码类别:

通讯编程

开发平台:

Visual C++

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