# -*- tcl -*- $Id: Access.xotcl,v 1.9 2007/08/14 16:38:26 neumann Exp $

set httpAccessVersion 1.0
package provide xotcl::comm::httpAccess $httpAccessVersion

package require -exact xotcl::comm::pcache 1.0
package require -exact xotcl::comm::mime 1.0
package require -exact xotcl::comm::connection 1.0
package require -exact xotcl::trace 1.0

package require XOTcl 1

namespace eval ::xotcl::comm::httpAccess {
    namespace import ::xotcl::*

    variable os
    variable VERSION

    if {[catch {set os [exec uname -sr]}]} {
	if {[catch {set os [exec uname -a]}]} { set os unknownOS }
    }
    if {![info exists VERSION]} { set VERSION 1.0 }


    # assert is used only for invariants in the source
    proc assert {f r} {
	set got [eval $f]
	if {$got != $r} {
	    puts stderr "assertion failed: \[$f\] == $r (got $got)"
	}
    }


    # resolve and checkUrl implement URL handling (primarily completion)
    proc checkUrl {url} {
	#puts stderr "checkUrl: <$url>"
	if {![regexp {^([^:]+:/)/([^/]+)(/.*)?$} $url _ scheme network path]} {
	    regexp {^([^:]+:)(/.*)?$} $url _ scheme path
	}
	if {![info exists path]} {
	    # no access scheme provided, try some heuristics...
	    if {[regexp {^[./]} $url]} {
		# expand leading "." to pwd
		if {[regexp {^\.(.*)$} $url _ url]} { set url [pwd]$url }
		return file:$url
	    } else {
		set url http://$url
		regsub {///$} $url // url
		return $url
	    }
	}
	if {$path eq ""} {set path /}
	return [expr {[info exists network] ?
		      "$scheme/$network$path" : "$scheme$path"}]
    }

    # resolving a relative url with respect to a base url (based on rfc 1808)
    proc resolve {rel base {childTagName ""}} {
	if {$base eq ""}    { return [checkUrl $rel] }
	if {$rel eq ""}     { return $base }
	if {[regexp {^[^:]+:/} $rel _]} { return [checkUrl $rel] }
	if {![regexp {^([^:]+:/)/([^/]+)(/.*)$} $base _ baseScheme baseNetwork basePath]} {
	    regexp {^([^:]+:)(/.*)$} $base _ baseScheme basePath
	} elseif {[regexp {^[^:/]+:} $rel]} {
	    return $rel
	}
	regexp {^(.*)\#.*$} $basePath _ basePath
	regexp {^(.*)[?].*$} $basePath _ basePath
	if {[regexp {^//([^/]+)/(.*)$} $rel _ relNetwork relPath]} {
	    return $baseScheme/$relNetwork/$relPath
	}
	if {[info exists baseNetwork]} {
	    append baseScheme /$baseNetwork
	}
	#puts stderr rel=<$rel>
	if {![string match "/*" $rel]} {
	    #puts stderr rel<$rel>base<$basePath>
	    if {[string match \#* $rel]} {
		set r $basePath$rel
	    } elseif {![regsub {/([^/]*)$} $basePath /$rel r]} {
		set r /$rel
	    }
	    while {[regsub -all {/\./} $r / r]} {}
	    regsub {/\.$} $r / r
	    while {[regsub -all {/[^/.]+/\.\./} $r / r]} {}
	    # remove leading /../ (netscapes navigator does this)
	    while {[regsub {^/\.\./} $r / r]} {}
	    set rel $r
	    #puts stderr finalrel<$r>
	}
	#if {$childTagName ne ""} 
	if {1} {
	    upvar 1 $childTagName childTag 
	    catch {unset childTag}
	    if {[regexp {^(.+)\#(.+)$} $rel _ rel childTag]} {
		#puts stderr childTag=$childTag,url=$baseScheme$rel.
	    }
	}
	return $baseScheme$rel
    }

    assert {resolve "" http://mohegan/} http://mohegan/
    assert {resolve http://mohegan/ ""} http://mohegan/
    assert {resolve http://mohegan ""} http://mohegan/
    assert {resolve http://mohegan/ http://nestroy} http://mohegan/
    assert {resolve test.html http://127.0.0.1/} http://127.0.0.1/test.html
    assert {resolve test http://nestroy/} http://nestroy/test
    assert {resolve test file:/u/neumann/old} file:/u/neumann/test
    assert {resolve /test http://nestroy/} http://nestroy/test
    assert {resolve //mohegan/index.html http://nestroy/} http://mohegan/index.html
    assert {resolve //mohegan/index.html http://nestroy/} http://mohegan/index.html
    assert {resolve index.html http://nestroy/dir/} http://nestroy/dir/index.html
    assert {resolve ./index.html http://nestroy/dir/} http://nestroy/dir/index.html
    assert {resolve ././index.html http://nestroy/dir/} http://nestroy/dir/index.html
    assert {resolve ../index.html http://nestroy/dir/} http://nestroy/index.html
    assert {resolve ../../index.html http://nestroy/dir/} http://nestroy/index.html
    assert {resolve ../../../test file:/u/neumann/old} file:/test
    assert {resolve newdir/  http://nestroy/dir/} http://nestroy/dir/newdir/
    assert {resolve /newdir/  http://nestroy/dir/} http://nestroy/newdir/
    assert {resolve file:/u/neumann/ice.html ""} file:/u/neumann/ice.html
    assert {resolve \#label http://nestroy/h.html} http://nestroy/h.html
    assert {resolve mailto:user@host http://nestroy/h.html} mailto:user@host
    assert {resolve ../../../../mis2001/folien/081101.ppt  http://wwwi.wu-wien.ac.at/Studium/Abschnitt_2/LVA_ws01/IT/index.html} http://wwwi.wu-wien.ac.at/mis2001/folien/081101.ppt


    ##############################################################
    # Every object of class Access informs the (possibly empty) list of 
    # informObjects during its lifecycle with the following messages
    #   
    #   startCb:   when the request for the object is created
    #              and was classified and initialized
    #
    #   notifyCb:  when the type of the incming data is dertermined
    #              (typically after parsing the http header)
    #
    #   incCb:     when new data is available
    #
    #   endCb:     when the request is finished sucessfully and the object
    #              is going to be destroyed
    #
    #   cancelCb:  when the request is finished nonsucessfully and the object
    #              is going to be destroyed
    #
    # All these messages receive the name of the Access object
    # as first parameter, incCb has two more arguments (totalsize 
    # and currentsize) 

    # caching:
    # 0 no caching
    # 1 volatile cache (does not check cache, keeps spool file for back button)
    # 2 persistent cache

    Class Access -parameter {
	{method GET} {blocking 0} {httpVersion 1.1} {headers {}} 
	url query data contentType path caching sinkClass parentUrl
    }
    Access instproc informObject x {
	foreach i $x { my lappend informObjects $i }
    }
    Access instproc method x {
			      my set method [string toupper $x]
			  }
    Access instproc unknown {m args} {
	error "Method '$m' with args '$args' is unknown for [self class]"
    }

    Access proc checkRunning {} {
	foreach u [my array names running] {
	    puts stderr "... running: $u"
	}
	puts stderr "... -----------"
    }
    Access proc createRequest args {
	#my showCall
	set informobject {}
	set allowJoin 1
	set nargs {}
	foreach {a v} $args {
	    switch -exact -- $a {
		-url           {set url $v;          lappend nargs $a $v}
		-informObject  {set informobject $v; lappend nargs $a $v}
		-parentRequest {set parentRequest $v}
		-allowJoin     {set allowJoin $v}
		default        {lappend nargs $a $v}
	    }
	}
	#if {[my array exists running]} {parray [self]::running}
	if {[my exists running($url)] && $allowJoin} {
	    my showMsg "we can join running($url)=[my set running($url)]"
	    # we can join the request.
	    # requests are joined by adding the informobjects to
	    # the existing request
	    set token [my set running($url)]
	    # delegation to a different request works only so easy
	    # when loading to a file...
	    $token informObject $informobject
	    return $token
	} else {
	    set token [my autoname ::req]
	    if {[info exists parentRequest]} {
		set token ${parentRequest}$token
	    } 
	    #my showMsg "TOKEN = $token $url"
	    return [eval my create $token $nargs]
	}
    }
    Access instproc running {} {
	#my showCall
	[self class] set running([my url]) [self]
    }
    Access instproc stop {} {
	#showCall
	my instvar url
	if {[[self class] exists running($url)]} {
	    #puts stderr "my unset [[self class] set running($url)]
	    [self class] unset running($url)
	    #if {[my array exists running]} { parray [self class]::running }
	} else {
	    #puts stderr "not running($url)"
	    #if {[my array exists running]} { parray [self class]::running }
	}
    }
    #Access instproc destroy args {
    #  my showCall
    #  next
    #}
    Access instproc init args {
	#my showCall
	my instvar method url
	if {![my exists informObjects]} {
	    my set informObjects {}
	}
	next
	if {![my exists caching]} { 
	    if {$method eq "GET"} {
		set defaultCaching 2
	    } else {
		set defaultCaching 1
	    }
	    my caching $defaultCaching
	}
	#my showVars

	set url [string trim $url]
	my initialize
	if {[my classify $url]} {
	    #my showVars
	    # now inform all interested objects that we have the object
	    my doCallbacks startCb
	    #my showVars blocking
	    # trigger the transfer... (might be blocking or not)
	    my $method
	    if {![my exists finished]} {
		# the request is not finished
		if {[my blocking]} {
		    #my showMsg "waiting"
		    my vwait finished
		    #my showMsg "waiting DONE"
		}
	    }
	}
    }
    Access instproc getContent {} {
	[my set sink] content
    }

    #########################################
    Access instproc timeout t {
	my set timeout [::after $t [self] timeoutOccured]
    }
    Access instproc timeoutOccured {} {
	#my showCall
	my unset timeout
	my abort "timeout exceeded"
    }
    Access instproc timeoutFinish {} {
	if {[my exists timeout]} {
	    after cancel [my set timeout]
	    my unset timeout
	}
    }
    #########################################

    Access instproc initialize {} {
	#my showCall
	my set state           0
	my set meta            {}
	my set currentsize     0
	my set totalsize       0
    }
    Access instproc classify {url} {
	my instvar host path port method
	#my showVars caching
	if {[my caching] > 1 && [persistentCache isValidated $url]} {
	    #puts stderr "*** cacheable && validated"
	    #showVars
	    #persistentCache dump
	    set access CacheAccess
	} elseif {[regexp -nocase {^http://([^/:]+)(:([0-9]+))?(/.*)?$} $url \
		       _ host y givenPort path]} {
	    if {$givenPort ne ""} {set port $givenPort } {set port 80}
	    switch -exact $method {
		PROPFIND -
		PROPPATCH -
		COPY -
		MKCOL -
		MOVE -
		LOCK -
		UNLOCK {
		    package require xotcl::comm::dav
		    set access Dav
		}
		default {set access Http}
	    }
	} elseif {[regexp -nocase {^https://([^/:]+)(:([0-9]+))?(/.*)$} $url \
		       _ host y givenPort path]} {
	    if {$givenPort ne ""} {set port $givenPort } {set port 443}
	    set access Https
	} elseif {[regexp -nocase {^file:(.*)$} $url _ path]} {
	    set access File
	} elseif {[regexp -nocase {^ftp://([^/]+)/(.*)$} $url _ host path]} {
	    package require -exact xotcl::comm::ftp 0.9
	    set access Ftp
	} elseif {[regexp -nocase {^imap://([^/]+)/(.*)$} $url _ host path]} {
	    package require xotcl::comm::imap
	    set access Imap
	} elseif {[regexp -nocase {^cmd:/(.*)$} $url _ path]} {
	    set access xotcl::Cmd    
	} elseif {[regexp -nocase {^ldap://([^/:]+)?(:([0-9]+))?(/.*)$} \
		       $url _ host y givenPort path]} {
	    if {$givenPort ne ""} { set port $givenPort }
	    my showMsg "*** ldap://<$host>:<$port>/<$path>"
	    package require xotcl::comm::ldap
	    set access Ldap  
	} else {
	    #my set state 0
	    my abort "Unsupported URL: '$url'"
	    return 0
	}
	my class $access
	#my showMsg "class of request = $access"
	return 1
    }
    Access instproc revisit {} {
	my class ReAccess
	my initialize
	my [my set method]
    }
    ### dummy stubs for 'close' and 'GET' for error cases
    Access instproc close {} {
    }
    Access instproc GET {} {
	if {[my exists errormsg]} { ;# the error was already reportet
	    my finish
	} else {
	    my abort "unknown error"
	}
    }

    Access instproc headerDone {} {
	my instvar caching sink
	if {[my exists ignoreBody]} return
	if {[my exists sinkClass] && $caching>0 } {
	    error "can´t set both sinkclass and caching"
	}
	switch $caching {
	    2 {
		set sink [CacheFileSink create [self]::cfs]
		#my showMsg "[self class] result goes to cache"
		$sink notifyCb [self]
	    }
	    1 {
		set sink [CacheFileSink create [self]::cfs -persistent 0]
		#my showMsg "result goes to volatile cache"
		$sink notifyCb [self]
	    }
	    0 {
		if {[my exists sinkClass]} {
		    set sink [[my sinkClass] create [self]::s]
		}
	    }
	}
	my doCallbacks notifyCb
    }
    Access instproc mkSpoolFile {{name ""}} {
	if {![my exists fileName]} {
	    my set fileName [persistentCache newEntry [my url] [self] [my caching] $name]
	}
    }
    Access instproc block {} {
	my set block
    }
    Access instproc kill {} {
	my showCall
	my set state -1; #interrupted
	my finish
    }
    Access instproc abort {msg} {
	#my showCall
	#my showVars
	my instvar state errormsg
	if {[catch {::printError "[self] ($state): $msg"} err]} {
	    puts stderr "\n$err\nERROR [self] ($state): $msg\n"
	}
	#my set error [list $msg $::errorInfo $::errorCode]
	my caching 0
	if {[my exists ignoreBody]} {
	    my unset ignoreBody
	}
	set state -2 ;# error
	set errormsg $msg
	my finish
    }
    Access instproc finish {} {
	#my showCall
	my timeoutFinish
	my close
	#my showVars state ignoreBody
	# state is "interrupted" or "error"
	if {[my set state] < 0} {
	    set action cancelCb
	    set success 0
	} else {
	    set action endCb
	    #set state ok
	    set success 1
	}
	if {[my exists ignoreBody]} {
	    my stop
	    #my set finished $success
	    set cmd [my set ignoreBody]
	    my unset ignoreBody
	    #my showMsg "executing... <$cmd>"
	    eval my $cmd
	} else {
	    if {[my exists sink]} {
		[my set sink] $action [self]
	    }
	    #catch {after cancel $after} ;# ????
	    my doCallbacks $action
	    my stop
	    my set finished $success
	}
    }
    Access instproc eof {} {
	#my showCall
	#my showMsg "c [my set currentsize]== t [[self set totalsize]]"
	#my set state eof
	my finish
    }
    Access instproc doCallbacks {cb} {
	#my showCall
	if {[my exists ignoreBody]} {
	    my showMsg "ignoring callback"
	} else {
	    foreach obj [my set informObjects] {
		#my showMsg "*** $obj $cb [self]"
		#puts stderr "------------ calling: $obj $cb [self]"
		if {[catch {$obj $cb [self]} errormsg]} {
		    puts stderr "--------------$cb:errormsg=$errormsg, \
		     errorInfo=$::errorInfo, \
		     errorCode=$::errorCode."
		}
		#puts stderr "------------ calling DONE: $obj $cb [self]"
	    }
	}
    }
    Access instproc shutdown {} {
	#my showMsg "state=[my set state] > 3"
	if {[my set state] > 3} {
	    my set mustDestroy 1
	} else {
	    #my showVars
	    #my showStack
	    #my showMsg "DESTROY !!!"
	    if {[my set state] > -2} {
		my destroy
	    }
	}
    }


    Class FileAccess -superclass Access
    FileAccess instproc initialize args {
	my caching 0
	next
    }
    FileAccess instproc close {} {
    }
    FileAccess instproc block {} {
	my showTimeStart
	set S [open [my set fileName] r]
	fconfigure $S -translation binary
	set block [::read $S]
	::close $S
	my showTimeEnd
	return $block
    }
    FileAccess instproc GET {} {
	my instvar path response totalsize currentsize \
	    fileName informObjects
	set fileName $path
	set totalsize [file size $path]
	set response "file 200 OK"
	my headerDone
	my set state 4
	set currentsize $totalsize
	#my showVars informObjects 
	foreach obj $informObjects {
	    $obj incCb [self] $totalsize $currentsize
	}
	my eof
    }


    Class File -superclass FileAccess
    File instproc GET {} {
	my instvar path
	#puts stderr path=$path,exists=[file exists $path]
	if {![file exists $path]} {
	    my abort "No such file '$path'"
	    return
	}
	if {![my exists contentType]} {
	    my contentType [Mime guessContentType $path]
	}
	my set sink [FileSink create [self]::fas -fileName $path]
	#puts stderr ****sink=$sink,[$sink info class]
	#puts stderr "*** before next ([self class])"
	next
	#puts stderr "*** after next ([self class])"
    }

    Class CacheAccess -superclass File
    CacheAccess instproc GET {} {
	my instvar url
	my path         [persistentCache cacheFileName $url]
	my contentType  [persistentCache contentType $url]
	my set meta     [persistentCache meta $url]
	next
    }

    Class ReAccess -superclass File
    ReAccess instproc GET {} {
	my instvar fileName sink
	my set block       ""
	my set currentsize 0
	my caching     0
	if {![info exists fileName]} {
	    set fileName [$sink set fileName]
	}
	my set path $fileName
	next
    }



    Class Cmd -superclass Access
    Cmd instproc init args {
	if {![my exists caching]} {
	    my caching 0
	}
	next
    }
    Cmd instproc GET {} {
	my instvar path block totalsize currentsize \
	    response informObjects state
	if {[catch {set block [eval $path]} error]} {
	    my contentType text/plain
	    set block $error
	} else {
	    my contentType text/html
	}
	set totalsize [string length $block]
	set response "cmd 200 OK"
	my headerDone
	my set state 4
	set currentsize $totalsize
	foreach obj $informObjects {
	    $obj incCb [self] $totalsize $currentsize
	    #$obj endCb [self]
	}
	#set state eof
	my finish
    }
    Cmd instproc block args {
	my instvar block
	return $block
    }


    #Class NetAccess -superclass Access -parameter {host port}
    Class NetAccess -superclass Access
    NetAccess instproc initialize args {
	if {![my exists blocksize]} {my set blocksize 512}
	my set readMethod read
	next
    }
    NetAccess instproc read {} {
	#my instvar S blocksize block
	#set block [::read $S $blocksize]
	my instvar S block blocksize
	set block [::read $S $blocksize]
    }
    NetAccess instproc gzread {} {
	my instvar S Z blocksize block
	puts -nonewline $Z [::read $S $blocksize]
	set block [::read $Z]
	#puts stderr len=[string length $block],block=<$block>
    }
    NetAccess instproc gzopen {} {
	my instvar Z S readMethod
	requireModules {zipchan libzipchan.so}
	fconfigure $S -translation binary
	set Z [zipchan]
	set readMethod gzread
    }
    NetAccess instproc close {} {
	#my showMsg "**** close persistent=[my exists persistent]"
	if {![my exists persistent]} {
	    foreach stream {S Z} {
		if {[my exists $stream]} {
		    ::close [my set $stream]
		    my unset $stream
		}
	    }
	}
	my stop
    }
    NetAccess instproc hold {} {
	my instvar S
	$S hold
    }
    NetAccess instproc resume {} {
	my instvar S
	$S resume
    }
    NetAccess instproc readData {} {
	#my showCall
	if {[catch {
	    #puts stderr "??????????????readMethod=[my set readMethod]"
	    my [my set readMethod]
	    my pushBlock
	} err]} {
	    puts stderr ERR=$err
	    my set block ""
	    my abort $err
	}
    }
    NetAccess instproc pushBlock {} {
	#my showCall
	my instvar block
	if {[set n [string length $block]]} {
	    my instvar currentsize totalsize informObjects sink
	    #my showVars n currentsize totalsize
	    incr currentsize $n
	    if {$currentsize > $totalsize} {
		set totalsize $currentsize
	    }
	    #my showMsg "total=$totalsize current=$currentsize"
	    if {[my exists ignoreBody]} {
		#puts stderr "ignoring: <$block>"
	    } else {
		if {[info exists sink]} {
		    $sink incCb [self] $totalsize $currentsize
		}
		foreach obj $informObjects {
		    #my showMsg "call incbCb $totalsize $currentsize $obj [$obj info class]"
		    $obj incCb [self] $totalsize $currentsize
		    #my showMsg "done incbCb $totalsize $currentsize"
		}
	    }
	} else {
	    #my showVars n
	    return [my eof]
	}
    }

    Class Http -superclass NetAccess  ;###  -parameter {query {httpVersion 1.0}}
    Http set proxyfilter  httpProxyRequired
    Http set proxyhost    {}
    Http set proxyport    {}
    Http set accept       */*
    if {[info exists argv0]} {
	Http set useragent    "[file tail $argv0] httpAccess/$httpAccessVersion xotcl/$::xotcl::version ($os)"
    }
    Http proc proxyfilter {host phostv pportv} {
	my instvar proxyfilter proxyhost proxyport
	upvar \#1 $phostv phost $pportv pport
	switch -- $proxyfilter {
	    httpProxyRequired {
		if {[string length $proxyhost]} {
		    if {![string length $proxyport]} { set proxyport 8080 }
		    set phost $proxyhost
		    set pport $proxyport
		}
	    }
	}
    }

    Http instproc initialize args {
	if {![my exists port]}        {my set port 80}
	if {![my exists httpVersion]} {my set httpVersion 1.1}
	next
	#my showMsg "result queried from net"
    }
    Http instproc GET {} {
	#my showCall
	if {[my exists query]} {
	    my instvar query caching
	    my append url ?$query
	    my append path ?$query
	    if {$caching == 2} {set caching 1}
	    my showVars caching $query
	}
	my open
    }
    Http instproc HEAD {} {
	my open
    }
    Http instproc POST {} {
	# we have query and data only for a uniform interface for
	# forms that cann pass the attribute value pairs always via
	# query.
	if {[my exists query]} {
	    my data [my query]
	}
	my open
    }
    Http instproc PUT {} {  
	my open
    }
    # Http1.1
    Http instproc OPTIONS {} {
	my showCall
	my open  
    }
    # Http1.1
    Http instproc TRACE {} {
	my showCall
    }
    # Http1.1
    Http instproc DELETE {} {
	#my showCall
	my open  
    }
    Http instproc openConnection {host port reuse} {
	return [Connection make [self] $host $port $reuse _]
    }
    Http instproc open {} {
	#my showCall
	my instvar url S state host port path
	if {$state > 0} {
	    puts stderr "... [self]:$proc ignoring request in state $state"
	    return
	}
	Http proxyfilter $host phost pport
	if {[info exists phost] && [string length $phost]} {
	    set usePort $pport
	    set useHost $phost
	    set path $url
	} else {
	    set usePort $port
	    set useHost $host
	}

	set S [my openConnection $useHost $usePort [expr {[my httpVersion]>1.0}]]
	if {[$S exists error]} {
	    my abort [$S set error]
	    return
	}
	set state 2
	my running
	$S event writable [self] ask
    }

    Http instproc ask {} {
	my instvar url S state host port path \
	    method headers data caching

	$S event writable [self] {}

	if {[pwdManager checkAvailableCredentials $url credentials]} {
	    eval lappend headers $credentials
	    #my showMsg "*** new headers = <$headers>"
	}
	if {$caching==2 && [persistentCache ifModifiedHeader $url ifModified]} {
	    eval lappend headers $ifModified
	    #puts stderr "new headers = <$headers>"
	}
	# Send data in cr-lf format, but accept any line terminators
	$S translation {auto crlf}

	#my showMsg "'$method $path HTTP/[my httpVersion]' headers {$headers}"
	$S puts "$method $path HTTP/[my httpVersion]"
	if {[$S  exists error]} {
	    my abort "Connection refused by host '$host' port '$port'\n\
    	[$S set error]"
	    return
	}

	$S puts "Accept: [Http set accept]"
	$S puts "Host: $host"
	$S puts "User-Agent: [Http set useragent]"
	foreach {tag value} $headers {
	    regsub -all \[\n\r\] $value {} value
	    set tag [string trim $tag]
	    if {[string length $tag]} {
		#my showMsg "+++ <$tag: $value>"
		$S puts "$tag: $value"
	    }
	}
	if {[my exists data]} {
	    $S puts "Content-Length: [string length $data]"
	    $S puts "Content-Type: [my contentType]"
	    $S puts ""
	    $S translation {auto binary}
	    $S puts-nonewline $data
	} else {
	    $S puts ""
	}
	$S flush
	if {[$S  exists error]} {
	    my instvar host port
	    my abort "Connection refused by host '$host' port '$port'\n\
		[$S set error]"
	} else {
	    set state 3
	    $S event readable [self] headerStart
	}
    }

    Http instproc headerStart {} {
	#my showCall
	my instvar S response
	set n [$S gets response]
	#my showVars n response
	if {[$S  exists error]} {
	    my instvar host port
	    my abort "Error on connection to host '$host' port '$port'\n\
		[$S set error]"
	    return
	}
	#my showMsg "n=$n, eof=[$S eof]"
	if {$n == -1 && ![$S eof]} {
	    #my showMsg "******************************input pending, no full line"
	    return
	}
	my instvar responseCode responseHttpVersion
	if {[regexp {^HTTP/([0-9.]+) +([0-9]+) *} $response _ \
		 responseHttpVersion responseCode]} {
	    #my showMsg "response valid: '$response'"
	    $S event readable [self] header
	} else {
	    my instvar host port
	    my abort "Unexpected response from $host:$port\n    $n: '$response'"
	}
    }
    Http instproc header {} {
	my instvar S meta totalsize
	if {[$S gets line]} {
	    #my showMsg "line=$line"
	    if {[regexp -nocase {^content-type:(.+)$} $line _ type]} {
		my contentType [string trim $type]
	    } elseif {[regexp -nocase {^content-length:(.+)$} $line _ length]} {
		set totalsize [string trim $length]
	    }
	    if {[regexp -nocase {^([^:]+): *(.+)$} $line _ key value]} {
		lappend meta [string tolower $key] $value
	    }
	} else {
	    my headerDone
	}
    }
    Http array set expectsBody \
	{GET 1 HEAD 0 POST 1 PUT 0 DELETE 1 OPTIONS 0 TRACE 1}
    Http instproc headerDone {} {
	#my showVars meta
	my instvar S meta method responseCode responseHttpVersion
	[self class] instvar expectsBody
	set expectBody $expectsBody($method)

	array set v [my set meta]
	if {([info exists v(connection)] && $v(connection) eq "close") || \
		$responseHttpVersion < 1.1} {
	    $S makePersistent 0
	} else {
	    $S makePersistent 1
	}
	
	switch $responseCode {
	    200 {}
	    204 {
		#set state empty
		my finish
		set block ""
		set expectBody 0
		return
	    }
	    301 -
	    302 {
		# the request is redirected to another server
		my set ignoreBody [list redirect $v(location)]
		
		# RFC2068 Note: When automatically redirecting a POST request after
		# receiving a 302 status code, some existing HTTP/1.0 user agents
		# will erroneously change it into a GET request.
		#my method GET 
		
		my showMsg "redirect '[my url]' --> '$v(location)'"
	    }
	    304 { ;#  Not Modified, use cached version
		my set ignoreBody cacheAccess
		set expectBody 1
		#my showMsg "result comes from cache"
	    }
	    401 {
		my set ignoreBody \
		    [list resubmitAuthenticated $v(www-authenticate)]
		#my showMsg "resubmitAuthenticated $v(www-authenticate)"
		if {[my exists resubmitAuthenticated]} {
		    if {[my set resubmitAuthenticated] > 5} {
			my abort "Too many wrong passwords given"
		    } else {
			my incr resubmitAuthenticated
		    }
		} else {
		    my set resubmitAuthenticated 1
		}
		set expectBody 1
	    }
	    404 {
		my instvar url
		#my showVars
		my set ignoreBody [list abort "File Not Found on Server '$url'"]
		set expectBody 1
	    }
	    default {
		#my showVars responseCode
	    }
	}
	next
	if {![my exists S]} {;# this request was already canceled
	    return
	}
	if {[info exists v(transfer-encoding)]} {
	    if {$v(transfer-encoding) == "chunked"} {
		$S translation {auto binary}
		my set state 4
		$S event readable [self] readChunkedLength
	    } else {
		my abort "Unexpected Transfer encoding '$v(transfer-encoding)'"
	    }
	} else {
	    # yahoo does not send a content length for a get request
	    #if {$totalsize == 0 && ($responseCode > 300 || !$expectsBody($method) )} 
	    #my showVars method totalsize expectsBody($method) expectBody
	    # the following is used currently for Actiweb-Agents:
	    # the reponse of a PUTS contains a BODY!
	    if {!$expectBody && 
		[::info exists v(content-length)] &&
		$v(content-length) > 0} {
		set expectBody 1
		#my showMsg "setting expectBody 1"
	    }

	    if {!$expectBody} {
		#$S event readable [self] ""
		#set state eof
		my finish
		set block ""
	    } else {
		### To avoid CRLF problmes we set the translation for 
		### the body entity  to binary
		$S translation binary
		my set state 4
		$S event readable [self] readData
	    }
	}
    }

    Http instproc cacheAccess {} {
	# there is an actual version of the document in the cache
	#showCall
	persistentCache validated [my url]
	#my close
	my class CacheAccess
	#my showMsg "result comes from cache [persistentCache cacheFileName $url]"
	my caching 0 ;# should be really: toCache 0
	my GET
    }

    Http instproc redirect location {
	# the request is redirected to another server
	if {$location ne [my url] } {
	    #my showVars
	    my url $location
	    my initialize
	    my classify $location
	    my [my set method]
	}
    }
    Http instproc resubmitAuthenticated headerField {
	#my showCall
	# the server requires authentification
	my instvar path method
	if {[pwdManager checkRequestedAuthentification $method $path $headerField \
		 type realm]} {
	    my instvar url caching method headers
	    array set v $headers
	    #my showVars
	    catch {unset v(Authorization)}
	    set headers [array get v]
	    pwdManager removePasswd $realm
	    my close
	    if {[pwdManager requireCredentials $realm $url]} {
		my initialize
		if {$caching == 2} {set caching 1}
		my $method
	    }
	} else {
	    my abort "unknown authentication method '$headerField'"
	}
    }
    Http instproc readChunkedLength {} {
	#my showCall
	my instvar S chunkLength totalsize
	set length [$S gets lengthString]
	if {$length > 0} {
	    set chunkLength [expr 0x$lengthString]
	    #my showVars lengthString chunkLength
	    if {$chunkLength == 0} {
		$S event readable [self] readChunkedTrailer
	    } else {
		incr totalsize $chunkLength
		$S translation {binary}
		$S event readable [self] readChunkedData
	    }
	}
    }
    Http instproc readChunkedTrailer {} {
	#my showCall
	my instvar S state block
	set size [$S gets line]
	if {$size != 0} {
	    showObj [self]
	    my abort "expected trailer size 0, got size $size"
	} else {
	    #set state eof
	    my finish
	    set block ""
	    #showObj [self]
	}
    }
    Http instproc readChunkedData {} {
	#my showCall
	my instvar S block totalsize currentsize chunkLength
	set block [$S readSize $chunkLength]
	set received [string length $block]
	#my showVars block
	#my showVars currentsize totalsize chunkLength received
	if {$chunkLength == $received} {
	    $S translation {auto binary}
	    $S event readable [self] readChunkedLength
	} else {
	    incr chunkLength -$received
	}
	my pushBlock
    }

    Http instproc readData {} {
	#my showCall
	my instvar S block totalsize currentsize
	set block [$S read]
	#puts stderr "????? bl=[string length $block]"
	if {[$S exists error]} {
	    set block ""
	    my abort [$S set error]
	    return
	}
	my pushBlock
	#my showMsg "c [my set currentsize]== t [[self set totalsize]]"
	if {$currentsize == $totalsize && 
	    [my exists S] && [$S exists persistent]} {
	    #my showMsg "PERSITENT, end of entity reached"
	    #my set state eof
	    my finish
	    set block ""
	    #showObj [self]
	}
	if {[my exists mustDestroy]} {
	    #my showMsg "mustDestroy was set"
	    my destroy
	}
    }
    Http instproc close {} {
	#my showCall
	if {[my exists S]} {
	    [my set S] close
	    #unset S
	}
	#next
    }
    Http instproc freeConnection {} {
	#showCall
	my instvar S
	#::puts stderr "[self] freeing $S !!!!!!!!!!!!!!!!!!!!!!!"
	unset S
    }


    #Access instproc makeZombie {} {
    #  my showMsg "procs= [my info procs], i [Object info instcommands]"
    #  my class Zombie
    #}
    #Class Zombie
    #Zombie instproc unknown {m args} {
    #  my showMsg "+++ zombie method '$m' called"
    #}


    Class Https -superclass Http
    Https instproc initialize args {
	#my showCall
	package require tls 
	if {![my exists port]} { my set port 443}
	next
	### temporary solution, FIXME: 
	### zur zeit muss man den secure-webserver.xotcl und 
	### secure-webclient.xotcl jedenfalls aus dem xotcl/apps/xocomm-apps
	### verzeichnis starten, da haben wir mal die cfg files in unserem
	### baum....
	source .ssl/ssl-configuration.xotcl
	###
    }
    Https instproc openConnection {host port reuse} {
	set S [Connection make [self] $host $port $reuse reused]
	if {$reused} {
	    #my showMsg "reusing $S"
	} else {
	    my showMsg "make the socket ([$S socket]) secure ..."    
	    set cmd [list $S importSSL]
	    foreach attr {cafile cadir certfile cipher command keyfile \
			      model request require ssl2 ssl3 tls1} {
		if {[sslClientConfig exists $attr]} {
		    lappend cmd -$attr [sslClientConfig set $attr]
		}
	    }
	    my showMsg "the assembled command is... ´$cmd´"    
	    eval $cmd
	    puts stderr "CHANNELS= [file channels]"
	}
	return $S
    }



    #######################################################################
    Object pwdManager
    pwdManager proc requirePasswd {realm username password} {
	# used by ftp and imap
	my instvar user area
	upvar [self callinglevel] $password passwd
	if {[my exists pwd($realm)]} {
	    #my showMsg "*** reusing password for $realm"
	    set passwd [my set pwd($realm)]
	    return 1
	} else {
	    userPwd user $username
	    if {[userPwd show $realm user($realm) passwd]} {
		set area($realm/$username) $realm
		return 1
	    }
	}
	return 0
    }
    pwdManager proc storePasswd {realm username password} {
	# used by ftp and imap
	my instvar pwd user
	set pwd($realm) $password
	set user($realm) $username
    }
    pwdManager proc removePasswd {realm} {
	if {[my exists pwd($realm)]} {
	    my unset pwd($realm) 
	    my unset user($realm) 
	}
    }
    pwdManager proc requireCredentials {realm url} {
	regexp {^(.*/)[^/]*$} $url _ path
	if {[my exists pwd($realm)]} {
	    #my showMsg "*** register url=$url for ther realm=$realm"
	    my set area($path) $realm
	    return 1
	} else {
	    my instvar pwd user
	    if {[info exists ::env(USER)]} {
		set USER $::env(USER)
	    } elseif {[info exists ::env(USERNAME)]} {
		set USER $::env(USERNAME)
	    } else {
		set USER unknown
	    }

	    userPwd user $USER
	    if {[userPwd show $realm user($realm) pwd($realm)]} {
		#my showMsg "*** setting password for realm '$realm' url=$path"
		my set area($path) $realm
		return 1
	    }
	}
	return 0
    }
    pwdManager proc encodeCredentials {authMethod realm} {
	#my showCall
	switch $authMethod {
	    basic  {set credential [my encodeCredentialsBasic $realm]}
	    digest {set credential [my encodeCredentialsDigest $realm]}
	}
	return $credential
    }
    pwdManager proc encodeCredentialsBasic {realm} {
	my instvar pwd user
	return [list Authorization \
		    "Basic [base64 encode $user($realm):$pwd($realm)]"]
    }
    pwdManager proc encodeCredentialsDigest {realm} {
	#my showCall
	package require tcu;        #FIXME: noch nicht in distribution
	my instvar digestResponseData
	my mkDigestResponseData $realm
	set digestResponse {}
	foreach {t v} [array get digestResponseData] {
	    append digestResponse " $t = \"$v\","
	}
	return [list Authorization "Digest [string trimright $digestResponse ,]"] 
    }
    pwdManager proc mkDigestResponseData {realm} {
	#my showCall
	my instvar pwd user digestResponseData requestUri
	# RFC 2617
	#   credentials      = "Digest" digest-response
	#   digest-response  = 1#( username | realm | nonce | digest-uri
	#                                | response | [ algorithm ] | [cnonce] |
	#                                [opaque] | [message-qop] |
	#                                    [nonce-count]  | [auth-param] )
	#   username         = "username" "=" username-value
	#   username-value   = quoted-string
	#   digest-uri       = "uri" "=" digest-uri-value
	#   digest-uri-value = request-uri   ; As specified by HTTP/1.1
	#   message-qop      = "qop" "=" qop-value
	#   cnonce           = "cnonce" "=" cnonce-value
	#   cnonce-value     = nonce-value
	#   nonce-count      = "nc" "=" nc-value
	#   nc-value         = 8LHEX
	#   response         = "response" "=" request-digest
	#   request-digest = <"> 32LHEX <">
	#   LHEX             =  "0" | "1"| ...| "e" | "f"  
	set digestResponseData(username) $user($realm)
	set digestResponseData(uri) $requestUri
	set digestResponseData(cnonce) "TEST"
	set digestResponseData(nc) 00000001
	set digestResponseData(response) [my mkRequestDigest]
	#parray digestResponseData
    }
    pwdManager proc mkRequestDigest {} {
	# returns a string of 32 hex digits, which proves that the user
	# knows a password
	
	#A1 = unq(username-value) ":" unq(realm-value) ":" passwd
	my instvar digestResponseData pwd requestMethod requestUri
	append A1 $digestResponseData(username)\
	    : $digestResponseData(realm) : $pwd($digestResponseData(realm))
	if {![my exists digestResponseData(qop)] || 
	    $digestResponseData(qop) eq "auth"} {
	    append A2 $requestMethod : $requestUri
	} elseif {$digestResponseData(qop) eq "auth-int"} {
	    #A2 = Method ":" digest-uri-value ":" H(entity-body)
	    append A2 $requestMethod : $requestUri: ""
	}
	if {[my exists digestResponseData(qop)]} {
	    append reqDigest $digestResponseData(nonce) : \
		$digestResponseData(nc) : \
		$digestResponseData(cnonce): \
		$digestResponseData(qop)
	    set reqDigest [md5 [md5 $A1]:$reqDigest:[md5 $A2]]
	} else {    
	    set reqDigest [md5 [md5 $A1]:$digestResponseData(nonce):[md5 $A2]]
	}  
	return $reqDigest 
    }

    pwdManager proc checkAvailableCredentials {url returnCredentials} {
	# we start a fresh request and check, whether we have sent for this url
	# (directory) already some credentials in an earlier reqhest.
	my instvar authMethod
	regexp {^(.*/)[^/]*$} $url _ path
	if {[my exists area($path)]} {
	    set realm [my set area($path)]
	    upvar [self callinglevel] $returnCredentials credentials
	    #my showMsg "*** adding credentials for realm=$realm and $path"
	    set credentials [my encodeCredentials $authMethod $realm]
	    return 1
	}
	return 0
    }
    pwdManager proc checkRequestedAuthentification {reqMethod path headerField 
						    typeVar realmVar} {
	# check for which realm with which authentification method the
	# server wants an authentification
	#my showCall
	upvar [self callinglevel] $typeVar type $realmVar realm
	my instvar authMethod digestResponseData requestUri requestMethod
	set requestUri $path
	set requestMethod $reqMethod
	if {[regexp {^Basic realm="(.*)"$} $headerField _ realm]} {
	    set type basic;#    FD: musste da lassen wg. call by reference
	    set authMethod $type
	    return 1
	} elseif {[regsub {^Digest } $headerField _ headerField]} {
	    set type digest ;# FD: musste da lassen wg. call by reference
	    set authMethod $type
	    foreach pair [split $headerField ,] {      
		if {[regexp {^(.*) *= *(".*")$} $pair _ n v]} {
		    set digestResponseData([string trim $n]) [string trim [string trim $v] \"]
		}
	    }
	    set realm $digestResponseData(realm)
	    return 1
	}
	return 0
    }

    #######################################################################
    # test classes
    Class Sink
    Sink instproc startCb         {r}   {
	my set contentLength 0
	next
    }
    Sink instproc notifyCb      {r}     {
	next
    }
    Sink instproc incCb         {r t c} {
	my set contentLength $t
	next
    }
    Sink instproc endCb         {r}     {
	next
	#showCall
    }
    Sink instproc cancelCb      {r}     {
	next
	#showCall
    }
    Sink instproc content       {}      {
	next
	#showCall
    }
    Sink instproc contentLength {}      {
	my set contentLength
	#showCall
    }


    Class TimeSink -superclass Sink
    TimeSink instproc startCb         {r}   {
	my set startTime [clock clicks]
	next
    }
    TimeSink instproc notifyCb      {r}     {
	my set notifyTime [clock clicks]
	next
    }
    TimeSink instproc endCb         {r}     {
	my set endTime [clock clicks]
	next
	my reportTimes
    }
    TimeSink instproc cancelCb      {r}     {
	my set endTime [clock clicks]
	next
    }
    TimeSink instproc reportTimes {}     {
	my instvar startTime endTime notifyTime
	set bytes [my contentLength]
	set grossSecs		[expr {($endTime-$startTime)/1000000.0}]
	set grossKbps		[expr {($bytes/1000.0)/$grossSecs}]
	set netSecs		[expr {($endTime-$notifyTime)/1000000.0}]
	set netKbps [expr {($bytes/1000.0)/$netSecs}]
	#if {[catch {set netKbps [expr {($bytes/1000.0)/$netSecs}]}]} {
	#  set netKbps 0
	#}
	set headSecs		[expr {$grossSecs-$netSecs}]
	foreach v {grossSecs grossKbps netSecs netKbps headSecs} {
	    set $v [format %.2f [set $v]]
	}
	my showMsg "got $bytes bytes in $grossSecs ($headSecs+$netSecs) seconds,\
	$grossKbps ($netKbps) KB/S"
    }


    Class ParallelSink -superclass Sink -parameter {
	{httpVersion 1.1}
	{sinkClass TimeSink}
	{maxsimultaneous 20}
    }
    ParallelSink instproc init args {
	next
	my set running 1
	my set numrequests 0
	my set sumbytes 0
	my set blocked {}
    }
    ParallelSink instproc startCb r {
	#my showCall
	my incr running
	my incr numrequests
	#puts stderr "... running++ [my set running]"
    }
    ParallelSink instproc endCb r {
	#my showCall
	my incr sumbytes [$r set currentsize]
	my done $r
    }
    ParallelSink instproc cancelCb r {
	#my showCall
	my done $r
    }
    ParallelSink instproc done r {
	#my showCall
	my instvar blocked
	$r shutdown
	my incr running -1
	#puts stderr "... running-- [my set running] [llength [Http info instances]]"
	#puts stderr [Http info instances]
	#foreach i [Http info instances] {puts stderr "\t$i: [$i set method] [$i set url]"}
	#puts stderr RUNNING=[my set running]
	if {[llength $blocked] > 0} {
	    set newreq [lindex $blocked 0]
	    set blocked [lrange $blocked 1 end]
	    my scheduleRequest [lindex $newreq 0] [lindex $newreq 1] [lindex $newreq 2]
	} elseif {[my set running] < 1} {
	    my set done 1
	}
    }


    ParallelSink instproc scheduleRequest {method url {parentUrl ""}} {
	my instvar requests blocked running maxsimultaneous
	if {$running > $maxsimultaneous} {
	    lappend blocked [list $method $url $parentUrl]
	} else {
	    set cmd [list Access createRequest -url $url \
			 -sinkClass [my sinkClass] \
			 -informObject [self] \
			 -method $method \
			 -timeout 25000 \
			 -caching 0 -allowJoin 0 -httpVersion [my httpVersion]]
	    if {$parentUrl ne ""} {
		lappend cmd -parentUrl $parentUrl
	    }
	    set r [eval $cmd]
	}
    }

    ParallelSink instproc requests {urls} {
	my showTimeStart
	foreach url $urls { my scheduleRequest GET $url }
	my wait
	my showTimeEnd
    }

    ParallelSink instproc wait {} {
	my instvar running
	if {$running > 0} {
	    set savedValue $running
	    #my showMsg ".......... waiting for initially $running requests"
	    if {[catch {my vwait done} err]} {
		my showMsg "vwait returned: $err "
	    }
	    #my showMsg "$savedValue requests FINISHED "
	}
    }

    Class MemorySink -superclass Sink
    MemorySink instproc incCb         {r t c} {
	my append d [$r block]
	next
    }
    MemorySink instproc content       {}      {
	return [my set d]
    }
    MemorySink instproc contentLength {}      {
	if {[my exists d]} {
	    return [string length [my set d]]
	} else {
	    return 0
	}
    }

    Class FileSink  -superclass Sink -parameter fileName
    ### write methods
    #FileSink instproc startCb         {r}   {
    #  next
    #}
    FileSink instproc notifyCb      {r}     {
	#my showVars
	next
	my instvar file fileName
	if {[info exists fileName]} {
	    set file [::open $fileName w]
	    fconfigure $file -translation binary
	} else {
	    # we have no filename; we assume the sink must be a dummy sink
	    # that deletgates its work to some other FileSink
	    my class ShadowFileSink
	    my notifyCb $r
	}
    }
    FileSink instproc incCb {r t c} {
	next
	if {[my exists file]} {
	    if {$r == "req0"} {
		puts stderr "*******************************************************"
		puts stderr [$r block]
		puts stderr "*******************************************************"
	    }
	    puts -nonewline [my set file] [$r block]
	}
    }
    FileSink instproc endCb  {r} {
	#my showCall
	next
	my close
    }
    FileSink instproc cancelCb  {r} {
	next
	my close
    }
    FileSink instproc close {} {
	if {[my exists file]} {
	    ::close [my set file]
	    my unset file
	}
    }
    ### read methods
    FileSink instproc content {} {
	next
	my instvar file fileName
	set file [::open $fileName r]
	fconfigure $file -translation binary
	set d [read [my set file]]
	my close
	return $d
    }
    FileSink instproc contentLength {}      {
	next
	if {[my exists fileName]} {
	    return [file size [my set fileName]]
	} else {
	    return 0
	}
    }


    Class ShadowFileSink -superclass Sink
    ShadowFileSink instproc notifyCb      {r} {
	next
	my set token $r
    }
    ShadowFileSink instproc content       {} {
	my instvar token
	next
	return [[$token set sink] content]
    }
    ShadowFileSink instproc contentLength {} {
	my instvar token
	next
	return [[$token set sink] contentLength]
    }


    Class CacheFileSink -superclass FileSink -parameter {{persistent 1}}
    CacheFileSink instproc notifyCb req {
	#my showCall
	if {![my exists fileName]} {
	    my instvar persistent
	    set url [$req set url]
	    my set fileName [persistentCache newEntry $url $req $persistent ""]
	}
	# it is important to execute next after setting the fileName...
	next
    }
    CacheFileSink instproc endCb req {
	#my showCall
	my instvar persistent
	next
	if {$persistent} {
	    persistentCache entryDone [$req set url]
	}
    }
    CacheFileSink instproc cancelCb req {
	next
	if {[my exists fileName]} {
	    file delete [my set fileName]
	    my unset fileName
	}
    }
    CacheFileSink instproc destroy {} {
	#my showCall
	if {[my exists fileName] && ![my set persistent]} {
	    #my showMsg "file delete $fileName"
	    file delete [my set fileName]
	    my unset fileName
	}
	next
    }


    #===========================================================

    Class SimpleRequest -parameter {
	{caching 0} 
	{useFileSink 0} 
	{blocking 1} 
	{timing 0} 
	url fileName 
	timeout httpVersion method headers data query contentType informObject
    }
    SimpleRequest instproc fileName x {
	my set fileName $x
	my set useFileSink 1
    }
    SimpleRequest instproc init args {
	my instvar useFileSink fileName sink caching token  
	#my showMsg "Starting Request"
	next
	if {[info exists fileName]} {
	    set sink [FileSink create [self]::sink -fileName $fileName]
	} elseif {$useFileSink || $caching > 0} {
	    set sink [FileSink create [self]::sink]
	} else {
	    set sink [MemorySink create [self]::sink]
	}
	#my showMsg "class of sink = [$sink info class]"
	if {[my set timing]} {
	    $sink mixin TimeSink
	}
	set cmd [list Access createRequest \
		     -url [my url] \
		     -informObject $sink \
		     -blocking [my blocking] \
		     -caching $caching]
	foreach optionalParameter {
	    timeout httpVersion method headers data query  
	    contentType informObject
	} {
	    if {[my exists $optionalParameter]} {
		lappend cmd -$optionalParameter [my set $optionalParameter]
	    }
	}
	#my showMsg "cmd=$cmd"
	set token [eval $cmd]
	#if {[my success]} {
	#  $sink reportTimes
	#  #puts stderr <[$sink content]>
	#}
    }
    SimpleRequest instproc success {} {
	if {[my exists token]} {
	    return [expr {[[my set token] set finished] == 1}]
	} 
	return 0
    }
    SimpleRequest instproc destroy {} {
	if {[my exists token]} {
	    [my set token] destroy
	}
	next
    }
    SimpleRequest instproc getContent {} {
	[my set sink] content
    }
    SimpleRequest instproc getContentLength {} {
	[my set sink] contentLength
    }
    #SimpleRequest instproc destroy args { next }

    #######################################################################

    namespace export \
	Access FileAccess File CacheAccess ReAccess \
	Cmd NetAccess Http Https Sink TimeSink \
	ParallelSink MemorySink FileSink \
	ShadowFileSink CacheFileSink SimpleRequest
}

namespace import ::xotcl::comm::httpAccess::*
