1################################################## 2# 3# cgi.tcl - routines for writing CGI scripts in Tcl 4# Author: Don Libes <libes@nist.gov>, January '95 5# 6# These routines implement the code described in the paper 7# "Writing CGI scripts in Tcl" which appeared in the Tcl '96 conference. 8# Please read the paper before using this code. The paper is: 9# http://expect.nist.gov/doc/cgi.pdf 10# 11################################################## 12 13################################################## 14# http header support 15################################################## 16 17proc cgi_http_head {args} { 18 global _cgi env errorInfo 19 20 if {[info exists _cgi(http_head_done)]} return 21 22 set _cgi(http_head_in_progress) 1 23 24 if {0 == [llength $args]} { 25 cgi_content_type 26 } else { 27 if {[catch {uplevel 1 [lindex $args 0]} errMsg]} { 28 set savedInfo $errorInfo 29 cgi_content_type 30 } 31 } 32 cgi_puts "" 33 34 unset _cgi(http_head_in_progress) 35 set _cgi(http_head_done) 1 36 37 if {[info exists savedInfo]} { 38 error $errMsg $savedInfo 39 } 40} 41 42# avoid generating http head if not in CGI environment 43# to allow generation of pure HTML files 44proc _cgi_http_head_implicit {} { 45 global env 46 47 if {[info exists env(REQUEST_METHOD)]} cgi_http_head 48} 49 50proc cgi_status {num str} { 51 global _cgi 52 53 if {[info exists _cgi(http_status_done)]} return 54 set _cgi(http_status_done) 1 55 cgi_puts "Status: $num $str" 56} 57 58# If these are called manually, they automatically generate the extra newline 59 60proc cgi_content_type {args} { 61 global _cgi 62 63 if {0==[llength $args]} { 64 set t text/html 65 } else { 66 set t [lindex $args 0] 67 if {[regexp ^multipart/ $t]} { 68 set _cgi(multipart) 1 69 } 70 } 71 72 if {[info exists _cgi(http_head_in_progress)]} { 73 cgi_puts "Content-type: $t" 74 } else { 75 cgi_http_head [list cgi_content_type $t] 76 } 77} 78 79proc cgi_redirect {t} { 80 global _cgi 81 82 if {[info exists _cgi(http_head_in_progress)]} { 83 cgi_status 302 Redirected 84 cgi_puts "Uri: $t" 85 cgi_puts "Location: $t" 86 } else { 87 cgi_http_head { 88 cgi_redirect $t 89 } 90 } 91} 92 93# deprecated, use cgi_redirect 94proc cgi_location {t} { 95 global _cgi 96 97 if {[info exists _cgi(http_head_in_progress)]} { 98 cgi_puts "Location: $t" 99 } else { 100 cgi_http_head "cgi_location $t" 101 } 102} 103 104proc cgi_target {t} { 105 global _cgi 106 107 if {![info exists _cgi(http_head_in_progress)]} { 108 error "cgi_target must be set from within cgi_http_head." 109 } 110 cgi_puts "Window-target: $t" 111} 112 113# Make client retrieve url in this many seconds ("client pull"). 114# With no 2nd arg, current url is retrieved. 115proc cgi_refresh {seconds {url ""}} { 116 global _cgi 117 118 if {![info exists _cgi(http_head_in_progress)]} { 119 error "cgi_refresh must be set from within cgi_http_head. Try using cgi_http_equiv instead." 120 } 121 cgi_put "Refresh: $seconds" 122 123 if {0!=[string compare $url ""]} { 124 cgi_put "; $url" 125 } 126 cgi_puts "" 127} 128 129# Example: cgi_pragma no-cache 130proc cgi_pragma {arg} { 131 global _cgi 132 133 if {![info exists _cgi(http_head_in_progress)]} { 134 error "cgi_pragma must be set from within cgi_http_head." 135 } 136 cgi_puts "Pragma: $arg" 137} 138 139################################################## 140# support for debugging or other crucial things we need immediately 141################################################## 142 143proc cgi_comment {args} {} ;# need this asap 144 145proc cgi_html_comment {args} { 146 regsub -all {>} $args {\>} args 147 cgi_put "<!--[_cgi_list_to_string $args] -->" 148} 149 150set _cgi(debug) -off 151proc cgi_debug {args} { 152 global _cgi 153 154 set old $_cgi(debug) 155 set arg [lindex $args 0] 156 if {$arg == "-on"} { 157 set _cgi(debug) -on 158 set args [lrange $args 1 end] 159 } elseif {$arg == "-off"} { 160 set _cgi(debug) -off 161 set args [lrange $args 1 end] 162 } elseif {[regexp "^-t" $arg]} { 163 set temp 1 164 set _cgi(debug) -on 165 set args [lrange $args 1 end] 166 } elseif {[regexp "^-noprint$" $arg]} { 167 set noprint 1 168 set args [lrange $args 1 end] 169 } 170 171 set arg [lindex $args 0] 172 if {$arg == "--"} { 173 set args [lrange $args 1 end] 174 } 175 176 if {[llength $args]} { 177 if {$_cgi(debug) == "-on"} { 178 179 _cgi_close_tag 180 # force http head and open html, head, body 181 catch { 182 if {[info exists noprint]} { 183 uplevel 1 [lindex $args 0] 184 } else { 185 cgi_html { 186 cgi_head { 187 cgi_title "debugging before complete HTML head" 188 } 189 # force body open and leave open 190 _cgi_body_start 191 uplevel 1 [lindex $args 0] 192 # bop back out to catch, so we don't close body 193 error "ignore" 194 } 195 } 196 } 197 } 198 } 199 200 if {[info exists temp]} { 201 set _cgi(debug) $old 202 } 203 return $old 204} 205 206proc cgi_uid_check {user} { 207 global env 208 209 # leave in so old scripts don't blow up 210 if {[regexp "^-off$" $user]} return 211 212 if {[info exists env(USER)]} { 213 set whoami $env(USER) 214 } elseif {0==[catch {exec whoami} whoami]} { 215 # "who am i" on some Linux hosts returns "" so try whoami first 216 } elseif {0==[catch {exec who am i} whoami]} { 217 # skip over "host!" 218 regexp "(.*!)?(\[^ \t]*)" $whoami dummy dummy whoami 219 } elseif {0==[catch {package require registry}]} { 220 set whoami [registry get HKEY_LOCAL_MACHINE\\Network\\Logon username] 221 } else { 222 set whoami $user ;# give up and let go 223 } 224 if {$whoami != "$user"} { 225 error "Warning: This CGI script expects to run with uid \"$user\". However, this script is running as \"$whoami\"." 226 } 227} 228 229# print out elements of an array 230# like Tcl's parray, but formatted for browser 231proc cgi_parray {a {pattern *}} { 232 upvar 1 $a array 233 if {![array exists array]} { 234 error "\"$a\" isn't an array" 235 } 236 237 set maxl 0 238 foreach name [lsort [array names array $pattern]] { 239 if {[string length $name] > $maxl} { 240 set maxl [string length $name] 241 } 242 } 243 cgi_preformatted { 244 set maxl [expr {$maxl + [string length $a] + 2}] 245 foreach name [lsort [array names array $pattern]] { 246 set nameString [format %s(%s) $a $name] 247 cgi_puts [cgi_quote_html [format "%-*s = %s" $maxl $nameString $array($name)]] 248 } 249 } 250} 251 252proc cgi_eval {cmd} { 253 global env _cgi 254 255 # put cmd somewhere that uplevel can find it 256 set _cgi(body) $cmd 257 258 uplevel 1 { 259 global env _cgi errorInfo 260 261 if {1==[catch $_cgi(body) errMsg]} { 262 # error occurred, handle it 263 set _cgi(errorInfo) $errorInfo 264 265 if {![info exists env(REQUEST_METHOD)]} { 266 puts stderr $_cgi(errorInfo) 267 return 268 } 269 # the following code is all to force browsers into a state 270 # such that diagnostics can be reliably shown 271 272 # close irrelevant things 273 _cgi_close_procs 274 # force http head and open html, head, body 275 cgi_html { 276 cgi_body { 277 if {[info exists _cgi(client_error)]} { 278 cgi_h3 "Client Error" 279 cgi_p "$errMsg Report this to your system administrator or browser vendor." 280 } else { 281 cgi_put [cgi_anchor_name cgierror] 282 cgi_h3 "An internal error was detected in the service\ 283 software. The diagnostics are being emailed to\ 284 the service system administrator ($_cgi(admin_email))." 285 286 if {$_cgi(debug) == "-on"} { 287 cgi_puts "Heck, since you're debugging, I'll show you the\ 288 errors right here:" 289 # suppress formatting 290 cgi_preformatted { 291 cgi_puts [cgi_quote_html $_cgi(errorInfo)] 292 } 293 } else { 294 cgi_mail_start $_cgi(admin_email) 295 cgi_mail_add "Subject: [cgi_name] CGI problem" 296 cgi_mail_add 297 cgi_mail_add "CGI environment:" 298 cgi_mail_add "REQUEST_METHOD: $env(REQUEST_METHOD)" 299 cgi_mail_add "SCRIPT_NAME: $env(SCRIPT_NAME)" 300 # this next few things probably don't need 301 # a catch but I'm not positive 302 catch {cgi_mail_add "HTTP_USER_AGENT: $env(HTTP_USER_AGENT)"} 303 catch {cgi_mail_add "HTTP_REFERER: $env(HTTP_REFERER)"} 304 catch {cgi_mail_add "HTTP_HOST: $env(HTTP_HOST)"} 305 catch {cgi_mail_add "REMOTE_HOST: $env(REMOTE_HOST)"} 306 catch {cgi_mail_add "REMOTE_ADDR: $env(REMOTE_ADDR)"} 307 cgi_mail_add "cgi.tcl version: 1.10.0" 308 cgi_mail_add "input:" 309 catch {cgi_mail_add $_cgi(input)} 310 cgi_mail_add "cookie:" 311 catch {cgi_mail_add $env(HTTP_COOKIE)} 312 cgi_mail_add "errorInfo:" 313 cgi_mail_add "$_cgi(errorInfo)" 314 cgi_mail_end 315 } 316 } 317 } ;# end cgi_body 318 } ;# end cgi_html 319 } ;# end catch 320 } ;# end uplevel 321} 322 323# return true if cgi_eval caught an error 324proc cgi_error_occurred {} { 325 global _cgi 326 327 return [info exists _cgi(errorInfo)] 328} 329 330################################################## 331# CGI URL creation 332################################################## 333 334# declare location of root of CGI files 335# this allows all CGI references to be relative in the source 336# making it easy to move everything in the future 337# If you have multiple roots, just don't call this. 338proc cgi_root {args} { 339 global _cgi 340 341 if {[llength $args]} { 342 set _cgi(root) [lindex $args 0] 343 } else { 344 set _cgi(root) 345 } 346} 347 348# make a URL for a CGI script 349proc cgi_cgi {args} { 350 global _cgi 351 352 set root $_cgi(root) 353 if {0!=[string compare $root ""]} { 354 if {![regexp "/$" $root]} { 355 append root "/" 356 } 357 } 358 359 set suffix [cgi_suffix] 360 361 set arg [lindex $args 0] 362 if {0==[string compare $arg "-suffix"]} { 363 set suffix [lindex $args 1] 364 set args [lrange $args 2 end] 365 } 366 367 if {[llength $args]==1} { 368 return $root[lindex $args 0]$suffix 369 } else { 370 return $root[lindex $args 0]$suffix?[join [lrange $args 1 end] &] 371 } 372} 373 374proc cgi_suffix {args} { 375 global _cgi 376 if {[llength $args] > 0} { 377 set _cgi(suffix) [lindex $args 0] 378 } 379 if {![info exists _cgi(suffix)]} { 380 return .cgi 381 } else { 382 return $_cgi(suffix) 383 } 384} 385 386proc cgi_cgi_set {variable value} { 387 regsub -all {%} $value "%25" value 388 regsub -all {&} $value "%26" value 389 regsub -all {\+} $value "%2b" value 390 regsub -all { } $value "+" value 391 regsub -all {=} $value "%3d" value 392 regsub -all {#} $value "%23" value 393 regsub -all {/} $value "%2f" value ;# Added... 394 return $variable=$value 395} 396 397################################################## 398# URL dictionary support 399################################################## 400 401proc cgi_link {args} { 402 global _cgi_link 403 404 set tag [lindex $args 0] 405 switch -- [llength $args] { 406 1 { 407 set label $_cgi_link($tag,label) 408 } 2 { 409 set label [lindex $args end] 410 } default { 411 set _cgi_link($tag,label) [set label [lindex $args 1]] 412 set _cgi_link($tag,url) [lrange $args 2 end] 413 } 414 } 415 416 return [eval cgi_url [list $label] $_cgi_link($tag,url)] 417} 418 419# same as above but for images 420# note: uses different namespace 421proc cgi_imglink {args} { 422 global _cgi_imglink 423 424 set tag [lindex $args 0] 425 if {[llength $args] >= 2} { 426 set _cgi_imglink($tag) [eval cgi_img [lrange $args 1 end]] 427 } 428 return $_cgi_imglink($tag) 429} 430 431proc cgi_link_label {tag} { 432 global _cgi_link 433 return $_cgi_link($tag,label) 434} 435 436proc cgi_link_url {tag} { 437 global _cgi_link 438 return $_cgi_link($tag,url) 439} 440 441################################################## 442# hyperlink support 443################################################## 444 445# construct a hyperlink labeled "display" 446# last arg is the link destination 447# any other args are passed through into <a> display 448proc cgi_url {display args} { 449 global _cgi 450 451 set buf "<a href=\"[lindex $args 0]\"" 452 foreach a [lrange $args 1 end] { 453 if {[regexp $_cgi(attr,regexp) $a dummy attr str]} { 454 append buf " $attr=\"$str\"" 455 } else { 456 append buf " $a" 457 } 458 } 459 return "$buf>$display</a>" 460} 461 462# generate an image reference (<img ...>) 463# first arg is image url 464# other args are passed through into <img> tag 465proc cgi_img {args} { 466 global _cgi 467 468 set buf "<img src=\"[lindex $args 0]\"" 469 foreach a [lrange $args 1 end] { 470 if {[regexp "^(alt|lowsrc|usemap)=(.*)" $a dummy attr str]} { 471 append buf " $attr=[cgi_dquote_html $str]" 472 } elseif {[regexp $_cgi(attr,regexp) $a dummy attr str]} { 473 append buf " $attr=\"$str\"" 474 } else { 475 append buf " $a" 476 } 477 } 478 return "$buf />" 479} 480 481# names an anchor so that it can be linked to 482proc cgi_anchor_name {name} { 483 return "<a name=\"$name\"/>" 484} 485 486proc cgi_base {args} { 487 global _cgi 488 489 cgi_put "<base" 490 foreach a $args { 491 if {[regexp "^href=(.*)" $a dummy str]} { 492 cgi_put " href=[cgi_dquote_html $str]" 493 } elseif {[regexp $_cgi(attr,regexp) $a dummy attr str]} { 494 cgi_put " $attr=\"$str\"" 495 } else { 496 cgi_put " $a" 497 } 498 } 499 cgi_puts " />" 500} 501 502################################################## 503# quoting support 504################################################## 505 506if {[info tclversion] >= 8.2} { 507 proc cgi_unquote_input buf { 508 # rewrite "+" back to space 509 # protect \ from quoting another \ and throwing off other things 510 # replace line delimiters with newlines 511 set buf [string map -nocase [list + { } "\\" "\\\\" %0d%0a \n] $buf] 512 513 # prepare to process all %-escapes 514 regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf 515 516 # process \u unicode mapped chars 517 encoding convertfrom $::_cgi(queryencoding) \ 518 [subst -novar -nocommand $buf] 519 } 520} elseif {[info tclversion] >= 8.1} { 521 proc cgi_unquote_input buf { 522 # rewrite "+" back to space 523 regsub -all {\+} $buf { } buf 524 # protect \ from quoting another \ and throwing off other things 525 regsub -all {\\} $buf {\\\\} buf 526 527 # replace line delimiters with newlines 528 regsub -all -nocase "%0d%0a" $buf "\n" buf 529 530 # prepare to process all %-escapes 531 regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {\\u00\1} buf 532 # process \u unicode mapped chars 533 return [subst -novar -nocommand $buf] 534 } 535} else { 536 proc cgi_unquote_input {buf} { 537 # rewrite "+" back to space 538 regsub -all {\+} $buf { } buf 539 # protect \ from quoting another \ and throwing off other things first 540 # protect $ from doing variable expansion 541 # protect [ from doing evaluation 542 # protect " from terminating string 543 regsub -all {([\\["$])} $buf {\\\1} buf 544 545 # replace line delimiters with newlines 546 regsub -all -nocase "%0d%0a" $buf "\n" buf 547 # Mosaic sends just %0A. This is handled in the next command. 548 549 # prepare to process all %-escapes 550 regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {[format %c 0x\1]} buf 551 # process %-escapes and undo all protection 552 eval return \"$buf\" 553 } 554} 555 556# return string but with html-special characters escaped, 557# necessary if you want to send unknown text to an html-formatted page. 558proc cgi_quote_html {s} { 559 regsub -all {&} $s {\&} s ;# must be first! 560 regsub -all {"} $s {\"} s 561 regsub -all {<} $s {\<} s 562 regsub -all {>} $s {\>} s 563 return $s 564} 565 566proc cgi_dquote_html {s} { 567 return \"[cgi_quote_html $s]\" 568} 569 570# return string quoted appropriately to appear in a url 571proc cgi_quote_url {in} { 572 regsub -all {%} $in "%25" in 573 regsub -all {\+} $in "%2b" in 574 regsub -all { } $in "%20" in 575 regsub -all {"} $in "%22" in 576 regsub -all {\?} $in "%3f" in 577 return $in 578} 579 580################################################## 581# short or single paragraph support 582################################################## 583 584proc cgi_br {args} { 585 cgi_put "<br" 586 if {[llength $args]} { 587 cgi_put "[_cgi_list_to_string $args]" 588 } 589 cgi_put " />" 590} 591 592# generate cgi_h1 and others 593for {set _cgi(tmp) 1} {$_cgi(tmp)<8} {incr _cgi(tmp)} { 594 proc cgi_h$_cgi(tmp) {{args}} "eval cgi_h $_cgi(tmp) \$args" 595} 596proc cgi_h {num args} { 597 cgi_put "<h$num" 598 if {[llength $args] > 1} { 599 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 600 set args [lrange $args end end] 601 } 602 cgi_put ">[lindex $args 0]</h$num>" 603} 604 605proc cgi_p {args} { 606 cgi_put "<p" 607 if {[llength $args] > 1} { 608 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 609 set args [lrange $args end end] 610 } 611 cgi_put ">[lindex $args 0]</p>" 612} 613 614proc cgi_address {s} {cgi_put <address>$s</address>} 615proc cgi_blockquote {s} {cgi_puts <blockquote>$s</blockquote>} 616 617################################################## 618# long or multiple paragraph support 619################################################## 620 621# Shorthand for <div align=center>. We used to use <center> tags but that 622# is now officially unsupported. 623proc cgi_center {cmd} { 624 uplevel 1 "cgi_division align=center [list $cmd]" 625} 626 627proc cgi_division {args} { 628 cgi_put "<div" 629 _cgi_close_proc_push "cgi_put </div>" 630 631 if {[llength $args]} { 632 cgi_put "[_cgi_lrange $args 0 [expr {[llength $args]-2}]]" 633 } 634 cgi_put ">" 635 uplevel 1 [lindex $args end] 636 _cgi_close_proc 637} 638 639proc cgi_preformatted {args} { 640 cgi_put "<pre" 641 _cgi_close_proc_push "cgi_put </pre>" 642 643 if {[llength $args]} { 644 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 645 } 646 cgi_put ">" 647 uplevel 1 [lindex $args end] 648 _cgi_close_proc 649} 650 651################################################## 652# list support 653################################################## 654 655proc cgi_li {args} { 656 cgi_put <li 657 if {[llength $args] > 1} { 658 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 659 } 660 cgi_put ">[lindex $args end]</li>" 661} 662 663proc cgi_number_list {args} { 664 cgi_put "<ol" 665 _cgi_close_proc_push "cgi_put </ol>" 666 667 if {[llength $args] > 1} { 668 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 669 } 670 cgi_put ">" 671 uplevel 1 [lindex $args end] 672 673 _cgi_close_proc 674} 675 676proc cgi_bullet_list {args} { 677 cgi_put "<ul" 678 _cgi_close_proc_push "cgi_put </ul>" 679 680 if {[llength $args] > 1} { 681 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 682 } 683 cgi_put ">" 684 uplevel 1 [lindex $args end] 685 686 _cgi_close_proc 687} 688 689# Following two are normally used from within definition lists 690# but are actually paragraph types on their own. 691proc cgi_term {s} {cgi_put <dt>$s</dt>} 692proc cgi_term_definition {s} {cgi_put <dd>$s</dd>} 693 694proc cgi_definition_list {cmd} { 695 cgi_put "<dl>" 696 _cgi_close_proc_push "cgi_put </dl>" 697 698 uplevel 1 $cmd 699 _cgi_close_proc 700} 701 702proc cgi_menu_list {cmd} { 703 cgi_put "<menu>" 704 _cgi_close_proc_push "cgi_put </menu>" 705 706 uplevel 1 $cmd 707 _cgi_close_proc 708} 709proc cgi_directory_list {cmd} { 710 cgi_put "<dir>" 711 _cgi_close_proc_push "cgi_put </dir>" 712 713 uplevel 1 $cmd 714 _cgi_close_proc 715} 716 717################################################## 718# text support 719################################################## 720 721proc cgi_put {s} {cgi_puts -nonewline $s} 722 723# some common special characters 724proc cgi_lt {} {return "<"} 725proc cgi_gt {} {return ">"} 726proc cgi_amp {} {return "&"} 727proc cgi_quote {} {return """} 728proc cgi_enspace {} {return " "} 729proc cgi_emspace {} {return " "} 730proc cgi_nbspace {} {return " "} ;# nonbreaking space 731proc cgi_tm {} {return "®"} ;# registered trademark 732proc cgi_copyright {} {return "©"} 733proc cgi_isochar {n} {return "&#$n;"} 734proc cgi_breakable {} {return "<wbr />"} 735 736proc cgi_unbreakable_string {s} {return "<nobr>$s</nobr>"} 737proc cgi_unbreakable {cmd} { 738 cgi_put "<nobr>" 739 _cgi_close_proc_push "cgi_put </nobr>" 740 uplevel 1 $cmd 741 _cgi_close_proc 742} 743 744proc cgi_nl {args} { 745 set buf "<br" 746 if {[llength $args]} { 747 append buf "[_cgi_list_to_string $args]" 748 } 749 return "$buf />" 750} 751 752proc cgi_bold {s} {return "<b>$s</b>"} 753proc cgi_italic {s} {return "<i>$s</i>"} 754proc cgi_underline {s} {return "<u>$s</u>"} 755proc cgi_strikeout {s} {return "<s>$s</s>"} 756proc cgi_subscript {s} {return "<sub>$s</sub>"} 757proc cgi_superscript {s} {return "<sup>$s</sup>"} 758proc cgi_typewriter {s} {return "<tt>$s</tt>"} 759proc cgi_blink {s} {return "<blink>$s</blink>"} 760proc cgi_emphasis {s} {return "<em>$s</em>"} 761proc cgi_strong {s} {return "<strong>$s</strong>"} 762proc cgi_cite {s} {return "<cite>$s</cite>"} 763proc cgi_sample {s} {return "<samp>$s</samp>"} 764proc cgi_keyboard {s} {return "<kbd>$s</kbd>"} 765proc cgi_variable {s} {return "<var>$s</var>"} 766proc cgi_definition {s} {return "<dfn>$s</dfn>"} 767proc cgi_big {s} {return "<big>$s</big>"} 768proc cgi_small {s} {return "<small>$s</small>"} 769 770proc cgi_basefont {size} {cgi_put "<basefont size=$size />"} 771 772proc cgi_font {args} { 773 global _cgi 774 775 set buf "<font" 776 foreach a [lrange $args 0 [expr [llength $args]-2]] { 777 if {[regexp $_cgi(attr,regexp) $a dummy attr str]} { 778 append buf " $attr=\"$str\"" 779 } else { 780 append buf " $a" 781 } 782 } 783 return "$buf>[lindex $args end]</font>" 784} 785 786# take a cgi func and have it return what would normally print 787# This command is reentrant (that's why it's so complex). 788proc cgi_buffer {cmd} { 789 global _cgi 790 791 if {0==[info exists _cgi(returnIndex)]} { 792 set _cgi(returnIndex) 0 793 } 794 795 rename cgi_puts cgi_puts$_cgi(returnIndex) 796 incr _cgi(returnIndex) 797 set _cgi(return[set _cgi(returnIndex)]) "" 798 799 proc cgi_puts args { 800 global _cgi 801 upvar #0 _cgi(return[set _cgi(returnIndex)]) buffer 802 803 append buffer [lindex $args end] 804 if {[llength $args] == 1} { 805 append buffer $_cgi(buffer_nl) 806 } 807 } 808 809 # must restore things before allowing the eval to fail 810 # so catch here and rethrow later 811 if {[catch {uplevel 1 $cmd} errMsg]} { 812 global errorInfo 813 set savedInfo $errorInfo 814 } 815 816 # not necessary to put remainder of code in close_proc_push since it's 817 # all buffered anyway and hasn't yet put browser into a funky state. 818 819 set buffer $_cgi(return[set _cgi(returnIndex)]) 820 821 incr _cgi(returnIndex) -1 822 rename cgi_puts "" 823 rename cgi_puts$_cgi(returnIndex) cgi_puts 824 825 if {[info exists savedInfo]} { 826 error $errMsg $savedInfo 827 } 828 return $buffer 829} 830 831set _cgi(buffer_nl) "\n" 832proc cgi_buffer_nl {nl} { 833 global _cgi 834 835 set old $_cgi(buffer_nl) 836 set _cgi(buffer_nl) $nl 837 return $old 838} 839 840################################################## 841# html and tags that can appear in html top-level 842################################################## 843 844proc cgi_html {args} { 845 set html [lindex $args end] 846 set argc [llength $args] 847 if {$argc > 1} { 848 eval _cgi_html_start [lrange $args 0 [expr {$argc-2}]] 849 } else { 850 _cgi_html_start 851 } 852 uplevel 1 $html 853 _cgi_html_end 854} 855 856proc _cgi_html_start {args} { 857 global _cgi 858 859 if {[info exists _cgi(html_in_progress)]} return 860 _cgi_http_head_implicit 861 862 set _cgi(html_in_progress) 1 863 cgi_doctype 864 865 append buf "<html" 866 foreach a $args { 867 if {[regexp $_cgi(attr,regexp) $a dummy attr str]} { 868 append buf " $attr=\"$str\"" 869 } else { 870 append buf " $a" 871 } 872 } 873 cgi_puts "$buf>" 874} 875 876proc _cgi_html_end {} { 877 global _cgi 878 unset _cgi(html_in_progress) 879 set _cgi(html_done) 1 880 cgi_puts "</html>" 881} 882 883# force closure of all tags and exit without going through normal returns. 884# Very useful if you want to call exit from a deeply stacked CGI script 885# and still have the HTML be correct. 886proc cgi_exit {} { 887 _cgi_close_procs 888 cgi_html {cgi_body {}} 889 exit 890} 891 892################################################## 893# head support 894################################################## 895 896proc cgi_head {{head {}}} { 897 global _cgi 898 899 if {[info exists _cgi(head_done)]} { 900 return 901 } 902 903 # allow us to be recalled so that we can display errors 904 if {0 == [info exists _cgi(head_in_progress)]} { 905 _cgi_http_head_implicit 906 set _cgi(head_in_progress) 1 907 cgi_puts "<head>" 908 } 909 910 # prevent cgi_html (during error handling) from generating html tags 911 set _cgi(html_in_progress) 1 912 # don't actually generate html tags since there's nothing to clean 913 # them up 914 915 if {0 == [string length $head]} { 916 if {[catch {cgi_title}]} { 917 set head "cgi_title untitled" 918 } 919 } 920 uplevel 1 $head 921 if {![info exists _cgi(head_suppress_tag)]} { 922 cgi_puts "</head>" 923 } else { 924 unset _cgi(head_suppress_tag) 925 } 926 927 set _cgi(head_done) 1 928 929 # debugging can unset this in the uplevel above 930 catch {unset _cgi(head_in_progress)} 931} 932 933# with one arg: set, print, and return title 934# with no args: return title 935proc cgi_title {args} { 936 global _cgi 937 938 set title [lindex $args 0] 939 940 if {[llength $args]} { 941 _cgi_http_head_implicit 942 943 # we could just generate <head></head> tags, but head-level commands 944 # might follow so just suppress the head tags entirely 945 if {![info exists _cgi(head_in_progress)]} { 946 set _cgi(head_in_progress) 1 947 set _cgi(head_suppress_tag) 1 948 } 949 950 set _cgi(title) $title 951 cgi_puts "<title>$title</title>" 952 } 953 return $_cgi(title) 954} 955 956# This tag can only be called from with cgi_head. 957# example: cgi_http_equiv Refresh 1 958# There's really no reason to call this since it can be done directly 959# from cgi_http_head. 960proc cgi_http_equiv {type contents} { 961 _cgi_http_head_implicit 962 cgi_puts "<meta http-equiv=\"$type\" content=[cgi_dquote_html $contents]/>" 963} 964 965# Do whatever you want with meta tags. 966# Example: <meta name="author" content="Don Libes"> 967proc cgi_meta {args} { 968 cgi_put "<meta" 969 foreach a $args { 970 if {[regexp "^(name|content|http-equiv)=(.*)" $a dummy attr str]} { 971 cgi_put " $attr=[cgi_dquote_html $str]" 972 } else { 973 cgi_put " $a" 974 } 975 } 976 cgi_puts " />" 977} 978 979proc cgi_relationship {rel href args} { 980 cgi_puts "<link rel=$rel href=\"$href\"" 981 foreach a $args { 982 if {[regexp "^title=(.*)" $a dummy str]} { 983 cgi_put " title=[cgi_dquote_html $str]" 984 } elseif {[regexp "^type=(.*)" $a dummy str]} { 985 cgi_put " type=[cgi_dquote_html $str]" 986 } else { 987 cgi_put " $a" 988 } 989 } 990 cgi_puts "/>" 991} 992 993proc cgi_name {args} { 994 global _cgi 995 996 if {[llength $args]} { 997 set _cgi(name) [lindex $args 0] 998 } 999 return $_cgi(name) 1000} 1001 1002################################################## 1003# body and other top-level support 1004################################################## 1005 1006proc cgi_body {args} { 1007 global errorInfo errorCode _cgi 1008 1009 # allow user to "return" from the body without missing _cgi_body_end 1010 if {1==[catch { 1011 eval _cgi_body_start [lrange $args 0 [expr [llength $args]-2]] 1012 uplevel 1 [lindex $args end] 1013 } errMsg]} { 1014 set savedInfo $errorInfo 1015 set savedCode $errorCode 1016 error $errMsg $savedInfo $savedCode 1017 } 1018 _cgi_body_end 1019} 1020 1021proc _cgi_body_start {args} { 1022 global _cgi 1023 if {[info exists _cgi(body_in_progress)]} return 1024 1025 cgi_head 1026 1027 set _cgi(body_in_progress) 1 1028 1029 cgi_put "<body" 1030 foreach a "$args $_cgi(body_args)" { 1031 if {[regexp "^(background|bgcolor|text|link|vlink|alink|onLoad|onUnload)=(.*)" $a dummy attr str]} { 1032 cgi_put " $attr=\"$str\"" 1033 } else { 1034 cgi_put " $a" 1035 } 1036 } 1037 cgi_puts ">" 1038 1039 cgi_debug { 1040 global env 1041 catch {cgi_puts "Input: <pre>$_cgi(input)</pre>"} 1042 catch {cgi_puts "Cookie: <pre>$env(HTTP_COOKIE)</pre>"} 1043 } 1044 1045 if {![info exists _cgi(errorInfo)]} { 1046 uplevel 2 app_body_start 1047 } 1048} 1049 1050proc _cgi_body_end {} { 1051 global _cgi 1052 if {![info exists _cgi(errorInfo)]} { 1053 uplevel 2 app_body_end 1054 } 1055 unset _cgi(body_in_progress) 1056 cgi_puts "</body>" 1057 1058 if {[info exists _cgi(multipart)]} { 1059 unset _cgi(http_head_done) 1060 catch {unset _cgi(http_status_done)} 1061 unset _cgi(head_done) 1062 catch {unset _cgi(head_suppress_tag)} 1063 } 1064} 1065 1066proc cgi_body_args {args} { 1067 global _cgi 1068 1069 set _cgi(body_args) $args 1070} 1071 1072proc cgi_script {args} { 1073 cgi_puts "<script[_cgi_lrange $args 0 [expr [llength $args]-2]]>" 1074 _cgi_close_proc_push "cgi_puts </script>" 1075 1076 uplevel 1 [lindex $args end] 1077 1078 _cgi_close_proc 1079} 1080 1081proc cgi_javascript {args} { 1082 cgi_puts "<script[_cgi_lrange $args 0 [expr [llength $args]-2]]>" 1083 cgi_puts "<!--- Hide script from browsers that don't understand JavaScript" 1084 _cgi_close_proc_push {cgi_puts "// End hiding -->\n</script>"} 1085 1086 uplevel 1 [lindex $args end] 1087 1088 _cgi_close_proc 1089} 1090 1091proc cgi_noscript {args} { 1092 cgi_puts "<noscript[_cgi_lrange $args 0 [expr [llength $args]-2]]>" 1093 _cgi_close_proc_push {cgi_puts "</noscript>"} 1094 1095 uplevel 1 [lindex $args end] 1096 1097 _cgi_close_proc 1098} 1099 1100proc cgi_applet {args} { 1101 cgi_puts "<applet[_cgi_lrange $args 0 [expr [llength $args]-2]]>" 1102 _cgi_close_proc_push "cgi_puts </applet>" 1103 1104 uplevel 1 [lindex $args end] 1105 _cgi_close_proc 1106} 1107 1108proc cgi_param {nameval} { 1109 regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value 1110 1111 if {$q != "="} { 1112 set value "" 1113 } 1114 cgi_puts "<param name=\"$name\" value=[cgi_dquote_html $value]/>" 1115} 1116 1117# record any proc's that must be called prior to displaying an error 1118proc _cgi_close_proc_push {p} { 1119 global _cgi 1120 if {![info exists _cgi(close_proc)]} { 1121 set _cgi(close_proc) "" 1122 } 1123 set _cgi(close_proc) "$p; $_cgi(close_proc)" 1124} 1125 1126proc _cgi_close_proc_pop {} { 1127 global _cgi 1128 regexp "^(\[^;]*);(.*)" $_cgi(close_proc) dummy lastproc _cgi(close_proc) 1129 return $lastproc 1130} 1131 1132# generic proc to close whatever is on the top of the stack 1133proc _cgi_close_proc {} { 1134 eval [_cgi_close_proc_pop] 1135} 1136 1137proc _cgi_close_procs {} { 1138 global _cgi 1139 1140 _cgi_close_tag 1141 if {[info exists _cgi(close_proc)]} { 1142 uplevel #0 $_cgi(close_proc) 1143 } 1144} 1145 1146proc _cgi_close_tag {} { 1147 global _cgi 1148 1149 if {[info exists _cgi(tag_in_progress)]} { 1150 cgi_put ">" 1151 unset _cgi(tag_in_progress) 1152 } 1153} 1154 1155################################################## 1156# hr support 1157################################################## 1158 1159proc cgi_hr {args} { 1160 set buf "<hr" 1161 foreach a $args { 1162 if {[regexp "^width=(.*)" $a dummy str]} { 1163 append buf " width=\"$str\"" 1164 } else { 1165 append buf " $a" 1166 } 1167 } 1168 cgi_put "$buf />" 1169} 1170 1171################################################## 1172# form & isindex 1173################################################## 1174 1175proc cgi_form {action args} { 1176 global _cgi 1177 1178 _cgi_form_multiple_check 1179 set _cgi(form_in_progress) 1 1180 1181 _cgi_close_proc_push _cgi_form_end 1182 cgi_put "<form action=" 1183 if {[regexp {^[a-z]*:} $action]} { 1184 cgi_put "\"$action\"" 1185 } else { 1186 cgi_put "\"[cgi_cgi $action]\"" 1187 } 1188 set method "method=post" 1189 foreach a [lrange $args 0 [expr [llength $args]-2]] { 1190 if {[regexp "^method=" $a]} { 1191 set method $a 1192 } elseif {[regexp "^(target|onReset|onSubmit)=(.*)" $a dummy attr str]} { 1193 cgi_put " $attr=\"$str\"" 1194 } elseif {[regexp "^enctype=(.*)" $a dummy str]} { 1195 cgi_put " enctype=\"$str\"" 1196 set _cgi(form,enctype) $str 1197 } else { 1198 cgi_put " $a" 1199 } 1200 } 1201 cgi_put " $method>" 1202 uplevel 1 [lindex $args end] 1203 catch {unset _cgi(form,enctype)} 1204 _cgi_close_proc 1205} 1206 1207proc _cgi_form_end {} { 1208 global _cgi 1209 unset _cgi(form_in_progress) 1210 cgi_put "</form>" 1211} 1212 1213proc _cgi_form_multiple_check {} { 1214 global _cgi 1215 if {[info exists _cgi(form_in_progress)]} { 1216 error "Cannot create form (or isindex) with form already in progress." 1217 } 1218} 1219 1220proc cgi_isindex {args} { 1221 _cgi_form_multiple_check 1222 1223 cgi_put "<isindex" 1224 foreach a $args { 1225 if {[regexp "^href=(.*)" $a dummy str]} { 1226 cgi_put " href=\"$str\"" 1227 } elseif {[regexp "^prompt=(.*)" $a dummy str]} { 1228 cgi_put " prompt=[cgi_dquote_html $str]" 1229 } else { 1230 cgi_put " $a" 1231 } 1232 } 1233 cgi_put "/>" 1234} 1235 1236################################################## 1237# argument handling 1238################################################## 1239 1240proc cgi_input {{fakeinput {}} {fakecookie {}}} { 1241 global env _cgi _cgi_uservar _cgi_cookie _cgi_cookie_shadowed 1242 1243 set _cgi(uservars) {} 1244 set _cgi(uservars,autolist) {} 1245 1246 if {[info exists env(CONTENT_TYPE)] && [regexp ^multipart/form-data $env(CONTENT_TYPE)]} { 1247 if {![info exists env(REQUEST_METHOD)]} { 1248 # running by hand 1249 set fid [open $fakeinput] 1250 } else { 1251 set fid stdin 1252 } 1253 if {([info tclversion] >= 8.1) || [catch exp_version] || [info exists _cgi(no_binary_upload)]} { 1254 _cgi_input_multipart $fid 1255 } else { 1256 _cgi_input_multipart_binary $fid 1257 } 1258 } else { 1259 if {![info exists env(REQUEST_METHOD)]} { 1260 set input $fakeinput 1261 set env(HTTP_COOKIE) $fakecookie 1262 } elseif { $env(REQUEST_METHOD) == "GET" } { 1263 set input "" 1264 catch {set input $env(QUERY_STRING)} ;# doesn't have to be set 1265 } elseif { $env(REQUEST_METHOD) == "HEAD" } { 1266 set input "" 1267 } elseif {![info exists env(CONTENT_LENGTH)]} { 1268 set _cgi(client_error) 1 1269 error "Your browser failed to generate the content-length during a POST method." 1270 } else { 1271 set length $env(CONTENT_LENGTH) 1272 if {0!=[string compare $length "-1"]} { 1273 set input [read stdin $env(CONTENT_LENGTH)] 1274 } else { 1275 set _cgi(client_error) 1 1276 error "Your browser generated a content-length of -1 during a POST method." 1277 } 1278 if {[info tclversion] >= 8.1} { 1279 # guess query encoding from Content-Type header 1280 if {[info exists env(CONTENT_TYPE)] \ 1281 && [regexp -nocase -- {charset=([^[:space:]]+)} $env(CONTENT_TYPE) m cs]} { 1282 if {[regexp -nocase -- {iso-?8859-([[:digit:]]+)} $cs m d]} { 1283 set _cgi(queryencoding) "iso8859-$d" 1284 } elseif {[regexp -nocase -- {windows-([[:digit:]]+)} $cs m d]} { 1285 set _cgi(queryencoding) "cp$d" 1286 } elseif {0==[string compare -nocase $cs "utf-8"]} { 1287 set _cgi(queryencoding) "utf-8" 1288 } elseif {0==[string compare -nocase $cs "utf-16"]} { 1289 set _cgi(queryencoding) "unicode" 1290 } 1291 } else { 1292 set _cgi(queryencoding) [encoding system] 1293 } 1294 } 1295 } 1296 # save input for possible diagnostics later 1297 set _cgi(input) $input 1298 1299 set pairs [split $input &] 1300 foreach pair $pairs { 1301 if {0 == [regexp "^(\[^=]*)=(.*)$" $pair dummy varname val]} { 1302 # if no match, unquote and leave it at that 1303 # this is typical of <isindex>-style queries 1304 set varname anonymous 1305 set val $pair 1306 } 1307 1308 set varname [cgi_unquote_input $varname] 1309 set val [cgi_unquote_input $val] 1310 _cgi_set_uservar $varname $val 1311 } 1312 } 1313 1314 # O'Reilly's web server incorrectly uses COOKIE 1315 catch {set env(HTTP_COOKIE) $env(COOKIE)} 1316 if {![info exists env(HTTP_COOKIE)]} return 1317 foreach pair [split $env(HTTP_COOKIE) ";"] { 1318 # pairs are actually split by "; ", sigh 1319 set pair [string trimleft $pair " "] 1320 # spec is not clear but seems to allow = unencoded 1321 # only sensible interpretation is to assume no = in var names 1322 # appears MS IE can omit "=val" 1323 set val "" 1324 regexp (\[^=]*)=?(.*) $pair dummy varname val 1325 1326 set varname [cgi_unquote_input $varname] 1327 set val [cgi_unquote_input $val] 1328 1329 if {[info exists _cgi_cookie($varname)]} { 1330 lappend _cgi_cookie_shadowed($varname) $val 1331 } else { 1332 set _cgi_cookie($varname) $val 1333 } 1334 } 1335} 1336 1337proc _cgi_input_multipart {fin} { 1338 global env _cgi _cgi_uservar _cgi_userfile 1339 1340 cgi_debug -noprint { 1341 # save file for debugging purposes 1342 set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] 1343 # explicitly flush all writes to fout, because sometimes the writer 1344 # can hang and we won't get to the termination code 1345 set dbg_fout [open $dbg_filename w $_cgi(tmpperms)] 1346 set _cgi(input) $dbg_filename 1347 catch {fconfigure $dbg_fout -translation binary} 1348 } 1349 1350 # figure out boundary 1351 if {0==[regexp boundary=(.*) $env(CONTENT_TYPE) dummy boundary]} { 1352 set _cgi(client_error) 1 1353 error "Your browser failed to generate a \"boundary=\" line in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." 1354 } 1355 1356 # make boundary into a legal regsub pattern by protecting # 1357 # legal boundary characters include ()+.? (among others) 1358 regsub -all "\\(" $boundary "\\(" boundary 1359 regsub -all "\\)" $boundary "\\)" boundary 1360 regsub -all "\\+" $boundary "\\+" boundary 1361 regsub -all "\\." $boundary "\\." boundary 1362 regsub -all "\\?" $boundary "\\?" boundary 1363 1364 set boundary --$boundary 1365 1366 # don't corrupt or modify uploads yet allow Tcl 7.4 to work 1367 catch {fconfigure $fin -translation binary} 1368 1369 # get first boundary line 1370 gets $fin buf 1371 if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} 1372 1373 set _cgi(file,filecount) 0 1374 1375 while {1} { 1376 # process Content-Disposition: 1377 if {-1 == [gets $fin buf]} break 1378 if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} 1379 catch {unset filename} 1380 regexp {name="([^"]*)"} $buf dummy varname 1381 if {0==[info exists varname]} { 1382 # lynx violates spec and doesn't use quotes, so try again but 1383 # assume space is delimiter 1384 regexp {name=([^ ]*)} $buf dummy varname 1385 if {0==[info exists varname]} { 1386 set _cgi(client_error) 1 1387 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." 1388 } 1389 } 1390 # Lame-o encoding (on Netscape at least) doesn't escape field 1391 # delimiters (like quotes)!! Since all we've ever seen is filename= 1392 # at end of line, assuming nothing follows. Sigh. 1393 regexp {filename="(.*)"} $buf dummy filename 1394 1395 # Skip remaining headers until blank line. 1396 # Content-Type: can appear here. 1397 set conttype "" 1398 while {1} { 1399 if {-1 == [gets $fin buf]} break 1400 if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} 1401 if {0==[string compare $buf "\r"]} break 1402 regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype 1403 } 1404 1405 if {[info exists filename]} { 1406 if {$_cgi(file,filecount) > $_cgi(file,filelimit)} { 1407 error "Too many files submitted. Max files allowed: $_cgi(file,filelimit)" 1408 } 1409 1410 # read the part into a file 1411 set foutname [file join $_cgi(tmpdir) CGI[pid].[incr _cgi(file,filecount)]] 1412 set fout [open $foutname w $_cgi(tmpperms)] 1413 # "catch" permits this to work with Tcl 7.4 1414 catch {fconfigure $fout -translation binary} 1415 _cgi_set_uservar $varname [list $foutname $filename $conttype] 1416 set _cgi_userfile($varname) [list $foutname $filename $conttype] 1417 1418 # 1419 # Look for a boundary line preceded by \r\n. 1420 # 1421 # To do this, we buffer line terminators that might 1422 # be the start of the special \r\n$boundary sequence. 1423 # The buffer is called "leftover" and is just inserted 1424 # into the front of the next output (assuming it's 1425 # not a boundary line). 1426 1427 set leftover "" 1428 while {1} { 1429 if {-1 == [gets $fin buf]} break 1430 if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} 1431 1432 if {0 == [string compare "\r\n" $leftover]} { 1433 if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { 1434 if {$dashdash == "--"} {set eof 1} 1435 break 1436 } 1437 } 1438 if {[regexp (.*)\r$ $buf x data]} { 1439 puts -nonewline $fout $leftover$data 1440 set leftover "\r\n" 1441 } else { 1442 puts -nonewline $fout $leftover$buf 1443 set leftover "\n" 1444 } 1445 if {[file size $foutname] > $_cgi(file,charlimit)} { 1446 error "File size exceeded. Max file size allowed: $_cgi(file,charlimit)" 1447 } 1448 } 1449 1450 close $fout 1451 unset fout 1452 } else { 1453 # read the part into a variable 1454 set val "" 1455 set blanks 0 1456 while {1} { 1457 if {-1 == [gets $fin buf]} break 1458 if {[info exists dbg_fout]} {puts $dbg_fout $buf; flush $dbg_fout} 1459 if {[regexp ^[set boundary](--)?\r?$ $buf dummy dashdash]} { 1460 if {$dashdash == "--"} {set eof 1} 1461 break 1462 } 1463 if {0!=[string compare $val ""]} { 1464 append val \n 1465 } 1466 regexp (.*)\r$ $buf dummy buf 1467 if {[info exists blanks]} { 1468 if {0!=[string compare $buf ""]} { 1469 if {$blanks} { 1470 append val [string repeat \n [incr blanks]] 1471 } 1472 unset blanks 1473 } else { 1474 incr blanks 1475 } 1476 } 1477 append val $buf 1478 } 1479 _cgi_set_uservar $varname $val 1480 } 1481 if {[info exists eof]} break 1482 } 1483 if {[info exists dbg_fout]} {close $dbg_fout} 1484} 1485 1486proc _cgi_input_multipart_binary {fin} { 1487 global env _cgi _cgi_uservar _cgi_userfile 1488 1489 log_user 0 1490 set timeout -1 1491 1492 cgi_debug -noprint { 1493 # save file for debugging purposes 1494 set dbg_filename [file join $_cgi(tmpdir) CGIdbg.[pid]] 1495 set _cgi(input) $dbg_filename 1496 spawn -open [open $dbg_filename w $_cgi(tmpperms)] 1497 set dbg_sid $spawn_id 1498 } 1499 spawn -open $fin 1500 set fin_sid $spawn_id 1501 remove_nulls 0 1502 1503 if {0} { 1504 # dump input to screen 1505 cgi_debug { 1506 puts "<xmp>" 1507 expect { 1508 -i $fin_sid 1509 -re ^\r {puts -nonewline "CR"; exp_continue} 1510 -re ^\n {puts "NL"; exp_continue} 1511 -re . {puts -nonewline $expect_out(buffer); exp_continue} 1512 } 1513 puts "</xmp>" 1514 exit 1515 } 1516 } 1517 1518 # figure out boundary 1519 if {0==[regexp boundary=(.*) $env(CONTENT_TYPE) dummy boundary]} { 1520 set _cgi(client_error) 1 1521 error "Your browser failed to generate a \"boundary=\" definition in a multipart response (CONTENT_TYPE: $env(CONTENT_TYPE)). Please upgrade (or fix) your browser." 1522 } 1523 1524 # make boundary into a legal regsub pattern by protecting # 1525 # legal boundary characters include ()+.? (among others) 1526 regsub -all "\\(" $boundary "\\(" boundary 1527 regsub -all "\\)" $boundary "\\)" boundary 1528 regsub -all "\\+" $boundary "\\+" boundary 1529 regsub -all "\\." $boundary "\\." boundary 1530 regsub -all "\\?" $boundary "\\?" boundary 1531 1532 set boundary --$boundary 1533 set linepat "(\[^\r]*)\r\n" 1534 1535 # get first boundary line 1536 expect { 1537 -i $fin_sid 1538 -re $linepat { 1539 set buf $expect_out(1,string) 1540 if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} 1541 } 1542 eof { 1543 set _cgi(client_error) 1 1544 error "Your browser failed to provide an initial boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." 1545 } 1546 } 1547 1548 set _cgi(file,filecount) 0 1549 1550 while {1} { 1551 # process Content-Disposition: 1552 expect { 1553 -i $fin_sid 1554 -re $linepat { 1555 set buf $expect_out(1,string) 1556 if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} 1557 } 1558 eof break 1559 } 1560 catch {unset filename} 1561 regexp {name="([^"]*)"} $buf dummy varname 1562 if {0==[info exists varname]} { 1563 set _cgi(client_error) 1 1564 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." 1565 } 1566 1567 # Lame-o encoding (on Netscape at least) doesn't escape field 1568 # delimiters (like quotes)!! Since all we've ever seen is filename= 1569 # at end of line, assuming nothing follows. Sigh. 1570 regexp {filename="(.*)"} $buf dummy filename 1571 1572 # Skip remaining headers until blank line. 1573 # Content-Type: can appear here. 1574 set conttype "" 1575 expect { 1576 -i $fin_sid 1577 -re $linepat { 1578 set buf $expect_out(1,string) 1579 if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} 1580 if {0!=[string compare $buf ""]} exp_continue 1581 regexp -nocase "^Content-Type:\[ \t]+(.*)\r" $buf x conttype 1582 } 1583 eof break 1584 } 1585 1586 if {[info exists filename]} { 1587 if {$_cgi(file,filecount) > $_cgi(file,filelimit)} { 1588 error "Too many files submitted. Max files allowed: $_cgi(file,filelimit)" 1589 } 1590 1591 # read the part into a file 1592 set foutname [file join $_cgi(tmpdir) CGI[pid].[incr _cgi(file,filecount)]] 1593 spawn -open [open $foutname w $_cgi(tmpperms)] 1594 set fout_sid $spawn_id 1595 1596 _cgi_set_uservar $varname [list $foutname $filename $conttype] 1597 set _cgi_userfile($varname) [list $foutname $filename $conttype] 1598 1599 # This is tricky stuff - be very careful changing anything here! 1600 # In theory, all we have to is record everything up to 1601 # \r\n$boundary\r\n. Unfortunately, we can't simply wait on 1602 # such a pattern because the input can overflow any possible 1603 # buffer we might choose. We can't simply catch buffer_full 1604 # because the boundary might straddle a buffer. I doubt that 1605 # doing my own buffering would be any faster than taking the 1606 # approach I've done here. 1607 # 1608 # The code below basically implements a simple scanner that 1609 # keeps track of whether it's seen crlfs or pieces of them. 1610 # The idea is that we look for crlf pairs, separated by 1611 # things that aren't crlfs (or pieces of them). As we encounter 1612 # things that aren't crlfs (or pieces of them), or when we decide 1613 # they can't be, we mark them for output and resume scanning for 1614 # new pairs. 1615 # 1616 # The scanner runs tolerably fast because the [...]+ pattern picks 1617 # up most things. The \r and \n are ^-anchored so the pattern 1618 # match is pretty fast and these don't happen that often so the 1619 # huge \n action is executed rarely (once per line on text files). 1620 # The null pattern is, of course, only used when everything 1621 # else fails. 1622 1623 # crlf == "\r\n" if we've seen one, else == "" 1624 # cr == "\r" if we JUST saw one, else == "" 1625 # Yes, strange, but so much more efficient 1626 # that I'm willing to sacrifice readability, sigh. 1627 # buf accumulated data between crlf pairs 1628 1629 set buf "" 1630 set cr "" 1631 set crlf "" 1632 1633 expect { 1634 -i $fin_sid 1635 -re "^\r" { 1636 if {$cr == "\r"} { 1637 append buf "\r" 1638 } 1639 set cr \r 1640 exp_continue 1641 } -re "^\n" { 1642 if {$cr == "\r"} { 1643 if {$crlf == "\r\n"} { 1644 # do boundary test 1645 if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { 1646 if {$dashdash == "--"} { 1647 set eof 1 1648 } 1649 } else { 1650 # boundary test failed 1651 if {[info exists dbg_sid]} {send -i $dbg_sid -- \r\n$buf} 1652 send -i $fout_sid \r\n$buf ; set buf "" 1653 set cr "" 1654 exp_continue 1655 } 1656 } else { 1657 set crlf "\r\n" 1658 set cr "" 1659 if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf} 1660 send -i $fout_sid -- $buf ; set buf "" 1661 exp_continue 1662 } 1663 } else { 1664 if {[info exists dbg_sid]} {send -i $dbg_sid -- $crlf$buf\n} 1665 send -i $fout_sid -- $crlf$buf\n ; set buf "" 1666 set crlf "" 1667 exp_continue 1668 } 1669 } -re "\[^\r\n]+" { 1670 if {$cr == "\r"} { 1671 set buf $crlf$buf\r$expect_out(buffer) 1672 set crlf "" 1673 set cr "" 1674 } else { 1675 append buf $expect_out(buffer) 1676 } 1677 exp_continue 1678 } null { 1679 if {[info exists dbg_sid]} { 1680 send -i $dbg_sid -- $crlf$buf$cr 1681 send -i $dbg_sid -null 1682 } 1683 send -i $fout_sid -- $crlf$buf$cr ; set buf "" 1684 send -i $fout_sid -null 1685 set cr "" 1686 set crlf "" 1687 exp_continue 1688 } eof { 1689 set _cgi(client_error) 1 1690 error "Your browser failed to provide an ending boundary ($boundary) in a multipart response. Please upgrade (or fix) your browser." 1691 } 1692 } 1693 exp_close -i $fout_sid ;# implicitly closes fout 1694 exp_wait -i $fout_sid 1695 unset fout_sid 1696 } else { 1697 # read the part into a variable 1698 set val "" 1699 expect { 1700 -i $fin_sid 1701 -re $linepat { 1702 set buf $expect_out(1,string) 1703 if {[info exists dbg_sid]} {send -i $dbg_sid -- $buf\n} 1704 if {[regexp ^[set boundary](--)?$ $buf dummy dashdash]} { 1705 if {$dashdash == "--"} {set eof 1} 1706 } else { 1707 regexp (.*)\r$ $buf dummy buf 1708 if {0!=[string compare $val ""]} { 1709 append val \n 1710 } 1711 append val $buf 1712 exp_continue 1713 } 1714 } 1715 } 1716 _cgi_set_uservar $varname $val 1717 } 1718 if {[info exists eof]} break 1719 } 1720 if {[info exists fout]} { 1721 exp_close -i $dbg_sid 1722 exp_wait -i $dbg_sid 1723 } 1724 1725 # no need to close fin, fin_sid, or dbg_sid 1726} 1727 1728# internal routine for defining user variables 1729proc _cgi_set_uservar {varname val} { 1730 global _cgi _cgi_uservar 1731 1732 set exists [info exists _cgi_uservar($varname)] 1733 set isList $exists 1734 # anything we've seen before and is being set yet again necessarily 1735 # has to be (or become a list) 1736 1737 if {!$exists} { 1738 lappend _cgi(uservars) $varname 1739 } 1740 1741 if {[regexp List$ $varname]} { 1742 set isList 1 1743 } elseif {$exists} { 1744 # vars that we've seen before but aren't marked as lists 1745 # need to be "listified" so we can do appends later 1746 if {-1 == [lsearch $_cgi(uservars,autolist) $varname]} { 1747 # remember that we've listified it 1748 lappend _cgi(uservars,autolist) $varname 1749 set _cgi_uservar($varname) [list $_cgi_uservar($varname)] 1750 } 1751 } 1752 if {$isList} { 1753 lappend _cgi_uservar($varname) $val 1754 } else { 1755 set _cgi_uservar($varname) $val 1756 } 1757} 1758 1759# export named variable 1760proc cgi_export {nameval} { 1761 regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value 1762 1763 if {$q != "="} { 1764 set value [uplevel 1 set [list $name]] 1765 } 1766 1767 cgi_put "<input type=hidden name=\"$name\" value=[cgi_dquote_html $value]/>" 1768} 1769 1770proc cgi_export_cookie {name args} { 1771 upvar 1 $name x 1772 eval cgi_cookie_set [list $name=$x] $args 1773} 1774 1775# return list of variables available for import 1776# Explicit list is used to keep items in order originally found in form. 1777proc cgi_import_list {} { 1778 global _cgi 1779 1780 return $_cgi(uservars) 1781} 1782 1783# import named variable 1784proc cgi_import {name} { 1785 global _cgi_uservar 1786 upvar 1 $name var 1787 1788 set var $_cgi_uservar($name) 1789} 1790 1791proc cgi_import_as {name tclvar} { 1792 global _cgi_uservar 1793 upvar 1 $tclvar var 1794 1795 set var $_cgi_uservar($name) 1796} 1797 1798# like cgi_import but if not available, try cookie 1799proc cgi_import_cookie {name} { 1800 global _cgi_uservar 1801 upvar 1 $name var 1802 1803 if {0==[catch {set var $_cgi_uservar($name)}]} return 1804 set var [cgi_cookie_get $name] 1805} 1806 1807# like cgi_import but if not available, try cookie 1808proc cgi_import_cookie_as {name tclvar} { 1809 global _cgi_uservar 1810 upvar 1 $tclvar var 1811 1812 if {0==[catch {set var $_cgi_uservar($name)}]} return 1813 set var [cgi_cookie_get $name] 1814} 1815 1816proc cgi_import_file {type name} { 1817 global _cgi_userfile 1818 upvar 1 $name var 1819 1820 set var $_cgi_userfile($name) 1821 switch -- $type { 1822 "-server" { 1823 lindex $var 0 1824 } "-client" { 1825 lindex $var 1 1826 } "-type" { 1827 lindex $var 2 1828 } 1829 } 1830} 1831 1832# deprecated, use cgi_import_file 1833proc cgi_import_filename {type name} { 1834 global _cgi_userfile 1835 upvar 1 $name var 1836 1837 set var $_cgi_userfile($name) 1838 if {$type == "-server" || $type == "-local"} { 1839 # -local is deprecated 1840 lindex $var 0 1841 } else { 1842 lindex $var 1 1843 } 1844} 1845 1846# set the urlencoding 1847proc cgi_urlencoding {{encoding ""}} { 1848 global _cgi 1849 1850 set result [expr {[info exists _cgi(queryencoding)] 1851 ? $_cgi(queryencoding) 1852 : ""}] 1853 1854 # check if the encoding is available 1855 if {[info tclversion] >= 8.1 1856 && [lsearch -exact [encoding names] $encoding] != -1 } { 1857 set _cgi(queryencoding) $encoding 1858 } 1859 1860 return $result 1861} 1862 1863################################################## 1864# button support 1865################################################## 1866 1867# not sure about arg handling, do we need to support "name="? 1868proc cgi_button {value args} { 1869 cgi_put "<input type=button value=[cgi_dquote_html $value]" 1870 foreach a $args { 1871 if {[regexp "^onClick=(.*)" $a dummy str]} { 1872 cgi_put " onClick=\"$str\"" 1873 } else { 1874 cgi_put " $a" 1875 } 1876 } 1877 cgi_put "/>" 1878} 1879 1880# Derive a button from a link predefined by cgi_link 1881proc cgi_button_link {args} { 1882 global _cgi_link 1883 1884 set tag [lindex $args 0] 1885 if {[llength $args] == 2} { 1886 set label [lindex $args end] 1887 } else { 1888 set label $_cgi_link($tag,label) 1889 } 1890 1891 cgi_button $label onClick=$_cgi_link($tag,url) 1892} 1893 1894proc cgi_submit_button {{nameval {=Submit Query}} args} { 1895 regexp "(\[^=]*)=(.*)" $nameval dummy name value 1896 cgi_put "<input type=submit" 1897 if {0!=[string compare "" $name]} { 1898 cgi_put " name=\"$name\"" 1899 } 1900 cgi_put " value=[cgi_dquote_html $value]" 1901 foreach a $args { 1902 if {[regexp "^onClick=(.*)" $a dummy str]} { 1903 cgi_put " onClick=\"$str\"" 1904 } else { 1905 cgi_put " $a" 1906 } 1907 } 1908 cgi_put "/>" 1909} 1910 1911 1912proc cgi_reset_button {{value Reset} args} { 1913 cgi_put "<input type=reset value=[cgi_dquote_html $value]" 1914 1915 foreach a $args { 1916 if {[regexp "^onClick=(.*)" $a dummy str]} { 1917 cgi_put " onClick=\"$str\"" 1918 } else { 1919 cgi_put " $a" 1920 } 1921 } 1922 cgi_put "/>" 1923} 1924 1925proc cgi_radio_button {nameval args} { 1926 regexp "(\[^=]*)=(.*)" $nameval dummy name value 1927 1928 cgi_put "<input type=radio name=\"$name\" value=[cgi_dquote_html $value]" 1929 1930 foreach a $args { 1931 if {[regexp "^checked_if_equal=(.*)" $a dummy default]} { 1932 if {0==[string compare $default $value]} { 1933 cgi_put " checked" 1934 } 1935 } elseif {[regexp "^checked=(.*)" $a dummy checked]} { 1936 # test explicitly to avoid forcing user eval 1937 if {$checked} { 1938 cgi_put " checked" 1939 } 1940 } elseif {[regexp "^onClick=(.*)" $a dummy str]} { 1941 cgi_put " onClick=\"$str\"" 1942 } else { 1943 cgi_put " $a" 1944 } 1945 } 1946 cgi_put "/>" 1947} 1948 1949proc cgi_image_button {nameval args} { 1950 regexp "(\[^=]*)=(.*)" $nameval dummy name value 1951 cgi_put "<input type=image" 1952 if {0!=[string compare "" $name]} { 1953 cgi_put " name=\"$name\"" 1954 } 1955 cgi_put " src=\"$value\"" 1956 foreach a $args { 1957 if {[regexp "^onClick=(.*)" $a dummy str]} { 1958 cgi_put " onClick=\"$str\"" 1959 } else { 1960 cgi_put " $a" 1961 } 1962 } 1963 cgi_put "/>" 1964} 1965 1966# map/area implement client-side image maps 1967proc cgi_map {name cmd} { 1968 cgi_put "<map name=\"$name\">" 1969 _cgi_close_proc_push "cgi_put </map>" 1970 1971 uplevel 1 $cmd 1972 _cgi_close_proc 1973} 1974 1975proc cgi_area {args} { 1976 cgi_put "<area" 1977 foreach a $args { 1978 if {[regexp "^(coords|shape|href|target|onMouseOut|alt)=(.*)" $a dummy attr str]} { 1979 cgi_put " $attr=\"$str\"" 1980 } else { 1981 cgi_put " $a" 1982 } 1983 } 1984 cgi_put "/>" 1985} 1986 1987################################################## 1988# checkbox support 1989################################################## 1990 1991proc cgi_checkbox {nameval args} { 1992 regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value 1993 cgi_put "<input type=checkbox name=\"$name\"" 1994 1995 if {0!=[string compare "" $value]} { 1996 cgi_put " value=[cgi_dquote_html $value]" 1997 } 1998 1999 foreach a $args { 2000 if {[regexp "^checked_if_equal=(.*)" $a dummy default]} { 2001 if {0==[string compare $default $value]} { 2002 cgi_put " checked" 2003 } 2004 } elseif {[regexp "^checked=(.*)" $a dummy checked]} { 2005 # test explicitly to avoid forcing user eval 2006 if {$checked} { 2007 cgi_put " checked" 2008 } 2009 } elseif {[regexp "^onClick=(.*)" $a dummy str]} { 2010 cgi_put " onClick=\"$str\"" 2011 } else { 2012 cgi_put " $a" 2013 } 2014 } 2015 cgi_put "/>" 2016} 2017 2018################################################## 2019# textentry support 2020################################################## 2021 2022proc cgi_text {nameval args} { 2023 regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value 2024 2025 cgi_put "<input name=\"$name\"" 2026 2027 if {$q != "="} { 2028 set value [uplevel 1 set [list $name]] 2029 } 2030 cgi_put " value=[cgi_dquote_html $value]" 2031 2032 foreach a $args { 2033 if {[regexp "^on(Select|Focus|Blur|Change)=(.*)" $a dummy event str]} { 2034 cgi_put " on$event=\"$str\"" 2035 } else { 2036 cgi_put " $a" 2037 } 2038 } 2039 cgi_put "/>" 2040} 2041 2042################################################## 2043# textarea support 2044################################################## 2045 2046proc cgi_textarea {nameval args} { 2047 regexp "(\[^=]*)(=?)(.*)" $nameval dummy name q value 2048 2049 cgi_put "<textarea name=\"$name\"" 2050 foreach a $args { 2051 if {[regexp "^on(Select|Focus|Blur|Change)=(.*)" $a dummy event str]} { 2052 cgi_put " on$event=\"$str\"" 2053 } else { 2054 cgi_put " $a" 2055 } 2056 } 2057 cgi_put ">" 2058 2059 if {$q != "="} { 2060 set value [uplevel 1 set [list $name]] 2061 } 2062 cgi_put "[cgi_quote_html $value]</textarea>" 2063} 2064 2065################################################## 2066# file upload support 2067################################################## 2068 2069# for this to work, pass enctype=multipart/form-data to cgi_form 2070proc cgi_file_button {name args} { 2071 global _cgi 2072 if {[info exists _cgi(formtype)] && ("multipart/form-data" != $_cgi(form,enctype))} { 2073 error "cgi_file_button requires that cgi_form have the argument enctype=multipart/form-data" 2074 } 2075 cgi_put "<input type=file name=\"$name\"[_cgi_list_to_string $args]/>" 2076} 2077 2078# establish a per-file limit for uploads 2079 2080proc cgi_file_limit {files chars} { 2081 global _cgi 2082 2083 set _cgi(file,filelimit) $files 2084 set _cgi(file,charlimit) $chars 2085} 2086 2087################################################## 2088# select support 2089################################################## 2090 2091proc cgi_select {name args} { 2092 cgi_put "<select name=\"$name\"" 2093 _cgi_close_proc_push "cgi_put </select>" 2094 foreach a [lrange $args 0 [expr [llength $args]-2]] { 2095 if {[regexp "^on(Focus|Blur|Change)=(.*)" $a dummy event str]} { 2096 cgi_put " on$event=\"$str\"" 2097 } else { 2098 if {0==[string compare multiple $a]} { 2099 ;# sanity check 2100 if {![regexp "List$" $name]} { 2101 cgi_puts ">" ;# prevent error from being absorbed 2102 error "When selecting multiple options, select variable \ 2103 must end in \"List\" to allow the value to be \ 2104 recognized as a list when it is processed later." 2105 } 2106 } 2107 cgi_put " $a" 2108 } 2109 } 2110 cgi_put ">" 2111 uplevel 1 [lindex $args end] 2112 _cgi_close_proc 2113} 2114 2115proc cgi_option {o args} { 2116 cgi_put "<option" 2117 set value $o 2118 set selected 0 2119 foreach a $args { 2120 if {[regexp "^selected_if_equal=(.*)" $a dummy selected_if_equal]} { 2121 } elseif {[regexp "^value=(.*)" $a dummy value]} { 2122 cgi_put " value=[cgi_dquote_html $value]" 2123 } else { 2124 cgi_put " $a" 2125 } 2126 } 2127 if {[info exists selected_if_equal]} { 2128 if {0 == [string compare $selected_if_equal $value]} { 2129 cgi_put " selected" 2130 } 2131 } 2132 cgi_puts ">[cgi_quote_html $o]</option>" 2133} 2134 2135################################################## 2136# plug-in support 2137################################################## 2138 2139proc cgi_embed {src wh args} { 2140 regexp (.*)x(.*) $wh dummy width height 2141 cgi_put "<embed src=[cgi_dquote_html $src] width=\"$width\" height=\"$height\"" 2142 foreach a $args { 2143 if {[regexp "^palette=(.*)" $a dummy str]} { 2144 cgi_put " palette=\"$str\"" 2145 } elseif {[regexp -- "-quote" $a]} { 2146 set quote 1 2147 } else { 2148 if {[info exists quote]} { 2149 regexp "(\[^=]*)=(.*)" $a dummy var val 2150 cgi_put " var=[cgi_dquote_html $var]" 2151 } else { 2152 cgi_put " $a" 2153 } 2154 } 2155 } 2156 cgi_put "/>" 2157} 2158 2159################################################## 2160# mail support 2161################################################## 2162 2163# mail to/from the service itself 2164proc cgi_mail_addr {args} { 2165 global _cgi 2166 2167 if {[llength $args]} { 2168 set _cgi(email) [lindex $args 0] 2169 } 2170 return $_cgi(email) 2171} 2172 2173proc cgi_mail_start {to} { 2174 global _cgi 2175 2176 set _cgi(mailfile) [file join $_cgi(tmpdir) cgimail.[pid]] 2177 set _cgi(mailfid) [open $_cgi(mailfile) w+] 2178 set _cgi(mailto) $to 2179 2180 # mail is actually sent by "nobody". To force bounce messages 2181 # back to us, override the default return-path. 2182 cgi_mail_add "Return-Path: <$_cgi(email)>" 2183 cgi_mail_add "From: [cgi_name] <$_cgi(email)>" 2184 cgi_mail_add "To: $to" 2185} 2186 2187# add another line to outgoing mail 2188# if no arg, add a blank line 2189proc cgi_mail_add {{arg {}}} { 2190 global _cgi 2191 2192 puts $_cgi(mailfid) $arg 2193} 2194 2195# end the outgoing mail and send it 2196proc cgi_mail_end {} { 2197 global _cgi 2198 2199 flush $_cgi(mailfid) 2200 2201 foreach sendmail in $_cgi(sendmail) { 2202 if {[file executable $sendmail]} { 2203 exec $sendmail -t -odb < $_cgi(mailfile) 2204 # Explanation: 2205 # -t means: pick up recipient from body 2206 # -odb means: deliver in background 2207 # note: bogus local address cause sendmail to fail immediately 2208 set sent 1 2209 } 2210 } 2211 2212 if {0==[info exists sent]} { 2213 # fallback for sites without sendmail 2214 2215 if {0==[info exists _cgi(mail_relay)]} { 2216 regexp "@(.*)" $_cgi(mailto) dummy _cgi(mail_relay) 2217 } 2218 2219 set s [socket $_cgi(mail_relay) 25] 2220 gets $s answer 2221 if {[lindex $answer 0] != 220} {error $answer} 2222 2223 puts $s "HELO [info host]";flush $s 2224 gets $s answer 2225 if {[lindex $answer 0] != 250} {error $answer} 2226 2227 puts $s "MAIL FROM:<$_cgi(email)>";flush $s 2228 gets $s answer 2229 if {[lindex $answer 0] != 250} {error $answer} 2230 2231 puts $s "RCPT TO:<$_cgi(mailto)>";flush $s 2232 gets $s answer 2233 if {[lindex $answer 0] != 250} {error $answer} 2234 2235 puts $s DATA;flush $s 2236 gets $s answer 2237 if {[lindex $answer 0] != 354} {error $answer} 2238 2239 seek $_cgi(mailfid) 0 start 2240 puts $s [read $_cgi(mailfid)];flush $s 2241 puts $s .;flush $s 2242 gets $s answer 2243 if {[lindex $answer 0] != 250} {error $answer} 2244 2245 close $s 2246 } 2247 close $_cgi(mailfid) 2248 file delete -force $_cgi(mailfile) 2249} 2250 2251proc cgi_mail_relay {host} { 2252 global _cgi 2253 2254 set _cgi(mail_relay) $host 2255} 2256 2257proc cgi_sendmail {path} { 2258 global _cgi 2259 2260 set _cgi(sendmail) $path 2261} 2262 2263################################################## 2264# cookie support 2265################################################## 2266 2267# calls to cookie_set look like this: 2268# cgi_cookie_set user=don domain=nist.gov expires=never 2269# cgi_cookie_set user=don domain=nist.gov expires=now 2270# cgi_cookie_set user=don domain=nist.gov expires=...actual date... 2271 2272proc cgi_cookie_set {nameval args} { 2273 global _cgi 2274 2275 if {![info exists _cgi(http_head_in_progress)]} { 2276 error "Cookies must be set from within cgi_http_head." 2277 } 2278 cgi_puts -nonewline "Set-Cookie: [cgi_cookie_encode $nameval];" 2279 2280 foreach a $args { 2281 if {[regexp "^expires=(.*)" $a dummy expiration]} { 2282 if {0==[string compare $expiration "never"]} { 2283 set expiration "Friday, 11-Jan-2038 23:59:59 GMT" 2284 } elseif {0==[string compare $expiration "now"]} { 2285 set expiration "Friday, 31-Dec-1990 23:59:59 GMT" 2286 } 2287 cgi_puts -nonewline " expires=$expiration;" 2288 } elseif {[regexp "^(domain|path)=(.*)" $a dummy attr str]} { 2289 cgi_puts -nonewline " $attr=[cgi_cookie_encode $str];" 2290 } elseif {[regexp "^secure$" $a]} { 2291 cgi_puts -nonewline " secure;" 2292 } 2293 } 2294 cgi_puts "" 2295} 2296 2297# return list of cookies available for import 2298proc cgi_cookie_list {} { 2299 global _cgi_cookie 2300 2301 array names _cgi_cookie 2302} 2303 2304proc cgi_cookie_get {args} { 2305 global _cgi_cookie 2306 2307 set all 0 2308 2309 set flag [lindex $args 0] 2310 if {$flag == "-all"} { 2311 set args [lrange $args 1 end] 2312 set all 1 2313 } 2314 set name [lindex $args 0] 2315 2316 if {$all} { 2317 global _cgi_cookie_shadowed 2318 2319 if {[info exists _cgi_cookie_shadowed($name)]} { 2320 return [concat $_cgi_cookie($name) $_cgi_cookie_shadowed($name)] 2321 } else { 2322 return [concat $_cgi_cookie($name)] 2323 } 2324 } 2325 return $_cgi_cookie($name) 2326} 2327 2328proc cgi_cookie_encode {in} { 2329 regsub -all " " $in "+" in 2330 regsub -all "%" $in "%25" in ;# must precede other subs that produce % 2331 regsub -all ";" $in "%3B" in 2332 regsub -all "," $in "%2C" in 2333 regsub -all "\n" $in "%0D%0A" in 2334 return $in 2335} 2336 2337################################################## 2338# table support 2339################################################## 2340 2341proc cgi_table {args} { 2342 cgi_put "<table" 2343 _cgi_close_proc_push "cgi_put </table>" 2344 2345 if {[llength $args]} { 2346 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2347 } 2348 cgi_put ">" 2349 uplevel 1 [lindex $args end] 2350 _cgi_close_proc 2351} 2352 2353proc cgi_caption {args} { 2354 cgi_put "<caption" 2355 _cgi_close_proc_push "cgi_put </caption>" 2356 2357 if {[llength $args]} { 2358 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2359 } 2360 cgi_put ">" 2361 uplevel 1 [lindex $args end] 2362 _cgi_close_proc 2363} 2364 2365proc cgi_table_row {args} { 2366 cgi_put "<tr" 2367 _cgi_close_proc_push "cgi_put </tr>" 2368 if {[llength $args]} { 2369 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2370 } 2371 cgi_put ">" 2372 uplevel 1 [lindex $args end] 2373 _cgi_close_proc 2374} 2375 2376# like table_row but without eval 2377proc cgi_tr {args} { 2378 cgi_put <tr 2379 if {[llength $args] > 1} { 2380 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2381 } 2382 cgi_put ">" 2383 foreach i [lindex $args end] { 2384 cgi_td $i 2385 } 2386 cgi_put </tr> 2387} 2388 2389proc cgi_table_head {args} { 2390 cgi_put "<th" 2391 _cgi_close_proc_push "cgi_put </th>" 2392 2393 if {[llength $args]} { 2394 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2395 } 2396 cgi_put ">" 2397 uplevel 1 [lindex $args end] 2398 _cgi_close_proc 2399} 2400 2401# like table_head but without eval 2402proc cgi_th {args} { 2403 cgi_put "<th" 2404 2405 if {[llength $args] > 1} { 2406 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2407 } 2408 cgi_put ">[lindex $args end]</th>" 2409} 2410 2411proc cgi_table_data {args} { 2412 cgi_put "<td" 2413 _cgi_close_proc_push "cgi_put </td>" 2414 2415 if {[llength $args]} { 2416 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2417 } 2418 cgi_put ">" 2419 uplevel 1 [lindex $args end] 2420 _cgi_close_proc 2421} 2422 2423# like table_data but without eval 2424proc cgi_td {args} { 2425 cgi_put "<td" 2426 2427 if {[llength $args] > 1} { 2428 cgi_put "[_cgi_lrange $args 0 [expr [llength $args]-2]]" 2429 } 2430 cgi_put ">[lindex $args end]</td>" 2431} 2432 2433################################################## 2434# stylesheets - not yet documented 2435################################################## 2436 2437proc cgi_stylesheet {href} { 2438 cgi_puts "<link rel=stylesheet href=\"$href\" type=\"text/css\"/>" 2439} 2440 2441proc cgi_span {args} { 2442 set buf "<span" 2443 foreach a [lrange $args 0 [expr [llength $args]-2]] { 2444 if {[regexp "style=(.*)" $a dummy str]} { 2445 append buf " style=\"$str\"" 2446 } elseif {[regexp "class=(.*)" $a dummy str]} { 2447 append buf " class=\"$str\"" 2448 } else { 2449 append buf " $a" 2450 } 2451 } 2452 return "$buf>[lindex $args end]</span>" 2453} 2454 2455################################################## 2456# frames 2457################################################## 2458 2459proc cgi_frameset {args} { 2460 cgi_head ;# force it out, just in case none 2461 2462 cgi_put "<frameset" 2463 _cgi_close_proc_push "cgi_puts </frameset>" 2464 2465 foreach a [lrange $args 0 [expr [llength $args]-2]] { 2466 if {[regexp "^(rows|cols|onUnload|onLoad|onBlur)=(.*)" $a dummy attr str]} { 2467 cgi_put " $attr=\"$str\"" 2468 } else { 2469 cgi_put " $a" 2470 } 2471 } 2472 cgi_puts ">" 2473 uplevel 1 [lindex $args end] 2474 _cgi_close_proc 2475} 2476 2477proc cgi_frame {namesrc args} { 2478 cgi_put "<frame" 2479 2480 regexp "(\[^=]*)(=?)(.*)" $namesrc dummy name q src 2481 2482 if {$name != ""} { 2483 cgi_put " name=\"$name\"" 2484 } 2485 2486 if {$src != ""} { 2487 cgi_put " src=\"$src\"" 2488 } 2489 2490 foreach a $args { 2491 if {[regexp "^(marginwidth|marginheight|scrolling|onFocus)=(.*)" $a dummy attr str]} { 2492 cgi_put " $attr=\"$str\"" 2493 } else { 2494 cgi_put " $a" 2495 } 2496 } 2497 cgi_puts "/>" 2498} 2499 2500proc cgi_noframes {args} { 2501 cgi_puts "<noframes>" 2502 _cgi_close_proc_push "cgi_puts </noframes>" 2503 uplevel 1 [lindex $args end] 2504 _cgi_close_proc 2505} 2506 2507################################################## 2508# admin support 2509################################################## 2510 2511# mail address of the administrator 2512proc cgi_admin_mail_addr {args} { 2513 global _cgi 2514 2515 if {[llength $args]} { 2516 set _cgi(admin_email) [lindex $args 0] 2517 } 2518 return $_cgi(admin_email) 2519} 2520 2521################################################## 2522# if possible, make each cmd available without cgi_ prefix 2523################################################## 2524 2525if {[info tclversion] >= 7.5} { 2526 foreach _cgi(old) [info procs cgi_*] { 2527 regexp "^cgi_(.*)" $_cgi(old) _cgi(dummy) _cgi(new) 2528 if {[llength [info commands $_cgi(new)]]} continue 2529 interp alias {} $_cgi(new) {} $_cgi(old) 2530 } 2531} else { 2532 foreach _cgi(old) [info procs cgi_*] { 2533 regexp "^cgi_(.*)" $_cgi(old) _cgi(dummy) _cgi(new) 2534 if {[llength [info commands $_cgi(new)]]} continue 2535 proc $_cgi(new) {args} "uplevel 1 $_cgi(old) \$args" 2536 } 2537} 2538 2539################################################## 2540# internal utilities 2541################################################## 2542 2543# undo Tcl's quoting due to list protection 2544# This leaves a space at the beginning if the string is non-null 2545# but this is always desirable in the HTML context in which it is called 2546# and the resulting HTML looks more readable. 2547# (It makes the Tcl callers a little less readable - however, there aren't 2548# more than a handful and they're all right here, so we'll live with it.) 2549proc _cgi_list_to_string {list} { 2550 set string "" 2551 foreach l $list { 2552 append string " $l" 2553 } 2554 # remove first space if possible 2555 # regexp "^ ?(.*)" $string dummy string 2556 return $string 2557} 2558 2559# do lrange but return as string 2560# needed for stuff like: cgi_puts "[_cgi_lrange $args ...] 2561# Like _cgi_list_to_string, also returns string with initial blank if non-null 2562proc _cgi_lrange {list i1 i2} { 2563 _cgi_list_to_string [lrange $list $i1 $i2] 2564} 2565 2566################################################## 2567# temporary file procedures 2568################################################## 2569 2570# set appropriate temporary file modes 2571proc cgi_tmpfile_permissions {{mode ""}} { 2572 global _cgi 2573 2574 if {[string length $mode]} { 2575 set _cgi(tmpperms) $mode 2576 } 2577 2578 return $_cgi(tmpperms) 2579} 2580 2581################################################## 2582# user-defined procedures 2583################################################## 2584 2585# User-defined procedure called immediately after <body> 2586# Good mechanism for controlling things such as if all of your pages 2587# start with the same graphic or other boilerplate. 2588proc app_body_start {} {} 2589 2590# User-defined procedure called just before </body> 2591# Good place to generate signature lines, last-updated-by, etc. 2592proc app_body_end {} {} 2593 2594proc cgi_puts {args} { 2595 eval puts $args 2596} 2597 2598# User-defined procedure to generate DOCTYPE declaration 2599proc cgi_doctype {} {} 2600 2601################################################## 2602# do some initialization 2603################################################## 2604 2605# cgi_init initializes to a known state. 2606 2607proc cgi_init {} { 2608 global _cgi 2609 unset _cgi 2610 2611 # set explicitly for speed 2612 set _cgi(debug) -off 2613 set _cgi(buffer_nl) "\n" 2614 2615 cgi_name "" 2616 cgi_root "" 2617 cgi_body_args "" 2618 cgi_file_limit 10 100000000 2619 2620 if {[info tclversion] >= 8.1} { 2621 # set initial urlencoding 2622 if { [lsearch -exact [encoding names] "utf-8"] != -1} { 2623 cgi_urlencoding "utf-8" 2624 } else { 2625 cgi_urlencoding [encoding system] 2626 } 2627 } 2628 2629 # email addr of person responsible for this service 2630 cgi_admin_mail_addr "root" ;# you should override this! 2631 2632 # most services won't have an actual email addr 2633 cgi_mail_addr "CGI script - do not reply" 2634} 2635cgi_init 2636 2637# deduce tmp directory 2638switch $tcl_platform(platform) { 2639 unix { 2640 set _cgi(tmpdir) /tmp 2641 set _cgi(tmpperms) 0644 2642 set _cgi(sendmail) [list /usr/lib/sendmail /usr/sbin/sendmail] 2643 } macintosh { 2644 set _cgi(tmpdir) [pwd] 2645 set _cgi(tmpperms) {} 2646 set _cgi(sendmail) {} 2647 } default { 2648 set _cgi(tmpdir) [pwd] 2649 catch {set _cgi(tmpdir) $env(TMP)} 2650 catch {set _cgi(tmpdir) $env(TEMP)} 2651 set _cgi(tmpperms) {} 2652 set _cgi(sendmail) {} 2653 } 2654} 2655 2656# regexp for matching attr=val 2657set _cgi(attr,regexp) "^(\[^=]*)=(\[^\"].*)" 2658 2659package provide cgi 1.10.0 2660