Files
herolib/lib/dav/webdav/app_propfind.v

206 lines
5.0 KiB
V

module webdav
import encoding.xml
import log
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.vfs.vfs_db
import os
import time
import net.http
import veb
// PropfindRequest represents a parsed PROPFIND request
pub struct PropfindRequest {
pub:
typ PropfindType
props []string // Property names if typ is prop
depth Depth // Depth of the request (0, 1, or -1 for infinity)
xml_content string // Original XML content
}
pub enum Depth {
infinity = -1
zero = 0
one = 1
}
// PropfindType represents the type of PROPFIND request
pub enum PropfindType {
allprop // Request all properties
propname // Request property names only
prop // Request specific properties
invalid // Invalid request
}
// parse_propfind_xml parses the XML body of a PROPFIND request
pub fn parse_propfind_xml(req http.Request) !PropfindRequest {
data := req.data
// Parse Depth header
depth_str := req.header.get_custom('Depth') or { '0' }
depth := parse_depth(depth_str)
if data.len == 0 {
// If no body is provided, default to allprop
return PropfindRequest{
typ: .allprop
depth: depth
xml_content: ''
}
}
doc := xml.XMLDocument.from_string(data) or {
return error('Failed to parse XML: ${err}')
}
root := doc.root
if root.name.to_lower() != 'propfind' && !root.name.ends_with(':propfind') {
return error('Invalid PROPFIND request: root element must be propfind')
}
mut typ := PropfindType.invalid
mut props := []string{}
// Check for allprop, propname, or prop elements
for child in root.children {
if child is xml.XMLNode {
node := child as xml.XMLNode
// Check for allprop
if node.name == 'allprop' || node.name == 'D:allprop' {
typ = .allprop
break
}
// Check for propname
if node.name == 'propname' || node.name == 'D:propname' {
typ = .propname
break
}
// Check for prop
if node.name == 'prop' || node.name == 'D:prop' {
typ = .prop
// Extract property names
for prop_child in node.children {
if prop_child is xml.XMLNode {
prop_node := prop_child as xml.XMLNode
props << prop_node.name
}
}
break
}
}
}
if typ == .invalid {
return error('Invalid PROPFIND request: missing prop, allprop, or propname element')
}
return PropfindRequest{
typ: typ
props: props
depth: depth
xml_content: data
}
}
// parse_depth parses the Depth header value
pub fn parse_depth(depth_str string) Depth {
if depth_str == 'infinity' { return .infinity}
else if depth_str == '0' { return .zero}
else if depth_str == '1' { return .one}
else {
log.warn('[WebDAV] Invalid Depth header value: ${depth_str}, defaulting to infinity')
return .infinity
}
}
// returns the properties of a filesystem entry
fn get_properties(entry &vfs.FSEntry) []Property {
mut props := []Property{}
metadata := entry.get_metadata()
// Display name
props << DisplayName(metadata.name)
props << GetLastModified(format_iso8601(metadata.modified_time()))
props << GetContentType(if entry.is_dir() {'httpd/unix-directory'} else {get_file_content_type(entry.get_path())})
props << ResourceType(entry.is_dir())
// Content length (only for files)
if !entry.is_dir() {
props << GetContentLength(metadata.size.str())
}
// Creation date
props << CreationDate(format_iso8601(metadata.created_time()))
return props
}
// Response represents a WebDAV response for a resource
pub struct Response {
pub:
href string
found_props []Property
not_found_props []Property
}
fn (r Response) xml() string {
return '<D:response>\n<D:href>${r.href}</D:href>
<D:propstat><D:prop>${r.found_props.map(it.xml()).join_lines()}</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat>
</D:response>'
}
// generate_propfind_response generates a PROPFIND response XML string from Response structs
pub fn (r []Response) xml () string {
return '<?xml version="1.0" encoding="UTF-8"?>\n<D:multistatus xmlns:D="DAV:">
${r.map(it.xml()).join_lines()}\n</D:multistatus>'
}
fn get_file_content_type(path string) string {
ext := path.all_after_last('.')
content_type := if v := veb.mime_types[ext] {
v
} else {
'text/plain; charset=utf-8'
}
return content_type
}
// get_responses returns all properties for the given path and depth
fn (mut app App) get_responses(entry vfs.FSEntry, req PropfindRequest) ![]Response {
mut responses := []Response{}
path := if entry.is_dir() && entry.get_path() != '/' {
'${entry.get_path()}/'
} else {
entry.get_path()
}
log.debug('Finfing for ${path}')
// main entry response
responses << Response {
href: path
// not_found: entry.get_unfound_properties(req)
found_props: get_properties(entry)
}
if !entry.is_dir() || req.depth == .zero {
return responses
}
entries := app.vfs.dir_list(path) or {
log.error('Failed to list directory for ${path} ${err}')
return responses }
for e in entries {
responses << app.get_responses(e, PropfindRequest {
...req,
depth: if req.depth == .one { .zero } else { .infinity }
})!
}
return responses
}