c0b5b42004-04-20Martin Stjernholm //! Contains methods for querying and setting of properties for a //! resource. //! //! This default implementation takes care of the most important RFC //! 2518 properties for ordinary files and directories in read-only //! mode.
5811da2004-03-16Henrik Grubbström (Grubba) //! //! Objects of this class are usually created through //! @[RoxenModule()->query_properties()].
c0b5b42004-04-20Martin Stjernholm #ifdef DAV_DEBUG #define DAV_WERROR(X...) werror(X) #else /* !DAV_DEBUG */ #define DAV_WERROR(X...) #endif /* DAV_DEBUG */
5811da2004-03-16Henrik Grubbström (Grubba)  //! Path for which these properties apply. string path; //! Status information about @[path] as returned by @[stat_file()]. Stat st;
c0b5b42004-04-20Martin Stjernholm //! The current request. RequestID id;
5811da2004-03-16Henrik Grubbström (Grubba) //! Create a new property set. //! //! Usually called via @[query_properties()]. static void create(string path, Stat st, RequestID id) { global::path = path;
c0b5b42004-04-20Martin Stjernholm  global::st = st; global::id = id;
5811da2004-03-16Henrik Grubbström (Grubba) } //! Destruction callback. //! //! Note that this function must unroll any uncommitted //! property changes. static void destroy() { }
c0b5b42004-04-20Martin Stjernholm //! Called by the default @[query_property] implementation to get the //! response headers a GET or HEAD request on @[path] would yield. //! It's used to fill in the properties that should reflect various //! response headers. mapping(string:string) get_response_headers(); private constant all_properties_common = (< "DAV:getcontentlength", "DAV:getcontenttype", "DAV:displayname", "DAV:resourcetype", "DAV:supportedlock", "DAV:iscollection", "DAV:isfolder", >); private constant all_properties_file = all_properties_common + (< "http://apache.org/dav/props/executable", >); private constant all_properties_dir = all_properties_common;
5811da2004-03-16Henrik Grubbström (Grubba) //! Returns a multiset with the names of all supported properties. //! //! @note //! Only properties that should be listed by @tt{<DAV:allprop/>@} //! are returned. //! //! @note //! The following properties are required to keep //! @tt{Microsoft Data Access Internet Publishing Provider DAV 1.1@} //! as supplied with @tt{Microsoft Windows 2000@} happy:
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @string //! @value "DAV:creationdate" //! RFC2518 13.1
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:displayname" //! RFC2518 13.2
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:getcontentlanguage" //! RFC2518 13.3
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:getcontentlength" //! RFC2518 13.4
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:getcontenttype" //! RFC2518 13.5
c0b5b42004-04-20Martin Stjernholm //! //! @value "DAV:getetag" //! RFC2518 13.6 //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:getlastmodified" //! RFC2518 13.7
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:resourcetype" //! RFC2518 13.9 //!
c0b5b42004-04-20Martin Stjernholm //! @value "DAV:supportedlock" //! RFC2518 13.11 //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:defaultdocument" //! @tt{draft-hopmann-collection-props-00@} 1.3 //! //! Specifies the default document for a collection. //! //! This property contains an URL that identifies the default //! document for a collection. This is intended for collection //! owners to be able to set a default document, for example //! @tt{index.html@} or @tt{default.html@}. If this property //! is absent, other means must be found to determine the default //! document. //! //! If this property is present, but null, the collection does //! not have a default document and the collection member listing //! should be used (or nothing). //! //! Note: The server implementation does not need to store this //! property in the normal property store (the property could well //! be live).
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:ishidden"
c0b5b42004-04-20Martin Stjernholm //! @tt{draft-hopmann-collection-props-00@} 1.6
5811da2004-03-16Henrik Grubbström (Grubba) //! //! Specifies whether or not a resource is hidden. //! //! This property identifies whether or not a resource is hidden. //! It contains either the values @tt{"1"@} or @tt{"0"@}. This //! can be considered a hint to the client UI: under normal //! conditions, for non-expert users, hidden files should not be //! exposed to users. The server may omit the hidden resource from //! some presentational listings, otherwise the client is responsible //! for removing hidden resources when displaying to the user. If //! this property is absent, the collection is not hidden. Since this //! property provides no actual form of protection to the resources, //! this MUST NOT be used as a form of access control and should
c0b5b42004-04-20Martin Stjernholm //! only be used for presentation purposes. //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:isstructureddocument" //! @tt{draft-hopmann-collection-props-00@} 1.7 //! //! Specifies whether the resource is a structured document. //! //! A structured document is a collection (@tt{DAV:iscollection@} //! should also be true), so @tt{COPY@}, @tt{MOVE@} and @tt{DELETE@} //! work as for a collection. The structured document may behave at //! times like a document. For example, clients may wish to display //! the resource as a document rather than as a collection. This //! contains either @tt{"1"@} (true) or @tt{"0"@}. If this property //! is absent, the collection is not a structured document. //! //! This property can also be considered a hint for the client UI: //! if the value of @tt{"DAV:isstructureddocument"@} is @tt{"1"@}, //! then the client UI may display this to the user as if it were //! single document. This can be very useful when the default //! document of a collection is an HTML page with a bunch of images //! which are the other resources in the collection: only the default //! document is intended to be viewed as a document, so the entire //! structure can appear as one document. //! //! A Structured document may contain collections. A structured //! document must have a default document (if the //! @tt{"DAV:defaultdocument"@} property is absent, the default //! document is assumed by the client to be @tt{index.html@}). //! //! @value "DAV:iscollection" //! @tt{draft-ietf-dasl-protocol-00@} 5.18 //! //! The @tt{DAV:iscollection@} XML element is a synthetic property //! whose value is defined only in the context of a query. The //! property is TRUE (the literal string @tt{"1"@}) of a resource //! if and only if a @tt{PROPFIND@} of the @tt{DAV:resourcetype@} //! property for that resource would contain the @tt{DAV:collection@} //! XML element. The property is FALSE (the literal string @tt{"0"@}) //! otherwise. //! //! Rationale: This property is provided in lieu of defining generic //! structure queries, which would suffice for this and for many more //! powerful queries, but seems inappropriate to standardize at this //! time. //! //! @value "DAV:isreadonly" //! Microsoft specific. //! //! The @tt{isreadonly@} field specifies whether an item can be //! modified or deleted. If this field is TRUE, the item cannot //! be modified or deleted.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:isroot" //! Microsoft specific. //! //! The @tt{DAV:isroot@} field specifies whether an item is a //! root folder.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:lastaccessed" //! Microsoft specific. //! //! The @tt{DAV:lastaccessed@} field specifies the date and time //! when an item was last accessed. This field is read-only.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:href" //! Microsoft specific. //! //! Read-only. The @b{absolute URL@} of an item.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:contentclass" //! Microsoft specific. //! //! The item's content class.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:parentname" //! Microsoft specific. //! //! The @tt{DAV:parentname@} field specifies the name of the folder //! that contains an item.
c0b5b42004-04-20Martin Stjernholm //!
5811da2004-03-16Henrik Grubbström (Grubba) //! @value "DAV:name" //! Microsoft specific. //! //! Unknown definition. //! @endstring
c0b5b42004-04-20Martin Stjernholm //! //! Also, the MS DAV client requires a type argument to be able to //! parse date/time fields correctly, even when they are formatted //! according to the standard. @[XMLPropStatNode.add_property] has //! special cases for this for @tt{DAV:creationdate@} and //! @tt{DAV:getlastmodified@}.
5811da2004-03-16Henrik Grubbström (Grubba) multiset(string) query_all_properties() {
c0b5b42004-04-20Martin Stjernholm  multiset(string) props = (st->isreg ? all_properties_file : all_properties_dir) + (<>); // This isn't necessary for the Content-Length and Content-Type // headers since RequestID.make_response_headers always sets those. mapping(string:string) hdrs = get_response_headers(); if (hdrs["Content-Language"]) props["DAV:getcontentlanguage"] = 1; if (hdrs->ETag) props["DAV:getetag"] = 1; if (hdrs["Last-Modified"]) props["DAV:getlastmodified"] = 1; return props;
5811da2004-03-16Henrik Grubbström (Grubba) } //! Returns the value of the specified property, or an error code //! mapping. //! //! The default implementation takes care of the most important RFC //! 2518 properties. //! //! @note //! Returning a string is shorthand for returning an array //! with a single text node. string|array(Parser.XML.Tree.Node)|mapping(string:mixed) query_property(string prop_name) { switch(prop_name) {
c0b5b42004-04-20Martin Stjernholm #if 0 // We don't really have any idea of the creation time in a unix // style file system.
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:creationdate": // RFC2518 13.1 int t = st->ctime; if (t > st->atime) t = st->atime; if (t > st->mtime) t = st->mtime; return Roxen.iso8601_date_time(t); // MS kludge.
c0b5b42004-04-20Martin Stjernholm #endif
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:displayname": // RFC2518 13.2
a7ab612004-03-24Anders Johansson  if ((path == "") || (path == "/")) return "/"; if (path[-1] == '/') return basename(path[..sizeof(path)-2]);
5811da2004-03-16Henrik Grubbström (Grubba)  return basename(path);
c0b5b42004-04-20Martin Stjernholm 
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:getcontentlanguage":// RFC2518 13.3
c0b5b42004-04-20Martin Stjernholm  return get_response_headers()["Content-Language"];
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:getcontentlength": // RFC2518 13.4
c0b5b42004-04-20Martin Stjernholm  return get_response_headers()["Content-Length"];
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:getcontenttype": // RFC2518 13.5
c0b5b42004-04-20Martin Stjernholm  return get_response_headers()["Content-Type"];
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:getetag": // RFC2518 13.6
c0b5b42004-04-20Martin Stjernholm  return get_response_headers()->ETag;
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:getlastmodified": // RFC2518 13.7
c0b5b42004-04-20Martin Stjernholm  return get_response_headers()["Last-Modified"];
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:resourcetype": // RFC2518 13.9 if (st->isdir) { return ({ Parser.XML.Tree.ElementNode("DAV:collection", ([])), // 12.2 }); } return 0;
c0b5b42004-04-20Martin Stjernholm 
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:supportedlock": // RFC2518 13.11 return "";
c0b5b42004-04-20Martin Stjernholm 
5811da2004-03-16Henrik Grubbström (Grubba)  case "http://apache.org/dav/props/executable": // http://www.webdav.org/mod_dav/: // // Name: executable // Namespace: http://apache.org/dav/props/ // Purpose: Describes the executable status of the resource. // Value: "T" | "F" (case is significant) // Description: This property is defined by mod_dav's default // repository, the "filesystem" repository. It // corresponds to the "executable" permission flag // in most filesystems. // // This property is not defined on collections. if (st->isreg) { if (st->mode & 0111) return "T"; return "F"; } break;
c0b5b42004-04-20Martin Stjernholm #if 0 // Need more interaction with directory listing modules to handle // this. case "DAV:defaultdocument": // draft-hopmann-collection-props-00 1.3 return ""; // Absence means not hidden. case "DAV:ishidden": // draft-hopmann-collection-props-00 1.6
5811da2004-03-16Henrik Grubbström (Grubba)  return "0";
c0b5b42004-04-20Martin Stjernholm  // Absence means not a structured document. case "DAV:isstructureddocument": // draft-hopmann-collection-props-00 1.7 return "0"; #endif
5811da2004-03-16Henrik Grubbström (Grubba)  case "DAV:iscollection": // draft-ietf-dasl-protocol-00 5.18 case "DAV:isfolder": // draft-hopmann-collection-props-00 1.5 if (st->isdir) { return "1"; } return "0";
c0b5b42004-04-20Martin Stjernholm 
5811da2004-03-16Henrik Grubbström (Grubba) #if 0 // The following are properties in the DAV namespace // that Microsoft has stolen.
c0b5b42004-04-20Martin Stjernholm  case "DAV:isreadonly": // MS if (!(st->mode & 0222)) { return "1"; }
5811da2004-03-16Henrik Grubbström (Grubba)  return "0";
c0b5b42004-04-20Martin Stjernholm  case "DAV:isroot": // MS if (path == "") return "1";
5811da2004-03-16Henrik Grubbström (Grubba)  return "0"; case "DAV:lastaccessed": // MS return Roxen.iso8601_date_time(st->atime); case "DAV:href": // MS return sprintf("%s://%s%s%s%s", id->port_obj->prot_name, id->misc->host || id->port_obj->ip || gethostname(), (id->port_obj->port == id->port_obj->port)? "":(":"+(string)id->port_obj->port), id->port_obj->path||"", combine_path(query_location(), path)); case "DAV:contentclass": // MS return ""; case "DAV:parentname": // MS return "";
c0b5b42004-04-20Martin Stjernholm  case "DAV:name": // MS return combine_path(query_location(), path);
5811da2004-03-16Henrik Grubbström (Grubba) #endif /* 0 */
c0b5b42004-04-20Martin Stjernholm 
5811da2004-03-16Henrik Grubbström (Grubba)  default: break; }
c0b5b42004-04-20Martin Stjernholm  DAV_WERROR("query_property(): Unimplemented property:%O\n", prop_name);
5811da2004-03-16Henrik Grubbström (Grubba)  // RFC 2518 8.1: // A request to retrieve the value of a property which does not // exist is an error and MUST be noted, if the response uses a // multistatus XML element, with a response XML element which // contains a 404 (Not Found) status value. return Roxen.http_status (Protocols.HTTP.HTTP_NOT_FOUND, "No such property."); } // RFC 2518 8.2 // Instructions MUST either all be executed or none executed. // Thus if any error occurs during procesing all executed // instructions MUST be undone and a proper error result // returned. //! Signal start of patching of properties for @[path]. //! //! At end of patching one of @[commit()] or @[unroll()] //! will be called. //! //! @returns //! @mixed //! @type zero //! Ok, patching will commence. //! @type mapping //! Return code. No patching will be performed. //! @endmixed //! //! @seealso //! @[set_property()], @[set_dead_property()], @[remove_property()] mapping(string:mixed) start() { return 0; } //! Patching of the properties for @[path] failed. //! Restore the state to what it was when @[start()] //! was called. void unroll() { } //! Patching of the properties for @[path] succeeded. void commit() { } //! Attempt to set property @[prop_name] to @[value]. //! //! @param value //! Value to set the node to. //! The case of an array of a single text node is special cased, //! and is sent as a @expr{string@}. //! //! @returns //! Returns a result mapping. May return @expr{0@} (zero) on success. //! //! @note //! Actual changing of the property should be done first //! when @[commit()] is called, or unrolled //! when @[unroll()] is called. //! //! @note //! Overloaded variants should only set the live properties they can //! handle and call the inherited implementation for all others. //! Setting of dead properties should be done through overloading of //! @[set_dead_property()]. This way, the live properties handled on //! any level in the inherit hierachy take precedence over dead //! properties. //! //! @note //! RFC 2518: Live property - A property whose semantics and syntax //! are enforced by the server. For example, the live //! @tt{"getcontentlength"@} property has its value, the length of the //! entity returned by a GET request, automatically calculated by //! the server. mapping(string:mixed) set_property(string prop_name, string|array(Parser.XML.Tree.Node) value) { switch(prop_name) { case "http://apache.org/dav/props/executable": // FIXME: Could probably be implemented R/W. // FALL_THROUGH case "DAV:displayname": // 13.2 case "DAV:getcontentlength": // 13.4 case "DAV:getcontenttype": // 13.5 case "DAV:getlastmodified": // 13.7 return Roxen.http_status (Protocols.HTTP.HTTP_CONFLICT, "Attempt to set read-only property."); } return set_dead_property(prop_name, value); } //! Attempt to set dead property @[prop_name] to @[value]. //! //! @returns //! Returns a result mapping. May return @expr{0@} (zero) on success. //! //! @note //! Actual changing of the property should be done first //! when @[commit()] is called, or unrolled //! when @[unroll()] is called. //! //! @note //! This function is called as a fallback by @[set_property()] //! if all else fails. //! //! @note //! The default implementation currently does not support setting //! of dead properties, and will return an error code. //! //! @note //! RFC 2518: Dead Property - A property whose semantics and syntax //! are not enforced by the server. The server only records the //! value of a dead property; the client is responsible for //! maintaining the consistency of the syntax and semantics of a //! dead property. mapping(string:mixed) set_dead_property(string prop_name, array(Parser.XML.Tree.Node) value) { return Roxen.http_status (Protocols.HTTP.HTTP_METHOD_INVALID, "Setting of dead properties is not supported."); } //! Attempt to remove the property @[prop_name]. //! //! @note //! Actual removal of the property should be done first //! when @[commit()] is called, or unrolled //! when @[unroll()] is called. //! //! @returns //! Returns a result mapping. May return @expr{0@} (zero) on success. //! //! @note //! The default implementation does not support deletion. mapping(string:mixed) remove_property(string prop_name) { switch(prop_name) { case "http://apache.org/dav/props/executable": case "DAV:displayname": // 13.2 case "DAV:getcontentlength": // 13.4 case "DAV:getcontenttype": // 13.5 case "DAV:getlastmodified": // 13.7 return Roxen.http_status (Protocols.HTTP.HTTP_CONFLICT, "Attempt to remove a read-only property."); } // RFC 2518 12.13.1: // Specifying the removal of a property that does not exist // is not an error. return 0; } //! RFC 2518 PROPFIND implementation for a single resource (i.e. not //! recursive). //! //! @param path //! @[query_location()]-relative path. //! @param mode //! Query mode. Currently one of //! @string mode //! @value "DAV:propname" //! Query names of supported properties. //! @value "DAV:allprop" //! Query all properties and their values. //! @value "DAV:prop" //! Query properties specified by @[filt] and their values. //! @endstring //! @param result //! Result object. //! @param filt //! Optional multiset of requested properties. If this parameter //! is @expr{0@} (zero) then all available properties are requested. mapping(string:mixed) find_properties(string mode, MultiStatus result, multiset(string)|void filt) { switch(mode) { case "DAV:propname":
c0b5b42004-04-20Martin Stjernholm  foreach(query_all_properties(); string prop_name;) {
5811da2004-03-16Henrik Grubbström (Grubba)  result->add_property(path, prop_name, ""); } return 0; case "DAV:allprop": if (filt) { // Used in http://sapportals.com/xmlns/cm/webdavinclude case. // (draft-reschke-webdav-allprop-include-04). filt |= query_all_properties(); } else { filt = query_all_properties(); } // FALL_THROUGH case "DAV:prop": foreach(indices(filt), string prop_name) { result->add_property(path, prop_name, query_property(prop_name)); } return 0; } // FIXME: Unsupported DAV operation. return 0; }