summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/ncgi
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/ncgi
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/ncgi')
-rw-r--r--tcllib/modules/ncgi/ChangeLog373
-rw-r--r--tcllib/modules/ncgi/formdata.txt24
-rw-r--r--tcllib/modules/ncgi/ncgi.man313
-rw-r--r--tcllib/modules/ncgi/ncgi.tcl1120
-rw-r--r--tcllib/modules/ncgi/ncgi.test854
-rw-r--r--tcllib/modules/ncgi/pkgIndex.tcl2
6 files changed, 2686 insertions, 0 deletions
diff --git a/tcllib/modules/ncgi/ChangeLog b/tcllib/modules/ncgi/ChangeLog
new file mode 100644
index 0000000..ea5c63a
--- /dev/null
+++ b/tcllib/modules/ncgi/ChangeLog
@@ -0,0 +1,373 @@
+2013-02-08 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl (::ncgi::DecodeHex): [Bug 3603593]: Fixed bad scoping
+ * ncgi.man: of DecodeHex, now in the ncgi namespace instead of
+ * pkgIndex.tcl: polluting the global. Bumped version to 1.4.2.
+
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2013-01-30 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.man: [Bug 3601995]: Accepted [decode] changes by
+ * ncgi.tcl: <quantifier@users.sourceforge.net>. Fixed both missing
+ * ncgi.test: acceptance of various ut-8 sequences, and missing
+ * pkgIndex.tcl: rejection of bad sequences. Test cases
+ added. Bumped to version 1.4.1.
+
+2012-05-03 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl: Applied Richard Hipp's patch to extend handling of
+ * pkgIndex.tcl: utf characters in [decode]. Extended testsuite.
+ * ncgi.man: Used the opportunity to bump the minimum required
+ * ncgi.test: Tcl runtime up to 8.4. Bumped package version up
+ to 1.4 to reflect this latter change.
+
+2012-03-30 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl: [Bug 3513149]: Removed superfluous closing
+ * pkgIndex.tcl: bracket. Bumped version to 1.3.3.
+ * ncgi.man:
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-04-23 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl (::ncgi::exists): Fixed documentation in code
+ * ncgi.man: and outside, the result was specified wrongly.
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-08-22 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.man: Tweaked the formatting of the newly committed example
+ a bit.
+
+2007-03-21 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.man: Fixed all warnings due to use of now deprecated
+ commands. Added a section about how to give feedback.
+
+2006-10-08 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Rewritten to use new features for handling the
+ environment.
+
+2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.9 ========================
+ *
+
+2006-09-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Fixed ncgi dependencies in the scripts executed by
+ sub-shells.
+
+2006-09-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Added 'exit' to the scripts executed in sub-shells,
+ to make them usable with 'wish'-type shells as well. Fixed
+ 8.4ism in testsuite of 8.2+ package.
+
+2006-07-02 Michael Schlenker <mic42@users.sourceforge.net>
+
+ * ncgi.tcl: Applied patch from [SF Tcllib Bug 532774] to
+ speed up parsing of large values using string functions
+ instead of regexp.
+
+2006-07-02 Michael Schlenker <mic42@users.sourceforge.net>
+
+ * ncgi.tcl: Fixed [SF Tcllib Bug 547274]. We could further
+ enhance the value parsing in case a parameter is specified.
+
+2006-01-29 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Fixed use and cleanup of temp. files. Also fixed
+ warning about changes to the env array.
+
+2006-01-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: More boilerplate simplified via use of test support.
+
+2006-01-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Hooked into the new common test support code.
+
+2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.8 ========================
+ *
+
+2005-09-30 Andreas Kupries <andreask@activestate.com>
+
+ * pkgIndex.tcl: Applied fix for [SF Tcllib Bug 756939], and
+ * ncgi.tcl: accepted [SF Tcllib RFE 842066]. Added new
+ * ncgi.man: commands 'ncgi::names' and 'ncgi::exists'.
+ * ncgi.test: Extended the documentation and testsuite. Bumped
+ version to 1.3
+
+2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.7 ========================
+ *
+
+2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6.1 ========================
+ *
+
+2004-05-04 Brent Welch <welch@panasas.com>
+
+ * ngci.tcl: Added text/xml to the list of types allowed by
+ ncgi::nvlist. This is to support URL fetches in tclhttpd from
+ active X objects that specify their inputs in "xml".
+
+2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.6 ========================
+ *
+
+2004-02-09 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: The variable '_tmpfiles' was used in conjunction with
+ * ncgi.test: some 8.4'isms. The package is certified for Tcl 8.2.
+ Replaced the offending constructs with equivalents
+ acceptable to the lesser cores. Spelling fixes in the
+ * formdata.txt: tests, and supporting data file.
+
+2003-07-04 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.man: Trival spelling fix.
+
+2003-06-16 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.test:
+ * ncgi.tcl (importFile): Got a rewritten version from Steve
+ Cassidy which fixes some bugs. We now also have tests for
+ 'importFile'. See tcllib patch 611595 for the original code.
+
+2003-05-09 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl (import_file): Brace [expr].
+
+2003-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.4 ========================
+ *
+
+2003-05-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.test: Fixed all the tests which use a sub-process. The
+ auto_path was not propagated, causing the sub-process to require
+ an installed tcllib for correct operation (i.e. to find the
+ other packages ncgi depends on, like fileutil). also changed the
+ test prolog to match the other testsuites.
+
+2003-04-25 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.tcl (::ncgi::query): Added code to handle binary data in
+ query/upload correctly.
+
+2003-04-23 Andreas Kupries <andreask@activestate.com>
+
+ * ncgi.man:
+ * ncgi.tcl: Added command [importFile] from tcllib patch
+ 611595. The command [tempfile] was relocated into fileutil
+ instead.
+
+2003-04-10 Andreas Kupries <andreask@activestate.com>
+
+ * pkgIndex.tcl:
+ * ncgi.man:
+ * ncgi.tcl: Fixed bug #614591. Set version of the package to to
+ 1.2.2. Also fixed equivalnet of bug #648679.
+
+2003-02-05 David N. Welton <davidw@dedasys.com>
+
+ * ncgi.tcl: Use string match instead of regexp.
+
+2002-08-30 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: Updated 'info exist' to 'info exists'.
+
+2002-08-15 David N. Welton <davidw@dedasys.com>
+
+ * ncgi.tcl (ncgi::setValueList): Fix [ 593254 ] ncgi::SetValue bug
+ - SetValue now works correctly with multipart values with spaces
+ in them.
+
+2002-08-09 David N. Welton <davidw@dedasys.com>
+
+ * ncgi.test: Added two new tests for setValue.
+
+ * ncgi.tcl (ncgi::multipart): Fix [ 564279 ] ncgi::multipart bug -
+ commented out offending 'puts' statements.
+
+2002-04-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.man: Added doctools manpage.
+
+2002-01-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * Bumped version to 1.2.1
+
+2001-10-20 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl (ncgi::redirect): Fixed bug #464560 reported by Ed
+ Rolfe <erolfe@users.sourceforge.net>. The proposed fix is not
+ used as it does not pass the testsuite. We check for the
+ existence of "env(REQUEST_URI)" instead, again, and use the
+ appropriate alternate information if it does not exist.
+
+2001-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.n:
+ * ncgi.test:
+ * ncgi.tcl:
+ * pkgIndex.tcl: Version up to 1.2
+
+2001-09-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: Restricted export list to public API.
+ [456255]. Patch by Hemang Lavana
+ <hemanglavana@users.sourceforge.net>
+
+2001-09-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: Added missing [global env]. Bug [458023].
+
+2001-08-01 Jeff Hobbs <jeffh@ActiveState.com>
+
+ * ncgi.tcl: made require Tcl 8.1+, sped up encode and decode.
+
+2001-07-10 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: Frink 2.2 run, fixed dubious code.
+
+2001-06-21 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ncgi.tcl: Fixed dubious code reported by frink.
+
+2001-06-15 Melissa Chawla <melissachawla@yahoo.com>
+
+ * ncgi.tcl: Applied George Wu's patch (gwu@acm.org) to the
+ multipart function. It failed to process binary data correctly
+ because it replaced all "\r\n" sequences with "\n".
+
+2000-07-31 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl: Added ncgi::setValue, ncgi::setValueList,
+ ncgi::setDefaultValue, ncgi::setDefaultValueList to push values
+ back into the CGI environment.
+
+2000-05-26 Melissa Chawla <hershey@scriptics.com>
+
+ * ncgi.tcl: fixed bug 5727 where Netscape prepends an extra \n to
+ post data sent via HTTPS. Urlencoded post does not include
+ preceding or trailing whitespace, so to be safe, we trim
+ whitespace off the post data before parsing the attributes.
+
+2000-05-15 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl: Changed ncgi::redirect so it grabs the server name
+ from REQUEST_URI before using the SERVER_NAME value. This is so
+ the server name matches the previous page better. Otherwise a
+ transition from "www" to "www.scriptics.com" can trigger
+ Basic Authentication challenges.
+
+2000-05-02 Brent Welch <welch@scriptics.com>
+
+ * ncgi/ncgi.tcl:
+ Moved the '+' decoding from nvlist down into ncgi::decode.
+ Changed ncgi::value to strip out the structure associated with
+ multipart/form-data values. Use ncgi::valueList to get the
+ structured value.
+
+2000-05-02 Sandeep Tamhankar <sandeep@scriptics.com>
+
+ * ncgi.tcl: Changed ncgi::parseMimeValue such that a key-value
+ pair like name="" would turn into the list {name {}} instead of
+ {name {""}}.
+
+2000-04-26 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl, ncgi.test: changed names to get capitalization
+ right: setCookie, valueList, importAll, urlStub
+
+2000-04-17 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl: Fixed ncgi::reset with no query data. Fixed
+ ncgi::multipart because it usually gets \r\n data.
+
+2000-04-14 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl: Changed ncgi::list to ncgi::nvlist (for "name value
+ list") becauase of the inevitable conflict with the global list
+ command. Added ncgi::importall to import a set of cgi variables.
+ Added multipart/form-data parsing. Added ncgi::cookie and
+ ncgi::setcookie.
+
+2000-03-20 Eric Melski <ericm@scriptics.com>
+
+ * ncgi.test: Fixed tests that created files with "source ncgi.tcl"
+ in them to use full path for sourcing, so that tests could be run
+ from any directory. [Bug: 4393]
+
+2000-03-15 Brent Welch <welch@scriptics.com>
+
+ * ncgi.tcl: added ncgi::reset so the ncgi package can be used inside
+ TclHttpd
+
+ * ncgi.test: added ncgi::reset tests, renumbered everything, and
+ switch most tests to use ncgi::reset
+
+2000-03-10 Eric Melski <ericm@scriptics.com>
+
+ * pkgIndex.tcl: Added package index file.
+
+ * ncgi.test: Added code to add source dir to auto_path, so that
+ tests could be run on uninstalled package. Added call to
+ tcltest::cleanupTests.
+
+
diff --git a/tcllib/modules/ncgi/formdata.txt b/tcllib/modules/ncgi/formdata.txt
new file mode 100644
index 0000000..a1db029
--- /dev/null
+++ b/tcllib/modules/ncgi/formdata.txt
@@ -0,0 +1,24 @@
+Content-Type: multipart/form-data; boundary="---------------------------17661509020136"
+
+-----------------------------17661509020136
+Content-Disposition: form-data; name="field1"
+
+value
+-----------------------------17661509020136
+Content-Disposition: form-data; name="field2"
+
+another value
+-----------------------------17661509020136
+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>
+
+
+
+-----------------------------17661509020136--
+
diff --git a/tcllib/modules/ncgi/ncgi.man b/tcllib/modules/ncgi/ncgi.man
new file mode 100644
index 0000000..e7e2eaa
--- /dev/null
+++ b/tcllib/modules/ncgi/ncgi.man
@@ -0,0 +1,313 @@
+[vset VERSION 1.4.3]
+[manpage_begin ncgi n [vset VERSION]]
+[see_also html]
+[keywords CGI]
+[keywords cookie]
+[keywords form]
+[keywords html]
+[comment {-*- tcl -*- doctools manpage}]
+[moddesc {CGI Support}]
+[titledesc {Procedures to manipulate CGI values.}]
+[category {CGI programming}]
+[require Tcl 8.4]
+[require ncgi [opt [vset VERSION]]]
+[description]
+[para]
+
+The [package ncgi] package provides commands that manipulate CGI
+values. These are values that come from Web forms and are processed
+either by CGI scripts or web pages with embedded Tcl code. Use the
+[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.
+
+[cmd ::ncgi::input] restricts repeated CGI values to have names that
+end with "List". In this case, [cmd ::ncgi::value] will return the
+complete list of values, and [cmd ::ncgi::input] will raise errors if
+it find repeated form elements without the right name.
+
+[para]
+
+The [cmd ::ncgi::reset] procedure can be used in test suites and Web
+servers to initialize the source of the CGI values. Otherwise the
+values are read in from the CGI environment.
+
+[para]
+
+The complete set of procedures is described below.
+
+[list_begin definitions]
+
+[call [cmd ::ncgi::cookie] [arg cookie]]
+
+Return a list of values for [arg cookie], if any. It is possible that
+more than one cookie with the same name can be present, so this
+procedure returns a list.
+
+[call [cmd ::ncgi::decode] [arg str]]
+
+Decode strings in www-url-encoding, which represents special
+characters with a %xx sequence, where xx is the character code in hex.
+
+[call [cmd ::ncgi::empty] [arg name]]
+
+Returns 1 if the CGI variable [arg name] is not present or has the
+empty string as its value.
+
+[call [cmd ::ncgi::exists] [arg name]]
+
+The return value is a boolean. It returns [const 0] if the CGI
+variable [arg name] is not present, and [const 1] otherwise.
+
+[call [cmd ::ncgi::encode] [arg string]]
+
+Encode [arg string] into www-url-encoded format.
+
+[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].
+
+[list_begin definitions]
+
+[def "[option -client] [arg cginame]"]
+
+returns the filename as sent by the client.
+
+[def "[option -type] [arg cginame]"]
+
+returns the mime type of the uploaded file.
+
+[def "[option -data] [arg cginame]"]
+
+returns the contents of the file.
+
+[def "[option -server] [arg cginame] [arg filename]"]
+
+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]]
+
+This 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::query]]
+
+This returns the raw query data.
+
+[call [cmd ::ncgi::redirect] [arg url]]
+
+Generate a response that causes a 302 redirect by the Web server. The
+[arg url] is the new URL that is the target of the redirect. The URL
+will be qualified with the current server and current directory, if
+necessary, to convert it into a full URL.
+
+[call [cmd ::ncgi::reset] [arg {query type}]]
+
+Set the query data and Content-Type for the current CGI session. This
+is used by the test suite and by Web servers to initialize the ncgi
+module so it does not try to read standard input or use environment
+variables to get its data. If neither [arg query] or [arg type] are
+specified, then the [package ncgi] module will look in the standard
+CGI environment for its data.
+
+[call [cmd ::ncgi::setCookie] [arg args]]
+
+Set a cookie value that will be returned as part of the reply. This
+must be done before [cmd ::ncgi::header] or [cmd ::ncgi::redirect] is
+called in order for the cookie to be returned properly. The
+
+[arg args] are a set of flags and values:
+
+[list_begin definitions]
+
+[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:
+<html>
+<form action="/cgi-bin/upload.cgi" method="POST" enctype="multipart/form-data">
+Path: <input type="file" name="filedata"><br>
+Name: <input type="text" name="filedesc"><br>
+<input type="submit">
+</form>
+</html>
+
+TCL: upload.cgi
+#!/usr/local/bin/tclsh
+
+::ncgi::parse
+set filedata [::ncgi::value filedata]
+set filedesc [::ncgi::value filedesc]
+
+puts "<html> File uploaded at <a href=\"/images/$filedesc\">$filedesc</a> </html>"
+
+set filename /www/images/$filedesc
+
+set fh [open $filename w]
+puts -nonewline $fh $filedata
+close $fh
+}]
+
+[para]
+
+[vset CATEGORY ncgi]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/ncgi/ncgi.tcl b/tcllib/modules/ncgi/ncgi.tcl
new file mode 100644
index 0000000..70a96c1
--- /dev/null
+++ b/tcllib/modules/ncgi/ncgi.tcl
@@ -0,0 +1,1120 @@
+# ncgi.tcl
+#
+# Basic support for CGI programs
+#
+# Copyright (c) 2000 Ajuba Solutions.
+# Copyright (c) 2012 Richard Hipp, Andreas Kupries
+# Copyright (c) 2013-2014 Andreas Kupries
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+
+# 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 contract, 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.4.3
+
+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
+
+ # 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
+ # you use the ncgi::input procedure to parse inputs.
+
+ variable listRestrict 0
+
+ # This is the set of cookies that are pending for output
+
+ variable cookieOutput
+
+ # Support for x-www-urlencoded character mapping
+ # The spec says: "non-alphanumeric characters are replaced by '%HH'"
+
+ variable i
+ variable c
+ variable map
+
+ for {set i 1} {$i <= 256} {incr i} {
+ set c [format %c $i]
+ if {![string match \[a-zA-Z0-9\] $c]} {
+ set map($c) %[format %.2X $i]
+ }
+ }
+
+ # These are handled specially
+ array set map {
+ " " + \n %0D%0A
+ }
+
+ # Map of transient files
+
+ variable _tmpfiles
+ array set _tmpfiles {}
+
+ # I don't like importing, but this makes everything show up in
+ # pkgIndex.tcl
+
+ namespace export 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
+}
+
+# ::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
+
+ # 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
+ }
+ } else {
+ set query [lindex $args 0]
+ set contenttype [lindex $args 1]
+ }
+}
+
+# ::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::query
+#
+# 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 {} {
+ 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(REQUEST_METHOD)]} {
+ if {$env(REQUEST_METHOD) == "GET"} {
+ if {[info exists env(QUERY_STRING)]} {
+ set query $env(QUERY_STRING)
+ }
+ } elseif {$env(REQUEST_METHOD) == "POST"} {
+ if {[info exists env(CONTENT_LENGTH)] &&
+ [string length $env(CONTENT_LENGTH)] != 0} {
+ ## added by Steve Cassidy to try to fix binary file upload
+ fconfigure stdin -translation binary -encoding binary
+ set query [read stdin $env(CONTENT_LENGTH)]
+ }
+ }
+ }
+ return $query
+}
+
+# ::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:
+# An encoded value
+#
+# Results:
+# The decoded value
+
+if {[package vsatisfies [package present Tcl] 8.6]} {
+ # 8.6+, use 'binary decode hex'
+ proc ::ncgi::DecodeHex {hex} {
+ return [binary decode hex $hex]
+ }
+} else {
+ # 8.4+. More complex way of handling the hex conversion.
+ proc ::ncgi::DecodeHex {hex} {
+ return [binary format H* $hex]
+ }
+}
+
+proc ::ncgi::decode {str} {
+ # rewrite "+" back to space
+ # protect \ from quoting another '\'
+ set str [string map [list + { } "\\" "\\\\" \[ \\\[ \] \\\]] $str]
+
+ # prepare to process all %-escapes
+ regsub -all -- {%([Ee][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \
+ $str {[encoding convertfrom utf-8 [DecodeHex \1\2\3]]} str
+ 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
+#
+# Results:
+# The encoded value
+
+proc ::ncgi::encode {string} {
+ variable map
+
+ # 1 leave alphanumerics characters alone
+ # 2 Convert every other character to an array lookup
+ # 3 Escape constructs that are "special" to the tcl parser
+ # 4 "subst" the result, doing all the array substitutions
+
+ 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 type [type]
+ switch -glob -- $type {
+ "" -
+ text/xml* -
+ application/x-www-form-urlencoded* -
+ application/x-www-urlencoded* {
+ 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
+ }
+ multipart/* {
+ return [multipart $type $query]
+ }
+ 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 $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::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
+#
+# Arguments:
+# url The url to which to redirect
+#
+# Side Effects:
+# Outputs a redirect header
+
+proc ::ncgi::redirect {url} {
+ global env
+
+ if {![regexp -- {^[^:]+://} $url]} {
+
+ # The url is relative (no protocol/server spec in it), so
+ # here we create a canonical URL.
+
+ # request_uri The current URL used when dealing with relative URLs.
+ # proto http or https
+ # server The server, which we are careful to match with the
+ # current one in base Basic Authentication is being used.
+ # port This is set if it is not the default port.
+
+ if {[info exists env(REQUEST_URI)]} {
+ # Not all servers have the leading protocol spec
+ #regsub -- {^https?://[^/]*/} $env(REQUEST_URI) / request_uri
+ array set u [uri::split $env(REQUEST_URI)]
+ set request_uri /$u(path)
+ unset u
+ } elseif {[info exists env(SCRIPT_NAME)]} {
+ set request_uri $env(SCRIPT_NAME)
+ } else {
+ set request_uri /
+ }
+
+ set port ""
+ if {[info exists env(HTTPS)] && $env(HTTPS) == "on"} {
+ set proto https
+ if {$env(SERVER_PORT) != 443} {
+ set port :$env(SERVER_PORT)
+ }
+ } else {
+ set proto http
+ if {$env(SERVER_PORT) != 80} {
+ set port :$env(SERVER_PORT)
+ }
+ }
+ # Pick the server from REQUEST_URI so it matches the current
+ # URL. Otherwise use SERVER_NAME. These could be different, e.g.,
+ # "pop.scriptics.com" vs. "pop"
+
+ if {[info exists env(REQUEST_URI)]} {
+ # Not all servers have the leading protocol spec
+ if {![regexp -- {^https?://([^/:]*)} $env(REQUEST_URI) x server]} {
+ set server $env(SERVER_NAME)
+ }
+ } else {
+ set server $env(SERVER_NAME)
+ }
+ if {[string match /* $url]} {
+ set url $proto://$server$port$url
+ } else {
+ regexp -- {^(.*/)[^/]*$} $request_uri match dirname
+ 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 value 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 -- {^["']} $val quote]} { ;# need a " for balance
+ if {[regexp -- ^${quote}(\[^$quote\]*)$quote $val x val2]} {
+ # Trim quotes and any extra crap after close quote
+ set val $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
+}
diff --git a/tcllib/modules/ncgi/ncgi.test b/tcllib/modules/ncgi/ncgi.test
new file mode 100644
index 0000000..5089a24
--- /dev/null
+++ b/tcllib/modules/ncgi/ncgi.test
@@ -0,0 +1,854 @@
+# -*- tcl -*-
+# Tests for the cgi module.
+#
+# This file contains a collection of tests for one or more of the Tcl
+# built-in commands. Sourcing this file into Tcl runs the tests and
+# generates output for errors. No output means no errors were found.
+#
+# Copyright (c) 1998-2000 by Ajuba Solutions
+#
+# RCS: @(#) $Id: ncgi.test,v 1.28 2012/05/03 17:56:07 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+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=reset
+ list $ncgi::query $ncgi::contenttype
+} {query=reset {}}
+
+test ncgi-1.3 {ncgi::reset} {
+ ncgi::reset query=reset text/plain
+ list $ncgi::query $ncgi::contenttype
+} {query=reset text/plain}
+
+test ncgi-2.1 {ncgi::query fake query data} {
+ ncgi::reset "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::query
+ 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::query
+ puts $ncgi::query
+ 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 foo=bar 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 "name=hello+world&name2=%7ewelch"
+ ncgi::nvlist
+} {name {hello world} name2 ~welch}
+
+test ncgi-5.2 {ncgi::nvlist} {
+ ncgi::reset "name=&name2" application/x-www-urlencoded
+ ncgi::nvlist
+} {name {} anonymous name2}
+
+test ncgi-5.3 {ncgi::nvlist} {
+ ncgi::reset "name=&name2" application/x-www-form-urlencoded
+ ncgi::nvlist
+} {name {} anonymous name2}
+
+test ncgi-5.4 {ncgi::nvlist} {
+ ncgi::reset "name=&name2" 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 "name=&name2"
+ ncgi::parse
+} {name anonymous}
+
+test ncgi-6.2 {ncgi::parse, no list restrictions} {
+ ncgi::reset "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 "nameList=value1+stuff&nameList=value2+more"
+ ncgi::input
+ set ncgi::value(nameList)
+} {{value1 stuff} {value2 more}}
+
+test ncgi-7.3 {ncgi::input} {
+ ncgi::reset "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 "nameList=val+ue&nameList=value2"
+ ncgi::input
+ ncgi::value nameList
+} {{val ue} value2}
+
+test ncgi-8.2 {ncgi::value} {
+ ncgi::reset "name=val+ue&name=value2"
+ ncgi::parse
+ ncgi::value name
+} {val ue}
+
+test ncgi-8.3 {ncgi::value} {
+ ncgi::reset "name=val+ue&name=value2"
+ ncgi::parse
+ ncgi::value noname
+} {}
+
+test ncgi-9.1 {ncgi::valueList} {
+ ncgi::reset "name=val+ue&name=value2"
+ ncgi::parse
+ ncgi::valueList name
+} {{val ue} value2}
+
+test ncgi-9.2 {ncgi::valueList} {
+ ncgi::reset "name=val+ue&name=value2"
+ ncgi::parse
+ ncgi::valueList noname
+} {}
+
+test ncgi-10.1 {ncgi::import} {
+ ncgi::reset "nameList=val+ue&nameList=value2"
+ ncgi::input
+ ncgi::import nameList
+ set nameList
+} {{val ue} value2}
+
+test ncgi-10.2 {ncgi::import} {
+ ncgi::reset "nameList=val+ue&nameList=value2"
+ ncgi::input
+ ncgi::import nameList myx
+ set myx
+} {{val ue} value2}
+
+test ncgi-10.3 {ncgi::import} {
+ ncgi::reset "nameList=val+ue&nameList=value2"
+ ncgi::input
+ ncgi::import noname
+ set noname
+} {}
+
+test ncgi-10.4 {ncgi::importAll} {
+ ncgi::reset "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 "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-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::reset $X $type
+ 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 $X $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 $X $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 "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 $X [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 $X [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 $X [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 $X [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 $X [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 $X [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 $X [genformcontent_type]
+ ncgi::parse
+
+ ncgi::importFile -client the_file_name
+
+} {}
+
+
+test ncgi-17.1 {ncgi::names} {
+ ncgi::reset "name=hello+world&name2=%7ewelch"
+ ncgi::names
+} {name name2}
+
+test ncgi-17.2 {ncgi::names} {
+ ncgi::reset "name=&name2" application/x-www-urlencoded
+ ncgi::names
+} {name}
+
+test ncgi-17.3 {ncgi::names} {
+ ncgi::reset "name=&name2" application/x-www-form-urlencoded
+ ncgi::names
+} {name}
+
+test ncgi-17.4 {ncgi::names} {
+ ncgi::reset "name=&name2" application/xyzzy
+ set code [catch ncgi::names err]
+ list $code $err
+} {1 {Unknown Content-Type: application/xyzzy}}
+
+# -------------------------------------------------------------------------
+
+testsuiteCleanup
+return
diff --git a/tcllib/modules/ncgi/pkgIndex.tcl b/tcllib/modules/ncgi/pkgIndex.tcl
new file mode 100644
index 0000000..0b4506a
--- /dev/null
+++ b/tcllib/modules/ncgi/pkgIndex.tcl
@@ -0,0 +1,2 @@
+if {![package vsatisfies [package provide Tcl] 8.4]} {return}
+package ifneeded ncgi 1.4.3 [list source [file join $dir ncgi.tcl]]