///|
pub(all) struct XmlProperty {
element : XmlElement
status : Int
} derive(Debug, Eq, ToJson)
///|
pub(all) struct DavResponse {
hrefs : Array[String]
status : Int?
properties : Array[XmlProperty]
description : String?
location : String?
errors : Array[XmlElement]
} derive(Debug, Eq, ToJson)
///|
fn status(s : String) -> Int raise DavError {
let pieces = s
.trim()
.to_owned()
.split(" ")
.filter(x => !x.is_empty())
.collect()
if pieces.length() < 2 ||
(pieces[0] != "HTTP/1.1" && pieces[0] != "HTTP/1.0") ||
pieces[1].length() != 3 ||
!pieces[1].iter().all(c => c >= '0' && c <= '9') {
raise Invalid("HTTP status line")
}
let code = @strconv.parse_int(pieces[1].to_owned()) catch {
_ => raise Invalid("status code")
}
if code < 100 || code > 599 {
raise Invalid("HTTP status code range")
}
code
}
///|
fn optional_text(n : XmlElement, name : String) -> String? raise DavError {
let items = children(n, name)
if items.length() > 1 {
raise Invalid("duplicate DAV:" + name)
}
if items.is_empty() {
None
} else {
Some(items[0].text_content())
}
}
///|
pub fn parse_responses(source : String) -> Array[DavResponse] raise DavError {
let root = parse_xml(source)
if root.uri != "DAV:" || root.name != "multistatus" {
raise Invalid("DAV multistatus root required")
}
let out = []
for r in children(root, "response") {
let hrefs = children(r, "href").map(h => h.text_content().trim().to_owned())
if hrefs.is_empty() || hrefs.any(s => s.is_empty()) {
raise Invalid("DAV href required")
}
let propstats = children(r, "propstat")
let response_status = optional_text(r, "status").map(status)
if (response_status == None) == propstats.is_empty() ||
(!propstats.is_empty() && hrefs.length() != 1) {
raise Invalid("response requires status or propstat, not both")
}
let properties = []
let errors : Array[XmlElement] = []
for error in children(r, "error") {
for e in element_children(error) {
errors.push(e)
}
}
for ps in propstats {
let code = status(one(ps, "status").text_content())
for element in element_children(one(ps, "prop")) {
properties.push({ element, status: code, })
}
for error in children(ps, "error") {
for e in element_children(error) {
errors.push(e)
}
}
}
let location = match children(r, "location") {
[] => None
[node] => Some(one(node, "href").text_content())
_ => raise Invalid("duplicate DAV location")
}
out.push({
hrefs,
status: response_status,
properties,
description: optional_text(r, "responsedescription"),
location,
errors,
})
}
out
}
///|
/// Compatibility view; use parse_responses for namespaced XML attributes and mixed content.
pub fn parse_multistatus(source : String) -> Array[Resource] raise DavError {
let out = []
for response in parse_responses(source) {
let properties : Array[Property] = response.properties.map(p => {
let e = p.element
let value = if e.uri == "DAV:" &&
e.name == "resourcetype" &&
!children(e, "collection").is_empty() {
"collection"
} else {
e.text_content()
}
{ uri: e.uri, name: e.name, value, status: p.status, }
})
for href in response.hrefs {
out.push({ href, status: response.status, properties, })
}
}
out
}