blob: c02e18957b5496dbdaf63df3d1acc8ca693a9d63 [file] [log] [blame]
#
# cookie.ws3 -- session context using cookies
# nca-073-9
#
# Copyright (C) 1999 by Netcetera AG.
# Copyright (C) 2001 by Apache Software Foundation.
# All rights reserved.
#
# See the file "license.terms" for information on usage and
# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# @(#) $Id$
#
# Cookie-Specs: http://www.netscape.com/newsref/std/cookie_spec.html
proc web::cookiecontext {ctxmgrname args} {
# correct namespace (relative to caller)
if {![string match ::* $ctxmgrname]} {
set ctxmgrname [uplevel namespace current]::$ctxmgrname
}
web::sessioncontextfactory $ctxmgrname
# set default values for some properties
namespace eval $ctxmgrname {
# where to send cookie to
variable _channel ""
variable _domain ""
variable _path ""
variable _expires "24 hours"
# key to be used for key-value pair in cookie
variable _crypt 1
# secure flag of cookie
variable _secure 0
}
# parse args for this
set argc [llength $args]
set baseargs {}
for {set i 0} {$i < $argc} {incr i} {
set arg [lindex $args $i]
set found 0
foreach opt {channel domain path expires secure crypt} {
if {[string equal $arg -$opt]} {
if {[incr i]>$argc} {
error "argument -$opt needs a value."
}
set ${ctxmgrname}::_$opt [lindex $args $i]
set found 1
break
}
}
if {!$found} {
lappend baseargs $arg
}
}
# now eat up remaining args
${ctxmgrname}::_parseargs $baseargs
# getCookie - find my cookie
proc ${ctxmgrname}::_getCookie {id} {
::set cookie [web::request HTTP_COOKIE]
foreach v [split $cookie ";"] {
# get key and value: note: some cookies (e.g.. Google analytics)
# also use = in the value, so a simple split with =; does
# not always work...
set key [string trim [lindex [split $v =] 0]]
set value [string trim [join [lrange [split $v =] 1 end] =]]
lappend kvlist $key $value
}
# search for $datatag
foreach {key value} $kvlist {
if {[string compare $key $id] == 0} {
return $value
}
}
error "no matching cookie found"
}
#
# init (overwrite sessctx init here)
proc ${ctxmgrname}::init {id {create 0}} {
cunset
# load it
if { [catch {_load $id $create}]} {
# .. or make new
new $id
}
}
# Load - load a state from a cookie
proc ${ctxmgrname}::load {id {create 0}} {
variable _crypt
variable _id
if {$_crypt} {
namespace eval [namespace current] [web::decrypt [_getCookie $id]]
} else {
namespace eval [namespace current] [web::uridecode [_getCookie $id]]
}
set _id $id
}
# Save cookie
proc ${ctxmgrname}::save {id {doInvalidate 0}} {
variable _channel
variable _domain
variable _path
variable _expires
variable _secure
set ochannel [web::response]
if {[string length $_channel]} {
web::response -select $_channel
}
# test if header was not already sent
if {[web::response -sendheader] == 0} {
web::log ws3.error "web::cookiecontext::commit: commit too late, header already sent"
if {![info exists ochannel]} {
web::response -select $ochannel
}
error "cookie commit too late (header already sent)"
}
# write the data
variable _crypt
if {$_crypt} {
set data [web::encrypt [dump]]
} else {
set data [web::uriencode [dump]]
}
set cookie "$id=$data"
if { $doInvalidate } {
append cookie "; expires=Sat, 01-Jan-2000 00:00:00 GMT"
} else {
if { $data == "{}" || $data == ""} {
set cookie "$id=; expires=Sat, 01-Jan-2000 00:00:00 GMT"
} else {
# write the expiry-date
if {[regexp {^[0-9]+$} $_expires]} {
# expiry given as epoch seconds
set expsec $_expires
} elseif {[string length $_expires] && ![catch {clock scan $_expires} msg] } {
# expiry given in tcl scannable date-time string
# incl. "day", "tomorrow", "week" ...
set expsec $msg
}
if {[info exists expsec]} {
# we have an (optional) expiry
# we'd like to just format directly, but Tcl's locale
# handling (at least in 8.4.X) shows strange side effects
# when we play around with LC_TIME etc...
# And since we need a specific locale here, we just
# construct it manually ... (sorry)
# we tried the unset env(LC_TIME) stuff ... it didn't
# work reliably (and strange enough: now it's actually
# faster than before ;-)
set dayNum [clock format $expsec -format "%w" -gmt true]
set day [lindex "Sun Mon Tue Wed Thu Fri Sat" $dayNum]
scan [clock format $expsec -format "%m" -gmt true] "%d" monthNum
# monthNum is not zero based -> dummy entry in the list
set month [lindex "NaM Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec" $monthNum]
set expstr [clock format $expsec -format "$day, %d-$month-%Y %H:%M:%S GMT" -gmt true]
append cookie "; expires=$expstr"
}
}
}
# write the path
if {[string length $_path]} {
append cookie "; path=$_path"
}
# write the domain
if {[string length $_domain]} {
append cookie "; domain=$_domain"
}
# write secure if required
if {$_secure} {
append cookie "; secure"
}
# write the cookie into the header of the channel
web::response -set Set-Cookie $cookie
if {![info exists ochannel]} {
web::response -select $ochannel
}
}
# invalidate context
proc ${ctxmgrname}::invalidate {} {
# delete in namespace
cunset
# mark for deletion on client side
save [id] 1
}
}