Index: openacs-4/packages/oacs-dav/oacs-dav.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/oacs-dav.info,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/oacs-dav.info 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,26 @@ + + + + + webDAV Support + + f + t + + + dave bauer + Provides services to enable webDAV access to content repository items. + An interface to the tDAV webDAV package. oacs-dav provides services to offer webDAV access to content repository data. + + + + + + + + + + + + + Index: openacs-4/packages/oacs-dav/catalog/oacs-dav.en_US.ISO-8859-1.xml =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/catalog/oacs-dav.en_US.ISO-8859-1.xml,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/catalog/oacs-dav.en_US.ISO-8859-1.xml 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,18 @@ + + + + Disable + Disable Selected Folders + Disabled + Enable + Enable Selected Folders + Enabled + Folder Name + Folder URL + Selected Folders disabled for WebDAV support. + Selected Folders enabled for WebDAV support. + Package Name + Package Type + Status + WebDAV Folder Administration + Index: openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-create.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-create.sql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,21 @@ +-- +-- @author Dave Bauer (dave@thedesignexperience.org) +-- @creation-date 2003-10-18 +-- @cvs-id $Id: oacs-dav-create.sql,v 1.1 2004/02/16 16:49:27 daveb Exp $ +-- + +-- create a table to map site node_ids to cr_folders + +create table dav_site_node_folder_map ( + node_id integer + constraint dav_site_node_folder_map_node_id_un + unique + constraint dav_side_node_folder_map_node_id_fk + references apm_packages, + folder_id integer + constraint dav_impls_folder_id_fk + references cr_folders, + enabled_p char(1) + constraint dav_site_node_folder_map_enabled_p_bl + check enabled_p in ('t','f') +); Index: openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-drop.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/sql/oracle/oacs-dav-drop.sql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,7 @@ +-- +-- @author Dave Bauer (dave@thedesignexperience.org) +-- @creation-date 2003-10-19 +-- @cvs-id $Id: oacs-dav-drop.sql,v 1.1 2004/02/16 16:49:27 daveb Exp $ +-- + +drop table dav_site_node_folder_map \ No newline at end of file Index: openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-create.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-create.sql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,22 @@ +-- +-- @author Dave Bauer (dave@thedesignexperience.org) +-- @creation-date 2003-09-14 +-- @cvs-id $Id: oacs-dav-create.sql,v 1.1 2004/02/16 16:49:27 daveb Exp $ +-- + +-- create a table to map site node_ids to cr_folders + +create table dav_site_node_folder_map ( + node_id integer + constraint dav_site_node_folder_map_node_id_un + unique + constraint dav_side_node_folder_map_node_id_fk + references site_nodes, + folder_id integer + constraint dav_impls_folder_id_fk + references cr_folders, + enabled_p boolean +); + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-drop.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/sql/postgresql/oacs-dav-drop.sql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,8 @@ +-- +-- @author Dave Bauer (dave@thedesignexperience.org) +-- @creation-date 2003-09-28 +-- @cvs-id $Id: +-- + +drop table dav_site_node_folder_map; + \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-init.tcl 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,42 @@ +# + +ad_library { + + setup filters + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2003-12-18 + @cvs-id $Id: oacs-dav-init.tcl,v 1.1 2004/02/16 16:49:27 daveb Exp $ + +} + +set prefix [parameter::get \ + -package_id [apm_package_id_from_key "oacs-dav"] \ + -parameter "WebDAV URL Prefix" \ + -default "/dav"] + +set url "${prefix}/*" +set filter_url "${prefix}*" +ns_register_filter preauth GET ${filter_url} oacs_dav::authorize +ns_register_filter preauth PUT ${filter_url} oacs_dav::authorize +ns_register_filter preauth MKCOL ${filter_url} oacs_dav::authorize +ns_register_filter preauth COPY ${filter_url} oacs_dav::authorize +ns_register_filter preauth MOVE ${filter_url} oacs_dav::authorize +ns_register_filter preauth PROPFIND ${filter_url} oacs_dav::authorize +ns_register_filter preauth PROPPATCH ${filter_url} oacs_dav::authorize +ns_register_filter preauth DELETE ${filter_url} oacs_dav::authorize +ns_register_filter preauth LOCK ${filter_url} oacs_dav::authorize +ns_register_filter preauth UNLOCK ${filter_url} oacs_dav::authorize + +ns_log notice "OACS-DAV preauth filters loaded on $filter_url" + +ns_register_proc GET ${url} oacs_dav::handle_request +ns_register_proc COPY ${url} oacs_dav::handle_request +ns_register_proc PUT ${url} oacs_dav::handle_request +ns_register_proc DELETE ${url} oacs_dav::handle_request +ns_register_proc PROPFIND ${url} oacs_dav::handle_request +ns_register_proc PROPPATCH ${url} oacs_dav::handle_request +ns_register_proc MKCOL ${url} oacs_dav::handle_request +ns_register_proc MOVE ${url} oacs_dav::handle_request +ns_register_proc LOCK ${url} oacs_dav::handle_request +ns_register_proc UNLOCK ${url} oacs_dav::handle_request Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-install-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-install-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-install-procs.tcl 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,205 @@ +# + +ad_library { + + Setup procs to run at package install, should be run only once. + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2003-09-11 + @cvs-id $Id: oacs-dav-install-procs.tcl,v 1.1 2004/02/16 16:49:27 daveb Exp $ + +} + +namespace eval oacs_dav::install {} + +ad_proc -private oacs_dav::install::package_install {} { + setup DAV service contracts +} { + db_transaction { + create_service_contracts + register_implementation + } +} + +ad_proc -private oacs_dav::install::package_uninstall {} { + clean up for package uninstall +} { + db_transaction { + delete_service_contracts + unregister_implementation + } +} + +# this is far from complete or even known to be going in the +# right direction + +# somehow we need to get identication information from the +# user and send back status of permission allowed or denied + +# look at the DAV spec to get an idea of what inputs and +# outputs these methods have + +ad_proc -private oacs_dav::install::create_service_contracts { +} { + create service contract for DAV methods +} { + oacs_dav::install::create_dav_sc + oacs_dav::install::create_dav_put_type_sc +} + +ad_proc -private oacs_dav::install::create_dav_sc { +} { + create dav service contract spec +} { + set contract_name "dav" + set dav_spec { + description "implements DAV methods" + operations { + get { + description "DAV GET Method" + output { content:string } + } + put { + description "DAV PUT Method" + output { response:string } + } + propfind { + description "DAV PROPFIND Method" + output { + response:string + } + } + delete { + description "DAV DELETE Method" + output { + response:string + } + } + mkcol { + description "DAV MKCOL Method" + output { + response:string + } + } + copy { + description "DAV Copy Method" + output { + response:string + } + } + move { + description "DAV Move Method" + output { + response:string + } + } + proppatch { + description "DAV PROPATCH Method" + output { + response:string + } + } + lock { + description "DAV LOCK Method" + output { + response:string + } + } + unlock { + description "DAV UNLOCK Method" + output { + response:string + } + } + } + } + + + acs_sc::contract::new_from_spec \ + -spec [concat [list name $contract_name] $dav_spec ] +} + +ad_proc -private oacs_dav::install::create_dav_put_type_sc { +} { + create dav_put_type service contract +} { + set contract_name "dav_put_type" + set dav_spec { + description "returns content type to use for PUT operation" + operations { + get_type { + description "DAV PUT Content Type" + output { content_type:string } + } + } + } + + acs_sc::contract::new_from_spec \ + -spec [concat [list name $contract_name] $dav_spec ] + +} + +ad_proc -private oacs_dav::install::delete_service_contracts { +} { + remove service contracts on uninstall +} { + acs_sc::contract::delete -name dav + acs_sc::contract::delete -name dav_put_type +} + +ad_proc -private oacs_dav::install::register_implementation { +} { + add default content repository service contract + implementation +} { + + set spec { + name "content_revision" + aliases { + get oacs_dav::impl::content_revision::get + put oacs_dav::impl::content_revision::put + propfind oacs_dav::impl::content_revision::propfind + delete oacs_dav::impl::content_revision::delete + mkcol oacs_dav::impl::content_revision::mkcol + proppatch oacs_dav::impl::content_revision::proppatch + copy oacs_dav::impl::content_revision::copy + move oacs_dav::impl::content_revision::move + lock oacs_dav::impl::content_revision::lock + unlock oacs_dav::impl::content_revision::unlock + } + contract_name {dav} + owner [oacs_dav::package_key] + } + + acs_sc::impl::new_from_spec -spec $spec + + set spec { + name "content_folder" + aliases { + get oacs_dav::impl::content_folder::get + put oacs_dav::impl::content_folder::put + propfind oacs_dav::impl::content_folder::propfind + delete oacs_dav::impl::content_folder::delete + mkcol oacs_dav::impl::content_folder::mkcol + proppatch oacs_dav::impl::content_folder::proppatch + copy oacs_dav::impl::content_folder::copy + move oacs_dav::impl::content_folder::move + lock oacs_dav::impl::content_folder::lock + unlock oacs_dav::impl::content_folder::unlock + } + contract_name {dav} + owner [oacs_dav::package_key] + } + + acs_sc::impl::new_from_spec -spec $spec + +} + + +ad_proc -private oacs_dav::install::unregister_implementation { +} { + remove default service contract implementation +} { + acs_sc::impl::delete -contract_name dav -impl_name content_folder + acs_sc::impl::delete -contract_name dav -impl_name content_revision +} \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-oracle.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-oracle.xql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,242 @@ + + + oracle8.1.6 + + + select content_item.get_id( + name => :item_name, + root_folder_id => :parent_id, + resolve_index_p => 'f') + + + + + + select nvl (cr.content_length,4096) as content_length, + nvl (cr.mime_type,'*/*') as mime_type, + to_char(timezone('GMT',o.creation_date) :: timestamptz ,'YYYY-MM-DD"T"HH:MM:SS.MS"Z"') as creation_date, + to_char(timezone('GMT',o.last_modified) :: timestamptz ,'Dy, DD Mon YYYY HH:MM:SS TZ') as last_modified, + ci1.item_id, + case when ci1.item_id=ci2.item_id then '' else ci1.name end as name, + content_item.get_path(ci1.item_id,:folder_id) as item_uri, + case when o.object_type='content_folder' then 1 else 0 end + as collection_p + from ( + select * from cr_items + connect by prior item_id=parent_id + start with item_id=:item_id + ) ci1, + cr_revisions, + acs_objects o + where + ci1.live_revision(+) = cr.revision_id, + and exists (select 1 + from acs_object_party_privilege_map m + where m.object_id = ci1.item_id + and m.party_id = :user_id + and m.privilege = 'read') + + + + + + select + ci.item_id, + ci.name, + content_item__get_path(ci.item_id,:folder_id) as item_uri, + coalesce(cr.mime_type,'*/*') as mime_type, + cr.content_length, + to_char(timezone('GMT',o.creation_date) :: timestamptz ,'YYYY-MM-DD"T"HH:MM:SS.MS"Z"') as creation_date, + to_char(timezone('GMT',o.last_modified) :: timestamptz ,'Dy, DD Mon YYYY HH:MM:SS TZ') as last_modified + from cr_items ci, + acs_objects o, + cr_revisions cr + where + ci.item_id=:item_id + and ci.item_id = o.object_id + and cr.revision_id=ci.live_revision + + + + + + select content_folder.new( + name => :new_folder_name, + label => :label, + description => :description, + parent_id => :parent_id, + context_id => :parent_id, + new_folder_id => NULL, + creation_date => current_timestamp, + creation_user => :user_id, + creation_ip => :peer_addr + ) + + + + + + select content_folder.copy ( + folder_id => :copy_folder_id, + target_folder_id => :new_parent_folder_id, + creation_user => :user_id, + creation_ip => :peer_addr, + name => :new_name + ) + + + + + + select content_folder.move ( + folder_id => :move_folder_id, + target_folder_id => :new_parent_folder_id, + name => :new_name + ) + + + + + + select content_folder.rename ( + folder_id => :move_folder_id, + name => :new_name, + label => NULL, + description => NULL + ) + + + + + + select content_item.move ( + item_id => :item_id, + target_folder_id => :new_parent_folder_id, + name => :new_name + ) + + + + + + select content_item.rename ( + item_id => :item_id, + name => :new_name + ) + + + + + + select content_item.copy ( + item_id => :copy_item_id, + target_id => :new_parent_folder_id, + creation_user => :user_id, + creation_ip => :peer_addr, + name => :new_name + ) + + + + + + select content_item.delete( + item_id => :dest_item_id + ) + + + + + + select content_item.delete( + item_id => :dest_item_id + ) + + + + + + select content_item.delete ( + item_id => :item_id + ) + + + + + + select content_folder.delete ( + folder_id => :item_id, + cascade_p => 't' + ) + + + + + + + select content_item.get_id( + name=> :parent_name, + root_folder_id => :root_folder_id, + resolve_index_p => 'f') + + + + + + select content_item.get_id( + name => :new_name, + root_folder_id => :new_parent_folder_id, + resolve_index_p => 'f') + + + + + + select content_item.get_id( + name => :new_name, + root_folder_id => :new_parent_folder_id, + resolve_index_p => 'f') + + + + + + select content_folder.delete( + folder_id => :dest_item_id, + cascade_p => 't'); + + + + + + select content_folder.delete( + folder_id => :dest_item_id, + cascade_p => 't'); + + + + + + select content_item.get_id( + name => :new_name, + root_folder_id => :new_parent_folder_id, + resolve_index_p => 'f') + + + + + + select content_item.get_id( + name => :new_name, + root_folder_id => :new_parent_folder_id, + resolve_index_p => 'f') + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-postgresql.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-procs-postgresql.xql 16 Feb 2004 16:49:27 -0000 1.1 @@ -0,0 +1,223 @@ + + + postgresql7.1 + + + + select content_item__get_id(:item_name,:parent_id,'f') + + + + + + select coalesce (cr.content_length,4096) as content_length, + coalesce(cr.mime_type,'*/*') as mime_type, + to_char(timezone('GMT',o.creation_date) :: timestamptz ,'YYYY-MM-DD"T"HH:MM:SS.MS"Z"') as creation_date, + to_char(timezone('GMT',o.last_modified) :: timestamptz ,'Dy, DD Mon YYYY HH:MM:SS TZ') as last_modified, + ci1.item_id, + case when ci1.item_id=ci2.item_id then '' else ci1.name end as name, + content_item__get_path(ci1.item_id,:folder_id) as item_uri, + case when o.object_type='content_folder' then 1 else 0 end + as collection_p + from cr_items ci1 left join cr_revisions cr on ci1.live_revision = cr.revision_id, + cr_items ci2, + acs_objects o + where ci1.tree_sortkey between ci2.tree_sortkey and + tree_right(ci2.tree_sortkey) + and ci2.item_id=:folder_id + and ci1.item_id = o.object_id + and (tree_level(ci1.tree_sortkey) - tree_level(ci2.tree_sortkey)) + <= :depth :: integer + and exists (select 1 + from acs_object_party_privilege_map m + where m.object_id = ci1.item_id + and m.party_id = :user_id + and m.privilege = 'read') + + + + + + select + ci.item_id, + ci.name, + content_item__get_path(ci.item_id,:folder_id) as item_uri, + coalesce(cr.mime_type,'*/*') as mime_type, + cr.content_length, + to_char(timezone('GMT',o.creation_date) :: timestamptz ,'YYYY-MM-DD"T"HH:MM:SS.MS"Z"') as creation_date, + to_char(timezone('GMT',o.last_modified) :: timestamptz ,'Dy, DD Mon YYYY HH:MM:SS TZ') as last_modified + from cr_items ci, + acs_objects o, + cr_revisions cr + where + ci.item_id=:item_id + and ci.item_id = o.object_id + and cr.revision_id=ci.live_revision + and exists (select 1 + from acs_object_party_privilege_map m + where m.object_id = ci.item_id + and m.party_id = :user_id + and m.privilege = 'read') + + + + + + select content_folder__new( + :new_folder_name, + :label, + :description, + :parent_id, + :parent_id, + NULL, + current_timestamp, + :user_id, + :peer_addr + ) + + + + + + select content_folder__copy ( + :copy_folder_id, + :new_parent_folder_id, + :user_id, + :peer_addr, + :new_name + ) + + + + + + select content_folder__move ( + :move_folder_id, + :new_parent_folder_id, + :new_name + ) + + + + + + select content_folder__rename ( + :move_folder_id, + :new_name, + NULL, + NULL + ) + + + + + + select content_item__move ( + :item_id, + :new_parent_folder_id, + :new_name + ) + + + + + + select content_item__rename ( + :item_id, + :new_name + ) + + + + + + select content_item__copy ( + :copy_item_id, + :new_parent_folder_id, + :user_id, + :peer_addr, + :new_name + ) + + + + + + select content_item__delete(:dest_item_id) + + + + + + select content_item__delete(:dest_item_id) + + + + + + select content_item__delete ( + :item_id + ) + + + + + + select content_folder__delete ( + :item_id, + 't' + ) + + + + + + + select content_item__get_id(:parent_name,:root_folder_id,'f') + + + + + + select content_item__get_id(:new_name,:new_parent_folder_id,'f') + + + + + + select content_item__get_id(:new_name,:new_parent_folder_id,'f') + + + + + + select content_folder__delete(:dest_item_id,'t'); + + + + + + select content_folder__delete(:dest_item_id,'t'); + + + + + + select content_item__get_id(:new_name,:new_parent_folder_id,'f') + + + + + + select content_item__get_id(:new_name,:new_parent_folder_id,'f') + + + + + Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.tcl 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,1028 @@ +# /packages/oacs-dav/tcl/oacs-dav-procs.tcl +ns_log notice "Loading oacs-dav-procs.tcl" +ad_library { + + Support for tDAV tcl webDAV implemenation + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2003-09-11 + @cvs-id $Id: oacs-dav-procs.tcl,v 1.1 2004/02/16 16:49:28 daveb Exp $ + +} + +namespace eval oacs_dav {} + + +ad_proc oacs_dav::set_user_id {} { + set user_id based on authentication header +} { + + # should be something like "Basic 29234k3j49a" + set a [ns_set get [ns_conn headers] Authorization] + if {[string length $a]} { + ns_log notice "TDAV auth_check authentication info $a" + # get the second bit, the base64 encoded bit + set up [lindex [split $a " "] 1] + # after decoding, it should be user:password; get the username + set user [lindex [split [ns_uudecode $up] ":"] 0] + set password [lindex [split [ns_uudecode $up] ":"] 1] + ns_log notice "ACS VERSION [ad_acs_version]" + switch -glob -- [ad_acs_version] { + "5.0*" { + ns_log debug "TDAV 5.0 authentication" + array set auth [auth::authenticate \ + -username $user \ + -password $password] + if {![string equal $auth(auth_status) "ok"]} { + array set auth [auth::authenticate \ + -email $user \ + -password $password] + if {![string equal $auth(auth_status) "ok"]} { + ns_log debug "TDAV 5.0 auth status $auth(auth_status)" + ns_returnunauthorized + return 0 + } + + } + ns_log notice "TDAV: auth_check openacs 5.0 user_id= $auth(user_id)" + ad_conn -set user_id $auth(user_id) + return + } + default { + # for 4.6: + ns_log debug "TDAV 4.6 authentication" + set email [string tolower $user] + if {[db_0or1row user_login_user_id_from_email { + select user_id, member_state, email_verified_p + from cc_users + where email = :email}] } { + + if {[ad_check_password $user_id $password]} { + ns_log notice "TDAV setting user_id $user_id" + ad_conn -set user_id $user_id + ad_conn -set untrusted_user_id $user_id + return + } + + } + ns_log notice "TDAV: openacs user/password not matched" + ns_returnunauthorized + return + + } + } + } else { + # no authenticate header, anonymous visitor + ad_conn -set user_id 0 + ad_conn -set untrusted_user_id 0 + } +} + +ad_proc oacs_dav::authorize { args } { + check is user_id has permission to perform the WebDAV method on + the URI +} { + ns_log notice "OACS-DAV running oacs_dav::authorize" + # set common data for all requests + oacs_dav::conn_setup + + set method [string tolower [oacs_dav::conn method]] + set item_id [oacs_dav::conn item_id] + set user_id [oacs_dav::conn user_id] + set folder_id [oacs_dav::conn folder_id] + ns_log notice "OACS-DAV oacs_dav::authorize user_id $user_id method $method item_id $item_id" + set authorized_p 0 + # if item doesn't exist don't bother checking.... + if {[empty_string_p $item_id]} { + if {![string equal $method "put"] && ![string equal $method "mkcol"]} { + ns_log notice "oacs_dav::authorize file not found!!!!!" + ns_return 404 text/plain "File Not Found" + return filter_return + } + } + switch $method { + put - + mkcol { + set authorized_p [permission::permission_p \ + -object_id $folder_id \ + -party_id $user_id \ + -privilege "create"] + } + delete { + set authorized_p [permission::permission_p \ + -object_id $item_id \ + -party_id $user_id \ + -privilege "delete"] + } + lock - + unlock - + proppatch { + set authorized_p [permission::permission_p \ + -object_id $item_id \ + -party_id $user_id \ + -privilege "write"] + } + copy - + move { + set authorized_p [expr [permission::permission_p \ + -object_id $item_id \ + -party_id $user_id \ + -privilege "read"] && \ + [permission::permission_p \ + -object_id [oacs_dav::conn dest_parent_id ] \ + -party_id $user_id \ + -privilege "create"]] + + } + propfind - + get { + # default for GET PROPFIND + set authorized_p [permission::permission_p \ + -object_id $item_id \ + -party_id $user_id \ + -privilege "read"] + } + } + if {![string equal $authorized_p 1]} { + ns_returnunauthorized + return filter_return + } + return filter_ok +} + +ad_proc -public oacs_dav::conn { + args +} { + shared data for WebDAV requests +} { + global tdav_conn + set flag [lindex $args 0] + if { [string index $flag 0] != "-" } { + set var $flag + set flag "-get" + } else { + set var [lindex $args 1] + } + switch -- $flag { + -set { + set value [lindex $args 2] + set tdav_conn($var) $value + return $value + } + -get { + if { [info exists tdav_conn($var)] } { + return $tdav_conn($var) + } else { + return [ad_conn $var] + } + } + } +} + +ad_proc -public oacs_dav::register_folder { + {-enabled_p "t"} + folder_id + node_id +} { + add a uri to dav support + @param folder_id + @param node_id + Register a root WebDAV enabled folder for a site node_id + All requests that resolve to this site node id will be checked for + WebDAV content using this folder as the root. Only one folder per + node_id can be registered. +} { + + db_transaction { + db_dml add_folder "" + } on_error { + ns_log error "OACS-DAV Failed attempt to add folder_id $folder_id as a WebDAV enabled folder for node_id $node_id. One folder is already registered" + error "Only one folder per node_id may be registered." + } +} + +ad_proc -public oacs_dav::unregister_folder { + folder_id + node_id +} { + remove a uri from dav support + @param folder_id + @param node_id +} { + db_dml remove_folder "" +} + +ad_proc -public oacs_dav::item_parent_folder_id { + uri +} { + get the folder_id of the parent of an item + from the uri + @param uri + @returns parent_folder_id or empty string if folder does not exist +} { + ns_log notice "OACS-DAV:item parent folder_id uri $uri" + array set sn [oacs_dav::request_site_node $uri] + set node_id $sn(node_id) + set root_folder_id [oacs_dav::request_folder_id $node_id] + set urlv [split [string trimright [string range $uri [string length $sn(url)] end] "/"] "/"] + if {[llength $urlv] >1} { + set parent_name [join [lrange $urlv 0 [expr [llength $urlv] -2 ] ] "/" ] + } else { + set parent_name "/" + } + ns_log debug "parent_folder_id urlv $urlv parent_name $parent_name uri $uri" + if {[string equal [string trimright $parent_name "/"] [string trimright $sn(url) "/"]]} { + # content_item__get_id can't resolve "/" + # because it strips the leading and trailing / + # from the url you pass in, and cr_items.name of the folder + # is not and empty string + set parent_id $root_folder_id + } else { + set parent_id [db_exec_plsql get_parent_folder_id ""] + } + return $parent_id +} + +ad_proc -public oacs_dav::conn_setup {} { + Setup oacs_dav::conn, authenticate user +} { + ad_conn -reset + set uri [ns_conn url] + set oacs_dav_package_id [apm_package_id_from_key "oacs-dav"] + set dav_url_regexp "^[parameter::get -package_id $oacs_dav_package_id -parameter "WebDAV URL Prefix" -default "/dav"]" + set uri [regsub $dav_url_regexp $uri {}] + oacs_dav::conn -set uri $uri + set method [ns_conn method] + oacs_dav::set_user_id + ns_log notice "OACS-DAV conn_setup uri $uri method $method user_id [oacs_dav::conn user_id]" + array set sn [oacs_dav::request_site_node $uri] + set node_id [oacs_dav::conn -set node_id $sn(node_id)] + set package_id [oacs_dav::conn -set package_id $sn(package_id)] + set folder_id [oacs_dav::conn -set folder_id [oacs_dav::request_folder_id [oacs_dav::conn node_id]]] + set urlv [oacs_dav::conn -set urlv [split [string trimright $uri "/"] "/"]] + + if {[catch {[oacs_dav::conn destination]} destination]} { + set destination [oacs_dav::conn -set destination [ns_set iget [ns_conn headers] Destination]] + } + regsub {http://[^/]+/} $destination {/} dest + ns_log notice "DEST = $dest" + set dest [regsub $dav_url_regexp $dest {}] + oacs_dav::conn -set destination $dest + if {![empty_string_p $dest]} { + oacs_dav::conn -set dest_parent_id [oacs_dav::item_parent_folder_id $dest] + } + + # we need item_id and content_type + # we should use content::init but that has caching and I don't + # have time to resolve the issues that raises right now + # a full-featured, consistently used tcl api for CR will fix that + if {[llength $urlv] > 2} { + set parent_url [join [lrange $urlv 0 [expr [llength $urlv] -2 ] ] "/" ] + } else { + set parent_url "/" + } + ns_log debug "handle request parent_url $parent_url length urlv [llength $urlv] urlv $urlv" + set item_name [lindex $urlv end] + if {[empty_string_p $item_name]} { + # for propget etc we need the name of the folder + # the last element in urlv for a folder is an empty string + set item_name [lindex [split [string trimleft $parent_url "/"] "/"] end] + } + oacs_dav::conn -set item_name $item_name + ns_log debug "handle request parent_url $parent_url length urlv [llength $urlv] urlv $urlv item_name $item_name" + set parent_id [oacs_dav::item_parent_folder_id $uri] + + set item_id [oacs_dav::conn -set item_id [db_exec_plsql get_item_id ""]] + ns_log notice "OACS-DAV uri $uri parent_url $parent_url folder_id $folder_id" + if {[string equal [string trimright $uri "/"] [string trimright $sn(url) "/"]]} { + set item_id [oacs_dav::conn -set item_id $folder_id] + } + + ns_log notice "OACS-DAV setup conn item_id $item_id" +} + +ad_proc -public oacs_dav::handle_request { uri method args } { + dispatch request to the proper service contract implmentation +} { + + set uri [ns_conn url] + set method [string tolower [ns_conn method]] +ns_log debug "oacs_dav::handle_request method=$method uri=$uri" + set item_id [oacs_dav::conn item_id] + set folder_id [oacs_dav::conn folder_id] + set package_id [oacs_dav::conn package_id] + set node_id [oacs_dav::conn node_id] + set package_key [apm_package_key_from_id $package_id] + + ns_log debug "DAV item_id is $item_id" + if {[empty_string_p $item_id]} { + ns_log debug "DAV: item_id is empty" + # set this to null if nothing exists, only valid on PUT or MKCOL + # to create a new item, otherwise we bail + # item for URI does not exist + # ask package what content type to use + switch -- $method { + mkcol { + set content_type "content_folder" + } + put { + if {![acs_sc_binding_exists_p dav_put_type $package_key]} { + set content_type "content_revision" + } else { + set content_type [acs_sc_call dav_put_type get_type "" $package_key] + } + + } + default { + # return a 404 or other error + ns_log notice "DAV:handle request Item not found method $method URI $uri" + ns_return 404 text/html "File Not Found" + return + } + } + + } else { + # get content type of existing item + set content_type \ + [oacs_dav::conn -set content_type \ + [db_string get_content_type "" -default "content_revision"]] + } + # use content type + # i think we should walk up the object type hierarchy up to + # content_revision if we don't find an implementation + # implementation name is content_type + + if {![acs_sc_binding_exists_p dav $content_type]} { + # go up content_type hierarchy + # we do the query here to avoid running the query + # when the implementation for the content_type does + # exist + + #FIXME: write the query etc + set content_type "content_revision" + } + + oacs_dav::conn -set content_type $content_type + + # probably should catch this + + ns_log debug "oacs_dav::handle_request method $method uri $uri item_id $item_id folder_id $folder_id package_id $package_id node_id $node_id content_type $content_type args $args" + + set response [acs_sc_call dav $method "" $content_type] + + # here the sc impl might return us some data, + # then we would probably have to send that to tDAV for processing +ns_log debug "DAV: response is $response" + + tdav::respond $response +} + +ad_proc -public oacs_dav::request_site_node { uri } { + resolves uri to a site node_id +} { + # if you want to serve up DAV content at a different URL + # you still need to mount a package in the site-map + # might change later when we figure out how to actually use it + ns_log notice "OACS-DAV!! uri $uri" +# if {[empty_string_p $uri]} { +# set uri [ns_conn url] +# } + set sn [site_node::get -url $uri] + return $sn +} + +ad_proc -public oacs_dav::request_folder_id { node_id } { + resolves a node_id to a DAV enabled folder_id + @param node_id site node_id of request + @returns folder_id, or empty string if no folder exists + in dav_package_folder_map for this node_id +} { + return [db_string get_folder_id "" -default ""] +} + +namespace eval oacs_dav::impl::content_folder {} + +# this is probably going away, is there such thing as "source" +# of a folder/collection? + +ad_proc oacs_dav::impl::content_folder::get {} { + GET DAV method for content folders + can't get a folder +} { + + # return something + # if its just a plain file, and a GET then do we need to send anything + # extra or just the file? + return [list 409] +} + +ad_proc oacs_dav::impl::content_folder::mkcol {} { + MKCOL DAV method for generic content folder + @author Dave Bauer +} { + set uri [oacs_dav::conn uri] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set item_id [oacs_dav::conn item_id] + set fname [oacs_dav::conn item_name] + set parent_id [oacs_dav::item_parent_folder_id $uri] + if {[empty_string_p $parent_id]} { + return [list 409] + } + if { ![empty_string_p $item_id]} { + return [list 405] + } + + # probably have to revisit setting content_types allowed + # and permissions, but inheriting from the parent seems + # reasonable + + db_transaction { + set new_folder_name $fname + set label $fname + set description $fname + set new_folder_id [db_exec_plsql create_folder ""] + set response [list 201] + } on_error { + set response [list 500] + } + + return $response +} + +ad_proc oacs_dav::impl::content_folder::copy {} { + COPY DAV method for generic content folder +} { + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set copy_folder_id [oacs_dav::conn item_id] + set overwrite [oacs_dav::conn overwrite] + set target_uri [oacs_dav::conn destination] + set new_parent_folder_id [oacs_dav::conn dest_parent_id] + set durlv [split [string trimright $target_uri "/"] "/"] + set new_name [lindex $durlv end] + set uri [oacs_dav::conn uri] + # check that destination exists and is WebDAV enabled + # when depth is 0 copy just the folder + # when depth is 1 copy contents +ns_log notice "DAV Folder Copy dest $target_uri parent_id $new_parent_folder_id" + if {[empty_string_p $new_parent_folder_id]} { + return [list 409] + } + + set dest_item_id [db_string get_dest_id ""] + if {![empty_string_p $dest_item_id]} { + ns_log notice "DAV Folder Copy Folder Exists item_id $dest_item_id overwrite $overwrite" + if {![string equal -nocase $overwrite "T"]} { + return [list 412] + } elseif {![permission::permission_p \ + -object_id $dest_item_id \ + -party_id $user_id \ + -privilege "write"]} { + ns_returnunauthorized + } + # according to the spec copy with overwrite means + # delete then copy + if {![string equal "unlocked" [tdav::check_lock $target_uri]]} { + return [list 423] + } + db_exec_plsql delete_for_copy "" + } + + db_transaction { + db_exec_plsql copy_folder "" + } on_error { + return [list 500] + } + set response [list 201] + tdav::copy_props $uri $target_uri + return $response +} + +ad_proc oacs_dav::impl::content_folder::move {} { + MOVE DAV method for generic content folder +} { + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set uri [oacs_dav::conn uri] + set target_uri [oacs_dav::conn destination] + set move_folder_id [oacs_dav::conn item_id] + set item_name [oacs_dav::conn item_name] + set new_parent_folder_id [oacs_dav::conn dest_parent_id] + set cur_parent_folder_id [oacs_dav::item_parent_folder_id $uri] + set turlv [split [string trimright $target_uri "/"] "/"] + set new_name [lindex $turlv end] + set overwrite [oacs_dav::conn overwrite] + + if {[empty_string_p $new_parent_folder_id]} { + set response [list 412] + return $response + } + + set dest_item_id [db_string get_dest_id ""] + ns_log debug "@DAV@@ folder move new_name $new_name dest_id $dest_item_id new_folder_id $new_parent_folder_id" + if {![empty_string_p $dest_item_id]} { + ns_log notice "DAV Folder Move Folder Exists item_id $dest_item_id overwrite $overwrite" + if {![string equal -nocase $overwrite "T"]} { + return [list 412] + } elseif {![permission::permission_p \ + -object_id $dest_item_id \ + -party_id $user_id \ + -privilege "write"]} { + ns_returnunauthorized + } + # according to the spec copy with overwrite means + # delete then copy + if {![string equal "unlocked" [tdav::check_lock $target_uri]]} { + return [list 423] + } + + + db_exec_plsql delete_for_move "" + ns_log debug "CONTEXT IDS [db_list get_ids "select object_id from acs_objects where context_id=:dest_item_id"]" + } + # don't let anyone move root DAV folders in the + # dav_site_node_folder_map + if {![string equal [db_string site_node_folder ""] 0]} { + return [list 403] + } + + db_transaction { + + if {![string equal $cur_parent_folder_id $new_parent_folder_id]} { + ns_log debug "@@DAV@@ move folder $move_folder_id" + db_exec_plsql move_folder "" + } elseif {![empty_string_p $new_name]} { + ns_log debug "@@DAV@@ move folder rename $move_folder_id to $new_name" + db_exec_plsql rename_folder "" + } + set response [list 204] + } on_error { + return [list 500] + } + tdav::copy_props $uri $target_uri + tdav::delete_props $uri + tdav::remove_lock $uri + return $response +} + +ad_proc oacs_dav::impl::content_folder::delete {} { + DELETE DAV method for generic content folder +} { + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set item_id [oacs_dav::conn item_id] + set uri [oacs_dav::conn uri] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + if {[catch {db_exec_plsql delete_folder ""} errmsg]} { + ns_log error "content_folder::delete $errmsg" + set response [list 500] +# ns_log debug "CONTEXT IDS [db_list get_ids "select object_id from acs_objects where context_id=:item_id"]" + } else { + set response [list 204] + tdav::delete_props $uri + tdav::remove_lock $uri + } + + return $response +} + +ad_proc oacs_dav::impl::content_folder::propfind {} { + PROPFIND DAV method for generic content folder +} { + set user_id [oacs_dav::conn user_id] + set depth [oacs_dav::conn depth] + set folder_uri [ad_url][ad_conn url] + # if client didn't put a / on folder_uri go ahead and tack it on + if {![string match */ $folder_uri]} { + append folder_uri "/" + } + if {[empty_string_p $depth]} { + set depth 0 + } + + set prop_req [oacs_dav::conn prop_req] + set folder_id [oacs_dav::conn item_id] + + # append the properties into response + set all_properties [list] + db_foreach get_properties "" { + set name $name + set etag "1f9a-400-3948d0f5" + set properties [list] + # is "D" the namespace?? + lappend properties [list "D" "getcontentlength"] $content_length + + ns_log debug "DAVEB item_id $item_id folder_id $folder_id $item_uri" + if {$item_id == $folder_id} { + set item_uri "" + } + + lappend properties [list "D" "getcontenttype"] $mime_type + # where do we get an etag from? + lappend properties [list "D" "getetag"] $etag + lappend properties [list "D" "getlastmodified"] $last_modified + lappend properties [list "D" "creationdate"] $creation_date + if {$collection_p} { + lappend properties [list "D" "resourcetype"] "D:collection" + } else { + lappend properties [list "D" "resourcetype"] "" + } + + # according to Todd's example + # resourcetype for a folder(collection) is + # and getcontenttype is */* + foreach i [tdav::get_user_props ${folder_uri}${item_uri} $depth $prop_req] { + lappend properties $i + } + lappend all_properties [list ${folder_uri}${item_uri} $collection_p $properties] + } + + set response [list 207 $all_properties] + + return $response + + +} + +ad_proc oacs_dav::impl::content_folder::proppatch {} { + PROPPATCH DAV method for generic content folder + user-properties are stored in the filesystem by tDAV + this doesn't do anything until tDAV allows storage of + user properties in the database +} { + set uri [oacs_dav::conn uri] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + + set response [tdav::update_user_props $uri [oacs_dav::conn prop_req]] + return [list 207 $response] +} + +ad_proc oacs_dav::impl::content_folder::lock {} { + LOCK DAV method for generic content folder +} { + set uri [oacs_dav::conn uri] + set owner [oacs_dav::conn lock_owner] + set scope [oacs_dav::conn lock_scope] + set type [oacs_dav::conn lock_type] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + set ret_code 423 + + set response [list $ret_code] + } else { + set depth [tdav::conn depth] + set token [tdav::set_lock $uri $depth $type $scope $owner] + set ret_code 200 + set response [list $ret_code [list depth $depth token $token timeout "" owner $owner scope $scope type $type]] + } + return $response +} + +ad_proc oacs_dav::impl::content_folder::unlock {} { + UNLOCK DAV method for generic content folder +} { + set uri [oacs_dav::conn uri] + + if {![string equal unlocked [tdav::check_lock_for_unlock $uri]]} { + set ret_code 423 + set body "Resource is locked." + } else { + ns_log notice "tdav::check_lock_for_unlock = [tdav::check_lock_for_unlock $uri]]" + tdav::remove_lock $uri + set ret_code 204 + set body "" + } + + return [list $ret_code $body] +} + +namespace eval oacs_dav::impl::content_revision {} + +ad_proc oacs_dav::impl::content_revision::get {} { + GET DAV method for generic content revision + @author Dave Bauer + @param uri +} { + + set item_id [oacs_dav::conn item_id] + + #should return the DAV content for the content item + #for now we always get live/latest revision + + cr_write_content -item_id $item_id +} + +ad_proc oacs_dav::impl::content_revision::put {} { + PUT DAV method for generic content revision + @author Dave Bauer +} { + set user_id [oacs_dav::conn user_id] + set item_id [oacs_dav::conn item_id] + set root_folder_id [oacs_dav::conn folder_id] + set uri [oacs_dav::conn uri] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + + set tmp_filename [oacs_dav::conn tmpfile] + set tmp_size [file size $tmp_filename] + # authenticate that user has write privilege + + # we need to calculate parent_id from the URI + # it might not be the root DAV folder for the package + # check for folder or not + set urlv [split [oacs_dav::conn uri] "/"] + + set name [oacs_dav::conn item_name] + set parent_id [oacs_dav::item_parent_folder_id $uri] + if {[empty_string_p $parent_id]} { + set response [list 409] + return $response + } + + ns_log debug "oacs_dav::impl::content_revision::put parent_id=$parent_id item_id=:item_id root_folder_id=:root_folder_id name=$name" + + # create new item if necessary + db_transaction { + set mime_type [cr_filename_to_mime_type $name] + if {[empty_string_p $item_id]} { + # this won't really work very nicely if we support + # abstract url type names... maybe chop off the extension + # when we name the object? + + set revision_id [cr_import_content \ + -storage_type file \ + $parent_id \ + $tmp_filename \ + $tmp_size \ + $mime_type \ + $name] + } else { + set revision_id [cr_import_content \ + -item_id $item_id \ + -storage_type file \ + $parent_id \ + $tmp_size \ + $tmp_filename \ + $mime_type \ + $name] + } + db_dml set_live_revision "" + + set response [list 201] + } on_error { + set response [list 500] + } + + # at least we need to return the http_status + return $response + +} + +ad_proc oacs_dav::impl::content_revision::propfind {} { + PROPFIND DAV method for generic content revision + @author Dave Bauer +} { + set user_id [oacs_dav::conn user_id] + set item_id [oacs_dav::conn item_id] + set folder_id [oacs_dav::conn folder_id] + set uri [oacs_dav::conn uri] + + set depth [oacs_dav::conn depth] + set prop_req [oacs_dav::conn prop_req] + # find the values + db_1row get_properties "" + set etag "1f9a-400-3948d0f5" + set properties [list] + # is "D" the namespace?? + lappend properties [list "D" "getcontentlength"] $content_length +# lappend properties [list "D" "uri"] $item_uri + lappend properties [list "D" "getcontenttype"] $mime_type + # where do we get an etag from? + lappend properties [list "D" "getetag"] $etag + lappend properties [list "D" "getlastmodified"] $last_modified + lappend properties [list "D" "creationdate"] $creation_date + lappend properties [list "D" "resourcetype"] "" + + foreach i [tdav::get_user_props ${uri} $depth $prop_req] { + lappend properties $i + } + + set response [list 207 [list [list $uri "" $properties]]] + + return $response +} + +ad_proc oacs_dav::impl::content_revision::proppatch {} { + PROPPATCH DAV method for generic content revision + We store all user properties in the filesystem using tDAV for now + So this is just a stub until we can get everything stored in the + database. + @author Dave Bauer +} { + # get the properties out of the list + set uri [oacs_dav::conn uri] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + + # set the values + set response [tdav::update_user_props $uri [oacs_dav::conn prop_req]] + # return results + return [list 207 $response] +} + +ad_proc oacs_dav::impl::content_revision::delete {} { + DELETE DAV method for generic content revision + @author Dave Bauer +} { + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set item_id [oacs_dav::conn item_id] + set uri [oacs_dav::conn uri] + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + if {[catch {db_exec_plsql delete_item ""} errmsg]} { + set response [list 500] + } else { + set response [list 204] + tdav::delete_props $uri + tdav::remove_lock $uri + } + return $response +} + +ad_proc oacs_dav::impl::content_revision::copy {} { + COPY DAV method for generic content revision + @author Dave Bauer +} { + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set uri [oacs_dav::conn uri] + # check for write permission on target folder + set target_uri [oacs_dav::conn destination] + set copy_item_id [oacs_dav::conn item_id] + set overwrite [oacs_dav::conn overwrite] + set turlv [split $target_uri "/"] + set new_name [lindex $turlv end] + set new_parent_folder_id [oacs_dav::conn dest_parent_id] + if {[empty_string_p $new_parent_folder_id]} { + return [list 409] + } + set dest_item_id [db_string get_dest_id ""] + if {![empty_string_p $dest_item_id]} { + + if {![string equal -nocase $overwrite "T"]} { + return [list 412] + } elseif {![permission::permission_p \ + -object_id $dest_item_id \ + -party_id $user_id \ + -privilege "write"]} { + ns_returnunauthorized + } + # according to the spec copy with overwrite means + # delete then copy + ns_log notice "oacs_dav::revision::copy checking for lock on target" + if {![string equal "unlocked" [tdav::check_lock $target_uri]]} { + return [list 423] + } + + db_exec_plsql delete_for_copy "" + set response [list 204] + } else { + set response [list 201] + } + + db_transaction { + set item_id [db_exec_plsql copy_item ""] + db_dml set_live_revision "" + } on_error { + return [list 500] + } + tdav::copy_props $uri $target_uri + return $response +} + +ad_proc oacs_dav::impl::content_revision::move {} { + MOVE DAV method for generic content revision + @author Dave Bauer +} { + + set package_id [oacs_dav::conn package_id] + set user_id [oacs_dav::conn user_id] + set peer_addr [oacs_dav::conn peeraddr] + set item_id [oacs_dav::conn item_id] + set item_name [oacs_dav::conn item_name] + set uri [tdav::conn url] + set target_uri [oacs_dav::conn destination] + set cur_parent_folder_id [oacs_dav::conn folder_id] + set new_parent_folder_id [oacs_dav::conn dest_parent_id] + set turlv [split $target_uri "/"] + set new_name [lindex $turlv end] + set overwrite [oacs_dav::conn overwrite] + if {[empty_string_p $new_parent_folder_id]} { + return [list 409] + } + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + return [list 423] + } + set dest_item_id [db_string get_dest_id ""] + if {![empty_string_p $dest_item_id]} { + + if {![string equal -nocase $overwrite "T"]} { + return [list 412] + } elseif {![permission::permission_p \ + -object_id $dest_item_id \ + -party_id $user_id \ + -privilege "write"]} { + return [list 401] + } + if {![string equal "unlocked" [tdav::check_lock $target_uri]]} { + return [list 423] + } + + db_exec_plsql delete_for_move "" + set response [list 204] + } else { + set response [list 201] + } + + db_transaction { + if {![string equal $cur_parent_folder_id $new_parent_folder_id]} { + db_exec_plsql move_item "" + } elseif {![empty_string_p $new_name] } { + db_exec_plsql rename_item "" + } + } on_error { + return [list 500] + } + tdav::copy_props $uri $target_uri + tdav::delete_props $uri + tdav::remove_lock $uri + return $response +} + + +ad_proc oacs_dav::impl::content_revision::mkcol {} { + MKCOL DAV method for generic content revision + @author Dave Bauer +} { + # not allowed to create a collection inside a resource + # return some sort of error + set response [list 405] + return $response +} + +ad_proc oacs_dav::impl::content_revision::lock {} { + LOCK DAV method for generic content revision +} { + set uri [oacs_dav::conn uri] + set owner [oacs_dav::conn lock_owner] + set scope [oacs_dav::conn lock_scope] + set type [oacs_dav::conn lock_type] + + if {![string equal "unlocked" [tdav::check_lock $uri]]} { + set ret_code 423 + + set response [list $ret_code] + } else { + set depth [tdav::conn depth] + set token [tdav::set_lock $uri $depth $type $scope $owner] + set ret_code 200 + set response [list $ret_code [list depth $depth token $token timeout "" owner $owner scope $scope type $type]] + } + return $response +} + +ad_proc oacs_dav::impl::content_revision::unlock {} { + UNLOCK DAV method for generic content revision +} { + set uri [oacs_dav::conn uri] + + if {![string equal unlocked [tdav::check_lock_for_unlock $uri]]} { + set ret_code 423 + set body "Resource is locked." + } else { + ns_log notice "tdav::check_lock_for_unlock = [tdav::check_lock_for_unlock $uri]]" + tdav::remove_lock $uri + set ret_code 204 + set body "" + } + + return [list $ret_code $body] +} Index: openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/oacs-dav-procs.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,57 @@ + + + + + insert into dav_site_node_folder_map + (folder_id, node_id, enabled_p) + values + (:folder_id, :node_id, :enabled_p) + + + + + + delete from dav_site_node_folder_map + where folder_id=:folder_id + and node_id=:node_id + + + + + + select folder_id from dav_site_node_folder_map + where node_id=:node_id and enabled_p = 't' + + + + + + select content_type from cr_items where item_id=:item_id + + + + + + update cr_items set live_revision=:revision_id + where item_id=(select item_id from cr_revisions + where revision_id=:revision_id) + + + + + + select count(*) from dav_site_node_folder_map + where folder_id=:move_folder_id + + + + + + update cr_items set live_revision=latest_revision + where item_id=:item_id + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test.html 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1 @@ +TEST \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test1.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test1.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test1.html 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1 @@ +TEST \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test/__test_file.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test/__test_file.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test/__test_file.html 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,8 @@ + + + Test Dav File + + +

This is the test of OpenACS WebDAV support.

+ + \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs-postgresql.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs-postgresql.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,26 @@ + + + + + + + postgresql7.1 + + + + select content_folder__new ( + '__test_folder', + '__test_folder', + NULL, + NULL + ) + + + + + select + content_folder__register_content_type(:folder_id,'content_revision','t') + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.tcl 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,106 @@ +# + +ad_library { + + Test procedures for oacs-dav + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2003-09-14 + @cvs-id $Id: oacs-dav-procs.tcl,v 1.1 2004/02/16 16:49:28 daveb Exp $ + +} + +aa_register_case oacs_dav_sc_create { + test creation of DAV service contract +} { + aa_run_with_teardown \ + -rollback \ + -test_code { + + aa_true "DAV Service contract created" [expr [db_0or1row get_dav_sc ""]] + set sc_ops [db_list get_dav_ops ""] + set valid_ops [list get put mkcol copy propfind proppatch move delete] + foreach op_name $valid_ops { + aa_true "$op_name operation created" [expr [lsearch $sc_ops $op_name] > -1] + } + + aa_true "DAV put_type Service contract created" [expr [db_0or1row get_dav_pt_sc ""]] + aa_true "get_type operation created" [expr [db_0or1row get_dav_pt_op ""]] + } +} + +aa_register_case oacs_dav_put { + test generic cr_revision PUT +} { + aa_run_with_teardown \ + -rollback \ + -test_code { + array set sn [site_node::get -url "/"] + set package_id $sn(package_id) + set name "__test_file.html" + oacs_dav::conn -set item_name $name + set uri "/${name}" + set item_id "" + oacs_dav::conn -set method "PUT" + oacs_dav::conn -set item_id $item_id + oacs_dav::conn -set url $uri + oacs_dav::conn -set urlv $name + oacs_dav::conn -set tmpfile "[acs_root_dir]/packages/oacs-dav/tcl/test/$name" + # we probably want to create a bunch of files in the filesystem + # and test mime type and other attributes to make sure the + # content gets in the database + set fd [open [oacs_dav::conn tmpfile] r] + set orig_content [read $fd] + close $fd + set folder_id [db_exec_plsql create_test_folder ""] + aa_log "Folder Created $folder_id package_id $package_id" + oacs_dav::conn -set folder_id $folder_id + db_exec_plsql register_content_type "" + oacs_dav::register_folder $folder_id $sn(node_id) + set response [oacs_dav::impl::content_revision::put] + aa_log "Response was $response" + set new_item_id [db_string item_exists "" -default ""] + aa_log "Item_id=$new_item_id" + aa_true "Content Item Created" [expr ![empty_string_p $new_item_id]] + set revision_id [db_string revision_exists "" -default ""] + aa_trute "Content Revision Created" [expr ![empty_string_p $revision_id]] + set cr_filename "[cr_fs_path]/[db_string get_content_filename ""]" + aa_true "Content Attribute Set" [string equal [file size [oacs_dav::conn tmpfile]] [file size $cr_filename]] + + } + +} + +aa_register_case oacs_dav_mkcol { + test generic content folder creation +} { + aa_run_with_teardown \ + -rollback \ + -test_code { + array set sn [site_node::get -url "/"] + set package_id $sn(package_id) + set name "__test_folder1/__test_folder2" + set uri "/" + oacs_dav::conn -set item_id "" + oacs_dav::conn -set url $uri + oacs_dav::conn -set extra_url $name + oacs_dav::conn -set urlv [split $uri "/"] + oacs_dav::conn -set package_id $package_id + set parent_folder_id [db_string get_parent_folder "" -default "-100"] + oacs_dav::conn -set folder_id $parent_folder_id + oacs_dav::register_folder $parent_folder_id $sn(node_id) + foreach fname [split $name "/"] { + set uri "$uri${fname}/" + oacs_dav::conn -set item_name $fname + oacs_dav::conn -set url $uri + oacs_dav::conn -set extra_url $fname + oacs_dav::conn -set urlv [split $uri "/"] + aa_log "name $fname uri $uri" + set response [oacs_dav::impl::content_folder::mkcol] + set new_folder_id [db_string folder_exists "" -default ""] + aa_true "Content Folder $fname created" [expr ![empty_string_p $new_folder_id]] + } + + } + +} \ No newline at end of file Index: openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/tcl/test/oacs-dav-procs.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,77 @@ + + + + + + + + + + select * from acs_sc_contracts where contract_name='dav' + + + + + + select * from acs_sc_contracts where contract_name='dav_put_type' + + + + + + + select operation_name from acs_sc_operations where contract_name='dav' + + + + + + select operation_name from acs_sc_operations where + contract_name='dav_put_type' + and operation_name='get_type' + + + + + + select item_id from cr_items where name=:name + and parent_id=:folder_id + + + + + + select revision_id from cr_revisions + where item_id=:new_item_id + + + + + + select content from cr_revisions where revision_id=:revision_id + + + + + + select item_id from cr_items where name=:name + and parent_id=:folder_id + + + + + + select folder_id from cr_folders where package_id=:package_id + + + + + + select item_id + from cr_items + where name=:fname + and content_type='content_folder' + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/index.vuh =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/index.vuh,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/index.vuh 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,9 @@ +# +# +# Redirect to administration +# +# @author Dave Bauer (dave@thedesignexperience.org) +# @creation-date 2004-02-16 +# @cvs-id $Id: index.vuh,v 1.1 2004/02/16 16:49:28 daveb Exp $ + +ad_returnredirect "admin/" \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/disable.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/disable.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/disable.tcl 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,28 @@ +# + +ad_page_contract { + + WebDAV disable folders + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2004-02-15 + @cvs-id $Id: disable.tcl,v 1.1 2004/02/16 16:49:28 daveb Exp $ +} { + folder_id:integer,multiple +} -properties { +} -validate { +} -errors { +} + +permission::require_permission \ + -party_id [ad_conn user_id] \ + -object_id [ad_conn package_id ] \ + -privilege "admin" + +foreach id $folder_id { + + db_dml disable_folder "" + +} +util_user_message -message [_ oacs-dav.Folders_Disabled] +ad_returnredirect "." \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/disable.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/disable.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/disable.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,19 @@ + + + + + + + + + + + + update dav_site_node_folder_map + set enabled_p = 'f' + where folder_id=:id + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/enable.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/enable.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/enable.tcl 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,28 @@ +# + +ad_page_contract { + + WebDAV enable folders + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2004-02-15 + @cvs-id $Id: enable.tcl,v 1.1 2004/02/16 16:49:28 daveb Exp $ +} { + folder_id:integer,multiple +} -properties { +} -validate { +} -errors { +} + +permission::require_permission \ + -party_id [ad_conn user_id] \ + -object_id [ad_conn package_id ] \ + -privilege "admin" + +foreach id $folder_id { + + db_dml enable_folder "" + +} +util_user_message -message [_ oacs-dav.Folders_Enabled] +ad_returnredirect "." \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/enable.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/enable.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/enable.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,19 @@ + + + + + + + + + + + + update dav_site_node_folder_map + set enabled_p = 't' + where folder_id=:id + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/index.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/index.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/index.adp 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,5 @@ + + @title@ + @context@ + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/admin/index.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/index.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/index.tcl 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,47 @@ +# packages/oacs-dav/www/admin/index.tcl + +ad_page_contract { + + Administer webdav enabled folders + + @author Dave Bauer (dave@thedesignexperience.org) + @creation-date 2004-02-15 + @cvs-id $Id: index.tcl,v 1.1 2004/02/16 16:49:28 daveb Exp $ +} { + +} -properties { + title + context +} -validate { +} -errors { +} + +permission::require_permission \ + -party_id [ad_conn user_id] \ + -object_id [ad_conn package_id ] \ + -privilege "admin" +set bulk_actions [list "[_ oacs-dav.Enable]" "enable" "[_ oacs-dav.Enable_Folders]" "[_ oacs-dav.Disable]" "disable" "[_ oacs-dav.Disable_Folders]" ] +template::list::create \ + -name folders \ + -multirow folders \ + -key folder_id \ + -bulk_actions $bulk_actions \ + -elements { + package_key {label {[_ oacs-dav.Package_Type]}} + package_name { label {[_ oacs-dav.Package_Name]} } + label { label {[_ oacs-dav.Folder_Name]} } + folder_url { label {[_ oacs-dav.Folder_URL]} } + status { label {[_ oacs-dav.Status]} } + } + +db_multirow -extend {folder_url package_key package_name status} folders get_folders {} { + array set sn [site_node::get -node_id $node_id] + set folder_url $sn(url) + set package_key $sn(package_key) + set package_name $sn(instance_name) + set status [string map -nocase [list t [_ oacs-dav.Enabled] f [_ oacs-dav.Disabled] ] $enabled_p] +} + +set title [_ oacs-dav.WebDAV_Folder_Administration] +set context $title +ad_return_template Index: openacs-4/packages/oacs-dav/www/admin/index.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/admin/index.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/admin/index.xql 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,23 @@ + + + + + + + + + + + + select cf.folder_id, + cf.label, + sn.node_id, + sn.enabled_p + from cr_folders cf, + dav_site_node_folder_map sn + where cf.folder_id=sn.folder_id + + + + \ No newline at end of file Index: openacs-4/packages/oacs-dav/www/doc/index.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/oacs-dav/www/doc/index.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/oacs-dav/www/doc/index.html 16 Feb 2004 16:49:28 -0000 1.1 @@ -0,0 +1,73 @@ + + + OpenACS WebDAV Support + + + +

OpenACS WebDAV Support

+

Introduction

+

This package implements a WebDAV interface to the OpenACS + Content Repository. In addition to generic access to content + items, there is a service-contract interface so packages can + define custom handlers for WebDAV methods for objects that belong + to that package. +

+

How it Works

+

OpenACS WebDAV Support requires the tDAV AOLserver module to + implement most of the WebDAV protocol. OpenACS WebDAV Support just + provides and interface between tDAV and the Content Repository +

+

Each content_type that requires a custom handler much implement + the dav service contract. Each content type should + implement the dav service contract with the implementation name the same as the content_type. This includes operations + for every WebDAV method. Some operations do not make sense for + certian object types. Specifically, content_items, which are + mapped to WebDAV resources, should not perform a MKCOL (make + collection) method. Likewise, a content_folder, or WebDAV + collection, should not allow a PUT method. In addition to the + dav service contract is a helper contract to allow + packages to set the initial content_type for new items created + through WebDAV. Each package should implement the + dav_put_type service contract with the implementation + named the same as the package key.

+

Each package instance that will allow WebDAV access should + register a package_id and folder_id for the root content_folder + that corresponds with the URI of the package's mount point using oacs_dav::register_folder.

+ +

Dispatching Requests

+

A preauth filter is registered for all WebDAV methods. This + calls oacs_dav::authorize which will set oacs_dav::conn user_id to + the OpenACS user_id or 0 is the request is not authenticated. This + filter also calls oacs_dav::setup_conn sets up the basic + information needed to authorize the request. If authorization + fails a 401 HTTP response is returned requesting authentication + information. If authorization is successful the filter returns + filter_ok and the tdav::filter* filter for the method is called.

+

The tdav::filter* commands setup global information for each + method that is independent of the storage type. After this filter + runs, the request is handled by the registered procedure for + OpenACS oacs_dav::handle_request.

+

oacs_dav::handle_request determines the package_id that should + handle the URI. This is based on the standard OpenACS site_node + Tcl API. After the package is found, the root folder for that + package is retreived from the dav_package_folder_map table. Using + the folder_id, and the URI of the request, the + content_item__get_id pl/sql(plpgsql) procedure is + called to find the item_id for the request. If no item_id is found + and the requested method is PUT, a new item should be created. If + the method is not PUT, a 404 error should be returned. +

+ +

oacs_dav::handle_request will call the service contract +implemenation for the content_type of the item. If the request is a +PUT, first the dav_put_type service contract for the package_key of +the request is called. For file-storage this returns +"file_storage_object" so items created by PUT are created as +file_storage_objects instead of generic content_revisions.

+ +

The service contract implementation for each operation must return +the response data in the format required by tDAV. The documentation +for the tdav::respond::* procedures named for each method describe +what is required.

+ +