Explorar el Código

Auto merge of #432 - servo:data, r=nox

Add data-url crate

This is an implementation of https://fetch.spec.whatwg.org/#data-urls. It is independent of the `url` crate, but it seems within the scope of this repository and I didn’t feel like creating yet another repository :)

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-url/432)
<!-- Reviewable:end -->
bors-servo hace 8 años
padre
commit
e447349cbb

+ 20 - 10
.travis.yml

@@ -1,19 +1,29 @@
 language: rust
-script: make test
 
 jobs:
   include:
-  - rust: nightly
-  - rust: beta
-  - rust: stable
   - rust: 1.17.0
-  - rust: nightly
-    env:
-    - WASM32=true  # just to show it in travis UI
     install:
-    - rustup target add wasm32-unknown-unknown
-    script:
-    - cargo build --target=wasm32-unknown-unknown
+    # --precise requires Cargo.lock to already exist
+    - cargo update
+    # getopts is only used in tests. Its versions 0.2.16+ don’t build on 1.17.0
+    - cargo update -p getopts --precise 0.2.15
+    # data-url uses pub(crate) which is unstable in 1.17
+    script: cargo test --all-features -p url -p idna -p percent-encoding -p url_serde
+
+  - rust: stable
+    script: cargo test --all-features --all
+
+  - rust: beta
+    script: cargo test --all-features --all
+
+  - rust: nightly
+    script: cargo test --all-features --all
+
+  - rust: nightly
+    env: TARGET=WASM32  # For job list UI
+    install: rustup target add wasm32-unknown-unknown
+    script: cargo build --all --target=wasm32-unknown-unknown
 
 notifications:
   webhooks: http://build.servo.org:54856/travis

+ 1 - 1
Cargo.toml

@@ -18,7 +18,7 @@ travis-ci = { repository = "servo/rust-url" }
 appveyor = { repository = "Manishearth/rust-url" }
 
 [workspace]
-members = [".", "idna", "percent_encoding", "url_serde"]
+members = [".", "idna", "percent_encoding", "url_serde", "data-url"]
 
 [[test]]
 name = "unit"

+ 0 - 6
Makefile

@@ -1,6 +0,0 @@
-test:
-	cargo test --features "query_encoding serde rustc-serialize heapsize"
-	(cd idna && cargo test)
-	(cd url_serde && cargo test)
-
-.PHONY: test

+ 22 - 0
data-url/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "data-url"
+version = "0.1.0"
+authors = ["Simon Sapin <simon.sapin@exyr.org>"]
+description = "Processing of data: URL according to WHATWG’s Fetch Standard"
+repository = "https://github.com/servo/rust-url"
+license = "MIT OR Apache-2.0"
+
+[dependencies]
+matches = "0.1"
+
+[dev-dependencies]
+rustc-test = "0.3"
+serde = {version = "1.0", features = ["derive"]}
+serde_json = "1.0"
+
+[lib]
+test = false
+
+[[test]]
+name = "wpt"
+harness = false

+ 1 - 0
data-url/LICENSE-APACHE

@@ -0,0 +1 @@
+../LICENSE-APACHE

+ 1 - 0
data-url/LICENSE-MIT

@@ -0,0 +1 @@
+../LICENSE-MIT

+ 21 - 0
data-url/README.md

@@ -0,0 +1,21 @@
+# data-url
+
+![crates.io](https://img.shields.io/crates/v/url.svg)
+![docs.rs](https://docs.rs/data-url/)
+
+Processing of `data:` URLs in Rust according to the Fetch Standard:
+<https://fetch.spec.whatwg.org/#data-urls>
+but starting from a string rather than a parsed URL to avoid extra copies.
+
+```rust
+use data_url::{DataUrl, mime};
+//!
+let url = DataUrl::process("data:,Hello%20World!").unwrap();
+let (body, fragment) = url.decode_to_vec().unwrap();
+//!
+assert_eq!(url.mime_type().type_, "text");
+assert_eq!(url.mime_type().subtype, "plain");
+assert_eq!(url.mime_type().get_parameter("charset"), Some("US-ASCII"));
+assert_eq!(body, b"Hello World!");
+assert!(fragment.is_none());
+```

+ 168 - 0
data-url/src/forgiving_base64.rs

@@ -0,0 +1,168 @@
+//! <https://infra.spec.whatwg.org/#forgiving-base64-decode>
+
+#[derive(Debug)]
+pub struct InvalidBase64(InvalidBase64Details);
+
+#[derive(Debug)]
+enum InvalidBase64Details {
+    UnexpectedSymbol(u8),
+    AlphabetSymbolAfterPadding,
+    LoneAlphabetSymbol,
+    Padding,
+}
+
+#[derive(Debug)]
+pub enum DecodeError<E> {
+    InvalidBase64(InvalidBase64),
+    WriteError(E),
+}
+
+impl<E> From<InvalidBase64Details> for DecodeError<E> {
+    fn from(e: InvalidBase64Details) -> Self {
+        DecodeError::InvalidBase64(InvalidBase64(e))
+    }
+}
+
+pub(crate) enum Impossible {}
+
+impl From<DecodeError<Impossible>> for InvalidBase64 {
+    fn from(e: DecodeError<Impossible>) -> Self {
+        match e {
+            DecodeError::InvalidBase64(e) => e,
+            DecodeError::WriteError(e) => match e {}
+        }
+    }
+}
+
+/// `input` is assumed to be in an ASCII-compatible encoding
+pub fn decode_to_vec(input: &[u8]) -> Result<Vec<u8>, InvalidBase64> {
+    let mut v = Vec::new();
+    {
+        let mut decoder = Decoder::new(|bytes| Ok(v.extend_from_slice(bytes)));
+        decoder.feed(input)?;
+        decoder.finish()?;
+    }
+    Ok(v)
+}
+
+/// <https://infra.spec.whatwg.org/#forgiving-base64-decode>
+pub struct Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
+    write_bytes: F,
+    bit_buffer: u32,
+    buffer_bit_length: u8,
+    padding_symbols: u8,
+}
+
+impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
+    pub fn new(write_bytes: F) -> Self {
+        Self {
+            write_bytes,
+            bit_buffer: 0,
+            buffer_bit_length: 0,
+            padding_symbols: 0,
+        }
+    }
+
+    /// Feed to the decoder partial input in an ASCII-compatible encoding
+    pub fn feed(&mut self, input: &[u8]) -> Result<(), DecodeError<E>> {
+        for &byte in input.iter() {
+            let value = BASE64_DECODE_TABLE[byte as usize];
+            if value < 0 {
+                // A character that’s not part of the alphabet
+
+                // Remove ASCII whitespace
+                if matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | b'\x0C') {
+                    continue
+                }
+
+                if byte == b'=' {
+                    self.padding_symbols = self.padding_symbols.saturating_add(1);
+                    continue
+                }
+
+                Err(InvalidBase64Details::UnexpectedSymbol(byte))?
+            }
+            if self.padding_symbols > 0 {
+                Err(InvalidBase64Details::AlphabetSymbolAfterPadding)?
+            }
+            self.bit_buffer <<= 6;
+            self.bit_buffer |= value as u32;
+            // 18 before incrementing means we’ve just reached 24
+            if self.buffer_bit_length < 18 {
+                self.buffer_bit_length += 6;
+            } else {
+                // We’ve accumulated four times 6 bits, which equals three times 8 bits.
+                let byte_buffer = [
+                    (self.bit_buffer >> 16) as u8,
+                    (self.bit_buffer >> 8) as u8,
+                    self.bit_buffer as u8,
+                ];
+                (self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
+                self.buffer_bit_length = 0;
+                // No need to reset bit_buffer,
+                // since next time we’re only gonna read relevant bits.
+            }
+        }
+        Ok(())
+    }
+
+    /// Call this to signal the end of the input
+    pub fn finish(mut self) -> Result<(), DecodeError<E>> {
+        match (self.buffer_bit_length, self.padding_symbols) {
+            (0, 0) => {
+                // A multiple of four of alphabet symbols, and nothing else.
+            }
+            (12, 2) | (12, 0) => {
+                // A multiple of four of alphabet symbols, followed by two more symbols,
+                // optionally followed by two padding characters (which make a total multiple of four).
+                let byte_buffer = [
+                    (self.bit_buffer >> 4) as u8,
+                ];
+                (self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
+            }
+            (18, 1) | (18, 0) => {
+                // A multiple of four of alphabet symbols, followed by three more symbols,
+                // optionally followed by one padding character (which make a total multiple of four).
+                let byte_buffer = [
+                    (self.bit_buffer >> 10) as u8,
+                    (self.bit_buffer >> 2) as u8,
+                ];
+                (self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
+            }
+            (6, _) => {
+                Err(InvalidBase64Details::LoneAlphabetSymbol)?
+            }
+            _ => {
+                Err(InvalidBase64Details::Padding)?
+            }
+        }
+        Ok(())
+    }
+}
+
+
+/// Generated by `make_base64_decode_table.py` based on "Table 1: The Base 64 Alphabet"
+/// at <https://tools.ietf.org/html/rfc4648#section-4>
+///
+/// Array indices are the byte value of symbols.
+/// Array values are their positions in the base64 alphabet,
+/// or -1 for symbols not in the alphabet.
+/// The position contributes 6 bits to the decoded bytes.
+const BASE64_DECODE_TABLE: [i8; 256] = [
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+];

+ 301 - 0
data-url/src/lib.rs

@@ -0,0 +1,301 @@
+//! Processing of `data:` URLs according to the Fetch Standard:
+//! <https://fetch.spec.whatwg.org/#data-urls>
+//! but starting from a string rather than a parsed URL to avoid extra copies.
+//!
+//! ```rust
+//! use data_url::{DataUrl, mime};
+//!
+//! let url = DataUrl::process("data:,Hello%20World!").unwrap();
+//! let (body, fragment) = url.decode_to_vec().unwrap();
+//!
+//! assert_eq!(url.mime_type().type_, "text");
+//! assert_eq!(url.mime_type().subtype, "plain");
+//! assert_eq!(url.mime_type().get_parameter("charset"), Some("US-ASCII"));
+//! assert_eq!(body, b"Hello World!");
+//! assert!(fragment.is_none());
+//! ```
+
+#[macro_use] extern crate matches;
+
+macro_rules! require {
+    ($condition: expr) => {
+        if !$condition {
+            return None
+        }
+    }
+}
+
+pub mod forgiving_base64;
+pub mod mime;
+
+pub struct DataUrl<'a> {
+    mime_type: mime::Mime,
+    base64: bool,
+    encoded_body_plus_fragment: &'a str,
+}
+
+#[derive(Debug)]
+pub enum DataUrlError {
+    NotADataUrl,
+    NoComma,
+}
+
+impl<'a> DataUrl<'a> {
+    /// <https://fetch.spec.whatwg.org/#data-url-processor>
+    /// but starting from a string rather than a parsed `Url`, to avoid extra string copies.
+    pub fn process(input: &'a str) -> Result<Self, DataUrlError> {
+        use DataUrlError::*;
+
+        let after_colon = pretend_parse_data_url(input).ok_or(NotADataUrl)?;
+
+        let (from_colon_to_comma, encoded_body_plus_fragment) =
+            find_comma_before_fragment(after_colon).ok_or(NoComma)?;
+
+        let (mime_type, base64) = parse_header(from_colon_to_comma);
+
+        Ok(DataUrl { mime_type, base64, encoded_body_plus_fragment })
+    }
+
+    pub fn mime_type(&self) -> &mime::Mime {
+        &self.mime_type
+    }
+
+    /// Streaming-decode the data URL’s body to `write_body_bytes`,
+    /// and return the URL’s fragment identifier if it has one.
+    pub fn decode<F, E>(&self, write_body_bytes: F)
+                        -> Result<Option<FragmentIdentifier<'a>>, forgiving_base64::DecodeError<E>>
+        where F: FnMut(&[u8]) -> Result<(), E>
+    {
+        if self.base64 {
+            decode_with_base64(self.encoded_body_plus_fragment, write_body_bytes)
+        } else {
+            decode_without_base64(self.encoded_body_plus_fragment, write_body_bytes)
+                .map_err(forgiving_base64::DecodeError::WriteError)
+        }
+    }
+
+    /// Return the decoded body, and the URL’s fragment identifier if it has one.
+    pub fn decode_to_vec(&self)
+        -> Result<(Vec<u8>, Option<FragmentIdentifier<'a>>), forgiving_base64::InvalidBase64>
+    {
+        let mut body = Vec::new();
+        let fragment = self.decode(|bytes| Ok(body.extend_from_slice(bytes)))?;
+        Ok((body, fragment))
+    }
+}
+
+/// The URL’s fragment identifier (after `#`)
+pub struct FragmentIdentifier<'a>(&'a str);
+
+impl<'a> FragmentIdentifier<'a> {
+    /// Like in a parsed URL
+    pub fn to_percent_encoded(&self) -> String {
+        let mut string = String::new();
+        for byte in self.0.bytes() {
+            match byte {
+                // Ignore ASCII tabs or newlines like the URL parser would
+                b'\t' | b'\n' | b'\r' => continue,
+                // Fragment encode set
+                b'\0'...b' ' | b'"' | b'<' | b'>' | b'`' | b'\x7F'...b'\xFF' => {
+                    percent_encode(byte, &mut string)
+                }
+                // Printable ASCII
+                _ => string.push(byte as char)
+            }
+        }
+        string
+    }
+}
+
+/// Similar to <https://url.spec.whatwg.org/#concept-basic-url-parser>
+/// followed by <https://url.spec.whatwg.org/#concept-url-serializer>
+///
+/// * `None`: not a data URL.
+///
+/// * `Some(s)`: sort of the result of serialization, except:
+///
+///   - `data:` prefix removed
+///   - The fragment is included
+///   - Other components are **not** UTF-8 percent-encoded
+///   - ASCII tabs and newlines in the middle are **not** removed
+fn pretend_parse_data_url(input: &str) -> Option<&str> {
+    // Trim C0 control or space
+    let left_trimmed = input.trim_left_matches(|ch| ch <= ' ');
+
+    let mut bytes = left_trimmed.bytes();
+    {
+        // Ignore ASCII tabs or newlines like the URL parser would
+        let mut iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
+        require!(iter.next()?.to_ascii_lowercase() == b'd');
+        require!(iter.next()?.to_ascii_lowercase() == b'a');
+        require!(iter.next()?.to_ascii_lowercase() == b't');
+        require!(iter.next()?.to_ascii_lowercase() == b'a');
+        require!(iter.next()? == b':');
+    }
+    let bytes_consumed = left_trimmed.len() - bytes.len();
+    let after_colon = &left_trimmed[bytes_consumed..];
+
+    // Trim C0 control or space
+    Some(after_colon.trim_right_matches(|ch| ch <= ' '))
+}
+
+fn find_comma_before_fragment(after_colon: &str) -> Option<(&str, &str)> {
+    for (i, byte) in after_colon.bytes().enumerate() {
+        if byte == b',' {
+            return Some((&after_colon[..i], &after_colon[i + 1..]))
+        }
+        if byte == b'#' {
+            break
+        }
+    }
+    None
+}
+
+fn parse_header(from_colon_to_comma: &str) -> (mime::Mime, bool) {
+    // "Strip leading and trailing ASCII whitespace"
+    //     \t, \n, and \r would have been filtered by the URL parser
+    //     \f percent-encoded by the URL parser
+    //     space is the only remaining ASCII whitespace
+    let trimmed = from_colon_to_comma.trim_matches(|c| matches!(c, ' ' | '\t' | '\n' | '\r'));
+
+    let without_base64_suffix = remove_base64_suffix(trimmed);
+    let base64 = without_base64_suffix.is_some();
+    let mime_type = without_base64_suffix.unwrap_or(trimmed);
+
+    let mut string = String::new();
+    if mime_type.starts_with(';') {
+        string.push_str("text/plain")
+    }
+    let mut in_query = false;
+    for byte in mime_type.bytes() {
+        match byte {
+            // Ignore ASCII tabs or newlines like the URL parser would
+            b'\t' | b'\n' | b'\r' => continue,
+
+            // C0 encode set
+            b'\0'...b'\x1F' | b'\x7F'...b'\xFF' => percent_encode(byte, &mut string),
+
+            // Bytes other than the C0 encode set that are percent-encoded
+            // by the URL parser in the query state.
+            // '#' is also in that list but cannot occur here
+            // since it indicates the start of the URL’s fragment.
+            b' ' | b'"' | b'<' | b'>' if in_query => percent_encode(byte, &mut string),
+
+            b'?' => {
+                in_query = true;
+                string.push('?')
+            }
+
+            // Printable ASCII
+            _ => string.push(byte as char)
+        }
+    }
+
+    // FIXME: does Mime::from_str match the MIME Sniffing Standard’s parsing algorithm?
+    // <https://mimesniff.spec.whatwg.org/#parse-a-mime-type>
+    let mime_type = string.parse().unwrap_or_else(|_| {
+        mime::Mime {
+            type_: String::from("text"),
+            subtype: String::from("plain"),
+            parameters: vec![(String::from("charset"), String::from("US-ASCII"))],
+        }
+    });
+
+    (mime_type, base64)
+}
+
+/// None: no base64 suffix
+fn remove_base64_suffix(s: &str) -> Option<&str> {
+    let mut bytes = s.bytes();
+    {
+        // Ignore ASCII tabs or newlines like the URL parser would
+        let iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
+
+        // Search from the end
+        let mut iter = iter.rev();
+
+        require!(iter.next()? == b'4');
+        require!(iter.next()? == b'6');
+        require!(iter.next()?.to_ascii_lowercase() == b'e');
+        require!(iter.next()?.to_ascii_lowercase() == b's');
+        require!(iter.next()?.to_ascii_lowercase() == b'a');
+        require!(iter.next()?.to_ascii_lowercase() == b'b');
+        require!(iter.skip_while(|&byte| byte == b' ').next()? == b';');
+    }
+    Some(&s[..bytes.len()])
+}
+
+fn percent_encode(byte: u8, string: &mut String) {
+    const HEX_UPPER: [u8; 16] = *b"0123456789ABCDEF";
+    string.push('%');
+    string.push(HEX_UPPER[(byte >> 4) as usize] as char);
+    string.push(HEX_UPPER[(byte & 0x0f) as usize] as char);
+}
+
+/// This is <https://url.spec.whatwg.org/#string-percent-decode> while also:
+///
+/// * Ignoring ASCII tab or newlines
+/// * Stopping at the first '#' (which indicates the start of the fragment)
+///
+/// Anything that would have been UTF-8 percent-encoded by the URL parser
+/// would be percent-decoded here.
+/// We skip that round-trip and pass it through unchanged.
+fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes: F)
+                               -> Result<Option<FragmentIdentifier>, E>
+    where F: FnMut(&[u8]) -> Result<(), E>
+{
+    let bytes = encoded_body_plus_fragment.as_bytes();
+    let mut slice_start = 0;
+    for (i, &byte) in bytes.iter().enumerate() {
+        // We only need to look for 5 different "special" byte values.
+        // For everything else we make slices as large as possible, borrowing the input,
+        // in order to make fewer write_all() calls.
+        if matches!(byte, b'%' | b'#' | b'\t' | b'\n' | b'\r') {
+            // Write everything (if anything) "non-special" we’ve accumulated
+            // before this special byte
+            if i > slice_start {
+                write_bytes(&bytes[slice_start..i])?;
+            }
+            // Then deal with the special byte.
+            match byte {
+                b'%' => {
+                    let l = bytes.get(i + 2).and_then(|&b| (b as char).to_digit(16));
+                    let h = bytes.get(i + 1).and_then(|&b| (b as char).to_digit(16));
+                    if let (Some(h), Some(l)) = (h, l) {
+                        // '%' followed by two ASCII hex digits
+                        let one_byte = h as u8 * 0x10 + l as u8;
+                        write_bytes(&[one_byte])?;
+                        slice_start = i + 3;
+                    } else {
+                        // Do nothing. Leave slice_start unchanged.
+                        // The % sign will be part of the next slice.
+                    }
+                }
+
+                b'#' => {
+                    let fragment_start = i + 1;
+                    let fragment = &encoded_body_plus_fragment[fragment_start..];
+                    return Ok(Some(FragmentIdentifier(fragment)))
+                }
+
+                // Ignore over '\t' | '\n' | '\r'
+                _ => slice_start = i + 1
+            }
+        }
+    }
+    write_bytes(&bytes[slice_start..])?;
+    Ok(None)
+}
+
+/// `decode_without_base64()` composed with
+/// <https://infra.spec.whatwg.org/#isomorphic-decode> composed with
+/// <https://infra.spec.whatwg.org/#forgiving-base64-decode>.
+fn decode_with_base64<F, E>(encoded_body_plus_fragment: &str, write_bytes: F)
+                            -> Result<Option<FragmentIdentifier>, forgiving_base64::DecodeError<E>>
+    where F: FnMut(&[u8]) -> Result<(), E>
+{
+    let mut decoder = forgiving_base64::Decoder::new(write_bytes);
+    let fragment = decode_without_base64(encoded_body_plus_fragment, |bytes| decoder.feed(bytes))?;
+    decoder.finish()?;
+    Ok(fragment)
+}

+ 19 - 0
data-url/src/make_base64_decode_table.py

@@ -0,0 +1,19 @@
+"""
+Generate the BASE64_DECODE_TABLE constant. See its doc-comment.
+"""
+
+import string
+
+# https://tools.ietf.org/html/rfc4648#section-4
+alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits + "+/"
+assert len(alphabet) == 64
+
+reverse_table = [-1] * 256
+for i, symbol in enumerate(alphabet):
+    reverse_table[ord(symbol)] = i
+
+print("[")
+per_line = 16
+for line in range(0, 256, per_line):
+    print("   " + "".join(" %2s," % value for value in reverse_table[line:][:per_line]))
+print("]")

+ 180 - 0
data-url/src/mime.rs

@@ -0,0 +1,180 @@
+use std::fmt::{self, Write};
+use std::str::FromStr;
+
+/// <https://mimesniff.spec.whatwg.org/#mime-type-representation>
+#[derive(Debug, PartialEq, Eq)]
+pub struct Mime {
+    pub type_: String,
+    pub subtype: String,
+    /// (name, value)
+    pub parameters: Vec<(String, String)>
+}
+
+impl Mime {
+    pub fn get_parameter<P>(&self, name: &P) -> Option<&str>
+        where P: ?Sized + PartialEq<str>
+    {
+        self.parameters.iter()
+            .find(|&&(ref n, _)| name == &**n)
+            .map(|&(_, ref v)| &**v)
+    }
+}
+
+#[derive(Debug)]
+pub struct MimeParsingError(());
+
+/// <https://mimesniff.spec.whatwg.org/#parsing-a-mime-type>
+impl FromStr for Mime {
+    type Err = MimeParsingError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        parse(s).ok_or(MimeParsingError(()))
+    }
+}
+
+fn parse(s: &str) -> Option<Mime> {
+    let trimmed = s.trim_matches(ascii_whitespace);
+
+    let (type_, rest) = split2(trimmed, '/');
+    require!(only_http_token_code_points(type_) && !type_.is_empty());
+
+    let (subtype, rest) = split2(rest?, ';');
+    let subtype = subtype.trim_right_matches(ascii_whitespace);
+    require!(only_http_token_code_points(subtype) && !subtype.is_empty());
+
+    let mut parameters = Vec::new();
+    if let Some(rest) = rest {
+        parse_parameters(rest, &mut parameters)
+    }
+
+    Some(Mime {
+        type_: type_.to_ascii_lowercase(),
+        subtype: subtype.to_ascii_lowercase(),
+        parameters,
+    })
+}
+
+fn split2(s: &str, separator: char) -> (&str, Option<&str>) {
+    let mut iter = s.splitn(2, separator);
+    let first = iter.next().unwrap();
+    (first, iter.next())
+}
+
+fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
+    let mut semicolon_separated = s.split(';');
+
+    while let Some(piece) = semicolon_separated.next() {
+        let piece = piece.trim_left_matches(ascii_whitespace);
+        let (name, value) = split2(piece, '=');
+        if name.is_empty() || !only_http_token_code_points(name) || contains(&parameters, name) {
+            continue
+        }
+        if let Some(value) = value {
+            let value = if value.starts_with('"') {
+                let max_len = value.len().saturating_sub(2);  // without start or end quotes
+                let mut unescaped_value = String::with_capacity(max_len);
+                let mut chars = value[1..].chars();
+                'until_closing_quote: loop {
+                    while let Some(c) = chars.next() {
+                        match c {
+                            '"' => break 'until_closing_quote,
+                            '\\' => unescaped_value.push(chars.next().unwrap_or('\\')),
+                            _ => unescaped_value.push(c)
+                        }
+                    }
+                    if let Some(piece) = semicolon_separated.next() {
+                        // A semicolon inside a quoted value is not a separator
+                        // for the next parameter, but part of the value.
+                        unescaped_value.push(';');
+                        chars = piece.chars()
+                    } else {
+                        break
+                    }
+                }
+                if !valid_value(&unescaped_value) {
+                    continue
+                }
+                unescaped_value
+            } else {
+                let value = value.trim_right_matches(ascii_whitespace);
+                if !valid_value(value) {
+                    continue
+                }
+                value.to_owned()
+            };
+            parameters.push((name.to_ascii_lowercase(), value))
+        }
+    }
+}
+
+fn contains(parameters: &[(String, String)], name: &str) -> bool {
+    parameters.iter().any(|&(ref n, _)| n == name)
+}
+
+fn valid_value(s: &str) -> bool {
+    s.chars().all(|c| {
+        // <https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point>
+        matches!(c, '\t' | ' '...'~' | '\u{80}'...'\u{FF}')
+    }) && !s.is_empty()
+}
+
+/// <https://mimesniff.spec.whatwg.org/#serializing-a-mime-type>
+impl fmt::Display for Mime {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.type_)?;
+        f.write_str("/")?;
+        f.write_str(&self.subtype)?;
+        for &(ref name, ref value) in &self.parameters {
+            f.write_str(";")?;
+            f.write_str(name)?;
+            f.write_str("=")?;
+            if only_http_token_code_points(value) {
+                f.write_str(value)?
+            } else {
+                f.write_str("\"")?;
+                for c in value.chars() {
+                    if c == '"' || c == '\\' {
+                        f.write_str("\\")?
+                    }
+                    f.write_char(c)?
+                }
+                f.write_str("\"")?
+            }
+        }
+        Ok(())
+    }
+}
+
+fn ascii_whitespace(c: char) -> bool {
+    matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0C')
+}
+
+fn only_http_token_code_points(s: &str) -> bool {
+    s.bytes().all(|byte| IS_HTTP_TOKEN[byte as usize])
+}
+
+macro_rules! byte_map {
+    ($($flag:expr,)*) => ([
+        $($flag != 0,)*
+    ])
+}
+
+// Copied from https://github.com/hyperium/mime/blob/v0.3.5/src/parse.rs#L293
+static IS_HTTP_TOKEN: [bool; 256] = byte_map![
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];

+ 79 - 0
data-url/tests/base64.json

@@ -0,0 +1,79 @@
+[
+  ["", []],
+  ["abcd", [105, 183, 29]],
+  [" abcd", [105, 183, 29]],
+  ["abcd ", [105, 183, 29]],
+  [" abcd===", null],
+  ["abcd=== ", null],
+  ["abcd ===", null],
+  ["a", null],
+  ["ab", [105]],
+  ["abc", [105, 183]],
+  ["abcde", null],
+  ["𐀀", null],
+  ["=", null],
+  ["==", null],
+  ["===", null],
+  ["====", null],
+  ["=====", null],
+  ["a=", null],
+  ["a==", null],
+  ["a===", null],
+  ["a====", null],
+  ["a=====", null],
+  ["ab=", null],
+  ["ab==", [105]],
+  ["ab===", null],
+  ["ab====", null],
+  ["ab=====", null],
+  ["abc=", [105, 183]],
+  ["abc==", null],
+  ["abc===", null],
+  ["abc====", null],
+  ["abc=====", null],
+  ["abcd=", null],
+  ["abcd==", null],
+  ["abcd===", null],
+  ["abcd====", null],
+  ["abcd=====", null],
+  ["abcde=", null],
+  ["abcde==", null],
+  ["abcde===", null],
+  ["abcde====", null],
+  ["abcde=====", null],
+  ["=a", null],
+  ["=a=", null],
+  ["a=b", null],
+  ["a=b=", null],
+  ["ab=c", null],
+  ["ab=c=", null],
+  ["abc=d", null],
+  ["abc=d=", null],
+  ["ab\tcd", [105, 183, 29]],
+  ["ab\ncd", [105, 183, 29]],
+  ["ab\fcd", [105, 183, 29]],
+  ["ab\rcd", [105, 183, 29]],
+  ["ab cd", [105, 183, 29]],
+  ["ab\u00a0cd", null],
+  ["ab\t\n\f\r cd", [105, 183, 29]],
+  [" \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", [105, 183, 29]],
+  ["ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", [105]],
+  ["A", null],
+  ["/A", [252]],
+  ["//A", [255, 240]],
+  ["///A", [255, 255, 192]],
+  ["////A", null],
+  ["/", null],
+  ["A/", [3]],
+  ["AA/", [0, 15]],
+  ["AAAA/", null],
+  ["AAA/", [0, 0, 63]],
+  ["\u0000nonsense", null],
+  ["abcd\u0000nonsense", null],
+  ["YQ", [97]],
+  ["YR", [97]],
+  ["~~", null],
+  ["..", null],
+  ["--", null],
+  ["__", null]
+]

+ 199 - 0
data-url/tests/data-urls.json

@@ -0,0 +1,199 @@
+[
+  ["data://test/,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data://test:test/,X",
+   null],
+  ["data:,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:",
+   null],
+  ["data:text/html",
+   null],
+  ["data:text/html    ;charset=x   ",
+   null],
+  ["data:,",
+   "text/plain;charset=US-ASCII",
+   []],
+  ["data:,X#X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:text/plain,X",
+   "text/plain",
+   [88]],
+  ["data:text/plain ,X",
+   "text/plain",
+   [88]],
+  ["data:text/plain%20,X",
+   "text/plain%20",
+   [88]],
+  ["data:text/plain\f,X",
+   "text/plain%0c",
+   [88]],
+  ["data:text/plain%0C,X",
+   "text/plain%0c",
+   [88]],
+  ["data:text/plain;,X",
+   "text/plain",
+   [88]],
+  ["data:;x=x;charset=x,X",
+   "text/plain;x=x;charset=x",
+   [88]],
+  ["data:;x=x,X",
+   "text/plain;x=x",
+   [88]],
+  ["data:text/plain;charset=windows-1252,%C2%B1",
+   "text/plain;charset=windows-1252",
+   [194, 177]],
+  ["data:text/plain;Charset=UTF-8,%C2%B1",
+   "text/plain;charset=UTF-8",
+   [194, 177]],
+  ["data:image/gif,%C2%B1",
+   "image/gif",
+   [194, 177]],
+  ["data:IMAGE/gif,%C2%B1",
+   "image/gif",
+   [194, 177]],
+  ["data:IMAGE/gif;hi=x,%C2%B1",
+   "image/gif;hi=x",
+   [194, 177]],
+  ["data:IMAGE/gif;CHARSET=x,%C2%B1",
+   "image/gif;charset=x",
+   [194, 177]],
+  ["data: ,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%20,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:\f,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%1F,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:\u0000,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%00,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:text/html  ,X",
+   "text/html",
+   [88]],
+  ["data:text / html,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:†,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:†/†,X",
+   "%e2%80%a0/%e2%80%a0",
+   [88]],
+  ["data:X,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:image/png,X X",
+   "image/png",
+   [88, 32, 88]],
+  ["data:application/xml,X X",
+   "application/xml",
+   [88, 32, 88]],
+  ["data:unknown/unknown,X X",
+   "unknown/unknown",
+   [88, 32, 88]],
+  ["data:text/plain;a=\",\",X",
+   "text/plain",
+   [34, 44, 88]],
+  ["data:text/plain;a=%2C,X",
+   "text/plain;a=%2C",
+   [88]],
+  ["data:;base64;base64,WA",
+   "text/plain",
+   [88]],
+  ["data:x/x;base64;base64,WA",
+   "x/x",
+   [88]],
+  ["data:x/x;base64;charset=x,WA",
+   "x/x;charset=x",
+   [87, 65]],
+  ["data:x/x;base64;charset=x;base64,WA",
+   "x/x;charset=x",
+   [88]],
+  ["data:x/x;base64;base64x,WA",
+   "x/x",
+   [87, 65]],
+  ["data:;base64,W%20A",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base64,W%0CA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:x;base64x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:x;base64;x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:x;base64=x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:; base64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;  base64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:  ;charset=x   ;  base64,WA",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;base64;,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:;base64 ,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base64   ,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base 64,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:;BASe64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;%62ase64,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:%3Bbase64,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:;charset=x,X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:; charset=x,X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;charset =x,X",
+   "text/plain",
+   [88]],
+  ["data:;charset= x,X",
+   "text/plain;charset=\" x\"",
+   [88]],
+  ["data:;charset=,X",
+   "text/plain",
+   [88]],
+  ["data:;charset,X",
+   "text/plain",
+   [88]],
+  ["data:;charset=\"x\",X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;CHARSET=\"X\",X",
+   "text/plain;charset=X",
+   [88]]
+]

+ 3526 - 0
data-url/tests/generated-mime-types.json

@@ -0,0 +1,3526 @@
+[
+  {
+    "input": "\u0000/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0000",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0000=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0000;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0000\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0001/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0001",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0001=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0001;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0001\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0002/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0002",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0002=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0002;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0002\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0003/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0003",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0003=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0003;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0003\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0004/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0004",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0004=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0004;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0004\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0005/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0005",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0005=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0005;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0005\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0006/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0006",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0006=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0006;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0006\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0007/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0007",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0007=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0007;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0007\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\b/x",
+    "output": null
+  },
+  {
+    "input": "x/\b",
+    "output": null
+  },
+  {
+    "input": "x/x;\b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\t/x",
+    "output": null
+  },
+  {
+    "input": "x/\t",
+    "output": null
+  },
+  {
+    "input": "x/x;\t=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\n/x",
+    "output": null
+  },
+  {
+    "input": "x/\n",
+    "output": null
+  },
+  {
+    "input": "x/x;\n=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\n;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\n\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\f/x",
+    "output": null
+  },
+  {
+    "input": "x/\f",
+    "output": null
+  },
+  {
+    "input": "x/x;\f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\r/x",
+    "output": null
+  },
+  {
+    "input": "x/\r",
+    "output": null
+  },
+  {
+    "input": "x/x;\r=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\r;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\r\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000e;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000e\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0010/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0010",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0010=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0010;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0010\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0011/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0011",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0011=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0011;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0011\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0012/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0012",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0012=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0012;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0012\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0013/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0013",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0013=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0013;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0013\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0014/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0014",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0014=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0014;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0014\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0015/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0015",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0015=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0015;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0015\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0016/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0016",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0016=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0016;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0016\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0017/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0017",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0017=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0017;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0017\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0018/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0018",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0018=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0018;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0018\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0019/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0019",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0019=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0019;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0019\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001a;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001a\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001c;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001c\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001d;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001d\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001e;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001e\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": " /x",
+    "output": null
+  },
+  {
+    "input": "x/ ",
+    "output": null
+  },
+  {
+    "input": "x/x; =x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\"/x",
+    "output": null
+  },
+  {
+    "input": "x/\"",
+    "output": null
+  },
+  {
+    "input": "x/x;\"=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "(/x",
+    "output": null
+  },
+  {
+    "input": "x/(",
+    "output": null
+  },
+  {
+    "input": "x/x;(=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=(;bonus=x",
+    "output": "x/x;x=\"(\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"(\";bonus=x",
+    "output": "x/x;x=\"(\";bonus=x"
+  },
+  {
+    "input": ")/x",
+    "output": null
+  },
+  {
+    "input": "x/)",
+    "output": null
+  },
+  {
+    "input": "x/x;)=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=);bonus=x",
+    "output": "x/x;x=\")\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\")\";bonus=x",
+    "output": "x/x;x=\")\";bonus=x"
+  },
+  {
+    "input": ",/x",
+    "output": null
+  },
+  {
+    "input": "x/,",
+    "output": null
+  },
+  {
+    "input": "x/x;,=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=,;bonus=x",
+    "output": "x/x;x=\",\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\",\";bonus=x",
+    "output": "x/x;x=\",\";bonus=x"
+  },
+  {
+    "input": "x/x;/=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=/;bonus=x",
+    "output": "x/x;x=\"/\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"/\";bonus=x",
+    "output": "x/x;x=\"/\";bonus=x"
+  },
+  {
+    "input": ":/x",
+    "output": null
+  },
+  {
+    "input": "x/:",
+    "output": null
+  },
+  {
+    "input": "x/x;:=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=:;bonus=x",
+    "output": "x/x;x=\":\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\":\";bonus=x",
+    "output": "x/x;x=\":\";bonus=x"
+  },
+  {
+    "input": ";/x",
+    "output": null
+  },
+  {
+    "input": "x/;",
+    "output": null
+  },
+  {
+    "input": "</x",
+    "output": null
+  },
+  {
+    "input": "x/<",
+    "output": null
+  },
+  {
+    "input": "x/x;<=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=<;bonus=x",
+    "output": "x/x;x=\"<\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"<\";bonus=x",
+    "output": "x/x;x=\"<\";bonus=x"
+  },
+  {
+    "input": "=/x",
+    "output": null
+  },
+  {
+    "input": "x/=",
+    "output": null
+  },
+  {
+    "input": "x/x;x==;bonus=x",
+    "output": "x/x;x=\"=\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"=\";bonus=x",
+    "output": "x/x;x=\"=\";bonus=x"
+  },
+  {
+    "input": ">/x",
+    "output": null
+  },
+  {
+    "input": "x/>",
+    "output": null
+  },
+  {
+    "input": "x/x;>=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=>;bonus=x",
+    "output": "x/x;x=\">\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\">\";bonus=x",
+    "output": "x/x;x=\">\";bonus=x"
+  },
+  {
+    "input": "?/x",
+    "output": null
+  },
+  {
+    "input": "x/?",
+    "output": null
+  },
+  {
+    "input": "x/x;?=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=?;bonus=x",
+    "output": "x/x;x=\"?\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"?\";bonus=x",
+    "output": "x/x;x=\"?\";bonus=x"
+  },
+  {
+    "input": "@/x",
+    "output": null
+  },
+  {
+    "input": "x/@",
+    "output": null
+  },
+  {
+    "input": "x/x;@=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=@;bonus=x",
+    "output": "x/x;x=\"@\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"@\";bonus=x",
+    "output": "x/x;x=\"@\";bonus=x"
+  },
+  {
+    "input": "[/x",
+    "output": null
+  },
+  {
+    "input": "x/[",
+    "output": null
+  },
+  {
+    "input": "x/x;[=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=[;bonus=x",
+    "output": "x/x;x=\"[\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"[\";bonus=x",
+    "output": "x/x;x=\"[\";bonus=x"
+  },
+  {
+    "input": "\\/x",
+    "output": null
+  },
+  {
+    "input": "x/\\",
+    "output": null
+  },
+  {
+    "input": "x/x;\\=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "]/x",
+    "output": null
+  },
+  {
+    "input": "x/]",
+    "output": null
+  },
+  {
+    "input": "x/x;]=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=];bonus=x",
+    "output": "x/x;x=\"]\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"]\";bonus=x",
+    "output": "x/x;x=\"]\";bonus=x"
+  },
+  {
+    "input": "{/x",
+    "output": null
+  },
+  {
+    "input": "x/{",
+    "output": null
+  },
+  {
+    "input": "x/x;{=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x={;bonus=x",
+    "output": "x/x;x=\"{\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"{\";bonus=x",
+    "output": "x/x;x=\"{\";bonus=x"
+  },
+  {
+    "input": "}/x",
+    "output": null
+  },
+  {
+    "input": "x/}",
+    "output": null
+  },
+  {
+    "input": "x/x;}=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=};bonus=x",
+    "output": "x/x;x=\"}\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"}\";bonus=x",
+    "output": "x/x;x=\"}\";bonus=x"
+  },
+  {
+    "input": "\u007f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u007f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u007f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u007f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u007f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0080/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0080",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0080=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0080;bonus=x",
+    "output": "x/x;x=\"\u0080\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0080\";bonus=x",
+    "output": "x/x;x=\"\u0080\";bonus=x"
+  },
+  {
+    "input": "\u0081/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0081",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0081=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0081;bonus=x",
+    "output": "x/x;x=\"\u0081\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0081\";bonus=x",
+    "output": "x/x;x=\"\u0081\";bonus=x"
+  },
+  {
+    "input": "\u0082/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0082",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0082=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0082;bonus=x",
+    "output": "x/x;x=\"\u0082\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0082\";bonus=x",
+    "output": "x/x;x=\"\u0082\";bonus=x"
+  },
+  {
+    "input": "\u0083/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0083",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0083=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0083;bonus=x",
+    "output": "x/x;x=\"\u0083\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0083\";bonus=x",
+    "output": "x/x;x=\"\u0083\";bonus=x"
+  },
+  {
+    "input": "\u0084/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0084",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0084=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0084;bonus=x",
+    "output": "x/x;x=\"\u0084\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0084\";bonus=x",
+    "output": "x/x;x=\"\u0084\";bonus=x"
+  },
+  {
+    "input": "\u0085/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0085",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0085=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0085;bonus=x",
+    "output": "x/x;x=\"\u0085\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0085\";bonus=x",
+    "output": "x/x;x=\"\u0085\";bonus=x"
+  },
+  {
+    "input": "\u0086/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0086",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0086=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0086;bonus=x",
+    "output": "x/x;x=\"\u0086\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0086\";bonus=x",
+    "output": "x/x;x=\"\u0086\";bonus=x"
+  },
+  {
+    "input": "\u0087/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0087",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0087=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0087;bonus=x",
+    "output": "x/x;x=\"\u0087\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0087\";bonus=x",
+    "output": "x/x;x=\"\u0087\";bonus=x"
+  },
+  {
+    "input": "\u0088/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0088",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0088=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0088;bonus=x",
+    "output": "x/x;x=\"\u0088\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0088\";bonus=x",
+    "output": "x/x;x=\"\u0088\";bonus=x"
+  },
+  {
+    "input": "\u0089/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0089",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0089=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0089;bonus=x",
+    "output": "x/x;x=\"\u0089\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0089\";bonus=x",
+    "output": "x/x;x=\"\u0089\";bonus=x"
+  },
+  {
+    "input": "\u008a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008a;bonus=x",
+    "output": "x/x;x=\"\u008a\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008a\";bonus=x",
+    "output": "x/x;x=\"\u008a\";bonus=x"
+  },
+  {
+    "input": "\u008b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008b;bonus=x",
+    "output": "x/x;x=\"\u008b\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008b\";bonus=x",
+    "output": "x/x;x=\"\u008b\";bonus=x"
+  },
+  {
+    "input": "\u008c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008c;bonus=x",
+    "output": "x/x;x=\"\u008c\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008c\";bonus=x",
+    "output": "x/x;x=\"\u008c\";bonus=x"
+  },
+  {
+    "input": "\u008d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008d;bonus=x",
+    "output": "x/x;x=\"\u008d\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008d\";bonus=x",
+    "output": "x/x;x=\"\u008d\";bonus=x"
+  },
+  {
+    "input": "\u008e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008e;bonus=x",
+    "output": "x/x;x=\"\u008e\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008e\";bonus=x",
+    "output": "x/x;x=\"\u008e\";bonus=x"
+  },
+  {
+    "input": "\u008f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008f;bonus=x",
+    "output": "x/x;x=\"\u008f\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008f\";bonus=x",
+    "output": "x/x;x=\"\u008f\";bonus=x"
+  },
+  {
+    "input": "\u0090/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0090",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0090=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0090;bonus=x",
+    "output": "x/x;x=\"\u0090\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0090\";bonus=x",
+    "output": "x/x;x=\"\u0090\";bonus=x"
+  },
+  {
+    "input": "\u0091/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0091",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0091=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0091;bonus=x",
+    "output": "x/x;x=\"\u0091\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0091\";bonus=x",
+    "output": "x/x;x=\"\u0091\";bonus=x"
+  },
+  {
+    "input": "\u0092/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0092",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0092=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0092;bonus=x",
+    "output": "x/x;x=\"\u0092\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0092\";bonus=x",
+    "output": "x/x;x=\"\u0092\";bonus=x"
+  },
+  {
+    "input": "\u0093/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0093",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0093=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0093;bonus=x",
+    "output": "x/x;x=\"\u0093\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0093\";bonus=x",
+    "output": "x/x;x=\"\u0093\";bonus=x"
+  },
+  {
+    "input": "\u0094/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0094",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0094=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0094;bonus=x",
+    "output": "x/x;x=\"\u0094\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0094\";bonus=x",
+    "output": "x/x;x=\"\u0094\";bonus=x"
+  },
+  {
+    "input": "\u0095/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0095",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0095=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0095;bonus=x",
+    "output": "x/x;x=\"\u0095\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0095\";bonus=x",
+    "output": "x/x;x=\"\u0095\";bonus=x"
+  },
+  {
+    "input": "\u0096/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0096",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0096=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0096;bonus=x",
+    "output": "x/x;x=\"\u0096\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0096\";bonus=x",
+    "output": "x/x;x=\"\u0096\";bonus=x"
+  },
+  {
+    "input": "\u0097/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0097",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0097=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0097;bonus=x",
+    "output": "x/x;x=\"\u0097\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0097\";bonus=x",
+    "output": "x/x;x=\"\u0097\";bonus=x"
+  },
+  {
+    "input": "\u0098/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0098",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0098=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0098;bonus=x",
+    "output": "x/x;x=\"\u0098\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0098\";bonus=x",
+    "output": "x/x;x=\"\u0098\";bonus=x"
+  },
+  {
+    "input": "\u0099/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0099",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0099=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0099;bonus=x",
+    "output": "x/x;x=\"\u0099\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0099\";bonus=x",
+    "output": "x/x;x=\"\u0099\";bonus=x"
+  },
+  {
+    "input": "\u009a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009a;bonus=x",
+    "output": "x/x;x=\"\u009a\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009a\";bonus=x",
+    "output": "x/x;x=\"\u009a\";bonus=x"
+  },
+  {
+    "input": "\u009b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009b;bonus=x",
+    "output": "x/x;x=\"\u009b\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009b\";bonus=x",
+    "output": "x/x;x=\"\u009b\";bonus=x"
+  },
+  {
+    "input": "\u009c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009c;bonus=x",
+    "output": "x/x;x=\"\u009c\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009c\";bonus=x",
+    "output": "x/x;x=\"\u009c\";bonus=x"
+  },
+  {
+    "input": "\u009d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009d;bonus=x",
+    "output": "x/x;x=\"\u009d\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009d\";bonus=x",
+    "output": "x/x;x=\"\u009d\";bonus=x"
+  },
+  {
+    "input": "\u009e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009e;bonus=x",
+    "output": "x/x;x=\"\u009e\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009e\";bonus=x",
+    "output": "x/x;x=\"\u009e\";bonus=x"
+  },
+  {
+    "input": "\u009f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009f;bonus=x",
+    "output": "x/x;x=\"\u009f\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009f\";bonus=x",
+    "output": "x/x;x=\"\u009f\";bonus=x"
+  },
+  {
+    "input": "\u00a0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a0;bonus=x",
+    "output": "x/x;x=\"\u00a0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a0\";bonus=x",
+    "output": "x/x;x=\"\u00a0\";bonus=x"
+  },
+  {
+    "input": "\u00a1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a1;bonus=x",
+    "output": "x/x;x=\"\u00a1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a1\";bonus=x",
+    "output": "x/x;x=\"\u00a1\";bonus=x"
+  },
+  {
+    "input": "\u00a2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a2;bonus=x",
+    "output": "x/x;x=\"\u00a2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a2\";bonus=x",
+    "output": "x/x;x=\"\u00a2\";bonus=x"
+  },
+  {
+    "input": "\u00a3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a3;bonus=x",
+    "output": "x/x;x=\"\u00a3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a3\";bonus=x",
+    "output": "x/x;x=\"\u00a3\";bonus=x"
+  },
+  {
+    "input": "\u00a4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a4;bonus=x",
+    "output": "x/x;x=\"\u00a4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a4\";bonus=x",
+    "output": "x/x;x=\"\u00a4\";bonus=x"
+  },
+  {
+    "input": "\u00a5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a5;bonus=x",
+    "output": "x/x;x=\"\u00a5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a5\";bonus=x",
+    "output": "x/x;x=\"\u00a5\";bonus=x"
+  },
+  {
+    "input": "\u00a6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a6;bonus=x",
+    "output": "x/x;x=\"\u00a6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a6\";bonus=x",
+    "output": "x/x;x=\"\u00a6\";bonus=x"
+  },
+  {
+    "input": "\u00a7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a7;bonus=x",
+    "output": "x/x;x=\"\u00a7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a7\";bonus=x",
+    "output": "x/x;x=\"\u00a7\";bonus=x"
+  },
+  {
+    "input": "\u00a8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a8;bonus=x",
+    "output": "x/x;x=\"\u00a8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a8\";bonus=x",
+    "output": "x/x;x=\"\u00a8\";bonus=x"
+  },
+  {
+    "input": "\u00a9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a9;bonus=x",
+    "output": "x/x;x=\"\u00a9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a9\";bonus=x",
+    "output": "x/x;x=\"\u00a9\";bonus=x"
+  },
+  {
+    "input": "\u00aa/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00aa",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00aa=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00aa;bonus=x",
+    "output": "x/x;x=\"\u00aa\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00aa\";bonus=x",
+    "output": "x/x;x=\"\u00aa\";bonus=x"
+  },
+  {
+    "input": "\u00ab/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ab",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ab=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ab;bonus=x",
+    "output": "x/x;x=\"\u00ab\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ab\";bonus=x",
+    "output": "x/x;x=\"\u00ab\";bonus=x"
+  },
+  {
+    "input": "\u00ac/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ac",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ac=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ac;bonus=x",
+    "output": "x/x;x=\"\u00ac\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ac\";bonus=x",
+    "output": "x/x;x=\"\u00ac\";bonus=x"
+  },
+  {
+    "input": "\u00ad/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ad",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ad=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ad;bonus=x",
+    "output": "x/x;x=\"\u00ad\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ad\";bonus=x",
+    "output": "x/x;x=\"\u00ad\";bonus=x"
+  },
+  {
+    "input": "\u00ae/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ae",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ae=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ae;bonus=x",
+    "output": "x/x;x=\"\u00ae\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ae\";bonus=x",
+    "output": "x/x;x=\"\u00ae\";bonus=x"
+  },
+  {
+    "input": "\u00af/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00af",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00af=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00af;bonus=x",
+    "output": "x/x;x=\"\u00af\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00af\";bonus=x",
+    "output": "x/x;x=\"\u00af\";bonus=x"
+  },
+  {
+    "input": "\u00b0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b0;bonus=x",
+    "output": "x/x;x=\"\u00b0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b0\";bonus=x",
+    "output": "x/x;x=\"\u00b0\";bonus=x"
+  },
+  {
+    "input": "\u00b1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b1;bonus=x",
+    "output": "x/x;x=\"\u00b1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b1\";bonus=x",
+    "output": "x/x;x=\"\u00b1\";bonus=x"
+  },
+  {
+    "input": "\u00b2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b2;bonus=x",
+    "output": "x/x;x=\"\u00b2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b2\";bonus=x",
+    "output": "x/x;x=\"\u00b2\";bonus=x"
+  },
+  {
+    "input": "\u00b3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b3;bonus=x",
+    "output": "x/x;x=\"\u00b3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b3\";bonus=x",
+    "output": "x/x;x=\"\u00b3\";bonus=x"
+  },
+  {
+    "input": "\u00b4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b4;bonus=x",
+    "output": "x/x;x=\"\u00b4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b4\";bonus=x",
+    "output": "x/x;x=\"\u00b4\";bonus=x"
+  },
+  {
+    "input": "\u00b5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b5;bonus=x",
+    "output": "x/x;x=\"\u00b5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b5\";bonus=x",
+    "output": "x/x;x=\"\u00b5\";bonus=x"
+  },
+  {
+    "input": "\u00b6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b6;bonus=x",
+    "output": "x/x;x=\"\u00b6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b6\";bonus=x",
+    "output": "x/x;x=\"\u00b6\";bonus=x"
+  },
+  {
+    "input": "\u00b7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b7;bonus=x",
+    "output": "x/x;x=\"\u00b7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b7\";bonus=x",
+    "output": "x/x;x=\"\u00b7\";bonus=x"
+  },
+  {
+    "input": "\u00b8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b8;bonus=x",
+    "output": "x/x;x=\"\u00b8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b8\";bonus=x",
+    "output": "x/x;x=\"\u00b8\";bonus=x"
+  },
+  {
+    "input": "\u00b9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b9;bonus=x",
+    "output": "x/x;x=\"\u00b9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b9\";bonus=x",
+    "output": "x/x;x=\"\u00b9\";bonus=x"
+  },
+  {
+    "input": "\u00ba/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ba",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ba=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ba;bonus=x",
+    "output": "x/x;x=\"\u00ba\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ba\";bonus=x",
+    "output": "x/x;x=\"\u00ba\";bonus=x"
+  },
+  {
+    "input": "\u00bb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bb;bonus=x",
+    "output": "x/x;x=\"\u00bb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bb\";bonus=x",
+    "output": "x/x;x=\"\u00bb\";bonus=x"
+  },
+  {
+    "input": "\u00bc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bc;bonus=x",
+    "output": "x/x;x=\"\u00bc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bc\";bonus=x",
+    "output": "x/x;x=\"\u00bc\";bonus=x"
+  },
+  {
+    "input": "\u00bd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bd;bonus=x",
+    "output": "x/x;x=\"\u00bd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bd\";bonus=x",
+    "output": "x/x;x=\"\u00bd\";bonus=x"
+  },
+  {
+    "input": "\u00be/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00be",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00be=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00be;bonus=x",
+    "output": "x/x;x=\"\u00be\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00be\";bonus=x",
+    "output": "x/x;x=\"\u00be\";bonus=x"
+  },
+  {
+    "input": "\u00bf/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bf",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bf=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bf;bonus=x",
+    "output": "x/x;x=\"\u00bf\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bf\";bonus=x",
+    "output": "x/x;x=\"\u00bf\";bonus=x"
+  },
+  {
+    "input": "\u00c0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c0;bonus=x",
+    "output": "x/x;x=\"\u00c0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c0\";bonus=x",
+    "output": "x/x;x=\"\u00c0\";bonus=x"
+  },
+  {
+    "input": "\u00c1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c1;bonus=x",
+    "output": "x/x;x=\"\u00c1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c1\";bonus=x",
+    "output": "x/x;x=\"\u00c1\";bonus=x"
+  },
+  {
+    "input": "\u00c2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c2;bonus=x",
+    "output": "x/x;x=\"\u00c2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c2\";bonus=x",
+    "output": "x/x;x=\"\u00c2\";bonus=x"
+  },
+  {
+    "input": "\u00c3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c3;bonus=x",
+    "output": "x/x;x=\"\u00c3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c3\";bonus=x",
+    "output": "x/x;x=\"\u00c3\";bonus=x"
+  },
+  {
+    "input": "\u00c4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c4;bonus=x",
+    "output": "x/x;x=\"\u00c4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c4\";bonus=x",
+    "output": "x/x;x=\"\u00c4\";bonus=x"
+  },
+  {
+    "input": "\u00c5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c5;bonus=x",
+    "output": "x/x;x=\"\u00c5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c5\";bonus=x",
+    "output": "x/x;x=\"\u00c5\";bonus=x"
+  },
+  {
+    "input": "\u00c6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c6;bonus=x",
+    "output": "x/x;x=\"\u00c6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c6\";bonus=x",
+    "output": "x/x;x=\"\u00c6\";bonus=x"
+  },
+  {
+    "input": "\u00c7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c7;bonus=x",
+    "output": "x/x;x=\"\u00c7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c7\";bonus=x",
+    "output": "x/x;x=\"\u00c7\";bonus=x"
+  },
+  {
+    "input": "\u00c8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c8;bonus=x",
+    "output": "x/x;x=\"\u00c8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c8\";bonus=x",
+    "output": "x/x;x=\"\u00c8\";bonus=x"
+  },
+  {
+    "input": "\u00c9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c9;bonus=x",
+    "output": "x/x;x=\"\u00c9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c9\";bonus=x",
+    "output": "x/x;x=\"\u00c9\";bonus=x"
+  },
+  {
+    "input": "\u00ca/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ca",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ca=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ca;bonus=x",
+    "output": "x/x;x=\"\u00ca\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ca\";bonus=x",
+    "output": "x/x;x=\"\u00ca\";bonus=x"
+  },
+  {
+    "input": "\u00cb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cb;bonus=x",
+    "output": "x/x;x=\"\u00cb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cb\";bonus=x",
+    "output": "x/x;x=\"\u00cb\";bonus=x"
+  },
+  {
+    "input": "\u00cc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cc;bonus=x",
+    "output": "x/x;x=\"\u00cc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cc\";bonus=x",
+    "output": "x/x;x=\"\u00cc\";bonus=x"
+  },
+  {
+    "input": "\u00cd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cd;bonus=x",
+    "output": "x/x;x=\"\u00cd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cd\";bonus=x",
+    "output": "x/x;x=\"\u00cd\";bonus=x"
+  },
+  {
+    "input": "\u00ce/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ce",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ce=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ce;bonus=x",
+    "output": "x/x;x=\"\u00ce\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ce\";bonus=x",
+    "output": "x/x;x=\"\u00ce\";bonus=x"
+  },
+  {
+    "input": "\u00cf/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cf",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cf=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cf;bonus=x",
+    "output": "x/x;x=\"\u00cf\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cf\";bonus=x",
+    "output": "x/x;x=\"\u00cf\";bonus=x"
+  },
+  {
+    "input": "\u00d0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d0;bonus=x",
+    "output": "x/x;x=\"\u00d0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d0\";bonus=x",
+    "output": "x/x;x=\"\u00d0\";bonus=x"
+  },
+  {
+    "input": "\u00d1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d1;bonus=x",
+    "output": "x/x;x=\"\u00d1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d1\";bonus=x",
+    "output": "x/x;x=\"\u00d1\";bonus=x"
+  },
+  {
+    "input": "\u00d2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d2;bonus=x",
+    "output": "x/x;x=\"\u00d2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d2\";bonus=x",
+    "output": "x/x;x=\"\u00d2\";bonus=x"
+  },
+  {
+    "input": "\u00d3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d3;bonus=x",
+    "output": "x/x;x=\"\u00d3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d3\";bonus=x",
+    "output": "x/x;x=\"\u00d3\";bonus=x"
+  },
+  {
+    "input": "\u00d4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d4;bonus=x",
+    "output": "x/x;x=\"\u00d4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d4\";bonus=x",
+    "output": "x/x;x=\"\u00d4\";bonus=x"
+  },
+  {
+    "input": "\u00d5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d5;bonus=x",
+    "output": "x/x;x=\"\u00d5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d5\";bonus=x",
+    "output": "x/x;x=\"\u00d5\";bonus=x"
+  },
+  {
+    "input": "\u00d6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d6;bonus=x",
+    "output": "x/x;x=\"\u00d6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d6\";bonus=x",
+    "output": "x/x;x=\"\u00d6\";bonus=x"
+  },
+  {
+    "input": "\u00d7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d7;bonus=x",
+    "output": "x/x;x=\"\u00d7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d7\";bonus=x",
+    "output": "x/x;x=\"\u00d7\";bonus=x"
+  },
+  {
+    "input": "\u00d8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d8;bonus=x",
+    "output": "x/x;x=\"\u00d8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d8\";bonus=x",
+    "output": "x/x;x=\"\u00d8\";bonus=x"
+  },
+  {
+    "input": "\u00d9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d9;bonus=x",
+    "output": "x/x;x=\"\u00d9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d9\";bonus=x",
+    "output": "x/x;x=\"\u00d9\";bonus=x"
+  },
+  {
+    "input": "\u00da/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00da",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00da=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00da;bonus=x",
+    "output": "x/x;x=\"\u00da\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00da\";bonus=x",
+    "output": "x/x;x=\"\u00da\";bonus=x"
+  },
+  {
+    "input": "\u00db/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00db",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00db=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00db;bonus=x",
+    "output": "x/x;x=\"\u00db\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00db\";bonus=x",
+    "output": "x/x;x=\"\u00db\";bonus=x"
+  },
+  {
+    "input": "\u00dc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00dc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00dc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00dc;bonus=x",
+    "output": "x/x;x=\"\u00dc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00dc\";bonus=x",
+    "output": "x/x;x=\"\u00dc\";bonus=x"
+  },
+  {
+    "input": "\u00dd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00dd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00dd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00dd;bonus=x",
+    "output": "x/x;x=\"\u00dd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00dd\";bonus=x",
+    "output": "x/x;x=\"\u00dd\";bonus=x"
+  },
+  {
+    "input": "\u00de/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00de",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00de=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00de;bonus=x",
+    "output": "x/x;x=\"\u00de\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00de\";bonus=x",
+    "output": "x/x;x=\"\u00de\";bonus=x"
+  },
+  {
+    "input": "\u00df/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00df",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00df=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00df;bonus=x",
+    "output": "x/x;x=\"\u00df\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00df\";bonus=x",
+    "output": "x/x;x=\"\u00df\";bonus=x"
+  },
+  {
+    "input": "\u00e0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e0;bonus=x",
+    "output": "x/x;x=\"\u00e0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e0\";bonus=x",
+    "output": "x/x;x=\"\u00e0\";bonus=x"
+  },
+  {
+    "input": "\u00e1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e1;bonus=x",
+    "output": "x/x;x=\"\u00e1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e1\";bonus=x",
+    "output": "x/x;x=\"\u00e1\";bonus=x"
+  },
+  {
+    "input": "\u00e2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e2;bonus=x",
+    "output": "x/x;x=\"\u00e2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e2\";bonus=x",
+    "output": "x/x;x=\"\u00e2\";bonus=x"
+  },
+  {
+    "input": "\u00e3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e3;bonus=x",
+    "output": "x/x;x=\"\u00e3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e3\";bonus=x",
+    "output": "x/x;x=\"\u00e3\";bonus=x"
+  },
+  {
+    "input": "\u00e4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e4;bonus=x",
+    "output": "x/x;x=\"\u00e4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e4\";bonus=x",
+    "output": "x/x;x=\"\u00e4\";bonus=x"
+  },
+  {
+    "input": "\u00e5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e5;bonus=x",
+    "output": "x/x;x=\"\u00e5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e5\";bonus=x",
+    "output": "x/x;x=\"\u00e5\";bonus=x"
+  },
+  {
+    "input": "\u00e6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e6;bonus=x",
+    "output": "x/x;x=\"\u00e6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e6\";bonus=x",
+    "output": "x/x;x=\"\u00e6\";bonus=x"
+  },
+  {
+    "input": "\u00e7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e7;bonus=x",
+    "output": "x/x;x=\"\u00e7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e7\";bonus=x",
+    "output": "x/x;x=\"\u00e7\";bonus=x"
+  },
+  {
+    "input": "\u00e8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e8;bonus=x",
+    "output": "x/x;x=\"\u00e8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e8\";bonus=x",
+    "output": "x/x;x=\"\u00e8\";bonus=x"
+  },
+  {
+    "input": "\u00e9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e9;bonus=x",
+    "output": "x/x;x=\"\u00e9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e9\";bonus=x",
+    "output": "x/x;x=\"\u00e9\";bonus=x"
+  },
+  {
+    "input": "\u00ea/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ea",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ea=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ea;bonus=x",
+    "output": "x/x;x=\"\u00ea\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ea\";bonus=x",
+    "output": "x/x;x=\"\u00ea\";bonus=x"
+  },
+  {
+    "input": "\u00eb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00eb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00eb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00eb;bonus=x",
+    "output": "x/x;x=\"\u00eb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00eb\";bonus=x",
+    "output": "x/x;x=\"\u00eb\";bonus=x"
+  },
+  {
+    "input": "\u00ec/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ec",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ec=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ec;bonus=x",
+    "output": "x/x;x=\"\u00ec\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ec\";bonus=x",
+    "output": "x/x;x=\"\u00ec\";bonus=x"
+  },
+  {
+    "input": "\u00ed/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ed",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ed=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ed;bonus=x",
+    "output": "x/x;x=\"\u00ed\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ed\";bonus=x",
+    "output": "x/x;x=\"\u00ed\";bonus=x"
+  },
+  {
+    "input": "\u00ee/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ee",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ee=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ee;bonus=x",
+    "output": "x/x;x=\"\u00ee\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ee\";bonus=x",
+    "output": "x/x;x=\"\u00ee\";bonus=x"
+  },
+  {
+    "input": "\u00ef/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ef",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ef=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ef;bonus=x",
+    "output": "x/x;x=\"\u00ef\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ef\";bonus=x",
+    "output": "x/x;x=\"\u00ef\";bonus=x"
+  },
+  {
+    "input": "\u00f0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f0;bonus=x",
+    "output": "x/x;x=\"\u00f0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f0\";bonus=x",
+    "output": "x/x;x=\"\u00f0\";bonus=x"
+  },
+  {
+    "input": "\u00f1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f1;bonus=x",
+    "output": "x/x;x=\"\u00f1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f1\";bonus=x",
+    "output": "x/x;x=\"\u00f1\";bonus=x"
+  },
+  {
+    "input": "\u00f2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f2;bonus=x",
+    "output": "x/x;x=\"\u00f2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f2\";bonus=x",
+    "output": "x/x;x=\"\u00f2\";bonus=x"
+  },
+  {
+    "input": "\u00f3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f3;bonus=x",
+    "output": "x/x;x=\"\u00f3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f3\";bonus=x",
+    "output": "x/x;x=\"\u00f3\";bonus=x"
+  },
+  {
+    "input": "\u00f4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f4;bonus=x",
+    "output": "x/x;x=\"\u00f4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f4\";bonus=x",
+    "output": "x/x;x=\"\u00f4\";bonus=x"
+  },
+  {
+    "input": "\u00f5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f5;bonus=x",
+    "output": "x/x;x=\"\u00f5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f5\";bonus=x",
+    "output": "x/x;x=\"\u00f5\";bonus=x"
+  },
+  {
+    "input": "\u00f6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f6;bonus=x",
+    "output": "x/x;x=\"\u00f6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f6\";bonus=x",
+    "output": "x/x;x=\"\u00f6\";bonus=x"
+  },
+  {
+    "input": "\u00f7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f7;bonus=x",
+    "output": "x/x;x=\"\u00f7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f7\";bonus=x",
+    "output": "x/x;x=\"\u00f7\";bonus=x"
+  },
+  {
+    "input": "\u00f8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f8;bonus=x",
+    "output": "x/x;x=\"\u00f8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f8\";bonus=x",
+    "output": "x/x;x=\"\u00f8\";bonus=x"
+  },
+  {
+    "input": "\u00f9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f9;bonus=x",
+    "output": "x/x;x=\"\u00f9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f9\";bonus=x",
+    "output": "x/x;x=\"\u00f9\";bonus=x"
+  },
+  {
+    "input": "\u00fa/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fa",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fa=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fa;bonus=x",
+    "output": "x/x;x=\"\u00fa\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fa\";bonus=x",
+    "output": "x/x;x=\"\u00fa\";bonus=x"
+  },
+  {
+    "input": "\u00fb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fb;bonus=x",
+    "output": "x/x;x=\"\u00fb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fb\";bonus=x",
+    "output": "x/x;x=\"\u00fb\";bonus=x"
+  },
+  {
+    "input": "\u00fc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fc;bonus=x",
+    "output": "x/x;x=\"\u00fc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fc\";bonus=x",
+    "output": "x/x;x=\"\u00fc\";bonus=x"
+  },
+  {
+    "input": "\u00fd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fd;bonus=x",
+    "output": "x/x;x=\"\u00fd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fd\";bonus=x",
+    "output": "x/x;x=\"\u00fd\";bonus=x"
+  },
+  {
+    "input": "\u00fe/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fe",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fe=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fe;bonus=x",
+    "output": "x/x;x=\"\u00fe\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fe\";bonus=x",
+    "output": "x/x;x=\"\u00fe\";bonus=x"
+  },
+  {
+    "input": "\u00ff/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ff",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ff=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ff;bonus=x",
+    "output": "x/x;x=\"\u00ff\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ff\";bonus=x",
+    "output": "x/x;x=\"\u00ff\";bonus=x"
+  }
+]

+ 262 - 0
data-url/tests/mime-types.json

@@ -0,0 +1,262 @@
+[
+  "Basics",
+  {
+    "input": "text/html;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "TEXT/HTML;CHARSET=GBK",
+    "output": "text/html;charset=GBK",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Legacy comment syntax",
+  {
+    "input": "text/html;charset=gbk(",
+    "output": "text/html;charset=\"gbk(\"",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;x=(;charset=gbk",
+    "output": "text/html;x=\"(\";charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Duplicate parameter",
+  {
+    "input": "text/html;charset=gbk;charset=windows-1255",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Spaces",
+  {
+    "input": "text/html;charset =gbk",
+    "output": "text/html",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html ;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html; charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset= gbk",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Single quotes are a token, not a delimiter",
+  {
+    "input": "text/html;charset='gbk'",
+    "output": "text/html;charset='gbk'",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset='gbk",
+    "output": "text/html;charset='gbk",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset=gbk'",
+    "output": "text/html;charset=gbk'",
+    "navigable": true,
+    "encoding": null
+  },
+  "Invalid parameters",
+  {
+    "input": "text/html;test;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;test=;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;';charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;\";charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html ; ; charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;;;;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Double quotes",
+  {
+    "input": "text/html;charset=\"gbk\"",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=gbk\"",
+    "output": "text/html;charset=\"gbk\\\"\"",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset=\" gbk\"",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\\ gbk\"",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\\g\\b\\k\"",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"gbk\"x",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Unexpected code points",
+  {
+    "input": "text/html;charset={gbk}",
+    "output": "text/html;charset=\"{gbk}\"",
+    "navigable": true,
+    "encoding": null
+  },
+  "Parameter name longer than 127",
+  {
+    "input": "text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk",
+    "output": "text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "type/subtype longer than 127",
+  {
+    "input": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
+    "output": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+  },
+  "Valid",
+  {
+    "input": "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+    "output": "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+  },
+  {
+    "input": "x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\"",
+    "output": "x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\""
+  },
+  "End-of-file handling",
+  {
+    "input": "x/x;test",
+    "output": "x/x"
+  },
+  {
+    "input": "x/x;test=\"\\",
+    "output": "x/x;test=\"\\\\\""
+  },
+  "Whitespace (not handled by generated-mime-types.json or above)",
+  {
+    "input": "x/x;x= ",
+    "output": "x/x"
+  },
+  {
+    "input": "x/x;x=\t",
+    "output": "x/x"
+  },
+  "Latin1",
+  {
+    "input": "text/html;test=\u00FF;charset=gbk",
+    "output": "text/html;test=\"\u00FF\";charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  ">Latin1",
+  {
+    "input": "x/x;test=\uFFFD;x=x",
+    "output": "x/x;x=x"
+  },
+  "Failure",
+  {
+    "input": "",
+    "output": null
+  },
+  {
+    "input": "\t",
+    "output": null
+  },
+  {
+    "input": "bogus",
+    "output": null
+  },
+  {
+    "input": "bogus/",
+    "output": null
+  },
+  {
+    "input": "bogus/ ",
+    "output": null
+  },
+  {
+    "input": "bogus/bogus/;",
+    "output": null
+  },
+  {
+    "input": "</>",
+    "output": null
+  },
+  {
+    "input": "(/)",
+    "output": null
+  },
+  {
+    "input": "text/html(;doesnot=matter",
+    "output": null
+  },
+  {
+    "input": "{/}",
+    "output": null
+  },
+  {
+    "input": "\u0100/\u0100",
+    "output": null
+  }
+]

+ 152 - 0
data-url/tests/wpt.rs

@@ -0,0 +1,152 @@
+extern crate data_url;
+extern crate rustc_test;
+#[macro_use] extern crate serde;
+extern crate serde_json;
+
+fn run_data_url(input: String, expected_mime: Option<String>, expected_body: Option<Vec<u8>>) {
+    let url = data_url::DataUrl::process(&input);
+    if let Some(expected_mime) = expected_mime {
+        let url = url.unwrap();
+        let (body, _) = url.decode_to_vec().unwrap();
+        if expected_mime == "" {
+            assert_eq!(url.mime_type().to_string(), "text/plain;charset=US-ASCII")
+        } else {
+            assert_eq!(url.mime_type().to_string(), expected_mime)
+        }
+        if let Some(expected_body) = expected_body {
+            assert_eq!(body, expected_body)
+        }
+    } else if let Ok(url) = url {
+        assert!(url.decode_to_vec().is_err(), "{:?}", url.mime_type())
+    }
+}
+
+fn collect_data_url<F>(add_test: &mut F)
+    where F: FnMut(String, bool, rustc_test::TestFn)
+{
+    let known_failures = [
+        "data://test:test/,X",
+    ];
+
+    #[derive(Deserialize)]
+    #[serde(untagged)]
+    enum TestCase {
+        Two(String, Option<String>),
+        Three(String, Option<String>, Vec<u8>),
+    };
+
+    let v: Vec<TestCase> = serde_json::from_str(include_str!("data-urls.json")).unwrap();
+    for test in v {
+        let (input, expected_mime, expected_body) = match test {
+            TestCase::Two(i, m) => (i, m, None),
+            TestCase::Three(i, m, b) => (i, m, Some(b)),
+        };
+        let should_panic = known_failures.contains(&&*input);
+        add_test(
+            format!("data: URL {:?}", input),
+            should_panic,
+            rustc_test::TestFn::dyn_test_fn(move || {
+                run_data_url(input, expected_mime, expected_body)
+            })
+        );
+    }
+}
+
+fn run_base64(input: String, expected: Option<Vec<u8>>) {
+    let result = data_url::forgiving_base64::decode_to_vec(input.as_bytes());
+    match (result, expected) {
+        (Ok(bytes), Some(expected)) => assert_eq!(bytes, expected),
+        (Ok(bytes), None) => panic!("Expected error, got {:?}", bytes),
+        (Err(e), Some(expected)) => panic!("Expected {:?}, got error {:?}", expected, e),
+        (Err(_), None) => {}
+    }
+}
+
+
+fn collect_base64<F>(add_test: &mut F)
+    where F: FnMut(String, bool, rustc_test::TestFn)
+{
+    let known_failures = [];
+
+    let v: Vec<(String, Option<Vec<u8>>)> =
+        serde_json::from_str(include_str!("base64.json")).unwrap();
+    for (input, expected) in v {
+        let should_panic = known_failures.contains(&&*input);
+        add_test(
+            format!("base64 {:?}", input),
+            should_panic,
+            rustc_test::TestFn::dyn_test_fn(move || {
+                run_base64(input, expected)
+            })
+        );
+    }
+}
+
+fn run_mime(input: String, expected: Option<String>) {
+    let result = input.parse::<data_url::mime::Mime>();
+    match (result, expected) {
+        (Ok(mime), Some(expected)) => assert_eq!(mime.to_string(), expected),
+        (Ok(mime), None) => panic!("Expected error, got {:?}", mime),
+        (Err(e), Some(expected)) => panic!("Expected {:?}, got error {:?}", expected, e),
+        (Err(_), None) => {}
+    }
+}
+
+
+fn collect_mime<F>(add_test: &mut F)
+    where F: FnMut(String, bool, rustc_test::TestFn)
+{
+    let known_failures = [];
+
+    #[derive(Deserialize)]
+    #[serde(untagged)]
+    enum Entry {
+        Comment(String),
+        TestCase { input: String, output: Option<String> }
+    }
+
+    let v: Vec<Entry> = serde_json::from_str(include_str!("mime-types.json")).unwrap();
+    let v2: Vec<Entry> = serde_json::from_str(include_str!("generated-mime-types.json")).unwrap();
+    let entries = v.into_iter().chain(v2);
+
+    let mut last_comment = None;
+    for entry in entries {
+        let (input, expected) = match entry {
+            Entry::TestCase { input, output } => (input, output),
+            Entry::Comment(s) => {
+                last_comment = Some(s);
+                continue
+            }
+        };
+
+        let should_panic = known_failures.contains(&&*input);
+        add_test(
+            if let Some(ref s) = last_comment {
+                format!("MIME type {:?} {:?}", s, input)
+            } else {
+                format!("MIME type {:?}", input)
+            },
+            should_panic,
+            rustc_test::TestFn::dyn_test_fn(move || {
+                run_mime(input, expected)
+            })
+        );
+    }
+}
+
+fn main() {
+    let mut tests = Vec::new();
+    {
+        let mut add_one = |name: String, should_panic: bool, run: rustc_test::TestFn| {
+            let mut desc = rustc_test::TestDesc::new(rustc_test::DynTestName(name));
+            if should_panic {
+                desc.should_panic = rustc_test::ShouldPanic::Yes
+            }
+            tests.push(rustc_test::TestDescAndFn { desc, testfn: run })
+        };
+        collect_data_url(&mut add_one);
+        collect_base64(&mut add_one);
+        collect_mime(&mut add_one);
+    }
+    rustc_test::test_main(&std::env::args().collect::<Vec<_>>(), tests)
+}

+ 1 - 1
url_serde/Cargo.toml

@@ -13,7 +13,7 @@ license = "MIT/Apache-2.0"
 
 [dependencies]
 serde = "1.0"
-url = "1.0.0"
+url = {version = "1.0.0", path = ".."}
 
 [dev-dependencies]
 serde_json = "1.0"