Tcl Library Source Code

Check-in [ebcce7f24e]
Login

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

Overview
Comment:mime: [header get] now returns one matching value, plus parameters. Documentation updated to reflect new API.
Timelines: family | ancestors | descendants | both | pooryorick
Files: files | file ages | folders
SHA3-256: ebcce7f24e38ff15104e2ce0b5d4acca0158f28f52bf4a95a3661037391f8adf
User & Date: pooryorick 2018-10-26 17:46:12
Context
2021-05-01
10:30
mime: [header get] now returns one matching value, plus parameters. Documentation updated to reflect new API. check-in: 6a0823d6ef user: pooryorick tags: mime
2018-10-29
18:54
mime: Fix problem with content type handling. ncgi: Add object interface, update documentation. check-in: efdd4f9d20 user: pooryorick tags: pooryorick
2018-10-26
17:46
mime: [header get] now returns one matching value, plus parameters. Documentation updated to reflect new API. check-in: ebcce7f24e user: pooryorick tags: pooryorick
2018-09-28
14:20
alphabetize routines migrate tests from ncgi module. check-in: 9eb4b1a47a user: pooryorick tags: pooryorick
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modules/mime/mime.man.

107
108
109
110
111
112
113
114

115
116
117
118
119
120
121

[option -decode] converts the message body from the character set it is encoded
in.


[call [cmd ::mime::datetime] ([arg time] | [option -now]) [arg property]]

Returns the [arg property] of [arg time], which 822-style date-time value.



[para]

Available properties and their ranges are:

[list_begin definitions]







|
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

[option -decode] converts the message body from the character set it is encoded
in.


[call [cmd ::mime::datetime] ([arg time] | [option -now]) [arg property]]

Returns the [arg property] of [arg time], which is an 822-style date-time
value.


[para]

Available properties and their ranges are:

[list_begin definitions]
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

Destroys the message corresponding to [arg token] and
returns the empty string.


[para]

[option -subordinates] specifies which messages 
comprising the body should also be destroyed.  The default value is
[const dynamic], which destroys all component messages that were created by
[cmd ::mime::initialize].




[call [cmd ::mime::header] [cmd serialize] [arg value] [arg parameters]]

Serialize a header.


[call [cmd ::mime::header] [cmd get] [arg token] [opt "[arg key] | [option -names]"]]

Returns the header of a message as a multidict where each value is a list
containing the header value and a dictionary parameters for that header.


[para]

If [arg name] is provided returns a list of values for that name, without
regard to case.


[para]

If [option -names] is provided, returns a list of all header names.


[call [cmd ::mime::header] [cmd set] [arg token] [arg {name value}] [ \
    opt "[arg parameters] [opt "[option -mode] [const write] | [\
	const append] | [const delete]"]"]]



If [const append] is provided, creates a new header named [arg name] with the
value of [arg value] is added.

If [const write] is provided, deletes any existing headers whose names match
[arg key] and then creates a new header named [arg key] with the value of
[arg value].

If [const delete] is provided, deletes any existing header having a name that matches
[arg key]. 

[arg parameters] is a dictionary of parameters for the header.

Returns a list of strings containing the previous value associated with the
key.


[para]








|
|
<
|
>
>




|





|




|
|











>
>

|

|
<
|

|
<
<
<







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

Destroys the message corresponding to [arg token] and
returns the empty string.


[para]

[option -subordinates] specifies which messages comprising the body should also
be destroyed.  The default value is [const dynamic], indicates all component

messages that were created by [cmd ::mime::initialize].  [const all] indicates
all component messages. [const none] indicates that no component messages should be
destroyed.


[call [cmd ::mime::header] [cmd serialize] [arg value] [arg parameters]]

Returns the the serialization of a header.


[call [cmd ::mime::header] [cmd get] [arg token] [opt "[arg key] | [option -names]"]]

Returns the header of a message as a multidict where each value is a list
containing the header value and a dictionary of parameters for that header.


[para]

If [arg name] is provided, returns the value and parameters of the last entry
matching that name, without regard to case.


[para]

If [option -names] is provided, returns a list of all header names.


[call [cmd ::mime::header] [cmd set] [arg token] [arg {name value}] [ \
    opt "[arg parameters] [opt "[option -mode] [const write] | [\
	const append] | [const delete]"]"]]

[arg parameters] is a dictionary of parameters for the header.

If [const append] is provided, creates a new header named [arg name] with the
value of [arg value] and any provided [arg parameters].

If [const write] is provided, first deletes any existing headers matching 

[arg name].

If [const delete] is provided, deletes any existing header matching [arg name].




Returns a list of strings containing the previous value associated with the
key.


[para]

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

[def [const params]]

A list of "Content-Type" parameters

[def [const parts]]

A list of tokens for messages that should comprise a multipart body.  Only exists if
there are any such messages.

[def [const size]]

The approximate size of the unencoded content.

[list_end]


[call [cmd ::mime::serialize] [arg token] [opt [option -level]] [
    opt "[option -chan] [arg channel]"]]

Return the serialization of the message corresponding to [arg token].  If
[option -chan] is provided, write the message to [arg channel] and return the
empty string.  If [option -level] is provided, indicates the level of the part
in the message hierarchy.  The [const MIME-Version] header is only included at
level [const 0].  Yields from the current coroutine as needed to wait for input to
become available.


[call [cmd ::mime::parseaddress] [arg addresses]]

Returns a list of describing the comma-separated 822-style [arg addresses].


[para]

Each dictionary contains the following keys, whose values may be the empty
string:








|












|
|
|







|







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

[def [const params]]

A list of "Content-Type" parameters

[def [const parts]]

A list of tokens for messages that comprise a multipart body.  Only exists if
there are any such messages.

[def [const size]]

The approximate size of the unencoded content.

[list_end]


[call [cmd ::mime::serialize] [arg token] [opt [option -level]] [
    opt "[option -chan] [arg channel]"]]

Returns the serialization of the message corresponding to [arg token].  If
[option -chan] is provided, writes the serialization to [arg channel] and returns the
empty string.  [option -level], if provided, indicates the level of the part
in the message hierarchy.  The [const MIME-Version] header is only included at
level [const 0].  Yields from the current coroutine as needed to wait for input to
become available.


[call [cmd ::mime::parseaddress] [arg addresses]]

Returns a list describing the comma-separated 822-style [arg addresses].


[para]

Each dictionary contains the following keys, whose values may be the empty
string:

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
[call [cmd ::mime::reversemapencoding] [arg charset_type]]

Maps MIME charset types onto tcl encoding names.  Those
that are unknown return "".

[list_end]

[section {KNOWN BUGS}]

[list_begin definitions]
[def {Tcllib Bug #447037}]

This problem affects only people which are using Tcl and Mime on a
64-bit system. The currently recommended fix for this problem is to
upgrade to Tcl version 8.4. This version has extended 64 bit support
and the bug does not appear anymore.


[para]

The problem could have been generally solved by requiring the use of
Tcl 8.4 for this package. We decided against this solution as it would
force a large number of unaffected users to upgrade their Tcl
interpreter for no reason.


[para]

See [uri {/tktview?name=447037} {Ticket 447037}] for additional information.

[list_end]

[vset CATEGORY mime]
[include ../doctools2base/include/feedback.inc]
[manpage_end]







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




376
377
378
379
380
381
382
























383
384
385
386
[call [cmd ::mime::reversemapencoding] [arg charset_type]]

Maps MIME charset types onto tcl encoding names.  Those
that are unknown return "".

[list_end]


























[vset CATEGORY mime]
[include ../doctools2base/include/feedback.inc]
[manpage_end]

Changes to modules/mime/mime.tcl.

18
19
20
21
22
23
24

25
26
27
28
29
30
31
#
# Influenced by Borenstein's/Rose's safe-tcl (circa 1993) and Darren New's
# unpublished package of 1999.
#

# new string features and inline scan are used, requiring 8.3.
package require Tcl 8.6


package require tcl::chan::memchan
package require tcl::chan::string
package require coroutine
namespace eval ::mime {
    namespace path ::coroutine::util {*}[namespace path]
}







>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#
# Influenced by Borenstein's/Rose's safe-tcl (circa 1993) and Darren New's
# unpublished package of 1999.
#

# new string features and inline scan are used, requiring 8.3.
package require Tcl 8.6


package require tcl::chan::memchan
package require tcl::chan::string
package require coroutine
namespace eval ::mime {
    namespace path ::coroutine::util {*}[namespace path]
}
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

#
# state variables:
#
#     canonicalP: input is in its canonical form
#     encoding: transfer encoding
#     version: MIME-version
#     header: dicttionary (keys are lower-case)
#     lowerL: list of header keys, lower-case
#     mixedL: list of header keys, mixed-case
#     value: either "file", "parts", or "string"
#
#     file: input file
#     fd: cached file-descriptor, typically for root
#     root: token for top-level part, for (distant) subordinates
#     offset: number of octets from beginning of file/string
#     count: length in octets of (encoded) content







|
<
<







73
74
75
76
77
78
79
80


81
82
83
84
85
86
87

#
# state variables:
#
#     canonicalP: input is in its canonical form
#     encoding: transfer encoding
#     version: MIME-version
#     header: dictionary (keys are lower-case)


#     value: either "file", "parts", or "string"
#
#     file: input file
#     fd: cached file-descriptor, typically for root
#     root: token for top-level part, for (distant) subordinates
#     offset: number of octets from beginning of file/string
#     count: length in octets of (encoded) content
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
#       or quoted-printable).

proc ::mime::encoding token {
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(params) params

    lassign [header get $token content-type] content

    switch -glob $content {
        audio/*
            -
        image/*
            -
        video/* {







|







1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
#       or quoted-printable).

proc ::mime::encoding token {
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(params) params

    lassign [header get $token content-type]  content

    switch -glob $content {
        audio/*
            -
        image/*
            -
        video/* {
1797
1798
1799
1800
1801
1802
1803













1804
1805
1806
1807
1808
1809
1810
}


proc ::mime::header::boundary {} {
    return [uniqueID]
}















proc ::mime::header::serialize {token name value params} {
    variable notattchar_re
    set lname [string tolower $name]

    # to do: check key for conformance
    # to do: properly quote/process $value for interpolation







>
>
>
>
>
>
>
>
>
>
>
>
>







1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
}


proc ::mime::header::boundary {} {
    return [uniqueID]
}


# ::mime::dunset --
#
#   Unset all values for $key, without "normalizing" other redundant keys
proc ::mime::header::dunset {dictname key} {
    upvar 1 $dictname dict
    join [lmap {key1 val} $dict[set dict {}] {
	if {$key1 eq $key} continue
	list $key $val
    }]
}



proc ::mime::header::serialize {token name value params} {
    variable notattchar_re
    set lname [string tolower $name]

    # to do: check key for conformance
    # to do: properly quote/process $value for interpolation
1867
1868
1869
1870
1871
1872
1873
1874

1875
1876
1877
1878
1879
1880

1881
1882

1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907

1908


1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939



1940
1941


1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
    return $res
}


proc ::mime::header::exists {token name} {
    upvar 0 $token state
    set lname [string tolower $name]
    dict exists $state(header) $lname

}


# ::mime::header get --
#
#    [mime::header get] returns the header of a MIME part.

#
#    A header consists of zero or more key/value pairs. Each value is a

#    list containing one or more strings.
#
#    If [mime::header get] is invoked with the name of a specific key, then
#    a list containing the corresponding value(s) is returned; instead,
#    if -names is specified, a list of all keys is returned; otherwise, a
#    dictionary is returned. Note that when a
#    key is specified (e.g., "Subject"), the list returned usually
#    contains exactly one string; however, some keys (e.g., "Received")
#    often occur more than once in the header, accordingly the list
#    returned usually contains more than one string.
#
# Arguments:
#       token      The MIME token to parse.
#       key        Either a key or '-names'.  If it is '-names' a list
#                  of all keys is returned.
#
# Results:
#       Returns the header of a MIME part.

proc ::mime::header::get {token {key {}}} {
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(hparams) hparams
    parse $token


    array set header $state(header)


    switch $key {
	{} {
	    set result {}
	    foreach lower $state(lowerL) mixed $state(mixedL) {
		foreach value $header($lower) hparam [
		    dict get $hparams $lower] {
		    lappend result $mixed [list $value $hparam]
		}
	    }
	    set tencoding [getTransferEncoding $token]
	    if {$tencoding ne {}} {
		lappend result Content-Transfer-Encoding [list $tencoding {}]
	    }
	    return $result
	}

	-names {
	    return $state(mixedL)
	}

	default {
	    set lower [string tolower $key]

	    switch $lower {
		content-transfer-encoding {
		    return [list [getTransferEncoding $token] {}]
		}
		mime-version {
		    return [list $state(version) {}]
		}
		default {



		    if {![info exists header($lower)]} {
			error "key $key not in header"


		    }
		    return [list $header($lower) [lindex [
			dict get $hparams $lower] end]]
		}
	    }
	}
    }
}









|
>





|
>
|
<
>
|

<
<
|
<
<
<
<
<

<
<
<
<
<
<
<




<


>
|
>
>


|
<
<
<
<
<
<








|













>
>
>
|
|
>
>

<
<







1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895

1896
1897
1898


1899





1900







1901
1902
1903
1904

1905
1906
1907
1908
1909
1910
1911
1912
1913






1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943


1944
1945
1946
1947
1948
1949
1950
    return $res
}


proc ::mime::header::exists {token name} {
    upvar 0 $token state
    set lname [string tolower $name]
    expr {[dict exists $state(headerlower) $lname]
	|| [dict exists $state(headerinternallower) $lname]}
}


# ::mime::header get --
#
#    Returns the header of a message as a multidict where each value is a list
#    containing the header value and a dictionary parameters for that header.


#    If $key is provided, returns only the value and paramemters of the last
#    maching header, without regard for case. 
#


#    If -names is specified, a list of all header names is returned.





#








proc ::mime::header::get {token {key {}}} {
    # FRINK: nocheck
    upvar 0 $token state

    parse $token

    set headerlower $state(headerlower)
    set header $state(header)
    set headerinternallower $state(headerinternallower)
    set headerinternal $state(headerinternal)
    switch $key {
	{} {
	    set result [dict merge $headerinternal $header]






	    set tencoding [getTransferEncoding $token]
	    if {$tencoding ne {}} {
		lappend result Content-Transfer-Encoding [list $tencoding {}]
	    }
	    return $result
	}

	-names {
	    return [dict keys $header]
	}

	default {
	    set lower [string tolower $key]

	    switch $lower {
		content-transfer-encoding {
		    return [list [getTransferEncoding $token] {}]
		}
		mime-version {
		    return [list $state(version) {}]
		}
		default {
		    set res {}
		    if {[dict exists $headerinternallower $lower]} {
			return [dict get $headerinternallower $lower]
		    } elseif {[dict exists headerlower $lower]} {
			return [dict get $headerlower $lower]
		    } else {
			error [list {no such header} $key]
		    }


		}
	    }
	}
    }
}


2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355




2356






2357

2358

2359

2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
# Results:
#       Returns previous value associated with the specified key.

proc ::mime::header::set_ {token key value args} {
    variable internal
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(hparams) hparams
    parse $token

    set params {}
    switch [llength $args] {
	1 - 3 {
	    set args [lassign $args[set args {}] params]
	}
	0 - 2 {
	    # carry on
	}
	default {
	    error [list {wrong # args}]
	}
    }
    array set options [list -mode write]
    array set options $args











    set lower [string tolower $key]

    array set header $state(header)

    if {[set x [lsearch -exact $state(lowerL) $lower]] < 0} {

        #TODO: this code path is not tested
        if {$options(-mode) eq "delete"} {
            error "key $key not in header"
        }

        lappend state(lowerL) $lower
        lappend state(mixedL) $key

        set result {}
    } else {
        set result $header($lower)
    }
    switch $options(-mode) {
	append - write {
	    switch $lower {
		content-md5
		    -
		content-transfer-encoding







<















|
>
>
>
>
|
>
>
>
>
>
>

>
|
>
|
>

|
|

<
<
<
<
|
<
<







2331
2332
2333
2334
2335
2336
2337

2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374




2375


2376
2377
2378
2379
2380
2381
2382
# Results:
#       Returns previous value associated with the specified key.

proc ::mime::header::set_ {token key value args} {
    variable internal
    # FRINK: nocheck
    upvar 0 $token state

    parse $token

    set params {}
    switch [llength $args] {
	1 - 3 {
	    set args [lassign $args[set args {}] params]
	}
	0 - 2 {
	    # carry on
	}
	default {
	    error [list {wrong # args}]
	}
    }
    array set options [list -mode write]
    array set options {}
    dict for {opt val} $args {
	switch $opt {
	    -mode {
		set options($opt) $val
	    }
	    default {
		error [list {unknon option} $opt]
	    }
	}
    }

    set lower [string tolower $key]
    set headerlower $state(headerlower)
    set header $state(header)
    set headerinternallower $state(headerinternallower)
    set headerinternal $state(headerinternal)
    if {[catch {header get $lower} result]} {
        #TODO: this code path is not tested
        if {$options(-mode) eq {delete}} {
            error [list {key not in header} $key]
        }




	set result {}


    }
    switch $options(-mode) {
	append - write {
	    switch $lower {
		content-md5
		    -
		content-transfer-encoding
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409


2410
2411
2412

2413




2414
2415







2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430



2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443






2444
2445
2446
2447
2448
2449
2450
			}
			default {
			    #carry on
			}
		    }
		}
	    }
	    switch $options(-mode) {
		append {
		    lappend header($lower) $value


		    dict lappend hparams $lower $params
		}
		write {

		    set header($lower) [list $value]




		    dict set hparams $lower [list $params]
		}







	    }
	}
        delete {
            unset header($lower)
	    dict unset hparams $lower
            set state(lowerL) [lreplace $state(lowerL) $x $x]
            set state(mixedL) [lreplace $state(mixedL) $x $x]
        }

        default {
            error "unknown value for -mode $options(-mode)"
        }
    }

    set state(header) [array get header]




    return $result
}


proc ::mime::header::setinternal args {
    variable internal 1
    try {
	set_ {*}$args
    } finally {
	set internal 0
    }
}








# ::mime::initialize --
#
#    the public interface for initializeaux

proc ::mime::initialize args {







|
|
|
>
>
|

|
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>



|
|
|
|







|
>
>
>













>
>
>
>
>
>







2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
			}
			default {
			    #carry on
			}
		    }
		}
	    }
	    if {$options(-mode) eq {write}} {
		if {[dict exists $header $key]} {
		    dunset header $key
		}
		if {[dict exists $headerlower $lower]} {
		    dunset headerlower $lower
		}

		if {[dict exists headerinternal $key]} {
		    dunset headerinternal $key
		}
		if {[dict exists $headerinternallower $lower]} {
		    dunset headerinternallower $lower
		}

	    }
	    set newval [list $value $params]
	    if {$internal} {
		lappend headerinternal $key $newval 
		lappend headerinternallower $lower $newval 
	    } else {
		lappend header $key $newval 
		lappend headerlower $lower $newval 
	    }
	}
        delete {
            unset headerlower($lower)
	    unset headerinternallower($lower)
	    unset header($key)
	    unset headerinternal($key)
        }

        default {
            error "unknown value for -mode $options(-mode)"
        }
    }

    set state(header) $header 
    set state(headerlower) $headerlower 
    set state(headerinternal) $headerinternal 
    set state(headerinternallower) $headerinternallower

    return $result
}


proc ::mime::header::setinternal args {
    variable internal 1
    try {
	set_ {*}$args
    } finally {
	set internal 0
    }
}

proc ::mime::header::dset {name key val} {
    if {[dict exists $name]} {
	set name [lsearch
    }
}


# ::mime::initialize --
#
#    the public interface for initializeaux

proc ::mime::initialize args {
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519

2520
2521
2522
2523
2524
2525
2526
2527
2528
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(canonicalP) canonicalP
    upvar 0 state(params) params

    set params {}

    set state(hparams) {}
    set state(encoding) {}
    set state(version) 1.0

    set state(bodyparsed) 0
    set canonicalP 0
    set state(header) {}
    set state(headerparsed) 0

    set state(lowerL) {}
    set state(mixedL) {}

    set state(cid) 0
    set state(closechan) 1
    set state(root) $token

    set userparams 0








<






|
>
|
|







2533
2534
2535
2536
2537
2538
2539

2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
    # FRINK: nocheck
    upvar 0 $token state
    upvar 0 state(canonicalP) canonicalP
    upvar 0 state(params) params

    set params {}


    set state(encoding) {}
    set state(version) 1.0

    set state(bodyparsed) 0
    set canonicalP 0
    set state(header) {}
    set state(headerinternal) {}
    set state(headerinternallower) {}
    set state(headerlower) {}
    set state(headerparsed) 0

    set state(cid) 0
    set state(closechan) 1
    set state(root) $token

    set userparams 0

2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
		if {$lname in {content-md5 mime-version}} {
		    error [list {don't go there...}]
		}
		header::setinternal $token $name $hvalue
	    }
	}

	lassign [header get $token content-type] content

        switch $state(value) {
            file {
                set state(offset) 0
            }

            parts {







|







2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
		if {$lname in {content-md5 mime-version}} {
		    error [list {don't go there...}]
		}
		header::setinternal $token $name $hvalue
	    }
	}

	lassign [header get $token content-type] content dummy

        switch $state(value) {
            file {
                set state(offset) 0
            }

            parts {
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
                    mime::initializeaux $child -root $state(root) -string $strng
                } else {
                    mime::initializeaux $child -root $state(root) -lineslist [
                        lrange $state(lines) $state(lines.current) end]
                }
            }
        }

        return
    }

    set state(value) parts

    dict update params boundary boundary {}
    if {![info exists boundary]} {







<







2865
2866
2867
2868
2869
2870
2871

2872
2873
2874
2875
2876
2877
2878
                    mime::initializeaux $child -root $state(root) -string $strng
                } else {
                    mime::initializeaux $child -root $state(root) -lineslist [
                        lrange $state(lines) $state(lines.current) end]
                }
            }
        }

        return
    }

    set state(value) parts

    dict update params boundary boundary {}
    if {![info exists boundary]} {
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
    upvar 0 state(fd) fd
    parsepart $token

    set result {}
    if {!$level} {
	puts $channel [header serialize $token MIME-Version $state(version) {}]
    }
    foreach {mixed value} [header get $token] {
	puts $channel [header serialize $token $mixed {*}$value]
    }

    set converter {}
    set encoding {}
    if {$state(value) ne "parts"} {
        if {$state(canonicalP)} {
            if {[set encoding $state(encoding)] eq {}} {







|
|







3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
    upvar 0 state(fd) fd
    parsepart $token

    set result {}
    if {!$level} {
	puts $channel [header serialize $token MIME-Version $state(version) {}]
    }
    foreach {name value} [header get $token] {
	puts $channel [header serialize $token $name {*}$value]
    }

    set converter {}
    set encoding {}
    if {$state(value) ne "parts"} {
        if {$state(canonicalP)} {
            if {[set encoding $state(encoding)] eq {}} {

Changes to modules/mime/mime.test.

752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
    fconfigure $ofh -translation binary
    mime::serialize $attachment -chan $ofh -level 1
    close $ofh

    set data [viewFile $out]
    file delete $in $out
    set data
}} {Content-Disposition: attachment
	; filename=a0036.dss
Content-Type: application/octet-stream
	; name=a0036.dss
Content-Transfer-Encoding: base64

BGRzcwEAAQABAAAAYQAAAAAAAAAAAAAAAAAAACQAAAD+//7/+/8wNzA2MTYwODE1MjQwNzA2
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ}

# -------------------------------------------------------------------------








|
|
|
|







752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
    fconfigure $ofh -translation binary
    mime::serialize $attachment -chan $ofh -level 1
    close $ofh

    set data [viewFile $out]
    file delete $in $out
    set data
}} {Content-Type: application/octet-stream
	; name=a0036.dss
Content-Disposition: attachment
	; filename=a0036.dss
Content-Transfer-Encoding: base64

BGRzcwEAAQABAAAAYQAAAAAAAAAAAAAAAAAAACQAAAD+//7/+/8wNzA2MTYwODE1MjQwNzA2
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ}

# -------------------------------------------------------------------------

777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
Content-Transfer-Encoding: base64

T3JpZ2luYWwtUmVjaXBpZW50OiA8L2ZheD1ibHViYkBndW1taS5ib290PgpBY3Rpb246IGZhaWxl
ZApEaWFnbm9zdGljLUNvZGU6IHNtdHA7IDU1MCAjNS4xLjAgQWRkcmVzcyByZWplY3RlZC4KUmVt
b3RlLU1UQTogNTMuMjQuMjgyLjE1MA==
}]
    set parts [mime::property $token parts]
    lassign [mime::header get [lindex $parts end] Remote-MTA] result
    return $result
}} 53.24.282.150

# -------------------------------------------------------------------------


test mime-13.0 {cleanly {
    issue a16b1095974e071d
}} {







|

|







777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
Content-Transfer-Encoding: base64

T3JpZ2luYWwtUmVjaXBpZW50OiA8L2ZheD1ibHViYkBndW1taS5ib290PgpBY3Rpb246IGZhaWxl
ZApEaWFnbm9zdGljLUNvZGU6IHNtdHA7IDU1MCAjNS4xLjAgQWRkcmVzcyByZWplY3RlZC4KUmVt
b3RlLU1UQTogNTMuMjQuMjgyLjE1MA==
}]
    set parts [mime::property $token parts]
    set result [mime::header get [lindex $parts end] Remote-MTA]
    return $result
}} {53.24.282.150 {}}

# -------------------------------------------------------------------------


test mime-13.0 {cleanly {
    issue a16b1095974e071d
}} {
894
895
896
897
898
899
900

901
902
903
904
905
906
907
dawg one" \
[list Content-ID {<> {}} Content-Type {text/plain {}} \
    Content-Disposition [list attachment [list param1 $char]]]

]




test mime-13.1 {header parsing} {cleanly {
    set mime [mime::initialize -string {Content-Type: text/html}]
    mime::header get $mime Content-Type
}} {text/html {}}









>







894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
dawg one" \
[list Content-ID {<> {}} Content-Type {text/plain {}} \
    Content-Disposition [list attachment [list param1 $char]]]

]


# -------------------------------------------------------------------------

test mime-13.1 {header parsing} {cleanly {
    set mime [mime::initialize -string {Content-Type: text/html}]
    mime::header get $mime Content-Type
}} {text/html {}}


Changes to modules/ncgi/ncgi.man.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[package ncgi] package to query these values, set and get cookies, and
encode and decode www-url-encoded values.

[para]

In the simplest case, a CGI script first calls [cmd ::ncgi::parse] and
then calls [cmd ::ncgi::value] to get different form values.  If a CGI
value is repeated, you should use [cmd ::ncgi::valueList] to get back
the complete list of values.

[para]

An alternative to [cmd ::ncgi::parse] is [cmd ::ncgi::input], which
has semantics similar to Don Libes' [cmd cgi_input] procedure.








|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[package ncgi] package to query these values, set and get cookies, and
encode and decode www-url-encoded values.

[para]

In the simplest case, a CGI script first calls [cmd ::ncgi::parse] and
then calls [cmd ::ncgi::value] to get different form values.  If a CGI
value is repeated, you should use [cmd ::ncgi::all] to get back
the complete list of values.

[para]

An alternative to [cmd ::ncgi::parse] is [cmd ::ncgi::input], which
has semantics similar to Don Libes' [cmd cgi_input] procedure.

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
[call [cmd ::ncgi::header] [opt [arg type]] [arg args]]

Output the CGI header to standard output.  This emits a Content-Type:
header and additional headers based on [arg args], which is a list of
header names and header values. The [arg type] defaults to
"text/html".

[call [cmd ::ncgi::import] [arg cginame] [opt [arg tclname]]]

This creates a variable in the current scope with the value of the CGI
variable [arg cginame].  The name of the variable is [arg tclname], or
[arg cginame] if [arg tclname] is empty (default).

[call [cmd ::ncgi::importAll] [arg args]]

This imports several CGI variables as Tcl variables.  If [arg args] is
empty, then every CGI value is imported.  Otherwise each CGI variable
listed in [arg args] is imported.

[call [cmd ::ncgi::importFile] [arg cmd] [arg cginame] [opt [arg filename]]]

This provides information about an uploaded file from a form input
field of type [const file] with name [arg cginame].  [arg cmd] can be
one of [option -server] [option -client], [option -type] or
[option -data].







<
<
<
<
<
<
<
<
<
<
<







77
78
79
80
81
82
83











84
85
86
87
88
89
90
[call [cmd ::ncgi::header] [opt [arg type]] [arg args]]

Output the CGI header to standard output.  This emits a Content-Type:
header and additional headers based on [arg args], which is a list of
header names and header values. The [arg type] defaults to
"text/html".













[call [cmd ::ncgi::importFile] [arg cmd] [arg cginame] [opt [arg filename]]]

This provides information about an uploaded file from a form input
field of type [const file] with name [arg cginame].  [arg cmd] can be
one of [option -server] [option -client], [option -type] or
[option -data].
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

writes the file contents to a local temporary file (or [arg filename]
if supplied) and returns the name of the file. The caller is
responsible for deleting this file after use.

[list_end]











[call [cmd ::ncgi::input] [opt [arg fakeinput]] [opt [arg fakecookie]]]


This reads and decodes the CGI values from the environment.  It
restricts repeated form values to have a trailing "List" in their

name.  The CGI values are obtained later with the [cmd ::ncgi::value]

procedure.








[call [cmd ::ncgi::multipart] [arg {type query}]]

This procedure parses a multipart/form-data [arg query].  This is used
by [cmd ::ncgi::nvlist] and not normally called directly.  It returns
an alternating list of names and structured values.  Each structure
value is in turn a list of two elements.  The first element is
meta-data from the multipart/form-data structure.  The second element
is the form value.  If you use [cmd ::ncgi::value] you just get the
form value.  If you use [cmd ::ncgi::valueList] you get the structured
value with meta data and the value.

[para]

The [arg type] is the whole Content-Type, including the parameters
like [arg boundary].  This returns a list of names and values that
describe the multipart data.  The values are a nested list structure
that has some descriptive information first, and the actual form value
second.  The descriptive information is list of header names and
values that describe the content.


[call [cmd ::ncgi::nvlist]]

This returns all the query data as a name, value list.  In the case of
multipart/form-data, the values are structured as described in


[cmd ::ncgi::multipart].

[call [cmd ::ncgi::names]]

This returns all names found in the query data, as a list.

[cmd ::ncgi::multipart].


[call [cmd ::ncgi::parse]]

This reads and decodes the CGI values from the environment.  The CGI
values are obtained later with the [cmd ::ncgi::value] procedure.  IF
a CGI value is repeated, then you should use [cmd ::ncgi::valueList]
to get the complete list of values.


[call [cmd ::ncgi::parseMimeValue] [arg value]]

Decodes the Content-Type and other MIME headers that have the
form of "primary value; param=val; p2=v2" It returns a list, where the
first element is the primary value, and the second element is a list
of parameter names and values.


[call [cmd ::ncgi::post]]

Returns the parsed post data as a multidict.


[call [cmd ::ncgi::poststring]]

Returns the raw post data.

[call [cmd ::ncgi::query]]

Returns the parsed query data as a multidict.


[call [cmd ::ncgi::querystring]]

Returns the raw query data.


[call [cmd ::ncgi::redirect] [arg url]]

Generate a response that causes a 302 redirect by the Web server.  The







>
>
>
>
>
>
>
>
>
>
|

>
|
|
>
|
>
|
>
>
>
>
>
>
>









|












<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

|


<
<
<
<
|

|


|







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

writes the file contents to a local temporary file (or [arg filename]
if supplied) and returns the name of the file. The caller is
responsible for deleting this file after use.

[list_end]


[call [cmd ::ncgi::all] [arg key] [opt [arg default]]]

Like [cmd ::ncgi::value], but this always returns a list of values
(even if there is only one value).  In the case of
multipart/form-data, this procedure returns a list of two elements.
The first element is meta-data in the form of a parameter, value list.
The second element is the form value.


[call [cmd ::ncgi::body]]

Returns the raw body.


[call [cmd ::ncgi::form]]

Returns the parsed form data as a multidict.


[call [cmd ::ncgi::get] [arg key] [opt [arg default]]]

Given [arg key], returns the value corresponding argument. If there is no such
key , the [arg default] value, which by default is the empty string, is
returned instead.


[call [cmd ::ncgi::multipart] [arg {type query}]]

This procedure parses a multipart/form-data [arg query].  This is used
by [cmd ::ncgi::nvlist] and not normally called directly.  It returns
an alternating list of names and structured values.  Each structure
value is in turn a list of two elements.  The first element is
meta-data from the multipart/form-data structure.  The second element
is the form value.  If you use [cmd ::ncgi::value] you just get the
form value.  If you use [cmd ::ncgi::all] you get the structured
value with meta data and the value.

[para]

The [arg type] is the whole Content-Type, including the parameters
like [arg boundary].  This returns a list of names and values that
describe the multipart data.  The values are a nested list structure
that has some descriptive information first, and the actual form value
second.  The descriptive information is list of header names and
values that describe the content.

































[call [cmd ::ncgi::query] [cmd parse]]

Returns the parsed query data as a multidict.






[call [cmd ::ncgi::query] [cmd set] [arg {key value}]]

Set a query value.


[call [cmd ::ncgi::query] [cmd string]]

Returns the raw query data.


[call [cmd ::ncgi::redirect] [arg url]]

Generate a response that causes a 302 redirect by the Web server.  The
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
[def "[option -name] [arg name]"]
[def "[option -value] [arg value]"]
[def "[option -expires] [arg date]"]
[def "[option -path] [arg {path restriction}]"]
[def "[option -domain] [arg {domain restriction}]"]
[list_end]


[call [cmd ::ncgi::setDefaultValue] [arg {key defvalue}]]

Set a CGI value if it does not already exists.  This affects future
calls to [cmd ::ncgi::value] (but not future calls to

[cmd ::ncgi::nvlist]).  If the CGI value already is present, then this
procedure has no side effects.


[call [cmd ::ncgi::setDefaultValueList] [arg {key defvaluelist}]]

Like [cmd ::ncgi::setDefaultValue] except that the value already has
list structure to represent multiple checkboxes or a multi-selection.


[call [cmd ::ncgi::setValue] [arg {key value}]]

Set a CGI value, overriding whatever was present in the CGI
environment already.  This affects future calls to [cmd ::ncgi::value]
(but not future calls to [cmd ::ncgi::nvlist]).


[call [cmd ::ncgi::setValueList] [arg {key valuelist}]]

Like [cmd ::ncgi::setValue] except that the value already has list
structure to represent multiple checkboxes or a multi-selection.


[call [cmd ::ncgi::type]]

Returns the Content-Type of the current CGI values.


[call [cmd ::ncgi::urlStub] [opt [arg url]]]

Returns the current URL, but without the protocol, server, and port.
If [arg url] is specified, then it defines the URL for the current
session.  That value will be returned by future calls to

[cmd ::ncgi::urlStub]


[call [cmd ::ncgi::value] [arg key] [opt [arg default]]]

Return the CGI value identified by [arg key].  If the CGI value is not
present, then the [arg default] value is returned instead. This value
defaults to the empty string.

[para]

If the form value [arg key] is repeated, then there are two cases: if
[cmd ::ncgi::parse] was called, then [cmd ::ncgi::value] only returns
the first value associated with [arg key].  If [cmd ::ncgi::input] was
called, then [cmd ::ncgi::value] returns a Tcl list value and

[arg key] must end in "List" (e.g., "skuList").  In the case of
multipart/form-data, this procedure just returns the value of the form
element.  If you want the meta-data associated with each form value,
then use [cmd ::ncgi::valueList].


[call [cmd ::ncgi::valueList] [arg key] [opt [arg default]]]

Like [cmd ::ncgi::value], but this always returns a list of values
(even if there is only one value).  In the case of
multipart/form-data, this procedure returns a list of two elements.
The first element is meta-data in the form of a parameter, value list.
The second element is the form value.

[list_end]

[section EXAMPLES]

Uploading a file
[example {
HTML:







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<















<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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
[def "[option -name] [arg name]"]
[def "[option -value] [arg value]"]
[def "[option -expires] [arg date]"]
[def "[option -path] [arg {path restriction}]"]
[def "[option -domain] [arg {domain restriction}]"]
[list_end]






























[call [cmd ::ncgi::type]]

Returns the Content-Type of the current CGI values.


[call [cmd ::ncgi::urlStub] [opt [arg url]]]

Returns the current URL, but without the protocol, server, and port.
If [arg url] is specified, then it defines the URL for the current
session.  That value will be returned by future calls to

[cmd ::ncgi::urlStub]





























[list_end]

[section EXAMPLES]

Uploading a file
[example {
HTML:

Changes to modules/ncgi/ncgi.tcl.

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
# Please note that Don Libes' has a "cgi.tcl" that implements version 1.0
# of the cgi package.  That implementation provides a bunch of cgi_ procedures
# (it doesn't use the ::cgi:: namespace) and has a wealth of procedures for
# generating HTML.  In contrast, the package provided here is primarly
# concerned with processing input to CGI programs.  I have tried to mirror his
# API's where possible.  So, ncgi::input is equivalent to cgi_input, and so
# on.  There are also some different APIs for accessing values (ncgi::list,
# ncgi::parse and ncgi::value come to mind)

# Note, I use the term "query data" to refer to the data that is passed in
# to a CGI program.  Typically this comes from a Form in an HTML browser.
# The query data is composed of names and values, and the names can be
# repeated.  The names and values are encoded, and this module takes care
# of decoding them.

# We use newer string routines
package require Tcl 8.4
package require fileutil ; # Required by importFile.

package require uri

package provide ncgi 1.5.0

namespace eval ::ncgi {

    # "query" holds the raw query (i.e., form) data
    # This is treated as a cache, too, so you can call ncgi::query more than
    # once

    variable query

    # This is the content-type which affects how the query is parsed

    variable contenttype

    if {[info exists env(CONTENT_LENGTH)] && [
	string length $env(CONTENT_LENGTH)] != 0} {
	variable content_length [expr {$env(CONTENT_LENGTH)}]
    }





    if {[info exists ::env(REQUEST_METHOD)]} {
	variable method [string tolower $::env(REQUEST_METHOD)]
    }

    # value is an array of parsed query data.  Each array element is a list
    # of values, and the array index is the form element name.
    # See the differences among ncgi::parse, ncgi::input, ncgi::value
    # and ncgi::valuelist for the various approaches to handling these values.

    variable value

    # This lists the names that appear in the query data

    variable varlist

    # This holds the URL coresponding to the current request
    # This does not include the server name.

    variable urlStub

    # This flags compatibility with Don Libes cgi.tcl when dealing with
    # form values that appear more than once.  This bit gets flipped when







|










>












<
<
<






>
>
>
>




<
<
<
<
<
<
<
<
<
<
<







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
# Please note that Don Libes' has a "cgi.tcl" that implements version 1.0
# of the cgi package.  That implementation provides a bunch of cgi_ procedures
# (it doesn't use the ::cgi:: namespace) and has a wealth of procedures for
# generating HTML.  In contrast, the package provided here is primarly
# concerned with processing input to CGI programs.  I have tried to mirror his
# API's where possible.  So, ncgi::input is equivalent to cgi_input, and so
# on.  There are also some different APIs for accessing values (ncgi::list,
# and ncgi::get come to mind)

# Note, I use the term "query data" to refer to the data that is passed in
# to a CGI program.  Typically this comes from a Form in an HTML browser.
# The query data is composed of names and values, and the names can be
# repeated.  The names and values are encoded, and this module takes care
# of decoding them.

# We use newer string routines
package require Tcl 8.4
package require fileutil ; # Required by importFile.
package require mime
package require uri

package provide ncgi 1.5.0

namespace eval ::ncgi {

    # "query" holds the raw query (i.e., form) data
    # This is treated as a cache, too, so you can call ncgi::query more than
    # once

    variable query





    if {[info exists env(CONTENT_LENGTH)] && [
	string length $env(CONTENT_LENGTH)] != 0} {
	variable content_length [expr {$env(CONTENT_LENGTH)}]
    }


    # This is the content-type which affects how the query is parsed
    variable contenttype

    if {[info exists ::env(REQUEST_METHOD)]} {
	variable method [string tolower $::env(REQUEST_METHOD)]
    }












    # This holds the URL coresponding to the current request
    # This does not include the server name.

    variable urlStub

    # This flags compatibility with Don Libes cgi.tcl when dealing with
    # form values that appear more than once.  This bit gets flipped when
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

    variable  _tmpfiles
    array set _tmpfiles {}

    # I don't like importing, but this makes everything show up in 
    # pkgIndex.tcl

    namespace export method reset urlStub query type decode encode
    namespace export nvlist parse input value valueList names
    namespace export setValue setValueList setDefaultValue setDefaultValueList
    namespace export empty import importAll importFile redirect header
    namespace export parseMimeValue multipart cookie setCookie
}






proc ::ncgi::post {} {


    set type [type]


    switch -glob $type {

	{} -
	text/xml* -
	application/x-www-form-urlencoded* -
	application/x-www-urlencoded* {
	    return [urlencoded [poststring]]
	}










	multipart/* {

	    return [multipart $type [poststring]]






	}
	default {
	    return -code error "Unknown Content-Type: $type"



	}
    }




}






proc ::ncgi::poststring {} {
    global env
    variable content_length
    variable method
    variable post
    if {![info exists post]} {
	if {([info exists method] && $method eq {post})
	    && [info exist content_length]
	} {
	    fconfigure stdin -translation binary -encoding binary
	    set post [read stdin $env(CONTENT_LENGTH)]
	} else {
	    set post {}
	}
    }

    return $post
}

# ::ncgi::reset
#
#	This resets the state of the CGI input processor.  This is primarily
#	used for tests, although it is also designed so that TclHttpd can
#	call this with the current query data
#	so the ncgi package can be shared among TclHttpd and CGI scripts.
#
#	DO NOT CALL this in a standard cgi environment if you have not
#	yet processed the query data, which will not be used after a
#	call to ncgi::reset is made.  Instead, just call ncgi::parse



#
# Arguments:
#	newquery	The query data to be used instead of external CGI.
#	newtype		The raw content type.
#
# Side Effects:
#	Resets the cached query data and wipes any environment variables
#	associated with CGI inputs (like QUERY_STRING)

proc ::ncgi::reset args {
    global env
    variable _tmpfiles
    variable query
    variable contenttype
    variable cookieOutput
    variable post

    # array unset _tmpfiles -- Not a Tcl 8.2 idiom
    unset _tmpfiles ; array set _tmpfiles {}


    set cookieOutput {}



    if {[llength $args] == 0} {

	# We use and test args here so we can detect the
	# difference between empty query data and a full reset.

	if {[info exists query]} {
	    unset query
	}
	if {[info exists contenttype]} {
	    unset contenttype
	}
	if {[info exists post]} {
	    unset post
	}
    } else {
	set contenttype {}
	set post {}
	set query {}
	dict for {opt val} $args {
	    switch $opt {
		contenttype - post - query {
		    set $opt $val
		}
		default {
		    error [list {unknown reset option} $opt]
		}
	    }
	}
    }
}

# ::ncgi::urlStub
#
#	Set or return the URL associated with the current page.
#	This is for use by TclHttpd to override the default value
#	that otherwise comes from the CGI environment

#
# Arguments:

#	url	(option) The url of the page, not counting the server name.
#		If not specified, the current urlStub is returned




#
# Side Effects:
#	May affects future calls to ncgi::urlStub


proc ::ncgi::urlStub {{url {}}} {
    global   env
    variable urlStub
    if {[string length $url]} {
	set urlStub $url

	return ""
    } elseif {[info exists urlStub]} {
	return $urlStub
    } elseif {[info exists env(SCRIPT_NAME)]} {
	set urlStub $env(SCRIPT_NAME)
	return $urlStub
    } else {
	return ""
    }
}









# ::ncgi::type
#
#	This returns the content type of the query data.
#
# Arguments:
#	none
#
# Results:
#	The content type of the query data.

proc ::ncgi::type {} {
    global env
    variable contenttype

    if {![info exists contenttype]} {
	if {[info exists env(CONTENT_TYPE)]} {
	    set contenttype $env(CONTENT_TYPE)
	} else {
	    return ""
	}
    }
    return $contenttype

}

# ::ncgi::decode
#
#	This decodes data in www-url-encoded format.
#
# Arguments:







|
|
|

|
|
>

>
>
>
|
|
>
>
|
>
>
|
>
|
<
<
<
<
|
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
|
|
<
>
>
>


>
>
>
>
|
|
|
>
>
>
>
|



|
|



|
|

|


>
|


<
|
<
<
<
|

<
<
<
>
>
>


<
|

|
<
<
|
<
<
<
<
<
<
<

<
<
|
>
|
>
>
>
|
|
<
<
|
<
<

<
<
|
<
|
|
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
|

|
<
<
>


>
|
<
>
>
>
>


<
>

|
<
|
<
|
>
|
<
<
|
|
<
<
<
|
|
>
>
>
>
|
>
>
>
|
<
<
<
<
<
<
<
<
<
|
<
|
<
|
<
|
<
|
<
|
<
<
>







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

    variable  _tmpfiles
    array set _tmpfiles {}

    # I don't like importing, but this makes everything show up in 
    # pkgIndex.tcl

    namespace export all body get merge method reset urlStub query type decode encode
    namespace export input
    namespace export setDefaultValue setDefaultValueList
    namespace export empty import importAll importFile redirect header
    namespace export multipart cookie setCookie

    namespace ensemble create

    namespace ensemble create -command [namespace current]::form -map {
	exists form_exists
	get form_get
    }

    namespace ensemble create -command [namespace current]::query -map {
	parse query_parse
	set query_set
	string query_string
    }

}






# ::ncgi::all
#
#	Return all the values of a named query element as a list, or
#	the empty list if it was not not specified.  This always returns
#	lists - if you do not want the extra level of listification, use
#	ncgi::get instead.
#
# Arguments:
#	key	The name of the query element
#
# Results:
#	The first value of the named element, or ""

proc ::ncgi::all key {
    variable query
    variable form
    query parse
    if {[form exists]} {
	form get
    }
    set result {}

    foreach {qkey val} $query {
	if {$qkey eq $key} {
	    lappend result $val
	}
    }
    if {[form exists]} {
	foreach {fkey val} $form {
	    if {$fkey eq $key} {
		lappend result [lindex $val 0]
	    }
	}
    }
    return $result
}


proc ::ncgi::body {} {
    global env
    variable content_length
    variable method
    variable body
    if {![info exists body]} {
	if {([info exists method] && $method eq {post})
	    && [info exist content_length]
	} {
	    chan configure stdin -translation binary
	    set body [read stdin $env(CONTENT_LENGTH)]
	} else {
	    set body {}
	}
    }
    chan configure stdout -translation binary
    return $body
}






# ::ncgi::cookie
#



#	Return a *list* of cookie values, if present, else ""
#	It is possible for multiple cookies with the same key
#	to be present, so we return a list.
#
# Arguments:

#	cookie	The name of the cookie (the key)
#
# Results:


#	A list of values for the cookie










proc ::ncgi::cookie cookie {
    global env
    set result {} 
    if {[info exists env(HTTP_COOKIE)]} {
	foreach pair [split $env(HTTP_COOKIE) \;] {
	    foreach {key value} [split [string trim $pair] =] { break ;# lassign }
	    if {[string compare $cookie $key] == 0} {
		lappend result $value


	    }


	}


    }

    return $result
}

















# ::ncgi::setCookie
#
#	Set a return cookie.  You must call this before you call


#	ncgi::header or ncgi::redirect
#
# Arguments:
#	args	Name value pairs, where the names are:
#		-name	Cookie name

#		-value	Cookie value
#		-path	Path restriction
#		-domain	domain restriction
#		-expires	Time restriction
#
# Side Effects:

#	Formats and stores the Set-Cookie header for the reply.

proc ::ncgi::setCookie {args} {

    variable cookieOutput

    array set opt $args
    set line "$opt(-name)=$opt(-value) ;"
    foreach extra {path domain} {


	if {[info exists opt(-$extra)]} {
	    append line " $extra=$opt(-$extra) ;"



	}
    }
    if {[info exists opt(-expires)]} {
	switch -glob -- $opt(-expires) {
	    *GMT {
		set expires $opt(-expires)
	    }
	    default {
		set expires [clock format [clock scan $opt(-expires)] \
			-format "%A, %d-%b-%Y %H:%M:%S GMT" -gmt 1]
	    }









	}

	append line " expires=$expires ;"

    }

    if {[info exists opt(-secure)]} {

	append line " secure "

    }


    lappend cookieOutput $line
}

# ::ncgi::decode
#
#	This decodes data in www-url-encoded format.
#
# Arguments:
301
302
303
304
305
306
307

308
309
310
311
312
313
314
    regsub -all -- {%([CDcd][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])}                     \
	$str {[encoding convertfrom utf-8 [DecodeHex \1\2]]} str
    regsub -all -- {%([0-7][A-Fa-f0-9])} $str {\\u00\1} str

    # process \u unicode mapped chars
    return [subst -novar $str]
}


# ::ncgi::encode
#
#	This encodes data in www-url-encoded format.
#
# Arguments:
#	A string







>







279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
    regsub -all -- {%([CDcd][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])}                     \
	$str {[encoding convertfrom utf-8 [DecodeHex \1\2]]} str
    regsub -all -- {%([0-7][A-Fa-f0-9])} $str {\\u00\1} str

    # process \u unicode mapped chars
    return [subst -novar $str]
}


# ::ncgi::encode
#
#	This encodes data in www-url-encoded format.
#
# Arguments:
#	A string
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
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782

    regsub -all -- \[^a-zA-Z0-9\] $string {$map(&)} string
    # This quotes cases like $map([) or $map($) => $map(\[) ...
    regsub -all -- {[][{})\\]\)} $string {\\&} string
    return [subst -nocommand $string]
}

# ::ncgi::names
#










#	This parses the query data and returns a list of the names found therein.
#






# 	Note: If you use ncgi::setValue or ncgi::setDefaultValue, this
#	names procedure doesn't see the effect of that.
#









# Arguments:


#	none



#
# Results:

#	A list of names












proc ::ncgi::names {} {
    array set names {}







    foreach {name val} [nvlist] {
        if {![string equal $name "anonymous"]} {


            set names($name) 1

        }






    }

    return [array names names]








}




# ::ncgi::nvlist
#
#	This parses the query data and returns it as a name, value list
#
# 	Note: If you use ncgi::setValue or ncgi::setDefaultValue, this
#	nvlist procedure doesn't see the effect of that.
#
# Arguments:





#	none














#









# Results:
#	An alternating list of names and values







proc ::ncgi::nvlist {} {

    set query [query]

    set post [post]

    return [dict merge $query $post]
    switch -glob -- [type] {







	{} -



	text/xml* -




	application/x-www-form-urlencoded* -
	application/x-www-urlencoded*  -









	multipart/* {







	}
	default {
	    return -code error "Unknown Content-Type: $type"
	}
    }
}

# ::ncgi::parse
#


#	The parses the query data and stores it into an array for later retrieval.
#	You should use the ncgi::value or ncgi::valueList procedures to get those
#	values, or you are allowed to access the ncgi::value array directly.








#
#	Note - all values have a level of list structure associated with them
#	to allow for multiple values for a given form element (e.g., a checkbox)
#
# Arguments:
#	none
#
# Results:
#	A list of names of the query values







proc ::ncgi::parse {} {
    variable value
    variable listRestrict 0
    variable varlist {}
    if {[info exists value]} {

	unset value
    }

    foreach {name val} [nvlist] {

	if {![info exists value($name)]} {




	    lappend varlist $name
	}
	lappend value($name) $val
    }
    return $varlist
} 



# ::ncgi::input
#

#	Like ncgi::parse, but with Don Libes cgi.tcl semantics.
#	Form elements must have a trailing "List" in their name to be
#	listified, otherwise this raises errors if an element appears twice.
#
# Arguments:
#	fakeinput	See ncgi::reset
#	fakecookie	The raw cookie string to use when testing.

#
# Results:
#	The list of element names in the form

proc ::ncgi::input {{fakeinput {}} {fakecookie {}}} {


    variable value
    variable varlist {}
    variable listRestrict 1
    if {[info exists value]} {
	unset value
    }

    if {[string length $fakeinput]} {
	ncgi::reset query $fakeinput
    }

    foreach {name val} [nvlist] {

	set exists [info exists value($name)]

	if {!$exists} {

	    lappend varlist $name





	}
	if {[string match "*List" $name]} {
	    # Accumulate a list of values for this name
	    lappend value($name) $val
	} elseif {$exists} {
	    error "Multiple definitions of $name encountered in input.\
	    If you're trying to do this intentionally (such as with select),\
	    the variable must have a \"List\" suffix."
	} else {
	    # Capture value with no list structure
	    set value($name) $val
	}
    }
    return $varlist

} 



# ::ncgi::query
#
#	Parses the query part of the URI
#
proc ::ncgi::query {} {
    urlencoded [querystring]
}


# ::ncgi::urlencoded
#
#	Parses $data as a url-encoded query and returns a multidict containing
#	the query.
#
proc ::ncgi::urlencoded query {
    set result {}

    # Any whitespace at the beginning or end of urlencoded data is not
    # considered to be part of that data, so we trim it off.  One special
    # case in which post data is preceded by a \n occurs when posting
    # with HTTPS in Netscape.
    foreach x [split [string trim $query] &] {
	# Turns out you might not get an = sign,
	# especially with <isindex> forms.

	set pos [string first = $x]
	set len [string length $x]

	if { $pos>=0 } {
	    if { $pos == 0 } { # if the = is at the beginning ...
		if { $len>1 } { 
		    # ... and there is something to the right ...
		    set varname anonymous



		    set val [string range $x 1 end]
		} else { 
		    # ... otherwise, all we have is an =
		    set varname anonymous


		    set val ""
		}
	    } elseif { $pos==[expr {$len-1}] } { 
		# if the = is at the end ...
		set varname [string range $x 0 [expr {$pos-1}]]
		set val ""
	    } else {
		set varname [string range $x 0 [expr {$pos-1}]]
		set val [string range $x [expr {$pos+1}] end]
	    }
	} else { # no = was found ...
	    set varname anonymous
	    set val $x
	}		
	lappend result [decode $varname] [decode $val]
    }
    return $result
}


# ::ncgi::querystring
#
#	This reads the query data from the appropriate location, which depends
#	on if it is a POST or GET request.
#
# Arguments:
#	none
#
# Results:
#	The raw query data.

proc ::ncgi::querystring {} {
    global env
    variable query

    if {[info exists query]} {
	# This ensures you can call ncgi::query more than once,
	# and that you can use it with ncgi::reset
	return $query
    }

    set query {} 
    if {[info exists env(QUERY_STRING)]} {
	set query $env(QUERY_STRING)
    }
    return $query
}


# ::ncgi::value
#
#	Return the value of a named query element, or the empty string if
#	it was not not specified.  This only returns the first value of
#	associated with the name.  If you want them all (like all values
#	of a checkbox), use ncgi::valueList
#
# Arguments:
#	key	The name of the query element
#	default	The value to return if the value is not present
#
# Results:
#	The first value of the named element, or the default

proc ::ncgi::value {key {default {}}} {
    variable value
    variable listRestrict
    variable contenttype
    if {[info exists value($key)]} {
	if {$listRestrict} {

	    # ::ncgi::input was called, and it already figured out if the
	    # user wants list structure or not.

	    set val $value($key)
	} else {

	    # Undo the level of list structure done by ncgi::parse

	    set val [lindex $value($key) 0]
	}
	if {[string match multipart/* [type]]} {

	    # Drop the meta-data information associated with each part

	    set val [lindex $val 1]
	}
	return $val
    } else {
	return $default
    }
}

# ::ncgi::valueList
#
#	Return all the values of a named query element as a list, or
#	the empty list if it was not not specified.  This always returns
#	lists - if you do not want the extra level of listification, use
#	ncgi::value instead.
#
# Arguments:
#	key	The name of the query element
#
# Results:
#	The first value of the named element, or ""

proc ::ncgi::valueList {key {default {}}} {
    variable value
    if {[info exists value($key)]} {
	return $value($key)
    } else {
	return $default
    }
}

# ::ncgi::setValue
#
#	Jam a new value into the CGI environment.  This is handy for preliminary
#	processing that does data validation and cleanup.
#
# Arguments:
#	key	The name of the query element
#	value	This is a single value, and this procedure wraps it up in a list
#		for compatibility with the ncgi::value array usage.  If you
#		want a list of values, use ngci::setValueList
#		
#
# Side Effects:
#	Alters the ncgi::value and possibly the ncgi::valueList variables

proc ::ncgi::setValue {key value} {
    variable listRestrict
    if {$listRestrict} {
	ncgi::setValueList $key $value
    } else {
	ncgi::setValueList $key [list $value]
    }
}

# ::ncgi::setValueList
#
#	Jam a list of new values into the CGI environment.
#
# Arguments:
#	key		The name of the query element
#	valuelist	This is a list of values, e.g., for checkbox or multiple
#			selections sets.
#		
# Side Effects:
#	Alters the ncgi::value and possibly the ncgi::valueList variables

proc ::ncgi::setValueList {key valuelist} {
    variable value
    variable varlist
    if {![info exists value($key)]} {
	lappend varlist $key
    }

    # This if statement is a workaround for another hack in
    # ::ncgi::value that treats multipart form data
    # differently.
    if {[string match multipart/* [type]]} {
	set value($key) [list [list {} [join $valuelist]]]
    } else {
	set value($key) $valuelist
    }
    return ""
}

# ::ncgi::setDefaultValue
#
#	Set a new value into the CGI environment if there is not already one there.
#
# Arguments:
#	key	The name of the query element
#	value	This is a single value, and this procedure wraps it up in a list
#		for compatibility with the ncgi::value array usage.
#		
#
# Side Effects:
#	Alters the ncgi::value and possibly the ncgi::valueList variables

proc ::ncgi::setDefaultValue {key value} {
    ncgi::setDefaultValueList $key [list $value]
}

# ::ncgi::setDefaultValueList
#
#	Jam a list of new values into the CGI environment if the CGI value
#	is not already defined.
#
# Arguments:
#	key		The name of the query element
#	valuelist	This is a list of values, e.g., for checkbox or multiple
#			selections sets.
#		
# Side Effects:
#	Alters the ncgi::value and possibly the ncgi::valueList variables

proc ::ncgi::setDefaultValueList {key valuelist} {
    variable value
    if {![info exists value($key)]} {
	ncgi::setValueList $key $valuelist
	return ""
    } else {
	return ""
    }
}

# ::ncgi::exists --
#
#	Return false if the CGI variable doesn't exist.
#
# Arguments:
#	name	Name of the CGI variable
#
# Results:
#	0 if the variable doesn't exist

proc ::ncgi::exists {var} {
    variable value
    return [info exists value($var)]
}

# ::ncgi::empty --
#
#	Return true if the CGI variable doesn't exist or is an empty string
#
# Arguments:
#	name	Name of the CGI variable
#
# Results:
#	1 if the variable doesn't exist or has the empty value

proc ::ncgi::empty {name} {
    return [expr {[string length [string trim [value $name]]] == 0}]
}

# ::ncgi::import
#
#	Map a CGI input into a Tcl variable.  This creates a Tcl variable in
#	the callers scope that has the value of the CGI input.  An alternate
#	name for the Tcl variable can be specified.
#
# Arguments:
#	cginame		The name of the form element
#	tclname		If present, an alternate name for the Tcl variable,
#			otherwise it is the same as the form element name

proc ::ncgi::import {cginame {tclname {}}} {
    if {[string length $tclname]} {
	upvar 1 $tclname var
    } else {
	upvar 1 $cginame var
    }
    set var [value $cginame]
}

# ::ncgi::importAll
#
#	Map a CGI input into a Tcl variable.  This creates a Tcl variable in
#	the callers scope for every CGI value, or just for those named values.
#
# Arguments:
#	args	A list of form element names.  If this is empty,
#		then all form value are imported.

proc ::ncgi::importAll {args} {
    variable varlist
    if {[llength $args] == 0} {
	set args $varlist
    }
    foreach cginame $args {
	upvar 1 $cginame var
	set var [value $cginame]
    }
}

# ::ncgi::redirect
#
#	Generate a redirect by returning a header that has a Location: field.
#	If the URL is not absolute, this automatically qualifies it to
#	the current server
#







<
|
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>
>
>
|
<
<
>
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
|
|
>
|
>
>
>
>
>
>
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>
|
|
>
>
|
>
|
>
>
>
>
>
>

>
|
>
>
>
>
>
>
>
>
|
>
>
>
|
|
|
|

<
|


>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>

<
>
>
>
>
>
>

|
>
|
>
|
>
|
|
>
>
>
>
>
>
>
|
>
>
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>


|




<
|
>
>
|
<
|
>
>
>
>
>
>
>
>
|
<
<
|
|
<

<
<
>
>
>
>
>
>

|
<
<
<
<
>
|
|
>
|
>
|
>
>
>
>
|
|
|

|
|


<
|

>
|
<
<


<
|
>


|
|
<
>
>
|
<
<
<
|
<
>
<
<
|
>
|
>
|
>
|
>
|
>
>
>
>
>
|
<
<
|
<
<
<
<
<
<
<


|
>
|
>
|

|



|
|
<
|
|
<
<
<
<
<
<
<
|
<
<
<
<
|
<
<
|
<
<

<
<
<
|
|
>
>
>
|
|
<
|
>
>
|
|
|
<
<
|
|
<
<
<
<
<
<
<
|

|



|










|

|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<

<
<
<
<
<
<
<
<
|
<
<
|
<
<
|
<
|
<
<
<
<
<
<
<
<
<
|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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

    regsub -all -- \[^a-zA-Z0-9\] $string {$map(&)} string
    # This quotes cases like $map([) or $map($) => $map(\[) ...
    regsub -all -- {[][{})\\]\)} $string {\\&} string
    return [subst -nocommand $string]
}



proc ::ncgi::form_get args {
    set type [type]
    variable form
    if {![info exists form]} {
	set form {}
	switch -glob $type {
	    {} -
	    text/xml* -
	    application/x-www-form-urlencoded* -
	    application/x-www-urlencoded* {
		foreach {key val} [urlquery [body]] {

		    lappend form $key [list $val {}]
		}
	    }
	    multipart/* {
		set form [multipart $type [body]]
	    }
	    default {


		return -code error "Unknown Content-Type: $type"
	    }
	}
    }
    if {[llength $args] == 1} {
	lindex [dict get $form {*}$args] 0
    } elseif {[llength $args] == 2} {
	set args [lassign $args[set args {}] key]
	lassign [dict get $form $key] value params
	dict get $params {*}$args
    } elseif {[llength $args]} {
	error [list wrong # args]
    } else {
	return $form
    }
}

proc ::ncgi::form_exists {} {
    variable content_length
    if {[info exists content_length]} {
	switch -glob [type] {
	    {}
	    - text/xml*
	    - application/x-www-form-urlencoded*
	    - application/x-www-urlencoded*
	    - multipart/* {
		return 1
	    }
	}
    }
    return 0
}


# ::ncgi::get
#
#	Return the value of a named query element, or the empty string if
#	it was not not specified.  This only returns the first value of
#	associated with the name.  If you want them all (like all values
#	of a checkbox), use ncgi::all
#
# Arguments:
#	key	The name of the query element
#	default	The value to return if the value is not present
#
# Results:
#	The first value of the named element, or the default

proc ::ncgi::get args {
    variable form
    variable query
    query parse
    if {[form exists]} {
	form get
    }
    if {![llength $args]} {
	return [merge]
    } elseif {[llength $args] <= 2} {
	lassign $args key default
	if {[form exists] && [dict exists $form $key]} {
	    return [lindex [dict get $form $key] 0]
	} elseif {[dict exists $query $key]} {
	    return [dict get $query $key]
	} else {
	    return $default
	}
    } else {
	error [list {wrong # args}]
    }
}


# ncgi:header
#

#	Output the Content-Type header.
#
# Arguments:
#	type	The MIME content type
#	args	Additional name, value pairs to specifiy output headers
#
# Side Effects:
#	Outputs a normal header

proc ::ncgi::header {{type text/html} args} {
    variable cookieOutput
    puts "Content-Type: $type"
    foreach {n v} $args {
	puts "$n: $v"
    }
    if {[info exists cookieOutput]} {
	foreach line $cookieOutput {
	    puts "Set-Cookie: $line"
	}
    }
    puts ""
    flush stdout
}


# ::ncgi::importFile --
#
#   get information about a file upload field
#
# Arguments:
#   cmd         one of '-server' '-client' '-type' '-data'
#   var         cgi variable name for the file field
#   filename    filename to write to for -server
# Results:

#   -server returns the name of the file on the server: side effect
#      is that the file gets stored on the server and the 
#      script is responsible for deleting/moving the file
#   -client returns the name of the file sent from the client 
#   -type   returns the mime type of the file
#   -data   returns the contents of the file 

proc ::ncgi::importFile {cmd var {filename {}}} {
    if {[form exists]} {
	set form [form get]
    }

    lassign [dict get $form $var] content params

    switch -exact -- $cmd {
	-server {
	    ## take care not to write it out more than once
	    variable _tmpfiles
	    if {![info exists _tmpfiles($var)]} {
		if {$filename eq {}} {
		    ## create a tmp file 
		    set _tmpfiles($var) [::fileutil::tempfile ncgi]
		} else {
		    ## use supplied filename 
		    set _tmpfiles($var) $filename
		}

		# write out the data only if it's not been done already
		if {[catch {open $_tmpfiles($var) w} h]} {
		    error "Can't open temporary file in ncgi::importFile ($h)"
		} 

		fconfigure $h -translation binary -encoding binary
		puts -nonewline $h $content 
		close $h
	    }
	    return $_tmpfiles($var)
	}
	-client {
	    if {[dict exists $params filename]} {
		return [dict get $params filename]
	    }
	    return {}
	}
	-type {
	    if {![info exists fileinfo(content-type)]} {return {}}
	    return $fileinfo(content-type)
	}
	-data {
	    return $contents
	}
	default {
	    error "Unknown subcommand to ncgi::import_file: $cmd"
	}
    }
}



proc ::ncgi::merge {} {
    variable form
    variable query

    query parse
    if {[form exists]} {
	list {*}$query {*}[join [lmap {fkey val} $form {
	    list $fkey [lindex $val 0]
	}]]
    } else {
	return $query
    }
}




# ::ncgi::multipart

#


#	Parses $data into a multidict using the boundary provided in $type,
#	which is a complete Content-type value.  Each value in the resulting
#	multi dict is a list where the first item is the value and the the
#	second item is a multidict where each key is the name of a header and
#	each value is a list containing the header value and a dictionary of
#	parameters for that header.

proc ::ncgi::multipart {type data} {




    set token [mime::initialize  -string "Content-Type: $type\n\n$data"]
    set parts [mime::property $token parts]

    set results [list]
    foreach part $parts {
	    set header [::mime::header get $part]
	    set value [::mime::body $part -decode]
	    lassign [::mime::header get $part content-disposition] hvalue params
	    if {$hvalue eq {form-data} && [dict exists $params name]} {
		set name [dict get $params name]
	    } else {
		set name {}
	    }
	    lappend results $name [list $value $header]
    }
    return $results
}



# ::ncgi::parseMimeValue
#
#	Parse a MIME header value, which has the form
#	value; param=value; param2="value2"; param3='value3'


#
# Arguments:

#	value	The mime header value.  This does not include the mime
#		header field name, but everything after it.
#
# Results:
#	A two-element list, the first is the primary value,
#	the second is in turn a name-value list corresponding to the

#	parameters.  Given the above example, the return value is
#	{
#		value



#		{param value param2 value2 param3 value3}

#	}



proc ::ncgi::parseMimeValue {value} {
    set parts [split $value \;]
    set results [list [string trim [lindex $parts 0]]]
    set paramList [list]
    foreach sub [lrange $parts 1 end] {
	if {[regexp -- {([^=]+)=(.+)} $sub match key val]} {
            set key [string trim [string tolower $key]]
            set val [string trim $val]
            # Allow single as well as double quotes
            if {[regexp -- {^(['"])(.*)\1} $val x quote val2]} { ; # need a " for balance
               # Trim quotes and any extra crap after close quote
               # remove quoted quotation marks
               set val [string map {\\" "\"" \\' "\'"} $val2]
            }


            lappend paramList $key $val







	}
    }
    if {[llength $paramList]} {
	lappend results $paramList
    }
    return $results
}

# ::ncgi::query parse
#
#	Parses the query part of the URI
#
proc ::ncgi::query_parse {} {
    variable query

    if {![info exists query]} {
	set query [urlquery [query_string]]







    }




    return $query


}







# ::ncgi::query_set
#
#	set the value of $key in the query dictionary to $value
#
proc ::ncgi::query_set {key value} {
    variable query

    query parse
    set idx [lindex [lmap idx [lsearch -exact -all $key $query] {
	if {[$idx % 2]} continue
	set idx
    }] end]
    if {$idx >= 0} {


	set query [lreplace $query[set query {}] $idx $idx $value]
    } else {







	lappend query $key $value
    }
    return $value
}


# ::ncgi::query_string
#
#	This reads the query data from the appropriate location, which depends
#	on if it is a POST or GET request.
#
# Arguments:
#	none
#
# Results:
#	The raw query data.

proc ::ncgi::query_string {} {
    global env
    variable querystring























































































































    if {[info exists querystring]} {


	# This ensures you can call ncgi::query more than once,










	# and that you can use it with ncgi::reset


































	return $querystring


    }











    set querystring {}


    if {[info exists env(QUERY_STRING)]} {

	set querystring $env(QUERY_STRING)









    }



    return $querystring










}





























# ::ncgi::redirect
#
#	Generate a redirect by returning a header that has a Location: field.
#	If the URL is not absolute, this automatically qualifies it to
#	the current server
#
843
844
845
846
847
848
849

850
851



852
853
854

855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907



908
909


910

911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995

996
997
998
999


1000
1001
1002

1003
1004
1005
1006

1007
1008
1009
1010
1011
1012
1013
1014

1015
1016
1017
1018
1019



1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041

1042
1043

1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059

1060
1061
1062


1063


1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078






1079
1080
1081
1082
1083
1084
1085
1086
1087

1088

1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110




1111
1112
1113


1114
1115
1116
1117
1118
1119
1120

1121

1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162


1163
1164
1165

1166
1167

1168

1169
1170
1171
1172
1173
1174
1175

1176
1177
1178
1179

1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192

	    set url $proto://$server$port$dirname$url
	}
    }
    ncgi::header text/html Location $url
    puts "Please go to <a href=\"$url\">$url</a>"
}


# ncgi:header
#



#	Output the Content-Type header.
#
# Arguments:

#	type	The MIME content type
#	args	Additional name, value pairs to specifiy output headers
#
# Side Effects:
#	Outputs a normal header

proc ::ncgi::header {{type text/html} args} {
    variable cookieOutput
    puts "Content-Type: $type"
    foreach {n v} $args {
	puts "$n: $v"
    }
    if {[info exists cookieOutput]} {
	foreach line $cookieOutput {
	    puts "Set-Cookie: $line"
	}
    }
    puts ""
    flush stdout
}

# ::ncgi::parseMimeValue
#
#	Parse a MIME header value, which has the form
#	value; param=value; param2="value2"; param3='value3'
#
# Arguments:
#	value	The mime header value.  This does not include the mime
#		header field name, but everything after it.
#
# Results:
#	A two-element list, the first is the primary value,
#	the second is in turn a name-value list corresponding to the
#	parameters.  Given the above example, the return value is
#	{
#		value
#		{param value param2 value2 param3 value3}
#	}

proc ::ncgi::parseMimeValue {value} {
    set parts [split $value \;]
    set results [list [string trim [lindex $parts 0]]]
    set paramList [list]
    foreach sub [lrange $parts 1 end] {
	if {[regexp -- {([^=]+)=(.+)} $sub match key val]} {
            set key [string trim [string tolower $key]]
            set val [string trim $val]
            # Allow single as well as double quotes
            if {[regexp -- {^(['"])(.*)\1} $val x quote val2]} { ; # need a " for balance
               # Trim quotes and any extra crap after close quote
               # remove quoted quotation marks
               set val [string map {\\" "\"" \\' "\'"} $val2]
            }



            lappend paramList $key $val
	}


    }

    if {[llength $paramList]} {
	lappend results $paramList
    }
    return $results
}

# ::ncgi::multipart
#
#	This parses multipart form data.
#	Based on work by Steve Ball for TclHttpd, but re-written to use
#	string first with an offset to iterate through the data instead
#	of using a regsub/subst combo.
#
# Arguments:
#	type	The Content-Type, because we need boundary options
#	query	The raw multipart query data
#
# Results:
#	An alternating list of names and values
#	In this case, the value is a two element list:
#		headers, which in turn is a list names and values
#		content, which is the main value of the element
#	The header name/value pairs come primarily from the MIME headers
#	like Content-Type that appear in each part.  However, the
#	Content-Disposition header is handled specially.  It has several
#	parameters like "name" and "filename" that are important, so they
#	are promoted to to the same level as Content-Type.  Otherwise,
#	if a header like Content-Type has parameters, they appear as a list
#	after the primary value of the header.  For example, if the
#	part has these two headers:
#
#	Content-Disposition: form-data; name="Foo"; filename="/a/b/C.txt"
#	Content-Type: text/html; charset="iso-8859-1"; mumble='extra'
#	
#	Then the header list will have this structure:
#	{
#		content-disposition form-data
#		name Foo
#		filename /a/b/C.txt
#		content-type {text/html {charset iso-8859-1 mumble extra}}
#	}
#	Note that the header names are mapped to all lowercase.  You can
#	use "array set" on the header list to easily find things like the
#	filename or content-type.  You should always use [lindex $value 0]
#	to account for values that have parameters, like the content-type
#	example above.  Finally, not that if the value has a second element,
#	which are the parameters, you can "array set" that as well.
#	
proc ::ncgi::multipart {type query} {

    set parsedType [parseMimeValue $type]
    if {![string match multipart/* [lindex $parsedType 0]]} {
	return -code error "Not a multipart Content-Type: [lindex $parsedType 0]"
    }
    array set options [lindex $parsedType 1]
    if {![info exists options(boundary)]} {
	return -code error "No boundary given for multipart document"
    }
    set boundary $options(boundary)

    # The query data is typically read in binary mode, which preserves
    # the \r\n sequence from a Windows-based browser.
    # Also, binary data may contain \r\n sequences.

    if {[string match "*$boundary\r\n*" $query]} {
        set lineDelim "\r\n"
	#	puts "DELIM"
    } else {
        set lineDelim "\n"
	#	puts "NO"
    }

    # Iterate over the boundary string and chop into parts

    set len [string length $query]
    # [string length $lineDelim]+2 is for "$lineDelim--"
    set blen [expr {[string length $lineDelim] + 2 + \
            [string length $boundary]}]
    set first 1
    set results [list]
    set offset 0

    # Ensuring the query data starts
    # with a newline makes the string first test simpler
    if {[string first $lineDelim $query 0]!=0} {

        set query $lineDelim$query
    }
    while {[set offset [string first $lineDelim--$boundary $query $offset]] \
            >= 0} {


	if {!$first} {
	    lappend results $formName [list $headers \
		[string range $query $off2 [expr {$offset -1}]]]

	} else {
	    set first 0
	}
	incr offset $blen


	# Check for the ending boundary, which is signaled by --$boundary--

	if {[string equal "--" \
		[string range $query $offset [expr {$offset + 1}]]]} {
	    break
	}


	# Split headers out from content
	# The headers become a nested list structure:
	#	{header-name {
	#		value {
	#			paramname paramvalue ... }



	#		}
	#	}

        set off2 [string first "$lineDelim$lineDelim" $query $offset]
	set headers [list]
	set formName ""
        foreach line [split [string range $query $offset $off2] $lineDelim] {
	    if {[regexp -- {([^:	 ]+):(.*)$} $line x hdrname value]} {
		set hdrname [string tolower $hdrname]
		set valueList [parseMimeValue $value]
		if {[string equal $hdrname "content-disposition"]} {

		    # Promote Conent-Disposition parameters up to headers,
		    # and look for the "name" that identifies the form element

		    lappend headers $hdrname [lindex $valueList 0]
		    foreach {n v} [lindex $valueList 1] {
			lappend headers $n $v
			if {[string equal $n "name"]} {
			    set formName $v
			}
		    }

		} else {
		    lappend headers $hdrname $valueList

		}
	    }
	}

	if {$off2 > 0} {
            # +[string length "$lineDelim$lineDelim"] for the
            # $lineDelim$lineDelim
            incr off2 [string length "$lineDelim$lineDelim"]
	    set offset $off2
	} else {
	    break
	}
    }
    return $results
}


# ::ncgi::importFile --
#
#   get information about a file upload field


#


# Arguments:
#   cmd         one of '-server' '-client' '-type' '-data'
#   var         cgi variable name for the file field
#   filename    filename to write to for -server
# Results:
#   -server returns the name of the file on the server: side effect
#      is that the file gets stored on the server and the 
#      script is responsible for deleting/moving the file
#   -client returns the name of the file sent from the client 
#   -type   returns the mime type of the file
#   -data   returns the contents of the file 

proc ::ncgi::importFile {cmd var {filename {}}} {

    set vlist [valueList $var]







    array set fileinfo [lindex [lindex $vlist 0] 0]
    set contents [lindex [lindex $vlist 0] 1]

    switch -exact -- $cmd {
	-server {
	    ## take care not to write it out more than once
	    variable _tmpfiles
	    if {![info exists _tmpfiles($var)]} {

		if {$filename != {}} {

		    ## use supplied filename 
		    set _tmpfiles($var) $filename
		} else {
		    ## create a tmp file 
		    set _tmpfiles($var) [::fileutil::tempfile ncgi]
		}

		# write out the data only if it's not been done already
		if {[catch {open $_tmpfiles($var) w} h]} {
		    error "Can't open temporary file in ncgi::importFile ($h)"
		} 

		fconfigure $h -translation binary -encoding binary
		puts -nonewline $h $contents 
		close $h
	    }
	    return $_tmpfiles($var)
	}
	-client {
	    if {![info exists fileinfo(filename)]} {return {}}
	    return $fileinfo(filename)
	}




	-type {
	    if {![info exists fileinfo(content-type)]} {return {}}
	    return $fileinfo(content-type)


	}
	-data {
	    return $contents
	}
	default {
	    error "Unknown subcommand to ncgi::import_file: $cmd"
	}

    }

}


# ::ncgi::cookie
#
#	Return a *list* of cookie values, if present, else ""
#	It is possible for multiple cookies with the same key
#	to be present, so we return a list.
#
# Arguments:
#	cookie	The name of the cookie (the key)
#
# Results:
#	A list of values for the cookie

proc ::ncgi::cookie {cookie} {
    global env
    set result ""
    if {[info exists env(HTTP_COOKIE)]} {
	foreach pair [split $env(HTTP_COOKIE) \;] {
	    foreach {key value} [split [string trim $pair] =] { break ;# lassign }
	    if {[string compare $cookie $key] == 0} {
		lappend result $value
	    }
	}
    }
    return $result
}

# ::ncgi::setCookie
#
#	Set a return cookie.  You must call this before you call
#	ncgi::header or ncgi::redirect
#
# Arguments:
#	args	Name value pairs, where the names are:
#		-name	Cookie name
#		-value	Cookie value
#		-path	Path restriction
#		-domain	domain restriction
#		-expires	Time restriction


#
# Side Effects:
#	Formats and stores the Set-Cookie header for the reply.


proc ::ncgi::setCookie {args} {

    variable cookieOutput

    array set opt $args
    set line "$opt(-name)=$opt(-value) ;"
    foreach extra {path domain} {
	if {[info exists opt(-$extra)]} {
	    append line " $extra=$opt(-$extra) ;"
	}
    }

    if {[info exists opt(-expires)]} {
	switch -glob -- $opt(-expires) {
	    *GMT {
		set expires $opt(-expires)

	    }
	    default {
		set expires [clock format [clock scan $opt(-expires)] \
			-format "%A, %d-%b-%Y %H:%M:%S GMT" -gmt 1]
	    }
	}
	append line " expires=$expires ;"
    }
    if {[info exists opt(-secure)]} {
	append line " secure "
    }
    lappend cookieOutput $line
}








>
|

>
>
>
|


>
|
<


<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
|
<
|
<
<
<
<
|
<
<
|
|
<
>
>
>
|
|
>
>
|
>
|
<
<
<
<
|
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
|
<
<
|
|
<
<
<
|
<
<
<

<
<
<
|
<
|
<
<
<
<
|
<
|
|
<
<
<
>
|
<
<
|
>
>
|
|
|
>
|
|
|
|
>
|
<
|
<
<
<

|
>
|
|
<
|
<
>
>
>
|
|
|
<
<
<
<
<
<
<
<
|
<
<
|
<
<
<
<
<
|
<
>
|
<
>
|
<
<
|
|
<
<
<
|

|


|


>
|

<
>
>

>
>
<
<
<
<
<
<
<
<
<
<
<

<
|
<
>
>
>
>
>
>

|
|

<
|
<
<
<
>
|
>
|
|
|
<
<
<
|
<
<
<
<
|
<
<
<
<
<
<
|
<
<
|
>
>
>
>
|
<
<
>
>
|
|
|
<
|
<
|
>

>



|

<
|
<
<
<
<
<
<
<
|
<
|
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<

<
<
<
<
<
<
>
>


<
>
|
|
>
|
>
|
<
|
|
<
<
<
>
|
<
<
|
>
|
|
<
<
|
|
<
|
<
<
|
<
<
>
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726

727
728

729














730

















731
732
733

734




735


736
737

738
739
740
741
742
743
744
745
746
747




748









749














750


















751





752


753
754



755



756



757

758




759

760
761



762
763


764
765
766
767
768
769
770
771
772
773
774
775
776

777



778
779
780
781
782

783

784
785
786
787
788
789








790


791





792

793
794

795
796


797
798



799
800
801
802
803
804
805
806
807
808
809

810
811
812
813
814











815

816

817
818
819
820
821
822
823
824
825
826

827



828
829
830
831
832
833



834




835






836


837
838
839
840
841
842


843
844
845
846
847

848

849
850
851
852
853
854
855
856
857

858







859

860











861





862






863
864
865
866

867
868
869
870
871
872
873

874
875



876
877


878
879
880
881


882
883

884


885


886
	    set url $proto://$server$port$dirname$url
	}
    }
    ncgi::header text/html Location $url
    puts "Please go to <a href=\"$url\">$url</a>"
}


# ::ncgi::reset
#
#	This resets the state of the CGI input processor.  This is primarily
#	used for tests, although it is also designed so that TclHttpd can
#	call this with the current query data
#	so the ncgi package can be shared among TclHttpd and CGI scripts.
#
# Arguments:
#	newquery	The query data to be used instead of external CGI.
#	newtype		The raw content type.

#
# Side Effects:

#	Resets the cached query data and wipes any environment variables














#	associated with CGI inputs (like QUERY_STRING)


















proc ::ncgi::reset args {
    global env

    variable _tmpfiles




    variable body


    variable query
    variable querystring

    variable contenttype
    variable content_length
    variable cookieOutput
    variable form

    # array unset _tmpfiles -- Not a Tcl 8.2 idiom
    unset _tmpfiles ; array set _tmpfiles {}

    set cookieOutput {}
    if {[llength $args] == 0} {




	# We use and test args here so we can detect the









	# difference between empty query data and a full reset.

































	foreach name {body contenttype form query querystring} {





	    if {[info exists $name]} {


		unset $name
	    }



	}



    } else {



	set contenttype {}

	if {[info exists body]} {




	    unset body

	    unset content_length
	}



	catch {unset form}
	catch {unset query}



	dict for {opt val} $args {
	    switch $opt {
		body {
		    set $opt $val
		    set content_length [string length $body]
		}
		contenttype - form - querystring {
		    set $opt $val
		}
		default {
		    error [list {unknown reset option} $opt]
		}

	    }



	}
    }
}



# ::ncgi::type

#
#	This returns the content type of the query data.
#
# Arguments:
#	none
#








# Results:


#	The content type of the query data.







proc ::ncgi::type {} {
    global env

    variable contenttype



    if {![info exists contenttype]} {
	if {[info exists env(CONTENT_TYPE)]} {



	    set contenttype $env(CONTENT_TYPE)
	} else {
	    return ""
	}
    }
    return $contenttype
}


# ::ncgi::parsequery
#

#	Parses $data as a url-encoded query and returns a multidict containing
#	the query.
#
proc ::ncgi::urlquery data {
    set result {}













    # Any whitespace at the beginning or end of urlquery data is not

    # considered to be part of that data, so we trim it off.  One special
    # case in which post data is preceded by a \n occurs when posting
    # with HTTPS in Netscape.
    foreach x [split [string trim $data] &] {
	# Turns out you might not get an = sign,
	# especially with <isindex> forms.

	set pos [string first = $x]
	set len [string length $x]


	if {$pos>=0} {



	    if {$pos == 0} { # if the = is at the beginning ...
		if {$len>1} { 
		    # ... and there is something to the right ...
		    set varname [string range $x 1 end]
		    set val {}
		} else { 



		    # ... otherwise, all we have is an =




		    set varname {}






		    set val {} 


		}
	    } elseif {$pos==[expr {$len-1}]} {
		# if the = is at the end ...
		set varname [string range $x 0 [expr {$pos-1}]]
		set val ""
	    } else {


		set varname [string range $x 0 [expr {$pos-1}]]
		set val [string range $x [expr {$pos+1}] end]
	    }
	} else { # no = was found ...
	    set varname $x

	    set val {}

	}		
	lappend result [decode $varname] [decode $val]
    }
    return $result
}


# ::ncgi::urlStub
#

#	Set or return the URL associated with the current page.







#	This is for use by TclHttpd to override the default value

#	that otherwise comes from the CGI environment











#





# Arguments:






#	url	(option) The url of the page, not counting the server name.
#		If not specified, the current urlStub is returned
#
# Side Effects:

#	May affects future calls to ncgi::urlStub
#
proc ::ncgi::urlStub {{url {}}} {
    global   env
    variable urlStub
    if {[string length $url]} {
	set urlStub $url

	return ""
    } elseif {[info exists urlStub]} {



	return $urlStub
    } elseif {[info exists env(SCRIPT_NAME)]} {


	set urlStub $env(SCRIPT_NAME)
	return $urlStub
    } else {
	return ""


    }
}







namespace eval ::ncgi reset

Changes to modules/ncgi/ncgi.test.

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
701
702
703
704
705


706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

741
742
743
744
745
746
747
748
749
750

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781

782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842

843
844
845
846
847

848
849
850
851
852
853

854
855
856
857
858
859
860
861
862






863
testsNeedTcl     8.4
testsNeedTcltest 2

testing {
    useLocal ncgi.tcl ncgi
}

# -------------------------------------------------------------------------



set     sub_ap $auto_path
lappend sub_ap $::tcltest::testsDirectory
set ncgiFile   [localPath ncgi.tcl]
set futlFile   [tcllibPath fileutil/fileutil.tcl]
set cmdlFile   [tcllibPath cmdline/cmdline.tcl]

# -------------------------------------------------------------------------

test ncgi-1.1 {ncgi::reset} {
    ncgi::reset
    list [info exist ncgi::query] [info exist ncgi::contenttype]
} {0 0}


test ncgi-1.2 {ncgi::reset} {
    ncgi::reset query query=reset
    list $ncgi::query $ncgi::contenttype
} {query=reset {}}


test ncgi-1.3 {ncgi::reset} {
    ncgi::reset query query=reset contenttype text/plain
    list $ncgi::query $ncgi::contenttype
} {query=reset text/plain}


test ncgi-2.1 {ncgi::query fake query data} {
    ncgi::reset query fake=query
    ncgi::query
    set ncgi::query
} "fake=query"


test ncgi-2.2 {ncgi::query GET} {
    ncgi::reset
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) name=value
    ncgi::query
    set ncgi::query
} "name=value"


test ncgi-2.3 {ncgi::query HEAD} {
    ncgi::reset
    set env(REQUEST_METHOD) HEAD
    catch {unset env(QUERY_STRING)}
    ncgi::querystring
    set ncgi::query
} ""


test ncgi-2.4 {ncgi::query POST} {
    ncgi::reset
    catch {unset env(QUERY_STRING)}
    set env(REQUEST_METHOD) POST
    set env(CONTENT_LENGTH) 10
    makeFile [format {
	set auto_path {%s}
	source {%s}
	source {%s}
	source {%s}
	ncgi::poststring
	puts $ncgi::post
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile] test1 ; # {}
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    puts  $f "name=value"
    flush $f
    gets  $f line
    close $f
    removeFile test1
    set line
} name=value


test ncgi-2.5 {ncgi::test} {
    ncgi::reset
    set env(CONTENT_TYPE) text/html
    ncgi::type
} text/html


test ncgi-2.6 {ncgi::test} {
    ncgi::reset query foo=bar contenttype text/plain
    set env(CONTENT_TYPE) text/html
    ncgi::type
} text/plain


test ncgi-3.1 {ncgi::decode} {
    ncgi::decode abcdef0123
} abcdef0123


test ncgi-3.2 {ncgi::decode} {
    ncgi::decode {[abc]def$0123\x}
} {[abc]def$0123\x}


test ncgi-3.3 {ncgi::decode} {
    ncgi::decode {[a%25c]def$01%7E3\x%3D}
} {[a%c]def$01~3\x=}


test ncgi-3.4 {ncgi::decode} {
    ncgi::decode {hello+world}
} {hello world}


test ncgi-3.5 {ncgi::decode} {
    ncgi::decode {aik%C5%ABloa}
} "aik\u016Bloa" ; # u+macron


test ncgi-3.6 {ncgi::decode} {
    ncgi::decode {paran%C3%A1}
} "paran\u00E1" ; # a+acute


test ncgi-3.7 {ncgi::decode, bug 3601995} {
    ncgi::decode {%C4%85}
} "\u0105" ; # a+ogonek


test ncgi-3.8 {ncgi::decode, bug 3601995} {
    ncgi::decode {%E2%80%A0}
} "\u2020" ; # dagger


test ncgi-3.9 {ncgi::decode, bug 3601995} {
    ncgi::decode {%E2%A0%90}
} "\u2810" ; # a braille pattern


test ncgi-3.10 {ncgi::decode, bug 3601995} {
    ncgi::decode {%E2%B1}
} "%E2%B1" ; # missing byte trailing %A0, do not accept/decode, pass through.


test ncgi-4.1 {ncgi::encode} {
    ncgi::encode abcdef0123
} abcdef0123


test ncgi-4.2 {ncgi::encode} {
    ncgi::encode "\[abc\]def\$0123\\x"
} {%5Babc%5Ddef%240123%5Cx}


test ncgi-4.3 {ncgi::encode} {
    ncgi::encode {hello world}
} {hello+world}


test ncgi-4.4 {ncgi::encode} {
    ncgi::encode "hello\nworld\r\tbar"
} {hello%0D%0Aworld%0D%09bar}


test ncgi-5.1 {ncgi::nvlist} {
    ncgi::reset query name=hello+world&name2=%7ewelch
    ncgi::nvlist
} {name {hello world} name2 ~welch}

test ncgi-5.2 {ncgi::nvlist} {
    ncgi::reset query name=&name2 contenttype application/x-www-urlencoded
    ncgi::nvlist
} {name {} anonymous name2}

test ncgi-5.3 {ncgi::nvlist} {
    ncgi::reset query name=&name2 contenttype application/x-www-form-urlencoded
    ncgi::nvlist
} {name {} anonymous name2}

test ncgi-5.4 {ncgi::nvlist} {
    ncgi::reset query name=&name2 contenttype application/xyzzy
    set code [catch ncgi::nvlist err]
    list $code $err
} {1 {Unknown Content-Type: application/xyzzy}}

# multipart tests at the end because I'm too lazy to renumber the tests

test ncgi-6.1 {ncgi::parse, anonymous values} {
    ncgi::reset query name=&name2
    ncgi::parse
} {name anonymous}

test ncgi-6.2 {ncgi::parse, no list restrictions} {




    ncgi::reset query name=value&name=value2
    ncgi::parse 
























} {name}


test ncgi-7.1 {ncgi::input} {
    ncgi::reset
    catch {unset env(REQUEST_METHOD)}
    ncgi::input "name=value&name2=value2"
} {name name2}


test ncgi-7.2 {ncgi::input} {
    ncgi::reset query nameList=value1+stuff&nameList=value2+more
    ncgi::input
    set ncgi::value(nameList)
} {{value1 stuff} {value2 more}}


test ncgi-7.3 {ncgi::input} {
    ncgi::reset query name=value&name=value2
    catch {ncgi::input} err
    set err
} {Multiple definitions of name encountered in input. If you're trying to do this intentionally (such as with select), the variable must have a "List" suffix.}

test ncgi-8.1 {ncgi::value} {
    ncgi::reset query nameList=val+ue&nameList=value2
    ncgi::input
    ncgi::value nameList
} {{val ue} value2}

test ncgi-8.2 {ncgi::value} {
    ncgi::reset query name=val+ue&name=value2
    ncgi::parse
    ncgi::value name
} {val ue}

test ncgi-8.3 {ncgi::value} {
    ncgi::reset query name=val+ue&name=value2
    ncgi::parse
    ncgi::value noname
} {}

test ncgi-9.1 {ncgi::valueList} {
    ncgi::reset query name=val+ue&name=value2
    ncgi::parse
    ncgi::valueList name
} {{val ue} value2}

test ncgi-9.2 {ncgi::valueList} {
    ncgi::reset query name=val+ue&name=value2
    ncgi::parse
    ncgi::valueList noname
} {}

test ncgi-10.1 {ncgi::import} {
    ncgi::reset query nameList=val+ue&nameList=value2
    ncgi::input
    ncgi::import nameList
    set nameList
} {{val ue} value2}

test ncgi-10.2 {ncgi::import} {
    ncgi::reset query nameList=val+ue&nameList=value2
    ncgi::input
    ncgi::import nameList myx
    set myx
} {{val ue} value2}

test ncgi-10.3 {ncgi::import} {
    ncgi::reset query nameList=val+ue&nameList=value2
    ncgi::input
    ncgi::import noname
    set noname

} {}

test ncgi-10.4 {ncgi::importAll} {
    ncgi::reset query name1=val+ue&name2=value2
    catch {unset name1}

    catch {unset name2}
    ncgi::parse
    ncgi::importAll

    list $name1 $name2
} {{val ue} value2}

test ncgi-10.5 {ncgi::importAll} {
    ncgi::reset query name1=val+ue&name2=value2
    catch {unset name1}
    catch {unset name2}
    catch {unset name3}
    ncgi::parse
    ncgi::importAll name2 name3
    list [info exist name1] $name2 $name3
} {0 value2 {}}

set URL http://www.tcltk.com/index.html
test ncgi-11.1 {ncgi::redirect} {
    set env(REQUEST_URI) http://www.scriptics.com/cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 80
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL\n\nPlease go to <a href=\"$URL\">$URL</a>\n"


set URL /elsewhere/foo.html
set URL2 http://www/elsewhere/foo.html
test ncgi-11.2 {ncgi::redirect} {
    set env(REQUEST_URI) http://www/cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 80
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::setCookie -name CookieName -value 12345
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\nSet-Cookie: CookieName=12345 ;\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


set URL foo.html
set URL2 http://www.scriptics.com/cgi-bin/foo.html
test ncgi-11.3 {ncgi::redirect} {
    set env(REQUEST_URI) http://www.scriptics.com/cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 80
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


set URL foo.html
set URL2 http://www.scriptics.com/cgi-bin/foo.html
test ncgi-11.4 {ncgi::redirect} {
    set env(REQUEST_URI) /cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 80
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


set URL foo.html
set URL2 http://www.scriptics.com:8000/cgi-bin/foo.html
test ncgi-11.5 {ncgi::redirect} {
    set env(REQUEST_URI) /cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 8000
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


set URL foo.html
set URL2 https://www.scriptics.com/cgi-bin/foo.html
test ncgi-11.6 {ncgi::redirect} {
    set env(REQUEST_URI) /cgi-bin/test.cgi
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) www.scriptics.com
    set env(SERVER_PORT) 443
    set env(HTTPS) "on"
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


set URL  login.tcl
set URL2 https://foo.com/cgi-bin/login.tcl
test ncgi-11.7 {ncgi::redirect} {
    set env(REQUEST_URI) https://foo.com/cgi-bin/view.tcl?path=/a/b/c
    set env(REQUEST_METHOD) GET
    set env(QUERY_STRING) {}
    set env(SERVER_NAME) foo.com
    set env(SERVER_PORT) 443
    set env(HTTPS) "on"
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::redirect %s
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


test ncgi-12.1 {ncgi::header} {
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::header
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\n\n"


test ncgi-12.2 {ncgi::header} {
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::header text/plain
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/plain\n\n"


test ncgi-12.3 {ncgi::header} {
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::header text/html X-Comment "This is a test"
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nX-Comment: This is a test\n\n"


test ncgi-12.4 {ncgi::header} {
    makeFile [format {
	set auto_path {%s}
	if {[catch {
	    source %s
	    source %s
	    source %s
	    ncgi::setCookie -name Name -value {The+Value}
	    ncgi::header
	} err]} {
	    puts $err
	}
	exit
    } $sub_ap $cmdlFile $futlFile $ncgiFile] test1
    set f [open "|[list $::tcltest::tcltest test1]" r+]
    set res [read $f]
    close $f
    removeFile test1
    set res
} "Content-Type: text/html\nSet-Cookie: Name=The+Value ;\n\n"

test ncgi-13.1 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue text/html
} text/html

test ncgi-13.2 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue "text/html; charset=iso-8859-1"
} {text/html {charset iso-8859-1}}

test ncgi-13.3 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue "text/html; charset='iso-8859-1'"
} {text/html {charset iso-8859-1}}

test ncgi-13.4 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue "text/html; charset=\"iso-8859-1\""
} {text/html {charset iso-8859-1}}

test ncgi-13.5 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue "text/html; charset=\"iso-8859-1\"; ignored"
} {text/html {charset iso-8859-1}}

test ncgi-13.6 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue "text/html; charset=\"iso-8859-1\"morecrap"
} {text/html {charset iso-8859-1}}

test ncgi-13.7 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue {test/test; foo="bar\"baz\""}
} {test/test {foo bar\"baz\"}}

test ncgi-13.8 {ncgi::parseMimeValue} {
    ncgi::parseMimeValue {test/test; foo=""}
} {test/test {foo {}}}


test ncgi-14.1 {ncgi::multipart} {
    catch {ncgi::multipart "application/x-www-urlencoded" name=val+ue} err
    set err
} {Not a multipart Content-Type: application/x-www-urlencoded}

test ncgi-14.2 {ncgi::multipart} {
    catch {ncgi::multipart "multipart/form-data" {}} err
    set err

} {No boundary given for multipart document}

test ncgi-14.3 {ncgi::multipart} {
    set in [open [file join [file dirname [info script]] formdata.txt]]
    set X [read $in]
    close $in

    foreach line [split $X \n] {
	if {[string length $line] == 0} {
	    break
	}
	if {[regexp {^Content-Type: (.*)$} $line x type]} {
	    break
	}
    }
    regsub ".*?\n\n" $X {} X

    ncgi::multipart $type $X













} {field1 {{content-disposition form-data name field1} value} field2 {{content-disposition form-data name field2} {another value}} the_file_name {{content-disposition form-data name the_file_name filename {C:\Program Files\Netscape\Communicator\Program\nareadme.htm} content-type text/html} {


























<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


}}}



















test ncgi-14.4 {ncgi::multipart} {
    set in [open [file join [file dirname [info script]] formdata.txt]]
    set X [read $in]
    close $in

    foreach line [split $X \n] {
	if {[string length $line] == 0} {
	    break
	}
	if {[regexp {^Content-Type: (.*)$} $line x type]} {
	    break
	}
    }
    regsub ".*?\n\n" $X {} X

    ncgi::reset post $X contenttype $type
    ncgi::parse

    list [ncgi::value field1] [ncgi::value field2] [ncgi::value the_file_name]
} {value {another value} {
<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


}}


test ncgi-14.6 {ncgi::multipart setValue} {
    set in [open [file join [file dirname [info script]] formdata.txt]]
    set X [read $in]
    close $in

    foreach line [split $X \n] {
	if {[string length $line] == 0} {
	    break
	}
	if {[regexp {^Content-Type: (.*)$} $line x type]} {
	    break
	}
    }
    regsub ".*?\n\n" $X {} X

    ncgi::reset post $X contenttype $type
    ncgi::parse
    ncgi::setValue userval1 foo
    ncgi::setValue userval2 "a b"
    list [ncgi::value field1] [ncgi::value field2] [ncgi::value userval1] [ncgi::value userval2] [ncgi::value the_file_name]
} {value {another value} foo {a b} {
<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


}}


test ncgi-15.1 {ncgi::setValue} {
    ncgi::reset query nameList=val+ue&nameList=value2
    ncgi::input
    ncgi::setValue foo 1
    ncgi::setValue bar "a b"
    list [ncgi::value nameList] [ncgi::value foo] [ncgi::value bar]
} {{{val ue} value2} 1 {a b}}




## ------------ tests for binary content and file upload ----------------

## some utility procedures to generate content 

set form_boundary {17661509020136}

proc genformcontent_type {} {
    global form_boundary
    return "multipart/form-data; boundary=\"$form_boundary\""
}

proc genformdata {bcontent} {



    global form_boundary

    proc genformdatapart {name cd value} {
	global form_boundary
	return "--$form_boundary\nContent-Disposition: form-data; name=\"$name\"$cd\n\n$value\n"
    }

    set a [genformdatapart field1 "" {value}]
    set b [genformdatapart field2 "" {another value}]
    set c [genformdatapart the_file_name "; filename=\"C:\\Program Files\\Netscape\\Communicator\\Program\\nareadme.htm\"\nContent-Type: text/html" $bcontent]

    return "$a$b$c--$form_boundary--\n" 
}

set binary_content "\r
\r
<center><h1>\r
                  Netscape Address Book Sync for Palm Pilot\r
                                         User Guide\r
</h1></center>\r
\r
"

test ncgi-14.5 {ncgi::multipart--check binary file} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse
    set content [ncgi::value the_file_name]
    list [ncgi::value field1] [ncgi::value field2] $content
} [list value {another value} $binary_content]



test ncgi-16.1 {ncgi::importFile} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse


    ncgi::importFile -client the_file_name

} "C:\\Program Files\\Netscape\\Communicator\\Program\\nareadme.htm"

test ncgi-16.2 {ncgi::importFile - content type} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse

    ncgi::importFile -type the_file_name

} text/html


test ncgi-16.3 {ncgi::importFile -- file contents} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse

    ncgi::importFile -data the_file_name

} $binary_content


test ncgi-16.4 {ncgi::importFile -- save file} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse

    set localfile [ncgi::importFile -server the_file_name]

    # get the contents of the local file to verify
    set in [open $localfile]
    fconfigure $in -translation binary
    set content [read $in]
    close $in
    file delete $localfile
    set content

} $binary_content


test ncgi-16.5 {ncgi::importFile -- save file, given name} {

    global binary_content

    set X [genformdata $binary_content]

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse

    set localfile [ncgi::importFile -server the_file_name fofo]

    # get the contents of the local file to verify
    set in [open $localfile]
    fconfigure $in -translation binary
    set content [read $in]
    close $in
    file delete $localfile
    set content

} $binary_content


test ncgi-16.6 {ncgi::importFile -- bad input} {

    set X "bad multipart data"

    ncgi::reset post $X contenttype [genformcontent_type]
    ncgi::parse

    ncgi::importFile -client the_file_name

} {}


test ncgi-17.1 {ncgi::names} {
    ncgi::reset query name=hello+world&name2=%7ewelch
    ncgi::names
} {name name2}


test ncgi-17.2 {ncgi::names} {
    ncgi::reset  query name=&name2 contenttype application/x-www-urlencoded
    ncgi::names
} {name}


test ncgi-17.3 {ncgi::names} {
    ncgi::reset query name=&name2 \
	contenttype application/x-www-form-urlencoded
    ncgi::names
} {name}


test ncgi-17.4 {ncgi::names} {
    ncgi::reset query name=&name2 contenttype application/xyzzy
    set code [catch ncgi::names err]
    list $code $err
} {1 {Unknown Content-Type: application/xyzzy}}

# -------------------------------------------------------------------------

testsuiteCleanup






return







|
>
>

|
|
|
|
|

<

|
|
|
|

>
|
|
|
|

>
|
|
|
|

>
|
|
|
|
|

>
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|

>
|
|
|
|
|

>
|
|
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|

>
|
|
|
|

<
<
<
<

|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<

|
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>
|
|
|
|
|

>
|
|
|
<
|

>
|
|
|
|
<
<
<
<
<
|
<

<
<
<
<
<

|
<
<
<
<
|
<
<
<
|
|

<
<
<
<
<

|
|
<
<
|
|

|
<
|
|
|
|

|
<
|
|
|
>
|

|
|
|
>
|
|
|
>
|
|

|
|
|
<
<
|
|
|
<

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|


|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|

|
|
|
>
|

|
|
|
|

|
|
|
|
|
|
|
|
|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|

|
|
|
|
|
|
|
|
|

|
|
>
|
|









|
<
<
<
|
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
|
|
<
|

|
|
|
|
<
|
|




|

|

|

|
|
|
|

|
>
>
|
<

|
|
<
<

|
|
|

|
|

<
<
<
<
<
<
<
<
|
<
<
<
<
|
<
|
|
<
<
<
>


|
<
|
<
|
<
|
|
>

<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
|
<
|
<
|
<
<
|
<
|
>

|

|

|

|
|

|

|
|
|
|
|
|
|

|
>

|

|

|

|
|

|

|
|
|
|
|
|
|

|


|

|

|
|

|

|


|
|
|
|

>
|
|
|
|

>
|
|
|
|
|

>
|
|
|
|
|

|

|
>
>
>
>
>
>

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










701
702

703
704
705
706
707
708

709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729

730
731
732


733
734
735
736
737
738
739
740








741




742

743
744



745
746
747
748

749

750

751
752
753
754

755
















756

757

758

759


760

761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
testsNeedTcl     8.4
testsNeedTcltest 2

testing {
    useLocal ncgi.tcl ncgi
}

proc main {} {
	global env
	global auto_path

	set     sub_ap $auto_path
	lappend sub_ap $::tcltest::testsDirectory
	set ncgiFile   [localPath ncgi.tcl]
	set futlFile   [tcllibPath fileutil/fileutil.tcl]
	set cmdlFile   [tcllibPath cmdline/cmdline.tcl]



	test ncgi-1.1 {[ncgi reset]} {
		ncgi::reset
		list [info exist ncgi::query] [info exist ncgi::contenttype]
	} {0 0}


	test ncgi-1.2 {[ncgi reset]} {
		ncgi reset querystring query=reset
		list $ncgi::querystring $ncgi::contenttype
	} {query=reset {}}


	test ncgi-1.3 {[ncgi reset]} {
		ncgi reset querystring query=reset contenttype text/plain
		list $ncgi::querystring $ncgi::contenttype
	} {query=reset text/plain}


	test ncgi-2.1 {[ncgi query] fake query data} {
		ncgi reset querystring fake=query
		ncgi query parse
		list $ncgi::querystring $ncgi::query
	} {fake=query {fake query}}


	test ncgi-2.2 {[ncgi query] GET} {
		ncgi reset
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) name=value
		ncgi query parse 
		list $ncgi::querystring $ncgi::query
	} {name=value {name value}}


	test ncgi-2.3 {[ncgi query] HEAD} {
		ncgi reset
		set env(REQUEST_METHOD) HEAD
		catch {unset env(QUERY_STRING)}
		ncgi query parse
		set ncgi::query
	} {} 


	test ncgi-2.4 {[ncgi query] POST} {
		ncgi reset
		catch {unset env(QUERY_STRING)}
		set env(REQUEST_METHOD) POST
		set env(CONTENT_LENGTH) 10
		makeFile [format {
		set auto_path {%s}
		source {%s}
		source {%s}
		source {%s}
		ncgi body
		puts $::ncgi::body

		} $sub_ap $cmdlFile $futlFile $ncgiFile] test1 ; # {}
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		puts  $f "name=value"
		flush $f
		gets  $f line
		close $f
		removeFile test1
		set line
	} name=value


	test ncgi-2.5 {ncgi::test} {
		ncgi reset
		set env(CONTENT_TYPE) text/html
		ncgi type
	} text/html


	test ncgi-2.6 {ncgi::test} {
		ncgi reset querystring foo=bar contenttype text/plain
		set env(CONTENT_TYPE) text/html
		ncgi type
	} text/plain


	test ncgi-3.1 {ncgi::decode} {
		ncgi decode abcdef0123
	} abcdef0123


	test ncgi-3.2 {ncgi::decode} {
		ncgi decode {[abc]def$0123\x}
	} {[abc]def$0123\x}


	test ncgi-3.3 {ncgi::decode} {
		ncgi decode {[a%25c]def$01%7E3\x%3D}
	} {[a%c]def$01~3\x=}


	test ncgi-3.4 {ncgi::decode} {
		ncgi decode {hello+world}
	} {hello world}


	test ncgi-3.5 {ncgi::decode} {
		ncgi decode {aik%C5%ABloa}
	} "aik\u016Bloa" ; # u+macron


	test ncgi-3.6 {ncgi::decode} {
		ncgi decode {paran%C3%A1}
	} "paran\u00E1" ; # a+acute


	test ncgi-3.7 {ncgi::decode, bug 3601995} {
		ncgi decode {%C4%85}
	} "\u0105" ; # a+ogonek


	test ncgi-3.8 {ncgi::decode, bug 3601995} {
		ncgi decode {%E2%80%A0}
	} "\u2020" ; # dagger


	test ncgi-3.9 {ncgi::decode, bug 3601995} {
		ncgi decode {%E2%A0%90}
	} "\u2810" ; # a braille pattern


	test ncgi-3.10 {ncgi::decode, bug 3601995} {
		ncgi decode {%E2%B1}
	} "%E2%B1" ; # missing byte trailing %A0, do not accept/decode, pass through.


	test ncgi-4.1 {ncgi::encode} {
		ncgi encode abcdef0123
	} abcdef0123


	test ncgi-4.2 {ncgi::encode} {
		ncgi encode "\[abc\]def\$0123\\x"
	} {%5Babc%5Ddef%240123%5Cx}


	test ncgi-4.3 {ncgi::encode} {
		ncgi encode {hello world}
	} {hello+world}


	test ncgi-4.4 {ncgi::encode} {
		ncgi encode "hello\nworld\r\tbar"
	} {hello%0D%0Aworld%0D%09bar}


	test ncgi-5.1 {ncgi::query parse} {
		ncgi reset querystring name=hello+world&name2=%7ewelch
		ncgi query parse
	} {name {hello world} name2 ~welch}






	test ncgi-5.2 {ncgi::merge} {
		ncgi reset querystring name=&name2 contenttype application/x-www-urlencoded
		ncgi merge
	} {name {} name2 {}}















	test ncgi-5.3 {ncgi::merge} {
		ncgi reset querystring name=&name2 contenttype application/x-www-form-urlencoded
		ncgi merge
	} {name {} name2 {}}


	test ncgi-5.4.1 {ncgi::merge} {
		ncgi reset querystring name=&name2 contenttype application/xyzzy
		set code [catch ncgi::merge err]
		list $code $err
	} {0 {name {} name2 {}}}

	test ncgi-5.4.2 {ncgi::merge} {
		ncgi reset body name=&name2 contenttype application/xyzzy
		set code [catch ncgi::merge err]
		list $code $err
	} {0 {name {} name2 {}}}

	test ncgi-5.4.3 {ncgi::merge} {
		ncgi reset body name=&name2 contenttype application/xyzzy
		set code [catch {ncgi::form get} err]
		list $code $err
	} {1 {Unknown Content-Type: application/xyzzy}}


	# multipart tests at the end because I'm too lazy to renumber the tests

	test ncgi-6.1 {ncgi::parse, anonymous values, redundant keys} {
		ncgi reset querystring name=&name2
		ncgi query parse
	} {name {} name2 {}}


	test ncgi-7.1 {ncgi::get} {
		ncgi reset querystring name=value&name2=value2
		catch {unset env(REQUEST_METHOD)}
		ncgi get
	} {name value name2 value2}


	test ncgi-7.2 {ncgi::get} {
		ncgi reset querystring nameList=value1+stuff&nameList=value2+more
		ncgi all nameList

	} {{value1 stuff} {value2 more}}


	test ncgi-7.3 {ncgi::get} {
		ncgi reset querystring name=value&name=value2
		catch {ncgi get} err
		set err





	} {name value name value2}








	test ncgi-8.1.1 {ncgi::value} {




		ncgi reset querystring nameList=val+ue&nameList=value2



		ncgi get nameList
	} value2







	test ncgi-8.1.2 {ncgi::value} {
		ncgi reset querystring nameList=val+ue&nameList=value2


		ncgi all nameList
	} {{val ue} value2}



	test ncgi-8.2.1 {ncgi::value} {
		ncgi reset querystring name=val+ue&name=value2
		ncgi get name
	} value2 



	test ncgi-8.2.2 {ncgi::value} {
		ncgi reset querystring name=val+ue&name=value2
		ncgi all name
	} {{val ue} value2}


	test ncgi-8.3 {ncgi::get default} {
		ncgi reset querystring name=val+ue&name=value2
		ncgi get noname
	} {}


	test ncgi-9.1 {ncgi::valueList} {
		ncgi reset querystring name=val+ue&name=value2
		ncgi all name
	} {{val ue} value2}


	test ncgi-9.2 {ncgi::valueList} {
		ncgi reset querystring name=val+ue&name=value2


		ncgi all noname
	} {}



	set URL http://www.tcltk.com/index.html
	test ncgi-11.1 {ncgi::redirect} {
		set env(REQUEST_URI) http://www.scriptics.com/cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 80
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL\n\nPlease go to <a href=\"$URL\">$URL</a>\n"


	set URL /elsewhere/foo.html
	set URL2 http://www/elsewhere/foo.html
	test ncgi-11.2 {ncgi::redirect} {
		set env(REQUEST_URI) http://www/cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 80
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::setCookie -name CookieName -value 12345
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\nSet-Cookie: CookieName=12345 ;\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	set URL foo.html
	set URL2 http://www.scriptics.com/cgi-bin/foo.html
	test ncgi-11.3 {ncgi::redirect} {
		set env(REQUEST_URI) http://www.scriptics.com/cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 80
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	set URL foo.html
	set URL2 http://www.scriptics.com/cgi-bin/foo.html
	test ncgi-11.4 {ncgi::redirect} {
		set env(REQUEST_URI) /cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 80
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	set URL foo.html
	set URL2 http://www.scriptics.com:8000/cgi-bin/foo.html
	test ncgi-11.5 {ncgi::redirect} {
		set env(REQUEST_URI) /cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 8000
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	set URL foo.html
	set URL2 https://www.scriptics.com/cgi-bin/foo.html
	test ncgi-11.6 {ncgi::redirect} {
		set env(REQUEST_URI) /cgi-bin/test.cgi
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) www.scriptics.com
		set env(SERVER_PORT) 443
		set env(HTTPS) "on"
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	set URL  login.tcl
	set URL2 https://foo.com/cgi-bin/login.tcl
	test ncgi-11.7 {ncgi::redirect} {
		set env(REQUEST_URI) https://foo.com/cgi-bin/view.tcl?path=/a/b/c
		set env(REQUEST_METHOD) GET
		set env(QUERY_STRING) {}
		set env(SERVER_NAME) foo.com
		set env(SERVER_PORT) 443
		set env(HTTPS) "on"
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::redirect %s
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile $URL] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nLocation: $URL2\n\nPlease go to <a href=\"$URL2\">$URL2</a>\n"


	test ncgi-12.1 {ncgi::header} {
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::header
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\n\n"


	test ncgi-12.2 {ncgi::header} {
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::header text/plain
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/plain\n\n"


	test ncgi-12.3 {ncgi::header} {
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::header text/html X-Comment "This is a test"
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nX-Comment: This is a test\n\n"


	test ncgi-12.4 {ncgi::header} {
		makeFile [format {
		set auto_path {%s}
		if {[catch {
			source %s
			source %s
			source %s
			ncgi::setCookie -name Name -value {The+Value}
			ncgi::header
		} err]} {
			puts $err
		}
		exit
		} $sub_ap $cmdlFile $futlFile $ncgiFile] test1
		set f [open "|[list $::tcltest::tcltest test1]" r+]
		set res [read $f]
		close $f
		removeFile test1
		set res
	} "Content-Type: text/html\nSet-Cookie: Name=The+Value ;\n\n"


































	test ncgi-14.1 {ncgi::multipart} {
		catch {ncgi::multipart "application/x-www-urlencoded" name=val+ue} err
		set err
	} {not a multipart message}

	test ncgi-14.2 {ncgi::multipart} {
		catch {ncgi::multipart "multipart/form-data" {}} err
		set err
	} {end-of-string encountered while parsing multipart/form-data}


	test ncgi-14.3 {ncgi::multipart} {
		set in [open [file join [file dirname [info script]] formdata.txt]]
		set X [read $in]
		close $in

		foreach line [split $X \n] {
		if {[string length $line] == 0} {
			break
		}
		if {[regexp {^Content-Type: (.*)$} $line x type]} {
			break
		}
		}
		regsub ".*?\n\n" $X {} X

		ncgi::multipart $type $X
	} [list \
		field1 [list value [list \
			Content-Disposition {form-data {name field1}} \
			Content-Type {text/plain {charset us-ascii}}
		]] \
		field2 [list {another value} [list \
			Content-Disposition {form-data {name field2}}
		]] \
		the_file_name [list {
<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


} \
	[list \
		filename {C:Program FilesNetscapeCommunicatorProgramnareadme.htm} \
		name the_file_name
	]]]

	
	test ncgi-14.4 {ncgi::multipart} {
		set in [open [file join [file dirname [info script]] formdata.txt]]
		set X [read $in]
		close $in

		foreach line [split $X \n] {
		if {[string length $line] == 0} {
			break
		}
		if {[regexp {^Content-Type: (.*)$} $line x type]} {
			break
		}
		}
		regsub ".*?\n\n" $X {} X

		ncgi::reset body $X contenttype $type
		list [ncgi::get field1] [ncgi::get field2] [ncgi::get the_file_name]
	} {value {another value} {
<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


}}

	variable binary_content "\r
	\r
	<center><h1>\r
					  Netscape Address Book Sync for Palm Pilot\r
											 User Guide\r
	</h1></center>\r
	\r
	"

	test ncgi-14.5 {ncgi::multipart--check binary file} {
		set X [genformdata $binary_content]
		ncgi::reset body $X contenttype [genformcontent_type]
		set content [ncgi::get the_file_name]
		list [ncgi::get field1] [ncgi::get field2] $content
	} [list value {another value} [string map [list \r {}] $binary_content]]



	test ncgi-14.6 {ncgi::multipart [query set]} {
		set in [open [file join [file dirname [info script]] formdata.txt]]
		set X [read $in]
		close $in

		foreach line [split $X \n] {
		if {[string length $line] == 0} {
			break
		}
		if {[regexp {^Content-Type: (.*)$} $line x type]} {
			break
		}
		}
		regsub ".*?\n\n" $X {} X

		ncgi reset body $X contenttype $type
		ncgi query set userval1 foo
		ncgi query set userval2 {a b}
		list [ncgi get field1] [ncgi get field2] [ncgi get userval1] [ncgi get userval2] [ncgi get the_file_name]
	} {value {another value} foo {a b} {
<center><h1>
                  Netscape Address Book Sync for Palm Pilot
                                         User Guide
</h1></center>


}}


	test ncgi-15.1.1 {ncgi query set} {



		ncgi reset querystring nameList=val+ue&nameList=value2









		ncgi query set foo 1










		ncgi query set bar {a b}
		list [ncgi get nameList] [ncgi get foo] [ncgi get bar]

	} {value2 1 {a b}}

	test ncgi-15.1.2 {ncgi query set} {
		ncgi reset querystring nameList=val+ue&nameList=value2
		ncgi query set foo 1
		ncgi query set bar {a b}

		list [ncgi all nameList] [ncgi get foo] [ncgi get bar]
	} {{{val ue} value2} 1 {a b}}




	## ------------ tests for binary content and file upload ----------------

	## some utility procedures to generate content 

	variable form_boundary {17661509020136}

	proc genformcontent_type {} {
		variable form_boundary
		return "multipart/form-data; boundary=\"$form_boundary\""
	}

	proc genformdatapart {name cd value} {
		variable form_boundary
		return "--$form_boundary\nContent-Disposition: form-data; name=\"$name\"$cd\n\n$value\n"
	}


	proc genformdata {bcontent} {
		variable form_boundary



		set a [genformdatapart field1 "" {value}]
		set b [genformdatapart field2 "" {another value}]
		set c [genformdatapart the_file_name "; filename=\"C:\\Program Files\\Netscape\\Communicator\\Program\\nareadme.htm\"\nContent-Type: text/html" $bcontent]

		return "$a$b$c--$form_boundary--\n" 
	}









	test ncgi-16.1 {ncgi::importFile} {




		set X [genformdata $binary_content]

		ncgi::reset body $X contenttype [genformcontent_type]
		ncgi::importFile -client the_file_name



	} {C:Program FilesNetscapeCommunicatorProgramnareadme.htm}


	test ncgi-16.2 {ncgi::importFile - content type} {

		global binary_content

		set X [genformdata $binary_content]

		ncgi::reset post $X contenttype [genformcontent_type]
		ncgi::importFile -type the_file_name
	} text/html



















	test ncgi-16.3 {ncgi::importFile -- file contents} {

		global binary_content

		set X [genformdata $binary_content]

		ncgi::reset post $X contenttype [genformcontent_type]


		ncgi::importFile -data the_file_name

	} $binary_content


	test ncgi-16.4 {ncgi::importFile -- save file} {

		global binary_content

		set X [genformdata $binary_content]

		ncgi::reset post $X contenttype [genformcontent_type]
		ncgi::parse

		set localfile [ncgi::importFile -server the_file_name]

		# get the contents of the local file to verify
		set in [open $localfile]
		fconfigure $in -translation binary
		set content [read $in]
		close $in
		file delete $localfile
		set content

	} $binary_content


	test ncgi-16.5 {ncgi::importFile -- save file, given name} {

		global binary_content

		set X [genformdata $binary_content]

		ncgi::reset post $X contenttype [genformcontent_type]
		ncgi::parse

		set localfile [ncgi::importFile -server the_file_name fofo]

		# get the contents of the local file to verify
		set in [open $localfile]
		fconfigure $in -translation binary
		set content [read $in]
		close $in
		file delete $localfile
		set content

	} $binary_content


	test ncgi-16.6 {ncgi::importFile -- bad input} {

		set X "bad multipart data"

		ncgi::reset post $X contenttype [genformcontent_type]
		ncgi::parse

		ncgi::importFile -client the_file_name

	} {}


	test ncgi-17.1 {ncgi::names} {
		ncgi::reset querystring name=hello+world&name2=%7ewelch
		ncgi::names
	} {name name2}


	test ncgi-17.2 {ncgi::names} {
		ncgi::reset querystring name=&name2 contenttype application/x-www-urlencoded
		ncgi::names
	} {name}


	test ncgi-17.3 {ncgi::names} {
		ncgi::reset querystring name=&name2 \
			contenttype application/x-www-form-urlencoded
		ncgi::names
	} {name}


	test ncgi-17.4 {ncgi::names} {
		ncgi::reset querystring name=&name2 contenttype application/xyzzy
		set code [catch ncgi::names err]
		list $code $err
	} {1 {Unknown Content-Type: application/xyzzy}}

	# -------------------------------------------------------------------------

	testsuiteCleanup
	set [namespace current]::done 1
	return
}

after 0 [list ::coroutine [info cmdcount]_main [namespace current]::main]
vwait [namespace current]::done
return