diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
commit | ea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch) | |
tree | 6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/ncgi | |
parent | b5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff) | |
parent | 7c621da28f07e449ad90c387344f07a453927569 (diff) | |
download | blt-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/ChangeLog | 373 | ||||
-rw-r--r-- | tcllib/modules/ncgi/formdata.txt | 24 | ||||
-rw-r--r-- | tcllib/modules/ncgi/ncgi.man | 313 | ||||
-rw-r--r-- | tcllib/modules/ncgi/ncgi.tcl | 1120 | ||||
-rw-r--r-- | tcllib/modules/ncgi/ncgi.test | 854 | ||||
-rw-r--r-- | tcllib/modules/ncgi/pkgIndex.tcl | 2 |
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]] |