///|
let wasm_source_mapping_url_name = "sourceMappingURL"
///|
priv struct WasmSourceMappingSection {
start : Int
end : Int
url_start : Int
url_end : Int
}
///|
fn decode_wasm_u32(
input : Bytes,
start : Int,
limit : Int,
) -> (Int, Int) raise SourceMapError {
let mut offset = start
let mut value = 0
let mut shift = 0
for count = 0; count < 5; count = count + 1 {
if offset >= limit {
raise SourceMapError::At(start, "truncated Wasm unsigned LEB128")
}
let byte = input[offset].to_int()
if count == 4 && (byte & 0xf0) != 0 {
raise SourceMapError::At(start, "Wasm unsigned LEB128 exceeds u32")
}
if count == 4 && (byte & 0x08) != 0 {
raise SourceMapError::At(
start, "Wasm unsigned LEB128 exceeds supported Int range",
)
}
value = value | ((byte & 0x7f) << shift)
offset += 1
if (byte & 0x80) == 0 {
return (value, offset)
}
shift += 7
}
raise SourceMapError::At(start, "Wasm unsigned LEB128 exceeds u32")
}
///|
fn require_wasm_header(input : Bytes) -> Unit raise SourceMapError {
if input.length() < 8 {
raise SourceMapError::At(0, "truncated WebAssembly header")
}
let expected : Array[Byte] = [
b'\x00', b'\x61', b'\x73', b'\x6d', b'\x01', b'\x00', b'\x00', b'\x00',
]
for i in 0.. WasmSourceMappingSection? raise SourceMapError {
require_wasm_header(input)
let mut offset = 8
let mut found : WasmSourceMappingSection? = None
while offset < input.length() {
let section_start = offset
let section_id = input[offset].to_int()
offset += 1
let (payload_size, payload_start) = decode_wasm_u32(
input,
offset,
input.length(),
)
if payload_size > input.length() - payload_start {
raise SourceMapError::At(
section_start, "Wasm section exceeds input length",
)
}
let section_end = payload_start + payload_size
if section_id == 0 {
let (name_size, name_start) = decode_wasm_u32(
input, payload_start, section_end,
)
if name_size > section_end - name_start {
raise SourceMapError::At(
payload_start, "Wasm custom section name exceeds payload",
)
}
let name_end = name_start + name_size
let name = @utf8.decode(input.view(start=name_start, end=name_end)) catch {
_ =>
raise SourceMapError::At(
name_start, "Wasm custom section name is not UTF-8",
)
}
if name == wasm_source_mapping_url_name {
if found is Some(_) {
raise SourceMapError::At(
section_start, "duplicate sourceMappingURL custom section",
)
}
let (url_size, url_start) = decode_wasm_u32(
input, name_end, section_end,
)
if url_size > section_end - url_start {
raise SourceMapError::At(name_end, "sourceMappingURL exceeds payload")
}
let url_end = url_start + url_size
if url_end != section_end {
raise SourceMapError::At(
url_end, "sourceMappingURL has trailing bytes",
)
}
found = Some({
start: section_start,
end: section_end,
url_start,
url_end,
})
}
}
offset = section_end
}
found
}
///|
fn append_wasm_bytes(
output : Array[Byte],
input : Bytes,
start : Int,
end : Int,
) -> Unit {
for i in start.. Unit {
let mut remaining = value
for ;; {
let mut byte = remaining & 0x7f
remaining = remaining >> 7
if remaining != 0 {
byte = byte | 0x80
}
output.push(byte.to_byte())
if remaining == 0 {
break
}
}
}
///|
fn make_source_mapping_section(url : String) -> Bytes {
let name = @utf8.encode(wasm_source_mapping_url_name)
let encoded_url = @utf8.encode(url)
let payload : Array[Byte] = []
append_wasm_u32(payload, name.length())
append_wasm_bytes(payload, name, 0, name.length())
append_wasm_u32(payload, encoded_url.length())
append_wasm_bytes(payload, encoded_url, 0, encoded_url.length())
let section : Array[Byte] = [b'\x00']
append_wasm_u32(section, payload.length())
for byte in payload {
section.push(byte)
}
Bytes::from_array(section)
}
///|
/// Read the URL stored in a WebAssembly `sourceMappingURL` custom section.
pub fn wasm_source_mapping_url(wasm : Bytes) -> String? raise SourceMapError {
match find_wasm_source_mapping_section(wasm) {
None => None
Some(section) =>
Some(
@utf8.decode(wasm.view(start=section.url_start, end=section.url_end)) catch {
_ =>
raise SourceMapError::At(
section.url_start,
"sourceMappingURL payload is not UTF-8",
)
},
)
}
}
///|
/// Add or replace a WebAssembly `sourceMappingURL` custom section.
pub fn wasm_set_source_mapping_url(
wasm : Bytes,
url : String,
) -> Bytes raise SourceMapError {
let existing = find_wasm_source_mapping_section(wasm)
let replacement = make_source_mapping_section(url)
let output : Array[Byte] = []
match existing {
None => {
append_wasm_bytes(output, wasm, 0, wasm.length())
append_wasm_bytes(output, replacement, 0, replacement.length())
}
Some(section) => {
append_wasm_bytes(output, wasm, 0, section.start)
append_wasm_bytes(output, replacement, 0, replacement.length())
append_wasm_bytes(output, wasm, section.end, wasm.length())
}
}
Bytes::from_array(output)
}
///|
/// Remove a WebAssembly `sourceMappingURL` custom section when present.
pub fn wasm_remove_source_mapping_url(
wasm : Bytes,
) -> Bytes raise SourceMapError {
match find_wasm_source_mapping_section(wasm) {
None => wasm
Some(section) => {
let output : Array[Byte] = []
append_wasm_bytes(output, wasm, 0, section.start)
append_wasm_bytes(output, wasm, section.end, wasm.length())
Bytes::from_array(output)
}
}
}