2
* Copyright 2007 Google Inc. All rights reserved.
3
* Copyright 2012 Apple Inc. All rights reserved.
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions are
9
* * Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* * Redistributions in binary form must reproduce the above
12
* copyright notice, this list of conditions and the following disclaimer
13
* in the documentation and/or other materials provided with the
15
* * Neither the name of Google Inc. nor the names of its
16
* contributors may be used to endorse or promote products derived from
17
* this software without specific prior written permission.
19
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
// Canonicalizer functions for working with and resolving relative URLs.
37
#include "URLCanonInternal.h"
39
#include "URLParseInternal.h"
40
#include "URLUtilInternal.h"
46
namespace URLCanonicalizer {
50
// Firefox does a case-sensitive compare (which is probably wrong--Mozilla bug
51
// 379034), whereas IE is case-insensetive.
53
// We choose to be more permissive like IE. We don't need to worry about
54
// unescaping or anything here: neither IE or Firefox allow this. We also
55
// don't have to worry about invalid scheme characters since we are comparing
56
// against the canonical scheme of the base.
58
// The base URL should always be canonical, therefore is ASCII.
59
template<typename CHAR>
60
bool AreSchemesEqual(const char* base,
61
const URLComponent& baseScheme,
63
const URLComponent& cmpScheme)
65
if (baseScheme.length() != cmpScheme.length())
67
for (int i = 0; i < baseScheme.length(); i++) {
68
// We assume the base is already canonical, so we don't have to
70
if (canonicalSchemeChar(cmp[cmpScheme.begin() + i]) !=
71
base[baseScheme.begin() + i])
78
// Here, we also allow Windows paths to be represented as "/C:/" so we can be
79
// consistent about URL paths beginning with slashes. This function is like
80
// DoesBeginWindowsDrivePath except that it also requires a slash at the
82
template<typename CHAR>
83
bool doesBeginSlashWindowsDriveSpec(const CHAR* spec, int startOffset, int specLength)
85
if (startOffset >= specLength)
87
return URLParser::isURLSlash(spec[startOffset]) && URLParser::doesBeginWindowsDriveSpec(spec, startOffset + 1, specLength);
92
// See isRelativeURL in the header file for usage.
93
template<typename CharacterType>
94
bool doIsRelativeURL(const char* base, const URLSegments& baseParsed,
95
const CharacterType* url, int urlLength,
96
bool isBaseHierarchical,
97
bool& isRelative, URLComponent& relativeComponent)
99
isRelative = false; // So we can default later to not relative.
101
// Trim whitespace and construct a new range for the substring.
103
URLParser::trimURL(url, begin, urlLength);
104
if (begin >= urlLength) {
105
// Empty URLs are relative, but do nothing.
106
relativeComponent = URLComponent(begin, 0);
112
// We special case paths like "C:\foo" so they can link directly to the
113
// file on Windows (IE compatability). The security domain stuff should
114
// prevent a link like this from actually being followed if its on a
117
// We treat "C:/foo" as an absolute URL. We can go ahead and treat "/c:/"
118
// as relative, as this will just replace the path when the base scheme
119
// is a file and the answer will still be correct.
121
// We require strict backslashes when detecting UNC since two forward
122
// shashes should be treated a a relative URL with a hostname.
123
if (URLParser::doesBeginWindowsDriveSpec(url, begin, urlLength) || URLParser::doesBeginUNCPath(url, begin, urlLength, true))
125
#endif // OS(WINDOWS)
127
// See if we've got a scheme, if not, we know this is a relative URL.
128
// BUT: Just because we have a scheme, doesn't make it absolute.
129
// "http:foo.html" is a relative URL with path "foo.html". If the scheme is
130
// empty, we treat it as relative (":foo") like IE does.
132
if (!URLParser::ExtractScheme(url, urlLength, &scheme) || !scheme.length()) {
133
// Don't allow relative URLs if the base scheme doesn't support it.
134
if (!isBaseHierarchical)
137
relativeComponent = URLComponent::fromRange(begin, urlLength);
142
// If the scheme isn't valid, then it's relative.
143
int schemeEnd = scheme.end();
144
for (int i = scheme.begin(); i < schemeEnd; i++) {
145
if (!canonicalSchemeChar(url[i])) {
146
relativeComponent = URLComponent::fromRange(begin, urlLength);
152
// If the scheme is not the same, then we can't count it as relative.
153
if (!AreSchemesEqual(base, baseParsed.scheme, url, scheme))
156
// When the scheme that they both share is not hierarchical, treat the
157
// incoming scheme as absolute (this way with the base of "data:foo",
158
// "data:bar" will be reported as absolute.
159
if (!isBaseHierarchical)
162
int colonOffset = scheme.end();
164
// If it's a filesystem URL, the only valid way to make it relative is not to
165
// supply a scheme. There's no equivalent to e.g. http:index.html.
166
if (URLUtilities::CompareSchemeComponent(url, scheme, "filesystem"))
169
// ExtractScheme guarantees that the colon immediately follows what it
170
// considers to be the scheme. countConsecutiveSlashes will handle the
171
// case where the begin offset is the end of the input.
172
int numSlashes = URLParser::countConsecutiveSlashes(url, colonOffset + 1, urlLength);
174
if (!numSlashes || numSlashes == 1) {
175
// No slashes means it's a relative path like "http:foo.html". One slash
176
// is an absolute path. "http:/home/foo.html"
178
relativeComponent = URLComponent::fromRange(colonOffset + 1, urlLength);
182
// Two or more slashes after the scheme we treat as absolute.
186
// Copies all characters in the range [begin, end) of |spec| to the output,
187
// up until and including the last slash. There should be a slash in the
188
// range, if not, nothing will be copied.
190
// The input is assumed to be canonical, so we search only for exact slashes
191
// and not backslashes as well. We also know that it's ASCII.
192
void CopyToLastSlash(const char* spec, int begin, int end, URLBuffer<char>& output)
194
// Find the last slash.
196
for (int i = end - 1; i >= begin; --i) {
197
if (spec[i] == '/') {
206
for (int i = begin; i <= lastSlash; ++i)
207
output.append(spec[i]);
210
// Copies a single component from the source to the output. This is used
211
// when resolving relative URLs and a given component is unchanged. Since the
212
// source should already be canonical, we don't have to do anything special,
213
// and the input is ASCII.
214
void CopyOneComponent(const char* source,
215
const URLComponent& sourceComponent,
216
URLBuffer<char>& output,
217
URLComponent* outputComponent)
219
if (sourceComponent.length() < 0) {
220
// This component is not present.
221
*outputComponent = URLComponent();
225
outputComponent->setBegin(output.length());
226
int sourceEnd = sourceComponent.end();
227
for (int i = sourceComponent.begin(); i < sourceEnd; i++)
228
output.append(source[i]);
229
outputComponent->setLength(output.length() - outputComponent->begin());
234
// Called on Windows when the base URL is a file URL, this will copy the "C:"
235
// to the output, if there is a drive letter and if that drive letter is not
236
// being overridden by the relative URL. Otherwise, do nothing.
238
// It will return the index of the beginning of the next character in the
239
// base to be processed: if there is a "C:", the slash after it, or if
240
// there is no drive letter, the slash at the beginning of the path, or
241
// the end of the base. This can be used as the starting offset for further
243
template<typename CHAR>
244
int CopyBaseDriveSpecIfNecessary(const char* baseURL,
247
const CHAR* relativeURL,
249
int relativeUrlLength,
250
URLBuffer<char>& output)
252
if (basePathBegin >= basePathEnd)
253
return basePathBegin; // No path.
255
// If the relative begins with a drive spec, don't do anything. The existing
256
// drive spec in the base will be replaced.
257
if (URLParser::doesBeginWindowsDriveSpec(relativeURL,
258
pathStart, relativeUrlLength)) {
259
return basePathBegin; // Relative URL path is "C:/foo"
262
// The path should begin with a slash (as all canonical paths do). We check
263
// if it is followed by a drive letter and copy it.
264
if (doesBeginSlashWindowsDriveSpec(baseURL, basePathBegin, basePathEnd)) {
265
// Copy the two-character drive spec to the output. It will now look like
266
// "file:///C:" so the rest of it can be treated like a standard path.
268
output.append(baseURL[basePathBegin + 1]);
269
output.append(baseURL[basePathBegin + 2]);
270
return basePathBegin + 3;
273
return basePathBegin;
276
#endif // OS(WINDOWS)
278
// A subroutine of doResolveRelativeURL, this resolves the URL knowning that
279
// the input is a relative path or less (qyuery or ref).
280
template<typename CHAR>
281
bool doResolveRelativePath(const char* baseURL,
282
const URLSegments& baseParsed,
283
bool /* baseIsFile */,
284
const CHAR* relativeURL,
285
const URLComponent& relativeComponent,
286
URLQueryCharsetConverter* queryConverter,
287
URLBuffer<char>& output,
288
URLSegments* outputParsed)
292
// We know the authority section didn't change, copy it to the output. We
293
// also know we have a path so can copy up to there.
294
URLComponent path, query, ref;
295
URLParser::parsePathInternal(relativeURL,
300
// Canonical URLs always have a path, so we can use that offset.
301
output.append(baseURL, baseParsed.path.begin());
303
if (path.length() > 0) {
304
// The path is replaced or modified.
305
int truePathBegin = output.length();
307
// For file: URLs on Windows, we don't want to treat the drive letter and
308
// colon as part of the path for relative file resolution when the
309
// incoming URL does not provide a drive spec. We save the true path
310
// beginning so we can fix it up after we are done.
311
int basePathBegin = baseParsed.path.begin();
314
basePathBegin = CopyBaseDriveSpecIfNecessary(baseURL, baseParsed.path.begin(), baseParsed.path.end(),
315
relativeURL, relativeComponent.begin(), relativeComponent.end(),
317
// Now the output looks like either "file://" or "file:///C:"
318
// and we can start appending the rest of the path. |basePathBegin|
319
// points to the character in the base that comes next.
321
#endif // OS(WINDOWS)
323
if (URLParser::isURLSlash(relativeURL[path.begin()])) {
324
// Easy case: the path is an absolute path on the server, so we can
325
// just replace everything from the path on with the new versions.
326
// Since the input should be canonical hierarchical URL, we should
327
// always have a path.
328
success &= CanonicalizePath(relativeURL, path,
329
output, &outputParsed->path);
331
// Relative path, replace the query, and reference. We take the
332
// original path with the file part stripped, and append the new path.
333
// The canonicalizer will take care of resolving ".." and "."
334
int pathBegin = output.length();
335
CopyToLastSlash(baseURL, basePathBegin, baseParsed.path.end(),
337
success &= CanonicalizePartialPath(relativeURL, path, pathBegin,
339
outputParsed->path = URLComponent::fromRange(pathBegin, output.length());
341
// Copy the rest of the stuff after the path from the relative path.
344
// Finish with the query and reference part (these can't fail).
345
CanonicalizeQuery(relativeURL, query, queryConverter, output, &outputParsed->query);
346
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
348
// Fix the path beginning to add back the "C:" we may have written above.
349
outputParsed->path = URLComponent::fromRange(truePathBegin,
350
outputParsed->path.end());
354
// If we get here, the path is unchanged: copy to output.
355
CopyOneComponent(baseURL, baseParsed.path, output, &outputParsed->path);
357
if (query.isValid()) {
358
// Just the query specified, replace the query and reference (ignore
359
// failures for refs)
360
CanonicalizeQuery(relativeURL, query, queryConverter,
361
output, &outputParsed->query);
362
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
366
// If we get here, the query is unchanged: copy to output. Note that the
367
// range of the query parameter doesn't include the question mark, so we
368
// have to add it manually if there is a component.
369
if (baseParsed.query.isValid())
371
CopyOneComponent(baseURL, baseParsed.query, output, &outputParsed->query);
374
// Just the reference specified: replace it (ignoring failures).
375
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
379
// We should always have something to do in this function, the caller checks
380
// that some component is being replaced.
381
ASSERT_NOT_REACHED();
385
// Resolves a relative URL that contains a host. Typically, these will
386
// be of the form "//www.apple.com/foo/bar?baz#fragment" and the only thing which
387
// should be kept from the original URL is the scheme.
388
template<typename CHAR>
389
bool doResolveRelativeHost(const char* baseURL,
390
const URLSegments& baseParsed,
391
const CHAR* relativeURL,
392
const URLComponent& relativeComponent,
393
URLQueryCharsetConverter* queryConverter,
394
URLBuffer<char>& output,
395
URLSegments* outputParsed)
397
// Parse the relative URL, just like we would for anything following a
399
URLSegments relativeParsed; // Everything but the scheme is valid.
400
URLParser::parseAfterScheme(&relativeURL[relativeComponent.begin()],
401
relativeComponent.length(), relativeComponent.begin(),
404
// Now we can just use the replacement function to replace all the necessary
405
// parts of the old URL with the new one.
406
Replacements<CHAR> replacements;
407
replacements.SetUsername(relativeURL, relativeParsed.username);
408
replacements.SetPassword(relativeURL, relativeParsed.password);
409
replacements.SetHost(relativeURL, relativeParsed.host);
410
replacements.SetPort(relativeURL, relativeParsed.port);
411
replacements.SetPath(relativeURL, relativeParsed.path);
412
replacements.SetQuery(relativeURL, relativeParsed.query);
413
replacements.SetRef(relativeURL, relativeParsed.fragment);
415
return ReplaceStandardURL(baseURL, baseParsed, replacements,
416
queryConverter, output, outputParsed);
419
// Resolves a relative URL that happens to be an absolute file path. Examples
420
// include: "//hostname/path", "/c:/foo", and "//hostname/c:/foo".
421
template<typename CharacterType>
422
bool doResolveAbsoluteFile(const CharacterType* relativeURL,
423
const URLComponent& relativeComponent,
424
URLQueryCharsetConverter* queryConverter,
425
URLBuffer<char>& output,
426
URLSegments& outputParsed)
428
// Parse the file URL. The file URl parsing function uses the same logic
429
// as we do for determining if the file is absolute, in which case it will
430
// not bother to look for a scheme.
431
URLSegments relativeParsed;
432
URLParser::ParseFileURL(&relativeURL[relativeComponent.begin()],
433
relativeComponent.length(), &relativeParsed);
435
return CanonicalizeFileURL(&relativeURL[relativeComponent.begin()],
436
relativeComponent.length(), relativeParsed,
437
queryConverter, output, &outputParsed);
440
// TODO(brettw) treat two slashes as root like Mozilla for FTP?
441
template<typename CHAR>
442
bool doResolveRelativeURL(const char* baseURL,
443
const URLSegments& baseParsed,
445
const CHAR* relativeURL,
446
const URLComponent& relativeComponent,
447
URLQueryCharsetConverter* queryConverter,
448
URLBuffer<char>& output,
449
URLSegments* outputParsed)
451
// Starting point for our output parsed. We'll fix what we change.
452
*outputParsed = baseParsed;
454
// Sanity check: the input should have a host or we'll break badly below.
455
// We can only resolve relative URLs with base URLs that have hosts and
456
// paths (even the default path of "/" is OK).
458
// We allow hosts with no length so we can handle file URLs, for example.
459
if (baseParsed.path.length() <= 0) {
460
// On error, return the input (resolving a relative URL on a non-relative
462
int baseLength = baseParsed.length();
463
for (int i = 0; i < baseLength; i++)
464
output.append(baseURL[i]);
468
if (relativeComponent.length() <= 0) {
469
// Empty relative URL, leave unchanged, only removing the ref component.
470
int baseLength = baseParsed.length();
471
baseLength -= baseParsed.fragment.length() + 1;
472
outputParsed->fragment.reset();
473
output.append(baseURL, baseLength);
477
int numSlashes = URLParser::countConsecutiveSlashes(relativeURL, relativeComponent.begin(), relativeComponent.end());
480
// On Windows, two slashes for a file path (regardless of which direction
481
// they are) means that it's UNC. Two backslashes on any base scheme mean
482
// that it's an absolute UNC path (we use the baseIsFile flag to control
483
// how strict the UNC finder is).
485
// We also allow Windows absolute drive specs on any scheme (for example
486
// "c:\foo") like IE does. There must be no preceeding slashes in this
487
// case (we reject anything like "/c:/foo") because that should be treated
488
// as a path. For file URLs, we allow any number of slashes since that would
489
// be setting the path.
491
// This assumes the absolute path resolver handles absolute URLs like this
492
// properly. URLUtilities::DoCanonicalize does this.
493
int afterSlashes = relativeComponent.begin + numSlashes;
494
if (URLParser::doesBeginUNCPath(relativeURL, relativeComponent.begin(), relativeComponent.end(), !baseIsFile)
495
|| ((!numSlashes || baseIsFile) && URLParser::doesBeginWindowsDriveSpec(relativeURL, afterSlashes, relativeComponent.end()))) {
496
return doResolveAbsoluteFile(relativeURL, relativeComponent,
497
queryConverter, output, *outputParsed);
500
// Other platforms need explicit handling for file: URLs with multiple
501
// slashes because the generic scheme parsing always extracts a host, but a
502
// file: URL only has a host if it has exactly 2 slashes. This also
503
// handles the special case where the URL is only slashes, since that
504
// doesn't have a host part either.
505
if (baseIsFile && (numSlashes > 2 || numSlashes == relativeComponent.length())) {
506
return doResolveAbsoluteFile(relativeURL, relativeComponent,
507
queryConverter, output, *outputParsed);
511
// Any other double-slashes mean that this is relative to the scheme.
512
if (numSlashes >= 2) {
513
return doResolveRelativeHost(baseURL, baseParsed,
514
relativeURL, relativeComponent,
515
queryConverter, output, outputParsed);
518
// When we get here, we know that the relative URL is on the same host.
519
return doResolveRelativePath(baseURL, baseParsed, baseIsFile,
520
relativeURL, relativeComponent,
521
queryConverter, output, outputParsed);
526
bool isRelativeURL(const char* base, const URLSegments& baseParsed,
527
const char* fragment, int fragmentLength,
528
bool isBaseHierarchical,
529
bool& isRelative, URLComponent& relativeComponent)
531
return doIsRelativeURL<char>(base, baseParsed, fragment, fragmentLength, isBaseHierarchical, isRelative, relativeComponent);
534
bool isRelativeURL(const char* base, const URLSegments& baseParsed,
535
const UChar* fragment, int fragmentLength,
536
bool isBaseHierarchical,
537
bool& isRelative, URLComponent& relativeComponent)
539
return doIsRelativeURL<UChar>(base, baseParsed, fragment, fragmentLength, isBaseHierarchical, isRelative, relativeComponent);
542
bool resolveRelativeURL(const char* baseURL,
543
const URLSegments& baseParsed,
545
const char* relativeURL,
546
const URLComponent& relativeComponent,
547
URLQueryCharsetConverter* queryConverter,
548
URLBuffer<char>& output,
549
URLSegments* outputParsed)
551
return doResolveRelativeURL<char>(baseURL, baseParsed, baseIsFile, relativeURL,
552
relativeComponent, queryConverter, output, outputParsed);
555
bool resolveRelativeURL(const char* baseURL,
556
const URLSegments& baseParsed,
558
const UChar* relativeURL,
559
const URLComponent& relativeComponent,
560
URLQueryCharsetConverter* queryConverter,
561
URLBuffer<char>& output,
562
URLSegments* outputParsed)
564
return doResolveRelativeURL<UChar>(baseURL, baseParsed, baseIsFile, relativeURL,
565
relativeComponent, queryConverter, output, outputParsed);
568
} // namespace URLCanonicalizer
572
#endif // USE(WTFURL)