RFC 6266 Content-Disposition parser, serializer, filename resolver and safety audit toolkit for MoonBit.
let header = "attachment; filename=\"café.txt\"; filename*=UTF-8'en'caf%C3%A9.txt"
match @cd.parse_content_disposition(header) {
Err(e) => println("parse failed: \{e.to_display()}")
Ok(cd) => {
match @cd.resolve_filename(cd) {
Err(e) => println("resolve failed: \{e.to_display()}")
Ok(sel) => {
// sel.selected() == "café.txt", sel.source() == FilenameStar
match @cd.sanitize_portable_filename(sel.selected()) {
Err(e) => println("sanitize failed: \{e.to_display()}")
Ok(result) => println("safe download name: \{result.safe()}")
}
}
}
}
}let value = @cd.generate_attachment("café report.pdf")
// "attachment; filename=\"café report.pdf\"; filename*=UTF-8''caf%C3%A9%20report.pdf"let canonical = @cd.canonicalize_content_disposition(
"ATTACHMENT; FILE=fallback.txt; FILE*=UTF-8''caf%C3%A9.pdf",
)
// "attachment; filename=fallback.txt; filename*=UTF-8''caf%C3%A9.pdf"
match @cd.audit_header("attachment; filename=\"../../install.exe\"") {
Err(e) => println("audit failed: \{e.to_display()}")
Ok(report) =>
for issue in report.issues() {
println("[ \{issue.severity().to_string()} ] \{issue.kind().to_string()}: \{issue.message()}")
}
}parse → resolve → sanitize
│ │ └─ FilenamePolicy (Portable / WindowsLike / PosixLike)
│ └─ RFC 6266 §4.3 precedence, advisory warnings
└─ ContentDisposition model
├─ serialize / canonicalize
├─ generate (the inverse of parse)
└─ audit (advisory, never mutates)parse <value> serialize <value> canonicalize <value>
resolve <value> sanitize <name> [profile] generate <type> <name>
audit <value> limits [preset] profiles
version helpmoon run examples/parse
moon run examples/resolve -- "attachment; filename=fallback.txt; filename*=UTF-8'en'caf%C3%A9.txt"
moon run examples/sanitize
moon run examples/generate -- "café report.pdf"
moon run examples/canonicalize
moon run examples/auditmoon build # library
moon build cmd/disposition-tool --target wasm-gc
moon test # native (default)
moon test --target js
moon test --target wasm-gc
scripts/verify_all.ps1 # end-to-end verification (three targets)
scripts/count_code.py # source-line reportpub suberror DispositionError {
DispositionError(DispositionErrorStage, DispositionErrorKind, Int, String)
}pub struct AuditIssue {
severity : AuditSeverity
kind : AuditKind
parameter : String
message : String
}pub enum AuditKind {
MissingFilename
EmptyFilename
BothFilenameAndFilenameStar
PlainFilenameOnly
FilenameStarWithoutFallback
PathSeparatorInFilename
ControlCharacterInFilename
NonAsciiUnquotedFilename
MissingFilenameStarForNonAscii
UnsupportedCharset
InvalidLanguageTag
DuplicateParameter
RecoveryApplied
ExtensionRisk
ReservedWindowsName
TrailingDotOrSpace
LongFilename
ExtensionDispositionType
}pub enum AuditSeverity {
Info
Warning
High
}pub struct ContentDisposition {
disposition_type : DispositionType
parameters : Array[DispositionParameter]
raw_disposition_type : String?
}fn ContentDisposition::get_parameter(self : ContentDisposition, name : String) -> DispositionParameter?fn ContentDisposition::semantic_equal(self : ContentDisposition, other : ContentDisposition) -> Boolpub enum DispositionErrorKind {
EmptyInput
InvalidDispositionType
ExpectedToken
UnexpectedCharacter
MissingEquals
MissingParameterValue
InvalidParameterName
DuplicateParameter
UnterminatedQuotedString
InvalidQuotedPair
InvalidControlCharacter
InvalidExtendedValue
MissingCharset
InvalidCharset
UnsupportedCharset
InvalidLanguage
InvalidPercentEncoding
InvalidUtf8
InvalidFilename
UnsafeFilename
LimitExceeded
TrailingInput
}pub enum DispositionErrorStage {
Input
DispositionType
ParameterName
ParameterValue
Token
QuotedString
ExtendedValue
PercentEncoding
Charset
FilenameResolution
FilenamePolicy
Serialization
Limit
}fn DispositionParameter::semantic_equal(self : DispositionParameter, other : DispositionParameter) -> Boolpub struct DispositionParse {
content_disposition : ContentDisposition
duplicates : Array[String]
recoveries : Array[String]
mode : ParseMode
}pub enum DispositionType {
Inline
Attachment
Extension(String)
}pub struct ExtendedValue {
charset : String
language : String?
value : String
}fn ExtensionPolicy::enabled(allow_list : Array[String], deny_list : Array[String]) -> ExtensionPolicypub struct FilenamePolicy {
profile : PolicyProfile
max_length : Int
extension : ExtensionPolicy
windows_reserved : WindowsReservedPolicy
}fn FilenamePolicy::with_extension(self : FilenamePolicy, extension : ExtensionPolicy) -> FilenamePolicyfn FilenamePolicy::with_windows_reserved(self : FilenamePolicy, windows_reserved : WindowsReservedPolicy) -> FilenamePolicypub struct FilenameSelection {
selected : String
source : FilenameSource
fallback : Bool
warnings : Array[String]
}pub enum FilenameSource {
Filename
FilenameStar
}pub struct GenerateOptions {
include_filename_star : Bool
always_filename_star : Bool
language : String?
}pub struct Limits {
max_input_bytes : Int
max_parameters : Int
max_parameter_name_bytes : Int
max_parameter_value_bytes : Int
max_filename_bytes : Int
max_extended_value_bytes : Int
max_context_bytes : Int
}pub struct SafeFilenameResult {
original : String
safe : String
changed : Bool
issues : Array[String]
}pub struct Scanner {
bytes : Bytes
len : Int
pos : Int
}pub struct WindowsReservedPolicy {
enabled : Bool
}fn audit_filename_with_media_type(name : String, media_type : String, policy : FilenamePolicy) -> AuditReportfn can_be_token(value : String) -> Boolfn canonical_charset(charset : String) -> String?fn content_disposition_with_raw(disposition_type : DispositionType, raw : String) -> ContentDispositionfn disposition_error(stage : DispositionErrorStage, kind : DispositionErrorKind, context : String) -> DispositionErrorfn disposition_error_at(stage : DispositionErrorStage, kind : DispositionErrorKind, offset : Int, context : String) -> DispositionErrorfn fits_iso8859_1(value : String) -> Boolfn generate_attachment_with_options(filename : String, options : GenerateOptions) -> Result[String, DispositionError]fn generate_content_disposition(disposition_type : DispositionType, filename : String, options : GenerateOptions) -> Result[String, DispositionError]fn generate_inline_with_options(filename : String, options : GenerateOptions) -> Result[String, DispositionError]fn has_percent_encoding(value : String) -> Boolfn hex_value(b : Byte) -> Intfn is_control_byte(b : Byte) -> Boolfn is_extended_param_name(name : String) -> Boolfn is_header_safe(value : String) -> Boolfn is_obs_text(b : Byte) -> Boolfn is_ows_byte(b : Byte) -> Boolfn is_path_separator(b : Byte) -> Boolfn is_separator_byte(b : Byte) -> Boolfn is_supported_charset(charset : String) -> Boolfn is_token_char(b : Byte) -> Boolfn library_version() -> Stringfn max_context_bytes() -> Intfn parse_content_disposition_detailed(input : String, options : ParseOptions) -> Result[DispositionParse, DispositionError]fn parse_content_disposition_with_options(input : String, options : ParseOptions) -> Result[ContentDisposition, DispositionError]fn parse_extended_value(cursor : Scanner, limits : Limits) -> Result[ExtendedValue, DispositionError] raisefn parse_extended_value_string(input : String, limits : Limits) -> Result[ExtendedValue, DispositionError]fn parse_parameter(cursor : Scanner, limits : Limits, mode : ParseMode, collector : ParseCollector) -> Result[DispositionParameter, DispositionError]fn percent_decode_bytes(bytes : Bytes, start : Int, end : Int) -> Result[Array[Byte], DispositionError]fn percent_encode_attr_value(value : String) -> Stringfn qdtext_char(b : Byte) -> Boolfn quoted_pair_ok(b : Byte) -> Boolfn sanitize_filename(name : String, policy : FilenamePolicy) -> Result[SafeFilenameResult, DispositionError]fn serialize_content_disposition_preserve_case(cd : ContentDisposition) -> Result[String, DispositionError]fn serialize_quoted_string(value : String) -> Stringfn valid_language_tag(tag : String) -> Boolfn validate_token(value : String) -> BoolRFC 6266 Content-Disposition parser, serializer, filename resolver and safety audit toolkit for MoonBit.