diff --git a/README.md b/README.md index 7d33801..3524a95 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # XML-API CCU Addon -[![Release](https://img.shields.io/github/release/hobbyquaker/XML-API.svg)](https://github.com/hobbyquaker/XML-API/releases/latest) -[![Downloads](https://img.shields.io/github/downloads/hobbyquaker/XML-API/latest/total.svg)](https://github.com/hobbyquaker/XML-API/releases/latest) -[![Issues](https://img.shields.io/github/issues/hobbyquaker/XML-API.svg)](https://github.com/hobbyquaker/XML-API/issues) +[![Release](https://img.shields.io/github/release/homematic-community/XML-API.svg)](https://github.com/homematic-community/XML-API/releases/latest) +[![Downloads](https://img.shields.io/github/downloads/homematic-community/XML-API/latest/total.svg)](https://github.com/homematic-community/XML-API/releases/latest) +[![Issues](https://img.shields.io/github/issues/homematic-community/XML-API.svg)](https://github.com/homematic-community/XML-API/issues) [![License](https://img.shields.io/badge/license-GPL%203.0-green.svg)](https://opensource.org/licenses/GPL-3.0) A HomeMatic CCU Addon implementing a xml request functionality as an interface to all homematic deviced available to a CCU device. This addon provides useful scripts that can be accessed via a HTTP request to a CCU device and allows to query and set all e.g. room- and devicetype names. @@ -15,141 +15,55 @@ A HomeMatic CCU Addon implementing a xml request functionality as an interface t ## Installation This addon can be added like a usual CCU addon package via the WebUI provided functionality by selecting "System-Konfiguration » Systemsteuerung » Zusatzsoftware", to upload the addon package as a tar.gz and the use »Installieren« to actually install the addon. After a restart of the CCU the xml-api interface can then be selected from the »Zusatzsoftware« tab in the CCU settings. -## Security advice -The call to one of the API routines is without any authentication. If the HomeMatic control center can be reached via the Internet without special protection, **this is a serious security risk!** - ## Use After installation the XML-API should be avilable via the following URL call: ``` -http://[CCU_IP]/addons/xmlapi/[ScriptName] +http://[CCU_IP]/addons/xmlapi/[ScriptName]?sid=[TOKEN_ID] ``` -where [CCU_IP] corresponds to the IP address or name of your CCU device and [ScriptName] being one of the following tool scripts: +where [TOKEN_ID] corresponds to a stateless token-based authentication id a user can register using the `tokenregister.cgi` script listed below. In addition, [CCU_IP] corresponds to the IP address or hostname of your CCU device and [ScriptName] being one of the following scripts: | ScriptName | Description / Parameters | ----------------------------- |------------------------- -| `devicelist.cgi`             | Lists all devices with their channels. Contains name, serial number, device types and ids.
`show_internal=1` (outputs all internal channels also) -| `functionlist.cgi`           | Lists all functions with their channels.     -| `favoritelist.cgi`           | Lists all favorites and users.
`show_datapoint` (outputs also attribute `datapoint_id` and `datapoint_type`) -| `mastervalue.cgi`             | Returns one or more (1234,5678) devices with their names and values of their master values.
`device_id=1234` (returns all master values of device)
`requested_name=TEMPERATURE_COMFORT,TEMPERATURE_LOWERING` (returns only master values for specified names) -| `mastervaluechange.cgi`       | Sets one or more (TEMPERATURE_LOWERING,TEMPERATURE_COMFORT) master values of one or more (1234,5678) devices.
`device_id=1234` (sets master values of device)
`name=TEMPERATURE_LOWERING` (sets specified master value only)
`value=17.0,22.5` (sets master values to specified values) +| `checkuptodate.cgi` | ??? +| `devicelist.cgi`             | Lists all devices with channels. Contain names, serial number, device type and ids.
`show_internal=0/1` - adds internal channels also (default=0) +| `devicetypelist.cgi` | Lists all possible device types with their possible meta data. +| `exec.cgi` | Allows to execute arbitrary ReGaHss script commands (as POST data). +| `favoritelist.cgi`           | Lists all favorites and users.
`show_datapoint=0/1` - outputs datapoint_id and datapoint_type also (default=0)
`show_internal=0/1` - adds internal channels also (default=0) +| `functionlist.cgi`           | Lists all functions including channels. +| `mastervalue.cgi`             | Outputs a single or several '1234,5678' devices with their names and master values.
`device_id=list` - returns master values of specified devices (e.g. "1234,5678")
`requested_names=list` - returns only master values of selected types (e.g. "TEMPERATURE_COMFORT,TEMPERATURE_LOWERING") +| `mastervaluechange.cgi`       | Sets one or more master values for a list of devices.
`device_id=list` - sets master values of specified devices (e.g. "1234,5678")
`name=list` - sets only master values of selected types (e.g. "TEMPERATURE_LOWERING,TEMPERATURE_COMFORT")
`value=list` - sets master values to specified values (e.g. "17.0,22.5") +| `programactions.cgi` | Allows to change active and visible program options.
`program_id=int` - id of program to modify (e.g. "1234")
`active=true/false` - sets active status of program to true/false
`visible=true/false` - sets visible status of program to true/false | `programlist.cgi`             | Lists all programs. -| `programactions.cgi` |change the Programactions active and visible
Parameter: programactions.cgi?program_id=1234&active=true&visible=true -| `protocol.cgi`               | Returns the system protocol.
`clear=1` (clears the system protocol) -| `runprogram.cgi`             | Starts the specified program.
`program_id=1234` (id of program to start) -| `roomlist.cgi`               | Lists all rooms with their channels. -| `rssilist.cgi` | Lists all devices with their signal strength. -| `scripterrors.cgi`           | Searches the last 10 lines of `/var/log/messages` for homematic-script errors and output these. -| `state.cgi`                   | Returns for single or multiple devices (1234,5678) the channels and their values.
`device_id=1234` (id of the device to return values)
`channel_id=5678` (id of the channel to return values)
`datapoint_id=12839` (id of data to return only Value()) -| `statelist.cgi`               | Lists all devices with channels and current values.
`ise_id` (id of devices to list values for)
`show_internal=1` (also return internal attribute state) -| `statechange.cgi`             | Changes one or more channel states.
`ise_id=1234,5678` (id of the channels)
`new_value=0.20` (new value for channel state) -| `systemNotification.cgi`     | Returns the current system notifications -| `systemNotificationClear.cgi` | Clears all current clearable system notifications. -| `sysvarlist.cgi`             | Lists all system variable with values.
`text=true` (return current value of system variable in attribute value_text) -| `sysvar.cgi`                 | Returns single system variable with values.
`ise_id=1234` (id of system variable) -| `version.cgi` | Outputs version of XML-API - -All of these scripts, if called, generate a xml structured output that can then be used by third-party applications to display or modify certain information. -All of these scripts rely on a `ise_id` device or channel identifier. You can get this `ise_id` for the needed datapoint by analysing the result of `statelist.cgi`. The id can be used in the following way: +| `protocol.cgi`               | Outputs the system protocol.
`start=int` - start of the protocol
`show=int` - number of entries to output
`clear=0/1` - allows to clear the system protocol +| `roomlist.cgi`               | Lists all configured rooms including channels. +| `rssilist.cgi` | Lists RSSI values of all RF devices. +| `runprogram.cgi`             | Starts a program with the specified id.
`program_id=int` - id of program to modify (e.g. "1234") +| `scripterrors.cgi`           | Searches for the last 10 lines in `/var/log/messages` containing script runtime errors and outputs them. +| `state.cgi`                   | Outputs one or more devices with their channels and current values.
`device_id=list` - returns values of specified devices (e.g. "1234,5678")
`channel_id=list` - returns values of specified channels (e.g. "1234,5678")
`datapoint_id=list` - returns Value() for datapoint with id (e.g. "1234,5678") +| `statechange.cgi`             | Allows to change the state of one or more devices.
`ise_id=list` - selects the devices with the specified ids (e.g. "1234,5678")
`new_value=list` - new values for device states (e.g. "0.20,1.45") +| `statelist.cgi`               | Outputs all devices with channels and their current values.
`ise_id=int` - output only channels and values of device with specified id (e.g. "1234")
`show_internal=0/1` - adds internal channels also (default=0) +| `systemNotification.cgi`     | Outputs the currently existing system notifications. +| `systemNotificationClear.cgi` | Clears the current active system notifications (if not sticky). +| `sysvar.cgi`                 | Outputs a single system variable with its corresponding values.
`ise_id=int` - the id of the system variable to output (e.g. "1234")
`text=true/false` - outputs or suppressed the text for string variables (default=true) +| `sysvarlist.cgi`             | Outputs all system variables with their corresponding values.
`text=true/false` - outputs or suppressed the text for string variables (default=false) +| `tokenlist.cgi` | Lists all registered security access tokens. +| `tokenregister.cgi` | Registers a new security access token.
`desc=string` - description for new token id +| `tokenrevoke.cgi` | Revokes an existing security access token.
`sid=string` - security access token id +| `update.cgi` | ????
`checkupdate=list` - ???
`maxdurchlaeufe=int` - ??? (default=7) +| `version.cgi` | Outputs version of XML-API. + +All of these scripts, in addition to the listed parameters require a security access token id to be specified via a mandatory `?sid=[TOKEN_ID]` URL parameter with an adequate token ID specified. Such a security token can be generated using `tokenregister.cgi` from within the standard CCU addon webui page (`Settings -> Control panel -> Additional software -> XML-API -> Set`) or by using an already registered security token. Furthermore, already registered tokens can be listed via `tokenlist.cgi` and revoked via `tokenrevoke.cgi` with the token id supplied. + +If a script will be correctly called, it generates a xml structured output that can then be used by third-party applications to display or modify certain information. + +In addition many of these scripts rely on additional URL parameter to be specifeid (e.g. `ise_id` device or channel identifier). And example of such script executing URL can be seen here: ``` -http:///addons/xmlapi/statechange.cgi?ise_id=12345&new_value=0.20 +http:///addons/xmlapi/statechange.cgi?sid=[TOKEN_ID]&ise_id=12345&new_value=0.20 ``` -This call, if executed with the right ise_id and IP adress would then set a dimmer to 20%. +This call, if executed with a registered [TOKEN_ID] and the right ise_id and IP address would then e.g. set a dimmer to 20%. ## Support http://homematic-forum.de/forum/viewtopic.php?f=26&t=10098&p=75959#p75959 -## ChangeLog -1.18 -* implemented mastervalue query + change which can also handle HmIP devices. -* fixed version output - -1.17 -* fixed incorrect use of `.Variable()` on alarm type system variables. - -1.16 -* add programactions.cgi for activ and visible Programactions - -1.15 -* fixed bug in `sysvar.cgi` if called without any argument (ise_id) resulting in a SyntaxError in ReGa. -* fixed bug where calling `runprogram.cgi` with no argument or with an non-program program_id ended up in a ReGa Exec/ScriptRuntimeError. - -1.14 -* fixed a bug where `.Timestamp()` was incorrectly used in `protocol.cgi`. - -1.13 -* Support to query and set master values via `mastervalue.cgi` and `mastervaluechange.cgi` -* Fixed `systemNotification.cgi` to not use `.AlDestMapDP()` incorrectly. - -1.12 -* Workaround für Osram Lightify - -1.11 -* Kompatibilität zu RaspberryMatic (HM-RASPBERRYMATIC) hergestellt. - -1.10 -* Die XML-API kann jetzt als Addon/Zusatzsoftware über das WebUI installiert/deinstalliert werden -* statechange.cgi - aendern eines oder mehrere Kanaele-Zustaende -* sysvar.cgi - Anpassung wegen Variablen Name "Timer>>" - -1.9 -* devicelist.cgi - operate und show_internal hinzugefügt - -1.8 -* programlist.cgi - operate und visible hinzugefügt -* statelist.cgi - channel visible, operate und operations hinzugefügt - -1.7 -* statechange.cgi - encoden von Hexadezimalwerten -* protocol.cgi - Timestamp hinzugefügt -* state.cgi - einzelne Datenpunktausgabe (...) entfernt - -1.6 -* state.cgi - Abfrage Abfrage von mehreren IDs hinzugefügt (z.Bsp.: state.cgi?device_id=12796,1245789 ) -* neues cgi systemNotification.cgi - Gibt die System Meldungen aus -* neues cgi systemNotificationClear.cgi - Löcht die vorhandenen System Meldungen - -1.5 -* Bugfix -* Anpassung für CCU2 - -1.4 -* Datenpunktausgabe "value_name_0 und value_name_1" in sysvar.cgi und sysvarlist.cgi hinzugefügt - -1.3 -* Datenpunktausgabe "unit" in state.cgi und statelist.cgi hinzugefügt -* scripterrors.cgi - Sucht in den letzten 10 Zeilen von /var/log/messages nach Homematic-Script Fehlermeldungen - -1.2-hq10 -* Ausgabe von version.cgi von 1.3 auf 1.2 zurück-geändert um Probleme mit Homedroid zu vermeiden -* statechange.cgi - Anführungszeichen hinzugefügt damit auch Varialben vom Typ Zeichenkette gesetzt werden können - -1.2-hq9 -* neues cgi scripterrors.cgi hinzugefügt. Gibt aus den letzten 10 Zeilen der /var/log/messages Homematic-Script Fehlermeldungen aus - -1.2-hq8 -* Fehler in sysvarlist.cgi behoben, 3 Attribute haben gefehlt. (Danke Monty) - -1.2-hq7 -* Datenpunktausgabe in favoritelist.cgi arbeitet nun wie erwartet (gleiches verhalten wie state.cgi, danke Monty). - -1.2-hq6 -* exec.cgi (von http://homematic-forum.de/forum/viewtopic.php?f=31&t=7014) hinzugefügt. Liefert zwar json und kein xml - passt aber thematisch imho trotzdem dazu -* favoritelist.cgi - Parameter show_datapoint aktiviert Ausgabe der zugehörigen Datenpunkte bzw systemvariablen (übernommen aus statelist.cgi und sysvar.cgi). Paramter show_internal siehe statelist.cgi -* statelist.cgi - Parameter show_internal=1 aktiviert nun die Ausgabe des Datenpunkt-Attributs state -* info.html aktualisiert - -1.2-hq5 -* version.cgi liefert nun 1.3 zurück -* protocol.cgi hinzugefügt: Gibt das Systemprotokol zurück. Parameter: start, show, clear. clear=1 löscht das Protokoll - -1.2-hq4 -* allow-origin Header hinzugefügt -* info.html aktualisiert - -1.2-hq3 -* sysvar.cgi hinzugefügt: Gibt eine einzelne Variable zurück. Liefert Wertelisten. Parameter: ise_id -* sysvarlist.cgi: neuer Parameter text um die neuen Attribute value_list and value_text zu aktivieren (text=true) -* cgi.tcl und once.tcl entfernt - ## Authors * jens-maus, Maik (Monty1979), Philipp (ultrah), hobbyquaker, dirch, Uwe (uwe111) diff --git a/VERSION b/VERSION index 5fb5a6b..e437e99 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.20 +2.0b2 diff --git a/update_script b/update_script index 039fd2d..c116635 100755 --- a/update_script +++ b/update_script @@ -2,7 +2,12 @@ ADDON_DIR=/usr/local/etc/config/addons/www/xmlapi RCD_DIR=/usr/local/etc/config/rc.d -CONFIG_DIR=/usr/local/etc/config +CONFIG_DIR=/usr/local/etc/config/addons/xmlapi + +# check for unsupported platforms +if grep -qim1 busmatic /www/api/methods/ccu/downloadFirmware.tcl; then + exit 13 +fi if [ "$1" = "" ]; then echo "CCU1" @@ -20,6 +25,8 @@ mkdir -p $ADDON_DIR chmod 755 $ADDON_DIR mkdir -p $RCD_DIR chmod 755 $RCD_DIR +mkdir -p $CONFIG_DIR +chmod 755 $CONFIG_DIR # remove old stuff rm -f $ADDON_DIR/* diff --git a/xml-api b/xml-api index d774839..a1ef4ab 100755 --- a/xml-api +++ b/xml-api @@ -3,6 +3,11 @@ WWW_DIR=/etc/config/addons/www/xmlapi CONFIG_URL=/addons/xmlapi/ +# check for unsupported platforms +if grep -qim1 busmatic /www/api/methods/ccu/downloadFirmware.tcl; then + exit 13 +fi + case "$1" in ""|start) if [ ! -e /www/config/xmlapi ]; then @@ -14,7 +19,7 @@ case "$1" in info) VER=`cat ${WWW_DIR}/VERSION` echo "Info: XML-API CCU Addon
" - echo "Info: https://github.com/hobbyquaker/XML-API" + echo "Info: https://github.com/homematic-community/XML-API" echo "Version: $(cat ${WWW_DIR}/VERSION)" echo "Name: XML-API" echo "Operations: uninstall" diff --git a/xmlapi/DEVDB.tcl b/xmlapi/DEVDB.tcl old mode 100755 new mode 100644 index 7aa64e5..2643d1b --- a/xmlapi/DEVDB.tcl +++ b/xmlapi/DEVDB.tcl @@ -11,7 +11,7 @@ source once.tcl ################################################################################ set DEVDB_DIRECTORY "/www/config/devdescr" -set DEVDB_FILE "/var/tmp/devdb.txt" +set DEVDB_FILE "$DEVDB_DIRECTORY/DEVDB.tcl" ################################################################################ # Globale Variablen # @@ -29,7 +29,7 @@ array set DEV_HIGHLIGHT "" proc devdb_saveToFile { file_name content } { upvar $content file_content - + if { ![catch { open $file_name w } fd] } then { puts $fd $file_content close $fd @@ -53,29 +53,29 @@ proc devdb_loadFromFile { file_name } { ################################################################################ proc AddUIDescription {key} { - global DEV_LIST - - lappend DEV_LIST $key + global DEV_LIST + + lappend DEV_LIST $key } proc AddDescription {key desc} { - global DEV_DESCRIPTION - - set DEV_DESCRIPTION($key) $desc + global DEV_DESCRIPTION + + set DEV_DESCRIPTION($key) $desc } proc AddPaths {key p_PATHS} { - global DEV_PATHS + global DEV_PATHS upvar $p_PATHS PATHS - - set DEV_PATHS($key) $PATHS + + set DEV_PATHS($key) $PATHS } proc AddCoordinates {key p_P} { - global DEV_HIGHLIGHT + global DEV_HIGHLIGHT upvar $p_P P - - set DEV_HIGHLIGHT($key) $P + + set DEV_HIGHLIGHT($key) $P } ################################################################################ @@ -83,26 +83,26 @@ proc AddCoordinates {key p_P} { ################################################################################ proc DEV_getImagePath {key size} { - global DEV_PATHS DEV_LIST - - if { [lsearch $DEV_LIST $key] < 0 } then { return "" } - - set pathlist $DEV_PATHS($key) - set path "" - - foreach px $pathlist { - - set asize [lindex $px 0] - set apath [lindex $px 1] - - if {$asize == $size} then { - set path $apath - break - } - } - - - return $path + global DEV_PATHS DEV_LIST + + if { [lsearch $DEV_LIST $key] < 0 } then { return "" } + + set pathlist $DEV_PATHS($key) + set path "" + + foreach px $pathlist { + + set asize [lindex $px 0] + set apath [lindex $px 1] + + if {$asize == $size} then { + set path $apath + break + } + } + + + return $path } ################################################################################ @@ -111,40 +111,40 @@ proc DEV_getImagePath {key size} { proc PrintCoordinates {} { - global DEV_HIGHLIGHT - foreach descr [array names DEV_HIGHLIGHT] { - - puts "Type: $descr -- Coordinates: $DEV_HIGHLIGHT($descr)" - } + global DEV_HIGHLIGHT + foreach descr [array names DEV_HIGHLIGHT] { + + puts "Type: $descr -- Coordinates: $DEV_HIGHLIGHT($descr)" + } } proc PrintPaths {} { - global DEV_PATHS - foreach descr [array names DEV_PATHS] { - puts "Type: $descr -- Path: $DEV_PATHS($descr)" - } + global DEV_PATHS + foreach descr [array names DEV_PATHS] { + puts "Type: $descr -- Path: $DEV_PATHS($descr)" + } } proc PrintDescriptions {} { - global DEV_DESCRIPTION - foreach descr [array names DEV_DESCRIPTION] { - puts "Type: $descr -- Description: $DEV_DESCRIPTION($descr)" - } + global DEV_DESCRIPTION + foreach descr [array names DEV_DESCRIPTION] { + puts "Type: $descr -- Description: $DEV_DESCRIPTION($descr)" + } } proc PrintList {} { - global DEV_LIST - puts "List: $DEV_LIST" + global DEV_LIST + puts "List: $DEV_LIST" } proc Print {} { - PrintList - PrintDescriptions - PrintPaths - PrintCoordinates + PrintList + PrintDescriptions + PrintPaths + PrintCoordinates } ################################################################################ @@ -152,10 +152,10 @@ proc Print {} { ################################################################################ proc DEVDB_create { } { - global DEVDB_DIRECTORY - + global DEVDB_DIRECTORY + set filelist [glob -nocomplain "$DEVDB_DIRECTORY/*.tcl"] - + foreach file $filelist { if {[file tail $file] == "DEVDB.tcl"} then { continue } @@ -164,14 +164,14 @@ proc DEVDB_create { } { set DESCRIPTION "" set PATHLIST "" set P "" - + sourceOnce $file catch { AddUIDescription $TYPE AddDescription $TYPE $DESCRIPTION AddPaths $TYPE PATHLIST - AddCoordinates $TYPE P + AddCoordinates $TYPE P } } } @@ -179,15 +179,15 @@ proc DEVDB_create { } { proc DEVDB_save { } { global DEVDB_FILE global DEV_LIST DEV_DESCRIPTION DEV_PATHS DEV_HIGHLIGHT - + array set debdb "" set devdb(DEV_LIST) $DEV_LIST set devdb(DEV_DESCRIPTION) [array get DEV_DESCRIPTION] set devdb(DEV_PATHS) [array get DEV_PATHS] set devdb(DEV_HIGHLIGHT) [array get DEV_HIGHLIGHT] - + set content [array get devdb] - + devdb_saveToFile $DEVDB_FILE content } @@ -207,7 +207,7 @@ proc DEVDB_load { } { array set DEV_PATHS $devdb(DEV_PATHS) array set DEV_HIGHLIGHT $devdb(DEV_HIGHLIGHT) } - + } ################################################################################ @@ -216,21 +216,21 @@ proc DEVDB_load { } { #proc Clear {} { # -# global DEV_LIST DEV_DESCRIPTION DEV_PATHS DEV_HIGHLIGHT -# -# set DEV_LIST "" -# array_clear DEV_DESCRIPTION "" -# array_clear DEV_PATHS "" -# array_clear DEV_HIGHLIGHT "" +# global DEV_LIST DEV_DESCRIPTION DEV_PATHS DEV_HIGHLIGHT +# +# set DEV_LIST "" +# array_clear DEV_DESCRIPTION "" +# array_clear DEV_PATHS "" +# array_clear DEV_HIGHLIGHT "" #} - + ################################################################################ # Einsprungpunkt # ################################################################################ -if { ![file exists $DEVDB_FILE] } then { - DEVDB_create - DEVDB_save -} else { +#if { ![file exists $DEVDB_FILE] } then { +# DEVDB_create +# DEVDB_save +#} else { DEVDB_load -} +#} diff --git a/xmlapi/cgi.tcl b/xmlapi/cgi.tcl old mode 100755 new mode 100644 index 21b2565..d4cef62 --- a/xmlapi/cgi.tcl +++ b/xmlapi/cgi.tcl @@ -22,14 +22,14 @@ proc cgi_http_head {args} { set _cgi(http_head_in_progress) 1 if {0 == [llength $args]} { - cgi_content_type + cgi_content_type cgi_puts "Expires: Sun, 06 Nov 1994 00:00:00 GMT" } else { - if {[catch {uplevel 1 [lindex $args 0]} errMsg]} { - set savedInfo $errorInfo - cgi_content_type + if {[catch {uplevel 1 [lindex $args 0]} errMsg]} { + set savedInfo $errorInfo + cgi_content_type cgi_puts "Expires: Sun, 06 Nov 1994 00:00:00 GMT" - } + } } cgi_puts "" @@ -37,7 +37,7 @@ proc cgi_http_head {args} { set _cgi(http_head_done) 1 if {[info exists savedInfo]} { - error $errMsg $savedInfo + error $errMsg $savedInfo } } @@ -63,18 +63,18 @@ proc cgi_content_type {args} { global _cgi if {0==[llength $args]} { - set t text/html + set t text/html } else { - set t [lindex $args 0] - if {[regexp ^multipart/ $t]} { - set _cgi(multipart) 1 - } + set t [lindex $args 0] + if {[regexp ^multipart/ $t]} { + set _cgi(multipart) 1 + } } if {[info exists _cgi(http_head_in_progress)]} { - cgi_puts "Content-type: $t" + cgi_puts "Content-type: $t" } else { - cgi_http_head [list cgi_content_type $t] + cgi_http_head [list cgi_content_type $t] } } @@ -82,13 +82,13 @@ proc cgi_redirect {t} { global _cgi if {[info exists _cgi(http_head_in_progress)]} { - cgi_status 302 Redirected - cgi_puts "Uri: $t" - cgi_puts "Location: $t" + cgi_status 302 Redirected + cgi_puts "Uri: $t" + cgi_puts "Location: $t" } else { - cgi_http_head { - cgi_redirect $t - } + cgi_http_head { + cgi_redirect $t + } } } @@ -97,9 +97,9 @@ proc cgi_location {t} { global _cgi if {[info exists _cgi(http_head_in_progress)]} { - cgi_puts "Location: $t" + cgi_puts "Location: $t" } else { - cgi_http_head "cgi_location $t" + cgi_http_head "cgi_location $t" } } @@ -107,7 +107,7 @@ proc cgi_target {t} { global _cgi if {![info exists _cgi(http_head_in_progress)]} { - error "cgi_target must be set from within cgi_http_head." + error "cgi_target must be set from within cgi_http_head." } cgi_puts "Window-target: $t" } @@ -118,12 +118,12 @@ proc cgi_refresh {seconds {url ""}} { global _cgi if {![info exists _cgi(http_head_in_progress)]} { - error "cgi_refresh must be set from within cgi_http_head. Try using cgi_http_equiv instead." + error "cgi_refresh must be set from within cgi_http_head. Try using cgi_http_equiv instead." } cgi_put "Refresh: $seconds" if {0!=[string compare $url ""]} { - cgi_put "; $url" + cgi_put "; $url" } cgi_puts "" } @@ -133,7 +133,7 @@ proc cgi_pragma {arg} { global _cgi if {![info exists _cgi(http_head_in_progress)]} { - error "cgi_pragma must be set from within cgi_http_head." + error "cgi_pragma must be set from within cgi_http_head." } cgi_puts "Pragma: $arg" } @@ -142,9 +142,9 @@ proc cgi_pragma {arg} { # support for debugging or other crucial things we need immediately ################################################## -proc cgi_comment {args} {} ;# need this asap +proc cgi_comment {args} {} ;# need this asap -proc cgi_html_comment {args} { +proc cgi_html_comment {args} { regsub -all {>} $args {\>} args cgi_put "" } @@ -156,51 +156,51 @@ proc cgi_debug {args} { set old $_cgi(debug) set arg [lindex $args 0] if {$arg == "-on"} { - set _cgi(debug) -on - set args [lrange $args 1 end] + set _cgi(debug) -on + set args [lrange $args 1 end] } elseif {$arg == "-off"} { - set _cgi(debug) -off - set args [lrange $args 1 end] + set _cgi(debug) -off + set args [lrange $args 1 end] } elseif {[regexp "^-t" $arg]} { - set temp 1 - set _cgi(debug) -on - set args [lrange $args 1 end] + set temp 1 + set _cgi(debug) -on + set args [lrange $args 1 end] } elseif {[regexp "^-noprint$" $arg]} { - set noprint 1 - set args [lrange $args 1 end] + set noprint 1 + set args [lrange $args 1 end] } set arg [lindex $args 0] if {$arg == "--"} { - set args [lrange $args 1 end] + set args [lrange $args 1 end] } if {[llength $args]} { - if {$_cgi(debug) == "-on"} { - - _cgi_close_tag - # force http head and open html, head, body - catch { - if {[info exists noprint]} { - uplevel 1 [lindex $args 0] - } else { - cgi_html { - cgi_head { - cgi_title "debugging before complete HTML head" - } - # force body open and leave open - _cgi_body_start - uplevel 1 [lindex $args 0] - # bop back out to catch, so we don't close body - error "ignore" - } - } - } - } + if {$_cgi(debug) == "-on"} { + + _cgi_close_tag + # force http head and open html, head, body + catch { + if {[info exists noprint]} { + uplevel 1 [lindex $args 0] + } else { + cgi_html { + cgi_head { + cgi_title "debugging before complete HTML head" + } + # force body open and leave open + _cgi_body_start + uplevel 1 [lindex $args 0] + # bop back out to catch, so we don't close body + error "ignore" + } + } + } + } } if {[info exists temp]} { - set _cgi(debug) $old + set _cgi(debug) $old } return $old } @@ -212,19 +212,19 @@ proc cgi_uid_check {user} { if {[regexp "^-off$" $user]} return if {[info exists env(USER)]} { - set whoami $env(USER) + set whoami $env(USER) } elseif {0==[catch {exec whoami} whoami]} { - # "who am i" on some Linux hosts returns "" so try whoami first + # "who am i" on some Linux hosts returns "" so try whoami first } elseif {0==[catch {exec who am i} whoami]} { - # skip over "host!" - regexp "(.*!)?(\[^ \t]*)" $whoami dummy dummy whoami + # skip over "host!" + regexp "(.*!)?(\[^ \t]*)" $whoami dummy dummy whoami } elseif {0==[catch {package require registry}]} { - set whoami [registry get HKEY_LOCAL_MACHINE\\Network\\Logon username] + set whoami [registry get HKEY_LOCAL_MACHINE\\Network\\Logon username] } else { - set whoami $user ;# give up and let go + set whoami $user ;# give up and let go } if {$whoami != "$user"} { - error "Warning: This CGI script expects to run with uid \"$user\". However, this script is running as \"$whoami\"." + error "Warning: This CGI script expects to run with uid \"$user\". However, this script is running as \"$whoami\"." } } @@ -233,21 +233,21 @@ proc cgi_uid_check {user} { proc cgi_parray {a {pattern *}} { upvar 1 $a array if {![array exists array]} { - error "\"$a\" isn't an array" + error "\"$a\" isn't an array" } set maxl 0 foreach name [lsort [array names array $pattern]] { - if {[string length $name] > $maxl} { - set maxl [string length $name] - } + if {[string length $name] > $maxl} { + set maxl [string length $name] + } } cgi_preformatted { - set maxl [expr {$maxl + [string length $a] + 2}] - foreach name [lsort [array names array $pattern]] { - set nameString [format %s(%s) $a $name] - cgi_puts [cgi_quote_html [format "%-*s = %s" $maxl $nameString $array($name)]] - } + set maxl [expr {$maxl + [string length $a] + 2}] + foreach name [lsort [array names array $pattern]] { + set nameString [format %s(%s) $a $name] + cgi_puts [cgi_quote_html [format "%-*s = %s" $maxl $nameString $array($name)]] + } } } @@ -258,67 +258,67 @@ proc cgi_eval {cmd} { set _cgi(body) $cmd uplevel 1 { - global env _cgi errorInfo - - if {1==[catch $_cgi(body) errMsg]} { - # error occurred, handle it - set _cgi(errorInfo) $errorInfo - - if {![info exists env(REQUEST_METHOD)]} { - puts stderr $_cgi(errorInfo) - return - } - # the following code is all to force browsers into a state - # such that diagnostics can be reliably shown - - # close irrelevant things - _cgi_close_procs - # force http head and open html, head, body - cgi_html { - cgi_body { - if {[info exists _cgi(client_error)]} { - cgi_h3 "Client Error" - cgi_p "$errMsg Report this to your system administrator or browser vendor." - } else { - cgi_put [cgi_anchor_name cgierror] - cgi_h3 "An internal error was detected in the service\ - software. The diagnostics are being emailed to\ - the service system administrator ($_cgi(admin_email))." - - if {$_cgi(debug) == "-on"} { - cgi_puts "Heck, since you're debugging, I'll show you the\ - errors right here:" - # suppress formatting - cgi_preformatted { - cgi_puts [cgi_quote_html $_cgi(errorInfo)] - } - } else { - cgi_mail_start $_cgi(admin_email) - cgi_mail_add "Subject: [cgi_name] CGI problem" - cgi_mail_add - cgi_mail_add "CGI environment:" - cgi_mail_add "REQUEST_METHOD: $env(REQUEST_METHOD)" - cgi_mail_add "SCRIPT_NAME: $env(SCRIPT_NAME)" - # this next few things probably don't need - # a catch but I'm not positive - catch {cgi_mail_add "HTTP_USER_AGENT: $env(HTTP_USER_AGENT)"} - catch {cgi_mail_add "HTTP_REFERER: $env(HTTP_REFERER)"} - catch {cgi_mail_add "HTTP_HOST: $env(HTTP_HOST)"} - catch {cgi_mail_add "REMOTE_HOST: $env(REMOTE_HOST)"} - catch {cgi_mail_add "REMOTE_ADDR: $env(REMOTE_ADDR)"} - cgi_mail_add "cgi.tcl version: 1.8.0" - cgi_mail_add "input:" - catch {cgi_mail_add $_cgi(input)} - cgi_mail_add "cookie:" - catch {cgi_mail_add $env(HTTP_COOKIE)} - cgi_mail_add "errorInfo:" - cgi_mail_add "$_cgi(errorInfo)" - cgi_mail_end - } - } - } ;# end cgi_body - } ;# end cgi_html - } ;# end catch + global env _cgi errorInfo + + if {1==[catch $_cgi(body) errMsg]} { + # error occurred, handle it + set _cgi(errorInfo) $errorInfo + + if {![info exists env(REQUEST_METHOD)]} { + puts stderr $_cgi(errorInfo) + return + } + # the following code is all to force browsers into a state + # such that diagnostics can be reliably shown + + # close irrelevant things + _cgi_close_procs + # force http head and open html, head, body + cgi_html { + cgi_body { + if {[info exists _cgi(client_error)]} { + cgi_h3 "Client Error" + cgi_p "$errMsg Report this to your system administrator or browser vendor." + } else { + cgi_put [cgi_anchor_name cgierror] + cgi_h3 "An internal error was detected in the service\ + software. The diagnostics are being emailed to\ + the service system administrator ($_cgi(admin_email))." + + if {$_cgi(debug) == "-on"} { + cgi_puts "Heck, since you're debugging, I'll show you the\ + errors right here:" + # suppress formatting + cgi_preformatted { + cgi_puts [cgi_quote_html $_cgi(errorInfo)] + } + } else { + cgi_mail_start $_cgi(admin_email) + cgi_mail_add "Subject: [cgi_name] CGI problem" + cgi_mail_add + cgi_mail_add "CGI environment:" + cgi_mail_add "REQUEST_METHOD: $env(REQUEST_METHOD)" + cgi_mail_add "SCRIPT_NAME: $env(SCRIPT_NAME)" + # this next few things probably don't need + # a catch but I'm not positive + catch {cgi_mail_add "HTTP_USER_AGENT: $env(HTTP_USER_AGENT)"} + catch {cgi_mail_add "HTTP_REFERER: $env(HTTP_REFERER)"} + catch {cgi_mail_add "HTTP_HOST: $env(HTTP_HOST)"} + catch {cgi_mail_add "REMOTE_HOST: $env(REMOTE_HOST)"} + catch {cgi_mail_add "REMOTE_ADDR: $env(REMOTE_ADDR)"} + cgi_mail_add "cgi.tcl version: 1.8.0" + cgi_mail_add "input:" + catch {cgi_mail_add $_cgi(input)} + cgi_mail_add "cookie:" + catch {cgi_mail_add $env(HTTP_COOKIE)} + cgi_mail_add "errorInfo:" + cgi_mail_add "$_cgi(errorInfo)" + cgi_mail_end + } + } + } ;# end cgi_body + } ;# end cgi_html + } ;# end catch } ;# end uplevel } @@ -341,9 +341,9 @@ proc cgi_root {args} { global _cgi if {[llength $args]} { - set _cgi(root) [lindex $args 0] + set _cgi(root) [lindex $args 0] } else { - set _cgi(root) + set _cgi(root) } } @@ -353,35 +353,35 @@ proc cgi_cgi {args} { set root $_cgi(root) if {0!=[string compare $root ""]} { - if {![regexp "/$" $root]} { - append root "/" - } + if {![regexp "/$" $root]} { + append root "/" + } } - + set suffix [cgi_suffix] set arg [lindex $args 0] if {0==[string compare $arg "-suffix"]} { - set suffix [lindex $args 1] - set args [lrange $args 2 end] + set suffix [lindex $args 1] + set args [lrange $args 2 end] } if {[llength $args]==1} { - return $root[lindex $args 0]$suffix + return $root[lindex $args 0]$suffix } else { - return $root[lindex $args 0]$suffix?[join [lrange $args 1 end] &] + return $root[lindex $args 0]$suffix?[join [lrange $args 1 end] &] } } proc cgi_suffix {args} { global _cgi if {[llength $args] > 0} { - set _cgi(suffix) [lindex $args 0] + set _cgi(suffix) [lindex $args 0] } if {![info exists _cgi(suffix)]} { - return .cgi + return .cgi } else { - return $_cgi(suffix) + return $_cgi(suffix) } } @@ -405,14 +405,14 @@ proc cgi_link {args} { set tag [lindex $args 0] switch -- [llength $args] { - 1 { - set label $_cgi_link($tag,label) - } 2 { - set label [lindex $args end] - } default { - set _cgi_link($tag,label) [set label [lindex $args 1]] - set _cgi_link($tag,url) [lrange $args 2 end] - } + 1 { + set label $_cgi_link($tag,label) + } 2 { + set label [lindex $args end] + } default { + set _cgi_link($tag,label) [set label [lindex $args 1]] + set _cgi_link($tag,url) [lrange $args 2 end] + } } return [eval cgi_url [list $label] $_cgi_link($tag,url)] @@ -425,7 +425,7 @@ proc cgi_imglink {args} { set tag [lindex $args 0] if {[llength $args] >= 2} { - set _cgi_imglink($tag) [eval cgi_img [lrange $args 1 end]] + set _cgi_imglink($tag) [eval cgi_img [lrange $args 1 end]] } return $_cgi_imglink($tag) } @@ -452,11 +452,11 @@ proc cgi_url {display args} { set buf "$display" } @@ -480,13 +480,13 @@ proc cgi_img {args} { set buf "" } @@ -501,13 +501,13 @@ proc cgi_base {args} { cgi_put "" } @@ -518,67 +518,67 @@ proc cgi_base {args} { if {[info tclversion] >= 8.2} { proc cgi_unquote_input buf { - # rewrite "+" back to space - # protect \ from quoting another \ and throwing off other things - # replace line delimiters with newlines - set buf [string map -nocase [list + { } "\\" "\\\\" %0d%0a \n] $buf] + # rewrite "+" back to space + # protect \ from quoting another \ and throwing off other things + # replace line delimiters with newlines + set buf [string map -nocase [list + { } "\\" "\\\\" %0d%0a \n] $buf] - # prepare to process all %-escapes - regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf + # prepare to process all %-escapes + regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf - # process \u unicode mapped chars - encoding convertfrom [subst -novar -nocommand $buf] + # process \u unicode mapped chars + encoding convertfrom [subst -novar -nocommand $buf] } } elseif {[info tclversion] >= 8.1} { proc cgi_unquote_input buf { - # rewrite "+" back to space - regsub -all {\+} $buf { } buf - # protect \ from quoting another \ and throwing off other things - regsub -all {\\} $buf {\\\\} buf + # rewrite "+" back to space + regsub -all {\+} $buf { } buf + # protect \ from quoting another \ and throwing off other things + regsub -all {\\} $buf {\\\\} buf - # replace line delimiters with newlines - regsub -all -nocase "%0d%0a" $buf "\n" buf + # replace line delimiters with newlines + regsub -all -nocase "%0d%0a" $buf "\n" buf - # prepare to process all %-escapes - regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf - # process \u unicode mapped chars - return [subst -novar -nocommand $buf] + # prepare to process all %-escapes + regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf + # process \u unicode mapped chars + return [subst -novar -nocommand $buf] } } else { proc cgi_unquote_input {buf} { - # rewrite "+" back to space - regsub -all {\+} $buf { } buf - # protect \ from quoting another \ and throwing off other things first - # protect $ from doing variable expansion - # protect [ from doing evaluation - # protect " from terminating string - regsub -all {([\\["$])} $buf {\\\1} buf - - # replace line delimiters with newlines - regsub -all -nocase "%0d%0a" $buf "\n" buf - # Mosaic sends just %0A. This is handled in the next command. + # rewrite "+" back to space + regsub -all {\+} $buf { } buf + # protect \ from quoting another \ and throwing off other things first + # protect $ from doing variable expansion + # protect [ from doing evaluation + # protect " from terminating string + regsub -all {([\\["$])} $buf {\\\1} buf + + # replace line delimiters with newlines + regsub -all -nocase "%0d%0a" $buf "\n" buf + # Mosaic sends just %0A. This is handled in the next command. - # prepare to process all %-escapes - regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {[format %c 0x\1]} buf - # process %-escapes and undo all protection - eval return \"$buf\" + # prepare to process all %-escapes + regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {[format %c 0x\1]} buf + # process %-escapes and undo all protection + eval return \"$buf\" } } # return string but with html-special characters escaped, # necessary if you want to send unknown text to an html-formatted page. proc cgi_quote_html {s} { - regsub -all {&} $s {\&} s ;# must be first! - regsub -all {"} $s {\"} s - regsub -all {<} $s {\<} s - regsub -all {>} $s {\>} s - regsub -all {�} $s {\ä} s - regsub -all {�} $s {\Ä} s - regsub -all {�} $s {\ö} s - regsub -all {�} $s {\Ö} s - regsub -all {�} $s {\ü} s - regsub -all {�} $s {\Ü} s - regsub -all {�} $s {\ß} s + regsub -all {&} $s {\&} s ;# must be first! + regsub -all {"} $s {\"} s + regsub -all {<} $s {\<} s + regsub -all {>} $s {\>} s + regsub -all {�} $s {\ä} s + regsub -all {�} $s {\Ä} s + regsub -all {�} $s {\ö} s + regsub -all {�} $s {\Ö} s + regsub -all {�} $s {\ü} s + regsub -all {�} $s {\Ü} s + regsub -all {�} $s {\ß} s return $s } @@ -603,7 +603,7 @@ proc cgi_quote_url {in} { proc cgi_br {args} { cgi_put "" } @@ -615,8 +615,8 @@ for {set _cgi(tmp) 1} {$_cgi(tmp)<8} {incr _cgi(tmp)} { proc cgi_h {num args} { cgi_put " 1} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" - set args [lrange $args end end] + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + set args [lrange $args end end] } cgi_put ">[lindex $args 0]" } @@ -624,8 +624,8 @@ proc cgi_h {num args} { proc cgi_p {args} { cgi_put " 1} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" - set args [lrange $args end end] + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + set args [lrange $args end end] } cgi_put ">[lindex $args 0]

" } @@ -639,7 +639,7 @@ proc cgi_blockquote {s} {cgi_puts
$s
} # Shorthand for
. We used to use
tags but that # is now officially unsupported. -proc cgi_center {cmd} { +proc cgi_center {cmd} { uplevel 1 "cgi_division align=center [list $cmd]" } @@ -648,7 +648,7 @@ proc cgi_division {args} { _cgi_close_proc_push "cgi_put
" if {[llength $args]} { - cgi_put "[_cgi_lrange $args 0 [expr {[llength $args]-2}]]" + cgi_put "[_cgi_lrange $args 0 [expr {[llength $args]-2}]]" } cgi_put ">" uplevel 1 [lindex $args end] @@ -660,7 +660,7 @@ proc cgi_preformatted {args} { _cgi_close_proc_push "cgi_put " if {[llength $args]} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_put ">" uplevel 1 [lindex $args end] @@ -674,7 +674,7 @@ proc cgi_preformatted {args} { proc cgi_li {args} { cgi_put
  • 1} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_put ">[lindex $args end]
  • " } @@ -684,7 +684,7 @@ proc cgi_number_list {args} { _cgi_close_proc_push "cgi_put " if {[llength $args] > 1} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_put ">" uplevel 1 [lindex $args end] @@ -697,7 +697,7 @@ proc cgi_bullet_list {args} { _cgi_close_proc_push "cgi_put " if {[llength $args] > 1} { - cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" + cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_put ">" uplevel 1 [lindex $args end] @@ -737,17 +737,17 @@ proc cgi_directory_list {cmd} { # text support ################################################## -proc cgi_put {s} {cgi_puts -nonewline $s} +proc cgi_put {s} {cgi_puts -nonewline $s} # some common special characters -proc cgi_lt {} {return "<"} -proc cgi_gt {} {return ">"} -proc cgi_amp {} {return "&"} -proc cgi_quote {} {return """} +proc cgi_lt {} {return "<"} +proc cgi_gt {} {return ">"} +proc cgi_amp {} {return "&"} +proc cgi_quote {} {return """} proc cgi_enspace {} {return " "} proc cgi_emspace {} {return " "} proc cgi_nbspace {} {return " "} ;# nonbreaking space -proc cgi_tm {} {return "®"} ;# registered trademark +proc cgi_tm {} {return "®"} ;# registered trademark proc cgi_copyright {} {return "©"} proc cgi_isochar {n} {return "&#$n;"} proc cgi_breakable {} {return ""} @@ -763,28 +763,28 @@ proc cgi_unbreakable {cmd} { proc cgi_nl {args} { set buf "" } -proc cgi_bold {s} {return "$s"} +proc cgi_bold {s} {return "$s"} proc cgi_italic {s} {return "$s"} proc cgi_underline {s} {return "$s"} proc cgi_strikeout {s} {return "$s"} proc cgi_subscript {s} {return "$s"} proc cgi_superscript {s} {return "$s"} proc cgi_typewriter {s} {return "$s"} -proc cgi_blink {s} {return "$s"} +proc cgi_blink {s} {return "$s"} proc cgi_emphasis {s} {return "$s"} -proc cgi_strong {s} {return "$s"} -proc cgi_cite {s} {return "$s"} +proc cgi_strong {s} {return "$s"} +proc cgi_cite {s} {return "$s"} proc cgi_sample {s} {return "$s"} proc cgi_keyboard {s} {return "$s"} proc cgi_variable {s} {return "$s"} proc cgi_definition {s} {return "$s"} -proc cgi_big {s} {return "$s"} -proc cgi_small {s} {return "$s"} +proc cgi_big {s} {return "$s"} +proc cgi_small {s} {return "$s"} proc cgi_basefont {size} {cgi_put ""} @@ -793,11 +793,11 @@ proc cgi_font {args} { set buf "[lindex $args end]" } @@ -808,7 +808,7 @@ proc cgi_buffer {cmd} { global _cgi if {0==[info exists _cgi(returnIndex)]} { - set _cgi(returnIndex) 0 + set _cgi(returnIndex) 0 } rename cgi_puts cgi_puts$_cgi(returnIndex) @@ -816,20 +816,20 @@ proc cgi_buffer {cmd} { set _cgi(return[set _cgi(returnIndex)]) "" proc cgi_puts args { - global _cgi - upvar #0 _cgi(return[set _cgi(returnIndex)]) buffer + global _cgi + upvar #0 _cgi(return[set _cgi(returnIndex)]) buffer - append buffer [lindex $args end] - if {[llength $args] == 1} { - append buffer $_cgi(buffer_nl) - } + append buffer [lindex $args end] + if {[llength $args] == 1} { + append buffer $_cgi(buffer_nl) + } } # must restore things before allowing the eval to fail # so catch here and rethrow later if {[catch {uplevel 1 $cmd} errMsg]} { - global errorInfo - set savedInfo $errorInfo + global errorInfo + set savedInfo $errorInfo } # not necessary to put remainder of code in close_proc_push since it's @@ -842,7 +842,7 @@ proc cgi_buffer {cmd} { rename cgi_puts$_cgi(returnIndex) cgi_puts if {[info exists savedInfo]} { - error $errMsg $savedInfo + error $errMsg $savedInfo } return $buffer } @@ -864,9 +864,9 @@ proc cgi_html {args} { set html [lindex $args end] set argc [llength $args] if {$argc > 1} { - eval _cgi_html_start [lrange $args 0 [expr {$argc-2}]] + eval _cgi_html_start [lrange $args 0 [expr {$argc-2}]] } else { - _cgi_html_start + _cgi_html_start } uplevel 1 $html _cgi_html_end @@ -874,7 +874,7 @@ proc cgi_html {args} { proc _cgi_html_start {args} { global _cgi - + if {[info exists _cgi(html_in_progress)]} return _cgi_http_head_implicit @@ -883,11 +883,11 @@ proc _cgi_html_start {args} { append buf "" } @@ -916,14 +916,14 @@ proc cgi_head {{head {}}} { global _cgi if {[info exists _cgi(head_done)]} { - return + return } # allow us to be recalled so that we can display errors if {0 == [info exists _cgi(head_in_progress)]} { - _cgi_http_head_implicit - set _cgi(head_in_progress) 1 - cgi_puts "" + _cgi_http_head_implicit + set _cgi(head_in_progress) 1 + cgi_puts "" } # prevent cgi_html (during error handling) from generating html tags @@ -932,15 +932,15 @@ proc cgi_head {{head {}}} { # them up if {0 == [string length $head]} { - if {[catch {cgi_title}]} { - set head "cgi_title untitled" - } + if {[catch {cgi_title}]} { + set head "cgi_title untitled" + } } uplevel 1 $head if {![info exists _cgi(head_suppress_tag)]} { - cgi_puts "" + cgi_puts "" } else { - unset _cgi(head_suppress_tag) + unset _cgi(head_suppress_tag) } set _cgi(head_done) 1 @@ -957,17 +957,17 @@ proc cgi_title {args} { set title [lindex $args 0] if {[llength $args]} { - _cgi_http_head_implicit + _cgi_http_head_implicit - # we could just generate tags, but head-level commands - # might follow so just suppress the head tags entirely - if {![info exists _cgi(head_in_progress)]} { - set _cgi(head_in_progress) 1 - set _cgi(head_suppress_tag) 1 - } + # we could just generate tags, but head-level commands + # might follow so just suppress the head tags entirely + if {![info exists _cgi(head_in_progress)]} { + set _cgi(head_in_progress) 1 + set _cgi(head_suppress_tag) 1 + } - set _cgi(title) $title - cgi_puts "$title" + set _cgi(title) $title + cgi_puts "$title" } return $_cgi(title) } @@ -986,11 +986,11 @@ proc cgi_http_equiv {type contents} { proc cgi_meta {args} { cgi_put "" } @@ -998,13 +998,13 @@ proc cgi_meta {args} { proc cgi_relationship {rel href args} { cgi_puts "" } @@ -1013,7 +1013,7 @@ proc cgi_name {args} { global _cgi if {[llength $args]} { - set _cgi(name) [lindex $args 0] + set _cgi(name) [lindex $args 0] } return $_cgi(name) } @@ -1027,12 +1027,12 @@ proc cgi_body {args} { # allow user to "return" from the body without missing _cgi_body_end if {1==[catch { - eval _cgi_body_start [lrange $args 0 [expr [llength $args]-2]] - uplevel 1 [lindex $args end] + eval _cgi_body_start [lrange $args 0 [expr [llength $args]-2]] + uplevel 1 [lindex $args end] } errMsg]} { - set savedInfo $errorInfo - set savedCode $errorCode - error $errMsg $savedInfo $savedCode + set savedInfo $errorInfo + set savedCode $errorCode + error $errMsg $savedInfo $savedCode } _cgi_body_end } @@ -1047,38 +1047,38 @@ proc _cgi_body_start {args} { cgi_put "" cgi_debug { - global env - catch {cgi_puts "Input:
    $_cgi(input)
    "} - catch {cgi_puts "Cookie:
    $env(HTTP_COOKIE)
    "} + global env + catch {cgi_puts "Input:
    $_cgi(input)
    "} + catch {cgi_puts "Cookie:
    $env(HTTP_COOKIE)
    "} } if {![info exists _cgi(errorInfo)]} { - uplevel 2 app_body_start + uplevel 2 app_body_start } } proc _cgi_body_end {} { global _cgi if {![info exists _cgi(errorInfo)]} { - uplevel 2 app_body_end + uplevel 2 app_body_end } unset _cgi(body_in_progress) cgi_puts "" if {[info exists _cgi(multipart)]} { - unset _cgi(http_head_done) - catch {unset _cgi(http_status_done)} - unset _cgi(head_done) - catch {unset _cgi(head_suppress_tag)} + unset _cgi(http_head_done) + catch {unset _cgi(http_status_done)} + unset _cgi(head_done) + catch {unset _cgi(head_suppress_tag)} } } @@ -1129,7 +1129,7 @@ proc cgi_param {nameval} { regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value if {$q != "="} { - set value "" + set value "" } cgi_puts "" } @@ -1138,7 +1138,7 @@ proc cgi_param {nameval} { proc _cgi_close_proc_push {p} { global _cgi if {![info exists _cgi(close_proc)]} { - set _cgi(close_proc) "" + set _cgi(close_proc) "" } set _cgi(close_proc) "$p; $_cgi(close_proc)" } @@ -1159,7 +1159,7 @@ proc _cgi_close_procs {} { _cgi_close_tag if {[info exists _cgi(close_proc)]} { - uplevel #0 $_cgi(close_proc) + uplevel #0 $_cgi(close_proc) } } @@ -1167,8 +1167,8 @@ proc _cgi_close_tag {} { global _cgi if {[info exists _cgi(tag_in_progress)]} { - cgi_put ">" - unset _cgi(tag_in_progress) + cgi_put ">" + unset _cgi(tag_in_progress) } } @@ -1179,11 +1179,11 @@ proc _cgi_close_tag {} { proc cgi_hr {args} { set buf "" } @@ -1201,22 +1201,22 @@ proc cgi_form {action args} { _cgi_close_proc_push _cgi_form_end cgi_put "
    " uplevel 1 [lindex $args end] @@ -1233,7 +1233,7 @@ proc _cgi_form_end {} { proc _cgi_form_multiple_check {} { global _cgi if {[info exists _cgi(form_in_progress)]} { - error "Cannot create form (or isindex) with form already in progress." + error "Cannot create form (or isindex) with form already in progress." } } @@ -1242,13 +1242,13 @@ proc cgi_isindex {args} { cgi_put "" } @@ -1264,76 +1264,76 @@ proc cgi_input {{fakeinput {}} {fakecookie {}}} { set _cgi(uservars,autolist) {} if {[info exists env(CONTENT_TYPE)] && [regexp ^multipart/form-data $env(CONTENT_TYPE)]} { - if {![info exists env(REQUEST_METHOD)]} { - # running by hand - set fid [open $fakeinput] - } else { - set fid stdin - } - if {([info tclversion] >= 8.1) || [catch exp_version] || [info exists _cgi(no_binary_upload)]} { - _cgi_input_multipart $fid - } else { - _cgi_input_multipart_binary $fid - } + if {![info exists env(REQUEST_METHOD)]} { + # running by hand + set fid [open $fakeinput] + } else { + set fid stdin + } + if {([info tclversion] >= 8.1) || [catch exp_version] || [info exists _cgi(no_binary_upload)]} { + _cgi_input_multipart $fid + } else { + _cgi_input_multipart_binary $fid + } } else { - if {![info exists env(REQUEST_METHOD)]} { - set input $fakeinput - set env(HTTP_COOKIE) $fakecookie - } elseif { $env(REQUEST_METHOD) == "GET" } { - set input "" - catch {set input $env(QUERY_STRING)} ;# doesn't have to be set - } elseif { $env(REQUEST_METHOD) == "HEAD" } { - set input "" - } elseif {![info exists env(CONTENT_LENGTH)]} { - set _cgi(client_error) 1 - error "Your browser failed to generate the content-length during a POST method." - } else { - set length $env(CONTENT_LENGTH) - if {0!=[string compare $length "-1"]} { - set input [read stdin $env(CONTENT_LENGTH)] - } else { - set _cgi(client_error) 1 - error "Your browser generated a content-length of -1 during a POST method." - } - } - # save input for possible diagnostics later - set _cgi(input) $input - - set pairs [split $input &] - foreach pair $pairs { - if {0 == [regexp "^(\[^=]*)=(.*)$" $pair dummy varname val]} { - # if no match, unquote and leave it at that - # this is typical of -style queries - set varname anonymous - set val $pair - } - - set varname [cgi_unquote_input $varname] - set val [cgi_unquote_input $val] - _cgi_set_uservar $varname $val - } + if {![info exists env(REQUEST_METHOD)]} { + set input $fakeinput + set env(HTTP_COOKIE) $fakecookie + } elseif { $env(REQUEST_METHOD) == "GET" } { + set input "" + catch {set input $env(QUERY_STRING)} ;# doesn't have to be set + } elseif { $env(REQUEST_METHOD) == "HEAD" } { + set input "" + } elseif {![info exists env(CONTENT_LENGTH)]} { + set _cgi(client_error) 1 + error "Your browser failed to generate the content-length during a POST method." + } else { + set length $env(CONTENT_LENGTH) + if {0!=[string compare $length "-1"]} { + set input [read stdin $env(CONTENT_LENGTH)] + } else { + set _cgi(client_error) 1 + error "Your browser generated a content-length of -1 during a POST method." + } + } + # save input for possible diagnostics later + set _cgi(input) $input + + set pairs [split $input &] + foreach pair $pairs { + if {0 == [regexp "^(\[^=]*)=(.*)$" $pair dummy varname val]} { + # if no match, unquote and leave it at that + # this is typical of -style queries + set varname anonymous + set val $pair + } + + set varname [cgi_unquote_input $varname] + set val [cgi_unquote_input $val] + _cgi_set_uservar $varname $val + } } # O'Reilly's web server incorrectly uses COOKIE catch {set env(HTTP_COOKIE) $env(COOKIE)} if {![info exists env(HTTP_COOKIE)]} return foreach pair [split $env(HTTP_COOKIE) ";"] { - # pairs are actually split by "; ", sigh - set pair [string trimleft $pair " "] - # spec is not clear but seems to allow = unencoded - # only sensible interpretation is to assume no = in var names - # appears MS IE can omit "=val" - set val "" - regexp (\[^=]*)=?(.*) $pair dummy varname val + # pairs are actually split by "; ", sigh + set pair [string trimleft $pair " "] + # spec is not clear but seems to allow = unencoded + # only sensible interpretation is to assume no = in var names + # appears MS IE can omit "=val" + set val "" + regexp (\[^=]*)=?(.*) $pair dummy varname val - set varname [cgi_unquote_input $varname] - set val [cgi_unquote_input $val] + set varname [cgi_unquote_input $varname] + set val [cgi_unquote_input $val] - if {[info exists _cgi_cookie($varname)]} { - lappend _cgi_cookie_shadowed($varname) $val - } else { - set _cgi_cookie($varname) $val - } + if {[info exists _cgi_cookie($varname)]} { + lappend _cgi_cookie_shadowed($varname) $val + } else { + set _cgi_cookie($varname) $val + } } } @@ -1347,7 +1347,7 @@ proc _cgi_read_line {fin bufvar crlfvar} { append _cgi_read_line_buffer [read $fin 65536] set line_end [string first "\r\n" $_cgi_read_line_buffer] } - if {$line_end >= 0} { + if {$line_end >= 0} { incr line_end -1 set buffer [string range $_cgi_read_line_buffer 0 $line_end] set crlf "\r\n" @@ -1365,24 +1365,24 @@ proc _cgi_input_multipart {fin} { global env _cgi _cgi_uservar _cgi_userfile cgi_debug -noprint { - # save file for debugging purposes - set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] - # explicitly flush all writes to fout, because sometimes the writer - # can hang and we won't get to the termination code - set dbg_fout [open $dbg_filename w] - set _cgi(input) $dbg_filename - catch {fconfigure $dbg_fout -translation binary -encoding binary} + # save file for debugging purposes + set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] + # explicitly flush all writes to fout, because sometimes the writer + # can hang and we won't get to the termination code + set dbg_fout [open $dbg_filename w] + set _cgi(input) $dbg_filename + catch {fconfigure $dbg_fout -translation binary -encoding binary} } # figure out boundary if {0==[regexp boundary=(.*) $env(CONTENT_TYPE) dummy boundary]} { - set _cgi(client_error) 1 - error "Your browser failed to generate a \"boundary=\" line in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." + set _cgi(client_error) 1 + error "Your browser failed to generate a \"boundary=\" line in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." } set boundary "--$boundary" set boundary_length [string length $boundary] - + # don't corrupt or modify uploads yet allow Tcl 7.4 to work catch {fconfigure $fin -translation binary -encoding binary} @@ -1393,51 +1393,51 @@ proc _cgi_input_multipart {fin} { set filecount 0 set crlf "" while {1} { - # process Content-Disposition: - if { ! [_cgi_read_line $fin buf crlf] } break - if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} - catch {unset filename} - catch {unset varname} - foreach b $buf { - regexp {^name="(.*)"} $b dummy varname - } - if {0==[info exists varname]} { - set _cgi(client_error) 1 - error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." - } - # Lame-o encoding (on Netscape at least) doesn't escape field - # delimiters (like quotes)!! Since all we've ever seen is filename= - # at end of line, assuming nothing follows. Sigh. - regexp {filename="(.*)"} $buf dummy filename - - # Skip remaining headers until blank line. - # Content-Type: can appear here. - set conttype "" - while {1} { + # process Content-Disposition: + if { ! [_cgi_read_line $fin buf crlf] } break + if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} + catch {unset filename} + catch {unset varname} + foreach b $buf { + regexp {^name="(.*)"} $b dummy varname + } + if {0==[info exists varname]} { + set _cgi(client_error) 1 + error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." + } + # Lame-o encoding (on Netscape at least) doesn't escape field + # delimiters (like quotes)!! Since all we've ever seen is filename= + # at end of line, assuming nothing follows. Sigh. + regexp {filename="(.*)"} $buf dummy filename + + # Skip remaining headers until blank line. + # Content-Type: can appear here. + set conttype "" + while {1} { if { ! [_cgi_read_line $fin buf crlf] } break - if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} - if {0==[string compare $buf ""]} break - regexp -nocase "^Content-Type:\[ \t]+(.*)\r\n" $buf$crlf x conttype - } + if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} + if {0==[string compare $buf ""]} break + regexp -nocase "^Content-Type:\[ \t]+(.*)\r\n" $buf$crlf x conttype + } - if {[info exists filename]} { + if {[info exists filename]} { if {[info exists dbg_fout]} {puts $dbg_fout ">>>>>Reading file $filename"; flush $dbg_fout} - # read the part into a file - set foutname /tmp/CGI[pid].[incr filecount] - set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] - set fout [open $foutname w] - # "catch" permits this to work with Tcl 7.4 - catch {fconfigure $fout -translation binary -encoding binary} - _cgi_set_uservar $varname [list $foutname $filename $conttype] - set _cgi_userfile($varname) [list $foutname $filename $conttype] - + # read the part into a file + set foutname /tmp/CGI[pid].[incr filecount] + set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] + set fout [open $foutname w] + # "catch" permits this to work with Tcl 7.4 + catch {fconfigure $fout -translation binary -encoding binary} + _cgi_set_uservar $varname [list $foutname $filename $conttype] + set _cgi_userfile($varname) [list $foutname $filename $conttype] + set leftover "" while { 1 } { if { ! [_cgi_read_line $fin buf crlf] } { set _cgi(client_error) 1 error "Unexpected end of input data." } - if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} + if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} if {[string compare -length $boundary_length $buf $boundary] == 0} { if {[string first "--" $buf $boundary_length]>=0} {set eof 1} break; @@ -1446,30 +1446,30 @@ proc _cgi_input_multipart {fin} { set leftover $crlf } if {[info exists dbg_fout]} {puts $dbg_fout ">>>>>Read file $filename"; flush $dbg_fout} - close $fout - unset fout - - } else { - # read the part into a variable + close $fout + unset fout + + } else { + # read the part into a variable if {[info exists dbg_fout]} {puts $dbg_fout ">>>>>Reading variable $varname"; flush $dbg_fout} - set val "" + set val "" set leftover "" while { 1 } { if { ! [_cgi_read_line $fin buf crlf] } { set _cgi(client_error) 1 error "Unexpected end of input data." } - if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} + if {[info exists dbg_fout]} {puts -nonewline $dbg_fout $buf$crlf; flush $dbg_fout} if {[string compare -length $boundary_length $buf $boundary] == 0} { if {[string first "--" $buf $boundary_length]>=0} {set eof 1} break; } append val $leftover$buf set leftover $crlf - } - _cgi_set_uservar $varname $val + } + _cgi_set_uservar $varname $val if {[info exists dbg_fout]} {puts $dbg_fout ">>>>>$varname=$val"; flush $dbg_fout} - } + } if {[info exists eof]} break } if {[info exists dbg_fout]} {close $dbg_fout} @@ -1479,19 +1479,19 @@ proc _cgi_input_multipart_buggy {fin} { global env _cgi _cgi_uservar _cgi_userfile cgi_debug -noprint { - # save file for debugging purposes - set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] - # explicitly flush all writes to fout, because sometimes the writer - # can hang and we won't get to the termination code - set dbg_fout [open $dbg_filename w] - set _cgi(input) $dbg_filename - catch {fconfigure $dbg_fout -translation binary -encoding binary} + # save file for debugging purposes + set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] + # explicitly flush all writes to fout, because sometimes the writer + # can hang and we won't get to the termination code + set dbg_fout [open $dbg_filename w] + set _cgi(input) $dbg_filename + catch {fconfigure $dbg_fout -translation binary -encoding binary} } # figure out boundary if {0==[regexp boundary=(.*) $env(CONTENT_TYPE) dummy boundary]} { - set _cgi(client_error) 1 - error "Your browser failed to generate a \"boundary=\" line in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." + set _cgi(client_error) 1 + error "Your browser failed to generate a \"boundary=\" line in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." } # make boundary into a legal regsub pattern by protecting # @@ -1513,90 +1513,90 @@ proc _cgi_input_multipart_buggy {fin} { set filecount 0 while {1} { - # process Content-Disposition: - if {-1 == [gets $fin buf]} break - if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} - catch {unset filename} - foreach b $buf { - regexp {^name="(.*)"} $b dummy varname - } - if {0==[info exists varname]} { - set _cgi(client_error) 1 - error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." - } - # Lame-o encoding (on Netscape at least) doesn't escape field - # delimiters (like quotes)!! Since all we've ever seen is filename= - # at end of line, assuming nothing follows. Sigh. - regexp {filename="(.*)"} $buf dummy filename - - # Skip remaining headers until blank line. - # Content-Type: can appear here. - set conttype "" - while {1} { - if {-1 == [gets $fin buf]} break - if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} - if {0==[string compare $buf "\r"]} break - regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype - } - - if {[info exists filename]} { - # read the part into a file - set foutname /tmp/CGI[pid].[incr filecount] - set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] - set fout [open $foutname w] - # "catch" permits this to work with Tcl 7.4 - catch {fconfigure $fout -translation binary -encoding binary} - _cgi_set_uservar $varname [list $foutname $filename $conttype] - set _cgi_userfile($varname) [list $foutname $filename $conttype] - # - # Look for a boundary line preceded by \r\n. - # - # To do this, we buffer line terminators that might - # be the start of the special \r\n$boundary sequence. - # The buffer is called "leftover" and is just inserted - # into the front of the next output (assuming it's - # not a boundary line). - - set leftover "" - while {1} { - if {-1 == [gets $fin buf]} break - if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} - - if {0 == [string compare "\r\n" $leftover]} { - if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { - if {$dashdash == "--"} {set eof 1} - break - } - } - if {[regexp (.*)\r$ $buf x data]} { - puts -nonewline $fout $leftover$data - set leftover "\r\n" - } else { - puts -nonewline $fout $leftover$buf - set leftover "\n" - } - } - close $fout - unset fout - - } else { - # read the part into a variable - set val "" - while {1} { - if {-1 == [gets $fin buf]} break - if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} - if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { - if {$dashdash == "--"} {set eof 1} - break - } - if {0!=[string compare $val ""]} { - append val \n - } - regexp (.*)\r$ $buf dummy buf - append val $buf - } - _cgi_set_uservar $varname $val - } + # process Content-Disposition: + if {-1 == [gets $fin buf]} break + if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} + catch {unset filename} + foreach b $buf { + regexp {^name="(.*)"} $b dummy varname + } + if {0==[info exists varname]} { + set _cgi(client_error) 1 + error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." + } + # Lame-o encoding (on Netscape at least) doesn't escape field + # delimiters (like quotes)!! Since all we've ever seen is filename= + # at end of line, assuming nothing follows. Sigh. + regexp {filename="(.*)"} $buf dummy filename + + # Skip remaining headers until blank line. + # Content-Type: can appear here. + set conttype "" + while {1} { + if {-1 == [gets $fin buf]} break + if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} + if {0==[string compare $buf "\r"]} break + regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype + } + + if {[info exists filename]} { + # read the part into a file + set foutname /tmp/CGI[pid].[incr filecount] + set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] + set fout [open $foutname w] + # "catch" permits this to work with Tcl 7.4 + catch {fconfigure $fout -translation binary -encoding binary} + _cgi_set_uservar $varname [list $foutname $filename $conttype] + set _cgi_userfile($varname) [list $foutname $filename $conttype] + # + # Look for a boundary line preceded by \r\n. + # + # To do this, we buffer line terminators that might + # be the start of the special \r\n$boundary sequence. + # The buffer is called "leftover" and is just inserted + # into the front of the next output (assuming it's + # not a boundary line). + + set leftover "" + while {1} { + if {-1 == [gets $fin buf]} break + if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} + + if {0 == [string compare "\r\n" $leftover]} { + if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { + if {$dashdash == "--"} {set eof 1} + break + } + } + if {[regexp (.*)\r$ $buf x data]} { + puts -nonewline $fout $leftover$data + set leftover "\r\n" + } else { + puts -nonewline $fout $leftover$buf + set leftover "\n" + } + } + close $fout + unset fout + + } else { + # read the part into a variable + set val "" + while {1} { + if {-1 == [gets $fin buf]} break + if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} + if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { + if {$dashdash == "--"} {set eof 1} + break + } + if {0!=[string compare $val ""]} { + append val \n + } + regexp (.*)\r$ $buf dummy buf + append val $buf + } + _cgi_set_uservar $varname $val + } if {[info exists eof]} break } if {[info exists dbg_fout]} {close $dbg_fout} @@ -1609,35 +1609,35 @@ proc _cgi_input_multipart_binary {fin} { set timeout -1 cgi_debug -noprint { - # save file for debugging purposes - set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] - set _cgi(input) $dbg_filename - spawn -open [open $dbg_filename w] - set dbg_sid $spawn_id + # save file for debugging purposes + set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] + set _cgi(input) $dbg_filename + spawn -open [open $dbg_filename w] + set dbg_sid $spawn_id } spawn -open $fin set fin_sid $spawn_id remove_nulls 0 if {0} { - # dump input to screen - cgi_debug { - puts "" - expect { - -i $fin_sid - -re ^\r {puts -nonewline "CR"; exp_continue} - -re ^\n {puts "NL"; exp_continue} - -re . {puts -nonewline $expect_out(buffer); exp_continue} - } - puts "" - exit - } + # dump input to screen + cgi_debug { + puts "" + expect { + -i $fin_sid + -re ^\r {puts -nonewline "CR"; exp_continue} + -re ^\n {puts "NL"; exp_continue} + -re . {puts -nonewline $expect_out(buffer); exp_continue} + } + puts "" + exit + } } # figure out boundary if {0==[regexp boundary=(.*) $env(CONTENT_TYPE) dummy boundary]} { - set _cgi(client_error) 1 - error "Your browser failed to generate a \"boundary=\" definition in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." + set _cgi(client_error) 1 + error "Your browser failed to generate a \"boundary=\" definition in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." } # make boundary into a legal regsub pattern by protecting # @@ -1653,189 +1653,189 @@ proc _cgi_input_multipart_binary {fin} { # get first boundary line expect { - -i $fin_sid - -re $linepat { - set buf $expect_out(1,string) - if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} - } - eof { - set _cgi(client_error) 1 - error "Your browser failed to provide an initial boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." - } + -i $fin_sid + -re $linepat { + set buf $expect_out(1,string) + if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} + } + eof { + set _cgi(client_error) 1 + error "Your browser failed to provide an initial boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." + } } set filecount 0 while {1} { - # process Content-Disposition: - expect { - -i $fin_sid - -re $linepat { - set buf $expect_out(1,string) - if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} - } - eof break - } - catch {unset filename} - foreach b $buf { - regexp {^name="(.*)"} $b dummy varname - } - if {0==[info exists varname]} { - set _cgi(client_error) 1 - error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." - } - - # Lame-o encoding (on Netscape at least) doesn't escape field - # delimiters (like quotes)!! Since all we've ever seen is filename= - # at end of line, assuming nothing follows. Sigh. - regexp {filename="(.*)"} $buf dummy filename - - # Skip remaining headers until blank line. - # Content-Type: can appear here. - set conttype "" - expect { - -i $fin_sid - -re $linepat { - set buf $expect_out(1,string) - if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} - if {0!=[string compare $buf ""]} exp_continue - regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype - } - eof break - } - - if {[info exists filename]} { - # read the part into a file - set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] - spawn -open [open $foutname w] - set fout_sid $spawn_id - - _cgi_set_uservar $varname [list $foutname $filename $conttype] - set _cgi_userfile($varname) [list $foutname $filename $conttype] - - # This is tricky stuff - be very careful changing anything here! - # In theory, all we have to is record everything up to - # \r\n$boundary\r\n. Unfortunately, we can't simply wait on - # such a pattern because the input can overflow any possible - # buffer we might choose. We can't simply catch buffer_full - # because the boundary might straddle a buffer. I doubt that - # doing my own buffering would be any faster than taking the - # approach I've done here. - # - # The code below basically implements a simple scanner that - # keeps track of whether it's seen crlfs or pieces of them. - # The idea is that we look for crlf pairs, separated by - # things that aren't crlfs (or pieces of them). As we encounter - # things that aren't crlfs (or pieces of them), or when we decide - # they can't be, we mark them for output and resume scanning for - # new pairs. - # - # The scanner runs tolerably fast because the [...]+ pattern picks - # up most things. The \r and \n are ^-anchored so the pattern - # match is pretty fast and these don't happen that often so the - # huge \n action is executed rarely (once per line on text files). - # The null pattern is, of course, only used when everything - # else fails. - - # crlf == "\r\n" if we've seen one, else == "" - # cr == "\r" if we JUST saw one, else == "" - # Yes, strange, but so much more efficient - # that I'm willing to sacrifice readability, sigh. - # buf accumulated data between crlf pairs - - set buf "" - set cr "" - set crlf "" - - expect { - -i $fin_sid - -re "^\r" { - if {$cr == "\r"} { - append buf "\r" - } - set cr \r - exp_continue - } -re "^\n" { - if {$cr == "\r"} { - if {$crlf == "\r\n"} { - # do boundary test - if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { - if {$dashdash == "--"} { - set eof 1 - } - } else { - # boundary test failed - if {[info exists dbg_sid]} {send -i $dbg_sid -- \r\n$buf} - send -i $fout_sid \r\n$buf ; set buf "" - set cr "" - exp_continue - } - } else { - set crlf "\r\n" - set cr "" - if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf} - send -i $fout_sid -- $buf ; set buf "" - exp_continue - } - } else { - if {[info exists dbg_sid]} {send -i $dbg_sid -- $crlf$buf\n} - send -i $fout_sid -- $crlf$buf\n ; set buf "" - set crlf "" - exp_continue - } - } -re "\[^\r\n]+" { - if {$cr == "\r"} { - set buf $crlf$buf\r$expect_out(buffer) - set crlf "" - set cr "" - } else { - append buf $expect_out(buffer) - } - exp_continue - } null { - if {[info exists dbg_sid]} { - send -i $dbg_sid -- $crlf$buf$cr - send -i $dbg_sid -null - } - send -i $fout_sid -- $crlf$buf$cr ; set buf "" - send -i $fout_sid -null - set cr "" - set crlf "" - exp_continue - } eof { - set _cgi(client_error) 1 - error "Your browser failed to provide an ending boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." - } - } - exp_close -i $fout_sid ;# implicitly closes fout - exp_wait -i $fout_sid - unset fout_sid - } else { - # read the part into a variable - set val "" - expect { - -i $fin_sid - -re $linepat { - set buf $expect_out(1,string) - if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} - if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { - if {$dashdash == "--"} {set eof 1} - } else { - regexp (.*)\r$ $buf dummy buf - if {0!=[string compare $val ""]} { - append val \n - } - append val $buf - exp_continue - } - } - } - _cgi_set_uservar $varname $val - } + # process Content-Disposition: + expect { + -i $fin_sid + -re $linepat { + set buf $expect_out(1,string) + if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} + } + eof break + } + catch {unset filename} + foreach b $buf { + regexp {^name="(.*)"} $b dummy varname + } + if {0==[info exists varname]} { + set _cgi(client_error) 1 + error "In response to a request for a multipart form, your browser generated a part header without a name field. Please upgrade (or fix) your browser." + } + + # Lame-o encoding (on Netscape at least) doesn't escape field + # delimiters (like quotes)!! Since all we've ever seen is filename= + # at end of line, assuming nothing follows. Sigh. + regexp {filename="(.*)"} $buf dummy filename + + # Skip remaining headers until blank line. + # Content-Type: can appear here. + set conttype "" + expect { + -i $fin_sid + -re $linepat { + set buf $expect_out(1,string) + if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} + if {0!=[string compare $buf ""]} exp_continue + regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype + } + eof break + } + + if {[info exists filename]} { + # read the part into a file + set foutname [file join $_cgi(tmpdir) CGI[pid].[incr filecount]] + spawn -open [open $foutname w] + set fout_sid $spawn_id + + _cgi_set_uservar $varname [list $foutname $filename $conttype] + set _cgi_userfile($varname) [list $foutname $filename $conttype] + + # This is tricky stuff - be very careful changing anything here! + # In theory, all we have to is record everything up to + # \r\n$boundary\r\n. Unfortunately, we can't simply wait on + # such a pattern because the input can overflow any possible + # buffer we might choose. We can't simply catch buffer_full + # because the boundary might straddle a buffer. I doubt that + # doing my own buffering would be any faster than taking the + # approach I've done here. + # + # The code below basically implements a simple scanner that + # keeps track of whether it's seen crlfs or pieces of them. + # The idea is that we look for crlf pairs, separated by + # things that aren't crlfs (or pieces of them). As we encounter + # things that aren't crlfs (or pieces of them), or when we decide + # they can't be, we mark them for output and resume scanning for + # new pairs. + # + # The scanner runs tolerably fast because the [...]+ pattern picks + # up most things. The \r and \n are ^-anchored so the pattern + # match is pretty fast and these don't happen that often so the + # huge \n action is executed rarely (once per line on text files). + # The null pattern is, of course, only used when everything + # else fails. + + # crlf == "\r\n" if we've seen one, else == "" + # cr == "\r" if we JUST saw one, else == "" + # Yes, strange, but so much more efficient + # that I'm willing to sacrifice readability, sigh. + # buf accumulated data between crlf pairs + + set buf "" + set cr "" + set crlf "" + + expect { + -i $fin_sid + -re "^\r" { + if {$cr == "\r"} { + append buf "\r" + } + set cr \r + exp_continue + } -re "^\n" { + if {$cr == "\r"} { + if {$crlf == "\r\n"} { + # do boundary test + if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { + if {$dashdash == "--"} { + set eof 1 + } + } else { + # boundary test failed + if {[info exists dbg_sid]} {send -i $dbg_sid -- \r\n$buf} + send -i $fout_sid \r\n$buf ; set buf "" + set cr "" + exp_continue + } + } else { + set crlf "\r\n" + set cr "" + if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf} + send -i $fout_sid -- $buf ; set buf "" + exp_continue + } + } else { + if {[info exists dbg_sid]} {send -i $dbg_sid -- $crlf$buf\n} + send -i $fout_sid -- $crlf$buf\n ; set buf "" + set crlf "" + exp_continue + } + } -re "\[^\r\n]+" { + if {$cr == "\r"} { + set buf $crlf$buf\r$expect_out(buffer) + set crlf "" + set cr "" + } else { + append buf $expect_out(buffer) + } + exp_continue + } null { + if {[info exists dbg_sid]} { + send -i $dbg_sid -- $crlf$buf$cr + send -i $dbg_sid -null + } + send -i $fout_sid -- $crlf$buf$cr ; set buf "" + send -i $fout_sid -null + set cr "" + set crlf "" + exp_continue + } eof { + set _cgi(client_error) 1 + error "Your browser failed to provide an ending boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." + } + } + exp_close -i $fout_sid ;# implicitly closes fout + exp_wait -i $fout_sid + unset fout_sid + } else { + # read the part into a variable + set val "" + expect { + -i $fin_sid + -re $linepat { + set buf $expect_out(1,string) + if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} + if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { + if {$dashdash == "--"} {set eof 1} + } else { + regexp (.*)\r$ $buf dummy buf + if {0!=[string compare $val ""]} { + append val \n + } + append val $buf + exp_continue + } + } + } + _cgi_set_uservar $varname $val + } if {[info exists eof]} break } if {[info exists fout]} { - exp_close -i $dbg_sid - exp_wait -i $dbg_sid + exp_close -i $dbg_sid + exp_wait -i $dbg_sid } # no need to close fin, fin_sid, or dbg_sid @@ -1851,24 +1851,24 @@ proc _cgi_set_uservar {varname val} { # has to be (or become a list) if {!$exists} { - lappend _cgi(uservars) $varname + lappend _cgi(uservars) $varname } if {[regexp List$ $varname]} { - set isList 1 + set isList 1 } elseif {$exists} { - # vars that we've seen before but aren't marked as lists - # need to be "listified" so we can do appends later - if {-1 == [lsearch $_cgi(uservars,autolist) $varname]} { - # remember that we've listified it - lappend _cgi(uservars,autolist) $varname - set _cgi_uservar($varname) [list $_cgi_uservar($varname)] - } + # vars that we've seen before but aren't marked as lists + # need to be "listified" so we can do appends later + if {-1 == [lsearch $_cgi(uservars,autolist) $varname]} { + # remember that we've listified it + lappend _cgi(uservars,autolist) $varname + set _cgi_uservar($varname) [list $_cgi_uservar($varname)] + } } if {$isList} { - lappend _cgi_uservar($varname) $val + lappend _cgi_uservar($varname) $val } else { - set _cgi_uservar($varname) $val + set _cgi_uservar($varname) $val } } @@ -1877,7 +1877,7 @@ proc cgi_export {nameval} { regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value if {$q != "="} { - set value [uplevel 1 set [list $name]] + set value [uplevel 1 set [list $name]] } cgi_put "" @@ -1935,13 +1935,13 @@ proc cgi_import_file {type name} { set var $_cgi_userfile($name) switch -- $type { - "-server" { - lindex $var 0 - } "-client" { - lindex $var 1 - } "-type" { - lindex $var 2 - } + "-server" { + lindex $var 0 + } "-client" { + lindex $var 1 + } "-type" { + lindex $var 2 + } } } @@ -1952,10 +1952,10 @@ proc cgi_import_filename {type name} { set var $_cgi_userfile($name) if {$type == "-server" || $type == "-local"} { - # -local is deprecated - lindex $var 0 + # -local is deprecated + lindex $var 0 } else { - lindex $var 1 + lindex $var 1 } } @@ -1967,11 +1967,11 @@ proc cgi_import_filename {type name} { proc cgi_button {value args} { cgi_put "" } @@ -1982,11 +1982,11 @@ proc cgi_button_link {args} { set tag [lindex $args 0] if {[llength $args] == 2} { - set label [lindex $args end] + set label [lindex $args end] } else { - set label $_cgi_link($tag,label) + set label $_cgi_link($tag,label) } - + cgi_button $label onClick=$_cgi_link($tag,url) } @@ -1994,15 +1994,15 @@ proc cgi_submit_button {{nameval {=Submit Query}} args} { regexp "(\[^=]*)=(.*)" $nameval dummy name value cgi_put "" } @@ -2012,11 +2012,11 @@ proc cgi_reset_button {{value Reset} args} { cgi_put "" } @@ -2027,20 +2027,20 @@ proc cgi_radio_button {nameval args} { cgi_put "" } @@ -2049,15 +2049,15 @@ proc cgi_image_button {nameval args} { regexp "(\[^=]*)=(.*)" $nameval dummy name value cgi_put "" } @@ -2074,11 +2074,11 @@ proc cgi_map {name cmd} { proc cgi_area {args} { cgi_put "" } @@ -2092,24 +2092,24 @@ proc cgi_checkbox {nameval args} { cgi_put "" } @@ -2124,16 +2124,16 @@ proc cgi_text {nameval args} { cgi_put "" } @@ -2147,16 +2147,16 @@ proc cgi_textarea {nameval args} { cgi_put "" } @@ -2169,7 +2169,7 @@ proc cgi_textarea {nameval args} { proc cgi_file_button {name args} { global _cgi if {[info exists _cgi(formtype)] && ("multipart/form-data" != $_cgi(form,enctype))} { - error "cgi_file_button requires that cgi_form have the argument enctype=multipart/form-data" + error "cgi_file_button requires that cgi_form have the argument enctype=multipart/form-data" } cgi_put "" } @@ -2182,20 +2182,20 @@ proc cgi_select {name args} { cgi_put "