internal static unsafe void UnescapeString(char *pStr, int start, int end, ref ValueStringBuilder dest, char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser?syntax, bool isQuery) { if ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.CopyOnly) { dest.Append(pStr + start, end - start); return; } bool escapeReserved = false; bool iriParsing = Uri.IriParsingStatic(syntax) && ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.EscapeUnescape); for (int next = start; next < end;) { char ch = (char)0; for (; next < end; ++next) { if ((ch = pStr[next]) == '%') { if ((unescapeMode & UnescapeMode.Unescape) == 0) { // re-escape, don't check anything else escapeReserved = true; } else if (next + 2 < end) { ch = DecodeHexChars(pStr[next + 1], pStr[next + 2]); // Unescape a good sequence if full unescape is requested if (unescapeMode >= UnescapeMode.UnescapeAll) { if (ch == Uri.c_DummyChar) { if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow) { // Should be a rare case where the app tries to feed an invalid escaped sequence throw new UriFormatException(SR.net_uri_BadString); } continue; } } // re-escape % from an invalid sequence else if (ch == Uri.c_DummyChar) { if ((unescapeMode & UnescapeMode.Escape) != 0) { escapeReserved = true; } else { continue; // we should throw instead but since v1.0 would just print '%' } } // Do not unescape '%' itself unless full unescape is requested else if (ch == '%') { next += 2; continue; } // Do not unescape a reserved char unless full unescape is requested else if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3) { next += 2; continue; } // Do not unescape a dangerous char unless it's V1ToStringFlags mode else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0 && IsNotSafeForUnescape(ch)) { next += 2; continue; } else if (iriParsing && ((ch <= '\x9F' && IsNotSafeForUnescape(ch)) || (ch > '\x9F' && !IriHelper.CheckIriUnicodeRange(ch, isQuery)))) { // check if unenscaping gives a char outside iri range // if it does then keep it escaped next += 2; continue; } // unescape escaped char or escape % break; } else if (unescapeMode >= UnescapeMode.UnescapeAll) { if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow) { // Should be a rare case where the app tries to feed an invalid escaped sequence throw new UriFormatException(SR.net_uri_BadString); } // keep a '%' as part of a bogus sequence continue; } else { escapeReserved = true; } // escape (escapeReserved==true) or otherwise unescape the sequence break; } else if ((unescapeMode & (UnescapeMode.Unescape | UnescapeMode.UnescapeAll)) == (UnescapeMode.Unescape | UnescapeMode.UnescapeAll)) { continue; } else if ((unescapeMode & UnescapeMode.Escape) != 0) { // Could actually escape some of the characters if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3) { // found an unescaped reserved character -> escape it escapeReserved = true; break; } else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0 && (ch <= '\x1F' || (ch >= '\x7F' && ch <= '\x9F'))) { // found an unescaped reserved character -> escape it escapeReserved = true; break; } } } //copy off previous characters from input while (start < next) { dest.Append(pStr[start++]); } if (next != end) { if (escapeReserved) { EscapeAsciiChar((byte)pStr[next], ref dest); escapeReserved = false; next++; } else if (ch <= 127) { dest.Append(ch); next += 3; } else { // Unicode int charactersRead = PercentEncodingHelper.UnescapePercentEncodedUTF8Sequence( pStr + next, end - next, ref dest, isQuery, iriParsing); Debug.Assert(charactersRead > 0); next += charactersRead; } start = next; } } }
// // IRI normalization for strings containing characters that are not allowed or // escaped characters that should be unescaped in the context of the specified Uri component. // internal static unsafe string EscapeUnescapeIri(char *pInput, int start, int end, UriComponents component) { int size = end - start; ValueStringBuilder dest = size <= 256 ? new ValueStringBuilder(stackalloc char[256]) : new ValueStringBuilder(size); Span <byte> maxUtf8EncodedSpan = stackalloc byte[4]; for (int i = start; i < end; ++i) { char ch = pInput[i]; if (ch == '%') { if (end - i > 2) { ch = UriHelper.DecodeHexChars(pInput[i + 1], pInput[i + 2]); // Do not unescape a reserved char if (ch == Uri.c_DummyChar || ch == '%' || CheckIsReserved(ch, component) || UriHelper.IsNotSafeForUnescape(ch)) { // keep as is dest.Append(pInput[i++]); dest.Append(pInput[i++]); dest.Append(pInput[i]); continue; } else if (ch <= '\x7F') { Debug.Assert(ch < 0xFF, "Expecting ASCII character."); //ASCII dest.Append(ch); i += 2; continue; } else { // possibly utf8 encoded sequence of unicode int charactersRead = PercentEncodingHelper.UnescapePercentEncodedUTF8Sequence( pInput + i, end - i, ref dest, component == UriComponents.Query, iriParsing: true); Debug.Assert(charactersRead > 0); i += charactersRead - 1; // -1 as i will be incremented in the loop } } else { dest.Append(pInput[i]); } } else if (ch > '\x7f') { // unicode bool isInIriUnicodeRange; bool surrogatePair = false; char ch2 = '\0'; if ((char.IsHighSurrogate(ch)) && (i + 1 < end)) { ch2 = pInput[i + 1]; isInIriUnicodeRange = CheckIriUnicodeRange(ch, ch2, out surrogatePair, component == UriComponents.Query); } else { isInIriUnicodeRange = CheckIriUnicodeRange(ch, component == UriComponents.Query); } if (isInIriUnicodeRange) { dest.Append(ch); if (surrogatePair) { dest.Append(ch2); } } else { Rune rune; if (surrogatePair) { rune = new Rune(ch, ch2); } else if (!Rune.TryCreate(ch, out rune)) { rune = Rune.ReplacementChar; } int bytesWritten = rune.EncodeToUtf8(maxUtf8EncodedSpan); Span <byte> encodedBytes = maxUtf8EncodedSpan.Slice(0, bytesWritten); foreach (byte b in encodedBytes) { UriHelper.EscapeAsciiChar(b, ref dest); } } if (surrogatePair) { i++; } } else { // just copy the character dest.Append(pInput[i]); } } return(dest.ToString()); }