- Publicity: Public Only All
50-protocol-handler-procs.tcl
Protocol handles, mostly for defining extra HTTP methods.
- Location:
- packages/xotcl-core/tcl/50-protocol-handler-procs.tcl
- Author:
- Gustaf Neumann <neumann@wu-wien.ac.at>
Procedures in this file
Detailed information
[ hide source ] | [ make this the default ]Content File Source
::xo::library doc { Protocol handles, mostly for defining extra HTTP methods. @author Gustaf Neumann (neumann@wu-wien.ac.at) } namespace eval ::xo { Class create ProtocolHandler -parameter { {url} {package} } ProtocolHandler ad_instproc -private unknown {method args} { Return connection information similar to ad_conn } { :log "--[self class] unknown called with '$method' <$args>" switch -- [llength $args] { 0 { if {[info exists :$method]} { return ${:method} } return [ad_conn $method] } 1 {set :method $args} default {:log "--[self class] ignoring <$method> <$args>"} } } ProtocolHandler instproc log {message} { # # Comment/uncomment one of the two following lines to # activate/silence the logging. # #next "xo::ProtocolHandler: $message" #ns_log notice "xo::ProtocolHandler [self]: $message" } ProtocolHandler ad_instproc set_user_id {} { Set user_id based on authentication header } { :log "[ns_conn method] request comes with headers [ns_set array [ns_conn headers]]" set ah [ns_set iget [ns_conn headers] Authorization] if {$ah ne ""} { # # Get credentials from a basic authentication string like # "Basic 29234k3j49a". # set credentials [http_auth::basic_authentication_decode $ah] set auth [auth::authenticate \ -username [dict get $credentials user] \ -authority_id [::auth::get_register_authority] \ -password [dict get $credentials password]] if {[dict get $auth auth_status] ne "ok"} { set auth [auth::authenticate \ -email [dict get $credentials user] \ -password [dict get $credentials password]] if {[dict get $auth auth_status] ne "ok"} { :log "auth status [dict get $auth auth_status]" set :user_id 0 throw {AUTH UNAUTHORIZED {unauthorized}} [dict get $auth auth_status] } } :log "auth_check user_id='[dict get $auth user_id]'" ad_conn -set user_id [dict get $auth user_id] } else { # no authenticate header, anonymous visitor ad_conn -set user_id 0 ad_conn -set untrusted_user_id 0 } set :user_id [ad_conn user_id] } ProtocolHandler ad_instproc initialize {} { Setup connection object and authenticate user } { ad_conn -reset # # Make sure, there is no ::ad_conn(request); otherwise the # developer support will add all its output to a single var, which # can lead easily to running out of resources in busy sites. When # unset, the developer support will create its own id. unset -nocomplain ::ad_conn(request) set :uri [ns_urldecode [ns_conn url]] if {[string length ${:uri}] < [string length ${:url}]} {append :uri /} set url_regexp "^${:url}" regsub $url_regexp ${:uri} {} :uri if {![regexp {^[./]} ${:uri}]} {set :uri /${:uri}} #:log "--conn_setup: uri '${:uri}' url='${:url}' con='[ns_conn url]'" :set_user_id set :method [string toupper [ns_conn method]] #:log "--conn_setup: uri '${:uri}' method ${:method}" set :urlv [split [string trim ${:uri} "/"] "/"] set :user_agent [ns_set iget [ns_conn headers] user-agent] set :destination [ns_urldecode [ns_set iget [ns_conn headers] Destination]] if {${:destination} ne ""} { regsub {https?://[^/]+/} ${:destination} {/} dest regsub $url_regexp $dest {} :destination if {![regexp {^[./]} ${:destination}]} {set :destination /${:destination}} } :log "--conn_setup: method ${:method} destination '${:destination}' uri '${:uri}' peer [ns_conn peeraddr]" } ProtocolHandler ad_instproc preauth { args } { Handle authorization. This method is called via ns_filter. } { #:log "--preauth args=<$args>" # Restrict to SSL if required if { [security::RestrictLoginToSSLP] && ![security::secure_conn_p] } { ns_returnunauthorized return filter_return } # # Set common data for all kind of requests. A possible outcome is # that we cannot proceed (authentication failure), so we have # to trap such cases. try { :initialize } trap {AUTH UNAUTHORIZED} {errorMsg} { :log "not authorized: $errorMsg" ns_returnunauthorized return filter_return } on error {errorMsg} { ns_log error "ProtocolHandler: exception during initialization: $errorMsg" return filter_return } if {${:user_id} == 0} { # # Check, if we are running under the regression test. For this, # the nsv must exist and the peeraddr must be the regression # test. If this is all true, accept the user_id if provided. # if {[nsv_array exists aa_test] && [nsv_get aa_test logindata logindata] && [ns_conn peeraddr] eq [dict get $logindata peeraddr] } { #ns_log notice logindata=$logindata if {[dict exists $logindata user_id]} { ad_conn -set user_id [dict get $logindata user_id] ad_conn -set untrusted_user_id [dict get $logindata user_id] set :user_id [ad_conn user_id] } } else { # for now, require for every user authentication :log "not authorized 2 uri ${:uri} conn-url [ns_conn url]" if {${:uri} ne "xxx/principal/"} { ns_returnunauthorized return filter_return } } } :log "--preauth filter_ok - user_id ${:user_id}" return filter_ok } ProtocolHandler ad_instproc register { } { Register the NaviServer/AOLserver filter and traces. This method is typically called via *-init.tcl. Note that the specified url must not have an entry in the site-nodes, otherwise the OpenACS request processor performs always the cockie-based authorization. To change that, it would be necessary to register the filter before the request processor (currently, there are no hooks for that). } { set filter_url ${:url}* set url ${:url}/* set root [string trimright ${:url} /] # # Methods defined by RFC 2086 (19.6.1 Additional Request Methods): # # LINK UNLINK PATCH # # Methods defined by RFC 2616: # # OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT # # Methods defined by RF C2518: # # PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK # # Methods defined by RFC 3253 (versioning extensions): # # VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT # MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL # MKACTIVITY # # Methods defined by RFC 3648 (ordered collections): # # ORDERPATCH # # Methods defined by RFC 3744 (WebDAV): # # ACL REPORT # # Methods defined by RFC 4437 (redirect reference resources): # # MKREDIRECTREF UPDATEREDIRECTREF # # Methods defined by RFC $791 (CalDAV): # # MKCALENDAR # # Methods defined by RFC 4918 (HTTP Extensions): # # COPY LOCK MKCOL MOVE PROPFIND PROPPATCH UNLOCK # # Methods defined by RFC 5323 (WebDAV SEARCH): # # SEARCH # # Methods defined by RFC 5789: # # PATCH # foreach method { GET HEAD PUT POST MKCOL COPY MOVE PROPFIND PROPPATCH DELETE LOCK UNLOCK OPTIONS REPORT } { ns_register_filter preauth $method $filter_url [self] ns_register_filter preauth $method $root [self] ns_register_proc $method $url [self] handle_request ns_register_proc $method $root [self] handle_request :log "ns_register_filter preauth $method $filter_url [self]" :log "ns_register_filter preauth $method $root [self]" :log "ns_register_proc $method $url [self] handle_request" :log "ns_register_proc $method $root [self] handle_request" } ns_register_proc OPTIONS / ::xo::minimalProctocolHandler OPTIONS ns_register_proc PROPFIND / ::xo::minimalProctocolHandler PROPFIND } ProtocolHandler ad_instproc get_package_id {} { Initialize the given package and return the package_id @return package_id } { ${:package} initialize -url ${:uri} :log "-- ${:package} initialize -url ${:uri}" return $package_id } ProtocolHandler ad_instproc handle_request { args } { Process the incoming HTTP request. This method could be overloaded by the application and dispatches the HTTP requests. } { :log "--handle_request method=${:method} uri=${:uri}\ userid=${:user_id} -ns_conn query '[ns_conn query]'" if {[info exists :package] && ${:uri} ne "/"} { # We don't call package-initialize for ${:uri} = "/" set :package_id [:get_package_id] } if {[:procsearch ${:method}] ne ""} { :${:method} } else { ns_return 404 text/plain "not implemented" } } # # Formatting methods # ProtocolHandler instproc tcl_time_to_iso8601 {datetime} { # RFC2518 requires this just for creationdate if {$datetime eq ""} return "" set tcl_time [::xo::db::tcl_date $datetime tz] return [clock format [clock scan $tcl_time] -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1] } ProtocolHandler instproc http_date {seconds} { # HTTP-Date as defined in RFC2068#section-3.3.1 return "[clock format $seconds -format {%a, %d %b %Y %T} -gmt 1] GMT" } ProtocolHandler instproc tcl_time_to_http_date {datetime} { # RFC2518 requires this e.g. for getlastmodified if {$datetime eq ""} return "" return [:http_date [clock scan [::xo::db::tcl_date $datetime tz]]] } ProtocolHandler instproc multiStatus {body} { append _ {<?xml version="1.0" encoding="utf-8" ?>} \n \ {<D:multistatus xmlns:D="DAV:">} $body \n </D:multistatus> \n } ProtocolHandler instproc multiStatusResponse { -href:required -propstats:required {-propstatus true} } { :log "multiStatusResponse href $href propstats $propstats" append reply \n \ {<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/" xmlns:g0="DAV:">} \ "\n<D:href>$href</D:href>\n" # The multi-status response has 2 formats # - with <D:propstat> (used in PROPFIND and PROPPATCH) # - without <D:propstat> (used in other cases, e.g. DELETE, COPY, MOVE for collections) # http://www.webdav.org/specs/rfc4918.html#multi-status.response # foreach {props status} $propstats { if {$propstatus} { append reply <D:propstat>\n if {[llength $props] > 0} { append reply <D:prop>\n foreach {name value} $props { if {$value ne ""} { append reply <$name>$value</$name>\n } else { append reply <$name/>\n } } append reply </D:prop>\n } else { append reply <D:prop/>\n } append reply <D:status>$status</D:status>\n</D:propstat>\n } else { append reply <D:status>$status</D:status>\n } } append reply </D:response>\n } ProtocolHandler instproc multiStatusError {status} { lappend davprops \ D:getlastmodified "" \ D:getcontentlength "" \ D:creationdate "" \ D:resourcetype "" set r [:multiStatus [:multiStatusResponse \ -href [ns_urldecode [ns_conn url]] \ -propstats [list $davprops $status]]] :log multiStatusError=$r ns_return 207 text/xml $r } # # Some dummy HTTP methods # ProtocolHandler instproc GET {} { :log "--GET method" ns_return 200 text/plain GET-${:uri} } ProtocolHandler instproc PUT {} { :log "--PUT method [ns_conn content]" ns_return 201 text/plain "received put with content-length [string length [ns_conn content]]" } ProtocolHandler instproc OPTIONS {} { ns_set put [ns_conn outputheaders] Allow OPTIONS ns_return 200 text/plain {} } ProtocolHandler instproc PROPFIND {} { #:log "--ProtocolHandler PROPFIND [ns_conn content]" # when GET is not supported on this resource, the get* properties are not be sent # see http://www.webdav.org/specs/rfc4918.html, 9.1.5 lappend davprops \ lp1:resourcetype <D:collection/> \ lp1:creationdate [:tcl_time_to_iso8601 "2013-06-30 01:21:22.648325+02"] \ D:supportedlock {} \ D:lockdiscovery {} ns_return 207 text/xml [:multiStatus \ [:multiStatusResponse \ -href ${:uri} \ -propstats [list $davprops "HTTP/1.1 200 OK"]]] } ::xo::ProtocolHandler create ::xo::minimalProctocolHandler ::xo::minimalProctocolHandler proc OPTIONS {args} { ns_set put [ns_conn outputheaders] Allow OPTIONS ns_return 200 text/plain {} } ::xo::minimalProctocolHandler proc PROPFIND {args} { :multiStatusError "HTTP/1.1 403 Forbidden" } } # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: