1// Copyright 2007, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30// Canonicalizer functions for working with and resolving relative URLs. 31 32#include "base/logging.h" 33#include "googleurl/src/url_canon.h" 34#include "googleurl/src/url_canon_internal.h" 35#include "googleurl/src/url_file.h" 36#include "googleurl/src/url_parse_internal.h" 37 38namespace url_canon { 39 40namespace { 41 42// Firefox does a case-sensitive compare (which is probably wrong--Mozilla bug 43// 379034), whereas IE is case-insensetive. 44// 45// We choose to be more permissive like IE. We don't need to worry about 46// unescaping or anything here: neither IE or Firefox allow this. We also 47// don't have to worry about invalid scheme characters since we are comparing 48// against the canonical scheme of the base. 49// 50// The base URL should always be canonical, therefore is ASCII. 51template<typename CHAR> 52bool AreSchemesEqual(const char* base, 53 const url_parse::Component& base_scheme, 54 const CHAR* cmp, 55 const url_parse::Component& cmp_scheme) { 56 if (base_scheme.len != cmp_scheme.len) 57 return false; 58 for (int i = 0; i < base_scheme.len; i++) { 59 // We assume the base is already canonical, so we don't have to 60 // canonicalize it. 61 if (CanonicalSchemeChar(cmp[cmp_scheme.begin + i]) != 62 base[base_scheme.begin + i]) 63 return false; 64 } 65 return true; 66} 67 68#ifdef WIN32 69 70// Here, we also allow Windows paths to be represented as "/C:/" so we can be 71// consistent about URL paths beginning with slashes. This function is like 72// DoesBeginWindowsDrivePath except that it also requires a slash at the 73// beginning. 74template<typename CHAR> 75bool DoesBeginSlashWindowsDriveSpec(const CHAR* spec, int start_offset, 76 int spec_len) { 77 if (start_offset >= spec_len) 78 return false; 79 return url_parse::IsURLSlash(spec[start_offset]) && 80 url_parse::DoesBeginWindowsDriveSpec(spec, start_offset + 1, spec_len); 81} 82 83#endif // WIN32 84 85// See IsRelativeURL in the header file for usage. 86template<typename CHAR> 87bool DoIsRelativeURL(const char* base, 88 const url_parse::Parsed& base_parsed, 89 const CHAR* url, 90 int url_len, 91 bool is_base_hierarchical, 92 bool* is_relative, 93 url_parse::Component* relative_component) { 94 *is_relative = false; // So we can default later to not relative. 95 96 // Trim whitespace and construct a new range for the substring. 97 int begin = 0; 98 url_parse::TrimURL(url, &begin, &url_len); 99 if (begin >= url_len) { 100 // Empty URLs are relative, but do nothing. 101 *relative_component = url_parse::Component(begin, 0); 102 *is_relative = true; 103 return true; 104 } 105 106#ifdef WIN32 107 // We special case paths like "C:\foo" so they can link directly to the 108 // file on Windows (IE compatability). The security domain stuff should 109 // prevent a link like this from actually being followed if its on a 110 // web page. 111 // 112 // We treat "C:/foo" as an absolute URL. We can go ahead and treat "/c:/" 113 // as relative, as this will just replace the path when the base scheme 114 // is a file and the answer will still be correct. 115 // 116 // We require strict backslashes when detecting UNC since two forward 117 // shashes should be treated a a relative URL with a hostname. 118 if (url_parse::DoesBeginWindowsDriveSpec(url, begin, url_len) || 119 url_parse::DoesBeginUNCPath(url, begin, url_len, true)) 120 return true; 121#endif // WIN32 122 123 // See if we've got a scheme, if not, we know this is a relative URL. 124 // BUT: Just because we have a scheme, doesn't make it absolute. 125 // "http:foo.html" is a relative URL with path "foo.html". If the scheme is 126 // empty, we treat it as relative (":foo") like IE does. 127 url_parse::Component scheme; 128 if (!url_parse::ExtractScheme(url, url_len, &scheme) || scheme.len == 0) { 129 // Don't allow relative URLs if the base scheme doesn't support it. 130 if (!is_base_hierarchical) 131 return false; 132 133 *relative_component = url_parse::MakeRange(begin, url_len); 134 *is_relative = true; 135 return true; 136 } 137 138 // If the scheme isn't valid, then it's relative. 139 int scheme_end = scheme.end(); 140 for (int i = scheme.begin; i < scheme_end; i++) { 141 if (!CanonicalSchemeChar(url[i])) { 142 *relative_component = url_parse::MakeRange(begin, url_len); 143 *is_relative = true; 144 return true; 145 } 146 } 147 148 // If the scheme is not the same, then we can't count it as relative. 149 if (!AreSchemesEqual(base, base_parsed.scheme, url, scheme)) 150 return true; 151 152 // When the scheme that they both share is not hierarchical, treat the 153 // incoming scheme as absolute (this way with the base of "data:foo", 154 // "data:bar" will be reported as absolute. 155 if (!is_base_hierarchical) 156 return true; 157 158 // ExtractScheme guarantees that the colon immediately follows what it 159 // considers to be the scheme. CountConsecutiveSlashes will handle the 160 // case where the begin offset is the end of the input. 161 int colon_offset = scheme.end(); 162 int num_slashes = url_parse::CountConsecutiveSlashes(url, colon_offset + 1, 163 url_len); 164 165 if (num_slashes == 0 || num_slashes == 1) { 166 // No slashes means it's a relative path like "http:foo.html". One slash 167 // is an absolute path. "http:/home/foo.html" 168 *is_relative = true; 169 *relative_component = url_parse::MakeRange(colon_offset + 1, url_len); 170 return true; 171 } 172 173 // Two or more slashes after the scheme we treat as absolute. 174 return true; 175} 176 177// Copies all characters in the range [begin, end) of |spec| to the output, 178// up until and including the last slash. There should be a slash in the 179// range, if not, nothing will be copied. 180// 181// The input is assumed to be canonical, so we search only for exact slashes 182// and not backslashes as well. We also know that it's ASCII. 183void CopyToLastSlash(const char* spec, 184 int begin, 185 int end, 186 CanonOutput* output) { 187 // Find the last slash. 188 int last_slash = -1; 189 for (int i = end - 1; i >= begin; i--) { 190 if (spec[i] == '/') { 191 last_slash = i; 192 break; 193 } 194 } 195 if (last_slash < 0) 196 return; // No slash. 197 198 // Copy. 199 for (int i = begin; i <= last_slash; i++) 200 output->push_back(spec[i]); 201} 202 203// Copies a single component from the source to the output. This is used 204// when resolving relative URLs and a given component is unchanged. Since the 205// source should already be canonical, we don't have to do anything special, 206// and the input is ASCII. 207void CopyOneComponent(const char* source, 208 const url_parse::Component& source_component, 209 CanonOutput* output, 210 url_parse::Component* output_component) { 211 if (source_component.len < 0) { 212 // This component is not present. 213 *output_component = url_parse::Component(); 214 return; 215 } 216 217 output_component->begin = output->length(); 218 int source_end = source_component.end(); 219 for (int i = source_component.begin; i < source_end; i++) 220 output->push_back(source[i]); 221 output_component->len = output->length() - output_component->begin; 222} 223 224#ifdef WIN32 225 226// Called on Windows when the base URL is a file URL, this will copy the "C:" 227// to the output, if there is a drive letter and if that drive letter is not 228// being overridden by the relative URL. Otherwise, do nothing. 229// 230// It will return the index of the beginning of the next character in the 231// base to be processed: if there is a "C:", the slash after it, or if 232// there is no drive letter, the slash at the beginning of the path, or 233// the end of the base. This can be used as the starting offset for further 234// path processing. 235template<typename CHAR> 236int CopyBaseDriveSpecIfNecessary(const char* base_url, 237 int base_path_begin, 238 int base_path_end, 239 const CHAR* relative_url, 240 int path_start, 241 int relative_url_len, 242 CanonOutput* output) { 243 if (base_path_begin >= base_path_end) 244 return base_path_begin; // No path. 245 246 // If the relative begins with a drive spec, don't do anything. The existing 247 // drive spec in the base will be replaced. 248 if (url_parse::DoesBeginWindowsDriveSpec(relative_url, 249 path_start, relative_url_len)) { 250 return base_path_begin; // Relative URL path is "C:/foo" 251 } 252 253 // The path should begin with a slash (as all canonical paths do). We check 254 // if it is followed by a drive letter and copy it. 255 if (DoesBeginSlashWindowsDriveSpec(base_url, 256 base_path_begin, 257 base_path_end)) { 258 // Copy the two-character drive spec to the output. It will now look like 259 // "file:///C:" so the rest of it can be treated like a standard path. 260 output->push_back('/'); 261 output->push_back(base_url[base_path_begin + 1]); 262 output->push_back(base_url[base_path_begin + 2]); 263 return base_path_begin + 3; 264 } 265 266 return base_path_begin; 267} 268 269#endif // WIN32 270 271// A subroutine of DoResolveRelativeURL, this resolves the URL knowning that 272// the input is a relative path or less (qyuery or ref). 273template<typename CHAR> 274bool DoResolveRelativePath(const char* base_url, 275 const url_parse::Parsed& base_parsed, 276 bool base_is_file, 277 const CHAR* relative_url, 278 const url_parse::Component& relative_component, 279 CharsetConverter* query_converter, 280 CanonOutput* output, 281 url_parse::Parsed* out_parsed) { 282 bool success = true; 283 284 // We know the authority section didn't change, copy it to the output. We 285 // also know we have a path so can copy up to there. 286 url_parse::Component path, query, ref; 287 url_parse::ParsePathInternal(relative_url, 288 relative_component, 289 &path, 290 &query, 291 &ref); 292 // Canonical URLs always have a path, so we can use that offset. 293 output->Append(base_url, base_parsed.path.begin); 294 295 if (path.len > 0) { 296 // The path is replaced or modified. 297 int true_path_begin = output->length(); 298 299 // For file: URLs on Windows, we don't want to treat the drive letter and 300 // colon as part of the path for relative file resolution when the 301 // incoming URL does not provide a drive spec. We save the true path 302 // beginning so we can fix it up after we are done. 303 int base_path_begin = base_parsed.path.begin; 304#ifdef WIN32 305 if (base_is_file) { 306 base_path_begin = CopyBaseDriveSpecIfNecessary( 307 base_url, base_parsed.path.begin, base_parsed.path.end(), 308 relative_url, relative_component.begin, relative_component.end(), 309 output); 310 // Now the output looks like either "file://" or "file:///C:" 311 // and we can start appending the rest of the path. |base_path_begin| 312 // points to the character in the base that comes next. 313 } 314#endif // WIN32 315 316 if (url_parse::IsURLSlash(relative_url[path.begin])) { 317 // Easy case: the path is an absolute path on the server, so we can 318 // just replace everything from the path on with the new versions. 319 // Since the input should be canonical hierarchical URL, we should 320 // always have a path. 321 success &= CanonicalizePath(relative_url, path, 322 output, &out_parsed->path); 323 } else { 324 // Relative path, replace the query, and reference. We take the 325 // original path with the file part stripped, and append the new path. 326 // The canonicalizer will take care of resolving ".." and "." 327 int path_begin = output->length(); 328 CopyToLastSlash(base_url, base_path_begin, base_parsed.path.end(), 329 output); 330 success &= CanonicalizePartialPath(relative_url, path, path_begin, 331 output); 332 out_parsed->path = url_parse::MakeRange(path_begin, output->length()); 333 334 // Copy the rest of the stuff after the path from the relative path. 335 } 336 337 // Finish with the query and reference part (these can't fail). 338 CanonicalizeQuery(relative_url, query, query_converter, 339 output, &out_parsed->query); 340 CanonicalizeRef(relative_url, ref, output, &out_parsed->ref); 341 342 // Fix the path beginning to add back the "C:" we may have written above. 343 out_parsed->path = url_parse::MakeRange(true_path_begin, 344 out_parsed->path.end()); 345 return success; 346 } 347 348 // If we get here, the path is unchanged: copy to output. 349 CopyOneComponent(base_url, base_parsed.path, output, &out_parsed->path); 350 351 if (query.is_valid()) { 352 // Just the query specified, replace the query and reference (ignore 353 // failures for refs) 354 CanonicalizeQuery(relative_url, query, query_converter, 355 output, &out_parsed->query); 356 CanonicalizeRef(relative_url, ref, output, &out_parsed->ref); 357 return success; 358 } 359 360 // If we get here, the query is unchanged: copy to output. Note that the 361 // range of the query parameter doesn't include the question mark, so we 362 // have to add it manually if there is a component. 363 if (base_parsed.query.is_valid()) 364 output->push_back('?'); 365 CopyOneComponent(base_url, base_parsed.query, output, &out_parsed->query); 366 367 if (ref.is_valid()) { 368 // Just the reference specified: replace it (ignoring failures). 369 CanonicalizeRef(relative_url, ref, output, &out_parsed->ref); 370 return success; 371 } 372 373 // We should always have something to do in this function, the caller checks 374 // that some component is being replaced. 375 DCHECK(false) << "Not reached"; 376 return success; 377} 378 379// Resolves a relative URL that contains a host. Typically, these will 380// be of the form "//www.google.com/foo/bar?baz#ref" and the only thing which 381// should be kept from the original URL is the scheme. 382template<typename CHAR> 383bool DoResolveRelativeHost(const char* base_url, 384 const url_parse::Parsed& base_parsed, 385 const CHAR* relative_url, 386 const url_parse::Component& relative_component, 387 CharsetConverter* query_converter, 388 CanonOutput* output, 389 url_parse::Parsed* out_parsed) { 390 // Parse the relative URL, just like we would for anything following a 391 // scheme. 392 url_parse::Parsed relative_parsed; // Everything but the scheme is valid. 393 url_parse::ParseAfterScheme(&relative_url[relative_component.begin], 394 relative_component.len, relative_component.begin, 395 &relative_parsed); 396 397 // Now we can just use the replacement function to replace all the necessary 398 // parts of the old URL with the new one. 399 Replacements<CHAR> replacements; 400 replacements.SetUsername(relative_url, relative_parsed.username); 401 replacements.SetPassword(relative_url, relative_parsed.password); 402 replacements.SetHost(relative_url, relative_parsed.host); 403 replacements.SetPort(relative_url, relative_parsed.port); 404 replacements.SetPath(relative_url, relative_parsed.path); 405 replacements.SetQuery(relative_url, relative_parsed.query); 406 replacements.SetRef(relative_url, relative_parsed.ref); 407 408 return ReplaceStandardURL(base_url, base_parsed, replacements, 409 query_converter, output, out_parsed); 410} 411 412// Resolves a relative URL that happens to be an absolute file path. Examples 413// include: "//hostname/path", "/c:/foo", and "//hostname/c:/foo". 414template<typename CHAR> 415bool DoResolveAbsoluteFile(const CHAR* relative_url, 416 const url_parse::Component& relative_component, 417 CharsetConverter* query_converter, 418 CanonOutput* output, 419 url_parse::Parsed* out_parsed) { 420 // Parse the file URL. The file URl parsing function uses the same logic 421 // as we do for determining if the file is absolute, in which case it will 422 // not bother to look for a scheme. 423 url_parse::Parsed relative_parsed; 424 url_parse::ParseFileURL(&relative_url[relative_component.begin], 425 relative_component.len, &relative_parsed); 426 427 return CanonicalizeFileURL(&relative_url[relative_component.begin], 428 relative_component.len, relative_parsed, 429 query_converter, output, out_parsed); 430} 431 432// TODO(brettw) treat two slashes as root like Mozilla for FTP? 433template<typename CHAR> 434bool DoResolveRelativeURL(const char* base_url, 435 const url_parse::Parsed& base_parsed, 436 bool base_is_file, 437 const CHAR* relative_url, 438 const url_parse::Component& relative_component, 439 CharsetConverter* query_converter, 440 CanonOutput* output, 441 url_parse::Parsed* out_parsed) { 442 // Starting point for our output parsed. We'll fix what we change. 443 *out_parsed = base_parsed; 444 445 // Sanity check: the input should have a host or we'll break badly below. 446 // We can only resolve relative URLs with base URLs that have hosts and 447 // paths (even the default path of "/" is OK). 448 // 449 // We allow hosts with no length so we can handle file URLs, for example. 450 if (base_parsed.path.len <= 0) { 451 // On error, return the input (resolving a relative URL on a non-relative 452 // base = the base). 453 int base_len = base_parsed.Length(); 454 for (int i = 0; i < base_len; i++) 455 output->push_back(base_url[i]); 456 return false; 457 } 458 459 if (relative_component.len <= 0) { 460 // Empty relative URL, leave unchanged, only removing the ref component. 461 int base_len = base_parsed.Length(); 462 base_len -= base_parsed.ref.len + 1; 463 out_parsed->ref.reset(); 464 output->Append(base_url, base_len); 465 return true; 466 } 467 468 int num_slashes = url_parse::CountConsecutiveSlashes( 469 relative_url, relative_component.begin, relative_component.end()); 470 471#ifdef WIN32 472 // On Windows, two slashes for a file path (regardless of which direction 473 // they are) means that it's UNC. Two backslashes on any base scheme mean 474 // that it's an absolute UNC path (we use the base_is_file flag to control 475 // how strict the UNC finder is). 476 // 477 // We also allow Windows absolute drive specs on any scheme (for example 478 // "c:\foo") like IE does. There must be no preceeding slashes in this 479 // case (we reject anything like "/c:/foo") because that should be treated 480 // as a path. For file URLs, we allow any number of slashes since that would 481 // be setting the path. 482 // 483 // This assumes the absolute path resolver handles absolute URLs like this 484 // properly. url_util::DoCanonicalize does this. 485 int after_slashes = relative_component.begin + num_slashes; 486 if (url_parse::DoesBeginUNCPath(relative_url, relative_component.begin, 487 relative_component.end(), !base_is_file) || 488 ((num_slashes == 0 || base_is_file) && 489 url_parse::DoesBeginWindowsDriveSpec(relative_url, after_slashes, 490 relative_component.end()))) { 491 return DoResolveAbsoluteFile(relative_url, relative_component, 492 query_converter, output, out_parsed); 493 } 494#else 495 // Other platforms need explicit handling for file: URLs with multiple 496 // slashes because the generic scheme parsing always extracts a host, but a 497 // file: URL only has a host if it has exactly 2 slashes. This also 498 // handles the special case where the URL is only slashes, since that 499 // doesn't have a host part either. 500 if (base_is_file && 501 (num_slashes > 2 || num_slashes == relative_component.len)) { 502 return DoResolveAbsoluteFile(relative_url, relative_component, 503 query_converter, output, out_parsed); 504 } 505#endif 506 507 // Any other double-slashes mean that this is relative to the scheme. 508 if (num_slashes >= 2) { 509 return DoResolveRelativeHost(base_url, base_parsed, 510 relative_url, relative_component, 511 query_converter, output, out_parsed); 512 } 513 514 // When we get here, we know that the relative URL is on the same host. 515 return DoResolveRelativePath(base_url, base_parsed, base_is_file, 516 relative_url, relative_component, 517 query_converter, output, out_parsed); 518} 519 520} // namespace 521 522bool IsRelativeURL(const char* base, 523 const url_parse::Parsed& base_parsed, 524 const char* fragment, 525 int fragment_len, 526 bool is_base_hierarchical, 527 bool* is_relative, 528 url_parse::Component* relative_component) { 529 return DoIsRelativeURL<char>( 530 base, base_parsed, fragment, fragment_len, is_base_hierarchical, 531 is_relative, relative_component); 532} 533 534bool IsRelativeURL(const char* base, 535 const url_parse::Parsed& base_parsed, 536 const char16* fragment, 537 int fragment_len, 538 bool is_base_hierarchical, 539 bool* is_relative, 540 url_parse::Component* relative_component) { 541 return DoIsRelativeURL<char16>( 542 base, base_parsed, fragment, fragment_len, is_base_hierarchical, 543 is_relative, relative_component); 544} 545 546bool ResolveRelativeURL(const char* base_url, 547 const url_parse::Parsed& base_parsed, 548 bool base_is_file, 549 const char* relative_url, 550 const url_parse::Component& relative_component, 551 CharsetConverter* query_converter, 552 CanonOutput* output, 553 url_parse::Parsed* out_parsed) { 554 return DoResolveRelativeURL<char>( 555 base_url, base_parsed, base_is_file, relative_url, 556 relative_component, query_converter, output, out_parsed); 557} 558 559bool ResolveRelativeURL(const char* base_url, 560 const url_parse::Parsed& base_parsed, 561 bool base_is_file, 562 const char16* relative_url, 563 const url_parse::Component& relative_component, 564 CharsetConverter* query_converter, 565 CanonOutput* output, 566 url_parse::Parsed* out_parsed) { 567 return DoResolveRelativeURL<char16>( 568 base_url, base_parsed, base_is_file, relative_url, 569 relative_component, query_converter, output, out_parsed); 570} 571 572} // namespace url_canon 573