| // Copyright 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/logging.h" | 
 | #include "url/url_file.h" | 
 | #include "url/url_parse.h" | 
 | #include "url/url_parse_internal.h" | 
 |  | 
 | // Interesting IE file:isms... | 
 | // | 
 | //  INPUT                      OUTPUT | 
 | //  =========================  ============================== | 
 | //  file:/foo/bar              file:///foo/bar | 
 | //      The result here seems totally invalid!?!? This isn't UNC. | 
 | // | 
 | //  file:/ | 
 | //  file:// or any other number of slashes | 
 | //      IE6 doesn't do anything at all if you click on this link. No error: | 
 | //      nothing. IE6's history system seems to always color this link, so I'm | 
 | //      guessing that it maps internally to the empty URL. | 
 | // | 
 | //  C:\                        file:///C:/ | 
 | //      When on a file: URL source page, this link will work. When over HTTP, | 
 | //      the file: URL will appear in the status bar but the link will not work | 
 | //      (security restriction for all file URLs). | 
 | // | 
 | //  file:foo/                  file:foo/     (invalid?!?!?) | 
 | //  file:/foo/                 file:///foo/  (invalid?!?!?) | 
 | //  file://foo/                file://foo/   (UNC to server "foo") | 
 | //  file:///foo/               file:///foo/  (invalid, seems to be a file) | 
 | //  file:////foo/              file://foo/   (UNC to server "foo") | 
 | //      Any more than four slashes is also treated as UNC. | 
 | // | 
 | //  file:C:/                   file://C:/ | 
 | //  file:/C:/                  file://C:/ | 
 | //      The number of slashes after "file:" don't matter if the thing following | 
 | //      it looks like an absolute drive path. Also, slashes and backslashes are | 
 | //      equally valid here. | 
 |  | 
 | namespace url { | 
 |  | 
 | namespace { | 
 |  | 
 | // A subcomponent of DoInitFileURL, the input of this function should be a UNC | 
 | // path name, with the index of the first character after the slashes following | 
 | // the scheme given in |after_slashes|. This will initialize the host, path, | 
 | // query, and ref, and leave the other output components untouched | 
 | // (DoInitFileURL handles these for us). | 
 | template<typename CHAR> | 
 | void DoParseUNC(const CHAR* spec, | 
 |                 int after_slashes, | 
 |                 int spec_len, | 
 |                Parsed* parsed) { | 
 |   int next_slash = FindNextSlash(spec, after_slashes, spec_len); | 
 |   if (next_slash == spec_len) { | 
 |     // No additional slash found, as in "file://foo", treat the text as the | 
 |     // host with no path (this will end up being UNC to server "foo"). | 
 |     int host_len = spec_len - after_slashes; | 
 |     if (host_len) | 
 |       parsed->host = Component(after_slashes, host_len); | 
 |     else | 
 |       parsed->host.reset(); | 
 |     parsed->path.reset(); | 
 |     return; | 
 |   } | 
 |  | 
 | #ifdef WIN32 | 
 |   // See if we have something that looks like a path following the first | 
 |   // component. As in "file://localhost/c:/", we get "c:/" out. We want to | 
 |   // treat this as a having no host but the path given. Works on Windows only. | 
 |   if (DoesBeginWindowsDriveSpec(spec, next_slash + 1, spec_len)) { | 
 |     parsed->host.reset(); | 
 |     ParsePathInternal(spec, MakeRange(next_slash, spec_len), | 
 |                       &parsed->path, &parsed->query, &parsed->ref); | 
 |     return; | 
 |   } | 
 | #endif | 
 |  | 
 |   // Otherwise, everything up until that first slash we found is the host name, | 
 |   // which will end up being the UNC host. For example "file://foo/bar.txt" | 
 |   // will get a server name of "foo" and a path of "/bar". Later, on Windows, | 
 |   // this should be treated as the filename "\\foo\bar.txt" in proper UNC | 
 |   // notation. | 
 |   int host_len = next_slash - after_slashes; | 
 |   if (host_len) | 
 |     parsed->host = MakeRange(after_slashes, next_slash); | 
 |   else | 
 |     parsed->host.reset(); | 
 |   if (next_slash < spec_len) { | 
 |     ParsePathInternal(spec, MakeRange(next_slash, spec_len), | 
 |                       &parsed->path, &parsed->query, &parsed->ref); | 
 |   } else { | 
 |     parsed->path.reset(); | 
 |   } | 
 | } | 
 |  | 
 | // A subcomponent of DoParseFileURL, the input should be a local file, with the | 
 | // beginning of the path indicated by the index in |path_begin|. This will | 
 | // initialize the host, path, query, and ref, and leave the other output | 
 | // components untouched (DoInitFileURL handles these for us). | 
 | template<typename CHAR> | 
 | void DoParseLocalFile(const CHAR* spec, | 
 |                       int path_begin, | 
 |                       int spec_len, | 
 |                       Parsed* parsed) { | 
 |   parsed->host.reset(); | 
 |   ParsePathInternal(spec, MakeRange(path_begin, spec_len), | 
 |                     &parsed->path, &parsed->query, &parsed->ref); | 
 | } | 
 |  | 
 | // Backend for the external functions that operates on either char type. | 
 | // Handles cases where there is a scheme, but also when handed the first | 
 | // character following the "file:" at the beginning of the spec. If so, | 
 | // this is usually a slash, but needn't be; we allow paths like "file:c:\foo". | 
 | template<typename CHAR> | 
 | void DoParseFileURL(const CHAR* spec, int spec_len, Parsed* parsed) { | 
 |   DCHECK(spec_len >= 0); | 
 |  | 
 |   // Get the parts we never use for file URLs out of the way. | 
 |   parsed->username.reset(); | 
 |   parsed->password.reset(); | 
 |   parsed->port.reset(); | 
 |  | 
 |   // Many of the code paths don't set these, so it's convenient to just clear | 
 |   // them. We'll write them in those cases we need them. | 
 |   parsed->query.reset(); | 
 |   parsed->ref.reset(); | 
 |  | 
 |   // Strip leading & trailing spaces and control characters. | 
 |   int begin = 0; | 
 |   TrimURL(spec, &begin, &spec_len); | 
 |  | 
 |   // Find the scheme, if any. | 
 |   int num_slashes = CountConsecutiveSlashes(spec, begin, spec_len); | 
 |   int after_scheme; | 
 |   int after_slashes; | 
 | #ifdef WIN32 | 
 |   // See how many slashes there are. We want to handle cases like UNC but also | 
 |   // "/c:/foo". This is when there is no scheme, so we can allow pages to do | 
 |   // links like "c:/foo/bar" or "//foo/bar". This is also called by the | 
 |   // relative URL resolver when it determines there is an absolute URL, which | 
 |   // may give us input like "/c:/foo". | 
 |   after_slashes = begin + num_slashes; | 
 |   if (DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len)) { | 
 |     // Windows path, don't try to extract the scheme (for example, "c:\foo"). | 
 |     parsed->scheme.reset(); | 
 |     after_scheme = after_slashes; | 
 |   } else if (DoesBeginUNCPath(spec, begin, spec_len, false)) { | 
 |     // Windows UNC path: don't try to extract the scheme, but keep the slashes. | 
 |     parsed->scheme.reset(); | 
 |     after_scheme = begin; | 
 |   } else | 
 | #endif | 
 |   { | 
 |     // ExtractScheme doesn't understand the possibility of filenames with | 
 |     // colons in them, in which case it returns the entire spec up to the | 
 |     // colon as the scheme. So handle /foo.c:5 as a file but foo.c:5 as | 
 |     // the foo.c: scheme. | 
 |     if (!num_slashes && | 
 |         ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { | 
 |       // Offset the results since we gave ExtractScheme a substring. | 
 |       parsed->scheme.begin += begin; | 
 |       after_scheme = parsed->scheme.end() + 1; | 
 |     } else { | 
 |       // No scheme found, remember that. | 
 |       parsed->scheme.reset(); | 
 |       after_scheme = begin; | 
 |     } | 
 |   } | 
 |  | 
 |   // Handle empty specs ones that contain only whitespace or control chars, | 
 |   // or that are just the scheme (for example "file:"). | 
 |   if (after_scheme == spec_len) { | 
 |     parsed->host.reset(); | 
 |     parsed->path.reset(); | 
 |     return; | 
 |   } | 
 |  | 
 |   num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len); | 
 |   after_slashes = after_scheme + num_slashes; | 
 | #ifdef WIN32 | 
 |   // Check whether the input is a drive again. We checked above for windows | 
 |   // drive specs, but that's only at the very beginning to see if we have a | 
 |   // scheme at all. This test will be duplicated in that case, but will | 
 |   // additionally handle all cases with a real scheme such as "file:///C:/". | 
 |   if (!DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len) && | 
 |       num_slashes != 3) { | 
 |     // Anything not beginning with a drive spec ("c:\") on Windows is treated | 
 |     // as UNC, with the exception of three slashes which always means a file. | 
 |     // Even IE7 treats file:///foo/bar as "/foo/bar", which then fails. | 
 |     DoParseUNC(spec, after_slashes, spec_len, parsed); | 
 |     return; | 
 |   } | 
 | #else | 
 |   // file: URL with exactly 2 slashes is considered to have a host component. | 
 |   if (num_slashes == 2) { | 
 |     DoParseUNC(spec, after_slashes, spec_len, parsed); | 
 |     return; | 
 |   } | 
 | #endif  // WIN32 | 
 |  | 
 |   // Easy and common case, the full path immediately follows the scheme | 
 |   // (modulo slashes), as in "file://c:/foo". Just treat everything from | 
 |   // there to the end as the path. Empty hosts have 0 length instead of -1. | 
 |   // We include the last slash as part of the path if there is one. | 
 |   DoParseLocalFile(spec, | 
 |       num_slashes > 0 ? after_scheme + num_slashes - 1 : after_scheme, | 
 |       spec_len, parsed); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void ParseFileURL(const char* url, int url_len, Parsed* parsed) { | 
 |   DoParseFileURL(url, url_len, parsed); | 
 | } | 
 |  | 
 | void ParseFileURL(const base::char16* url, int url_len, Parsed* parsed) { | 
 |   DoParseFileURL(url, url_len, parsed); | 
 | } | 
 |  | 
 | }  // namespace url |