Files
herolib/lib/dav/webdav/model_propfind.v
2025-03-24 06:44:39 +01:00

234 lines
5.3 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.allprop
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
}
}
// Response represents a WebDAV response for a resource
pub struct PropfindResponse {
pub:
href string
found_props []Property
not_found_props []Property
}
fn (r PropfindResponse) xml() xml.XMLNodeContents {
return xml.XMLNode{
name: 'D:response'
children: [
xml.XMLNode{
name: 'D:href'
children: [xml.XMLNodeContents(r.href)]
},
xml.XMLNode{
name: 'D:propstat'
children: [xml.XMLNode{
name: 'D:prop'
children: r.found_props.map(it.xml())
}, xml.XMLNode{
name: 'D:status'
children: [xml.XMLNodeContents('HTTP/1.1 200 OK')]
}]
},
]
}
}
// generate_propfind_response generates a PROPFIND response XML string from Response structs
pub fn (r []PropfindResponse) xml() string {
// Create multistatus root node
multistatus_node := xml.XMLNode{
name: 'D:multistatus'
attributes: {
'xmlns:D': 'DAV:'
}
children: r.map(it.xml())
}
// Create a new XML document with the root node
doc := xml.XMLDocument{
version: '1.0'
root: multistatus_node
}
// Generate XML string
doc.validate() or { panic('this should never happen ${err}') }
return format_xml(doc.str())
}
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
}
// parse_xml takes an XML string and returns a cleaned version with whitespace removed between tags
pub fn format_xml(xml_str string) string {
mut result := ''
mut i := 0
mut in_tag := false
mut content_start := 0
// Process the string character by character
for i < xml_str.len {
ch := xml_str[i]
// Start of a tag
if ch == `<` {
// If we were collecting content between tags, process it
if !in_tag && i > content_start {
// Get the content between tags and trim whitespace
content := xml_str[content_start..i].trim_space()
result += content
}
in_tag = true
result += '<'
}
// End of a tag
else if ch == `>` {
in_tag = false
result += '>'
content_start = i + 1
}
// Inside a tag - preserve all characters including whitespace
else if in_tag {
result += ch.ascii_str()
}
// Outside a tag - only add non-whitespace or handle whitespace in content
else if !in_tag {
// We'll collect and process this content when we reach the next tag
// or at the end of the string
}
i++
}
// Handle any remaining content at the end of the string
if !in_tag && content_start < xml_str.len {
content := xml_str[content_start..].trim_space()
result += content
}
return result
}