| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| package require Tcl 8.6- |
| |
| |
| package provide http 2.9.8 |
|
|
| namespace eval http { |
| |
|
|
| variable http |
| if {![info exists http]} { |
| array set http { |
| -accept */* |
| -pipeline 1 |
| -postfresh 0 |
| -proxyhost {} |
| -proxyport {} |
| -proxyfilter http::ProxyRequired |
| -repost 0 |
| -urlencoding utf-8 |
| -zip 1 |
| } |
| |
| |
| |
| |
| |
| if {[interp issafe]} { |
| set http(-useragent) "Mozilla/5.0\ |
| (Windows; U;\ |
| Windows NT 10.0)\ |
| http/[package provide http] Tcl/[package provide Tcl]" |
| } else { |
| set http(-useragent) "Mozilla/5.0\ |
| ([string totitle $::tcl_platform(platform)]; U;\ |
| $::tcl_platform(os) $::tcl_platform(osVersion))\ |
| http/[package provide http] Tcl/[package provide Tcl]" |
| } |
| } |
|
|
| proc init {} { |
| |
| |
| |
| |
| |
| for {set i 0} {$i <= 256} {incr i} { |
| set c [format %c $i] |
| if {![string match {[-._~a-zA-Z0-9]} $c]} { |
| set map($c) %[format %.2X $i] |
| } |
| } |
| |
| set map(\n) %0D%0A |
| variable formMap [array get map] |
|
|
| |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
| if {[info exists socketMapping]} { |
| |
| foreach {url sock} [array get socketMapping] { |
| unset -nocomplain socketClosing($url) |
| unset -nocomplain socketPlayCmd($url) |
| CloseSocket $sock |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| array unset socketMapping |
| array unset socketRdState |
| array unset socketWrState |
| array unset socketRdQueue |
| array unset socketWrQueue |
| array unset socketClosing |
| array unset socketPlayCmd |
| array set socketMapping {} |
| array set socketRdState {} |
| array set socketWrState {} |
| array set socketRdQueue {} |
| array set socketWrQueue {} |
| array set socketClosing {} |
| array set socketPlayCmd {} |
| } |
| init |
|
|
| variable urlTypes |
| if {![info exists urlTypes]} { |
| set urlTypes(http) [list 80 ::socket] |
| } |
|
|
| variable encodings [string tolower [encoding names]] |
| |
| variable defaultCharset |
| if {![info exists defaultCharset]} { |
| set defaultCharset "iso8859-1" |
| } |
|
|
| |
| variable strict |
| if {![info exists strict]} { |
| set strict 1 |
| } |
|
|
| |
| variable defaultKeepalive |
| if {![info exists defaultKeepalive]} { |
| set defaultKeepalive 0 |
| } |
|
|
| namespace export geturl config reset wait formatQuery quoteString |
| namespace export register unregister registerError |
| |
| |
| |
| |
| |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| if {[info command http::Log] eq {}} {proc http::Log {args} {}} |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::register {proto port command} { |
| variable urlTypes |
| set urlTypes([string tolower $proto]) [list $port $command] |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::unregister {proto} { |
| variable urlTypes |
| set lower [string tolower $proto] |
| if {![info exists urlTypes($lower)]} { |
| return -code error "unsupported url type \"$proto\"" |
| } |
| set old $urlTypes($lower) |
| unset urlTypes($lower) |
| return $old |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::config {args} { |
| variable http |
| set options [lsort [array names http -*]] |
| set usage [join $options ", "] |
| if {[llength $args] == 0} { |
| set result {} |
| foreach name $options { |
| lappend result $name $http($name) |
| } |
| return $result |
| } |
| set options [string map {- ""} $options] |
| set pat ^-(?:[join $options |])$ |
| if {[llength $args] == 1} { |
| set flag [lindex $args 0] |
| if {![regexp -- $pat $flag]} { |
| return -code error "Unknown option $flag, must be: $usage" |
| } |
| return $http($flag) |
| } else { |
| foreach {flag value} $args { |
| if {![regexp -- $pat $flag]} { |
| return -code error "Unknown option $flag, must be: $usage" |
| } |
| set http($flag) $value |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::Finish {token {errormsg ""} {skipCB 0}} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| global errorInfo errorCode |
| set closeQueue 0 |
| if {$errormsg ne ""} { |
| set state(error) [list $errormsg $errorInfo $errorCode] |
| set state(status) "error" |
| } |
| if {[info commands ${token}EventCoroutine] ne {}} { |
| rename ${token}EventCoroutine {} |
| } |
|
|
| |
| set upgradeResponse \ |
| [expr { [info exists state(upgradeRequest)] && $state(upgradeRequest) |
| && [info exists state(http)] && [ncode $token] eq {101} |
| && [info exists state(connection)] && "upgrade" in $state(connection) |
| && [info exists state(upgrade)] && "" ne $state(upgrade)}] |
|
|
| if { ($state(status) eq "timeout") |
| || ($state(status) eq "error") |
| || ($state(status) eq "eof") |
| } { |
| set closeQueue 1 |
| set connId $state(socketinfo) |
| set sock $state(sock) |
| CloseSocket $state(sock) $token |
| } elseif {$upgradeResponse} { |
| |
| |
| |
| |
| |
| |
| |
| catch {fileevent $state(sock) readable {}} |
| catch {fileevent $state(sock) writable {}} |
| } elseif { |
| ([info exists state(-keepalive)] && !$state(-keepalive)) |
| || ([info exists state(connection)] && ("close" in $state(connection))) |
| } { |
| set closeQueue 1 |
| set connId $state(socketinfo) |
| set sock $state(sock) |
| CloseSocket $state(sock) $token |
| } elseif { |
| ([info exists state(-keepalive)] && $state(-keepalive)) |
| && ([info exists state(connection)] && ("close" ni $state(connection))) |
| } { |
| KeepSocket $token |
| } |
| if {[info exists state(after)]} { |
| after cancel $state(after) |
| unset state(after) |
| } |
| if {[info exists state(-command)] && (!$skipCB) |
| && (![info exists state(done-command-cb)])} { |
| set state(done-command-cb) yes |
| if {[catch {eval $state(-command) {$token}} err] && $errormsg eq ""} { |
| set state(error) [list $err $errorInfo $errorCode] |
| set state(status) error |
| } |
| } |
|
|
| if { $closeQueue |
| && [info exists socketMapping($connId)] |
| && ($socketMapping($connId) eq $sock) |
| } { |
| http::CloseQueuedQueries $connId $token |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::KeepSocket {token} { |
| variable http |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
|
|
| |
| |
| |
| catch {fileevent $state(sock) readable [list http::CheckEof $state(sock)]} |
|
|
| |
| |
| set TEST_EOF 0 |
| if {$TEST_EOF} { |
| |
| |
| catch {fileevent $state(sock) readable {}} |
| } |
|
|
| if { [info exists state(socketinfo)] |
| && [info exists socketMapping($state(socketinfo))] |
| } { |
| set connId $state(socketinfo) |
| |
| set socketRdState($connId) Rready |
|
|
| if { $state(-pipeline) |
| && [info exists socketRdQueue($connId)] |
| && [llength $socketRdQueue($connId)] |
| } { |
| |
| |
| set token3 [lindex $socketRdQueue($connId) 0] |
| set socketRdQueue($connId) [lrange $socketRdQueue($connId) 1 end] |
| variable $token3 |
| upvar 0 $token3 state3 |
| set tk2 [namespace tail $token3] |
|
|
| |
| set socketRdState($connId) $token3 |
| ReceiveResponse $token3 |
|
|
| |
| |
| |
| |
| |
| } elseif { |
| $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "peNding") |
|
|
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && (![set token3 [lindex $socketWrQueue($connId) 0] |
| set ${token3}(-pipeline) |
| ] |
| ) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| variable $token3 |
| set conn [set ${token3}(tmpConnArgs)] |
| |
| set socketRdState($connId) $token3 |
| set socketWrState($connId) $token3 |
| set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] |
| |
| fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] |
| |
|
|
| } elseif { |
| $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "peNding") |
|
|
| } { |
| |
| |
| |
| |
| |
| Log ^X$tk <<<<< Error in queueing of requests >>>>> - token $token |
|
|
| } elseif { |
| $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "Wready") |
|
|
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && (![set token3 [lindex $socketWrQueue($connId) 0] |
| set ${token3}(-pipeline) |
| ] |
| ) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| variable $token3 |
| set conn [set ${token3}(tmpConnArgs)] |
| |
| set socketRdState($connId) $token3 |
| set socketWrState($connId) $token3 |
| set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] |
| |
| fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] |
| |
|
|
| } elseif { |
| (!$state(-pipeline)) |
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && ("close" ni $state(connection)) |
| } { |
| |
| |
| |
| |
| |
| set token3 [lindex $socketWrQueue($connId) 0] |
| variable $token3 |
| set conn [set ${token3}(tmpConnArgs)] |
| |
| set socketRdState($connId) $token3 |
| set socketWrState($connId) $token3 |
| set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] |
| |
| fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] |
| |
|
|
| } elseif {(!$state(-pipeline))} { |
| set socketWrState($connId) Wready |
| |
| } |
|
|
| } else { |
| CloseSocket $state(sock) $token |
| |
| |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::CheckEof {sock} { |
| set junk [read $sock] |
| set n [string length $junk] |
| if {$n} { |
| Log "WARNING: $n bytes received but no HTTP request sent" |
| } |
|
|
| if {[catch {eof $sock} res] || $res} { |
| |
| |
| |
| CloseSocket $sock |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| proc http::CloseSocket {s {token {}}} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| set tk [namespace tail $token] |
|
|
| catch {fileevent $s readable {}} |
| set connId {} |
| if {$token ne ""} { |
| variable $token |
| upvar 0 $token state |
| if {[info exists state(socketinfo)]} { |
| set connId $state(socketinfo) |
| } |
| } else { |
| set map [array get socketMapping] |
| set ndx [lsearch -exact $map $s] |
| if {$ndx >= 0} { |
| incr ndx -1 |
| set connId [lindex $map $ndx] |
| } |
| } |
| if { ($connId ne {}) |
| && [info exists socketMapping($connId)] |
| && ($socketMapping($connId) eq $s) |
| } { |
| Log "Closing connection $connId (sock $socketMapping($connId))" |
| if {[catch {close $socketMapping($connId)} err]} { |
| Log "Error closing connection: $err" |
| } |
| if {$token eq {}} { |
| |
| |
| http::CloseQueuedQueries $connId |
| } |
| } else { |
| Log "Closing socket $s (no connection info)" |
| if {[catch {close $s} err]} { |
| Log "Error closing socket: $err" |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::CloseQueuedQueries {connId {token {}}} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| if {![info exists socketMapping($connId)]} { |
| |
| |
| return |
| } |
|
|
| |
| if {$token eq {}} { |
| set tk {} |
| } else { |
| set tk [namespace tail $token] |
| } |
|
|
| if { [info exists socketPlayCmd($connId)] |
| && ($socketPlayCmd($connId) ne {ReplayIfClose Wready {} {}}) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| set unfinished $socketPlayCmd($connId) |
| set socketRdQueue($connId) {} |
| set socketWrQueue($connId) {} |
| } else { |
| set unfinished {} |
| } |
|
|
| Unset $connId |
|
|
| if {$unfinished ne {}} { |
| Log ^R$tk Any unfinished transactions (excluding $token) failed \ |
| - token $token |
| {*}$unfinished |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| proc http::Unset {connId} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| unset socketMapping($connId) |
| unset socketRdState($connId) |
| unset socketWrState($connId) |
| unset -nocomplain socketRdQueue($connId) |
| unset -nocomplain socketWrQueue($connId) |
| unset -nocomplain socketClosing($connId) |
| unset -nocomplain socketPlayCmd($connId) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::reset {token {why reset}} { |
| variable $token |
| upvar 0 $token state |
| set state(status) $why |
| catch {fileevent $state(sock) readable {}} |
| catch {fileevent $state(sock) writable {}} |
| Finish $token |
| if {[info exists state(error)]} { |
| set errorlist $state(error) |
| unset state |
| eval ::error $errorlist |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::geturl {url args} { |
| variable http |
| variable urlTypes |
| variable defaultCharset |
| variable defaultKeepalive |
| variable strict |
|
|
| |
| |
|
|
| if {![info exists http(uid)]} { |
| set http(uid) 0 |
| } |
| set token [namespace current]::[incr http(uid)] |
| |
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| reset $token |
| Log ^A$tk URL $url - token $token |
|
|
| |
|
|
| array set state { |
| -binary false |
| -blocksize 8192 |
| -queryblocksize 8192 |
| -validate 0 |
| -headers {} |
| -timeout 0 |
| -type application/x-www-form-urlencoded |
| -queryprogress {} |
| -protocol 1.1 |
| binary 0 |
| state created |
| meta {} |
| method {} |
| coding {} |
| currentsize 0 |
| totalsize 0 |
| querylength 0 |
| queryoffset 0 |
| type text/html |
| body {} |
| status "" |
| http "" |
| connection keep-alive |
| } |
| set state(-keepalive) $defaultKeepalive |
| set state(-strict) $strict |
| |
| array set type { |
| -binary boolean |
| -blocksize integer |
| -queryblocksize integer |
| -strict boolean |
| -timeout integer |
| -validate boolean |
| -headers list |
| } |
| set state(charset) $defaultCharset |
| set options { |
| -binary -blocksize -channel -command -handler -headers -keepalive |
| -method -myaddr -progress -protocol -query -queryblocksize |
| -querychannel -queryprogress -strict -timeout -type -validate |
| } |
| set usage [join [lsort $options] ", "] |
| set options [string map {- ""} $options] |
| set pat ^-(?:[join $options |])$ |
| foreach {flag value} $args { |
| if {[regexp -- $pat $flag]} { |
| |
| if { [info exists type($flag)] |
| && (![string is $type($flag) -strict $value]) |
| } { |
| unset $token |
| return -code error \ |
| "Bad value for $flag ($value), must be $type($flag)" |
| } |
| if {($flag eq "-headers") && ([llength $value] % 2 != 0)} { |
| unset $token |
| return -code error \ |
| "Bad value for $flag ($value), number of list elements must be even" |
| } |
| set state($flag) $value |
| } else { |
| unset $token |
| return -code error "Unknown option $flag, can be: $usage" |
| } |
| } |
|
|
| |
|
|
| set isQueryChannel [info exists state(-querychannel)] |
| set isQuery [info exists state(-query)] |
| if {$isQuery && $isQueryChannel} { |
| unset $token |
| return -code error "Can't combine -query and -querychannel options!" |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| set URLmatcher {(?x) # this is _expanded_ syntax |
| ^ |
| (?: (\w+) : ) ? # <protocol scheme> |
| (?: // |
| (?: |
| ( |
| [^@/\#?]+ # <userinfo part of authority> |
| ) @ |
| )? |
| ( # <host part of authority> |
| [^/:\#?]+ | # host name or IPv4 address |
| \[ [^/\#?]+ \] # IPv6 address in square brackets |
| ) |
| (?: : (\d+) )? # <port part of authority> |
| )? |
| ( [/\?] [^\#]*)? # <path> (including query) |
| (?: \# (.*) )? # <fragment> |
| $ |
| } |
|
|
| |
| if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} { |
| unset $token |
| return -code error "Unsupported URL: $url" |
| } |
| |
| set host [string trim $host {[]}] |
| if {$host eq ""} { |
| |
| |
| unset $token |
| return -code error "Missing host part: $url" |
| |
| |
| } |
| if {$port ne "" && $port > 65535} { |
| unset $token |
| return -code error "Invalid port number: $port" |
| } |
| |
| |
| if {$user ne ""} { |
| |
| set validityRE {(?xi) |
| ^ |
| (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+ |
| $ |
| } |
| if {$state(-strict) && ![regexp -- $validityRE $user]} { |
| unset $token |
| |
| if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} { |
| return -code error \ |
| "Illegal encoding character usage \"$bad\" in URL user" |
| } |
| return -code error "Illegal characters in URL user" |
| } |
| } |
| if {$srvurl ne ""} { |
| |
| |
| |
| if {[string index $srvurl 0] ne "/"} { |
| set srvurl /$srvurl |
| } |
| |
| set validityRE {(?xi) |
| ^ |
| |
| (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )* |
| |
| (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )? |
| $ |
| } |
| if {$state(-strict) && ![regexp -- $validityRE $srvurl]} { |
| unset $token |
| |
| if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} { |
| return -code error \ |
| "Illegal encoding character usage \"$bad\" in URL path" |
| } |
| return -code error "Illegal characters in URL path" |
| } |
| if {![regexp {^[^?#]+} $srvurl state(path)]} { |
| set state(path) / |
| } |
| } else { |
| set srvurl / |
| set state(path) / |
| } |
| if {$proto eq ""} { |
| set proto http |
| } |
| set lower [string tolower $proto] |
| if {![info exists urlTypes($lower)]} { |
| unset $token |
| return -code error "Unsupported URL type \"$proto\"" |
| } |
| set defport [lindex $urlTypes($lower) 0] |
| set defcmd [lindex $urlTypes($lower) 1] |
|
|
| if {$port eq ""} { |
| set port $defport |
| } |
| if {![catch {$http(-proxyfilter) $host} proxy]} { |
| set phost [lindex $proxy 0] |
| set pport [lindex $proxy 1] |
| } |
|
|
| |
| set url ${proto}:// |
| if {$user ne ""} { |
| append url $user |
| append url @ |
| } |
| append url $host |
| if {$port != $defport} { |
| append url : $port |
| } |
| append url $srvurl |
| |
| set state(url) $url |
|
|
| set sockopts [list -async] |
|
|
| |
| |
|
|
| if {[info exists phost] && ($phost ne "")} { |
| set srvurl $url |
| set targetAddr [list $phost $pport] |
| } else { |
| set targetAddr [list $host $port] |
| } |
| |
| set state(socketinfo) $host:$port |
|
|
| |
| |
| set state(accept-types) $http(-accept) |
|
|
| |
| set connectionValues [SplitCommaSeparatedFieldValue \ |
| [GetFieldValue $state(-headers) Connection]] |
| set connectionValues [string tolower $connectionValues] |
| set upgradeValues [SplitCommaSeparatedFieldValue \ |
| [GetFieldValue $state(-headers) Upgrade]] |
| set state(upgradeRequest) [expr { "upgrade" in $connectionValues |
| && [llength $upgradeValues] >= 1}] |
|
|
| if {$isQuery || $isQueryChannel} { |
| |
| |
| |
| |
| if {$http(-postfresh)} { |
| |
| |
| set state(-keepalive) 0 |
| } else { |
| |
| |
| |
| set state(-pipeline) 0 |
| } |
| } elseif {$state(upgradeRequest)} { |
| |
| |
| |
| set state(-keepalive) 0 |
| } else { |
| |
| set state(-pipeline) $http(-pipeline) |
| } |
|
|
| |
| |
| if {[info exists state(-handler)]} { |
| set state(-protocol) 1.0 |
| } |
|
|
| |
| if {$state(-protocol) eq "1.0"} { |
| set state(connection) close |
| set state(-keepalive) 0 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| set reusing 0 |
| set alreadyQueued 0 |
| if {$state(-keepalive)} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| if {[info exists socketMapping($state(socketinfo))]} { |
| |
| |
| |
| |
| |
| |
|
|
| if { [info exists socketClosing($state(socketinfo))] |
| && $socketClosing($state(socketinfo)) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| |
| set reusing 1 |
| set sock $socketMapping($state(socketinfo)) |
| Log "reusing socket $sock for $state(socketinfo) - token $token" |
|
|
| set alreadyQueued 1 |
| lassign $socketPlayCmd($state(socketinfo)) com0 com1 com2 com3 |
| lappend com3 $token |
| set socketPlayCmd($state(socketinfo)) [list $com0 $com1 $com2 $com3] |
| lappend socketWrQueue($state(socketinfo)) $token |
| } elseif {[catch {fconfigure $socketMapping($state(socketinfo))}]} { |
| |
| |
| |
| Log "WARNING: socket for $state(socketinfo) was closed\ |
| - token $token" |
| Log "WARNING - if testing, pay special attention to this\ |
| case (GH) which is seldom executed - token $token" |
|
|
| |
| |
| Unset $state(socketinfo) |
| } else { |
| |
| |
| |
| |
| |
| set reusing 1 |
| set sock $socketMapping($state(socketinfo)) |
| Log "reusing socket $sock for $state(socketinfo) - token $token" |
|
|
| } |
| |
| set state(connection) keep-alive |
| } |
| } |
|
|
| if {$reusing} { |
| |
| |
| set state(tmpState) [array get state] |
|
|
| |
| if {[info exists state(-myaddr)]} { |
| lappend sockopts -myaddr $state(-myaddr) |
| } |
|
|
| set state(tmpOpenCmd) [list {*}$defcmd {*}$sockopts {*}$targetAddr] |
| } |
|
|
| set state(reusing) $reusing |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| if {(!$state(reusing)) && ($state(-timeout) > 0)} { |
| set state(after) [after $state(-timeout) \ |
| [list http::reset $token timeout]] |
| } |
|
|
| if {![info exists sock]} { |
| |
| if {[info exists state(-myaddr)]} { |
| lappend sockopts -myaddr $state(-myaddr) |
| } |
| set pre [clock milliseconds] |
| |
| |
| if {[catch {eval $defcmd $sockopts $targetAddr} sock errdict]} { |
| |
| |
| |
| |
|
|
| set state(sock) NONE |
| Finish $token $sock 1 |
| cleanup $token |
| dict unset errdict -level |
| return -options $errdict $sock |
| } else { |
| |
| |
| |
| set delay [expr {[clock milliseconds] - $pre}] |
| if {$delay > 3000} { |
| Log socket delay $delay - token $token |
| } |
| fconfigure $sock -translation {auto crlf} \ |
| -buffersize $state(-blocksize) |
| |
| } |
| } |
| |
| |
| |
| |
| |
|
|
| set state(sock) $sock |
| Log "Using $sock for $state(socketinfo) - token $token" \ |
| [expr {$state(-keepalive)?"keepalive":""}] |
|
|
| if { $state(-keepalive) |
| && (![info exists socketMapping($state(socketinfo))]) |
| } { |
| |
| set socketMapping($state(socketinfo)) $sock |
|
|
| if {![info exists socketRdState($state(socketinfo))]} { |
| set socketRdState($state(socketinfo)) {} |
| set varName ::http::socketRdState($state(socketinfo)) |
| trace add variable $varName unset ::http::CancelReadPipeline |
| } |
| if {![info exists socketWrState($state(socketinfo))]} { |
| set socketWrState($state(socketinfo)) {} |
| set varName ::http::socketWrState($state(socketinfo)) |
| trace add variable $varName unset ::http::CancelWritePipeline |
| } |
|
|
| if {$state(-pipeline)} { |
| |
| |
| set socketRdState($state(socketinfo)) $token |
| set socketWrState($state(socketinfo)) $token |
| } else { |
| |
| |
| |
| |
| |
| set socketRdState($state(socketinfo)) $token |
| set socketWrState($state(socketinfo)) $token |
| } |
|
|
| set socketRdQueue($state(socketinfo)) {} |
| set socketWrQueue($state(socketinfo)) {} |
| set socketClosing($state(socketinfo)) 0 |
| set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}} |
| } |
|
|
| if {![info exists phost]} { |
| set phost "" |
| } |
| if {$reusing} { |
| |
| |
| set state(tmpConnArgs) [list $proto $phost $srvurl] |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| if {$alreadyQueued} { |
| |
| |
| |
| |
| |
| |
|
|
| } elseif { $reusing |
| && $state(-pipeline) |
| && ($socketWrState($state(socketinfo)) ne "Wready") |
| } { |
| |
| lappend socketWrQueue($state(socketinfo)) $token |
|
|
| } elseif { $reusing |
| && (!$state(-pipeline)) |
| && ($socketWrState($state(socketinfo)) ne "Wready") |
| } { |
| |
| |
| lappend socketWrQueue($state(socketinfo)) $token |
|
|
| } elseif { $reusing |
| && (!$state(-pipeline)) |
| && ($socketWrState($state(socketinfo)) eq "Wready") |
| && ($socketRdState($state(socketinfo)) ne "Rready") |
| } { |
| |
| |
| |
| |
| |
|
|
| set socketWrState($state(socketinfo)) peNding |
| lappend socketWrQueue($state(socketinfo)) $token |
|
|
| } else { |
| if {$reusing && $state(-pipeline)} { |
| |
| set socketWrState($state(socketinfo)) $token |
|
|
| } elseif {$reusing} { |
| |
| |
| set socketRdState($state(socketinfo)) $token |
| set socketWrState($state(socketinfo)) $token |
| } |
|
|
| |
| |
| |
| |
| fileevent $sock writable \ |
| [list http::Connect $token $proto $phost $srvurl] |
| } |
|
|
| |
| if {![info exists state(-command)]} { |
| |
| |
| http::wait $token |
|
|
| if {![info exists state]} { |
| |
| |
| |
| return $token |
| } elseif {$state(status) eq "error"} { |
| |
| |
| |
| |
| set err [lindex $state(error) 0] |
| cleanup $token |
| return -code error $err |
| } |
| } |
| |
| return $token |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::Connected {token proto phost srvurl} { |
| variable http |
| variable urlTypes |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
|
|
| if {$state(reusing) && (!$state(-pipeline)) && ($state(-timeout) > 0)} { |
| set state(after) [after $state(-timeout) \ |
| [list http::reset $token timeout]] |
| } |
|
|
| |
| set sock $state(sock) |
| set isQueryChannel [info exists state(-querychannel)] |
| set isQuery [info exists state(-query)] |
| regexp {^(.+):([^:]+)$} $state(socketinfo) {} host port |
|
|
| set lower [string tolower $proto] |
| set defport [lindex $urlTypes($lower) 0] |
|
|
| |
| |
| |
| lassign [fconfigure $sock -translation] trRead trWrite |
| fconfigure $sock -translation [list $trRead crlf] \ |
| -buffersize $state(-blocksize) |
|
|
| |
| |
|
|
| catch {fconfigure $sock -blocking off} |
| set how GET |
| if {$isQuery} { |
| set state(querylength) [string length $state(-query)] |
| if {$state(querylength) > 0} { |
| set how POST |
| set contDone 0 |
| } else { |
| |
| unset state(-query) |
| set isQuery 0 |
| } |
| } elseif {$state(-validate)} { |
| set how HEAD |
| } elseif {$isQueryChannel} { |
| set how POST |
| |
| |
| fconfigure $state(-querychannel) -blocking 1 -translation binary |
| set contDone 0 |
| } |
| if {[info exists state(-method)] && ($state(-method) ne "")} { |
| set how $state(-method) |
| } |
| set accept_types_seen 0 |
|
|
| Log ^B$tk begin sending request - token $token |
|
|
| if {[catch { |
| set state(method) $how |
| puts $sock "$how $srvurl HTTP/$state(-protocol)" |
| set hostValue [GetFieldValue $state(-headers) Host] |
| if {$hostValue ne {}} { |
| |
| regexp {^[^:]+} $hostValue state(host) |
| puts $sock "Host: $hostValue" |
| } elseif {$port == $defport} { |
| |
| |
| set state(host) $host |
| puts $sock "Host: $host" |
| } else { |
| set state(host) $host |
| puts $sock "Host: $host:$port" |
| } |
| puts $sock "User-Agent: $http(-useragent)" |
| if {($state(-protocol) > 1.0) && $state(-keepalive)} { |
| |
| |
| puts $sock "Connection: keep-alive" |
| } |
| if {($state(-protocol) > 1.0) && !$state(-keepalive)} { |
| puts $sock "Connection: close" |
| } |
| if {($state(-protocol) < 1.1)} { |
| |
| |
| |
| |
| |
| |
| puts $sock "Connection: close" |
| } |
| |
| |
| set accept_encoding_seen 0 |
| set content_type_seen 0 |
| foreach {key value} $state(-headers) { |
| set value [string map [list \n "" \r ""] $value] |
| set key [string map {" " -} [string trim $key]] |
| if {[string equal -nocase $key "host"]} { |
| continue |
| } |
| if {[string equal -nocase $key "accept-encoding"]} { |
| set accept_encoding_seen 1 |
| } |
| if {[string equal -nocase $key "accept"]} { |
| set accept_types_seen 1 |
| } |
| if {[string equal -nocase $key "content-type"]} { |
| set content_type_seen 1 |
| } |
| if {[string equal -nocase $key "content-length"]} { |
| set contDone 1 |
| set state(querylength) $value |
| } |
| if {[string length $key]} { |
| puts $sock "$key: $value" |
| } |
| } |
| |
| |
| if {!$accept_types_seen} { |
| puts $sock "Accept: $state(accept-types)" |
| } |
| if { (!$accept_encoding_seen) |
| && (![info exists state(-handler)]) |
| && $http(-zip) |
| } { |
| puts $sock "Accept-Encoding: gzip,deflate,compress" |
| } |
| if {$isQueryChannel && ($state(querylength) == 0)} { |
| |
| |
|
|
| set start [tell $state(-querychannel)] |
| seek $state(-querychannel) 0 end |
| set state(querylength) \ |
| [expr {[tell $state(-querychannel)] - $start}] |
| seek $state(-querychannel) $start |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| if {$isQuery || $isQueryChannel} { |
| |
| if {!$content_type_seen} { |
| puts $sock "Content-Type: $state(-type)" |
| } |
| if {!$contDone} { |
| puts $sock "Content-Length: $state(querylength)" |
| } |
| puts $sock "" |
| flush $sock |
| |
| |
| |
|
|
| lassign [fconfigure $sock -translation] trRead trWrite |
| fconfigure $sock -translation [list $trRead binary] |
| fileevent $sock writable [list http::Write $token] |
| |
| |
| } else { |
| |
| if { (![catch {fileevent $sock readable} binding]) |
| && ($binding eq [list http::CheckEof $sock]) |
| } { |
| |
| |
| |
| |
| |
| fileevent $sock readable {} |
| } |
| puts $sock "" |
| flush $sock |
| Log ^C$tk end sending request - token $token |
| |
|
|
| DoneRequest $token |
| } |
|
|
| } err]} { |
| |
| |
| |
| Log "WARNING - if testing, pay special attention to this\ |
| case (GI) which is seldom executed - token $token" |
| if {[info exists state(reusing)] && $state(reusing)} { |
| |
| |
| if {[TestForReplay $token write $err a]} { |
| return |
| } else { |
| Finish $token {failed to re-use socket} |
| } |
|
|
| |
| |
| |
| |
| |
| } elseif {$state(status) eq ""} { |
| |
| set msg [registerError $sock] |
| registerError $sock {} |
| if {$msg eq {}} { |
| set msg {failed to use socket} |
| } |
| Finish $token $msg |
| } elseif {$state(status) ne "error"} { |
| Finish $token $err |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::registerError {sock args} { |
| variable registeredErrors |
|
|
| if { ([llength $args] == 0) |
| && (![info exists registeredErrors($sock)]) |
| } { |
| return |
| } elseif { ([llength $args] == 1) |
| && ([lindex $args 0] eq {}) |
| } { |
| unset -nocomplain registeredErrors($sock) |
| return |
| } |
| set registeredErrors($sock) {*}$args |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::DoneRequest {token} { |
| variable http |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| set sock $state(sock) |
|
|
| |
| if {$state(reusing) && $state(-pipeline)} { |
| |
| |
| |
| |
| |
| set socketWrState($state(socketinfo)) Wready |
|
|
| |
| http::NextPipelinedWrite $token |
| } else { |
| |
| |
| |
| |
| |
| |
| } |
|
|
| |
| |
| |
| |
| if { $state(-keepalive) |
| && $state(-pipeline) |
| && [info exists socketRdState($state(socketinfo))] |
| && ($socketRdState($state(socketinfo)) eq "Rready") |
| } { |
| |
| set socketRdState($state(socketinfo)) $token |
| } |
|
|
| if { $state(-keepalive) |
| && $state(-pipeline) |
| && [info exists socketRdState($state(socketinfo))] |
| && ($socketRdState($state(socketinfo)) ne $token) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| lappend socketRdQueue($state(socketinfo)) $token |
| } else { |
| |
| |
| |
| ReceiveResponse $token |
| } |
| } |
|
|
| |
| |
| |
|
|
| proc http::ReceiveResponse {token} { |
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| set sock $state(sock) |
|
|
| |
| lassign [fconfigure $sock -translation] trRead trWrite |
| fconfigure $sock -translation [list auto $trWrite] \ |
| -buffersize $state(-blocksize) |
| Log ^D$tk begin receiving response - token $token |
|
|
| coroutine ${token}EventCoroutine http::Event $sock $token |
| if {[info exists state(-handler)] || [info exists state(-progress)]} { |
| fileevent $sock readable [list http::EventGateway $sock $token] |
| } else { |
| fileevent $sock readable ${token}EventCoroutine |
| } |
| return |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::EventGateway {sock token} { |
| variable $token |
| upvar 0 $token state |
| fileevent $sock readable {} |
| catch {${token}EventCoroutine} res opts |
| if {[info commands ${token}EventCoroutine] ne {}} { |
| |
| |
| |
| |
| |
| |
| |
| |
| catch {fileevent $sock readable [list http::EventGateway $sock $token]} |
| } |
|
|
| |
| return -options $opts $res |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::NextPipelinedWrite {token} { |
| variable http |
| variable socketRdState |
| variable socketWrState |
| variable socketWrQueue |
| variable socketClosing |
| variable $token |
| upvar 0 $token state |
| set connId $state(socketinfo) |
|
|
| if { [info exists socketClosing($connId)] |
| && $socketClosing($connId) |
| } { |
| |
| |
| |
| } elseif { $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "Wready") |
|
|
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && ([set token2 [lindex $socketWrQueue($connId) 0] |
| set ${token2}(-pipeline) |
| ] |
| ) |
| } { |
| |
| |
| set conn [set ${token2}(tmpConnArgs)] |
| set socketWrState($connId) $token2 |
| set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] |
| |
| fileevent $state(sock) writable [list http::Connect $token2 {*}$conn] |
| |
|
|
| |
| } elseif { $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "Wready") |
|
|
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && (![ set token3 [lindex $socketWrQueue($connId) 0] |
| set ${token3}(-pipeline) |
| ] |
| ) |
|
|
| && [info exists socketRdState($connId)] |
| && ($socketRdState($connId) eq "Rready") |
| } { |
| |
| |
| |
| variable $token3 |
| upvar 0 $token3 state3 |
| set conn [set ${token3}(tmpConnArgs)] |
| |
| set socketRdState($connId) $token3 |
| set socketWrState($connId) $token3 |
| set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] |
| |
| fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] |
| |
|
|
| } elseif { $state(-pipeline) |
| && [info exists socketWrState($connId)] |
| && ($socketWrState($connId) eq "Wready") |
|
|
| && [info exists socketWrQueue($connId)] |
| && [llength $socketWrQueue($connId)] |
| && (![set token2 [lindex $socketWrQueue($connId) 0] |
| set ${token2}(-pipeline) |
| ] |
| ) |
| } { |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| set socketWrState($connId) peNding |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::CancelReadPipeline {name1 connId op} { |
| variable socketRdQueue |
| |
| if {[info exists socketRdQueue($connId)]} { |
| set msg {the connection was closed by CancelReadPipeline} |
| foreach token $socketRdQueue($connId) { |
| set tk [namespace tail $token] |
| Log ^X$tk end of response "($msg)" - token $token |
| set ${token}(status) eof |
| Finish $token |
| } |
| set socketRdQueue($connId) {} |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::CancelWritePipeline {name1 connId op} { |
| variable socketWrQueue |
|
|
| |
| if {[info exists socketWrQueue($connId)]} { |
| set msg {the connection was closed by CancelWritePipeline} |
| foreach token $socketWrQueue($connId) { |
| set tk [namespace tail $token] |
| Log ^X$tk end of response "($msg)" - token $token |
| set ${token}(status) eof |
| Finish $token |
| } |
| set socketWrQueue($connId) {} |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::ReplayIfDead {tokenArg doing} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $tokenArg |
| upvar 0 $tokenArg stateArg |
|
|
| Log running http::ReplayIfDead for $tokenArg $doing |
|
|
| |
| |
|
|
| set InFlightR {} |
| set InFlightW {} |
|
|
| |
| if {$stateArg(-pipeline)} { |
| |
| |
| |
| |
| |
|
|
| if { [info exists socketRdState($stateArg(socketinfo))] |
| && ($socketRdState($stateArg(socketinfo)) ne "Rready") |
| } { |
| lappend InFlightR $socketRdState($stateArg(socketinfo)) |
| } elseif {($doing eq "read")} { |
| lappend InFlightR $tokenArg |
| } |
|
|
| if { [info exists socketWrState($stateArg(socketinfo))] |
| && $socketWrState($stateArg(socketinfo)) ni {Wready peNding} |
| } { |
| lappend InFlightW $socketWrState($stateArg(socketinfo)) |
| } elseif {($doing eq "write")} { |
| lappend InFlightW $tokenArg |
| } |
|
|
| |
| if { ($doing eq "read") |
| && [info exists socketRdState($stateArg(socketinfo))] |
| && ($tokenArg ne $socketRdState($stateArg(socketinfo))) |
| } { |
| Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \ |
| ne socketRdState($stateArg(socketinfo)) \ |
| $socketRdState($stateArg(socketinfo)) |
|
|
| } elseif { |
| ($doing eq "write") |
| && [info exists socketWrState($stateArg(socketinfo))] |
| && ($tokenArg ne $socketWrState($stateArg(socketinfo))) |
| } { |
| Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \ |
| ne socketWrState($stateArg(socketinfo)) \ |
| $socketWrState($stateArg(socketinfo)) |
| } |
| } else { |
| |
| |
| |
|
|
| |
| if {$tokenArg ne $socketRdState($stateArg(socketinfo))} { |
| Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \ |
| ne socketRdState($stateArg(socketinfo)) \ |
| $socketRdState($stateArg(socketinfo)) |
| } |
|
|
| |
| if { [info exists socketRdQueue($stateArg(socketinfo))] |
| && ($socketRdQueue($stateArg(socketinfo)) ne {}) |
| } { |
| Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \ |
| has read queue socketRdQueue($stateArg(socketinfo)) \ |
| $socketRdQueue($stateArg(socketinfo)) ne {} |
| } |
|
|
| lappend InFlightW $socketRdState($stateArg(socketinfo)) |
| set socketRdQueue($stateArg(socketinfo)) {} |
| } |
|
|
| set newQueue {} |
| lappend newQueue {*}$InFlightR |
| lappend newQueue {*}$socketRdQueue($stateArg(socketinfo)) |
| lappend newQueue {*}$InFlightW |
| lappend newQueue {*}$socketWrQueue($stateArg(socketinfo)) |
|
|
|
|
| |
| |
| |
| |
|
|
| catch {close $stateArg(sock)} |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| ReplayCore $newQueue |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::ReplayIfClose {Wstate Rqueue Wqueue} { |
| Log running http::ReplayIfClose for $Wstate $Rqueue $Wqueue |
|
|
| if {$Wstate in $Rqueue || $Wstate in $Wqueue} { |
| Log WARNING duplicate token in http::ReplayIfClose - token $Wstate |
| set Wstate Wready |
| } |
|
|
| |
| set InFlightW {} |
| if {$Wstate ni {Wready peNding}} { |
| lappend InFlightW $Wstate |
| } |
|
|
| set newQueue {} |
| lappend newQueue {*}$Rqueue |
| lappend newQueue {*}$InFlightW |
| lappend newQueue {*}$Wqueue |
|
|
| |
|
|
| ReplayCore $newQueue |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::ReInit {token} { |
| variable $token |
| upvar 0 $token state |
|
|
| if {!( |
| [info exists state(tmpState)] |
| && [info exists state(tmpOpenCmd)] |
| && [info exists state(tmpConnArgs)] |
| ) |
| } { |
| Log FAILED in http::ReInit via ReplayCore - NO tmp vars for $token |
| return 0 |
| } |
|
|
| if {[info exists state(after)]} { |
| after cancel $state(after) |
| unset state(after) |
| } |
|
|
| |
| set tmpState $state(tmpState) |
| set tmpOpenCmd $state(tmpOpenCmd) |
| set tmpConnArgs $state(tmpConnArgs) |
| foreach name [array names state] { |
| if {$name ne "status"} { |
| unset state($name) |
| } |
| } |
|
|
| |
| |
| |
| |
|
|
| dict unset tmpState status |
| array set state $tmpState |
| set state(tmpState) $tmpState |
| set state(tmpOpenCmd) $tmpOpenCmd |
| set state(tmpConnArgs) $tmpConnArgs |
|
|
| return 1 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::ReplayCore {newQueue} { |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| if {[llength $newQueue] == 0} { |
| |
| return |
| } |
|
|
| |
| set newToken [lindex $newQueue 0] |
| set newQueue [lrange $newQueue 1 end] |
|
|
| |
| |
|
|
| set token $newToken |
| variable $token |
| upvar 0 $token state |
|
|
| if {![ReInit $token]} { |
| Log FAILED in http::ReplayCore - NO tmp vars |
| Finish $token {cannot send this request again} |
| return |
| } |
|
|
| set tmpState $state(tmpState) |
| set tmpOpenCmd $state(tmpOpenCmd) |
| set tmpConnArgs $state(tmpConnArgs) |
| unset state(tmpState) |
| unset state(tmpOpenCmd) |
| unset state(tmpConnArgs) |
|
|
| set state(reusing) 0 |
|
|
| if {$state(-timeout) > 0} { |
| set resetCmd [list http::reset $token timeout] |
| set state(after) [after $state(-timeout) $resetCmd] |
| } |
|
|
| set pre [clock milliseconds] |
| |
| |
| |
| if {[catch {eval $tmpOpenCmd} sock]} { |
| |
| Log FAILED - $sock |
| set state(sock) NONE |
| Finish $token $sock |
| return |
| } |
| |
| set delay [expr {[clock milliseconds] - $pre}] |
| if {$delay > 3000} { |
| Log socket delay $delay - token $token |
| } |
| |
| |
| |
| |
| |
|
|
| |
| if {$state(-keepalive)} { |
| set socketMapping($state(socketinfo)) $sock |
|
|
| if {![info exists socketRdState($state(socketinfo))]} { |
| set socketRdState($state(socketinfo)) {} |
| set varName ::http::socketRdState($state(socketinfo)) |
| trace add variable $varName unset ::http::CancelReadPipeline |
| } |
|
|
| if {![info exists socketWrState($state(socketinfo))]} { |
| set socketWrState($state(socketinfo)) {} |
| set varName ::http::socketWrState($state(socketinfo)) |
| trace add variable $varName unset ::http::CancelWritePipeline |
| } |
|
|
| if {$state(-pipeline)} { |
| |
| set socketRdState($state(socketinfo)) $token |
| set socketWrState($state(socketinfo)) $token |
| } else { |
| |
| set socketRdState($state(socketinfo)) $token |
| set socketWrState($state(socketinfo)) $token |
| } |
|
|
| set socketRdQueue($state(socketinfo)) {} |
| set socketWrQueue($state(socketinfo)) $newQueue |
| set socketClosing($state(socketinfo)) 0 |
| set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}} |
| } |
|
|
| |
| |
| foreach tok $newQueue { |
| if {[ReInit $tok]} { |
| set ${tok}(reusing) 1 |
| set ${tok}(sock) $sock |
| } else { |
| set ${tok}(reusing) 1 |
| set ${tok}(sock) NONE |
| Finish $token {cannot send this request again} |
| } |
| } |
|
|
| |
| set state(sock) $sock |
| Log "Using $sock for $state(socketinfo) - token $token" \ |
| [expr {$state(-keepalive)?"keepalive":""}] |
|
|
| |
| |
| fconfigure $sock -translation {auto crlf} -buffersize $state(-blocksize) |
| |
|
|
| |
| fileevent $sock writable [list http::Connect $token {*}$tmpConnArgs] |
| |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| proc http::data {token} { |
| variable $token |
| upvar 0 $token state |
| return $state(body) |
| } |
| proc http::status {token} { |
| if {![info exists $token]} { |
| return "error" |
| } |
| variable $token |
| upvar 0 $token state |
| return $state(status) |
| } |
| proc http::code {token} { |
| variable $token |
| upvar 0 $token state |
| return $state(http) |
| } |
| proc http::ncode {token} { |
| variable $token |
| upvar 0 $token state |
| if {[regexp {[0-9]{3}} $state(http) numeric_code]} { |
| return $numeric_code |
| } else { |
| return $state(http) |
| } |
| } |
| proc http::size {token} { |
| variable $token |
| upvar 0 $token state |
| return $state(currentsize) |
| } |
| proc http::meta {token} { |
| variable $token |
| upvar 0 $token state |
| return $state(meta) |
| } |
| proc http::error {token} { |
| variable $token |
| upvar 0 $token state |
| if {[info exists state(error)]} { |
| return $state(error) |
| } |
| return "" |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::cleanup {token} { |
| variable $token |
| upvar 0 $token state |
| if {[info commands ${token}EventCoroutine] ne {}} { |
| rename ${token}EventCoroutine {} |
| } |
| if {[info exists state(after)]} { |
| after cancel $state(after) |
| unset state(after) |
| } |
| if {[info exists state]} { |
| unset state |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::Connect {token proto phost srvurl} { |
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| set err "due to unexpected EOF" |
| if { |
| [eof $state(sock)] || |
| [set err [fconfigure $state(sock) -error]] ne "" |
| } { |
| Log "WARNING - if testing, pay special attention to this\ |
| case (GJ) which is seldom executed - token $token" |
| if {[info exists state(reusing)] && $state(reusing)} { |
| |
| |
| if {[TestForReplay $token write $err b]} { |
| return |
| } |
|
|
| |
| |
| |
| |
| |
| } |
| Finish $token "connect failed $err" |
| } else { |
| set state(state) connecting |
| fileevent $state(sock) writable {} |
| ::http::Connected $token $proto $phost $srvurl |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::Write {token} { |
| variable http |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| set sock $state(sock) |
|
|
| |
| set done 0 |
| if {[catch { |
| |
|
|
| if {[info exists state(-query)]} { |
| |
| |
| if { $state(queryoffset) + $state(-queryblocksize) |
| >= $state(querylength) |
| } { |
| |
| if { (![catch {fileevent $sock readable} binding]) |
| && ($binding eq [list http::CheckEof $sock]) |
| } { |
| |
| |
| |
| |
| |
| fileevent $sock readable {} |
| } |
| } |
| puts -nonewline $sock \ |
| [string range $state(-query) $state(queryoffset) \ |
| [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]] |
| incr state(queryoffset) $state(-queryblocksize) |
| if {$state(queryoffset) >= $state(querylength)} { |
| set state(queryoffset) $state(querylength) |
| set done 1 |
| } |
| } else { |
| |
|
|
| set outStr [read $state(-querychannel) $state(-queryblocksize)] |
| if {[eof $state(-querychannel)]} { |
| |
| if { (![catch {fileevent $sock readable} binding]) |
| && ($binding eq [list http::CheckEof $sock]) |
| } { |
| |
| |
| |
| |
| |
| fileevent $sock readable {} |
| } |
| } |
| puts -nonewline $sock $outStr |
| incr state(queryoffset) [string length $outStr] |
| if {[eof $state(-querychannel)]} { |
| set done 1 |
| } |
| } |
| } err]} { |
| |
| |
|
|
| set state(posterror) $err |
| set done 1 |
| } |
|
|
| if {$done} { |
| catch {flush $sock} |
| fileevent $sock writable {} |
| Log ^C$tk end sending request - token $token |
| |
|
|
| DoneRequest $token |
| } |
|
|
| |
|
|
| if {[string length $state(-queryprogress)]} { |
| eval $state(-queryprogress) \ |
| [list $token $state(querylength) $state(queryoffset)] |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::Event {sock token} { |
| variable http |
| variable socketMapping |
| variable socketRdState |
| variable socketWrState |
| variable socketRdQueue |
| variable socketWrQueue |
| variable socketClosing |
| variable socketPlayCmd |
|
|
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| while 1 { |
| yield |
| |
|
|
| if {![info exists state]} { |
| Log "Event $sock with invalid token '$token' - remote close?" |
| if {![eof $sock]} { |
| if {[set d [read $sock]] ne ""} { |
| Log "WARNING: additional data left on closed socket\ |
| - token $token" |
| } |
| } |
| Log ^X$tk end of response (token error) - token $token |
| CloseSocket $sock |
| return |
| } |
| if {$state(state) eq "connecting"} { |
| |
| if { $state(reusing) |
| && $state(-pipeline) |
| && ($state(-timeout) > 0) |
| && (![info exists state(after)]) |
| } { |
| set state(after) [after $state(-timeout) \ |
| [list http::reset $token timeout]] |
| } |
|
|
| if {[catch {gets $sock state(http)} nsl]} { |
| Log "WARNING - if testing, pay special attention to this\ |
| case (GK) which is seldom executed - token $token" |
| if {[info exists state(reusing)] && $state(reusing)} { |
| |
| |
|
|
| if {[TestForReplay $token read $nsl c]} { |
| return |
| } |
|
|
| |
| |
| |
| |
| |
| } else { |
| Log ^X$tk end of response (error) - token $token |
| Finish $token $nsl |
| return |
| } |
| } elseif {$nsl >= 0} { |
| |
| set state(state) "header" |
| } elseif { [eof $sock] |
| && [info exists state(reusing)] |
| && $state(reusing) |
| } { |
| |
| |
| |
|
|
| if {[TestForReplay $token read {} d]} { |
| return |
| } |
|
|
| |
| |
| |
| |
| |
| } |
| } elseif {$state(state) eq "header"} { |
| if {[catch {gets $sock line} nhl]} { |
| |
| Log ^X$tk end of response (error) - token $token |
| Finish $token $nhl |
| return |
| } elseif {$nhl == 0} { |
| |
| Log ^E$tk end of response headers - token $token |
| |
| |
| if { ($state(http) == "") |
| || ([regexp {^\S+\s(\d+)} $state(http) {} x] && $x == 100) |
| } { |
| set state(state) "connecting" |
| continue |
| |
| } |
|
|
| if { ([info exists state(connection)]) |
| && ([info exists socketMapping($state(socketinfo))]) |
| && ("keep-alive" in $state(connection)) |
| && ($state(-keepalive)) |
| && (!$state(reusing)) |
| && ($state(-pipeline)) |
| } { |
| |
| |
| |
| |
| set socketWrState($state(socketinfo)) Wready |
| http::NextPipelinedWrite $token |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| if { ([info exists state(connection)]) |
| && ([info exists socketMapping($state(socketinfo))]) |
| && ("close" in $state(connection)) |
| && ($state(-keepalive)) |
| } { |
| |
| |
| |
| |
| if { ($socketRdQueue($state(socketinfo)) ne {}) |
| || ($socketWrQueue($state(socketinfo)) ne {}) |
| || ($socketWrState($state(socketinfo)) ni |
| [list Wready peNding $token]) |
| } { |
| set InFlightW $socketWrState($state(socketinfo)) |
| if {$InFlightW in [list Wready peNding $token]} { |
| set InFlightW Wready |
| } else { |
| set msg "token ${InFlightW} is InFlightW" |
| |
| } |
|
|
| set socketPlayCmd($state(socketinfo)) \ |
| [list ReplayIfClose $InFlightW \ |
| $socketRdQueue($state(socketinfo)) \ |
| $socketWrQueue($state(socketinfo))] |
|
|
| |
| |
| |
| |
| |
| foreach tokenVal $socketRdQueue($state(socketinfo)) { |
| if {[info exists ${tokenVal}(after)]} { |
| after cancel [set ${tokenVal}(after)] |
| unset ${tokenVal}(after) |
| } |
| } |
|
|
| } else { |
| set socketPlayCmd($state(socketinfo)) \ |
| {ReplayIfClose Wready {} {}} |
| } |
|
|
| |
| set socketClosing($state(socketinfo)) 1 |
| } |
|
|
| set state(state) body |
|
|
| |
| |
| |
| |
| |
| |
| if { [info exists state(connection)] |
| && ("close" ni $state(connection)) |
| && ("keep-alive" ni $state(connection)) |
| } { |
| lappend state(connection) "keep-alive" |
| } |
|
|
| |
| if {$state(-validate)} { |
| Log ^F$tk end of response for HEAD request - token $token |
| set state(state) complete |
| Eot $token |
| return |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| if { (!( [info exists state(connection)] |
| && ("close" in $state(connection)) |
| ) |
| ) |
| && (![info exists state(transfer)]) |
| && ($state(totalsize) == 0) |
| } { |
| set msg {body size is 0 and no events likely - complete} |
| Log "$msg - token $token" |
| set msg {(length unknown, set to 0)} |
| Log ^F$tk end of response body {*}$msg - token $token |
| set state(state) complete |
| Eot $token |
| return |
| } |
|
|
| |
| lassign [fconfigure $sock -translation] trRead trWrite |
| fconfigure $sock -translation [list binary $trWrite] |
|
|
| if { |
| $state(-binary) || [IsBinaryContentType $state(type)] |
| } { |
| |
| set state(binary) 1 |
| } |
| if {[info exists state(-channel)]} { |
| if {$state(binary) || [llength [ContentEncoding $token]]} { |
| fconfigure $state(-channel) -translation binary |
| } |
| if {![info exists state(-handler)]} { |
| |
| fileevent $sock readable {} |
| rename ${token}EventCoroutine {} |
| CopyStart $sock $token |
| return |
| } |
| } |
| } elseif {$nhl > 0} { |
| |
| |
| if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} { |
| switch -- [string tolower $key] { |
| content-type { |
| set state(type) [string trim [string tolower $value]] |
| |
| if {[regexp -nocase \ |
| {charset\s*=\s*\"((?:[^""]|\\\")*)\"} \ |
| $state(type) -> cs]} { |
| set state(charset) [string map {{\"} \"} $cs] |
| } else { |
| regexp -nocase {charset\s*=\s*(\S+?);?} \ |
| $state(type) -> state(charset) |
| } |
| } |
| content-length { |
| set state(totalsize) [string trim $value] |
| } |
| content-encoding { |
| set state(coding) [string trim $value] |
| } |
| transfer-encoding { |
| set state(transfer) \ |
| [string trim [string tolower $value]] |
| } |
| proxy-connection - |
| connection { |
| # RFC 7230 Section 6.1 states that a comma-separated |
| # list is an acceptable value. |
| foreach el [SplitCommaSeparatedFieldValue $value] { |
| lappend state(connection) [string tolower $el] |
| } |
| } |
| upgrade { |
| set state(upgrade) [string trim $value] |
| } |
| } |
| lappend state(meta) $key [string trim $value] |
| } |
| } |
| } else { |
| # Now reading body |
| ##Log body - token $token |
| if {[catch { |
| if {[info exists state(-handler)]} { |
| set n [eval $state(-handler) [list $sock $token]] |
| ##Log handler $n - token $token |
| # N.B. the protocol has been set to 1.0 because the -handler |
| # logic is not expected to handle chunked encoding. |
| # FIXME Allow -handler with 1.1 on dechunked stacked chan. |
| if {$state(totalsize) == 0} { |
| # We know the transfer is complete only when the server |
| # closes the connection - i.e. eof is not an error. |
| set state(state) complete |
| } |
| if {![string is integer -strict $n]} { |
| if 1 { |
| # Do not tolerate bad -handler - fail with error |
| # status. |
| set msg {the -handler command for http::geturl must\ |
| return an integer (the number of bytes\ |
| read)} |
| Log ^X$tk end of response (handler error) -\ |
| token $token |
| Eot $token $msg |
| } else { |
| # Tolerate the bad -handler, and continue. The |
| # penalty: |
| # (a) Because the handler returns nonsense, we know |
| # the transfer is complete only when the server |
| # closes the connection - i.e. eof is not an |
| # error. |
| # (b) http::size will not be accurate. |
| # (c) The transaction is already downgraded to 1.0 |
| # to avoid chunked transfer encoding. It MUST |
| # also be forced to "Connection: close" or the |
| # HTTP/1.0 equivalent; or it MUST fail (as |
| # above) if the server sends |
| # "Connection: keep-alive" or the HTTP/1.0 |
| # equivalent. |
| set n 0 |
| set state(state) complete |
| } |
| } |
| } elseif {[info exists state(transfer_final)]} { |
| # This code forgives EOF in place of the final CRLF. |
| set line [getTextLine $sock] |
| set n [string length $line] |
| set state(state) complete |
| if {$n > 0} { |
| # - HTTP trailers (late response headers) are permitted |
| # by Chunked Transfer-Encoding, and can be safely |
| # ignored. |
| # - Do not count these bytes in the total received for |
| # the response body. |
| Log "trailer of $n bytes after final chunk -\ |
| token $token" |
| append state(transfer_final) $line |
| set n 0 |
| } else { |
| Log ^F$tk end of response body (chunked) - token $token |
| Log "final chunk part - token $token" |
| Eot $token |
| } |
| } elseif { [info exists state(transfer)] |
| && ($state(transfer) eq "chunked") |
| } { |
| ##Log chunked - token $token |
| set size 0 |
| set hexLenChunk [getTextLine $sock] |
| #set ntl [string length $hexLenChunk] |
| if {[string trim $hexLenChunk] ne ""} { |
| scan $hexLenChunk %x size |
| if {$size != 0} { |
| ##Log chunk-measure $size - token $token |
| set chunk [BlockingRead $sock $size] |
| set n [string length $chunk] |
| if {$n >= 0} { |
| append state(body) $chunk |
| incr state(log_size) [string length $chunk] |
| ##Log chunk $n cumul $state(log_size) -\ |
| token $token |
| } |
| if {$size != [string length $chunk]} { |
| Log "WARNING: mis-sized chunk:\ |
| was [string length $chunk], should be\ |
| $size - token $token" |
| set n 0 |
| set state(connection) close |
| Log ^X$tk end of response (chunk error) \ |
| - token $token |
| set msg {error in chunked encoding - fetch\ |
| terminated} |
| Eot $token $msg |
| } |
| # CRLF that follows chunk. |
| # If eof, this is handled at the end of this proc. |
| getTextLine $sock |
| } else { |
| set n 0 |
| set state(transfer_final) {} |
| } |
| } else { |
| # Line expected to hold chunk length is empty, or eof. |
| ##Log bad-chunk-measure - token $token |
| set n 0 |
| set state(connection) close |
| Log ^X$tk end of response (chunk error) - token $token |
| Eot $token {error in chunked encoding -\ |
| fetch terminated} |
| } |
| } else { |
| ##Log unchunked - token $token |
| if {$state(totalsize) == 0} { |
| # We know the transfer is complete only when the server |
| # closes the connection. |
| set state(state) complete |
| set reqSize $state(-blocksize) |
| } else { |
| # Ask for the whole of the unserved response-body. |
| # This works around a problem with a tls::socket - for |
| # https in keep-alive mode, and a request for |
| # $state(-blocksize) bytes, the last part of the |
| # resource does not get read until the server times out. |
| set reqSize [expr { $state(totalsize) |
| - $state(currentsize)}] |
| |
| # The workaround fails if reqSize is |
| # capped at $state(-blocksize). |
| # set reqSize [expr {min($reqSize, $state(-blocksize))}] |
| } |
| set c $state(currentsize) |
| set t $state(totalsize) |
| ##Log non-chunk currentsize $c of totalsize $t -\ |
| token $token |
| set block [read $sock $reqSize] |
| set n [string length $block] |
| if {$n >= 0} { |
| append state(body) $block |
| ##Log non-chunk [string length $state(body)] -\ |
| token $token |
| } |
| } |
| # This calculation uses n from the -handler, chunked, or |
| # unchunked case as appropriate. |
| if {[info exists state]} { |
| if {$n >= 0} { |
| incr state(currentsize) $n |
| set c $state(currentsize) |
| set t $state(totalsize) |
| ##Log another $n currentsize $c totalsize $t -\ |
| token $token |
| } |
| # If Content-Length - check for end of data. |
| if { |
| ($state(totalsize) > 0) |
| && ($state(currentsize) >= $state(totalsize)) |
| } { |
| Log ^F$tk end of response body (unchunked) -\ |
| token $token |
| set state(state) complete |
| Eot $token |
| } |
| } |
| } err]} { |
| Log ^X$tk end of response (error ${err}) - token $token |
| Finish $token $err |
| return |
| } else { |
| if {[info exists state(-progress)]} { |
| eval $state(-progress) \ |
| [list $token $state(totalsize) $state(currentsize)] |
| } |
| } |
| } |
| |
| # catch as an Eot above may have closed the socket already |
| # $state(state) may be connecting, header, body, or complete |
| if {![set cc [catch {eof $sock} eof]] && $eof} { |
| ##Log eof - token $token |
| if {[info exists $token]} { |
| set state(connection) close |
| if {$state(state) eq "complete"} { |
| # This includes all cases in which the transaction |
| # can be completed by eof. |
| # The value "complete" is set only in http::Event, and it is |
| # used only in the test above. |
| Log ^F$tk end of response body (unchunked, eof) -\ |
| token $token |
| Eot $token |
| } else { |
| # Premature eof. |
| Log ^X$tk end of response (unexpected eof) - token $token |
| Eot $token eof |
| } |
| } else { |
| # open connection closed on a token that has been cleaned up. |
| Log ^X$tk end of response (token error) - token $token |
| CloseSocket $sock |
| } |
| } elseif {$cc} { |
| return |
| } |
| } |
| } |
| |
| # http::TestForReplay |
| # |
| # Command called if eof is discovered when a socket is first used for a |
| # new transaction. Typically this occurs if a persistent socket is used |
| # after a period of idleness and the server has half-closed the socket. |
| # |
| # token - the connection token returned by http::geturl |
| # doing - "read" or "write" |
| # err - error message, if any |
| # caller - code to identify the caller - used only in logging |
| # |
| # Return Value: boolean, true iff the command calls http::ReplayIfDead. |
| |
| proc http::TestForReplay {token doing err caller} { |
| variable http |
| variable $token |
| upvar 0 $token state |
| set tk [namespace tail $token] |
| if {$doing eq "read"} { |
| set code Q |
| set action response |
| set ing reading |
| } else { |
| set code P |
| set action request |
| set ing writing |
| } |
| |
| if {$err eq {}} { |
| set err "detect eof when $ing (server timed out?)" |
| } |
| |
| if {$state(method) eq "POST" && !$http(-repost)} { |
| # No Replay. |
| # The present transaction will end when Finish is called. |
| # That call to Finish will abort any other transactions |
| # currently in the write queue. |
| # For calls from http::Event this occurs when execution |
| # reaches the code block at the end of that proc. |
| set msg {no retry for POST with http::config -repost 0} |
| Log reusing socket failed "($caller)" - $msg - token $token |
| Log error - $err - token $token |
| Log ^X$tk end of $action (error) - token $token |
| return 0 |
| } else { |
| # Replay. |
| set msg {try a new socket} |
| Log reusing socket failed "($caller)" - $msg - token $token |
| Log error - $err - token $token |
| Log ^$code$tk Any unfinished (incl this one) failed - token $token |
| ReplayIfDead $token $doing |
| return 1 |
| } |
| } |
| |
| # http::IsBinaryContentType -- |
| # |
| # Determine if the content-type means that we should definitely transfer |
| # the data as binary. [Bug 838e99a76d] |
| # |
| # Arguments |
| # type The content-type of the data. |
| # |
| # Results: |
| # Boolean, true if we definitely should be binary. |
| |
| proc http::IsBinaryContentType {type} { |
| lassign [split [string tolower $type] "/;"] major minor |
| if {$major eq "text"} { |
| return false |
| } |
| # There's a bunch of XML-as-application-format things about. See RFC 3023 |
| # and so on. |
| if {$major eq "application"} { |
| set minor [string trimright $minor] |
| if {$minor in {"json" "xml" "xml-external-parsed-entity" "xml-dtd"}} { |
| return false |
| } |
| } |
| # Not just application/foobar+xml but also image/svg+xml, so let us not |
| # restrict things for now... |
| if {[string match "*+xml" $minor]} { |
| return false |
| } |
| return true |
| } |
| |
| # http::getTextLine -- |
| # |
| # Get one line with the stream in crlf mode. |
| # Used if Transfer-Encoding is chunked. |
| # Empty line is not distinguished from eof. The caller must |
| # be able to handle this. |
| # |
| # Arguments |
| # sock The socket receiving input. |
| # |
| # Results: |
| # The line of text, without trailing newline |
| |
| proc http::getTextLine {sock} { |
| set tr [fconfigure $sock -translation] |
| lassign $tr trRead trWrite |
| fconfigure $sock -translation [list crlf $trWrite] |
| set r [BlockingGets $sock] |
| fconfigure $sock -translation $tr |
| return $r |
| } |
| |
| # http::BlockingRead |
| # |
| # Replacement for a blocking read. |
| # The caller must be a coroutine. |
| |
| proc http::BlockingRead {sock size} { |
| if {$size < 1} { |
| return |
| } |
| set result {} |
| while 1 { |
| set need [expr {$size - [string length $result]}] |
| set block [read $sock $need] |
| set eof [eof $sock] |
| append result $block |
| if {[string length $result] >= $size || $eof} { |
| return $result |
| } else { |
| yield |
| } |
| } |
| } |
| |
| # http::BlockingGets |
| # |
| # Replacement for a blocking gets. |
| # The caller must be a coroutine. |
| # Empty line is not distinguished from eof. The caller must |
| # be able to handle this. |
| |
| proc http::BlockingGets {sock} { |
| while 1 { |
| set count [gets $sock line] |
| set eof [eof $sock] |
| if {$count >= 0 || $eof} { |
| return $line |
| } else { |
| yield |
| } |
| } |
| } |
| |
| # http::CopyStart |
| # |
| # Error handling wrapper around fcopy |
| # |
| # Arguments |
| # sock The socket to copy from |
| # token The token returned from http::geturl |
| # |
| # Side Effects |
| # This closes the connection upon error |
| |
| proc http::CopyStart {sock token {initial 1}} { |
| upvar #0 $token state |
| if {[info exists state(transfer)] && $state(transfer) eq "chunked"} { |
| foreach coding [ContentEncoding $token] { |
| lappend state(zlib) [zlib stream $coding] |
| } |
| make-transformation-chunked $sock [namespace code [list CopyChunk $token]] |
| } else { |
| if {$initial} { |
| foreach coding [ContentEncoding $token] { |
| zlib push $coding $sock |
| } |
| } |
| if {[catch { |
| # FIXME Keep-Alive on https tls::socket with unchunked transfer |
| # hangs until the server times out. A workaround is possible, as for |
| # the case without -channel, but it does not use the neat "fcopy" |
| # solution. |
| fcopy $sock $state(-channel) -size $state(-blocksize) -command \ |
| [list http::CopyDone $token] |
| } err]} { |
| Finish $token $err |
| } |
| } |
| } |
| |
| proc http::CopyChunk {token chunk} { |
| upvar 0 $token state |
| if {[set count [string length $chunk]]} { |
| incr state(currentsize) $count |
| if {[info exists state(zlib)]} { |
| foreach stream $state(zlib) { |
| set chunk [$stream add $chunk] |
| } |
| } |
| puts -nonewline $state(-channel) $chunk |
| if {[info exists state(-progress)]} { |
| eval [linsert $state(-progress) end \ |
| $token $state(totalsize) $state(currentsize)] |
| } |
| } else { |
| Log "CopyChunk Finish - token $token" |
| if {[info exists state(zlib)]} { |
| set excess "" |
| foreach stream $state(zlib) { |
| catch {set excess [$stream add -finalize $excess]} |
| } |
| puts -nonewline $state(-channel) $excess |
| foreach stream $state(zlib) { $stream close } |
| unset state(zlib) |
| } |
| Eot $token ;# FIX ME: pipelining. |
| } |
| } |
| |
| # http::CopyDone |
| # |
| # fcopy completion callback |
| # |
| # Arguments |
| # token The token returned from http::geturl |
| # count The amount transferred |
| # |
| # Side Effects |
| # Invokes callbacks |
| |
| proc http::CopyDone {token count {error {}}} { |
| variable $token |
| upvar 0 $token state |
| set sock $state(sock) |
| incr state(currentsize) $count |
| if {[info exists state(-progress)]} { |
| eval $state(-progress) \ |
| [list $token $state(totalsize) $state(currentsize)] |
| } |
| # At this point the token may have been reset. |
| if {[string length $error]} { |
| Finish $token $error |
| } elseif {[catch {eof $sock} iseof] || $iseof} { |
| Eot $token |
| } else { |
| CopyStart $sock $token 0 |
| } |
| } |
| |
| # http::Eot |
| # |
| # Called when either: |
| # a. An eof condition is detected on the socket. |
| # b. The client decides that the response is complete. |
| # c. The client detects an inconsistency and aborts the transaction. |
| # |
| # Does: |
| # 1. Set state(status) |
| # 2. Reverse any Content-Encoding |
| # 3. Convert charset encoding and line ends if necessary |
| # 4. Call http::Finish |
| # |
| # Arguments |
| # token The token returned from http::geturl |
| # force (previously) optional, has no effect |
| # reason - "eof" means premature EOF (not EOF as the natural end of |
| # the response) |
| # - "" means completion of response, with or without EOF |
| # - anything else describes an error condition other than |
| # premature EOF. |
| # |
| # Side Effects |
| # Clean up the socket |
| |
| proc http::Eot {token {reason {}}} { |
| variable $token |
| upvar 0 $token state |
| if {$reason eq "eof"} { |
| # Premature eof. |
| set state(status) eof |
| set reason {} |
| } elseif {$reason ne ""} { |
| # Abort the transaction. |
| set state(status) $reason |
| } else { |
| # The response is complete. |
| set state(status) ok |
| } |
| |
| if {[string length $state(body)] > 0} { |
| if {[catch { |
| foreach coding [ContentEncoding $token] { |
| set state(body) [zlib $coding $state(body)] |
| } |
| } err]} { |
| Log "error doing decompression for token $token: $err" |
| Finish $token $err |
| return |
| } |
| |
| if {!$state(binary)} { |
| # If we are getting text, set the incoming channel's encoding |
| # correctly. iso8859-1 is the RFC default, but this could be any |
| # IANA charset. However, we only know how to convert what we have |
| # encodings for. |
| |
| set enc [CharsetToEncoding $state(charset)] |
| if {$enc ne "binary"} { |
| set state(body) [encoding convertfrom $enc $state(body)] |
| } |
| |
| # Translate text line endings. |
| set state(body) [string map {\r\n \n \r \n} $state(body)] |
| } |
| } |
| Finish $token $reason |
| } |
| |
| # http::wait -- |
| # |
| # See documentation for details. |
| # |
| # Arguments: |
| # token Connection token. |
| # |
| # Results: |
| # The status after the wait. |
| |
| proc http::wait {token} { |
| variable $token |
| upvar 0 $token state |
| |
| if {![info exists state(status)] || $state(status) eq ""} { |
| # We must wait on the original variable name, not the upvar alias |
| vwait ${token}(status) |
| } |
| |
| return [status $token] |
| } |
| |
| # http::formatQuery -- |
| # |
| # See documentation for details. Call http::formatQuery with an even |
| # number of arguments, where the first is a name, the second is a value, |
| # the third is another name, and so on. |
| # |
| # Arguments: |
| # args A list of name-value pairs. |
| # |
| # Results: |
| # TODO |
| |
| proc http::formatQuery {args} { |
| if {[llength $args] % 2} { |
| return \ |
| -code error \ |
| -errorcode [list HTTP BADARGCNT $args] \ |
| {Incorrect number of arguments, must be an even number.} |
| } |
| set result "" |
| set sep "" |
| foreach i $args { |
| append result $sep [mapReply $i] |
| if {$sep eq "="} { |
| set sep & |
| } else { |
| set sep = |
| } |
| } |
| return $result |
| } |
| |
| # http::mapReply -- |
| # |
| # Do x-www-urlencoded character mapping |
| # |
| # Arguments: |
| # string The string the needs to be encoded |
| # |
| # Results: |
| # The encoded string |
| |
| proc http::mapReply {string} { |
| variable http |
| variable formMap |
| |
| # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use |
| # a pre-computed map and [string map] to do the conversion (much faster |
| # than [regsub]/[subst]). [Bug 1020491] |
| |
| if {$http(-urlencoding) ne ""} { |
| set string [encoding convertto $http(-urlencoding) $string] |
| return [string map $formMap $string] |
| } |
| set converted [string map $formMap $string] |
| if {[string match "*\[\u0100-\uffff\]*" $converted]} { |
| regexp "\[\u0100-\uffff\]" $converted badChar |
| # Return this error message for maximum compatibility... :^/ |
| return -code error \ |
| "can't read \"formMap($badChar)\": no such element in array" |
| } |
| return $converted |
| } |
| interp alias {} http::quoteString {} http::mapReply |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| proc http::ProxyRequired {host} { |
| variable http |
| if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} { |
| if { |
| ![info exists http(-proxyport)] || |
| ![string length $http(-proxyport)] |
| } { |
| set http(-proxyport) 8080 |
| } |
| return [list $http(-proxyhost) $http(-proxyport)] |
| } |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| proc http::CharsetToEncoding {charset} { |
| variable encodings |
|
|
| set charset [string tolower $charset] |
| if {[regexp {iso-?8859-([0-9]+)} $charset -> num]} { |
| set encoding "iso8859-$num" |
| } elseif {[regexp {iso-?2022-(jp|kr)} $charset -> ext]} { |
| set encoding "iso2022-$ext" |
| } elseif {[regexp {shift[-_]?jis} $charset]} { |
| set encoding "shiftjis" |
| } elseif {[regexp {(?:windows|cp)-?([0-9]+)} $charset -> num]} { |
| set encoding "cp$num" |
| } elseif {$charset eq "us-ascii"} { |
| set encoding "ascii" |
| } elseif {[regexp {(?:iso-?)?lat(?:in)?-?([0-9]+)} $charset -> num]} { |
| switch -- $num { |
| 5 {set encoding "iso8859-9"} |
| 1 - 2 - 3 { |
| set encoding "iso8859-$num" |
| } |
| default { |
| set encoding "binary" |
| } |
| } |
| } else { |
| |
| set encoding $charset |
| } |
| set idx [lsearch -exact $encodings $encoding] |
| if {$idx >= 0} { |
| return $encoding |
| } else { |
| return "binary" |
| } |
| } |
|
|
| |
| proc http::ContentEncoding {token} { |
| upvar 0 $token state |
| set r {} |
| if {[info exists state(coding)]} { |
| foreach coding [split $state(coding) ,] { |
| switch -exact -- $coding { |
| deflate { lappend r inflate } |
| gzip - x-gzip { lappend r gunzip } |
| compress - x-compress { lappend r decompress } |
| identity {} |
| br { |
| return -code error\ |
| "content-encoding \"br\" not implemented" |
| } |
| default { |
| Log "unknown content-encoding \"$coding\" ignored" |
| } |
| } |
| } |
| } |
| return $r |
| } |
|
|
| proc http::ReceiveChunked {chan command} { |
| set data "" |
| set size -1 |
| yield |
| while {1} { |
| chan configure $chan -translation {crlf binary} |
| while {[gets $chan line] < 1} { yield } |
| chan configure $chan -translation {binary binary} |
| if {[scan $line %x size] != 1} { |
| return -code error "invalid size: \"$line\"" |
| } |
| set chunk "" |
| while {$size && ![chan eof $chan]} { |
| set part [chan read $chan $size] |
| incr size -[string length $part] |
| append chunk $part |
| } |
| if {[catch { |
| uplevel #0 [linsert $command end $chunk] |
| }]} { |
| http::Log "Error in callback: $::errorInfo" |
| } |
| if {[string length $chunk] == 0} { |
| |
| catch {chan event $chan readable {}} |
| return |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| proc http::SplitCommaSeparatedFieldValue {fieldValue} { |
| set r {} |
| foreach el [split $fieldValue ,] { |
| lappend r [string trim $el] |
| } |
| return $r |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| proc http::GetFieldValue {headers fieldName} { |
| set r {} |
| foreach {field value} $headers { |
| if {[string equal -nocase $fieldName $field]} { |
| if {$r eq {}} { |
| set r $value |
| } else { |
| append r ", $value" |
| } |
| } |
| } |
| return $r |
| } |
|
|
| proc http::make-transformation-chunked {chan command} { |
| coroutine [namespace current]::dechunk$chan ::http::ReceiveChunked $chan $command |
| chan event $chan readable [namespace current]::dechunk$chan |
| } |
|
|
| |
| |
| |
|
|