Check-in [5f5005e0b0]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Added basic upload support Moved private procedures into rivet namespace
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:5f5005e0b09b59db479e2687babb587842fa015f
User & Date: rkeene 2011-05-20 23:07:43
Context
2011-06-12
21:07
Updated to correctly handle requests with no headers check-in: 6d69837cca user: rkeene tags: trunk
2011-05-20
23:07
Added basic upload support Moved private procedures into rivet namespace check-in: 5f5005e0b0 user: rkeene tags: trunk
23:06
Fixed parsing error check-in: 91ed5a77fe user: rkeene tags: trunk
Changes

Changes to packages/tclrivet/tclrivet.tcl.

38
39
40
41
42
43
44












45
46
47
48
49
50
51
...
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
...
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
...
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353

354
355
356


357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
...
444
445
446
447
448
449
450





















































































































































































451
452
453
454
455
456
457
...
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
...
527
528
529
530
531
532
533
534



































































535
536
537
538
539
    close $fl
}

namespace eval rivet {}
namespace eval rivet {
	proc ::rivet::reset {} {
		unset -nocomplain ::rivet::header_pairs ::rivet::statuscode ::rivet::header_redirect ::rivet::cache_vars ::rivet::cache_vars_qs ::rivet::cache_vars_post ::rivet::cache_vars_contenttype ::rivet::cache_vars_contenttype_var ::rivet::cache_tmpdir













		array set ::rivet::header_pairs {}
		set ::rivet::header_type "text/html"
		set ::rivet::header_sent 0
		set ::rivet::output_buffer ""
		set ::rivet::send_no_content 0

................................................................................
	if {!$::rivet::header_sent} {
		set ::rivet::header_sent 1

		if {![info exists ::rivet::statuscode]} {
			set ::rivet::statuscode 200
		}

		rivet_cgi_server_writehttpheader $::rivet::statuscode

		if {![info exists ::rivet::header_redirect]} {
			tcl_puts $outchan "Content-type: $::rivet::header_type"
			foreach {var val} [array get ::rivet::header_pairs] {
				tcl_puts $outchan "$var: $val"
			}
		} else {
................................................................................
		set incoming_errorInfo $errorInfo
	} else {
		set incoming_errorInfo "<<NO ERROR>>"
	}

	if {!$::rivet::header_sent} {
		set ::rivet::header_sent 1
		rivet_cgi_server_writehttpheader 200
		tcl_puts $outchan "Content-type: text/html"
		tcl_puts $outchan ""
	}

	set uidprefix ""
	catch {
		package require Tclx
................................................................................
		}
	}
}

rename puts tcl_puts
rename rivet_puts puts

proc dehexcode {val} {
        set val [string map [list "+" " "] $val]
        foreach pt [split $val %] {
                if {![info exists rval]} { set rval $pt; continue }
		set char [binary format c 0x[string range $pt 0 1]]
                append rval "$char[string range $pt 2 end]"
        }
        if {![info exists rval]} { set rval "" }
................................................................................
}

proc var_qs args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [_var get $cmd $var $defval]
}

proc var_post args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [_var post $cmd $var $defval]
}

proc var args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [_var all $cmd $var $defval]
}

proc _var args {
	set inchan stdin
	if {[info exists ::env(RIVET_INTERFACE)]} {
		set inchan [lindex $::env(RIVET_INTERFACE) 1]
	}

	if {![info exists ::rivet::cache_vars]} {
		global env
................................................................................
				if {[info exists ::rivet::cache_vars_contenttype_var(boundary)]} {
					# Create temporary directory
					if {[info exists env(TMPDIR)]} {
						set tmpdir $env(TMPDIR)
					} else {
						set tmpdir "/tmp"
					}
					set ::rivet::cache_tmpdir [file join $tmpdir rivet-upload-[pid][expr rand()]]
					catch {
						file mkdir $::rivet::cache_tmpdir
						file attributes $::rivet::cache_tmpdir -permissions 0700
					}

					# Copy stdin to file in temporary directory
					set tmpfile [file join $::rivet::cache_tmpdir $inchan]
					set tmpfd [open $tmpfile w]
					fconfigure $tmpfd -translation [list binary binary]

					fconfigure $inchan -translation [list binary binary]
					fcopy $inchan $tmpfd
					close $tmpfd



					# Split out everything with a content-type into a seperate file, noting this for "upload" to handle
					# Everything else put in "vars_post"
					set tmpfd [open $tmpfile r]
					while 1 {
						gets $tmpfd line
						if {[eof $tmpfd]} {
							break
						}
					}
					close $tmpfd

					# Cleanup temporary directory if no files have been saved there, otherwise schedule this cleanup atexit
					catch {
#						file delete -force -- $::rivet::cache_tmpdir
					}
				}
			}
		} else {
			set vars_post ""
		}

		foreach varpair [split $vars_qs &] {
			set varpair [split $varpair =]
			set var [lindex $varpair 0]
			set value [dehexcode [lindex $varpair 1]]
			lappend ::rivet::cache_vars_qs($var) $value
			lappend ::rivet::cache_vars($var) $value
		}
		foreach varpair [split $vars_post &] {
			set varpair [split $varpair =]
			set var [lindex $varpair 0]
			set value [dehexcode [lindex $varpair 1]]
			lappend ::rivet::cache_vars_post($var) $value
			lappend ::rivet::cache_vars($var) $value
		}
	}

	set type [lindex $args 0]
	set cmd [lindex $args 1]
................................................................................

	if {![info exists retval]} {
		return ""
	}

	return $retval
}






















































































































































































proc parse {file} {
	return [eval [rivet::parserivet $file]]
}

proc headers args {
	set cmd [lindex $args 0]
................................................................................
	if {![info exists ::env($var)]} {
		return ""
	}

	return $::env($var)
}
 
proc rivet_cgi_server_writehttpheader {statuscode {useenv ""}} {
	if {$useenv eq ""} {
		upvar ::env env
	} else {
		array set env $useenv
	}

	set outchan stdout
................................................................................
		}
	}

	tcl_puts $outchan "Status: $statuscode [::rivet::statuscode_to_str $statuscode]"
}

proc load_headers args { }




































































# We need to fill these in, of course.

proc makeurl args { return -code error "makeurl not implemented yet"}
proc upload args { return -code error "upload not implemented yet" }
proc virtual_filename args { return -code error "virtual_filename not implemented yet" }







>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|







 







|







 







|







|







|


|







 







|

|
|


|
|
|
<
>
|
|
|
>
>

<
<
|
|
|
|
|


<

|

|










|






|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



<

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
...
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
...
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364

365
366
367
368
369
370
371


372
373
374
375
376
377
378

379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
...
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
...
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796

797
    close $fl
}

namespace eval rivet {}
namespace eval rivet {
	proc ::rivet::reset {} {
		unset -nocomplain ::rivet::header_pairs ::rivet::statuscode ::rivet::header_redirect ::rivet::cache_vars ::rivet::cache_vars_qs ::rivet::cache_vars_post ::rivet::cache_vars_contenttype ::rivet::cache_vars_contenttype_var ::rivet::cache_tmpdir

		if {[info exists ::rivet::cache_uploads]} {
			foreach {var namefd} [array get ::rivet::cache_uploads] {
				set fd [lindex $namefd 1]

				catch {
					close $fd
				}
			}

			unset ::rivet::cache_uploads
		}

		array set ::rivet::header_pairs {}
		set ::rivet::header_type "text/html"
		set ::rivet::header_sent 0
		set ::rivet::output_buffer ""
		set ::rivet::send_no_content 0

................................................................................
	if {!$::rivet::header_sent} {
		set ::rivet::header_sent 1

		if {![info exists ::rivet::statuscode]} {
			set ::rivet::statuscode 200
		}

		::rivet::cgi_server_writehttpheader $::rivet::statuscode

		if {![info exists ::rivet::header_redirect]} {
			tcl_puts $outchan "Content-type: $::rivet::header_type"
			foreach {var val} [array get ::rivet::header_pairs] {
				tcl_puts $outchan "$var: $val"
			}
		} else {
................................................................................
		set incoming_errorInfo $errorInfo
	} else {
		set incoming_errorInfo "<<NO ERROR>>"
	}

	if {!$::rivet::header_sent} {
		set ::rivet::header_sent 1
		::rivet::cgi_server_writehttpheader 200
		tcl_puts $outchan "Content-type: text/html"
		tcl_puts $outchan ""
	}

	set uidprefix ""
	catch {
		package require Tclx
................................................................................
		}
	}
}

rename puts tcl_puts
rename rivet_puts puts

proc ::rivet::dehexcode {val} {
        set val [string map [list "+" " "] $val]
        foreach pt [split $val %] {
                if {![info exists rval]} { set rval $pt; continue }
		set char [binary format c 0x[string range $pt 0 1]]
                append rval "$char[string range $pt 2 end]"
        }
        if {![info exists rval]} { set rval "" }
................................................................................
}

proc var_qs args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [::rivet::_var get $cmd $var $defval]
}

proc var_post args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [::rivet::_var post $cmd $var $defval]
}

proc var args {
	set cmd [lindex $args 0]
	set var [lindex $args 1]
	set defval [lindex $args 2]

	return [::rivet::_var all $cmd $var $defval]
}

proc ::rivet::_var args {
	set inchan stdin
	if {[info exists ::env(RIVET_INTERFACE)]} {
		set inchan [lindex $::env(RIVET_INTERFACE) 1]
	}

	if {![info exists ::rivet::cache_vars]} {
		global env
................................................................................
				if {[info exists ::rivet::cache_vars_contenttype_var(boundary)]} {
					# Create temporary directory
					if {[info exists env(TMPDIR)]} {
						set tmpdir $env(TMPDIR)
					} else {
						set tmpdir "/tmp"
					}
					set cache_tmpdir [file join $tmpdir rivet-upload-[pid][expr rand()]]
					catch {
						file mkdir $cache_tmpdir
						file attributes $cache_tmpdir -permissions 0700
					}

					set vals_and_fds_arr [::rivet::handle_upload $inchan $cache_tmpdir $::rivet::cache_vars_contenttype_var(boundary)]
					array set vals [lindex $vals_and_fds_arr 0]
					array set fds [lindex $vals_and_fds_arr 1]


					foreach var [array names vals] {
						if {[info exists fds($var)]} {
							set fd [lindex $fds($var) 0]
							set contenttype [lindex $fds($var) 1]
							set size [lindex $fds($var) 2]



							set ::rivet::cache_uploads($var) [list $vals($var) $fd $contenttype $size]
						} else {
							set value $vals($var)
							lappend ::rivet::cache_vars_post($var) $value
							lappend ::rivet::cache_vars($var) $value
						}
					}


					# Cleanup temporary directory
					catch {
						file delete -force -- $cache_tmpdir
					}
				}
			}
		} else {
			set vars_post ""
		}

		foreach varpair [split $vars_qs &] {
			set varpair [split $varpair =]
			set var [lindex $varpair 0]
			set value [::rivet::dehexcode [lindex $varpair 1]]
			lappend ::rivet::cache_vars_qs($var) $value
			lappend ::rivet::cache_vars($var) $value
		}
		foreach varpair [split $vars_post &] {
			set varpair [split $varpair =]
			set var [lindex $varpair 0]
			set value [::rivet::dehexcode [lindex $varpair 1]]
			lappend ::rivet::cache_vars_post($var) $value
			lappend ::rivet::cache_vars($var) $value
		}
	}

	set type [lindex $args 0]
	set cmd [lindex $args 1]
................................................................................

	if {![info exists retval]} {
		return ""
	}

	return $retval
}

proc ::rivet::handle_upload {fd workdir seperator} {
	array set args {}
	array set argsfd {}

	set seperator "--${seperator}"

	# Select random base name for temporary files
	set basename_tmpfile [file join "$workdir" "upload-[expr rand()][expr rand()][expr rand()]"]

	# Configure fd
	fconfigure $fd -translation binary

	# Process fd into files or arguments
	set nextblock "\015\012[read $fd 1024]"
	set line_oriented 0
	set next_line_oriented 0
	set idx 0
	while 1 {
		if {[string length $nextblock] == 0 && [eof $fd]} {
			break
		}

		set block $nextblock
		set nextblock [read $fd 1024]
		set bigblock "${block}${nextblock}"

		if {$next_line_oriented} {
			set line_oriented 1

			set next_line_oriented 0
		}

		set blockend -1
		while 1 {
			set blockend [string first "\015\012$seperator\015\012" $bigblock [expr {$blockend + 1}]]

			if {$blockend == -1} {
				set blockend [string first "\015\012${seperator}--\015\012" $bigblock [expr {$blockend + 1}]]
			}

			if {$blockend == -1} {
				break
			}

			if {($blockend + [string length $seperator] + 4) >= [string length $block]} {
				break
			}

			set nextblockstart_idx [expr {$blockend + [string length $seperator] + 4}]
			set nextblockstart [string range $block $nextblockstart_idx end]

			set nextblock "$nextblockstart$nextblock"
			set block [string range $block 0 [expr {$blockend-1}]]

			set next_line_oriented 1
		}

		while {$line_oriented} {
			set line_end [string first "\015\012" $block 0]
			if {$line_end == -1} {
				append line $block

				break
			}

			append line [string range $block 0 [expr {$line_end - 1}]]
			set block "[string range $block [expr {$line_end + 2}] end]"

			if {$line == ""} {
				unset -nocomplain name tmpfile filename

				if {[info exists lineinfo([list content-disposition name])]} {
					set name $lineinfo([list content-disposition name])
				}

				if {[info exists lineinfo([list content-disposition filename])]} {
					set filename $lineinfo([list content-disposition filename])
				}

				if {[info exists outfd]} {
					close $outfd

					unset outfd
				}

				set appendmode "var"
				if {[info exists lineinfo(content-type)]} {
					# We have data that should be stored in a file
					set tmpfile "${basename_tmpfile}-${idx}"
					incr idx

					set outfd [open $tmpfile "w"]
					fconfigure $outfd -translation binary

					set contenttype $lineinfo(content-type)

					if {[info exists name]} {
						set tmpfd [open $tmpfile "r"]
						fconfigure $tmpfd -translation binary

						set argsfd($name) [list $tmpfd $contenttype]

						if {![info exists filename]} {
							set args($name) ""
						} else {
							set args($name) $filename
						}

						set appendmode "file"
					}
				}

				if {[info exists tmpfile]} {
					file delete -- $tmpfile
				}

				unset -nocomplain lineinfo

				set line_oriented 0

				continue
			}

			set work [split $line ":"]

			set cmd [lindex $work 0]
			set cmd [string trim [string tolower $cmd]]

			set value [string trim [join [lrange $work 1 end] ":"]]

			set work [split $value ";"]
			set value [lindex $work 0]

			set lineinfo([list $cmd]) $value

			foreach part [lrange $work 1 end] {
				set part [string trim $part]

				set partwork [split $part "="]

				set partvar [lindex $partwork 0]
				set partval [join [lrange $partwork 1 end] "="]

				if {[string index $partval 0] == "\"" && [string index $partval end] == "\""} {
					set partval [string range $partval 1 end-1]
				}

				set lineinfo([list $cmd $partvar]) $partval
			}

			set line ""
		}

		if {![info exists appendmode]} {
			continue
		}

		switch -- $appendmode {
			"file" {
				puts -nonewline $outfd $block
			}
			"var" {
				if {[info exists name]} {
					append args($name) $block
				}
			}
		}
	}

	foreach var [array names argsfd] {
		set fd [lindex $argsfd($var) 0]
		seek $fd 0 end

		lappend argsfd($var) [tell $fd]

		seek $fd 0 start
	}

	return [list [array get args] [array get argsfd]]
}

proc parse {file} {
	return [eval [rivet::parserivet $file]]
}

proc headers args {
	set cmd [lindex $args 0]
................................................................................
	if {![info exists ::env($var)]} {
		return ""
	}

	return $::env($var)
}
 
proc ::rivet::cgi_server_writehttpheader {statuscode {useenv ""}} {
	if {$useenv eq ""} {
		upvar ::env env
	} else {
		array set env $useenv
	}

	set outchan stdout
................................................................................
		}
	}

	tcl_puts $outchan "Status: $statuscode [::rivet::statuscode_to_str $statuscode]"
}

proc load_headers args { }

proc upload args {
	set cmd [lindex $args 0]
	set name [lindex $args 1]
	set filename [lindex $args 2]

	# Ensure that we have processed arguments
	::rivet::_var post number

	switch -- $cmd {
		channel {
			set fd [lindex $::rivet::cache_uploads($name) 1]

			return $fd
		}
		save {
			set fd [lindex $::rivet::cache_uploads($name) 1]
			set ofd [open $filename "w"]
			fconfigure $ofd -translation binary

			set start [tell $fd]
			seek $fd 0 start

			fcopy $fd $ofd

			seek $fd $start start

			close $ofd
		}
		data {
			set fd [lindex $::rivet::cache_uploads($name) 1]

			set start [tell $fd]
			seek $fd 0 start

			set retval [read $fd]

			seek $fd $start start

			return $fd
		}
		exists {
			return [info exists ::rivet::cache_uploads($name)]
		}
		size {
			set size [lindex $::rivet::cache_uploads($name) 3]

			return $size
		}
		type {
			set type [lindex $::rivet::cache_uploads($name) 2]

			return $type
		}
		filename {
			set remote_filename [lindex $::rivet::cache_uploads($name) 0]

			return $remote_filename
		}
		tempname {
			return -code error "\"upload tempname\" not implemented"
		}
		names {
			return [array names ::rivet::cache_uploads]
		}
	}
}

# We need to fill these in, of course.

proc makeurl args { return -code error "makeurl not implemented yet"}

proc virtual_filename args { return -code error "virtual_filename not implemented yet" }

Changes to rivet-starkit/main.tcl.

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
...
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
		}
	}
	
	# Check for file existance
	if {![file exists $targetfile]} {
		if {$targetfile == "__RIVETSTARKIT_FORBIDDEN__"} {
			# Return a 403 (Forbidden)
			rivet_cgi_server_writehttpheader 403 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>Forbidden</title></head><body><h1>File Access Forbidden</h1></body>"
		} elseif {[file tail $targetfile] == "__RIVETSTARKIT_INDEX__"} {
			# Return a 403 (Forbidden)
			rivet_cgi_server_writehttpheader 403 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>Directory Listing Forbidden</title></head><body><h1>Directory Listing Forbidden</h1></body>"
		} else {
			# Return a 404 (File Not Found)
			rivet_cgi_server_writehttpheader 404 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>File Not Found</title></head><body><h1>File Not Found</h1></body>"
		}

		return
	}
................................................................................
		default {
			set statictype "application/octet-stream"
		}
	}
	
	# Dump static files
	if {[info exists statictype]} {
		rivet_cgi_server_writehttpheader 200 [array get env]
		tcl_puts $outchan "Content-type: $statictype"
		catch {
			tcl_puts $outchan "Last-Modified: [clock format [file mtime $targetfile] -format {%a, %d %b %Y %H:%M:%S GMT} -gmt 1]"
			tcl_puts $outchan "Expires: Tue, 19 Jan 2038 03:14:07 GMT"
		}
		catch {
			tcl_puts $outchan "Content-Length: [file size $targetfile]"







|





|





|







 







|







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
...
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
		}
	}
	
	# Check for file existance
	if {![file exists $targetfile]} {
		if {$targetfile == "__RIVETSTARKIT_FORBIDDEN__"} {
			# Return a 403 (Forbidden)
			::rivet::cgi_server_writehttpheader 403 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>Forbidden</title></head><body><h1>File Access Forbidden</h1></body>"
		} elseif {[file tail $targetfile] == "__RIVETSTARKIT_INDEX__"} {
			# Return a 403 (Forbidden)
			::rivet::cgi_server_writehttpheader 403 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>Directory Listing Forbidden</title></head><body><h1>Directory Listing Forbidden</h1></body>"
		} else {
			# Return a 404 (File Not Found)
			::rivet::cgi_server_writehttpheader 404 [array get env]
			tcl_puts $outchan "Content-type: text/html"
			tcl_puts $outchan ""
			tcl_puts $outchan "<html><head><title>File Not Found</title></head><body><h1>File Not Found</h1></body>"
		}

		return
	}
................................................................................
		default {
			set statictype "application/octet-stream"
		}
	}
	
	# Dump static files
	if {[info exists statictype]} {
		::rivet::cgi_server_writehttpheader 200 [array get env]
		tcl_puts $outchan "Content-type: $statictype"
		catch {
			tcl_puts $outchan "Last-Modified: [clock format [file mtime $targetfile] -format {%a, %d %b %Y %H:%M:%S GMT} -gmt 1]"
			tcl_puts $outchan "Expires: Tue, 19 Jan 2038 03:14:07 GMT"
		}
		catch {
			tcl_puts $outchan "Content-Length: [file size $targetfile]"