ncgi.tcl

Go to the documentation of this file.
00001 /*  ncgi.tcl*/
00002 /* */
00003 /*  Basic support for CGI programs*/
00004 /* */
00005 /*  Copyright (c) 2000 Ajuba Solutions.*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 
00010 
00011 /*  Please note that Don Libes' has a "cgi.tcl" that implements version 1.0*/
00012 /*  of the cgi package.  That implementation provides a bunch of cgi_ procedures*/
00013 /*  (it doesn't use the ::cgi:: namespace) and has a wealth of procedures for*/
00014 /*  generating HTML.  In contract, the package provided here is primarly*/
00015 /*  concerned with processing input to CGI programs.  I have tried to mirror his*/
00016 /*  API's where possible.  So, ncgi::input is equivalent to cgi_input, and so*/
00017 /*  on.  There are also some different APIs for accessing values (ncgi::list,*/
00018 /*  ncgi::parse and ncgi::value come to mind)*/
00019 
00020 /*  Note, I use the term "query data" to refer to the data that is passed in*/
00021 /*  to a CGI program.  Typically this comes from a Form in an HTML browser.*/
00022 /*  The query data is composed of names and values, and the names can be*/
00023 /*  repeated.  The names and values are encoded, and this module takes care*/
00024 /*  of decoding them.*/
00025 
00026 /*  We use newer string routines*/
00027 package require Tcl 8.2
00028 package require fileutil ; /*  Required by importFile.*/
00029 
00030 package provide ncgi 1.3.2
00031 
00032 namespace ::ncgi {
00033 
00034     /*  "query" holds the raw query (i.e., form) data*/
00035     /*  This is treated as a cache, too, so you can call ncgi::query more than*/
00036     /*  once*/
00037 
00038     variable query
00039 
00040     /*  This is the content-type which affects how the query is parsed*/
00041 
00042     variable contenttype
00043 
00044     /*  value is an array of parsed query data.  Each array element is a list*/
00045     /*  of values, and the array index is the form element name.*/
00046     /*  See the differences among ncgi::parse, ncgi::input, ncgi::value*/
00047     /*  and ncgi::valuelist for the various approaches to handling these values.*/
00048 
00049     variable value
00050 
00051     /*  This lists the names that appear in the query data*/
00052 
00053     variable varlist
00054 
00055     /*  This holds the URL coresponding to the current request*/
00056     /*  This does not include the server name.*/
00057 
00058     variable urlStub
00059 
00060     /*  This flags compatibility with Don Libes cgi.tcl when dealing with*/
00061     /*  form values that appear more than once.  This bit gets flipped when*/
00062     /*  you use the ncgi::input procedure to parse inputs.*/
00063 
00064     variable listRestrict 0
00065 
00066     /*  This is the set of cookies that are pending for output*/
00067 
00068     variable cookieOutput
00069 
00070     /*  Support for x-www-urlencoded character mapping*/
00071     /*  The spec says: "non-alphanumeric characters are replaced by '%HH'"*/
00072  
00073     variable i
00074     variable c
00075     variable map
00076 
00077     for { i =  1} {$i <= 256} {incr i} {
00078      c =  [format %c $i]
00079     if {![string match \[a-zA-Z0-9\] $c]} {
00080          map = ($c) %[format %.2X $i]
00081     }
00082     }
00083      
00084     /*  These are handled specially*/
00085     array  map =  {
00086     " " +   \n %0D%0A
00087     }
00088 
00089     /*  Map of transient files*/
00090 
00091     variable  _tmpfiles
00092     array  _tmpfiles =  {}
00093 
00094     /*  I don't like importing, but this makes everything show up in */
00095     /*  pkgIndex.tcl*/
00096 
00097     namespace export re urlStub =  query type decode encode
00098     namespace export nvlist parse input value valueList names
00099     namespace export Value =  ValueList =  DefaultValue =  DefaultValueList = 
00100     namespace export empty import importAll importFile redirect header
00101     namespace export parseMimeValue multipart cookie Cookie = 
00102 }
00103 
00104 /*  ::ncgi::reset*/
00105 /* */
00106 /*  This resets the state of the CGI input processor.  This is primarily*/
00107 /*  used for tests, although it is also designed so that TclHttpd can*/
00108 /*  call this with the current query data*/
00109 /*  so the ncgi package can be shared among TclHttpd and CGI scripts.*/
00110 /* */
00111 /*  DO NOT CALL this in a standard cgi environment if you have not*/
00112 /*  yet processed the query data, which will not be used after a*/
00113 /*  call to ncgi::reset is made.  Instead, just call ncgi::parse*/
00114 /* */
00115 /*  Arguments:*/
00116 /*  newquery    The query data to be used instead of external CGI.*/
00117 /*  newtype     The raw content type.*/
00118 /* */
00119 /*  Side Effects:*/
00120 /*  Resets the cached query data and wipes any environment variables*/
00121 /*  associated with CGI inputs (like QUERY_STRING)*/
00122 
00123 ret  ::ncgi::reset (type args) {
00124     global env
00125     variable _tmpfiles
00126     variable query
00127     variable contenttype
00128     variable cookieOutput
00129 
00130     # array unset _tmpfiles -- Not a Tcl 8.2 idiom
00131     unset _tmpfiles ; array set _tmpfiles {}
00132 
00133     set cookieOutput {}
00134     if {[llength $args] == 0} {
00135 
00136     # We use and test args here so we can detect the
00137     # difference between empty query data and a full reset.
00138 
00139     if {[info exists query]} {
00140         unset query
00141     }
00142     if {[info exists contenttype]} {
00143         unset contenttype
00144     }
00145     } else {
00146     set query [lindex $args 0]
00147     set contenttype [lindex $args 1]
00148     }
00149 }
00150 
00151 /*  ::ncgi::urlStub*/
00152 /* */
00153 /*  Set or return the URL associated with the current page.*/
00154 /*  This is for use by TclHttpd to override the default value*/
00155 /*  that otherwise comes from the CGI environment*/
00156 /* */
00157 /*  Arguments:*/
00158 /*  url (option) The url of the page, not counting the server name.*/
00159 /*      If not specified, the current urlStub is returned*/
00160 /* */
00161 /*  Side Effects:*/
00162 /*  May affects future calls to ncgi::urlStub*/
00163 
00164 ret  ::ncgi::urlStub (optional url ={)} {
00165     global   env
00166     variable urlStub
00167     if {[string length $url]} {
00168      urlStub =  $url
00169     return ""
00170     } elseif {[info exists urlStub]} {
00171     return $urlStub
00172     } elseif {[info exists env(SCRIPT_NAME)]} {
00173      urlStub =  $env(SCRIPT_NAME)
00174     return $urlStub
00175     } else {
00176     return ""
00177     }
00178 }
00179 
00180 /*  ::ncgi::query*/
00181 /* */
00182 /*  This reads the query data from the appropriate location, which depends*/
00183 /*  on if it is a POST or GET request.*/
00184 /* */
00185 /*  Arguments:*/
00186 /*  none*/
00187 /* */
00188 /*  Results:*/
00189 /*  The raw query data.*/
00190 
00191 ret  ::ncgi::query () {
00192     global env
00193     variable query
00194 
00195     if {[info exists query]} {
00196     # This ensures you can call ncgi::query more than once,
00197     # and that you can use it with ncgi::reset
00198     return $query
00199     }
00200 
00201     set query ""
00202     if {[info exists env(REQUEST_METHOD)]} {
00203     if {$env(REQUEST_METHOD) == "GET"} {
00204         if {[info exists env(QUERY_STRING)]} {
00205         set query $env(QUERY_STRING)
00206         }
00207     } elseif {$env(REQUEST_METHOD) == "POST"} {
00208         if {[info exists env(CONTENT_LENGTH)] &&
00209             [string length $env(CONTENT_LENGTH)] != 0} {
00210         ## added by Steve Cassidy to try to fix binary file upload
00211         fconfigure stdin -translation binary -encoding binary
00212         set query [read stdin $env(CONTENT_LENGTH)]
00213         }
00214     }
00215     }
00216     return $query
00217 }
00218 
00219 /*  ::ncgi::type*/
00220 /* */
00221 /*  This returns the content type of the query data.*/
00222 /* */
00223 /*  Arguments:*/
00224 /*  none*/
00225 /* */
00226 /*  Results:*/
00227 /*  The content type of the query data.*/
00228 
00229 ret  ::ncgi::type () {
00230     global env
00231     variable contenttype
00232 
00233     if {![info exists contenttype]} {
00234     if {[info exists env(CONTENT_TYPE)]} {
00235         set contenttype $env(CONTENT_TYPE)
00236     } else {
00237         return ""
00238     }
00239     }
00240     return $contenttype
00241 }
00242 
00243 /*  ::ncgi::decode*/
00244 /* */
00245 /*  This decodes data in www-url-encoded format.*/
00246 /* */
00247 /*  Arguments:*/
00248 /*  An encoded value*/
00249 /* */
00250 /*  Results:*/
00251 /*  The decoded value*/
00252 
00253 ret  ::ncgi::decode (type str) {
00254     # rewrite "+" back to space
00255     # protect \ from quoting another '\'
00256     set str [string map [list + { } "\\" "\\\\"] $str]
00257 
00258     # prepare to process all %-escapes
00259     regsub -all -- {%([A-Fa-f0-9][A-Fa-f0-9])} $str {\\u00\1} str
00260 
00261     # process \u unicode mapped chars
00262     return [subst -novar -nocommand $str]
00263 }
00264 
00265 /*  ::ncgi::encode*/
00266 /* */
00267 /*  This encodes data in www-url-encoded format.*/
00268 /* */
00269 /*  Arguments:*/
00270 /*  A string*/
00271 /* */
00272 /*  Results:*/
00273 /*  The encoded value*/
00274 
00275 ret  ::ncgi::encode (type string) {
00276     variable map
00277 
00278     # 1 leave alphanumerics characters alone
00279     # 2 Convert every other character to an array lookup
00280     # 3 Escape constructs that are "special" to the tcl parser
00281     # 4 "subst" the result, doing all the array substitutions
00282 
00283     regsub -all -- \[^a-zA-Z0-9\] $string {$map(&)} string
00284     # This quotes cases like $map([) or $map($) => $map(\[) ...
00285     regsub -all -- {[][{})\\]\)} $string {\\&} string
00286     return [subst -nocommand $string]
00287 }
00288 
00289 /*  ::ncgi::names*/
00290 /* */
00291 /*  This parses the query data and returns a list of the names found therein.*/
00292 /* */
00293 /*      Note: If you use ncgi::setValue or ncgi::setDefaultValue, this*/
00294 /*  names procedure doesn't see the effect of that.*/
00295 /* */
00296 /*  Arguments:*/
00297 /*  none*/
00298 /* */
00299 /*  Results:*/
00300 /*  A list of names*/
00301 
00302 ret  ::ncgi::names () {
00303     array set names {}
00304     foreach {name val} [nvlist] {
00305         if {![string equal $name "anonymous"]} {
00306             set names($name) 1
00307         }
00308     }
00309     return [array names names]
00310 }
00311 
00312 /*  ::ncgi::nvlist*/
00313 /* */
00314 /*  This parses the query data and returns it as a name, value list*/
00315 /* */
00316 /*      Note: If you use ncgi::setValue or ncgi::setDefaultValue, this*/
00317 /*  nvlist procedure doesn't see the effect of that.*/
00318 /* */
00319 /*  Arguments:*/
00320 /*  none*/
00321 /* */
00322 /*  Results:*/
00323 /*  An alternating list of names and values*/
00324 
00325 ret  ::ncgi::nvlist () {
00326     set query [query]
00327     set type  [type]
00328     switch -glob -- $type {
00329     "" -
00330     text/xml* -
00331     application/x-www-form-urlencoded* -
00332     application/x-www-urlencoded* {
00333         set result {}
00334 
00335         # Any whitespace at the beginning or end of urlencoded data is not
00336         # considered to be part of that data, so we trim it off.  One special
00337         # case in which post data is preceded by a \n occurs when posting
00338         # with HTTPS in Netscape.
00339 
00340         foreach {x} [split [string trim $query] &] {
00341         # Turns out you might not get an = sign,
00342         # especially with <isindex> forms.
00343 
00344         set pos [string first = $x]
00345         set len [string length $x]
00346 
00347         if { $pos>=0 } {
00348             if { $pos == 0 } { # if the = is at the beginning ...
00349                 if { $len>1 } { 
00350                             # ... and there is something to the right ...
00351                     set varname anonymous
00352                     set val [string range $x 1 end]]
00353                 } else { 
00354                             # ... otherwise, all we have is an =
00355                     set varname anonymous
00356                     set val ""
00357                 }
00358             } elseif { $pos==[expr {$len-1}] } { 
00359                         # if the = is at the end ...
00360                 set varname [string range $x 0 [expr {$pos-1}]]
00361             set val ""
00362             } else {
00363                 set varname [string range $x 0 [expr {$pos-1}]]
00364                 set val [string range $x [expr {$pos+1}] end]
00365             }
00366         } else { # no = was found ...
00367             set varname anonymous
00368             set val $x
00369         }       
00370         lappend result [decode $varname] [decode $val]
00371         }
00372         return $result
00373     }
00374     multipart/* {
00375         return [multipart $type $query]
00376     }
00377     default {
00378         return -code error "Unknown Content-Type: $type"
00379     }
00380     }
00381 }
00382 
00383 /*  ::ncgi::parse*/
00384 /* */
00385 /*  The parses the query data and stores it into an array for later retrieval.*/
00386 /*  You should use the ncgi::value or ncgi::valueList procedures to get those*/
00387 /*  values, or you are allowed to access the ncgi::value array directly.*/
00388 /* */
00389 /*  Note - all values have a level of list structure associated with them*/
00390 /*  to allow for multiple values for a given form element (e.g., a checkbox)*/
00391 /* */
00392 /*  Arguments:*/
00393 /*  none*/
00394 /* */
00395 /*  Results:*/
00396 /*  A list of names of the query values*/
00397 
00398 ret  ::ncgi::parse () {
00399     variable value
00400     variable listRestrict 0
00401     variable varlist {}
00402     if {[info exists value]} {
00403     unset value
00404     }
00405     foreach {name val} [nvlist] {
00406     if {![info exists value($name)]} {
00407         lappend varlist $name
00408     }
00409     lappend value($name) $val
00410     }
00411     return $varlist
00412 } 
00413 
00414 /*  ::ncgi::input*/
00415 /* */
00416 /*  Like ncgi::parse, but with Don Libes cgi.tcl semantics.*/
00417 /*  Form elements must have a trailing "List" in their name to be*/
00418 /*  listified, otherwise this raises errors if an element appears twice.*/
00419 /* */
00420 /*  Arguments:*/
00421 /*  fakeinput   See ncgi::reset*/
00422 /*  fakecookie  The raw cookie string to use when testing.*/
00423 /* */
00424 /*  Results:*/
00425 /*  The list of element names in the form*/
00426 
00427 ret  ::ncgi::input (optional fakeinput ={) {fakecookie {}}} {
00428     variable value
00429     variable varlist {}
00430     variable listRestrict 1
00431     if {[info exists value]} {
00432     un value = 
00433     }
00434     if {[string length $fakeinput]} {
00435     ncgi::re $fakeinput = 
00436     }
00437     foreach {name val} [nvlist] {
00438      exists =  [info exists value($name)]
00439     if {!$exists} {
00440         lappend varlist $name
00441     }
00442     if {[string match "*List" $name]} {
00443         /*  Accumulate a list of values for this name*/
00444         lappend value($name) $val
00445     } elseif {$exists} {
00446         error "Multiple definitions of $name encountered in input.\
00447         If you're trying to do this intentionally (such as with select),\
00448         the variable must have a \"List\" suffix."
00449     } else {
00450         /*  Capture value with no list structure*/
00451          value = ($name) $val
00452     }
00453     }
00454     return $varlist
00455 } 
00456 
00457 /*  ::ncgi::value*/
00458 /* */
00459 /*  Return the value of a named query element, or the empty string if*/
00460 /*  it was not not specified.  This only returns the first value of*/
00461 /*  associated with the name.  If you want them all (like all values*/
00462 /*  of a checkbox), use ncgi::valueList*/
00463 /* */
00464 /*  Arguments:*/
00465 /*  key The name of the query element*/
00466 /*  default The value to return if the value is not present*/
00467 /* */
00468 /*  Results:*/
00469 /*  The first value of the named element, or the default*/
00470 
00471 ret  ::ncgi::value (type key , optional default ={)} {
00472     variable value
00473     variable listRestrict
00474     variable contenttype
00475     if {[info exists value($key)]} {
00476     if {$listRestrict} {
00477 
00478         /*  ::ncgi::input was called, and it already figured out if the*/
00479         /*  user wants list structure or not.*/
00480 
00481          val =  $value($key)
00482     } else {
00483 
00484         /*  Undo the level of list structure done by ncgi::parse*/
00485 
00486          val =  [lindex $value($key) 0]
00487     }
00488     if {[string match multipart/* [type]]} {
00489 
00490         /*  Drop the meta-data information associated with each part*/
00491 
00492          val =  [lindex $val 1]
00493     }
00494     return $val
00495     } else {
00496     return $default
00497     }
00498 }
00499 
00500 /*  ::ncgi::valueList*/
00501 /* */
00502 /*  Return all the values of a named query element as a list, or*/
00503 /*  the empty list if it was not not specified.  This always returns*/
00504 /*  lists - if you do not want the extra level of listification, use*/
00505 /*  ncgi::value instead.*/
00506 /* */
00507 /*  Arguments:*/
00508 /*  key The name of the query element*/
00509 /* */
00510 /*  Results:*/
00511 /*  The first value of the named element, or ""*/
00512 
00513 ret  ::ncgi::valueList (type key , optional default ={)} {
00514     variable value
00515     if {[info exists value($key)]} {
00516     return $value($key)
00517     } else {
00518     return $default
00519     }
00520 }
00521 
00522 /*  ::ncgi::setValue*/
00523 /* */
00524 /*  Jam a new value into the CGI environment.  This is handy for preliminary*/
00525 /*  processing that does data validation and cleanup.*/
00526 /* */
00527 /*  Arguments:*/
00528 /*  key The name of the query element*/
00529 /*  value   This is a single value, and this procedure wraps it up in a list*/
00530 /*      for compatibility with the ncgi::value array usage.  If you*/
00531 /*      want a list of values, use ngci::setValueList*/
00532 /*      */
00533 /* */
00534 /*  Side Effects:*/
00535 /*  Alters the ncgi::value and possibly the ncgi::valueList variables*/
00536 
00537 ret  ::ncgi::setValue (type key , type value) {
00538     variable listRestrict
00539     if {$listRestrict} {
00540     ncgi::setValueList $key $value
00541     } else {
00542     ncgi::setValueList $key [list $value]
00543     }
00544 }
00545 
00546 /*  ::ncgi::setValueList*/
00547 /* */
00548 /*  Jam a list of new values into the CGI environment.*/
00549 /* */
00550 /*  Arguments:*/
00551 /*  key     The name of the query element*/
00552 /*  valuelist   This is a list of values, e.g., for checkbox or multiple*/
00553 /*          selections sets.*/
00554 /*      */
00555 /*  Side Effects:*/
00556 /*  Alters the ncgi::value and possibly the ncgi::valueList variables*/
00557 
00558 ret  ::ncgi::setValueList (type key , type valuelist) {
00559     variable value
00560     variable varlist
00561     if {![info exists value($key)]} {
00562     lappend varlist $key
00563     }
00564 
00565     # This if statement is a workaround for another hack in
00566     # ::ncgi::value that treats multipart form data
00567     # differently.
00568     if {[string match multipart/* [type]]} {
00569     set value($key) [list [list {} [join $valuelist]]]
00570     } else {
00571     set value($key) $valuelist
00572     }
00573     return ""
00574 }
00575 
00576 /*  ::ncgi::setDefaultValue*/
00577 /* */
00578 /*  Set a new value into the CGI environment if there is not already one there.*/
00579 /* */
00580 /*  Arguments:*/
00581 /*  key The name of the query element*/
00582 /*  value   This is a single value, and this procedure wraps it up in a list*/
00583 /*      for compatibility with the ncgi::value array usage.*/
00584 /*      */
00585 /* */
00586 /*  Side Effects:*/
00587 /*  Alters the ncgi::value and possibly the ncgi::valueList variables*/
00588 
00589 ret  ::ncgi::setDefaultValue (type key , type value) {
00590     ncgi::setDefaultValueList $key [list $value]
00591 }
00592 
00593 /*  ::ncgi::setDefaultValueList*/
00594 /* */
00595 /*  Jam a list of new values into the CGI environment if the CGI value*/
00596 /*  is not already defined.*/
00597 /* */
00598 /*  Arguments:*/
00599 /*  key     The name of the query element*/
00600 /*  valuelist   This is a list of values, e.g., for checkbox or multiple*/
00601 /*          selections sets.*/
00602 /*      */
00603 /*  Side Effects:*/
00604 /*  Alters the ncgi::value and possibly the ncgi::valueList variables*/
00605 
00606 ret  ::ncgi::setDefaultValueList (type key , type valuelist) {
00607     variable value
00608     if {![info exists value($key)]} {
00609     ncgi::setValueList $key $valuelist
00610     return ""
00611     } else {
00612     return ""
00613     }
00614 }
00615 
00616 /*  ::ncgi::empty --*/
00617 /* */
00618 /*  Return true if the CGI variable doesn't exist.*/
00619 /* */
00620 /*  Arguments:*/
00621 /*  name    Name of the CGI variable*/
00622 /* */
00623 /*  Results:*/
00624 /*  1 if the variable doesn't exist*/
00625 
00626 ret  ::ncgi::exists (type var) {
00627     variable value
00628     return [info exists value($var)]
00629 }
00630 
00631 /*  ::ncgi::empty --*/
00632 /* */
00633 /*  Return true if the CGI variable doesn't exist or is an empty string*/
00634 /* */
00635 /*  Arguments:*/
00636 /*  name    Name of the CGI variable*/
00637 /* */
00638 /*  Results:*/
00639 /*  1 if the variable doesn't exist or has the empty value*/
00640 
00641 ret  ::ncgi::empty (type name) {
00642     return [expr {[string length [string trim [value $name]]] == 0}]
00643 }
00644 
00645 /*  ::ncgi::import*/
00646 /* */
00647 /*  Map a CGI input into a Tcl variable.  This creates a Tcl variable in*/
00648 /*  the callers scope that has the value of the CGI input.  An alternate*/
00649 /*  name for the Tcl variable can be specified.*/
00650 /* */
00651 /*  Arguments:*/
00652 /*  cginame     The name of the form element*/
00653 /*  tclname     If present, an alternate name for the Tcl variable,*/
00654 /*          otherwise it is the same as the form element name*/
00655 
00656 ret  ::ncgi::import (type cginame , optional tclname ={)} {
00657     if {[string length $tclname]} {
00658     upvar 1 $tclname var
00659     } else {
00660     upvar 1 $cginame var
00661     }
00662      var =  [value $cginame]
00663 }
00664 
00665 /*  ::ncgi::importAll*/
00666 /* */
00667 /*  Map a CGI input into a Tcl variable.  This creates a Tcl variable in*/
00668 /*  the callers scope for every CGI value, or just for those named values.*/
00669 /* */
00670 /*  Arguments:*/
00671 /*  args    A list of form element names.  If this is empty,*/
00672 /*      then all form value are imported.*/
00673 
00674 ret  ::ncgi::importAll (type args) {
00675     variable varlist
00676     if {[llength $args] == 0} {
00677     set args $varlist
00678     }
00679     foreach cginame $args {
00680     upvar 1 $cginame var
00681     set var [value $cginame]
00682     }
00683 }
00684 
00685 /*  ::ncgi::redirect*/
00686 /* */
00687 /*  Generate a redirect by returning a header that has a Location: field.*/
00688 /*  If the URL is not absolute, this automatically qualifies it to*/
00689 /*  the current server*/
00690 /* */
00691 /*  Arguments:*/
00692 /*  url     The url to which to redirect*/
00693 /* */
00694 /*  Side Effects:*/
00695 /*  Outputs a redirect header*/
00696 
00697 ret  ::ncgi::redirect (type url) {
00698     global env
00699 
00700     if {![regexp -- {^[^:]+://} $url]} {
00701 
00702     # The url is relative (no protocol/server spec in it), so
00703     # here we create a canonical URL.
00704 
00705     # request_uri   The current URL used when dealing with relative URLs.  
00706     # proto     http or https
00707     # server    The server, which we are careful to match with the
00708     #       current one in base Basic Authentication is being used.
00709     # port      This is set if it is not the default port.
00710 
00711     if {[info exists env(REQUEST_URI)]} {
00712         # Not all servers have the leading protocol spec
00713         regsub -- {^https?://[^/]*/} $env(REQUEST_URI) / request_uri
00714     } elseif {[info exists env(SCRIPT_NAME)]} {
00715         set request_uri $env(SCRIPT_NAME)
00716     } else {
00717         set request_uri /
00718     }
00719 
00720     set port ""
00721     if {[info exists env(HTTPS)] && $env(HTTPS) == "on"} {
00722         set proto https
00723         if {$env(SERVER_PORT) != 443} {
00724         set port :$env(SERVER_PORT)
00725         }
00726     } else {
00727         set proto http
00728         if {$env(SERVER_PORT) != 80} {
00729         set port :$env(SERVER_PORT)
00730         }
00731     }
00732     # Pick the server from REQUEST_URI so it matches the current
00733     # URL.  Otherwise use SERVER_NAME.  These could be different, e.g.,
00734     # "pop.scriptics.com" vs. "pop"
00735 
00736     if {[info exists env(REQUEST_URI)]} {
00737         # Not all servers have the leading protocol spec
00738         if {![regexp -- {^https?://([^/:]*)} $env(REQUEST_URI) x server]} {
00739         set server $env(SERVER_NAME)
00740         }
00741     } else {
00742         set server $env(SERVER_NAME)
00743     }
00744     if {[string match /* $url]} {
00745         set url $proto://$server$port$url
00746     } else {
00747         regexp -- {^(.*/)[^/]*$} $request_uri match dirname
00748         set url $proto://$server$port$dirname$url
00749     }
00750     }
00751     ncgi::header text/html Location $url
00752     puts "Please go to <a href=\"$url\">$url</a>"
00753 }
00754 
00755 /*  ncgi:header*/
00756 /* */
00757 /*  Output the Content-Type header.*/
00758 /* */
00759 /*  Arguments:*/
00760 /*  type    The MIME content type*/
00761 /*  args    Additional name, value pairs to specifiy output headers*/
00762 /* */
00763 /*  Side Effects:*/
00764 /*  Outputs a normal header*/
00765 
00766 ret  ::ncgi::header (optional type =text/html , type args) {
00767     variable cookieOutput
00768     puts "Content-Type: $type"
00769     foreach {n v} $args {
00770     puts "$n: $v"
00771     }
00772     if {[info exists cookieOutput]} {
00773     foreach line $cookieOutput {
00774         puts "Set-Cookie: $line"
00775     }
00776     }
00777     puts ""
00778     flush stdout
00779 }
00780 
00781 /*  ::ncgi::parseMimeValue*/
00782 /* */
00783 /*  Parse a MIME header value, which has the form*/
00784 /*  value; param=value; param2="value2"; param3='value3'*/
00785 /* */
00786 /*  Arguments:*/
00787 /*  value   The mime header value.  This does not include the mime*/
00788 /*      header field name, but everything after it.*/
00789 /* */
00790 /*  Results:*/
00791 /*  A two-element list, the first is the primary value,*/
00792 /*  the second is in turn a name-value list corresponding to the*/
00793 /*  parameters.  Given the above example, the return value is*/
00794 /*  {*/
00795 /*      value*/
00796 /*      {param value param2 value param3 value3}*/
00797 /*  }*/
00798 
00799 ret  ::ncgi::parseMimeValue (type value) {
00800     set parts [split $value \;]
00801     set results [list [string trim [lindex $parts 0]]]
00802     set paramList [list]
00803     foreach sub [lrange $parts 1 end] {
00804     if {[regexp -- {([^=]+)=(.+)} $sub match key val]} {
00805             set key [string trim [string tolower $key]]
00806             set val [string trim $val]
00807             # Allow single as well as double quotes
00808             if {[regexp -- {^["']} $val quote]} { ;# need a " for balance
00809                 if {[regexp -- ^${quote}(\[^$quote\]*)$quote $val x val2]} {
00810                     # Trim quotes and any extra crap after close quote
00811                     set val $val2
00812                 }
00813             }
00814             lappend paramList $key $val
00815     }
00816     }
00817     if {[llength $paramList]} {
00818     lappend results $paramList
00819     }
00820     return $results
00821 }
00822 
00823 /*  ::ncgi::multipart*/
00824 /* */
00825 /*  This parses multipart form data.*/
00826 /*  Based on work by Steve Ball for TclHttpd, but re-written to use*/
00827 /*  string first with an offset to iterate through the data instead*/
00828 /*  of using a regsub/subst combo.*/
00829 /* */
00830 /*  Arguments:*/
00831 /*  type    The Content-Type, because we need boundary options*/
00832 /*  query   The raw multipart query data*/
00833 /* */
00834 /*  Results:*/
00835 /*  An alternating list of names and values*/
00836 /*  In this case, the value is a two element list:*/
00837 /*      headers, which in turn is a list names and values*/
00838 /*      content, which is the main value of the element*/
00839 /*  The header name/value pairs come primarily from the MIME headers*/
00840 /*  like Content-Type that appear in each part.  However, the*/
00841 /*  Content-Disposition header is handled specially.  It has several*/
00842 /*  parameters like "name" and "filename" that are important, so they*/
00843 /*  are promoted to to the same level as Content-Type.  Otherwise,*/
00844 /*  if a header like Content-Type has parameters, they appear as a list*/
00845 /*  after the primary value of the header.  For example, if the*/
00846 /*  part has these two headers:*/
00847 /* */
00848 /*  Content-Disposition: form-data; name="Foo"; filename="/a/b/C.txt"*/
00849 /*  Content-Type: text/html; charset="iso-8859-1"; mumble='extra'*/
00850 /*  */
00851 /*  Then the header list will have this structure:*/
00852 /*  {*/
00853 /*      content-disposition form-data*/
00854 /*      name Foo*/
00855 /*      filename /a/b/C.txt*/
00856 /*      content-type {text/html {charset iso-8859-1 mumble extra}}*/
00857 /*  }*/
00858 /*  Note that the header names are mapped to all lowercase.  You can*/
00859 /*  use "array set" on the header list to easily find things like the*/
00860 /*  filename or content-type.  You should always use [lindex $value 0]*/
00861 /*  to account for values that have parameters, like the content-type*/
00862 /*  example above.  Finally, not that if the value has a second element,*/
00863 /*  which are the parameters, you can "array set" that as well.*/
00864 /*  */
00865 ret  ::ncgi::multipart (type type , type query) {
00866 
00867     set parsedType [parseMimeValue $type]
00868     if {![string match multipart/* [lindex $parsedType 0]]} {
00869     return -code error "Not a multipart Content-Type: [lindex $parsedType 0]"
00870     }
00871     array set options [lindex $parsedType 1]
00872     if {![info exists options(boundary)]} {
00873     return -code error "No boundary given for multipart document"
00874     }
00875     set boundary $options(boundary)
00876 
00877     # The query data is typically read in binary mode, which preserves
00878     # the \r\n sequence from a Windows-based browser.
00879     # Also, binary data may contain \r\n sequences.
00880 
00881     if {[string match "*$boundary\r\n*" $query]} {
00882         set lineDelim "\r\n"
00883     #   puts "DELIM"
00884     } else {
00885         set lineDelim "\n"
00886     #   puts "NO"
00887     }
00888 
00889     # Iterate over the boundary string and chop into parts
00890 
00891     set len [string length $query]
00892     # [string length $lineDelim]+2 is for "$lineDelim--"
00893     set blen [expr {[string length $lineDelim] + 2 + \
00894             [string length $boundary]}]
00895     set first 1
00896     set results [list]
00897     set offset 0
00898 
00899     # Ensuring the query data starts
00900     # with a newline makes the string first test simpler
00901     if {[string first $lineDelim $query 0]!=0} {
00902         set query $lineDelim$query
00903     }
00904     while {[set offset [string first $lineDelim--$boundary $query $offset]] \
00905             >= 0} {
00906     if {!$first} {
00907         lappend results $formName [list $headers \
00908         [string range $query $off2 [expr {$offset -1}]]]
00909     } else {
00910         set first 0
00911     }
00912     incr offset $blen
00913 
00914     # Check for the ending boundary, which is signaled by --$boundary--
00915 
00916     if {[string equal "--" \
00917         [string range $query $offset [expr {$offset + 1}]]]} {
00918         break
00919     }
00920 
00921     # Split headers out from content
00922     # The headers become a nested list structure:
00923     #   {header-name {
00924     #       value {
00925     #           paramname paramvalue ... }
00926     #       }
00927     #   }
00928 
00929         set off2 [string first "$lineDelim$lineDelim" $query $offset]
00930     set headers [list]
00931     set formName ""
00932         foreach line [split [string range $query $offset $off2] $lineDelim] {
00933         if {[regexp -- {([^:     ]+):(.*)$} $line x hdrname value]} {
00934         set hdrname [string tolower $hdrname]
00935         set valueList [parseMimeValue $value]
00936         if {[string equal $hdrname "content-disposition"]} {
00937 
00938             # Promote Conent-Disposition parameters up to headers,
00939             # and look for the "name" that identifies the form element
00940 
00941             lappend headers $hdrname [lindex $valueList 0]
00942             foreach {n v} [lindex $valueList 1] {
00943             lappend headers $n $v
00944             if {[string equal $n "name"]} {
00945                 set formName $v
00946             }
00947             }
00948         } else {
00949             lappend headers $hdrname $valueList
00950         }
00951         }
00952     }
00953 
00954     if {$off2 > 0} {
00955             # +[string length "$lineDelim$lineDelim"] for the
00956             # $lineDelim$lineDelim
00957             incr off2 [string length "$lineDelim$lineDelim"]
00958         set offset $off2
00959     } else {
00960         break
00961     }
00962     }
00963     return $results
00964 }
00965 
00966 /*  ::ncgi::importFile --*/
00967 /* */
00968 /*    get information about a file upload field*/
00969 /* */
00970 /*  Arguments:*/
00971 /*    cmd         one of '-server' '-client' '-type' '-data'*/
00972 /*    var         cgi variable name for the file field*/
00973 /*    filename    filename to write to for -server*/
00974 /*  Results:*/
00975 /*    -server returns the name of the file on the server: side effect*/
00976 /*       is that the file gets stored on the server and the */
00977 /*       script is responsible for deleting/moving the file*/
00978 /*    -client returns the name of the file sent from the client */
00979 /*    -type   returns the mime type of the file*/
00980 /*    -data   returns the contents of the file */
00981 
00982 ret  ::ncgi::importFile (type cmd , type var , optional filename ={)} {
00983 
00984     set vlist [valueList $var]
00985 
00986     array set fileinfo [lindex [lindex $vlist 0] 0]
00987     set contents [lindex [lindex $vlist 0] 1]
00988 
00989     switch -exact -- $cmd {
00990     -server {
00991         ## take care not to write it out more than once
00992         variable _tmpfiles
00993         if {![info exists _tmpfiles($var)]} {
00994         if {$filename != {}} {
00995             ## use supplied filename 
00996             set _tmpfiles($var) $filename
00997         } else {
00998             ## create a tmp file 
00999             set _tmpfiles($var) [::fileutil::tempfile ncgi]
01000         }
01001 
01002         # write out the data only if it's not been done already
01003         if {[catch {open $_tmpfiles($var) w} h]} {
01004             error "Can't open temporary file in ncgi::importFile ($h)"
01005         } 
01006 
01007         fconfigure $h -translation binary -encoding binary
01008         puts -nonewline $h $contents 
01009         close $h
01010         }
01011         return $_tmpfiles($var)
01012     }
01013     -client {
01014         if {![info exists fileinfo(filename)]} {return {}}
01015         return $fileinfo(filename)
01016     }
01017     -type {
01018         if {![info exists fileinfo(content-type)]} {return {}}
01019         return $fileinfo(content-type)
01020     }
01021     -data {
01022         return $contents
01023     }
01024     default {
01025         error "Unknown subcommand to ncgi::import_file: $cmd"
01026     }
01027     }
01028 }
01029 
01030 
01031 /*  ::ncgi::cookie*/
01032 /* */
01033 /*  Return a *list* of cookie values, if present, else ""*/
01034 /*  It is possible for multiple cookies with the same key*/
01035 /*  to be present, so we return a list.*/
01036 /* */
01037 /*  Arguments:*/
01038 /*  cookie  The name of the cookie (the key)*/
01039 /* */
01040 /*  Results:*/
01041 /*  A list of values for the cookie*/
01042 
01043 ret  ::ncgi::cookie (type cookie) {
01044     global env
01045     set result ""
01046     if {[info exists env(HTTP_COOKIE)]} {
01047     foreach pair [split $env(HTTP_COOKIE) \;] {
01048         foreach {key value} [split [string trim $pair] =] { break ;# lassign }
01049         if {[string compare $cookie $key] == 0} {
01050         lappend result $value
01051         }
01052     }
01053     }
01054     return $result
01055 }
01056 
01057 /*  ::ncgi::setCookie*/
01058 /* */
01059 /*  Set a return cookie.  You must call this before you call*/
01060 /*  ncgi::header or ncgi::redirect*/
01061 /* */
01062 /*  Arguments:*/
01063 /*  args    Name value pairs, where the names are:*/
01064 /*      -name   Cookie name*/
01065 /*      -value  Cookie value*/
01066 /*      -path   Path restriction*/
01067 /*      -domain domain restriction*/
01068 /*      -expires    Time restriction*/
01069 /* */
01070 /*  Side Effects:*/
01071 /*  Formats and stores the Set-Cookie header for the reply.*/
01072 
01073 ret  ::ncgi::setCookie (type args) {
01074     variable cookieOutput
01075     array set opt $args
01076     set line "$opt(-name)=$opt(-value) ;"
01077     foreach extra {path domain} {
01078     if {[info exists opt(-$extra)]} {
01079         append line " $extra=$opt(-$extra) ;"
01080     }
01081     }
01082     if {[info exists opt(-expires)]} {
01083     switch -glob -- $opt(-expires) {
01084         *GMT {
01085         set expires $opt(-expires)
01086         }
01087         default {
01088         set expires [clock format [clock scan $opt(-expires)] \
01089             -format "%A, %d-%b-%Y %H:%M:%S GMT" -gmt 1]
01090         }
01091     }
01092     append line " expires=$expires ;"
01093     }
01094     if {[info exists opt(-secure)]} {
01095     append line " secure "
01096     }
01097     lappend cookieOutput $line
01098 }
01099 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1