diff --git a/bin/jsonlint b/bin/jsonlint index e3c4626..f8c4578 100755 --- a/bin/jsonlint +++ b/bin/jsonlint @@ -88,7 +88,7 @@ function getFlags( $flags = 0; if($allowComments){ $flags |= JsonParser::ALLOW_COMMENTS; } if($detectKeyConflicts){ $flags |= JsonParser::DETECT_KEY_CONFLICTS; } - // TODO if($validateUTF8Encoding){ $flags |= JsonParser::VALIDATE_UTF8_ENCODING; } + if($validateUTF8Encoding){ $flags |= JsonParser::VALIDATE_UTF8_ENCODING; } return $flags; } @@ -142,6 +142,6 @@ function showUsage($programPath) echo ' -h, --help Show this message'.PHP_EOL; echo ' --allow-comments Cause jsonlint to enable syntax for comments similar to JSON5.'.PHP_EOL; echo ' --detect-key-conflicts Cause jsonlint to detect key conflicts in objects.'.PHP_EOL; - // TODO echo ' --validate-utf8-encoding Cause jsonlint to validate that the whole file is valid UTF-8 encoding.'.PHP_EOL; + echo ' --validate-utf8-encoding Cause jsonlint to validate that the whole file is valid UTF-8 encoding.'.PHP_EOL; exit(0); } diff --git a/src/Seld/JsonLint/InvalidEncodingException.php b/src/Seld/JsonLint/InvalidEncodingException.php new file mode 100644 index 0000000..ee5e7d5 --- /dev/null +++ b/src/Seld/JsonLint/InvalidEncodingException.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Seld\JsonLint; + +/** + * @author Laurent Lyaudet + */ +class InvalidEncodingException extends ParsingException +{ + /** + * @var array{key: string, line: int} + */ + protected $details; + + /** + * @param string $message + * @param string $key + * @phpstan-param array{line: int} $details + */ + public function __construct($message, $key, array $details) + { + $details['key'] = $key; + parent::__construct($message, $details); + } + + /** + * @return string + */ + public function getKey() + { + return $this->details['key']; + } + + /** + * @phpstan-return array{key: string, line: int} + */ + public function getDetails() + { + return $this->details; + } +} diff --git a/src/Seld/JsonLint/JsonParser.php b/src/Seld/JsonLint/JsonParser.php index cec4a87..64c3f04 100644 --- a/src/Seld/JsonLint/JsonParser.php +++ b/src/Seld/JsonLint/JsonParser.php @@ -32,6 +32,7 @@ class JsonParser const PARSE_TO_ASSOC = 4; const ALLOW_COMMENTS = 8; const ALLOW_DUPLICATE_KEYS_TO_ARRAY = 16; + const VALIDATE_UTF8_ENCODING = 32; /** @var Lexer */ private $lexer; @@ -205,6 +206,9 @@ public function parse($input, $flags = 0) if (($flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && ($flags & self::ALLOW_DUPLICATE_KEYS)) { throw new \InvalidArgumentException('Only one of ALLOW_DUPLICATE_KEYS and ALLOW_DUPLICATE_KEYS_TO_ARRAY can be used, you passed in both.'); } + if ($flags & self::VALIDATE_UTF8_ENCODING) { + $this->validateUTF8Encoding($input); + } $this->failOnBOM($input); @@ -605,4 +609,365 @@ private function failOnBOM($input) $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark"); } } + + /** + * @param string $input + * @return void + */ + private function validateUTF8Encoding($input) + { + // Fast-path + // But before PHP 5.4 Unicode support has bugs. + if (PHP_VERSION_ID >= 50400) { + if (function_exists("mb_check_encoding")) { + if(mb_check_encoding($input, 'UTF-8')){ + return; + } + } else { + if (preg_match('//u', $input) === 1) { + return; + } + } + } + + $iCurrentOctet = null; + $iContinuationOctetNeeded = 0; + $iOffsetInOctetsFromStringStart = 0; + $iOffsetInCharactersFromStringStart = -1; + $iCharacterStartPositionFromStringStart = 0; + $iCurrentLineNumber = 1; + $iOffsetInOctetsFromLineStart = -1; + $iOffsetInCharactersFromLineStart = -1; + $iCharacterStartPositionFromLineStart = -1; + + // For the second octet of a character, hence first continuation octet, + // further restriction may apply. + $I_CONTINUATION_OCTET_MINIMUM = 128; + $I_CONTINUATION_OCTET_MAXIMUM = 191; + $iCurrentContinuationOctetMinimum = $I_CONTINUATION_OCTET_MINIMUM; + $iCurrentContinuationOctetMaximum = $I_CONTINUATION_OCTET_MAXIMUM; + $sMessageForContinuationOctetAboveMaximum = null; + + for ($i = 0, $iMax = strlen($input); $i < $iMax; ++$i) { + $iCurrentOctet = ord($input[$i]); + $iOffsetInOctetsFromStringStart = $i; + ++$iOffsetInOctetsFromLineStart; + + /* + The octet values C0, C1, F5 to FF never appear. + C0 = 12*16 = 192 = 11000000 + C1 = 12*16 + 1 = 193 = 11000001 + F5 = 15*16 + 5 = 245 = 11110101 + FF = 15*16 + 15 = 255 = 11111111 + */ + if ( + $iCurrentOctet === 192 + || $iCurrentOctet === 193 + || $iCurrentOctet === 245 + || $iCurrentOctet === 255 + ) { + throw new InvalidEncodingException( + "Non-UTF8 character found on line " + .$iCurrentLineNumber + ."; the octet " + .($iOffsetInOctetsFromLineStart + 1) + .", part of the character " + .($iOffsetInCharactersFromLineStart + 1) + .", has value " + .$iCurrentOctet + ." which is one of the four forbidden values (C0, C1, F5, FF)." + ." This character starts at octet " + .($iCharacterStartPositionFromLineStart + 1) + ." of the current line." + ." (Sequential positions without line splitting:" + ." This is at character " + .($iOffsetInCharactersFromStringStart + 1) + ." and octet " + .($iOffsetInOctetsFromStringStart + 1) + ."." + ." This character starts at octet " + .($iCharacterStartPositionFromStringStart + 1) + .".)", + (string) $iCurrentOctet, + array( + 'current_octet' => $iCurrentOctet, + 'continuation_octet_needed' => $iContinuationOctetNeeded, + 'offset_in_octets_from_string_start' => $iOffsetInOctetsFromStringStart, + 'offset_in_characters_from_string_start' => $iOffsetInCharactersFromStringStart, + 'character_start_position_from_string_start' => $iCharacterStartPositionFromStringStart, + 'line' => $iCurrentLineNumber, + 'offset_in_octets_from_line_start' => $iOffsetInOctetsFromLineStart, + 'offset_in_characters_from_line_start' => $iOffsetInCharactersFromLineStart, + 'character_start_position_from_line_start' => $iCharacterStartPositionFromLineStart, + 'current_continuation_octet_minimum' => $iCurrentContinuationOctetMinimum, + 'current_continuation_octet_maximum' => $iCurrentContinuationOctetMaximum, + ) + ); + } + + /* + UTF8-octets = *( UTF8-char ) + UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 + UTF8-1 = %x00-7F + UTF8-2 = %xC2-DF UTF8-tail + Notice that values C0 and C1 are forbidden, hence %xC2 + UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / + %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) + Notice that E0 = 224 adds a restriction on second octet. + Notice that ED = 237 adds a restriction on second octet. + UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / + %xF4 %x80-8F 2( UTF8-tail ) + Notice that F0 = 240 adds a restriction on second octet. + Notice that F4 = 244 adds a restriction on second octet. + UTF8-tail = %x80-BF + */ + if ($iContinuationOctetNeeded > 0) { + if( + $iCurrentOctet < $iCurrentContinuationOctetMinimum + || $iCurrentOctet > $iCurrentContinuationOctetMaximum + ){ + throw new InvalidEncodingException( + "Non-UTF8 character found on line " + .$iCurrentLineNumber + ."; the octet " + .($iOffsetInOctetsFromLineStart + 1) + .", part of the character " + .($iOffsetInCharactersFromLineStart + 1) + .", has value " + .$iCurrentOctet + .( + $sMessageForContinuationOctetAboveMaximum !== null + && $iCurrentOctet > $iCurrentContinuationOctetMaximum + ? $sMessageForContinuationOctetAboveMaximum + : " which is not a continuation octet." + ) + ." This character starts at octet " + .($iCharacterStartPositionFromLineStart + 1) + ." of the current line." + ." (Sequential positions without line splitting:" + ." This is at character " + .($iOffsetInCharactersFromStringStart + 1) + ." and octet " + .($iOffsetInOctetsFromStringStart + 1) + ."." + ." This character starts at octet " + .($iCharacterStartPositionFromStringStart + 1) + .".)", + (string) $iCurrentOctet, + array( + 'current_octet' => $iCurrentOctet, + 'continuation_octet_needed' => $iContinuationOctetNeeded, + 'offset_in_octets_from_string_start' => $iOffsetInOctetsFromStringStart, + 'offset_in_characters_from_string_start' => $iOffsetInCharactersFromStringStart, + 'character_start_position_from_string_start' => $iCharacterStartPositionFromStringStart, + 'line' => $iCurrentLineNumber, + 'offset_in_octets_from_line_start' => $iOffsetInOctetsFromLineStart, + 'offset_in_characters_from_line_start' => $iOffsetInCharactersFromLineStart, + 'character_start_position_from_line_start' => $iCharacterStartPositionFromLineStart, + 'current_continuation_octet_minimum' => $iCurrentContinuationOctetMinimum, + 'current_continuation_octet_maximum' => $iCurrentContinuationOctetMaximum, + ) + ); + } + --$iContinuationOctetNeeded; + $iCurrentContinuationOctetMinimum = $I_CONTINUATION_OCTET_MINIMUM; + $iCurrentContinuationOctetMaximum = $I_CONTINUATION_OCTET_MAXIMUM; + $sMessageForContinuationOctetAboveMaximum = null; + continue; + } + + ++$iOffsetInCharactersFromStringStart; + ++$iOffsetInCharactersFromLineStart; + $iCharacterStartPositionFromStringStart = $iOffsetInOctetsFromStringStart; + $iCharacterStartPositionFromLineStart = $iOffsetInOctetsFromLineStart; + + if ($iCurrentOctet < 128) { // 0xxxxxxx ASCII + // if ($input[$i] === "\n") { + if ($iCurrentOctet === 10) { + ++$iCurrentLineNumber; + $iOffsetInOctetsFromLineStart = -1; + $iOffsetInCharactersFromLineStart = -1; + } + continue; + } + + if (/*$iCurrentOctet >= 128 &&*/$iCurrentOctet < 192) { + throw new InvalidEncodingException( + "Non-UTF8 character found on line " + .$iCurrentLineNumber + ."; the octet " + .($iOffsetInOctetsFromLineStart + 1) + .", part of the character " + .($iOffsetInCharactersFromLineStart + 1) + .", has value " + .$iCurrentOctet + ." which is a continuation octet." + ." This character starts at octet " + .($iCharacterStartPositionFromLineStart + 1) + ." of the current line." + ." (Sequential positions without line splitting:" + ." This is at character " + .($iOffsetInCharactersFromStringStart + 1) + ." and octet " + .($iOffsetInOctetsFromStringStart + 1) + ."." + ." This character starts at octet " + .($iCharacterStartPositionFromStringStart + 1) + .".)", + (string) $iCurrentOctet, + array( + 'current_octet' => $iCurrentOctet, + 'continuation_octet_needed' => $iContinuationOctetNeeded, + 'offset_in_octets_from_string_start' => $iOffsetInOctetsFromStringStart, + 'offset_in_characters_from_string_start' => $iOffsetInCharactersFromStringStart, + 'character_start_position_from_string_start' => $iCharacterStartPositionFromStringStart, + 'line' => $iCurrentLineNumber, + 'offset_in_octets_from_line_start' => $iOffsetInOctetsFromLineStart, + 'offset_in_characters_from_line_start' => $iOffsetInCharactersFromLineStart, + 'character_start_position_from_line_start' => $iCharacterStartPositionFromLineStart, + 'current_continuation_octet_minimum' => $iCurrentContinuationOctetMinimum, + 'current_continuation_octet_maximum' => $iCurrentContinuationOctetMaximum, + ) + ); + } + + if (/*$iCurrentOctet >= 192 &&*/$iCurrentOctet < 224) { + // 110xxxxx 10xxxxxx + $iContinuationOctetNeeded = 1; + continue; + } + if (/*$iCurrentOctet >= 224 &&*/$iCurrentOctet < 240) { + // 1110xxxx 10xxxxxx 10xxxxxx + $iContinuationOctetNeeded = 2; + /* + UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / + %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) + Notice that E0 = 224 adds a restriction on second octet. + Notice that ED = 237 adds a restriction on second octet. + + The definition of UTF-8 prohibits encoding character numbers between + U+D800 and U+DFFF. + D8 = 13*16 + 8 = 216 = 11010100 + DF = 13*16 + 15 = 223 = 11010101 + 216*256 = 55296 = 1101 1000 0000 0000 = D800 + to + 223*256 + 255 = 57343 = 1101 1111 1111 1111 = DFFF + + 1110xxxx + 11101101 = 237 = ED + + 237,160,128 + 237,191,191 + Thus, the whole "continuation range" is forbidden if start + is 237 and second octet, first continuation octet, + is >= 160. + */ + if($iCurrentOctet === 224){ + $iCurrentContinuationOctetMinimum = 160; + $iCurrentContinuationOctetMaximum = 191; // Normal value + } + if($iCurrentOctet === 237){ + $iCurrentContinuationOctetMinimum = 128; // Normal value + $iCurrentContinuationOctetMaximum = 159; + $sMessageForContinuationOctetAboveMaximum = ( + " which is into the forbidden range of surrogate pairs." + ); + } + continue; + } + if (/*$iCurrentOctet >= 240 &&*/$iCurrentOctet < /*248*/ 245) { + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + $iContinuationOctetNeeded = 3; + /* + UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / + %xF4 %x80-8F 2( UTF8-tail ) + Notice that F0 = 240 adds a restriction on second octet. + Notice that F4 = 244 adds a restriction on second octet. + */ + if($iCurrentOctet === 240){ + $iCurrentContinuationOctetMinimum = 144; + $iCurrentContinuationOctetMaximum = 191; // Normal value + } + if($iCurrentOctet === 244){ + $iCurrentContinuationOctetMinimum = 128; // Normal value + $iCurrentContinuationOctetMaximum = 143; + } + continue; + } + throw new InvalidEncodingException( + "Non-UTF8 character found on line " + .$iCurrentLineNumber + ."; the octet " + .($iOffsetInOctetsFromLineStart + 1) + .", part of the character " + .($iOffsetInCharactersFromLineStart + 1) + .", has value " + .$iCurrentOctet + ." which is invalid." + ." This character starts at octet " + .($iCharacterStartPositionFromLineStart + 1) + ." of the current line." + ." (Sequential positions without line splitting:" + ." This is at character " + .($iOffsetInCharactersFromStringStart + 1) + ." and octet " + .($iOffsetInOctetsFromStringStart + 1) + ."." + ." This character starts at octet " + .($iCharacterStartPositionFromStringStart + 1) + .".)", + (string) $iCurrentOctet, + array( + 'current_octet' => $iCurrentOctet, + 'continuation_octet_needed' => $iContinuationOctetNeeded, + 'offset_in_octets_from_string_start' => $iOffsetInOctetsFromStringStart, + 'offset_in_characters_from_string_start' => $iOffsetInCharactersFromStringStart, + 'character_start_position_from_string_start' => $iCharacterStartPositionFromStringStart, + 'line' => $iCurrentLineNumber, + 'offset_in_octets_from_line_start' => $iOffsetInOctetsFromLineStart, + 'offset_in_characters_from_line_start' => $iOffsetInCharactersFromLineStart, + 'character_start_position_from_line_start' => $iCharacterStartPositionFromLineStart, + 'current_continuation_octet_minimum' => $iCurrentContinuationOctetMinimum, + 'current_continuation_octet_maximum' => $iCurrentContinuationOctetMaximum, + ) + ); + } + if ($iContinuationOctetNeeded > 0) { + throw new InvalidEncodingException( + "Non-UTF8 character found on line " + .$iCurrentLineNumber + ."; at octet " + .($iOffsetInOctetsFromLineStart + 1) + .", part of the character " + .($iOffsetInCharactersFromLineStart + 1) + .", end of string was found instead of a continuation octet." + ." This character starts at octet " + .($iCharacterStartPositionFromLineStart + 1) + ." of the current line." + ." (Sequential positions without line splitting:" + ." This is at character " + .($iOffsetInCharactersFromStringStart + 1) + ." and octet " + .($iOffsetInOctetsFromStringStart + 1) + ."." + ." This character starts at octet " + .($iCharacterStartPositionFromStringStart + 1) + .".)", + "0", + array( + 'current_octet' => $iCurrentOctet, + 'continuation_octet_needed' => $iContinuationOctetNeeded, + 'offset_in_octets_from_string_start' => $iOffsetInOctetsFromStringStart, + 'offset_in_characters_from_string_start' => $iOffsetInCharactersFromStringStart, + 'character_start_position_from_string_start' => $iCharacterStartPositionFromStringStart, + 'line' => $iCurrentLineNumber, + 'offset_in_octets_from_line_start' => $iOffsetInOctetsFromLineStart, + 'offset_in_characters_from_line_start' => $iOffsetInCharactersFromLineStart, + 'character_start_position_from_line_start' => $iCharacterStartPositionFromLineStart, + 'current_continuation_octet_minimum' => $iCurrentContinuationOctetMinimum, + 'current_continuation_octet_maximum' => $iCurrentContinuationOctetMaximum, + ) + ); + } + } } diff --git a/tests/JsonParserTest.php b/tests/JsonParserTest.php index 9b903bf..370c838 100644 --- a/tests/JsonParserTest.php +++ b/tests/JsonParserTest.php @@ -13,6 +13,7 @@ use Seld\JsonLint\JsonParser; use Seld\JsonLint\ParsingException; use Seld\JsonLint\DuplicateKeyException; +use Seld\JsonLint\InvalidEncodingException; class JsonParserTest extends TestCase { @@ -286,6 +287,169 @@ public function testParseToArray() $this->assertSame(json_decode($json, true), $result); } + public function testFileValidUTF8() + { + try { + $parser = new JsonParser(); + $string = $parser->parse( + (string) file_get_contents(dirname(__FILE__) .'/validutf8.json'), + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->assertEquals($string, "abcdé"); + } catch (InvalidEncodingException $e) { + $this->fail('validutf8.json file should pass validation.'); + } + } + + public function testFileNonValidUTF8() + { + try { + $parser = new JsonParser(); + $parser->parse( + (string) file_get_contents(dirname(__FILE__) .'/nonvalidutf8.json'), + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('nonvalidutf8.json file should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + } + } + + public function testAllErrorTypesNonValidUTF8() + { + $parser = new JsonParser(); + try { + $parser->parse( + '"abcd'.chr(233).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('ISO 8859-15 "abcdé" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains(' which is not a continuation octet.', $e->getMessage()); + } + try { + $parser->parse( + '"abcd'.chr(233), + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('ISO 8859-15 "abcdé should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ', end of string was found instead of a continuation octet.', + $e->getMessage() + ); + } + $forbiddenOctets = array( + 192, + 193, + 245, + 255 + ); + foreach ($forbiddenOctets as $forbiddenOctet) { + try { + $parser->parse( + '"abcd'.chr(233).chr($forbiddenOctet).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d233\d'.$forbiddenOctet.'" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ' which is one of the four forbidden values (C0, C1, F5, FF).', + $e->getMessage() + ); + } + try { + $parser->parse( + '"abcd'.chr($forbiddenOctet).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d'.$forbiddenOctet.'" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ' which is one of the four forbidden values (C0, C1, F5, FF).', + $e->getMessage() + ); + } + } + try { + $parser->parse( + '"abcd'.chr(129).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d129" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ' which is a continuation octet.', + $e->getMessage() + ); + } + for ($i = 160; $i <= 191; ++$i) { + try { + $parser->parse( + '"abcd'.chr(237).chr($i).chr(129).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d237\d'.$i.'\d129" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ' which is into the forbidden range of surrogate pairs.', + $e->getMessage() + ); + } + try { + $parser->parse( + '"abcd'.chr(237).chr($i).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d237\d'.$i.'" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains( + ' which is into the forbidden range of surrogate pairs.', + $e->getMessage() + ); + } + } + for ($i = 246; $i < 255; ++$i) { // 245 and 255 already forbidden + try { + $parser->parse( + '"abcd'.chr($i).chr(129).chr(129).chr(129).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d'.$i.'\d129\d129\d129" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains(' which is invalid.', $e->getMessage()); + } + try { + $parser->parse( + '"abcd'.chr($i).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d'.$i.'" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains(' which is invalid.', $e->getMessage()); + } + try { + $parser->parse( + '"abcd'.chr(195).chr($i).'"', + JsonParser::VALIDATE_UTF8_ENCODING + ); + $this->fail('"abcd\d195\d'.$i.'" should not pass validation.'); + } catch (InvalidEncodingException $e) { + $this->assertContains('Non-UTF8 character found', $e->getMessage()); + $this->assertContains(' which is not a continuation octet.', $e->getMessage()); + } + } + } + public function testFileWithBOM() { try { diff --git a/tests/nonvalidutf8.json b/tests/nonvalidutf8.json new file mode 100644 index 0000000..6a7cbb6 --- /dev/null +++ b/tests/nonvalidutf8.json @@ -0,0 +1 @@ +"abcdé" diff --git a/tests/validutf8.json b/tests/validutf8.json new file mode 100644 index 0000000..148fada --- /dev/null +++ b/tests/validutf8.json @@ -0,0 +1 @@ +"abcdé"