| # |
| # 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 |
| } |
| } |
| |