unit class HTTP::MediaType; class X::MediaTypeParser::IllegalMediaType is Exception { has $.media-type; method message() { die "Illegal media type: '$.media-type'"; } } my grammar MediaTypeGrammar { token TOP { } # https://tools.ietf.org/html/rfc7231#section-3.1.1.1 token media-type { "/" [ <.OWS> ";" <.OWS> ]* } token type { <._token> } token subtype { <._token> } token parameter { "=" } token parameter-key { <._token> } token parameter-value { <._token> || <.quoted-string> } # https://tools.ietf.org/html/rfc7230#section-3.2.3 # optional white space token OWS { [ <.SP> || <.HTAB> ]* } # https://tools.ietf.org/html/rfc7230#section-3.2.6 token _token { <.tchar>+ } token tchar { || < ! # $ % & ' * + - . ^ _ ` | ~ > || <.DIGIT> || <.ALPHA> } token quoted-string { <.DQUOTE> [<.qdtext> || <.quoted-pair>]* <.DQUOTE> } token qdtext { <.HTAB> || <.SP> || "\x21" || <[\x23 .. \x5B]> || <[\x5D .. \x7E]> || <.obs-text> } token obs-text { <[\x80..\xff]> } token quoted-pair { '\\' [ <.HTAB> || <.SP> || <.VCHAR> || <.obs-text> ] } # https://tools.ietf.org/html/rfc5234#appendix-B.1 token DIGIT { <[ 0..9 ]> } token ALPHA { <[ A..Z a..z ]> } token SP { "\x20" } token HTAB { "\x09" } token DQUOTE { "\x22" } # visible (printing) characters token VCHAR { <[\x21..\x7E]> } } my class MediaTypeAction { method TOP($/) { $/.make: $.made() } method media-type($/) { $/.make: HTTP::MediaType.new( type => $.made ~ "/" ~ $.made, major-type => $.made, sub-type => $.made, parameters => $ยป.made()) } method type($/) { $/.make: ~$/ } method subtype($/) { $/.make: ~$/ } method parameter($/) { $/.make: $.made() => $.made } method parameter-key($/) { $/.make: ~$/ } method parameter-value($/) { $/.make: ~$/ } } has Str $.type; has Str $.major-type; has Str $.sub-type; has %.parameters; method charset(HTTP::MediaType:D:) returns Str { (%!parameters // '').lc; } method parse(Str $content-type) { my $result = MediaTypeGrammar.parse($content-type, :actions(MediaTypeAction)); if $result { $result.made; } else { X::MediaTypeParser::IllegalMediaType.new(media-type => $content-type) .throw(); } } proto method param(|c) { * } multi method param(Str $name) { %!parameters{$name}; } multi method param(Str $name, Str $value) { %!parameters{$name} = $value; } method Str(HTTP::MediaType:D:) { my Str $s = $.type; if %!parameters { $s ~= "; " ~ %!parameters.kv.map({ $^a ~ "=" ~ $^b }).join(";") } $s; }