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