http.tcl 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. # http.tcl --
  2. #
  3. # Client-side HTTP for GET, POST, and HEAD commands. These routines can
  4. # be used in untrusted code that uses the Safesock security policy.
  5. # These procedures use a callback interface to avoid using vwait, which
  6. # is not defined in the safe base.
  7. #
  8. # See the file "license.terms" for information on usage and redistribution of
  9. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  10. package require Tcl 8.6
  11. # Keep this in sync with pkgIndex.tcl and with the install directories in
  12. # Makefiles
  13. package provide http 2.8.5
  14. namespace eval http {
  15. # Allow resourcing to not clobber existing data
  16. variable http
  17. if {![info exists http]} {
  18. array set http {
  19. -accept */*
  20. -proxyhost {}
  21. -proxyport {}
  22. -proxyfilter http::ProxyRequired
  23. -urlencoding utf-8
  24. }
  25. # We need a useragent string of this style or various servers will refuse to
  26. # send us compressed content even when we ask for it. This follows the
  27. # de-facto layout of user-agent strings in current browsers.
  28. set http(-useragent) "Mozilla/5.0\
  29. ([string totitle $::tcl_platform(platform)]; U;\
  30. $::tcl_platform(os) $::tcl_platform(osVersion))\
  31. http/[package provide http] Tcl/[package provide Tcl]"
  32. }
  33. proc init {} {
  34. # Set up the map for quoting chars. RFC3986 Section 2.3 say percent
  35. # encode all except: "... percent-encoded octets in the ranges of
  36. # ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period
  37. # (%2E), underscore (%5F), or tilde (%7E) should not be created by URI
  38. # producers ..."
  39. for {set i 0} {$i <= 256} {incr i} {
  40. set c [format %c $i]
  41. if {![string match {[-._~a-zA-Z0-9]} $c]} {
  42. set map($c) %[format %.2X $i]
  43. }
  44. }
  45. # These are handled specially
  46. set map(\n) %0D%0A
  47. variable formMap [array get map]
  48. # Create a map for HTTP/1.1 open sockets
  49. variable socketmap
  50. if {[info exists socketmap]} {
  51. # Close but don't remove open sockets on re-init
  52. foreach {url sock} [array get socketmap] {
  53. catch {close $sock}
  54. }
  55. }
  56. array set socketmap {}
  57. }
  58. init
  59. variable urlTypes
  60. if {![info exists urlTypes]} {
  61. set urlTypes(http) [list 80 ::socket]
  62. }
  63. variable encodings [string tolower [encoding names]]
  64. # This can be changed, but iso8859-1 is the RFC standard.
  65. variable defaultCharset
  66. if {![info exists defaultCharset]} {
  67. set defaultCharset "iso8859-1"
  68. }
  69. # Force RFC 3986 strictness in geturl url verification?
  70. variable strict
  71. if {![info exists strict]} {
  72. set strict 1
  73. }
  74. # Let user control default keepalive for compatibility
  75. variable defaultKeepalive
  76. if {![info exists defaultKeepalive]} {
  77. set defaultKeepalive 0
  78. }
  79. namespace export geturl config reset wait formatQuery register unregister
  80. # Useful, but not exported: data size status code
  81. }
  82. # http::Log --
  83. #
  84. # Debugging output -- define this to observe HTTP/1.1 socket usage.
  85. # Should echo any args received.
  86. #
  87. # Arguments:
  88. # msg Message to output
  89. #
  90. if {[info command http::Log] eq {}} {proc http::Log {args} {}}
  91. # http::register --
  92. #
  93. # See documentation for details.
  94. #
  95. # Arguments:
  96. # proto URL protocol prefix, e.g. https
  97. # port Default port for protocol
  98. # command Command to use to create socket
  99. # Results:
  100. # list of port and command that was registered.
  101. proc http::register {proto port command} {
  102. variable urlTypes
  103. set urlTypes($proto) [list $port $command]
  104. }
  105. # http::unregister --
  106. #
  107. # Unregisters URL protocol handler
  108. #
  109. # Arguments:
  110. # proto URL protocol prefix, e.g. https
  111. # Results:
  112. # list of port and command that was unregistered.
  113. proc http::unregister {proto} {
  114. variable urlTypes
  115. if {![info exists urlTypes($proto)]} {
  116. return -code error "unsupported url type \"$proto\""
  117. }
  118. set old $urlTypes($proto)
  119. unset urlTypes($proto)
  120. return $old
  121. }
  122. # http::config --
  123. #
  124. # See documentation for details.
  125. #
  126. # Arguments:
  127. # args Options parsed by the procedure.
  128. # Results:
  129. # TODO
  130. proc http::config {args} {
  131. variable http
  132. set options [lsort [array names http -*]]
  133. set usage [join $options ", "]
  134. if {[llength $args] == 0} {
  135. set result {}
  136. foreach name $options {
  137. lappend result $name $http($name)
  138. }
  139. return $result
  140. }
  141. set options [string map {- ""} $options]
  142. set pat ^-(?:[join $options |])$
  143. if {[llength $args] == 1} {
  144. set flag [lindex $args 0]
  145. if {![regexp -- $pat $flag]} {
  146. return -code error "Unknown option $flag, must be: $usage"
  147. }
  148. return $http($flag)
  149. } else {
  150. foreach {flag value} $args {
  151. if {![regexp -- $pat $flag]} {
  152. return -code error "Unknown option $flag, must be: $usage"
  153. }
  154. set http($flag) $value
  155. }
  156. }
  157. }
  158. # http::Finish --
  159. #
  160. # Clean up the socket and eval close time callbacks
  161. #
  162. # Arguments:
  163. # token Connection token.
  164. # errormsg (optional) If set, forces status to error.
  165. # skipCB (optional) If set, don't call the -command callback. This
  166. # is useful when geturl wants to throw an exception instead
  167. # of calling the callback. That way, the same error isn't
  168. # reported to two places.
  169. #
  170. # Side Effects:
  171. # Closes the socket
  172. proc http::Finish {token {errormsg ""} {skipCB 0}} {
  173. variable $token
  174. upvar 0 $token state
  175. global errorInfo errorCode
  176. if {$errormsg ne ""} {
  177. set state(error) [list $errormsg $errorInfo $errorCode]
  178. set state(status) "error"
  179. }
  180. if {
  181. ($state(status) eq "timeout") || ($state(status) eq "error") ||
  182. ([info exists state(connection)] && ($state(connection) eq "close"))
  183. } {
  184. CloseSocket $state(sock) $token
  185. }
  186. if {[info exists state(after)]} {
  187. after cancel $state(after)
  188. }
  189. if {[info exists state(-command)] && !$skipCB
  190. && ![info exists state(done-command-cb)]} {
  191. set state(done-command-cb) yes
  192. if {[catch {eval $state(-command) {$token}} err] && $errormsg eq ""} {
  193. set state(error) [list $err $errorInfo $errorCode]
  194. set state(status) error
  195. }
  196. }
  197. }
  198. # http::CloseSocket -
  199. #
  200. # Close a socket and remove it from the persistent sockets table. If
  201. # possible an http token is included here but when we are called from a
  202. # fileevent on remote closure we need to find the correct entry - hence
  203. # the second section.
  204. proc ::http::CloseSocket {s {token {}}} {
  205. variable socketmap
  206. catch {fileevent $s readable {}}
  207. set conn_id {}
  208. if {$token ne ""} {
  209. variable $token
  210. upvar 0 $token state
  211. if {[info exists state(socketinfo)]} {
  212. set conn_id $state(socketinfo)
  213. }
  214. } else {
  215. set map [array get socketmap]
  216. set ndx [lsearch -exact $map $s]
  217. if {$ndx != -1} {
  218. incr ndx -1
  219. set conn_id [lindex $map $ndx]
  220. }
  221. }
  222. if {$conn_id eq {} || ![info exists socketmap($conn_id)]} {
  223. Log "Closing socket $s (no connection info)"
  224. if {[catch {close $s} err]} {
  225. Log "Error: $err"
  226. }
  227. } else {
  228. if {[info exists socketmap($conn_id)]} {
  229. Log "Closing connection $conn_id (sock $socketmap($conn_id))"
  230. if {[catch {close $socketmap($conn_id)} err]} {
  231. Log "Error: $err"
  232. }
  233. unset socketmap($conn_id)
  234. } else {
  235. Log "Cannot close connection $conn_id - no socket in socket map"
  236. }
  237. }
  238. }
  239. # http::reset --
  240. #
  241. # See documentation for details.
  242. #
  243. # Arguments:
  244. # token Connection token.
  245. # why Status info.
  246. #
  247. # Side Effects:
  248. # See Finish
  249. proc http::reset {token {why reset}} {
  250. variable $token
  251. upvar 0 $token state
  252. set state(status) $why
  253. catch {fileevent $state(sock) readable {}}
  254. catch {fileevent $state(sock) writable {}}
  255. Finish $token
  256. if {[info exists state(error)]} {
  257. set errorlist $state(error)
  258. unset state
  259. eval ::error $errorlist
  260. }
  261. }
  262. # http::geturl --
  263. #
  264. # Establishes a connection to a remote url via http.
  265. #
  266. # Arguments:
  267. # url The http URL to goget.
  268. # args Option value pairs. Valid options include:
  269. # -blocksize, -validate, -headers, -timeout
  270. # Results:
  271. # Returns a token for this connection. This token is the name of an
  272. # array that the caller should unset to garbage collect the state.
  273. proc http::geturl {url args} {
  274. variable http
  275. variable urlTypes
  276. variable defaultCharset
  277. variable defaultKeepalive
  278. variable strict
  279. # Initialize the state variable, an array. We'll return the name of this
  280. # array as the token for the transaction.
  281. if {![info exists http(uid)]} {
  282. set http(uid) 0
  283. }
  284. set token [namespace current]::[incr http(uid)]
  285. variable $token
  286. upvar 0 $token state
  287. reset $token
  288. # Process command options.
  289. array set state {
  290. -binary false
  291. -blocksize 8192
  292. -queryblocksize 8192
  293. -validate 0
  294. -headers {}
  295. -timeout 0
  296. -type application/x-www-form-urlencoded
  297. -queryprogress {}
  298. -protocol 1.1
  299. binary 0
  300. state connecting
  301. meta {}
  302. coding {}
  303. currentsize 0
  304. totalsize 0
  305. querylength 0
  306. queryoffset 0
  307. type text/html
  308. body {}
  309. status ""
  310. http ""
  311. connection close
  312. }
  313. set state(-keepalive) $defaultKeepalive
  314. set state(-strict) $strict
  315. # These flags have their types verified [Bug 811170]
  316. array set type {
  317. -binary boolean
  318. -blocksize integer
  319. -queryblocksize integer
  320. -strict boolean
  321. -timeout integer
  322. -validate boolean
  323. }
  324. set state(charset) $defaultCharset
  325. set options {
  326. -binary -blocksize -channel -command -handler -headers -keepalive
  327. -method -myaddr -progress -protocol -query -queryblocksize
  328. -querychannel -queryprogress -strict -timeout -type -validate
  329. }
  330. set usage [join [lsort $options] ", "]
  331. set options [string map {- ""} $options]
  332. set pat ^-(?:[join $options |])$
  333. foreach {flag value} $args {
  334. if {[regexp -- $pat $flag]} {
  335. # Validate numbers
  336. if {
  337. [info exists type($flag)] &&
  338. ![string is $type($flag) -strict $value]
  339. } {
  340. unset $token
  341. return -code error \
  342. "Bad value for $flag ($value), must be $type($flag)"
  343. }
  344. set state($flag) $value
  345. } else {
  346. unset $token
  347. return -code error "Unknown option $flag, can be: $usage"
  348. }
  349. }
  350. # Make sure -query and -querychannel aren't both specified
  351. set isQueryChannel [info exists state(-querychannel)]
  352. set isQuery [info exists state(-query)]
  353. if {$isQuery && $isQueryChannel} {
  354. unset $token
  355. return -code error "Can't combine -query and -querychannel options!"
  356. }
  357. # Validate URL, determine the server host and port, and check proxy case
  358. # Recognize user:pass@host URLs also, although we do not do anything with
  359. # that info yet.
  360. # URLs have basically four parts.
  361. # First, before the colon, is the protocol scheme (e.g. http)
  362. # Second, for HTTP-like protocols, is the authority
  363. # The authority is preceded by // and lasts up to (but not including)
  364. # the following / and it identifies up to four parts, of which only one,
  365. # the host, is required (if an authority is present at all). All other
  366. # parts of the authority (user name, password, port number) are optional.
  367. # Third is the resource name, which is split into two parts at a ?
  368. # The first part (from the single "/" up to "?") is the path, and the
  369. # second part (from that "?" up to "#") is the query. *HOWEVER*, we do
  370. # not need to separate them; we send the whole lot to the server.
  371. # Fourth is the fragment identifier, which is everything after the first
  372. # "#" in the URL. The fragment identifier MUST NOT be sent to the server
  373. # and indeed, we don't bother to validate it (it could be an error to
  374. # pass it in here, but it's cheap to strip).
  375. #
  376. # An example of a URL that has all the parts:
  377. #
  378. # http://jschmoe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes
  379. #
  380. # The "http" is the protocol, the user is "jschmoe", the password is
  381. # "xyzzy", the host is "www.bogus.net", the port is "8000", the path is
  382. # "/foo/bar.tml", the query is "q=foo", and the fragment is "changes".
  383. #
  384. # Note that the RE actually combines the user and password parts, as
  385. # recommended in RFC 3986. Indeed, that RFC states that putting passwords
  386. # in URLs is a Really Bad Idea, something with which I would agree utterly.
  387. #
  388. # From a validation perspective, we need to ensure that the parts of the
  389. # URL that are going to the server are correctly encoded. This is only
  390. # done if $state(-strict) is true (inherited from $::http::strict).
  391. set URLmatcher {(?x) # this is _expanded_ syntax
  392. ^
  393. (?: (\w+) : ) ? # <protocol scheme>
  394. (?: //
  395. (?:
  396. (
  397. [^@/\#?]+ # <userinfo part of authority>
  398. ) @
  399. )?
  400. ( # <host part of authority>
  401. [^/:\#?]+ | # host name or IPv4 address
  402. \[ [^/\#?]+ \] # IPv6 address in square brackets
  403. )
  404. (?: : (\d+) )? # <port part of authority>
  405. )?
  406. ( / [^\#]*)? # <path> (including query)
  407. (?: \# (.*) )? # <fragment>
  408. $
  409. }
  410. # Phase one: parse
  411. if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} {
  412. unset $token
  413. return -code error "Unsupported URL: $url"
  414. }
  415. # Phase two: validate
  416. set host [string trim $host {[]}]; # strip square brackets from IPv6 address
  417. if {$host eq ""} {
  418. # Caller has to provide a host name; we do not have a "default host"
  419. # that would enable us to handle relative URLs.
  420. unset $token
  421. return -code error "Missing host part: $url"
  422. # Note that we don't check the hostname for validity here; if it's
  423. # invalid, we'll simply fail to resolve it later on.
  424. }
  425. if {$port ne "" && $port > 65535} {
  426. unset $token
  427. return -code error "Invalid port number: $port"
  428. }
  429. # The user identification and resource identification parts of the URL can
  430. # have encoded characters in them; take care!
  431. if {$user ne ""} {
  432. # Check for validity according to RFC 3986, Appendix A
  433. set validityRE {(?xi)
  434. ^
  435. (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
  436. $
  437. }
  438. if {$state(-strict) && ![regexp -- $validityRE $user]} {
  439. unset $token
  440. # Provide a better error message in this error case
  441. if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} {
  442. return -code error \
  443. "Illegal encoding character usage \"$bad\" in URL user"
  444. }
  445. return -code error "Illegal characters in URL user"
  446. }
  447. }
  448. if {$srvurl ne ""} {
  449. # Check for validity according to RFC 3986, Appendix A
  450. set validityRE {(?xi)
  451. ^
  452. # Path part (already must start with / character)
  453. (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )*
  454. # Query part (optional, permits ? characters)
  455. (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
  456. $
  457. }
  458. if {$state(-strict) && ![regexp -- $validityRE $srvurl]} {
  459. unset $token
  460. # Provide a better error message in this error case
  461. if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} {
  462. return -code error \
  463. "Illegal encoding character usage \"$bad\" in URL path"
  464. }
  465. return -code error "Illegal characters in URL path"
  466. }
  467. } else {
  468. set srvurl /
  469. }
  470. if {$proto eq ""} {
  471. set proto http
  472. }
  473. if {![info exists urlTypes($proto)]} {
  474. unset $token
  475. return -code error "Unsupported URL type \"$proto\""
  476. }
  477. set defport [lindex $urlTypes($proto) 0]
  478. set defcmd [lindex $urlTypes($proto) 1]
  479. if {$port eq ""} {
  480. set port $defport
  481. }
  482. if {![catch {$http(-proxyfilter) $host} proxy]} {
  483. set phost [lindex $proxy 0]
  484. set pport [lindex $proxy 1]
  485. }
  486. # OK, now reassemble into a full URL
  487. set url ${proto}://
  488. if {$user ne ""} {
  489. append url $user
  490. append url @
  491. }
  492. append url $host
  493. if {$port != $defport} {
  494. append url : $port
  495. }
  496. append url $srvurl
  497. # Don't append the fragment!
  498. set state(url) $url
  499. # If a timeout is specified we set up the after event and arrange for an
  500. # asynchronous socket connection.
  501. set sockopts [list]
  502. if {$state(-timeout) > 0} {
  503. set state(after) [after $state(-timeout) \
  504. [list http::reset $token timeout]]
  505. lappend sockopts -async
  506. }
  507. # If we are using the proxy, we must pass in the full URL that includes
  508. # the server name.
  509. if {[info exists phost] && ($phost ne "")} {
  510. set srvurl $url
  511. set targetAddr [list $phost $pport]
  512. } else {
  513. set targetAddr [list $host $port]
  514. }
  515. # Proxy connections aren't shared among different hosts.
  516. set state(socketinfo) $host:$port
  517. # See if we are supposed to use a previously opened channel.
  518. if {$state(-keepalive)} {
  519. variable socketmap
  520. if {[info exists socketmap($state(socketinfo))]} {
  521. if {[catch {fconfigure $socketmap($state(socketinfo))}]} {
  522. Log "WARNING: socket for $state(socketinfo) was closed"
  523. unset socketmap($state(socketinfo))
  524. } else {
  525. set sock $socketmap($state(socketinfo))
  526. Log "reusing socket $sock for $state(socketinfo)"
  527. catch {fileevent $sock writable {}}
  528. catch {fileevent $sock readable {}}
  529. }
  530. }
  531. # don't automatically close this connection socket
  532. set state(connection) {}
  533. }
  534. if {![info exists sock]} {
  535. # Pass -myaddr directly to the socket command
  536. if {[info exists state(-myaddr)]} {
  537. lappend sockopts -myaddr $state(-myaddr)
  538. }
  539. if {[catch {eval $defcmd $sockopts $targetAddr} sock]} {
  540. # something went wrong while trying to establish the connection.
  541. # Clean up after events and such, but DON'T call the command
  542. # callback (if available) because we're going to throw an
  543. # exception from here instead.
  544. set state(sock) $sock
  545. Finish $token "" 1
  546. cleanup $token
  547. return -code error $sock
  548. }
  549. }
  550. set state(sock) $sock
  551. Log "Using $sock for $state(socketinfo)" \
  552. [expr {$state(-keepalive)?"keepalive":""}]
  553. if {$state(-keepalive)} {
  554. set socketmap($state(socketinfo)) $sock
  555. }
  556. # Wait for the connection to complete.
  557. if {$state(-timeout) > 0} {
  558. fileevent $sock writable [list http::Connect $token]
  559. http::wait $token
  560. if {![info exists state]} {
  561. # If we timed out then Finish has been called and the users
  562. # command callback may have cleaned up the token. If so we end up
  563. # here with nothing left to do.
  564. return $token
  565. } elseif {$state(status) eq "error"} {
  566. # Something went wrong while trying to establish the connection.
  567. # Clean up after events and such, but DON'T call the command
  568. # callback (if available) because we're going to throw an
  569. # exception from here instead.
  570. set err [lindex $state(error) 0]
  571. cleanup $token
  572. return -code error $err
  573. } elseif {$state(status) ne "connect"} {
  574. # Likely to be connection timeout
  575. return $token
  576. }
  577. set state(status) ""
  578. }
  579. # Send data in cr-lf format, but accept any line terminators
  580. fconfigure $sock -translation {auto crlf} -buffersize $state(-blocksize)
  581. # The following is disallowed in safe interpreters, but the socket is
  582. # already in non-blocking mode in that case.
  583. catch {fconfigure $sock -blocking off}
  584. set how GET
  585. if {$isQuery} {
  586. set state(querylength) [string length $state(-query)]
  587. if {$state(querylength) > 0} {
  588. set how POST
  589. set contDone 0
  590. } else {
  591. # There's no query data.
  592. unset state(-query)
  593. set isQuery 0
  594. }
  595. } elseif {$state(-validate)} {
  596. set how HEAD
  597. } elseif {$isQueryChannel} {
  598. set how POST
  599. # The query channel must be blocking for the async Write to
  600. # work properly.
  601. fconfigure $state(-querychannel) -blocking 1 -translation binary
  602. set contDone 0
  603. }
  604. if {[info exists state(-method)] && $state(-method) ne ""} {
  605. set how $state(-method)
  606. }
  607. # We cannot handle chunked encodings with -handler, so force HTTP/1.0
  608. # until we can manage this.
  609. if {[info exists state(-handler)]} {
  610. set state(-protocol) 1.0
  611. }
  612. if {[catch {
  613. puts $sock "$how $srvurl HTTP/$state(-protocol)"
  614. puts $sock "Accept: $http(-accept)"
  615. array set hdrs $state(-headers)
  616. if {[info exists hdrs(Host)]} {
  617. # Allow Host spoofing. [Bug 928154]
  618. puts $sock "Host: $hdrs(Host)"
  619. } elseif {$port == $defport} {
  620. # Don't add port in this case, to handle broken servers. [Bug
  621. # #504508]
  622. puts $sock "Host: $host"
  623. } else {
  624. puts $sock "Host: $host:$port"
  625. }
  626. unset hdrs
  627. puts $sock "User-Agent: $http(-useragent)"
  628. if {$state(-protocol) == 1.0 && $state(-keepalive)} {
  629. puts $sock "Connection: keep-alive"
  630. }
  631. if {$state(-protocol) > 1.0 && !$state(-keepalive)} {
  632. puts $sock "Connection: close" ;# RFC2616 sec 8.1.2.1
  633. }
  634. if {[info exists phost] && ($phost ne "") && $state(-keepalive)} {
  635. puts $sock "Proxy-Connection: Keep-Alive"
  636. }
  637. set accept_encoding_seen 0
  638. set content_type_seen 0
  639. foreach {key value} $state(-headers) {
  640. if {[string equal -nocase $key "host"]} {
  641. continue
  642. }
  643. if {[string equal -nocase $key "accept-encoding"]} {
  644. set accept_encoding_seen 1
  645. }
  646. if {[string equal -nocase $key "content-type"]} {
  647. set content_type_seen 1
  648. }
  649. set value [string map [list \n "" \r ""] $value]
  650. set key [string trim $key]
  651. if {[string equal -nocase $key "content-length"]} {
  652. set contDone 1
  653. set state(querylength) $value
  654. }
  655. if {[string length $key]} {
  656. puts $sock "$key: $value"
  657. }
  658. }
  659. if {!$accept_encoding_seen && ![info exists state(-handler)]} {
  660. puts $sock "Accept-Encoding: deflate,gzip,compress"
  661. }
  662. if {$isQueryChannel && $state(querylength) == 0} {
  663. # Try to determine size of data in channel. If we cannot seek, the
  664. # surrounding catch will trap us
  665. set start [tell $state(-querychannel)]
  666. seek $state(-querychannel) 0 end
  667. set state(querylength) \
  668. [expr {[tell $state(-querychannel)] - $start}]
  669. seek $state(-querychannel) $start
  670. }
  671. # Flush the request header and set up the fileevent that will either
  672. # push the POST data or read the response.
  673. #
  674. # fileevent note:
  675. #
  676. # It is possible to have both the read and write fileevents active at
  677. # this point. The only scenario it seems to affect is a server that
  678. # closes the connection without reading the POST data. (e.g., early
  679. # versions TclHttpd in various error cases). Depending on the
  680. # platform, the client may or may not be able to get the response from
  681. # the server because of the error it will get trying to write the post
  682. # data. Having both fileevents active changes the timing and the
  683. # behavior, but no two platforms (among Solaris, Linux, and NT) behave
  684. # the same, and none behave all that well in any case. Servers should
  685. # always read their POST data if they expect the client to read their
  686. # response.
  687. if {$isQuery || $isQueryChannel} {
  688. if {!$content_type_seen} {
  689. puts $sock "Content-Type: $state(-type)"
  690. }
  691. if {!$contDone} {
  692. puts $sock "Content-Length: $state(querylength)"
  693. }
  694. puts $sock ""
  695. fconfigure $sock -translation {auto binary}
  696. fileevent $sock writable [list http::Write $token]
  697. } else {
  698. puts $sock ""
  699. flush $sock
  700. fileevent $sock readable [list http::Event $sock $token]
  701. }
  702. if {![info exists state(-command)]} {
  703. # geturl does EVERYTHING asynchronously, so if the user calls it
  704. # synchronously, we just do a wait here.
  705. wait $token
  706. if {$state(status) eq "error"} {
  707. # Something went wrong, so throw the exception, and the
  708. # enclosing catch will do cleanup.
  709. return -code error [lindex $state(error) 0]
  710. }
  711. }
  712. } err]} {
  713. # The socket probably was never connected, or the connection dropped
  714. # later.
  715. # Clean up after events and such, but DON'T call the command callback
  716. # (if available) because we're going to throw an exception from here
  717. # instead.
  718. # if state(status) is error, it means someone's already called Finish
  719. # to do the above-described clean up.
  720. if {$state(status) ne "error"} {
  721. Finish $token $err 1
  722. }
  723. cleanup $token
  724. return -code error $err
  725. }
  726. return $token
  727. }
  728. # Data access functions:
  729. # Data - the URL data
  730. # Status - the transaction status: ok, reset, eof, timeout
  731. # Code - the HTTP transaction code, e.g., 200
  732. # Size - the size of the URL data
  733. proc http::data {token} {
  734. variable $token
  735. upvar 0 $token state
  736. return $state(body)
  737. }
  738. proc http::status {token} {
  739. if {![info exists $token]} {
  740. return "error"
  741. }
  742. variable $token
  743. upvar 0 $token state
  744. return $state(status)
  745. }
  746. proc http::code {token} {
  747. variable $token
  748. upvar 0 $token state
  749. return $state(http)
  750. }
  751. proc http::ncode {token} {
  752. variable $token
  753. upvar 0 $token state
  754. if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
  755. return $numeric_code
  756. } else {
  757. return $state(http)
  758. }
  759. }
  760. proc http::size {token} {
  761. variable $token
  762. upvar 0 $token state
  763. return $state(currentsize)
  764. }
  765. proc http::meta {token} {
  766. variable $token
  767. upvar 0 $token state
  768. return $state(meta)
  769. }
  770. proc http::error {token} {
  771. variable $token
  772. upvar 0 $token state
  773. if {[info exists state(error)]} {
  774. return $state(error)
  775. }
  776. return ""
  777. }
  778. # http::cleanup
  779. #
  780. # Garbage collect the state associated with a transaction
  781. #
  782. # Arguments
  783. # token The token returned from http::geturl
  784. #
  785. # Side Effects
  786. # unsets the state array
  787. proc http::cleanup {token} {
  788. variable $token
  789. upvar 0 $token state
  790. if {[info exists state]} {
  791. unset state
  792. }
  793. }
  794. # http::Connect
  795. #
  796. # This callback is made when an asyncronous connection completes.
  797. #
  798. # Arguments
  799. # token The token returned from http::geturl
  800. #
  801. # Side Effects
  802. # Sets the status of the connection, which unblocks
  803. # the waiting geturl call
  804. proc http::Connect {token} {
  805. variable $token
  806. upvar 0 $token state
  807. set err "due to unexpected EOF"
  808. if {
  809. [eof $state(sock)] ||
  810. [set err [fconfigure $state(sock) -error]] ne ""
  811. } {
  812. Finish $token "connect failed $err" 1
  813. } else {
  814. set state(status) connect
  815. fileevent $state(sock) writable {}
  816. }
  817. return
  818. }
  819. # http::Write
  820. #
  821. # Write POST query data to the socket
  822. #
  823. # Arguments
  824. # token The token for the connection
  825. #
  826. # Side Effects
  827. # Write the socket and handle callbacks.
  828. proc http::Write {token} {
  829. variable $token
  830. upvar 0 $token state
  831. set sock $state(sock)
  832. # Output a block. Tcl will buffer this if the socket blocks
  833. set done 0
  834. if {[catch {
  835. # Catch I/O errors on dead sockets
  836. if {[info exists state(-query)]} {
  837. # Chop up large query strings so queryprogress callback can give
  838. # smooth feedback.
  839. puts -nonewline $sock \
  840. [string range $state(-query) $state(queryoffset) \
  841. [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]]
  842. incr state(queryoffset) $state(-queryblocksize)
  843. if {$state(queryoffset) >= $state(querylength)} {
  844. set state(queryoffset) $state(querylength)
  845. set done 1
  846. }
  847. } else {
  848. # Copy blocks from the query channel
  849. set outStr [read $state(-querychannel) $state(-queryblocksize)]
  850. puts -nonewline $sock $outStr
  851. incr state(queryoffset) [string length $outStr]
  852. if {[eof $state(-querychannel)]} {
  853. set done 1
  854. }
  855. }
  856. } err]} {
  857. # Do not call Finish here, but instead let the read half of the socket
  858. # process whatever server reply there is to get.
  859. set state(posterror) $err
  860. set done 1
  861. }
  862. if {$done} {
  863. catch {flush $sock}
  864. fileevent $sock writable {}
  865. fileevent $sock readable [list http::Event $sock $token]
  866. }
  867. # Callback to the client after we've completely handled everything.
  868. if {[string length $state(-queryprogress)]} {
  869. eval $state(-queryprogress) \
  870. [list $token $state(querylength) $state(queryoffset)]
  871. }
  872. }
  873. # http::Event
  874. #
  875. # Handle input on the socket
  876. #
  877. # Arguments
  878. # sock The socket receiving input.
  879. # token The token returned from http::geturl
  880. #
  881. # Side Effects
  882. # Read the socket and handle callbacks.
  883. proc http::Event {sock token} {
  884. variable $token
  885. upvar 0 $token state
  886. if {![info exists state]} {
  887. Log "Event $sock with invalid token '$token' - remote close?"
  888. if {![eof $sock]} {
  889. if {[set d [read $sock]] ne ""} {
  890. Log "WARNING: additional data left on closed socket"
  891. }
  892. }
  893. CloseSocket $sock
  894. return
  895. }
  896. if {$state(state) eq "connecting"} {
  897. if {[catch {gets $sock state(http)} n]} {
  898. return [Finish $token $n]
  899. } elseif {$n >= 0} {
  900. set state(state) "header"
  901. }
  902. } elseif {$state(state) eq "header"} {
  903. if {[catch {gets $sock line} n]} {
  904. return [Finish $token $n]
  905. } elseif {$n == 0} {
  906. # We have now read all headers
  907. # We ignore HTTP/1.1 100 Continue returns. RFC2616 sec 8.2.3
  908. if {$state(http) == "" || [lindex $state(http) 1] == 100} {
  909. return
  910. }
  911. set state(state) body
  912. # If doing a HEAD, then we won't get any body
  913. if {$state(-validate)} {
  914. Eof $token
  915. return
  916. }
  917. # For non-chunked transfer we may have no body - in this case we
  918. # may get no further file event if the connection doesn't close
  919. # and no more data is sent. We can tell and must finish up now -
  920. # not later.
  921. if {
  922. !(([info exists state(connection)]
  923. && ($state(connection) eq "close"))
  924. || [info exists state(transfer)])
  925. && ($state(totalsize) == 0)
  926. } {
  927. Log "body size is 0 and no events likely - complete."
  928. Eof $token
  929. return
  930. }
  931. # We have to use binary translation to count bytes properly.
  932. fconfigure $sock -translation binary
  933. if {
  934. $state(-binary) || ![string match -nocase text* $state(type)]
  935. } {
  936. # Turn off conversions for non-text data
  937. set state(binary) 1
  938. }
  939. if {[info exists state(-channel)]} {
  940. if {$state(binary) || [llength [ContentEncoding $token]]} {
  941. fconfigure $state(-channel) -translation binary
  942. }
  943. if {![info exists state(-handler)]} {
  944. # Initiate a sequence of background fcopies
  945. fileevent $sock readable {}
  946. CopyStart $sock $token
  947. return
  948. }
  949. }
  950. } elseif {$n > 0} {
  951. # Process header lines
  952. if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
  953. switch -- [string tolower $key] {
  954. content-type {
  955. set state(type) [string trim [string tolower $value]]
  956. # grab the optional charset information
  957. if {[regexp -nocase \
  958. {charset\s*=\s*\"((?:[^""]|\\\")*)\"} \
  959. $state(type) -> cs]} {
  960. set state(charset) [string map {{\"} \"} $cs]
  961. } else {
  962. regexp -nocase {charset\s*=\s*(\S+?);?} \
  963. $state(type) -> state(charset)
  964. }
  965. }
  966. content-length {
  967. set state(totalsize) [string trim $value]
  968. }
  969. content-encoding {
  970. set state(coding) [string trim $value]
  971. }
  972. transfer-encoding {
  973. set state(transfer) \
  974. [string trim [string tolower $value]]
  975. }
  976. proxy-connection -
  977. connection {
  978. set state(connection) \
  979. [string trim [string tolower $value]]
  980. }
  981. }
  982. lappend state(meta) $key [string trim $value]
  983. }
  984. }
  985. } else {
  986. # Now reading body
  987. if {[catch {
  988. if {[info exists state(-handler)]} {
  989. set n [eval $state(-handler) [list $sock $token]]
  990. } elseif {[info exists state(transfer_final)]} {
  991. set line [getTextLine $sock]
  992. set n [string length $line]
  993. if {$n > 0} {
  994. Log "found $n bytes following final chunk"
  995. append state(transfer_final) $line
  996. } else {
  997. Log "final chunk part"
  998. Eof $token
  999. }
  1000. } elseif {
  1001. [info exists state(transfer)]
  1002. && $state(transfer) eq "chunked"
  1003. } {
  1004. set size 0
  1005. set chunk [getTextLine $sock]
  1006. set n [string length $chunk]
  1007. if {[string trim $chunk] ne ""} {
  1008. scan $chunk %x size
  1009. if {$size != 0} {
  1010. set bl [fconfigure $sock -blocking]
  1011. fconfigure $sock -blocking 1
  1012. set chunk [read $sock $size]
  1013. fconfigure $sock -blocking $bl
  1014. set n [string length $chunk]
  1015. if {$n >= 0} {
  1016. append state(body) $chunk
  1017. }
  1018. if {$size != [string length $chunk]} {
  1019. Log "WARNING: mis-sized chunk:\
  1020. was [string length $chunk], should be $size"
  1021. }
  1022. getTextLine $sock
  1023. } else {
  1024. set state(transfer_final) {}
  1025. }
  1026. }
  1027. } else {
  1028. #Log "read non-chunk $state(currentsize) of $state(totalsize)"
  1029. set block [read $sock $state(-blocksize)]
  1030. set n [string length $block]
  1031. if {$n >= 0} {
  1032. append state(body) $block
  1033. }
  1034. }
  1035. if {[info exists state]} {
  1036. if {$n >= 0} {
  1037. incr state(currentsize) $n
  1038. }
  1039. # If Content-Length - check for end of data.
  1040. if {
  1041. ($state(totalsize) > 0)
  1042. && ($state(currentsize) >= $state(totalsize))
  1043. } {
  1044. Eof $token
  1045. }
  1046. }
  1047. } err]} {
  1048. return [Finish $token $err]
  1049. } else {
  1050. if {[info exists state(-progress)]} {
  1051. eval $state(-progress) \
  1052. [list $token $state(totalsize) $state(currentsize)]
  1053. }
  1054. }
  1055. }
  1056. # catch as an Eof above may have closed the socket already
  1057. if {![catch {eof $sock} eof] && $eof} {
  1058. if {[info exists $token]} {
  1059. set state(connection) close
  1060. Eof $token
  1061. } else {
  1062. # open connection closed on a token that has been cleaned up.
  1063. CloseSocket $sock
  1064. }
  1065. return
  1066. }
  1067. }
  1068. # http::getTextLine --
  1069. #
  1070. # Get one line with the stream in blocking crlf mode
  1071. #
  1072. # Arguments
  1073. # sock The socket receiving input.
  1074. #
  1075. # Results:
  1076. # The line of text, without trailing newline
  1077. proc http::getTextLine {sock} {
  1078. set tr [fconfigure $sock -translation]
  1079. set bl [fconfigure $sock -blocking]
  1080. fconfigure $sock -translation crlf -blocking 1
  1081. set r [gets $sock]
  1082. fconfigure $sock -translation $tr -blocking $bl
  1083. return $r
  1084. }
  1085. # http::CopyStart
  1086. #
  1087. # Error handling wrapper around fcopy
  1088. #
  1089. # Arguments
  1090. # sock The socket to copy from
  1091. # token The token returned from http::geturl
  1092. #
  1093. # Side Effects
  1094. # This closes the connection upon error
  1095. proc http::CopyStart {sock token {initial 1}} {
  1096. upvar #0 $token state
  1097. if {[info exists state(transfer)] && $state(transfer) eq "chunked"} {
  1098. foreach coding [ContentEncoding $token] {
  1099. lappend state(zlib) [zlib stream $coding]
  1100. }
  1101. make-transformation-chunked $sock [namespace code [list CopyChunk $token]]
  1102. } else {
  1103. if {$initial} {
  1104. foreach coding [ContentEncoding $token] {
  1105. zlib push $coding $sock
  1106. }
  1107. }
  1108. if {[catch {
  1109. fcopy $sock $state(-channel) -size $state(-blocksize) -command \
  1110. [list http::CopyDone $token]
  1111. } err]} {
  1112. Finish $token $err
  1113. }
  1114. }
  1115. }
  1116. proc http::CopyChunk {token chunk} {
  1117. upvar 0 $token state
  1118. if {[set count [string length $chunk]]} {
  1119. incr state(currentsize) $count
  1120. if {[info exists state(zlib)]} {
  1121. foreach stream $state(zlib) {
  1122. set chunk [$stream add $chunk]
  1123. }
  1124. }
  1125. puts -nonewline $state(-channel) $chunk
  1126. if {[info exists state(-progress)]} {
  1127. eval [linsert $state(-progress) end \
  1128. $token $state(totalsize) $state(currentsize)]
  1129. }
  1130. } else {
  1131. Log "CopyChunk Finish $token"
  1132. if {[info exists state(zlib)]} {
  1133. set excess ""
  1134. foreach stream $state(zlib) {
  1135. catch {set excess [$stream add -finalize $excess]}
  1136. }
  1137. puts -nonewline $state(-channel) $excess
  1138. foreach stream $state(zlib) { $stream close }
  1139. unset state(zlib)
  1140. }
  1141. Eof $token ;# FIX ME: pipelining.
  1142. }
  1143. }
  1144. # http::CopyDone
  1145. #
  1146. # fcopy completion callback
  1147. #
  1148. # Arguments
  1149. # token The token returned from http::geturl
  1150. # count The amount transfered
  1151. #
  1152. # Side Effects
  1153. # Invokes callbacks
  1154. proc http::CopyDone {token count {error {}}} {
  1155. variable $token
  1156. upvar 0 $token state
  1157. set sock $state(sock)
  1158. incr state(currentsize) $count
  1159. if {[info exists state(-progress)]} {
  1160. eval $state(-progress) \
  1161. [list $token $state(totalsize) $state(currentsize)]
  1162. }
  1163. # At this point the token may have been reset
  1164. if {[string length $error]} {
  1165. Finish $token $error
  1166. } elseif {[catch {eof $sock} iseof] || $iseof} {
  1167. Eof $token
  1168. } else {
  1169. CopyStart $sock $token 0
  1170. }
  1171. }
  1172. # http::Eof
  1173. #
  1174. # Handle eof on the socket
  1175. #
  1176. # Arguments
  1177. # token The token returned from http::geturl
  1178. #
  1179. # Side Effects
  1180. # Clean up the socket
  1181. proc http::Eof {token {force 0}} {
  1182. variable $token
  1183. upvar 0 $token state
  1184. if {$state(state) eq "header"} {
  1185. # Premature eof
  1186. set state(status) eof
  1187. } else {
  1188. set state(status) ok
  1189. }
  1190. if {[string length $state(body)] > 0} {
  1191. if {[catch {
  1192. foreach coding [ContentEncoding $token] {
  1193. set state(body) [zlib $coding $state(body)]
  1194. }
  1195. } err]} {
  1196. Log "error doing $coding '$state(body)'"
  1197. return [Finish $token $err]
  1198. }
  1199. if {!$state(binary)} {
  1200. # If we are getting text, set the incoming channel's encoding
  1201. # correctly. iso8859-1 is the RFC default, but this could be any IANA
  1202. # charset. However, we only know how to convert what we have
  1203. # encodings for.
  1204. set enc [CharsetToEncoding $state(charset)]
  1205. if {$enc ne "binary"} {
  1206. set state(body) [encoding convertfrom $enc $state(body)]
  1207. }
  1208. # Translate text line endings.
  1209. set state(body) [string map {\r\n \n \r \n} $state(body)]
  1210. }
  1211. }
  1212. Finish $token
  1213. }
  1214. # http::wait --
  1215. #
  1216. # See documentation for details.
  1217. #
  1218. # Arguments:
  1219. # token Connection token.
  1220. #
  1221. # Results:
  1222. # The status after the wait.
  1223. proc http::wait {token} {
  1224. variable $token
  1225. upvar 0 $token state
  1226. if {![info exists state(status)] || $state(status) eq ""} {
  1227. # We must wait on the original variable name, not the upvar alias
  1228. vwait ${token}(status)
  1229. }
  1230. return [status $token]
  1231. }
  1232. # http::formatQuery --
  1233. #
  1234. # See documentation for details. Call http::formatQuery with an even
  1235. # number of arguments, where the first is a name, the second is a value,
  1236. # the third is another name, and so on.
  1237. #
  1238. # Arguments:
  1239. # args A list of name-value pairs.
  1240. #
  1241. # Results:
  1242. # TODO
  1243. proc http::formatQuery {args} {
  1244. set result ""
  1245. set sep ""
  1246. foreach i $args {
  1247. append result $sep [mapReply $i]
  1248. if {$sep eq "="} {
  1249. set sep &
  1250. } else {
  1251. set sep =
  1252. }
  1253. }
  1254. return $result
  1255. }
  1256. # http::mapReply --
  1257. #
  1258. # Do x-www-urlencoded character mapping
  1259. #
  1260. # Arguments:
  1261. # string The string the needs to be encoded
  1262. #
  1263. # Results:
  1264. # The encoded string
  1265. proc http::mapReply {string} {
  1266. variable http
  1267. variable formMap
  1268. # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use
  1269. # a pre-computed map and [string map] to do the conversion (much faster
  1270. # than [regsub]/[subst]). [Bug 1020491]
  1271. if {$http(-urlencoding) ne ""} {
  1272. set string [encoding convertto $http(-urlencoding) $string]
  1273. return [string map $formMap $string]
  1274. }
  1275. set converted [string map $formMap $string]
  1276. if {[string match "*\[\u0100-\uffff\]*" $converted]} {
  1277. regexp {[\u0100-\uffff]} $converted badChar
  1278. # Return this error message for maximum compatability... :^/
  1279. return -code error \
  1280. "can't read \"formMap($badChar)\": no such element in array"
  1281. }
  1282. return $converted
  1283. }
  1284. # http::ProxyRequired --
  1285. # Default proxy filter.
  1286. #
  1287. # Arguments:
  1288. # host The destination host
  1289. #
  1290. # Results:
  1291. # The current proxy settings
  1292. proc http::ProxyRequired {host} {
  1293. variable http
  1294. if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
  1295. if {
  1296. ![info exists http(-proxyport)] ||
  1297. ![string length $http(-proxyport)]
  1298. } {
  1299. set http(-proxyport) 8080
  1300. }
  1301. return [list $http(-proxyhost) $http(-proxyport)]
  1302. }
  1303. }
  1304. # http::CharsetToEncoding --
  1305. #
  1306. # Tries to map a given IANA charset to a tcl encoding. If no encoding
  1307. # can be found, returns binary.
  1308. #
  1309. proc http::CharsetToEncoding {charset} {
  1310. variable encodings
  1311. set charset [string tolower $charset]
  1312. if {[regexp {iso-?8859-([0-9]+)} $charset -> num]} {
  1313. set encoding "iso8859-$num"
  1314. } elseif {[regexp {iso-?2022-(jp|kr)} $charset -> ext]} {
  1315. set encoding "iso2022-$ext"
  1316. } elseif {[regexp {shift[-_]?js} $charset]} {
  1317. set encoding "shiftjis"
  1318. } elseif {[regexp {(?:windows|cp)-?([0-9]+)} $charset -> num]} {
  1319. set encoding "cp$num"
  1320. } elseif {$charset eq "us-ascii"} {
  1321. set encoding "ascii"
  1322. } elseif {[regexp {(?:iso-?)?lat(?:in)?-?([0-9]+)} $charset -> num]} {
  1323. switch -- $num {
  1324. 5 {set encoding "iso8859-9"}
  1325. 1 - 2 - 3 {
  1326. set encoding "iso8859-$num"
  1327. }
  1328. }
  1329. } else {
  1330. # other charset, like euc-xx, utf-8,... may directly map to encoding
  1331. set encoding $charset
  1332. }
  1333. set idx [lsearch -exact $encodings $encoding]
  1334. if {$idx >= 0} {
  1335. return $encoding
  1336. } else {
  1337. return "binary"
  1338. }
  1339. }
  1340. # Return the list of content-encoding transformations we need to do in order.
  1341. proc http::ContentEncoding {token} {
  1342. upvar 0 $token state
  1343. set r {}
  1344. if {[info exists state(coding)]} {
  1345. foreach coding [split $state(coding) ,] {
  1346. switch -exact -- $coding {
  1347. deflate { lappend r inflate }
  1348. gzip - x-gzip { lappend r gunzip }
  1349. compress - x-compress { lappend r decompress }
  1350. identity {}
  1351. default {
  1352. return -code error "unsupported content-encoding \"$coding\""
  1353. }
  1354. }
  1355. }
  1356. }
  1357. return $r
  1358. }
  1359. proc http::make-transformation-chunked {chan command} {
  1360. set lambda {{chan command} {
  1361. set data ""
  1362. set size -1
  1363. yield
  1364. while {1} {
  1365. chan configure $chan -translation {crlf binary}
  1366. while {[gets $chan line] < 1} { yield }
  1367. chan configure $chan -translation {binary binary}
  1368. if {[scan $line %x size] != 1} { return -code error "invalid size: \"$line\"" }
  1369. set chunk ""
  1370. while {$size && ![chan eof $chan]} {
  1371. set part [chan read $chan $size]
  1372. incr size -[string length $part]
  1373. append chunk $part
  1374. }
  1375. if {[catch {
  1376. uplevel #0 [linsert $command end $chunk]
  1377. }]} {
  1378. http::Log "Error in callback: $::errorInfo"
  1379. }
  1380. if {[string length $chunk] == 0} {
  1381. # channel might have been closed in the callback
  1382. catch {chan event $chan readable {}}
  1383. return
  1384. }
  1385. }
  1386. }}
  1387. coroutine dechunk$chan ::apply $lambda $chan $command
  1388. chan event $chan readable [namespace origin dechunk$chan]
  1389. return
  1390. }
  1391. # Local variables:
  1392. # indent-tabs-mode: t
  1393. # End: