Changes On Branch http3
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch http3 Excluding Merge-Ins

This is equivalent to a diff from 0bd419a4d7 to 13fc64f17c

2017-09-21
22:00
Checkpoint of work in progress. Leaf check-in: 13fc64f17c user: dkf tags: http3
2017-07-04
14:31
Add in the URL parsing as a general service. check-in: 68f522f39f user: dkf tags: http3
2017-06-25
22:16
Start of work on developing a new major version of the HTTP package. check-in: 21792340da user: dkf tags: http3
2017-06-23
08:11
TIP #472 implementation: Add Support for 0d Radix Prefix to Integer Literals check-in: 4f68bf6677 user: jan.nijtmans tags: trunk
2017-06-22
22:54
Add [regsub -command] case that wasn't actively tested for. check-in: 0bd419a4d7 user: dkf tags: trunk
22:03
Implement TIP #473: Allow a Defined Target Namespace in oo::copy check-in: 1f77049cc0 user: dkf tags: trunk

Added library/http3/http.tcl.
























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
package require Tcl 8.7-
package provided http 3

namespace eval ::http {
    if {[info command ::http::Log] eq {}} {proc ::http::Log {args} {}}

    variable ContextConfig {
	-accept {string {^[^\s/]+/+[^\s/+]$} "MIME type"}
	-charset string
	-connectionclass class
	-keepalive boolean
	-proxyfilter callback
	-proxyhost string
	-proxyport integer
	-strict boolean
	-urlencoding encoding
	-useragent string
    }

    variable ConnectionConfig {
	-binary boolean
	-blocksize integer
	-channel channel
	-command callback
	-handler callback
	-headers dict
	-keepalive boolean
	-method {string ^[A-Z0-9]+$ "uppercase string"}
	-myaddr string
	-progress callback
	-protocol string
	-query string
	-queryblocksize integer
	-querychannel channel
	-queryprogress callback
	-timeout integer
	-type {string {^[^\s/]+/+[^\s/+]$} "MIME type"}
	-validate boolean
    }
    variable ConnectionDefaults {
	-binary false
	-blocksize 8192
	-queryblocksize 8192
	-validate false
	-headers {}
	-timeout 0
	-type application/x-www-form-urlencoded
	-queryprogress {}
	-protocol 1.1
    }

    oo::class create Context {
	variable config
	variable socketmap urltypes encodings charset keepalive
	variable connectionclass counter

	constructor {} {
	    array set config {
		-accept */*
		-charset iso8859-1
		-keepalive 0
		-proxyhost {}
		-proxyport {}
		-urlencoding utf-8
	    }
	    set config(-proxyfilter) [namespace code {my ProxyRequired}]
	    set connectionclass ::http::Connection

	    # We need a useragent string of this style or various servers will
	    # refuse to send us compressed content even when we ask for it.
	    # This follows the de-facto layout of user-agent strings in
	    # current browsers.  Safe interpreters do not have
	    # ::tcl_platform(os) or ::tcl_platform(osVersion).

	    if {[interp issafe]} {
		set platform "Windows; U; Windows NT 10.0"
	    } else {
		global tcl_platform
		set platform "[string totitle $tcl_platform(platform)]; U;\
			$tcl_platform(os) $tcl_platform(osVersion)"
	    }
	    set config(-useragent) "Mozilla/5.0 ($platform)\
		    http/[package provide http] Tcl/[package provide Tcl]"

	    # Create a map for HTTP/1.1 open sockets
	    if {[info exists socketmap]} {
		# Close but don't remove open sockets on re-init
		foreach {url sock} [array get socketmap] {
		    catch {close $sock}
		}
	    }
	    array set socketmap {}

	    set urltypes(http) [list 80 ::socket]

	    set encodings [string tolower [encoding names]]
	    set charset "iso8859-1"
	    set keepalive 0
	    set counter 0
	}

	method register {proto port command} {
	    set lower [string tolower $proto]
	    try {
		return $urltypes($lower)
	    } on error {} {
		return {}
	    } finally {
		set urltypes($lower) [list $port $command]
	    }
	}

	method unregister {proto} {
	    set lower [string tolower $proto]
	    if {![info exists urlTypes($lower)]} {
		return -code error "unsupported url type \"$proto\""
	    }
	    try {
		return $urlTypes($lower)
	    } finally {
		unset -nocomplain urlTypes($lower)
	    }
	}

	method configure {args} {
	    variable ::http::ContextConfig

	    set options [dict keys $ContextConfig]
	    if {[llength $args] == 0} {
		set result {}
		dict for {option typeinfo} $ContextConfig {
		    lappend result $option $config($option)
		}
		return $result
	    }
	    if {[llength $args] == 1} {
		set opt [::tcl::prefix match $options [lindex $args 0]]
		return $config($opt)
	    }
	    foreach {option value} $args {
		set opt [::tcl::prefix match $options $option]
		set typeinfo [lassign [dict get $ContextConfig $opt] type]
		::http::Validate($type) $opt $value {*}$typeinfo
		set config($opt) $value
	    }
	}

	method formatQuery args {
	    set result ""
	    set sep ""
	    foreach i $args {
		append result $sep [my mapReply $i]
		if {$sep eq "="} {
		    set sep &
		} else {
		    set sep =
		}
	    }
	    return $result
	}

	method mapReply {string} {
	    # The spec says: "non-alphanumeric characters are replaced by
	    # '%HH'". [Bug 1020491] [regsub -command] is *designed* for this.

	    if {$urlencoding ne ""} {
		set string [encoding convertto $urlencoding $string]
	    }

	    set RE "(\r?\n)|(\[^._~a-zA-Z0-9\])"
	    return [regsub -all -command -- $RE $string {apply {{- nl ch} {
		# RFC3986 Section 2.3 say percent encode all except:
		# "... percent-encoded octets in the ranges of ALPHA (%41-%5A
		# and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
		# underscore (%5F), or tilde (%7E) should not be created by
		# URI producers ..."
		#
		# Note that newline is a special case
		if {$nl ne ""} {return %0D%0A}
		scan $ch %c c
		return [format %%%.2X $c]
	    }}}]
	}

	method geturl {url args} {
	    variable ::http::ConnectionConfig
	    variable ::http::ConnectionDefaults
	    if {[llength $args] & 1} {
		return -code error "missing configuration option"
	    }
	    set names [dict keys $ConnectionConfig]
	    set options [dict map {opt value} $args {
		set opt [::tcl::prefix match $names $opt]
		set typeinfo [lassign [dict get $ConnectionConfig $opt] type]
		::http::Validate($type) $opt $value {*}$typeinfo
		set value
	    }]
	    $connectionclass create [incr counter] \
		[self] $url $ConnectionDefaults $options
	}

	method ProxyRequred {host} {
	    if {[info exists proxyhost] && [string length $proxyhost]} {
		if {![info exists proxyport] || ![string length $proxyport]} {
		    set proxyport 8080
		}
		return [list $proxyhost $proxyport]
	    }
	}

	method parseURL {url} {
	    # Validate URL, determine the server host and port, and check
	    # proxy case Recognize user:pass@host URLs also, although we do
	    # not do anything with that info yet.

	    # URLs have basically four parts.
	    #
	    # First, before the colon, is the protocol scheme (e.g. http).
	    #
	    # Second, for HTTP-like protocols, is the authority. The authority
	    #	is preceded by // and lasts up to (but not including) the
	    #	following / or ? and it identifies up to four parts, of which
	    #	only one, the host, is required (if an authority is present at
	    #	all). All other parts of the authority (user name, password,
	    #	port number) are optional.
	    #
	    # Third is the resource name, which is split into two parts at a ?
	    #	The first part (from the single "/" up to "?") is the path,
	    #	and the second part (from that "?" up to "#") is the
	    #	query. *HOWEVER*, we do not need to separate them; we send the
	    #	whole lot to the server.  Both, path and query are allowed to
	    #	be missing, including their delimiting character.
	    #
	    # Fourth is the fragment identifier, which is everything after the
	    #	firsts "#" in the URL. The fragment identifier MUST NOT be
	    #	sent to the server and indeed, we don't bother to validate it
	    #	(it could be an error to pass it in here, but it's cheap to
	    #	strip).
	    #
	    # An example of a URL that has all the parts:
	    #
	    #   http://joe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes
	    #
	    # The "http" is the protocol, the user is "joe", the password is
	    # "xyzzy", the host is "www.bogus.net", the port is "8000", the
	    # path is "/foo/bar.tml", the query is "q=foo", and the fragment
	    # is "changes".
	    #
	    # Note that the RE actually combines the user and password parts,
	    # as recommended in RFC 3986. Indeed, that RFC states that putting
	    # passwords in URLs is a Really Bad Idea, something with which I
	    # would agree utterly.
	    #
	    # From a validation perspective, we need to ensure that the parts
	    # of the URL that are going to the server are correctly encoded.
	    # This is only done if $config(-strict) is true.

	    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>
		$
	    }

	    # Phase one: parse
	    if {![regexp -- $URLmatcher $url -> \
		    proto user host port srvurl fragment]} {
		return -code error "unsupported URL: $url"
	    }
	    # Phase two: validate
	    set host [string trim $host {[]}]; # strip square brackets from IPv6 address
	    if {$host eq ""} {
		# Caller has to provide a host name; we do not have a "default
		# host" that would enable us to handle relative URLs.
		return -code error "Missing host part: $url"
		# Note that we don't check the hostname for validity here; if
		# it's invalid, we'll simply fail to resolve it later on.
	    }
	    if {$port ne "" && $port > 65535} {
		return -code error "invalid port number: $port"
	    }
	    # The user identification and resource identification parts of the
	    # URL can have encoded characters in them; take care!
	    if {$user ne ""} {
		# Check for validity according to RFC 3986, Appendix A
		set validityRE {(?xi)
		    ^
		    (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+
		    $
		}
		if {$config(-strict) && ![regexp -- $validityRE $user]} {
		    # Provide a better error message in this error case
		    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 ""} {
		# RFC 3986 allows empty paths (not even a /), but servers
		# return 400 if the path in the HTTP request doesn't start
		# with / , so add it here if needed.
		if {[string index $srvurl 0] ne "/"} {
		    set srvurl /$srvurl
		}
		# Check for validity according to RFC 3986, Appendix A
		set validityRE {(?xi)
		    ^
		    # Path part (already must start with / character)
		    (?:	      [-\w.~!$&'()*+,;=:@/]  | %[0-9a-f][0-9a-f] )*
		    # Query part (optional, permits ? characters)
		    (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )?
		    $
		}
		if {$config(-strict) && ![regexp -- $validityRE $srvurl]} {
		    # Provide a better error message in this error case
		    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"
		}
	    }

	    return [list $proto $user $host $port $srvurl \
		    [string trimleft $fragment "#"]]
	}
    }

    oo::class create Connection {
	variable cfg urlTypes http
	variable binary state meta coding currentsize totalsize querylength
	variable queryoffset type body status httpline connection charset
	variable theURL after socketinfo sock acceptTypes

	constructor {context url defaults options} {
	    interp alias {} [namespace current]::Context {} $context
	    set ns [info object namespace $context]
	    my eval namespace upvar $ns \
		config http urlTypes urlTypes socketmap socketmap
	    foreach {opt value} $defaults {
		set cfg($opt) $value
	    }
	    set cfg(-keepalive) $http(-keepalive)
	    foreach {opt value} $options {
		set cfg($opt) $value
	    }

	    my reset

	    set binary 0
	    set state connecting
	    set meta {}
	    set coding {}
	    set currentsize 0
	    set totalsize 0
	    set querylength 0
	    set queryoffset 0
	    set type text/html
	    set body {}
	    set status ""
	    set httpline ""
	    set connection close
	    set charset $http(-charset)

	    if {[info exists cfg(-querychannel)]&&[info exists cfg(-query)]} {
		return -code error \
		    "can't use -query and -querychannel options together"
	    }

	    lassign [Context parseURL $url] proto user host port srvurl
	    if {$srvurl eq ""} {
		set srvurl "/"
	    }
	    if {$proto eq ""} {
		set proto "http"
	    }
	    set lower [string tolower $proto]
	    if {![info exists urlTypes($lower)]} {
		return -code error "unsupported URL type \"$proto\""
	    }
	    lassign $urlTypes($lower) defport defcmd
	    if {$port eq ""} {
		set port $defport
	    }

	    # Check for the proxy's opinion
	    catch {
		if {[llength $http(-proxyfilter)]} {
		    lassign [{*}$http(-proxyfilter) $host] phost pport
		}
	    }

	    # OK, now reassemble into a full URL
	    set url ${proto}://
	    if {$user ne ""} {
		append url $user
		append url @
	    }
	    append url $host
	    if {$port != $defport} {
		append url : $port
	    }
	    append url $srvurl
	    # Don't append the fragment!
	    set theURL $url

	    # If a timeout is specified we set up the after event and arrange
	    # for an asynchronous socket connection.

	    set sockopts [list -async]
	    if {$cfg(-timeout) > 0} {
		set after [after $cfg(-timeout) [namespace code {
		    my reset timeout
		}]]
	    }

	    # If we are using the proxy, we must pass in the full URL that
	    # includes the server name.

	    if {[info exists phost] && ($phost ne "")} {
		set srvurl $url
		set targetAddr [list $phost $pport]
	    } else {
		set targetAddr [list $host $port]
	    }
	    # Proxy connections aren't shared among different hosts.
	    set socketinfo $host:$port

	    # Save the accept types at this point to prevent a race condition.
	    # [Bug c11a51c482]
	    set acceptTypes $http(-accept)

	    # See if we are supposed to use a previously opened channel.
	    if {$cfg(-keepalive)} {
		if {[info exists socketmap($socketinfo)]} {
		    if {[catch {fconfigure $socketmap($socketinfo)}]} {
			Log "WARNING: socket for $socketinfo was closed"
			unset socketmap($socketinfo)
		    } else {
			set sock $socketmap($socketinfo)
			Log "reusing socket $sock for $socketinfo"
			catch {fileevent $sock writable {}}
			catch {fileevent $sock readable {}}
		    }
		}
		# don't automatically close this connection socket
		set connection {}
	    }
	    if {![info exists sock]} {
		# Pass -myaddr directly to the socket command
		if {[info exists cfg(-myaddr)]} {
		    lappend sockopts -myaddr $cfg(-myaddr)
		}
		try {
		    set sock [{*}$defcmd {*}$sockopts {*}$targetAddr]
		} on error {msg} {
		    # Something went wrong while trying to establish the
		    # connection.  Clean up after events and such, but DON'T
		    # call the command callback (if available) because we're
		    # going to throw an exception from here instead.
		    my Finish "" 1
		    return -code error $msg
		}
	    }
	    Log "Using $sock for $socketinfo" \
		[expr {$cfg(-keepalive) ? "keepalive" : ""}]
	    if {$cfg(-keepalive)} {
		set socketmap($socketinfo) $sock
	    }

	    if {![info exists phost]} {
		set phost ""
	    }
	    fileevent $sock writable [namespace code [list \
		my Connect $proto $phost $srvurl]]

	    # Wait for the connection to complete.
	    if {![info exists cfg(-command)]} {
		# geturl does EVERYTHING asynchronously, so if the user calls
		# it synchronously, we just do a wait here.
		my wait

		if {![info exists status]} {
		    # If we timed out then Finish has been called and the
		    # users command callback may have cleaned up the token. If
		    # so we end up here with nothing left to do.
		    return
		} elseif {$status eq "error"} {
		    # Something went wrong while trying to establish the
		    # connection.  Clean up after events and such, but DON'T
		    # call the command callback (if available) because we're
		    # going to throw an exception from here instead.
		    return -code error [lindex $error 0]
		}
	    }
	}

	method Connect {proto phost srvurl} {
	    
	}

	destructor {
	}

	method reset {{why ""}} {
	}

	method wait {} {
	    if {![info exists status] || $status eq ""} {
		# We must wait on the true variable name, not the local
		# unqualified version.
		vwait [my varname status]
	    }

	    return [my status]
	}

	method data {} {
	    return $body
	}

	method error {} {
	    if {[info exists error]} {
		return $error
	    }
	    return ""
	}

	method status {} {
	    if {![info exists status]} {
		return "error"
	    }
	    return $status
	}

	method code {} {
	    return $httpline
	}

	method ncode {} {
	    set thecode [my code]
	    if {[regexp {[0-9]{3}} $thecode numeric_code]} {
		return $numeric_code
	    } else {
		return $thecode
	    }
	}

	method size {} {
	    return $currentsize
	}

	method meta {} {
	    return $meta
	}

	# Return the list of content-encoding transformations we need to do in
	# order.
	method ContentEncoding {} {
	    set r {}
	    if {[info exists coding]} {
		foreach c [split $coding ,] {
		    switch -exact -- $c {
			deflate { lappend r inflate }
			gzip - x-gzip { lappend r gunzip }
			compress - x-compress { lappend r decompress }
			identity {}
			default {
			    return -code error \
				"unsupported content-encoding \"$c\""
			}
		    }
		}
	    }
	    return $r
	}
    }

    # ----------------------------------------------------------------------
    # General type validators

    proc Validate(boolean) {option value} {
	if {![string is boolean -strict $value]} {
	    return -code error \
		"bad value for $option ($value), must be boolean"
	}
    }

    proc Validate(integer) {option value} {
	if {![string is integer -strict $value] || $value < 0} {
	    return -code error \
		"bad value for $option ($value), must be non-negative integer"
	}
    }

    proc Validate(channel) {option value} {
	if {$value ni [channel names]} {
	    return -code error \
		"bad value for $option ($value), must be open channel"
	}
    }

    proc Validate(encoding) {option value} {
	if {$value ni [encoding names]} {
	    return -code error \
		"bad value for $option ($value), must be encoding"
	}
    }

    proc Validate(string) {option value {regexp ""} {typedesc ""}} {
	if {$regexp ne "" && ![regexp -- $regexp $value]} {
	    if {$typedesc eq ""} {
		set typedesc "match for $regexp"
	    }
	    return -code error \
		"bad value for $option ($value), must be $typedesc"
	}
    }

    proc Validate(callback) {option value} {
	if {![string is list $value]} {
	    return -code error \
		"bad value for $option ($value), must be command prefix"
	}
    }

    proc Validate(dict) {option value} {
	if {![string is list $value] || [llength $value] & 1} {
	    return -code error \
		"bad value for $opt ($value), must be dict"
	}
    }

    proc Validate(class) {option value} {
	if {![info object isa class $value]} {
	    return -code error \
		"bad value for $opt ($value), must be class"
	}
    }

    # http::CharsetToEncoding --
    #
    #	Tries to map a given IANA charset to a tcl encoding.  If no encoding
    #	can be found, returns binary.
    #

    proc 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[-_]?js} $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"
		}
	    }
	} else {
	    # other charset, like euc-xx, utf-8,... may directly map to
	    # encoding
	    set encoding $charset
	}
	set idx [lsearch -exact $encodings $encoding]
	if {$idx >= 0} {
	    return $encoding
	} else {
	    return "binary"
	}
    }
}

# Local variables:
# mode: tcl
# indent-tabs-mode: t
# End: