parse_string() * * Parse a string, starting at the current position. */
| 687 | * Parse a string, starting at the current position. |
| 688 | */ |
| 689 | string parse_string() { |
| 690 | string out; |
| 691 | long last_escaped_codepoint = -1; |
| 692 | while (true) { |
| 693 | if (i == str.size()) |
| 694 | return fail("unexpected end of input in string", ""); |
| 695 | |
| 696 | char ch = str[i++]; |
| 697 | |
| 698 | if (ch == '"') { |
| 699 | encode_utf8(last_escaped_codepoint, out); |
| 700 | return out; |
| 701 | } |
| 702 | |
| 703 | if (in_range(ch, 0, 0x1f)) |
| 704 | return fail("unescaped " + esc(ch) + " in string", ""); |
| 705 | |
| 706 | // The usual case: non-escaped characters |
| 707 | if (ch != '\\') { |
| 708 | encode_utf8(last_escaped_codepoint, out); |
| 709 | last_escaped_codepoint = -1; |
| 710 | out += ch; |
| 711 | continue; |
| 712 | } |
| 713 | |
| 714 | // Handle escapes |
| 715 | if (i == str.size()) |
| 716 | return fail("unexpected end of input in string", ""); |
| 717 | |
| 718 | ch = str[i++]; |
| 719 | |
| 720 | if (ch == 'u') { |
| 721 | // Extract 4-byte escape sequence |
| 722 | string esc = str.substr(i, 4); |
| 723 | // Explicitly check length of the substring. The following loop |
| 724 | // relies on std::string returning the terminating NUL when |
| 725 | // accessing str[length]. Checking here reduces brittleness. |
| 726 | if (esc.length() < 4) { |
| 727 | return fail("bad \\u escape: " + esc, ""); |
| 728 | } |
| 729 | for (int j = 0; j < 4; j++) { |
| 730 | if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') |
| 731 | && !in_range(esc[j], '0', '9')) |
| 732 | return fail("bad \\u escape: " + esc, ""); |
| 733 | } |
| 734 | |
| 735 | long codepoint = strtol(esc.data(), nullptr, 16); |
| 736 | |
| 737 | // JSON specifies that characters outside the BMP shall be encoded as a pair |
| 738 | // of 4-hex-digit \u escapes encoding their surrogate pair components. Check |
| 739 | // whether we're in the middle of such a beast: the previous codepoint was an |
| 740 | // escaped lead (high) surrogate, and this is a trail (low) surrogate. |
| 741 | if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) |
| 742 | && in_range(codepoint, 0xDC00, 0xDFFF)) { |
| 743 | // Reassemble the two surrogate pairs into one astral-plane character, per |
| 744 | // the UTF-16 algorithm. |
| 745 | encode_utf8((((last_escaped_codepoint - 0xD800) << 10) |
| 746 | | (codepoint - 0xDC00)) + 0x10000, out); |