~ubuntu-branches/ubuntu/gutsy/amsn/gutsy

« back to all changes in this revision

Viewing changes to utils/http2.4/http.tcl

  • Committer: Bazaar Package Importer
  • Author(s): Theodore Karkoulis
  • Date: 2006-01-04 15:26:02 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20060104152602-ipe1yg00rl3nlklv
Tags: 0.95-1
New Upstream Release (closes: #345052, #278575).

Show diffs side-by-side

added added

removed removed

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