///|
fn normalize_root_join(root : String, relative : String) -> String {
let norm_root = if root.length() > 1 &&
(root.has_suffix("/") || root.has_suffix("\\")) {
root[:root.length() - 1].to_owned()
} else {
root
}
if relative == "" {
norm_root
} else if relative.has_prefix("/") || relative.has_prefix("\\") {
norm_root + relative
} else {
norm_root + "/" + relative
}
}
///|
/// Escape characters for safe HTML rendering.
pub fn escape_html(s : String) -> String {
@plan.escape_html(s)
}
///|
/// Pure HTML directory listing renderer shared with the Wasm engine.
pub fn render_directory_listing_html(
title_path : String,
entry_names : Array[(String, Bool)],
raw_query? : String?,
host~ : String = "localhost",
) -> String {
@plan.render_directory_listing_html(
title_path,
entry_names,
raw_query=raw_query.unwrap_or(None),
host~,
)
}
///|
/// D-13: execute the same pure decision machine used by the Wasm host.
async fn StaticEngine::plan_request(
self : StaticEngine,
request : @core.Request,
) -> PlanResult {
let files : Map[Int32, @native.File] = Map([])
let mut next_token = 0
let mut retained = -1
let mut charge = 0
let mut retain_listing = false
defer {
for token, file in files {
if token != retained {
file.close()
}
}
if !retain_listing {
self.runtime.release(directory=true, charge)
}
}
let mut step = @plan.start(self.config, request, self.mime_registry)
for ;; {
step = match step {
Open(path, complete) => {
let file = Some(self.open_file(path)) catch {
@native.NativeError::Missing => None
e => raise e
}
match file {
None => complete(None)
Some(file) => {
let token = next_token
next_token += 1
files[token] = file
complete(
Some({
token,
kind: file.kind,
size: file.size,
mtime: file.mtime,
}),
)
}
}
}
Prefix(file, count, complete) => {
let f = files[file.token]
complete(
f.read(
0L,
if file.size < count.to_int64() {
file.size.to_int()
} else {
count
},
),
)
}
List(path, complete) => {
let base = request.target.length() * 32 + 16384
self.runtime.reserve(directory=true, base)
charge += base
let dir = self.open_file(path)
defer dir.close()
let (names, name_charge) = dir.names(
self.config.limits.directory_metadata_bytes.to_int64(),
)
charge += name_charge
let entries : Array[@plan.ListingEntry] = []
for name in names {
if !self.config.show_dotfiles && name.has_prefix(".") {
continue
}
let file = self.open_file(path + "/" + name) catch {
@native.NativeError::Missing => continue
e => raise e
}
entries.push({
name,
is_dir: file.kind == 2,
size: if file.kind == 2 {
0L
} else {
file.size
},
mtime_sec: file.mtime,
is_companion: false,
has_br: false,
has_gz: false,
})
file.close()
}
complete(entries)
}
Next => return Next
Done(response) => {
// Even HEAD/304 and generated errors must finish all opened observations.
for file in files.values() {
file.check()
}
let body = match response.body {
Empty => PlanBody::Empty
Bytes(bytes) => PlanBody::Bytes(bytes)
FileRegion(file, offset, length) => {
retained = file.token
PlanBody::FileRegion(files[file.token], offset, length)
}
Listing(stream) => {
retain_listing = true
PlanBody::Listing({ stream, metadata_charge: charge, })
}
}
return Handled({
status: response.status,
headers: response.headers,
body,
})
}
}
}
}