blob: d6fb64b5fa792eb85af23a532af1722e20162a30 [file] [log] [blame]
[email protected]51bcc5d2013-04-24 01:41:371// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
[email protected]e7bba5f82013-04-10 20:10:524
avi51ba3e692015-12-26 17:30:505#include <limits.h>
6
Hans Wennborg0e223682020-04-27 21:51:297#include "base/check.h"
Vaclav Brozek295d736c2020-12-15 10:09:568#include "base/check_op.h"
[email protected]318076b2013-04-18 21:19:459#include "url/url_canon.h"
10#include "url/url_canon_internal.h"
11#include "url/url_parse_internal.h"
[email protected]e7bba5f82013-04-10 20:10:5212
[email protected]0318f922014-04-22 00:09:2313namespace url {
[email protected]e7bba5f82013-04-10 20:10:5214
15namespace {
16
17enum CharacterFlags {
18 // Pass through unchanged, whether escaped or unescaped. This doesn't
19 // actually set anything so you can't OR it to check, it's just to make the
20 // table below more clear when neither ESCAPE or UNESCAPE is set.
21 PASS = 0,
22
Ben Kellyd18c0e822021-03-05 22:42:1723 // This character requires special handling in DoPartialPathInternal. Doing
24 // this test
[email protected]e7bba5f82013-04-10 20:10:5225 // first allows us to filter out the common cases of regular characters that
26 // can be directly copied.
27 SPECIAL = 1,
28
29 // This character must be escaped in the canonical output. Note that all
30 // escaped chars also have the "special" bit set so that the code that looks
31 // for this is triggered. Not valid with PASS or ESCAPE
32 ESCAPE_BIT = 2,
33 ESCAPE = ESCAPE_BIT | SPECIAL,
34
35 // This character must be unescaped in canonical output. Not valid with
36 // ESCAPE or PASS. We DON'T set the SPECIAL flag since if we encounter these
37 // characters unescaped, they should just be copied.
38 UNESCAPE = 4,
39
40 // This character is disallowed in URLs. Note that the "special" bit is also
41 // set to trigger handling.
42 INVALID_BIT = 8,
43 INVALID = INVALID_BIT | SPECIAL,
44};
45
46// This table contains one of the above flag values. Note some flags are more
47// than one bits because they also turn on the "special" flag. Special is the
48// only flag that may be combined with others.
49//
50// This table is designed to match exactly what IE does with the characters.
51//
52// Dot is even more special, and the escaped version is handled specially by
53// IsDot. Therefore, we don't need the "escape" flag, and even the "unescape"
54// bit is never handled (we just need the "special") bit.
55const unsigned char kPathCharLookup[0x100] = {
56// NULL control chars...
57 INVALID, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
58// control chars...
59 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
60// ' ' ! " # $ % & ' ( ) * + , - . /
61 ESCAPE, PASS, ESCAPE, ESCAPE, PASS, ESCAPE, PASS, PASS, PASS, PASS, PASS, PASS, PASS, UNESCAPE,SPECIAL, PASS,
62// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
63 UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,PASS, PASS, ESCAPE, PASS, ESCAPE, ESCAPE,
64// @ A B C D E F G H I J K L M N O
65 PASS, UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,
66// P Q R S T U V W X Y Z [ \ ] ^ _
67 UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,PASS, ESCAPE, PASS, ESCAPE, UNESCAPE,
68// ` a b c d e f g h i j k l m n o
69 ESCAPE, UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,
70// p q r s t u v w x y z { | } ~ <NBSP>
71 UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,ESCAPE, ESCAPE, ESCAPE, UNESCAPE,ESCAPE,
72// ...all the high-bit characters are escaped
73 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
74 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
75 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
76 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
77 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
78 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
79 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE,
80 ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE, ESCAPE};
81
82enum DotDisposition {
83 // The given dot is just part of a filename and is not special.
84 NOT_A_DIRECTORY,
85
86 // The given dot is the current directory.
87 DIRECTORY_CUR,
88
89 // The given dot is the first of a double dot that should take us up one.
90 DIRECTORY_UP
91};
92
93// When the path resolver finds a dot, this function is called with the
94// character following that dot to see what it is. The return value
95// indicates what type this dot is (see above). This code handles the case
96// where the dot is at the end of the input.
97//
98// |*consumed_len| will contain the number of characters in the input that
99// express what we found.
100//
101// If the input is "../foo", |after_dot| = 1, |end| = 6, and
102// at the end, |*consumed_len| = 2 for the "./" this function consumed. The
103// original dot length should be handled by the caller.
104template<typename CHAR>
105DotDisposition ClassifyAfterDot(const CHAR* spec, int after_dot,
106 int end, int* consumed_len) {
107 if (after_dot == end) {
108 // Single dot at the end.
109 *consumed_len = 0;
110 return DIRECTORY_CUR;
111 }
[email protected]0318f922014-04-22 00:09:23112 if (IsURLSlash(spec[after_dot])) {
[email protected]e7bba5f82013-04-10 20:10:52113 // Single dot followed by a slash.
114 *consumed_len = 1; // Consume the slash
115 return DIRECTORY_CUR;
116 }
117
118 int second_dot_len = IsDot(spec, after_dot, end);
119 if (second_dot_len) {
120 int after_second_dot = after_dot + second_dot_len;
121 if (after_second_dot == end) {
122 // Double dot at the end.
123 *consumed_len = second_dot_len;
124 return DIRECTORY_UP;
125 }
[email protected]0318f922014-04-22 00:09:23126 if (IsURLSlash(spec[after_second_dot])) {
[email protected]e7bba5f82013-04-10 20:10:52127 // Double dot followed by a slash.
128 *consumed_len = second_dot_len + 1;
129 return DIRECTORY_UP;
130 }
131 }
132
133 // The dots are followed by something else, not a directory.
134 *consumed_len = 0;
135 return NOT_A_DIRECTORY;
136}
137
138// Rewinds the output to the previous slash. It is assumed that the output
139// ends with a slash and this doesn't count (we call this when we are
140// appending directory paths, so the previous path component has and ending
141// slash).
142//
143// This will stop at the first slash (assumed to be at position
144// |path_begin_in_output| and not go any higher than that. Some web pages
145// do ".." too many times, so we need to handle that brokenness.
146//
147// It searches for a literal slash rather than including a backslash as well
148// because it is run only on the canonical output.
149//
150// The output is guaranteed to end in a slash when this function completes.
151void BackUpToPreviousSlash(int path_begin_in_output,
152 CanonOutput* output) {
153 DCHECK(output->length() > 0);
154
155 int i = output->length() - 1;
156 DCHECK(output->at(i) == '/');
157 if (i == path_begin_in_output)
158 return; // We're at the first slash, nothing to do.
159
160 // Now back up (skipping the trailing slash) until we find another slash.
161 i--;
162 while (output->at(i) != '/' && i > path_begin_in_output)
163 i--;
164
165 // Now shrink the output to just include that last slash we found.
166 output->set_length(i + 1);
167}
168
pkasting69820362015-09-22 01:54:05169// Looks for problematic nested escape sequences and escapes the output as
170// needed to ensure they can't be misinterpreted.
171//
172// Our concern is that in input escape sequence that's invalid because it
173// contains nested escape sequences might look valid once those are unescaped.
174// For example, "%%300" is not a valid escape sequence, but after unescaping the
175// inner "%30" this becomes "%00" which is valid. Leaving this in the output
176// string can result in callers re-canonicalizing the string and unescaping this
177// sequence, thus resulting in something fundamentally different than the
178// original input here. This can cause a variety of problems.
179//
180// This function is called after we've just unescaped a sequence that's within
181// two output characters of a previous '%' that we know didn't begin a valid
182// escape sequence in the input string. We look for whether the output is going
183// to turn into a valid escape sequence, and if so, convert the initial '%' into
184// an escaped "%25" so the output can't be misinterpreted.
185//
186// |spec| is the input string we're canonicalizing.
187// |next_input_index| is the index of the next unprocessed character in |spec|.
188// |input_len| is the length of |spec|.
189// |last_invalid_percent_index| is the index in |output| of a previously-seen
190// '%' character. The caller knows this '%' character isn't followed by a valid
191// escape sequence in the input string.
192// |output| is the canonicalized output thus far. The caller guarantees this
193// ends with a '%' followed by one or two characters, and the '%' is the one
194// pointed to by |last_invalid_percent_index|. The last character in the string
195// was just unescaped.
196template<typename CHAR>
197void CheckForNestedEscapes(const CHAR* spec,
198 int next_input_index,
199 int input_len,
200 int last_invalid_percent_index,
201 CanonOutput* output) {
202 const int length = output->length();
203 const char last_unescaped_char = output->at(length - 1);
204
205 // If |output| currently looks like "%c", we need to try appending the next
206 // input character to see if this will result in a problematic escape
207 // sequence. Note that this won't trigger on the first nested escape of a
208 // two-escape sequence like "%%30%30" -- we'll allow the conversion to
209 // "%0%30" -- but the second nested escape will be caught by this function
210 // when it's called again in that case.
211 const bool append_next_char = last_invalid_percent_index == length - 2;
212 if (append_next_char) {
213 // If the input doesn't contain a 7-bit character next, this case won't be a
214 // problem.
215 if ((next_input_index == input_len) || (spec[next_input_index] >= 0x80))
216 return;
217 output->push_back(static_cast<char>(spec[next_input_index]));
218 }
219
220 // Now output ends like "%cc". Try to unescape this.
221 int begin = last_invalid_percent_index;
222 unsigned char temp;
223 if (DecodeEscaped(output->data(), &begin, output->length(), &temp)) {
224 // New escape sequence found. Overwrite the characters following the '%'
225 // with "25", and push_back() the one or two characters that were following
226 // the '%' when we were called.
227 if (!append_next_char)
228 output->push_back(output->at(last_invalid_percent_index + 1));
229 output->set(last_invalid_percent_index + 1, '2');
230 output->set(last_invalid_percent_index + 2, '5');
231 output->push_back(last_unescaped_char);
232 } else if (append_next_char) {
233 // Not a valid escape sequence, but we still need to undo appending the next
234 // source character so the caller can process it normally.
235 output->set_length(length);
236 }
237}
238
Ben Kellyd18c0e822021-03-05 22:42:17239// Canonicalizes and appends the given path to the output. It assumes that if
240// the input path starts with a slash, it should be copied to the output.
[email protected]e7bba5f82013-04-10 20:10:52241//
242// If there are already path components (this mode is used when appending
243// relative paths for resolving), it assumes that the output already has
244// a trailing slash and that if the input begins with a slash, it should be
245// copied to the output.
246//
247// We do not collapse multiple slashes in a row to a single slash. It seems
qyearsley2bc727d2015-08-14 20:17:15248// no web browsers do this, and we don't want incompatibilities, even though
[email protected]e7bba5f82013-04-10 20:10:52249// it would be correct for most systems.
Ben Kellyd18c0e822021-03-05 22:42:17250template <typename CHAR, typename UCHAR>
251bool DoPartialPathInternal(const CHAR* spec,
252 const Component& path,
253 int path_begin_in_output,
254 CanonOutput* output) {
[email protected]e7bba5f82013-04-10 20:10:52255 int end = path.end();
256
pkasting69820362015-09-22 01:54:05257 // We use this variable to minimize the amount of work done when unescaping --
258 // we'll only call CheckForNestedEscapes() when this points at one of the last
259 // couple of characters in |output|.
260 int last_invalid_percent_index = INT_MIN;
261
[email protected]e7bba5f82013-04-10 20:10:52262 bool success = true;
263 for (int i = path.begin; i < end; i++) {
Vaclav Brozek295d736c2020-12-15 10:09:56264 DCHECK_LT(last_invalid_percent_index, output->length());
[email protected]e7bba5f82013-04-10 20:10:52265 UCHAR uch = static_cast<UCHAR>(spec[i]);
pkasting69820362015-09-22 01:54:05266 if (sizeof(CHAR) > 1 && uch >= 0x80) {
[email protected]e7bba5f82013-04-10 20:10:52267 // We only need to test wide input for having non-ASCII characters. For
268 // narrow input, we'll always just use the lookup table. We don't try to
269 // do anything tricky with decoding/validating UTF-8. This function will
270 // read one or two UTF-16 characters and append the output as UTF-8. This
271 // call will be removed in 8-bit mode.
272 success &= AppendUTF8EscapedChar(spec, &i, end, output);
273 } else {
274 // Normal ASCII character or 8-bit input, use the lookup table.
275 unsigned char out_ch = static_cast<unsigned char>(uch);
276 unsigned char flags = kPathCharLookup[out_ch];
277 if (flags & SPECIAL) {
278 // Needs special handling of some sort.
279 int dotlen;
280 if ((dotlen = IsDot(spec, i, end)) > 0) {
Ben Kellyd18c0e822021-03-05 22:42:17281 // See if this dot was preceded by a slash in the output.
[email protected]e7bba5f82013-04-10 20:10:52282 //
283 // Note that we check this in the case of dots so we don't have to
284 // special case slashes. Since slashes are much more common than
285 // dots, this actually increases performance measurably (though
286 // slightly).
[email protected]e7bba5f82013-04-10 20:10:52287 if (output->length() > path_begin_in_output &&
288 output->at(output->length() - 1) == '/') {
289 // Slash followed by a dot, check to see if this is means relative
290 int consumed_len;
291 switch (ClassifyAfterDot<CHAR>(spec, i + dotlen, end,
292 &consumed_len)) {
293 case NOT_A_DIRECTORY:
294 // Copy the dot to the output, it means nothing special.
295 output->push_back('.');
296 i += dotlen - 1;
297 break;
298 case DIRECTORY_CUR: // Current directory, just skip the input.
299 i += dotlen + consumed_len - 1;
300 break;
301 case DIRECTORY_UP:
302 BackUpToPreviousSlash(path_begin_in_output, output);
Vaclav Brozek295d736c2020-12-15 10:09:56303 if (last_invalid_percent_index >= output->length()) {
304 last_invalid_percent_index = INT_MIN;
305 }
[email protected]e7bba5f82013-04-10 20:10:52306 i += dotlen + consumed_len - 1;
307 break;
308 }
309 } else {
qyearsley2bc727d2015-08-14 20:17:15310 // This dot is not preceded by a slash, it is just part of some
[email protected]e7bba5f82013-04-10 20:10:52311 // file name.
312 output->push_back('.');
313 i += dotlen - 1;
314 }
315
316 } else if (out_ch == '\\') {
317 // Convert backslashes to forward slashes
318 output->push_back('/');
319
320 } else if (out_ch == '%') {
321 // Handle escape sequences.
322 unsigned char unescaped_value;
323 if (DecodeEscaped(spec, &i, end, &unescaped_value)) {
324 // Valid escape sequence, see if we keep, reject, or unescape it.
pkasting69820362015-09-22 01:54:05325 // Note that at this point DecodeEscape() will have advanced |i| to
326 // the last character of the escape sequence.
[email protected]e7bba5f82013-04-10 20:10:52327 char unescaped_flags = kPathCharLookup[unescaped_value];
328
329 if (unescaped_flags & UNESCAPE) {
pkasting69820362015-09-22 01:54:05330 // This escaped value shouldn't be escaped. Try to copy it.
[email protected]e7bba5f82013-04-10 20:10:52331 output->push_back(unescaped_value);
pkasting69820362015-09-22 01:54:05332 // If we just unescaped a value within 2 output characters of the
333 // '%' from a previously-detected invalid escape sequence, we
334 // might have an input string with problematic nested escape
335 // sequences; detect and fix them.
336 if (last_invalid_percent_index >= (output->length() - 3)) {
337 CheckForNestedEscapes(spec, i + 1, end,
338 last_invalid_percent_index, output);
339 }
[email protected]e7bba5f82013-04-10 20:10:52340 } else {
pkasting69820362015-09-22 01:54:05341 // Either this is an invalid escaped character, or it's a valid
342 // escaped character we should keep escaped. In the first case we
343 // should just copy it exactly and remember the error. In the
344 // second we also copy exactly in case the server is sensitive to
345 // changing the case of any hex letters.
[email protected]e7bba5f82013-04-10 20:10:52346 output->push_back('%');
347 output->push_back(static_cast<char>(spec[i - 1]));
348 output->push_back(static_cast<char>(spec[i]));
pkasting69820362015-09-22 01:54:05349 if (unescaped_flags & INVALID_BIT)
350 success = false;
[email protected]e7bba5f82013-04-10 20:10:52351 }
352 } else {
pkasting69820362015-09-22 01:54:05353 // Invalid escape sequence. IE7+ rejects any URLs with such
354 // sequences, while other browsers pass them through unchanged. We
355 // use the permissive behavior.
356 // TODO(brettw): Consider testing IE's strict behavior, which would
357 // allow removing the code to handle nested escapes above.
358 last_invalid_percent_index = output->length();
[email protected]e7bba5f82013-04-10 20:10:52359 output->push_back('%');
360 }
361
362 } else if (flags & INVALID_BIT) {
363 // For NULLs, etc. fail.
364 AppendEscapedChar(out_ch, output);
365 success = false;
366
367 } else if (flags & ESCAPE_BIT) {
368 // This character should be escaped.
369 AppendEscapedChar(out_ch, output);
370 }
371 } else {
372 // Nothing special about this character, just append it.
373 output->push_back(out_ch);
374 }
375 }
376 }
377 return success;
378}
379
Ben Kellyd18c0e822021-03-05 22:42:17380// Perform the same logic as in DoPartialPathInternal(), but updates the
381// publicly exposed CanonOutput structure similar to DoPath(). Returns
382// true if successful.
383template <typename CHAR, typename UCHAR>
384bool DoPartialPath(const CHAR* spec,
385 const Component& path,
386 CanonOutput* output,
387 Component* out_path) {
388 out_path->begin = output->length();
389 bool success =
390 DoPartialPathInternal<CHAR, UCHAR>(spec, path, out_path->begin, output);
391 out_path->len = output->length() - out_path->begin;
392 return success;
393}
394
[email protected]e7bba5f82013-04-10 20:10:52395template<typename CHAR, typename UCHAR>
396bool DoPath(const CHAR* spec,
[email protected]0318f922014-04-22 00:09:23397 const Component& path,
[email protected]e7bba5f82013-04-10 20:10:52398 CanonOutput* output,
[email protected]0318f922014-04-22 00:09:23399 Component* out_path) {
[email protected]e7bba5f82013-04-10 20:10:52400 bool success = true;
401 out_path->begin = output->length();
402 if (path.len > 0) {
403 // Write out an initial slash if the input has none. If we just parse a URL
404 // and then canonicalize it, it will of course have a slash already. This
405 // check is for the replacement and relative URL resolving cases of file
406 // URLs.
[email protected]0318f922014-04-22 00:09:23407 if (!IsURLSlash(spec[path.begin]))
[email protected]e7bba5f82013-04-10 20:10:52408 output->push_back('/');
409
Ben Kellyd18c0e822021-03-05 22:42:17410 success =
411 DoPartialPathInternal<CHAR, UCHAR>(spec, path, out_path->begin, output);
[email protected]e7bba5f82013-04-10 20:10:52412 } else {
413 // No input, canonical path is a slash.
414 output->push_back('/');
415 }
416 out_path->len = output->length() - out_path->begin;
417 return success;
418}
419
420} // namespace
421
422bool CanonicalizePath(const char* spec,
[email protected]0318f922014-04-22 00:09:23423 const Component& path,
[email protected]e7bba5f82013-04-10 20:10:52424 CanonOutput* output,
[email protected]0318f922014-04-22 00:09:23425 Component* out_path) {
[email protected]e7bba5f82013-04-10 20:10:52426 return DoPath<char, unsigned char>(spec, path, output, out_path);
427}
428
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12429bool CanonicalizePath(const char16_t* spec,
[email protected]0318f922014-04-22 00:09:23430 const Component& path,
[email protected]e7bba5f82013-04-10 20:10:52431 CanonOutput* output,
[email protected]0318f922014-04-22 00:09:23432 Component* out_path) {
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12433 return DoPath<char16_t, char16_t>(spec, path, output, out_path);
[email protected]e7bba5f82013-04-10 20:10:52434}
435
436bool CanonicalizePartialPath(const char* spec,
[email protected]0318f922014-04-22 00:09:23437 const Component& path,
Ben Kellyd18c0e822021-03-05 22:42:17438 CanonOutput* output,
439 Component* out_path) {
440 return DoPartialPath<char, unsigned char>(spec, path, output, out_path);
[email protected]e7bba5f82013-04-10 20:10:52441}
442
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12443bool CanonicalizePartialPath(const char16_t* spec,
[email protected]0318f922014-04-22 00:09:23444 const Component& path,
Ben Kellyd18c0e822021-03-05 22:42:17445 CanonOutput* output,
446 Component* out_path) {
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12447 return DoPartialPath<char16_t, char16_t>(spec, path, output, out_path);
Ben Kellyd18c0e822021-03-05 22:42:17448}
449
450bool CanonicalizePartialPathInternal(const char* spec,
451 const Component& path,
452 int path_begin_in_output,
453 CanonOutput* output) {
454 return DoPartialPathInternal<char, unsigned char>(
455 spec, path, path_begin_in_output, output);
456}
457
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12458bool CanonicalizePartialPathInternal(const char16_t* spec,
Ben Kellyd18c0e822021-03-05 22:42:17459 const Component& path,
460 int path_begin_in_output,
461 CanonOutput* output) {
Jan Wilken Dörrie5aad5c22021-03-08 21:44:12462 return DoPartialPathInternal<char16_t, char16_t>(
Ben Kellyd18c0e822021-03-05 22:42:17463 spec, path, path_begin_in_output, output);
[email protected]e7bba5f82013-04-10 20:10:52464}
465
[email protected]0318f922014-04-22 00:09:23466} // namespace url