1# ncgi.tcl
2#
3# Basic support for CGI programs
4#
5# Copyright (c) 2000 Ajuba Solutions.
6# Copyright (c) 2012 Richard Hipp, Andreas Kupries
7# Copyright (c) 2013-2014 Andreas Kupries
8#
9# See the file "license.terms" for information on usage and redistribution
10# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11
12
13# Please note that Don Libes' has a "cgi.tcl" that implements version 1.0
14# of the cgi package.  That implementation provides a bunch of cgi_ procedures
15# (it doesn't use the ::cgi:: namespace) and has a wealth of procedures for
16# generating HTML.  In contract, the package provided here is primarly
17# concerned with processing input to CGI programs.  I have tried to mirror his
18# API's where possible.  So, ncgi::input is equivalent to cgi_input, and so
19# on.  There are also some different APIs for accessing values (ncgi::list,
20# ncgi::parse and ncgi::value come to mind)
21
22# Note, I use the term "query data" to refer to the data that is passed in
23# to a CGI program.  Typically this comes from a Form in an HTML browser.
24# The query data is composed of names and values, and the names can be
25# repeated.  The names and values are encoded, and this module takes care
26# of decoding them.
27
28# We use newer string routines
29package require Tcl 8.4
30package require fileutil ; # Required by importFile.
31package require uri
32
33package provide ncgi 1.4.4
34
35namespace eval ::ncgi {
36
37    # "query" holds the raw query (i.e., form) data
38    # This is treated as a cache, too, so you can call ncgi::query more than
39    # once
40
41    variable query
42
43    # This is the content-type which affects how the query is parsed
44
45    variable contenttype
46
47    # value is an array of parsed query data.  Each array element is a list
48    # of values, and the array index is the form element name.
49    # See the differences among ncgi::parse, ncgi::input, ncgi::value
50    # and ncgi::valuelist for the various approaches to handling these values.
51
52    variable value
53
54    # This lists the names that appear in the query data
55
56    variable varlist
57
58    # This holds the URL coresponding to the current request
59    # This does not include the server name.
60
61    variable urlStub
62
63    # This flags compatibility with Don Libes cgi.tcl when dealing with
64    # form values that appear more than once.  This bit gets flipped when
65    # you use the ncgi::input procedure to parse inputs.
66
67    variable listRestrict 0
68
69    # This is the set of cookies that are pending for output
70
71    variable cookieOutput
72
73    # Support for x-www-urlencoded character mapping
74    # The spec says: "non-alphanumeric characters are replaced by '%HH'"
75
76    variable i
77    variable c
78    variable map
79
80    for {set i 1} {$i <= 256} {incr i} {
81	set c [format %c $i]
82	if {![string match \[a-zA-Z0-9\] $c]} {
83	    set map($c) %[format %.2X $i]
84	}
85    }
86
87    # These are handled specially
88    array set map {
89	" " +   \n %0D%0A
90    }
91
92    # Map of transient files
93
94    variable  _tmpfiles
95    array set _tmpfiles {}
96
97    # I don't like importing, but this makes everything show up in
98    # pkgIndex.tcl
99
100    namespace export reset urlStub query type decode encode
101    namespace export nvlist parse input value valueList names
102    namespace export setValue setValueList setDefaultValue setDefaultValueList
103    namespace export empty import importAll importFile redirect header
104    namespace export parseMimeValue multipart cookie setCookie
105}
106
107# ::ncgi::reset
108#
109#	This resets the state of the CGI input processor.  This is primarily
110#	used for tests, although it is also designed so that TclHttpd can
111#	call this with the current query data
112#	so the ncgi package can be shared among TclHttpd and CGI scripts.
113#
114#	DO NOT CALL this in a standard cgi environment if you have not
115#	yet processed the query data, which will not be used after a
116#	call to ncgi::reset is made.  Instead, just call ncgi::parse
117#
118# Arguments:
119#	newquery	The query data to be used instead of external CGI.
120#	newtype		The raw content type.
121#
122# Side Effects:
123#	Resets the cached query data and wipes any environment variables
124#	associated with CGI inputs (like QUERY_STRING)
125
126proc ::ncgi::reset {args} {
127    global env
128    variable _tmpfiles
129    variable query
130    variable contenttype
131    variable cookieOutput
132
133    # array unset _tmpfiles -- Not a Tcl 8.2 idiom
134    unset _tmpfiles ; array set _tmpfiles {}
135
136    set cookieOutput {}
137    if {[llength $args] == 0} {
138
139	# We use and test args here so we can detect the
140	# difference between empty query data and a full reset.
141
142	if {[info exists query]} {
143	    unset query
144	}
145	if {[info exists contenttype]} {
146	    unset contenttype
147	}
148    } else {
149	set query [lindex $args 0]
150	set contenttype [lindex $args 1]
151    }
152}
153
154# ::ncgi::urlStub
155#
156#	Set or return the URL associated with the current page.
157#	This is for use by TclHttpd to override the default value
158#	that otherwise comes from the CGI environment
159#
160# Arguments:
161#	url	(option) The url of the page, not counting the server name.
162#		If not specified, the current urlStub is returned
163#
164# Side Effects:
165#	May affects future calls to ncgi::urlStub
166
167proc ::ncgi::urlStub {{url {}}} {
168    global   env
169    variable urlStub
170    if {[string length $url]} {
171	set urlStub $url
172	return ""
173    } elseif {[info exists urlStub]} {
174	return $urlStub
175    } elseif {[info exists env(SCRIPT_NAME)]} {
176	set urlStub $env(SCRIPT_NAME)
177	return $urlStub
178    } else {
179	return ""
180    }
181}
182
183# ::ncgi::query
184#
185#	This reads the query data from the appropriate location, which depends
186#	on if it is a POST or GET request.
187#
188# Arguments:
189#	none
190#
191# Results:
192#	The raw query data.
193
194proc ::ncgi::query {} {
195    global env
196    variable query
197
198    if {[info exists query]} {
199	# This ensures you can call ncgi::query more than once,
200	# and that you can use it with ncgi::reset
201	return $query
202    }
203
204    set query ""
205    if {[info exists env(REQUEST_METHOD)]} {
206	if {$env(REQUEST_METHOD) == "GET"} {
207	    if {[info exists env(QUERY_STRING)]} {
208		set query $env(QUERY_STRING)
209	    }
210	} elseif {$env(REQUEST_METHOD) == "POST"} {
211	    if {[info exists env(CONTENT_LENGTH)] &&
212		    [string length $env(CONTENT_LENGTH)] != 0} {
213 		## added by Steve Cassidy to try to fix binary file upload
214 		fconfigure stdin -translation binary -encoding binary
215		set query [read stdin $env(CONTENT_LENGTH)]
216	    }
217	}
218    }
219    return $query
220}
221
222# ::ncgi::type
223#
224#	This returns the content type of the query data.
225#
226# Arguments:
227#	none
228#
229# Results:
230#	The content type of the query data.
231
232proc ::ncgi::type {} {
233    global env
234    variable contenttype
235
236    if {![info exists contenttype]} {
237	if {[info exists env(CONTENT_TYPE)]} {
238	    set contenttype $env(CONTENT_TYPE)
239	} else {
240	    return ""
241	}
242    }
243    return $contenttype
244}
245
246# ::ncgi::decode
247#
248#	This decodes data in www-url-encoded format.
249#
250# Arguments:
251#	An encoded value
252#
253# Results:
254#	The decoded value
255
256if {[package vsatisfies [package present Tcl] 8.6]} {
257    # 8.6+, use 'binary decode hex'
258    proc ::ncgi::DecodeHex {hex} {
259	return [binary decode hex $hex]
260    }
261} else {
262    # 8.4+. More complex way of handling the hex conversion.
263    proc ::ncgi::DecodeHex {hex} {
264	return [binary format H* $hex]
265    }
266}
267
268proc ::ncgi::decode {str} {
269    # rewrite "+" back to space
270    # protect \ from quoting another '\'
271    set str [string map [list + { } "\\" "\\\\" \[ \\\[ \] \\\]] $str]
272
273    # prepare to process all %-escapes
274    regsub -all -nocase -- {%([E][A-F0-9])%([89AB][A-F0-9])%([89AB][A-F0-9])} \
275	$str {[encoding convertfrom utf-8 [DecodeHex \1\2\3]]} str
276    regsub -all -nocase -- {%([CDcd][A-F0-9])%([89AB][A-F0-9])} \
277	$str {[encoding convertfrom utf-8 [DecodeHex \1\2]]} str
278    regsub -all -nocase -- {%([A-F0-9][A-F0-9])} $str {\\u00\1} str
279
280    # process \u unicode mapped chars
281    return [subst -novar $str]
282}
283
284# ::ncgi::encode
285#
286#	This encodes data in www-url-encoded format.
287#
288# Arguments:
289#	A string
290#
291# Results:
292#	The encoded value
293
294proc ::ncgi::encode {string} {
295    variable map
296
297    # 1 leave alphanumerics characters alone
298    # 2 Convert every other character to an array lookup
299    # 3 Escape constructs that are "special" to the tcl parser
300    # 4 "subst" the result, doing all the array substitutions
301
302    regsub -all -- \[^a-zA-Z0-9\] $string {$map(&)} string
303    # This quotes cases like $map([) or $map($) => $map(\[) ...
304    regsub -all -- {[][{})\\]\)} $string {\\&} string
305    return [subst -nocommand $string]
306}
307
308# ::ncgi::names
309#
310#	This parses the query data and returns a list of the names found therein.
311#
312# 	Note: If you use ncgi::setValue or ncgi::setDefaultValue, this
313#	names procedure doesn't see the effect of that.
314#
315# Arguments:
316#	none
317#
318# Results:
319#	A list of names
320
321proc ::ncgi::names {} {
322    array set names {}
323    foreach {name val} [nvlist] {
324        if {![string equal $name "anonymous"]} {
325            set names($name) 1
326        }
327    }
328    return [array names names]
329}
330
331# ::ncgi::nvlist
332#
333#	This parses the query data and returns it as a name, value list
334#
335# 	Note: If you use ncgi::setValue or ncgi::setDefaultValue, this
336#	nvlist procedure doesn't see the effect of that.
337#
338# Arguments:
339#	none
340#
341# Results:
342#	An alternating list of names and values
343
344proc ::ncgi::nvlist {} {
345    set query [query]
346    set type  [type]
347    switch -glob -- $type {
348	"" -
349	text/xml* -
350	application/x-www-form-urlencoded* -
351	application/x-www-urlencoded* {
352	    set result {}
353
354	    # Any whitespace at the beginning or end of urlencoded data is not
355	    # considered to be part of that data, so we trim it off.  One special
356	    # case in which post data is preceded by a \n occurs when posting
357	    # with HTTPS in Netscape.
358
359	    foreach {x} [split [string trim $query] &] {
360		# Turns out you might not get an = sign,
361		# especially with <isindex> forms.
362
363		set pos [string first = $x]
364		set len [string length $x]
365
366		if { $pos>=0 } {
367		    if { $pos == 0 } { # if the = is at the beginning ...
368		        if { $len>1 } {
369                            # ... and there is something to the right ...
370		            set varname anonymous
371		            set val [string range $x 1 end]
372		        } else {
373                            # ... otherwise, all we have is an =
374		            set varname anonymous
375		            set val ""
376		        }
377		    } elseif { $pos==[expr {$len-1}] } {
378                        # if the = is at the end ...
379		        set varname [string range $x 0 [expr {$pos-1}]]
380			set val ""
381		    } else {
382		        set varname [string range $x 0 [expr {$pos-1}]]
383		        set val [string range $x [expr {$pos+1}] end]
384		    }
385		} else { # no = was found ...
386		    set varname anonymous
387		    set val $x
388		}
389		lappend result [decode $varname] [decode $val]
390	    }
391	    return $result
392	}
393	multipart/* {
394	    return [multipart $type $query]
395	}
396	default {
397	    return -code error "Unknown Content-Type: $type"
398	}
399    }
400}
401
402# ::ncgi::parse
403#
404#	The parses the query data and stores it into an array for later retrieval.
405#	You should use the ncgi::value or ncgi::valueList procedures to get those
406#	values, or you are allowed to access the ncgi::value array directly.
407#
408#	Note - all values have a level of list structure associated with them
409#	to allow for multiple values for a given form element (e.g., a checkbox)
410#
411# Arguments:
412#	none
413#
414# Results:
415#	A list of names of the query values
416
417proc ::ncgi::parse {} {
418    variable value
419    variable listRestrict 0
420    variable varlist {}
421    if {[info exists value]} {
422	unset value
423    }
424    foreach {name val} [nvlist] {
425	if {![info exists value($name)]} {
426	    lappend varlist $name
427	}
428	lappend value($name) $val
429    }
430    return $varlist
431}
432
433# ::ncgi::input
434#
435#	Like ncgi::parse, but with Don Libes cgi.tcl semantics.
436#	Form elements must have a trailing "List" in their name to be
437#	listified, otherwise this raises errors if an element appears twice.
438#
439# Arguments:
440#	fakeinput	See ncgi::reset
441#	fakecookie	The raw cookie string to use when testing.
442#
443# Results:
444#	The list of element names in the form
445
446proc ::ncgi::input {{fakeinput {}} {fakecookie {}}} {
447    variable value
448    variable varlist {}
449    variable listRestrict 1
450    if {[info exists value]} {
451	unset value
452    }
453    if {[string length $fakeinput]} {
454	ncgi::reset $fakeinput
455    }
456    foreach {name val} [nvlist] {
457	set exists [info exists value($name)]
458	if {!$exists} {
459	    lappend varlist $name
460	}
461	if {[string match "*List" $name]} {
462	    # Accumulate a list of values for this name
463	    lappend value($name) $val
464	} elseif {$exists} {
465	    error "Multiple definitions of $name encountered in input.\
466	    If you're trying to do this intentionally (such as with select),\
467	    the variable must have a \"List\" suffix."
468	} else {
469	    # Capture value with no list structure
470	    set value($name) $val
471	}
472    }
473    return $varlist
474}
475
476# ::ncgi::value
477#
478#	Return the value of a named query element, or the empty string if
479#	it was not not specified.  This only returns the first value of
480#	associated with the name.  If you want them all (like all values
481#	of a checkbox), use ncgi::valueList
482#
483# Arguments:
484#	key	The name of the query element
485#	default	The value to return if the value is not present
486#
487# Results:
488#	The first value of the named element, or the default
489
490proc ::ncgi::value {key {default {}}} {
491    variable value
492    variable listRestrict
493    variable contenttype
494    if {[info exists value($key)]} {
495	if {$listRestrict} {
496
497	    # ::ncgi::input was called, and it already figured out if the
498	    # user wants list structure or not.
499
500	    set val $value($key)
501	} else {
502
503	    # Undo the level of list structure done by ncgi::parse
504
505	    set val [lindex $value($key) 0]
506	}
507	if {[string match multipart/* [type]]} {
508
509	    # Drop the meta-data information associated with each part
510
511	    set val [lindex $val 1]
512	}
513	return $val
514    } else {
515	return $default
516    }
517}
518
519# ::ncgi::valueList
520#
521#	Return all the values of a named query element as a list, or
522#	the empty list if it was not not specified.  This always returns
523#	lists - if you do not want the extra level of listification, use
524#	ncgi::value instead.
525#
526# Arguments:
527#	key	The name of the query element
528#
529# Results:
530#	The first value of the named element, or ""
531
532proc ::ncgi::valueList {key {default {}}} {
533    variable value
534    if {[info exists value($key)]} {
535	return $value($key)
536    } else {
537	return $default
538    }
539}
540
541# ::ncgi::setValue
542#
543#	Jam a new value into the CGI environment.  This is handy for preliminary
544#	processing that does data validation and cleanup.
545#
546# Arguments:
547#	key	The name of the query element
548#	value	This is a single value, and this procedure wraps it up in a list
549#		for compatibility with the ncgi::value array usage.  If you
550#		want a list of values, use ngci::setValueList
551#
552#
553# Side Effects:
554#	Alters the ncgi::value and possibly the ncgi::valueList variables
555
556proc ::ncgi::setValue {key value} {
557    variable listRestrict
558    if {$listRestrict} {
559	ncgi::setValueList $key $value
560    } else {
561	ncgi::setValueList $key [list $value]
562    }
563}
564
565# ::ncgi::setValueList
566#
567#	Jam a list of new values into the CGI environment.
568#
569# Arguments:
570#	key		The name of the query element
571#	valuelist	This is a list of values, e.g., for checkbox or multiple
572#			selections sets.
573#
574# Side Effects:
575#	Alters the ncgi::value and possibly the ncgi::valueList variables
576
577proc ::ncgi::setValueList {key valuelist} {
578    variable value
579    variable varlist
580    if {![info exists value($key)]} {
581	lappend varlist $key
582    }
583
584    # This if statement is a workaround for another hack in
585    # ::ncgi::value that treats multipart form data
586    # differently.
587    if {[string match multipart/* [type]]} {
588	set value($key) [list [list {} [join $valuelist]]]
589    } else {
590	set value($key) $valuelist
591    }
592    return ""
593}
594
595# ::ncgi::setDefaultValue
596#
597#	Set a new value into the CGI environment if there is not already one there.
598#
599# Arguments:
600#	key	The name of the query element
601#	value	This is a single value, and this procedure wraps it up in a list
602#		for compatibility with the ncgi::value array usage.
603#
604#
605# Side Effects:
606#	Alters the ncgi::value and possibly the ncgi::valueList variables
607
608proc ::ncgi::setDefaultValue {key value} {
609    ncgi::setDefaultValueList $key [list $value]
610}
611
612# ::ncgi::setDefaultValueList
613#
614#	Jam a list of new values into the CGI environment if the CGI value
615#	is not already defined.
616#
617# Arguments:
618#	key		The name of the query element
619#	valuelist	This is a list of values, e.g., for checkbox or multiple
620#			selections sets.
621#
622# Side Effects:
623#	Alters the ncgi::value and possibly the ncgi::valueList variables
624
625proc ::ncgi::setDefaultValueList {key valuelist} {
626    variable value
627    if {![info exists value($key)]} {
628	ncgi::setValueList $key $valuelist
629	return ""
630    } else {
631	return ""
632    }
633}
634
635# ::ncgi::exists --
636#
637#	Return false if the CGI variable doesn't exist.
638#
639# Arguments:
640#	name	Name of the CGI variable
641#
642# Results:
643#	0 if the variable doesn't exist
644
645proc ::ncgi::exists {var} {
646    variable value
647    return [info exists value($var)]
648}
649
650# ::ncgi::empty --
651#
652#	Return true if the CGI variable doesn't exist or is an empty string
653#
654# Arguments:
655#	name	Name of the CGI variable
656#
657# Results:
658#	1 if the variable doesn't exist or has the empty value
659
660proc ::ncgi::empty {name} {
661    return [expr {[string length [string trim [value $name]]] == 0}]
662}
663
664# ::ncgi::import
665#
666#	Map a CGI input into a Tcl variable.  This creates a Tcl variable in
667#	the callers scope that has the value of the CGI input.  An alternate
668#	name for the Tcl variable can be specified.
669#
670# Arguments:
671#	cginame		The name of the form element
672#	tclname		If present, an alternate name for the Tcl variable,
673#			otherwise it is the same as the form element name
674
675proc ::ncgi::import {cginame {tclname {}}} {
676    if {[string length $tclname]} {
677	upvar 1 $tclname var
678    } else {
679	upvar 1 $cginame var
680    }
681    set var [value $cginame]
682}
683
684# ::ncgi::importAll
685#
686#	Map a CGI input into a Tcl variable.  This creates a Tcl variable in
687#	the callers scope for every CGI value, or just for those named values.
688#
689# Arguments:
690#	args	A list of form element names.  If this is empty,
691#		then all form value are imported.
692
693proc ::ncgi::importAll {args} {
694    variable varlist
695    if {[llength $args] == 0} {
696	set args $varlist
697    }
698    foreach cginame $args {
699	upvar 1 $cginame var
700	set var [value $cginame]
701    }
702}
703
704# ::ncgi::redirect
705#
706#	Generate a redirect by returning a header that has a Location: field.
707#	If the URL is not absolute, this automatically qualifies it to
708#	the current server
709#
710# Arguments:
711#	url		The url to which to redirect
712#
713# Side Effects:
714#	Outputs a redirect header
715
716proc ::ncgi::redirect {url} {
717    global env
718
719    if {![regexp -- {^[^:]+://} $url]} {
720
721	# The url is relative (no protocol/server spec in it), so
722	# here we create a canonical URL.
723
724	# request_uri	The current URL used when dealing with relative URLs.
725	# proto		http or https
726	# server 	The server, which we are careful to match with the
727	#		current one in base Basic Authentication is being used.
728	# port		This is set if it is not the default port.
729
730	if {[info exists env(REQUEST_URI)]} {
731	    # Not all servers have the leading protocol spec
732	    #regsub -- {^https?://[^/]*/} $env(REQUEST_URI) / request_uri
733	    array set u [uri::split $env(REQUEST_URI)]
734	    set request_uri /$u(path)
735	    unset u
736	} elseif {[info exists env(SCRIPT_NAME)]} {
737	    set request_uri $env(SCRIPT_NAME)
738	} else {
739	    set request_uri /
740	}
741
742	set port ""
743	if {[info exists env(HTTPS)] && $env(HTTPS) == "on"} {
744	    set proto https
745	    if {$env(SERVER_PORT) != 443} {
746		set port :$env(SERVER_PORT)
747	    }
748	} else {
749	    set proto http
750	    if {$env(SERVER_PORT) != 80} {
751		set port :$env(SERVER_PORT)
752	    }
753	}
754	# Pick the server from REQUEST_URI so it matches the current
755	# URL.  Otherwise use SERVER_NAME.  These could be different, e.g.,
756	# "pop.scriptics.com" vs. "pop"
757
758	if {[info exists env(REQUEST_URI)]} {
759	    # Not all servers have the leading protocol spec
760	    if {![regexp -- {^https?://([^/:]*)} $env(REQUEST_URI) x server]} {
761		set server $env(SERVER_NAME)
762	    }
763	} else {
764	    set server $env(SERVER_NAME)
765	}
766	if {[string match /* $url]} {
767	    set url $proto://$server$port$url
768	} else {
769	    regexp -- {^(.*/)[^/]*$} $request_uri match dirname
770	    set url $proto://$server$port$dirname$url
771	}
772    }
773    ncgi::header text/html Location $url
774    puts "Please go to <a href=\"$url\">$url</a>"
775}
776
777# ncgi:header
778#
779#	Output the Content-Type header.
780#
781# Arguments:
782#	type	The MIME content type
783#	args	Additional name, value pairs to specifiy output headers
784#
785# Side Effects:
786#	Outputs a normal header
787
788proc ::ncgi::header {{type text/html} args} {
789    variable cookieOutput
790    puts "Content-Type: $type"
791    foreach {n v} $args {
792	puts "$n: $v"
793    }
794    if {[info exists cookieOutput]} {
795	foreach line $cookieOutput {
796	    puts "Set-Cookie: $line"
797	}
798    }
799    puts ""
800    flush stdout
801}
802
803# ::ncgi::parseMimeValue
804#
805#	Parse a MIME header value, which has the form
806#	value; param=value; param2="value2"; param3='value3'
807#
808# Arguments:
809#	value	The mime header value.  This does not include the mime
810#		header field name, but everything after it.
811#
812# Results:
813#	A two-element list, the first is the primary value,
814#	the second is in turn a name-value list corresponding to the
815#	parameters.  Given the above example, the return value is
816#	{
817#		value
818#		{param value param2 value param3 value3}
819#	}
820
821proc ::ncgi::parseMimeValue {value} {
822    set parts [split $value \;]
823    set results [list [string trim [lindex $parts 0]]]
824    set paramList [list]
825    foreach sub [lrange $parts 1 end] {
826	if {[regexp -- {([^=]+)=(.+)} $sub match key val]} {
827            set key [string trim [string tolower $key]]
828            set val [string trim $val]
829            # Allow single as well as double quotes
830            if {[regexp -- {^["']} $val quote]} { ;# need a " for balance
831                if {[regexp -- ^${quote}(\[^$quote\]*)$quote $val x val2]} {
832                    # Trim quotes and any extra crap after close quote
833                    set val $val2
834                }
835            }
836            lappend paramList $key $val
837	}
838    }
839    if {[llength $paramList]} {
840	lappend results $paramList
841    }
842    return $results
843}
844
845# ::ncgi::multipart
846#
847#	This parses multipart form data.
848#	Based on work by Steve Ball for TclHttpd, but re-written to use
849#	string first with an offset to iterate through the data instead
850#	of using a regsub/subst combo.
851#
852# Arguments:
853#	type	The Content-Type, because we need boundary options
854#	query	The raw multipart query data
855#
856# Results:
857#	An alternating list of names and values
858#	In this case, the value is a two element list:
859#		headers, which in turn is a list names and values
860#		content, which is the main value of the element
861#	The header name/value pairs come primarily from the MIME headers
862#	like Content-Type that appear in each part.  However, the
863#	Content-Disposition header is handled specially.  It has several
864#	parameters like "name" and "filename" that are important, so they
865#	are promoted to to the same level as Content-Type.  Otherwise,
866#	if a header like Content-Type has parameters, they appear as a list
867#	after the primary value of the header.  For example, if the
868#	part has these two headers:
869#
870#	Content-Disposition: form-data; name="Foo"; filename="/a/b/C.txt"
871#	Content-Type: text/html; charset="iso-8859-1"; mumble='extra'
872#
873#	Then the header list will have this structure:
874#	{
875#		content-disposition form-data
876#		name Foo
877#		filename /a/b/C.txt
878#		content-type {text/html {charset iso-8859-1 mumble extra}}
879#	}
880#	Note that the header names are mapped to all lowercase.  You can
881#	use "array set" on the header list to easily find things like the
882#	filename or content-type.  You should always use [lindex $value 0]
883#	to account for values that have parameters, like the content-type
884#	example above.  Finally, not that if the value has a second element,
885#	which are the parameters, you can "array set" that as well.
886#
887proc ::ncgi::multipart {type query} {
888
889    set parsedType [parseMimeValue $type]
890    if {![string match multipart/* [lindex $parsedType 0]]} {
891	return -code error "Not a multipart Content-Type: [lindex $parsedType 0]"
892    }
893    array set options [lindex $parsedType 1]
894    if {![info exists options(boundary)]} {
895	return -code error "No boundary given for multipart document"
896    }
897    set boundary $options(boundary)
898
899    # The query data is typically read in binary mode, which preserves
900    # the \r\n sequence from a Windows-based browser.
901    # Also, binary data may contain \r\n sequences.
902
903    if {[string match "*$boundary\r\n*" $query]} {
904        set lineDelim "\r\n"
905	#	puts "DELIM"
906    } else {
907        set lineDelim "\n"
908	#	puts "NO"
909    }
910
911    # Iterate over the boundary string and chop into parts
912
913    set len [string length $query]
914    # [string length $lineDelim]+2 is for "$lineDelim--"
915    set blen [expr {[string length $lineDelim] + 2 + \
916            [string length $boundary]}]
917    set first 1
918    set results [list]
919    set offset 0
920
921    # Ensuring the query data starts
922    # with a newline makes the string first test simpler
923    if {[string first $lineDelim $query 0]!=0} {
924        set query $lineDelim$query
925    }
926    while {[set offset [string first $lineDelim--$boundary $query $offset]] \
927            >= 0} {
928	if {!$first} {
929	    lappend results $formName [list $headers \
930		[string range $query $off2 [expr {$offset -1}]]]
931	} else {
932	    set first 0
933	}
934	incr offset $blen
935
936	# Check for the ending boundary, which is signaled by --$boundary--
937
938	if {[string equal "--" \
939		[string range $query $offset [expr {$offset + 1}]]]} {
940	    break
941	}
942
943	# Split headers out from content
944	# The headers become a nested list structure:
945	#	{header-name {
946	#		value {
947	#			paramname paramvalue ... }
948	#		}
949	#	}
950
951        set off2 [string first "$lineDelim$lineDelim" $query $offset]
952	set headers [list]
953	set formName ""
954        foreach line [split [string range $query $offset $off2] $lineDelim] {
955	    if {[regexp -- {([^:	 ]+):(.*)$} $line x hdrname value]} {
956		set hdrname [string tolower $hdrname]
957		set valueList [parseMimeValue $value]
958		if {[string equal $hdrname "content-disposition"]} {
959
960		    # Promote Conent-Disposition parameters up to headers,
961		    # and look for the "name" that identifies the form element
962
963		    lappend headers $hdrname [lindex $valueList 0]
964		    foreach {n v} [lindex $valueList 1] {
965			lappend headers $n $v
966			if {[string equal $n "name"]} {
967			    set formName $v
968			}
969		    }
970		} else {
971		    lappend headers $hdrname $valueList
972		}
973	    }
974	}
975
976	if {$off2 > 0} {
977            # +[string length "$lineDelim$lineDelim"] for the
978            # $lineDelim$lineDelim
979            incr off2 [string length "$lineDelim$lineDelim"]
980	    set offset $off2
981	} else {
982	    break
983	}
984    }
985    return $results
986}
987
988# ::ncgi::importFile --
989#
990#   get information about a file upload field
991#
992# Arguments:
993#   cmd         one of '-server' '-client' '-type' '-data'
994#   var         cgi variable name for the file field
995#   filename    filename to write to for -server
996# Results:
997#   -server returns the name of the file on the server: side effect
998#      is that the file gets stored on the server and the
999#      script is responsible for deleting/moving the file
1000#   -client returns the name of the file sent from the client
1001#   -type   returns the mime type of the file
1002#   -data   returns the contents of the file
1003
1004proc ::ncgi::importFile {cmd var {filename {}}} {
1005
1006    set vlist [valueList $var]
1007
1008    array set fileinfo [lindex [lindex $vlist 0] 0]
1009    set contents [lindex [lindex $vlist 0] 1]
1010
1011    switch -exact -- $cmd {
1012	-server {
1013	    ## take care not to write it out more than once
1014	    variable _tmpfiles
1015	    if {![info exists _tmpfiles($var)]} {
1016		if {$filename != {}} {
1017		    ## use supplied filename
1018		    set _tmpfiles($var) $filename
1019		} else {
1020		    ## create a tmp file
1021		    set _tmpfiles($var) [::fileutil::tempfile ncgi]
1022		}
1023
1024		# write out the data only if it's not been done already
1025		if {[catch {open $_tmpfiles($var) w} h]} {
1026		    error "Can't open temporary file in ncgi::importFile ($h)"
1027		}
1028
1029		fconfigure $h -translation binary -encoding binary
1030		puts -nonewline $h $contents
1031		close $h
1032	    }
1033	    return $_tmpfiles($var)
1034	}
1035	-client {
1036	    if {![info exists fileinfo(filename)]} {return {}}
1037	    return $fileinfo(filename)
1038	}
1039	-type {
1040	    if {![info exists fileinfo(content-type)]} {return {}}
1041	    return $fileinfo(content-type)
1042	}
1043	-data {
1044	    return $contents
1045	}
1046	default {
1047	    error "Unknown subcommand to ncgi::import_file: $cmd"
1048	}
1049    }
1050}
1051
1052
1053# ::ncgi::cookie
1054#
1055#	Return a *list* of cookie values, if present, else ""
1056#	It is possible for multiple cookies with the same key
1057#	to be present, so we return a list.
1058#
1059# Arguments:
1060#	cookie	The name of the cookie (the key)
1061#
1062# Results:
1063#	A list of values for the cookie
1064
1065proc ::ncgi::cookie {cookie} {
1066    global env
1067    set result ""
1068    if {[info exists env(HTTP_COOKIE)]} {
1069	foreach pair [split $env(HTTP_COOKIE) \;] {
1070	    foreach {key value} [split [string trim $pair] =] { break ;# lassign }
1071	    if {[string compare $cookie $key] == 0} {
1072		lappend result $value
1073	    }
1074	}
1075    }
1076    return $result
1077}
1078
1079# ::ncgi::setCookie
1080#
1081#	Set a return cookie.  You must call this before you call
1082#	ncgi::header or ncgi::redirect
1083#
1084# Arguments:
1085#	args	Name value pairs, where the names are:
1086#		-name	Cookie name
1087#		-value	Cookie value
1088#		-path	Path restriction
1089#		-domain	domain restriction
1090#		-expires	Time restriction
1091#
1092# Side Effects:
1093#	Formats and stores the Set-Cookie header for the reply.
1094
1095proc ::ncgi::setCookie {args} {
1096    variable cookieOutput
1097    array set opt $args
1098    set line "$opt(-name)=$opt(-value) ;"
1099    foreach extra {path domain} {
1100	if {[info exists opt(-$extra)]} {
1101	    append line " $extra=$opt(-$extra) ;"
1102	}
1103    }
1104    if {[info exists opt(-expires)]} {
1105	switch -glob -- $opt(-expires) {
1106	    *GMT {
1107		set expires $opt(-expires)
1108	    }
1109	    default {
1110		set expires [clock format [clock scan $opt(-expires)] \
1111			-format "%A, %d-%b-%Y %H:%M:%S GMT" -gmt 1]
1112	    }
1113	}
1114	append line " expires=$expires ;"
1115    }
1116    if {[info exists opt(-secure)]} {
1117	append line " secure "
1118    }
1119    lappend cookieOutput $line
1120}
1121