r00t-err0r

http.tcl

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