Avi Drissman | 3e1a26c | 2022-09-15 20:26:03 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors |
tfarina@chromium.org | 51bcc5d | 2013-04-24 01:41:37 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 4 | |
| 5 | // Functions for canonicalizing "file:" URLs. |
| 6 | |
David Benjamin | bf75caf | 2023-09-29 04:39:59 | [diff] [blame] | 7 | #include <string_view> |
| 8 | |
zhongyi | 2396034 | 2016-04-12 23:13:20 | [diff] [blame] | 9 | #include "base/strings/string_util.h" |
tfarina@chromium.org | 318076b | 2013-04-18 21:19:45 | [diff] [blame] | 10 | #include "url/url_canon.h" |
| 11 | #include "url/url_canon_internal.h" |
| 12 | #include "url/url_file.h" |
| 13 | #include "url/url_parse_internal.h" |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 14 | |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 15 | namespace url { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 16 | |
| 17 | namespace { |
| 18 | |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 19 | bool IsLocalhost(const char* spec, int begin, int end) { |
| 20 | if (begin > end) |
| 21 | return false; |
David Benjamin | bf75caf | 2023-09-29 04:39:59 | [diff] [blame] | 22 | return std::string_view(&spec[begin], end - begin) == "localhost"; |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 23 | } |
| 24 | |
| 25 | bool IsLocalhost(const char16_t* spec, int begin, int end) { |
| 26 | if (begin > end) |
| 27 | return false; |
David Benjamin | bf75caf | 2023-09-29 04:39:59 | [diff] [blame] | 28 | return std::u16string_view(&spec[begin], end - begin) == u"localhost"; |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 29 | } |
| 30 | |
| 31 | template <typename CHAR> |
| 32 | int DoFindWindowsDriveLetter(const CHAR* spec, int begin, int end) { |
| 33 | if (begin > end) |
| 34 | return -1; |
| 35 | |
| 36 | // First guess the beginning of the drive letter. |
| 37 | // If there is something that looks like a drive letter in the spec between |
| 38 | // begin and end, store its position in drive_letter_pos. |
| 39 | int drive_letter_pos = |
| 40 | DoesContainWindowsDriveSpecUntil(spec, begin, end, end); |
| 41 | if (drive_letter_pos < begin) |
| 42 | return -1; |
| 43 | |
| 44 | // Check if the path up to the drive letter candidate can be canonicalized as |
| 45 | // "/". |
| 46 | Component sub_path = MakeRange(begin, drive_letter_pos); |
| 47 | RawCanonOutput<1024> output; |
| 48 | Component output_path; |
| 49 | bool success = CanonicalizePath(spec, sub_path, &output, &output_path); |
| 50 | if (!success || output_path.len != 1 || output.at(output_path.begin) != '/') { |
| 51 | return -1; |
| 52 | } |
| 53 | |
| 54 | return drive_letter_pos; |
| 55 | } |
| 56 | |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 57 | #ifdef WIN32 |
| 58 | |
| 59 | // Given a pointer into the spec, this copies and canonicalizes the drive |
| 60 | // letter and colon to the output, if one is found. If there is not a drive |
| 61 | // spec, it won't do anything. The index of the next character in the input |
| 62 | // spec is returned (after the colon when a drive spec is found, the begin |
| 63 | // offset if one is not). |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 64 | template <typename CHAR> |
| 65 | int FileDoDriveSpec(const CHAR* spec, int begin, int end, CanonOutput* output) { |
| 66 | int drive_letter_pos = FindWindowsDriveLetter(spec, begin, end); |
Vaclav Brozek | 14aed3f | 2021-05-06 04:40:26 | [diff] [blame] | 67 | if (drive_letter_pos < begin) |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 68 | return begin; |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 69 | |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 70 | // By now, a valid drive letter is confirmed at position drive_letter_pos, |
| 71 | // followed by a valid drive letter separator (a colon or a pipe). |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 72 | |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 73 | output->push_back('/'); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 74 | |
Vaclav Brozek | 14aed3f | 2021-05-06 04:40:26 | [diff] [blame] | 75 | // Normalize Windows drive letters to uppercase. |
| 76 | if (base::IsAsciiLower(spec[drive_letter_pos])) |
| 77 | output->push_back(static_cast<char>(spec[drive_letter_pos] - 'a' + 'A')); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 78 | else |
Vaclav Brozek | 14aed3f | 2021-05-06 04:40:26 | [diff] [blame] | 79 | output->push_back(static_cast<char>(spec[drive_letter_pos])); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 80 | |
| 81 | // Normalize the character following it to a colon rather than pipe. |
| 82 | output->push_back(':'); |
Vaclav Brozek | 14aed3f | 2021-05-06 04:40:26 | [diff] [blame] | 83 | return drive_letter_pos + 2; |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 84 | } |
| 85 | |
| 86 | #endif // WIN32 |
| 87 | |
| 88 | template<typename CHAR, typename UCHAR> |
| 89 | bool DoFileCanonicalizePath(const CHAR* spec, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 90 | const Component& path, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 91 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 92 | Component* out_path) { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 93 | // Copies and normalizes the "c:" at the beginning, if present. |
| 94 | out_path->begin = output->length(); |
| 95 | int after_drive; |
| 96 | #ifdef WIN32 |
| 97 | after_drive = FileDoDriveSpec(spec, path.begin, path.end(), output); |
| 98 | #else |
| 99 | after_drive = path.begin; |
| 100 | #endif |
| 101 | |
| 102 | // Copies the rest of the path, starting from the slash following the |
| 103 | // drive colon (if any, Windows only), or the first slash of the path. |
| 104 | bool success = true; |
| 105 | if (after_drive < path.end()) { |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 106 | // Use the regular path canonicalizer to canonicalize the rest of the path |
| 107 | // after the drive. |
| 108 | // |
| 109 | // Give it a fake output component to write into, since we will be |
| 110 | // calculating the out_path ourselves (consisting of both the drive and the |
| 111 | // path we canonicalize here). |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 112 | Component sub_path = MakeRange(after_drive, path.end()); |
| 113 | Component fake_output_path; |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 114 | success = CanonicalizePath(spec, sub_path, output, &fake_output_path); |
Vaclav Brozek | 2dbbfbc | 2020-05-12 12:51:29 | [diff] [blame] | 115 | } else if (after_drive == path.begin) { |
| 116 | // No input path and no drive spec, canonicalize to a slash. |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 117 | output->push_back('/'); |
| 118 | } |
| 119 | |
| 120 | out_path->len = output->length() - out_path->begin; |
| 121 | return success; |
| 122 | } |
| 123 | |
| 124 | template<typename CHAR, typename UCHAR> |
| 125 | bool DoCanonicalizeFileURL(const URLComponentSource<CHAR>& source, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 126 | const Parsed& parsed, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 127 | CharsetConverter* query_converter, |
| 128 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 129 | Parsed* new_parsed) { |
Hayato Ito | 735a5cbc | 2023-12-13 02:49:38 | [diff] [blame] | 130 | DCHECK(!parsed.has_opaque_path); |
| 131 | |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 132 | // Things we don't set in file: URLs. |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 133 | new_parsed->username = Component(); |
| 134 | new_parsed->password = Component(); |
| 135 | new_parsed->port = Component(); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 136 | |
| 137 | // Scheme (known, so we don't bother running it through the more |
| 138 | // complicated scheme canonicalizer). |
| 139 | new_parsed->scheme.begin = output->length(); |
David Benjamin | cc4d2b2 | 2023-10-02 23:12:04 | [diff] [blame] | 140 | output->Append("file://"); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 141 | new_parsed->scheme.len = 4; |
| 142 | |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 143 | // If the host is localhost, and the path starts with a Windows drive letter, |
| 144 | // remove the host component. This does the following transformation: |
| 145 | // file://localhost/C:/hello.txt -> file:///C:/hello.txt |
| 146 | // |
| 147 | // Note: we do this on every platform per URL Standard, not just Windows. |
| 148 | // |
| 149 | // TODO(https://crbug.com/688961): According to the latest URL spec, this |
| 150 | // transformation should be done regardless of the path. |
| 151 | Component host_range = parsed.host; |
| 152 | if (IsLocalhost(source.host, host_range.begin, host_range.end()) && |
| 153 | FindWindowsDriveLetter(source.path, parsed.path.begin, |
| 154 | parsed.path.end()) >= parsed.path.begin) { |
| 155 | host_range.reset(); |
| 156 | } |
| 157 | |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 158 | // Append the host. For many file URLs, this will be empty. For UNC, this |
| 159 | // will be present. |
| 160 | // TODO(brettw) This doesn't do any checking for host name validity. We |
| 161 | // should probably handle validity checking of UNC hosts differently than |
| 162 | // for regular IP hosts. |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 163 | bool success = |
| 164 | CanonicalizeHost(source.host, host_range, output, &new_parsed->host); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 165 | success &= DoFileCanonicalizePath<CHAR, UCHAR>(source.path, parsed.path, |
| 166 | output, &new_parsed->path); |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 167 | |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 168 | CanonicalizeQuery(source.query, parsed.query, query_converter, |
| 169 | output, &new_parsed->query); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 170 | CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref); |
| 171 | |
| 172 | return success; |
| 173 | } |
| 174 | |
| 175 | } // namespace |
| 176 | |
Timothy Gu | 825e85c | 2021-09-21 05:28:48 | [diff] [blame] | 177 | int FindWindowsDriveLetter(const char* spec, int begin, int end) { |
| 178 | return DoFindWindowsDriveLetter(spec, begin, end); |
| 179 | } |
| 180 | |
| 181 | int FindWindowsDriveLetter(const char16_t* spec, int begin, int end) { |
| 182 | return DoFindWindowsDriveLetter(spec, begin, end); |
| 183 | } |
| 184 | |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 185 | bool CanonicalizeFileURL(const char* spec, |
| 186 | int spec_len, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 187 | const Parsed& parsed, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 188 | CharsetConverter* query_converter, |
| 189 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 190 | Parsed* new_parsed) { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 191 | return DoCanonicalizeFileURL<char, unsigned char>( |
| 192 | URLComponentSource<char>(spec), parsed, query_converter, |
| 193 | output, new_parsed); |
| 194 | } |
| 195 | |
Jan Wilken Dörrie | 5aad5c2 | 2021-03-08 21:44:12 | [diff] [blame] | 196 | bool CanonicalizeFileURL(const char16_t* spec, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 197 | int spec_len, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 198 | const Parsed& parsed, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 199 | CharsetConverter* query_converter, |
| 200 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 201 | Parsed* new_parsed) { |
Jan Wilken Dörrie | 5aad5c2 | 2021-03-08 21:44:12 | [diff] [blame] | 202 | return DoCanonicalizeFileURL<char16_t, char16_t>( |
| 203 | URLComponentSource<char16_t>(spec), parsed, query_converter, output, |
| 204 | new_parsed); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 205 | } |
| 206 | |
| 207 | bool FileCanonicalizePath(const char* spec, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 208 | const Component& path, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 209 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 210 | Component* out_path) { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 211 | return DoFileCanonicalizePath<char, unsigned char>(spec, path, |
| 212 | output, out_path); |
| 213 | } |
| 214 | |
Jan Wilken Dörrie | 5aad5c2 | 2021-03-08 21:44:12 | [diff] [blame] | 215 | bool FileCanonicalizePath(const char16_t* spec, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 216 | const Component& path, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 217 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 218 | Component* out_path) { |
Jan Wilken Dörrie | 5aad5c2 | 2021-03-08 21:44:12 | [diff] [blame] | 219 | return DoFileCanonicalizePath<char16_t, char16_t>(spec, path, output, |
| 220 | out_path); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | bool ReplaceFileURL(const char* base, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 224 | const Parsed& base_parsed, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 225 | const Replacements<char>& replacements, |
| 226 | CharsetConverter* query_converter, |
| 227 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 228 | Parsed* new_parsed) { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 229 | URLComponentSource<char> source(base); |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 230 | Parsed parsed(base_parsed); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 231 | SetupOverrideComponents(base, replacements, &source, &parsed); |
| 232 | return DoCanonicalizeFileURL<char, unsigned char>( |
| 233 | source, parsed, query_converter, output, new_parsed); |
| 234 | } |
| 235 | |
| 236 | bool ReplaceFileURL(const char* base, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 237 | const Parsed& base_parsed, |
Jan Wilken Dörrie | 5aad5c2 | 2021-03-08 21:44:12 | [diff] [blame] | 238 | const Replacements<char16_t>& replacements, |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 239 | CharsetConverter* query_converter, |
| 240 | CanonOutput* output, |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 241 | Parsed* new_parsed) { |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 242 | RawCanonOutput<1024> utf8; |
| 243 | URLComponentSource<char> source(base); |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 244 | Parsed parsed(base_parsed); |
brettw@chromium.org | e7bba5f | 2013-04-10 20:10:52 | [diff] [blame] | 245 | SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed); |
| 246 | return DoCanonicalizeFileURL<char, unsigned char>( |
| 247 | source, parsed, query_converter, output, new_parsed); |
| 248 | } |
| 249 | |
vitalybuka@chromium.org | 0318f92 | 2014-04-22 00:09:23 | [diff] [blame] | 250 | } // namespace url |