[CSS Exclusions] ExclusionShape API should use logical coordinates for input/output
[WebKit-https.git] / Source / WTF / wtf / url / src / URLParser.h
1 /* Based on nsURLParsers.cc from Mozilla
2  * -------------------------------------
3  * Copyright (C) 1998 Netscape Communications Corporation.
4  * Copyright (C) 2012 Apple Inc. All rights reserved.
5  *
6  * Other contributors:
7  *   Darin Fisher (original author)
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  * Alternatively, the contents of this file may be used under the terms
24  * of either the Mozilla Public License Version 1.1, found at
25  * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
26  * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
27  * (the "GPL"), in which case the provisions of the MPL or the GPL are
28  * applicable instead of those above.  If you wish to allow use of your
29  * version of this file only under the terms of one of those two
30  * licenses (the MPL or the GPL) and not to allow others to use your
31  * version of this file under the LGPL, indicate your decision by
32  * deletingthe provisions above and replace them with the notice and
33  * other provisions required by the MPL or the GPL, as the case may be.
34  * If you do not delete the provisions above, a recipient may use your
35  * version of this file under any of the LGPL, the MPL or the GPL.
36  */
37
38 #ifndef URLParser_h
39 #define URLParser_h
40
41 #include "URLComponent.h"
42 #include "URLSegments.h"
43 #include "UnusedParam.h"
44
45 #if USE(WTFURL)
46
47 namespace WTF {
48
49 template<typename CharacterType, typename BaseCharacterType = CharacterType>
50 class URLParser {
51 public:
52     enum SpecialPort {
53         UnspecifiedPort = -1,
54         InvalidPort = -2,
55     };
56
57     // This handles everything that may be an authority terminator, including
58     // backslash. For special backslash handling see parseAfterScheme.
59     static bool isPossibleAuthorityTerminator(CharacterType ch)
60     {
61         return isURLSlash(ch) || ch == '?' || ch == '#' || ch == ';';
62     }
63
64     // Given an already-identified auth section, breaks it into its constituent
65     // parts. The port number will be parsed and the resulting integer will be
66     // filled into the given *port variable, or -1 if there is no port number
67     // or it is invalid.
68     static void parseAuthority(const CharacterType* spec, const URLComponent& auth, URLComponent& username, URLComponent& password, URLComponent& host, URLComponent& port)
69     {
70         // FIXME: add ASSERT(auth.isValid()); // We should always get an authority.
71         if (!auth.length()) {
72             username.reset();
73             password.reset();
74             host.reset();
75             port.reset();
76             return;
77         }
78
79         // Search backwards for @, which is the separator between the user info
80         // and the server info. RFC 3986 forbids @ from occuring in auth, but
81         // someone might include it in a password unescaped.
82         int i = auth.begin() + auth.length() - 1;
83         while (i > auth.begin() && spec[i] != '@')
84             --i;
85
86         if (spec[i] == '@') {
87             // Found user info: <user-info>@<server-info>
88             parseUserInfo(spec, URLComponent(auth.begin(), i - auth.begin()), username, password);
89             parseServerInfo(spec, URLComponent::fromRange(i + 1, auth.begin() + auth.length()), host, port);
90         } else {
91             // No user info, everything is server info.
92             username.reset();
93             password.reset();
94             parseServerInfo(spec, auth, host, port);
95         }
96     }
97
98     static bool extractScheme(const CharacterType* spec, int specLength, URLComponent& scheme)
99     {
100         // Skip leading whitespace and control characters.
101         int begin = 0;
102         while (begin < specLength && shouldTrimFromURL(spec[begin]))
103             begin++;
104         if (begin == specLength)
105             return false; // Input is empty or all whitespace.
106
107         // Find the first colon character.
108         for (int i = begin; i < specLength; i++) {
109             if (spec[i] == ':') {
110                 scheme = URLComponent::fromRange(begin, i);
111                 return true;
112             }
113         }
114         return false; // No colon found: no scheme
115     }
116
117     // Fills in all members of the URLSegments structure (except for the
118     // scheme) for standard URLs.
119     //
120     // |spec| is the full spec being parsed, of length |specLength|.
121     // |afterScheme| is the character immediately following the scheme (after
122     // the colon) where we'll begin parsing.
123     static void parseAfterScheme(const CharacterType* spec, int specLength, int afterScheme, URLSegments& parsed)
124     {
125         int numberOfSlashes = consecutiveSlashes(spec, afterScheme, specLength);
126         int afterSlashes = afterScheme + numberOfSlashes;
127
128         // First split into two main parts, the authority (username, password,
129         // host, and port) and the full path (path, query, and reference).
130         URLComponent authority;
131         URLComponent fullPath;
132
133         // Found "//<some data>", looks like an authority section. Treat
134         // everything from there to the next slash (or end of spec) to be the
135         // authority. Note that we ignore the number of slashes and treat it as
136         // the authority.
137         int authEnd = nextAuthorityTerminator(spec, afterSlashes, specLength);
138         authority = URLComponent(afterSlashes, authEnd - afterSlashes);
139
140         if (authEnd == specLength) // No beginning of path found.
141             fullPath = URLComponent();
142         else // Everything starting from the slash to the end is the path.
143             fullPath = URLComponent(authEnd, specLength - authEnd);
144
145         // Now parse those two sub-parts.
146         parseAuthority(spec, authority, parsed.username, parsed.password, parsed.host, parsed.port);
147         parsePath(spec, fullPath, parsed.path, parsed.query, parsed.fragment);
148     }
149
150     // The main parsing function for standard URLs. Standard URLs have a scheme,
151     // host, path, etc.
152     static void parseStandardURL(const CharacterType* spec, int specLength, URLSegments& parsed)
153     {
154         // FIXME: add ASSERT(specLength >= 0);
155
156         // Strip leading & trailing spaces and control characters.
157         int begin = 0;
158         trimURL(spec, begin, specLength);
159
160         int afterScheme;
161         if (extractScheme(spec, specLength, parsed.scheme))
162             afterScheme = parsed.scheme.end() + 1; // Skip past the colon.
163         else {
164             // Say there's no scheme when there is a colon. We could also say
165             // that everything is the scheme. Both would produce an invalid
166             // URL, but this way seems less wrong in more cases.
167             parsed.scheme.reset();
168             afterScheme = begin;
169         }
170         parseAfterScheme(spec, specLength, afterScheme, parsed);
171     }
172
173     // The main parsing function for (may be) relative URLs.
174     static void parseURLWithBase(const CharacterType* spec, int specLength,
175                                  const BaseCharacterType* baseStringSpec, int baseStringSpecLength, const URLSegments& baseStringSegments,
176                                  URLBuffer<char>&outputBuffer, URLSegments& parsed)
177     {
178         UNUSED_PARAM(baseStringSpec);
179         UNUSED_PARAM(baseStringSpecLength);
180         UNUSED_PARAM(baseStringSegments);
181         UNUSED_PARAM(spec);
182         UNUSED_PARAM(specLength);
183         UNUSED_PARAM(outputBuffer);
184         UNUSED_PARAM(parsed);
185         // FIXME: To implement.
186     }
187
188     static void parsePath(const CharacterType* spec, const URLComponent& path, URLComponent& filepath, URLComponent& query, URLComponent& fragment)
189     {
190         // path = [/]<segment1>/<segment2>/<...>/<segmentN>;<param>?<query>#<fragment>
191
192         // Special case when there is no path.
193         if (!path.isValid()) {
194             filepath.reset();
195             query.reset();
196             fragment.reset();
197             return;
198         }
199         // FIXME: add ASSERT(path.length() > 0); // We should never have 0 length paths.
200
201         // Search for first occurrence of either ? or #.
202         int pathEnd = path.begin() + path.length();
203
204         int querySeparator = -1; // Index of the '?'
205         int refSeparator = -1; // Index of the '#'
206         for (int i = path.begin(); i < pathEnd; i++) {
207             switch (spec[i]) {
208             case '?':
209                 if (querySeparator < 0)
210                     querySeparator = i;
211                 break;
212             case '#':
213                 refSeparator = i;
214                 i = pathEnd; // Break out of the loop.
215                 break;
216             default:
217                 break;
218             }
219         }
220
221         // Markers pointing to the character after each of these corresponding
222         // components. The code below works from the end back to the beginning,
223         // and will update these indices as it finds components that exist.
224         int fileEnd, queryEnd;
225
226         // Fragment: from the # to the end of the path.
227         if (refSeparator >= 0) {
228             fileEnd = refSeparator;
229             queryEnd = refSeparator;
230             fragment = URLComponent::fromRange(refSeparator + 1, pathEnd);
231         } else {
232             fileEnd = pathEnd;
233             queryEnd = pathEnd;
234             fragment.reset();
235         }
236
237         // Query fragment: everything from the ? to the next boundary (either
238         // the end of the path or the fragment fragment).
239         if (querySeparator >= 0) {
240             fileEnd = querySeparator;
241             query = URLComponent::fromRange(querySeparator + 1, queryEnd);
242         } else
243             query.reset();
244
245         // File path: treat an empty file path as no file path.
246         if (fileEnd != path.begin())
247             filepath = URLComponent::fromRange(path.begin(), fileEnd);
248         else
249             filepath.reset();
250     }
251
252     // Initializes a path URL which is merely a scheme followed by a path.
253     // Examples include "about:foo" and "javascript:alert('bar');"
254     static void parsePathURL(const CharacterType* spec, int specLength, URLSegments& parsed)
255     {
256         // Get the non-path and non-scheme parts of the URL out of the way, we
257         // never use them.
258         parsed.username.reset();
259         parsed.password.reset();
260         parsed.host.reset();
261         parsed.port.reset();
262         parsed.query.reset();
263         parsed.fragment.reset();
264
265         // Strip leading & trailing spaces and control characters.
266         // FIXME: Perhaps this is unnecessary?
267         int begin = 0;
268         trimURL(spec, begin, specLength);
269
270         // Handle empty specs or ones that contain only whitespace or control
271         // chars.
272         if (begin == specLength) {
273             parsed.scheme.reset();
274             parsed.path.reset();
275             return;
276         }
277
278         // Extract the scheme, with the path being everything following. We also
279         // handle the case where there is no scheme.
280         if (extractScheme(&spec[begin], specLength - begin, parsed.scheme)) {
281             // Offset the results since we gave extractScheme a substring.
282             parsed.scheme.setBegin(parsed.scheme.begin() + begin);
283
284             // For compatibility with the standard URL parser, we treat no path
285             // as -1, rather than having a length of 0 (we normally wouldn't
286             // care so much for these non-standard URLs).
287             if (parsed.scheme.end() == specLength - 1)
288                 parsed.path.reset();
289             else
290                 parsed.path = URLComponent::fromRange(parsed.scheme.end() + 1, specLength);
291         } else {
292             // No scheme found, just path.
293             parsed.scheme.reset();
294             parsed.path = URLComponent::fromRange(begin, specLength);
295         }
296     }
297
298     static void parseMailtoURL(const CharacterType* spec, int specLength, URLSegments& parsed)
299     {
300         // FIXME: add ASSERT(specLength >= 0);
301
302         // Get the non-path and non-scheme parts of the URL out of the way, we
303         // never use them.
304         parsed.username.reset();
305         parsed.password.reset();
306         parsed.host.reset();
307         parsed.port.reset();
308         parsed.fragment.reset();
309         parsed.query.reset(); // May use this; reset for convenience.
310
311         // Strip leading & trailing spaces and control characters.
312         int begin = 0;
313         trimURL(spec, begin, specLength);
314
315         // Handle empty specs or ones that contain only whitespace or control
316         // chars.
317         if (begin == specLength) {
318             parsed.scheme.reset();
319             parsed.path.reset();
320             return;
321         }
322
323         int pathBegin = -1;
324         int pathEnd = -1;
325
326         // Extract the scheme, with the path being everything following. We also
327         // handle the case where there is no scheme.
328         if (extractScheme(&spec[begin], specLength - begin, parsed.scheme)) {
329             // Offset the results since we gave extractScheme a substring.
330             parsed.scheme.setBegin(parsed.scheme.begin() + begin);
331
332             if (parsed.scheme.end() != specLength - 1) {
333                 pathBegin = parsed.scheme.end() + 1;
334                 pathEnd = specLength;
335             }
336         } else {
337             // No scheme found, just path.
338             parsed.scheme.reset();
339             pathBegin = begin;
340             pathEnd = specLength;
341         }
342
343         // Split [pathBegin, pathEnd) into a path + query.
344         for (int i = pathBegin; i < pathEnd; ++i) {
345             if (spec[i] == '?') {
346                 parsed.query = URLComponent::fromRange(i + 1, pathEnd);
347                 pathEnd = i;
348                 break;
349             }
350         }
351
352         // For compatibility with the standard URL parser, treat no path as
353         // -1, rather than having a length of 0
354         if (pathBegin == pathEnd)
355             parsed.path.reset();
356         else
357             parsed.path = URLComponent::fromRange(pathBegin, pathEnd);
358     }
359
360     static int parsePort(const CharacterType* spec, const URLComponent& component)
361     {
362         // Easy success case when there is no port.
363         const int maxDigits = 5;
364         if (component.isEmptyOrInvalid())
365             return UnspecifiedPort;
366
367         URLComponent nonZeroDigits(component.end(), 0);
368         for (int i = 0; i < component.length(); ++i) {
369             if (spec[component.begin() + i] != '0') {
370                 nonZeroDigits = URLComponent::fromRange(component.begin() + i, component.end());
371                 break;
372             }
373         }
374         if (!nonZeroDigits.length())
375             return 0; // All digits were 0.
376
377         if (nonZeroDigits.length() > maxDigits)
378             return InvalidPort;
379
380         int port = 0;
381         for (int i = 0; i < nonZeroDigits.length(); ++i) {
382             CharacterType ch = spec[nonZeroDigits.begin() + i];
383             if (!isPortDigit(ch))
384                 return InvalidPort;
385             port *= 10;
386             port += static_cast<char>(ch) - '0';
387         }
388         if (port > 65535)
389             return InvalidPort;
390         return port;
391     }
392
393     static void extractFileName(const CharacterType* spec, const URLComponent& path, URLComponent& fileName)
394     {
395         // Handle empty paths: they have no file names.
396         if (path.isEmptyOrInvalid()) {
397             fileName.reset();
398             return;
399         }
400
401         // Search backwards for a parameter, which is a normally unused field
402         // in a URL delimited by a semicolon. We parse the parameter as part of
403         // the path, but here, we don't want to count it. The last semicolon is
404         // the parameter.
405         int fileEnd = path.end();
406         for (int i = path.end() - 1; i > path.begin(); --i) {
407             if (spec[i] == ';') {
408                 fileEnd = i;
409                 break;
410             }
411         }
412
413         // Now search backwards from the filename end to the previous slash
414         // to find the beginning of the filename.
415         for (int i = fileEnd - 1; i >= path.begin(); --i) {
416             if (isURLSlash(spec[i])) {
417                 // File name is everything following this character to the end
418                 fileName = URLComponent::fromRange(i + 1, fileEnd);
419                 return;
420             }
421         }
422
423         // No slash found, this means the input was degenerate (generally paths
424         // will start with a slash). Let's call everything the file name.
425         fileName = URLComponent::fromRange(path.begin(), fileEnd);
426     }
427
428     static bool extractQueryKeyValue(const CharacterType* spec, URLComponent& query, URLComponent& key, URLComponent& value)
429     {
430         if (query.isEmptyOrInvalid())
431             return false;
432
433         int start = query.begin();
434         int current = start;
435         int end = query.end();
436
437         // We assume the beginning of the input is the beginning of the "key"
438         // and we skip to the end of it.
439         key.setBegin(current);
440         while (current < end && spec[current] != '&' && spec[current] != '=')
441             ++current;
442         key.setLength(current - key.begin());
443
444         // Skip the separator after the key (if any).
445         if (current < end && spec[current] == '=')
446             ++current;
447
448         // Find the value part.
449         value.setBegin(current);
450         while (current < end && spec[current] != '&')
451             ++current;
452         value.setLength(current - value.begin());
453
454         // Finally skip the next separator if any
455         if (current < end && spec[current] == '&')
456             ++current;
457
458         // Save the new query
459         query = URLComponent::fromRange(current, end);
460         return true;
461     }
462
463 // FIXME: This should be protected or private.
464 public:
465     // We treat slashes and backslashes the same for IE compatibility.
466     static inline bool isURLSlash(CharacterType ch)
467     {
468         return ch == '/' || ch == '\\';
469     }
470
471     // Returns true if we should trim this character from the URL because it is
472     // a space or a control character.
473     static inline bool shouldTrimFromURL(CharacterType ch)
474     {
475         return ch <= ' ';
476     }
477
478     // Given an already-initialized begin index and end index (the index after
479     // the last CharacterType in spec), this shrinks the range to eliminate
480     // "should-be-trimmed" characters.
481     static inline void trimURL(const CharacterType* spec, int& begin, int& end)
482     {
483         // Strip leading whitespace and control characters.
484         while (begin < end && shouldTrimFromURL(spec[begin]))
485             ++begin;
486
487         // Strip trailing whitespace and control characters. We need the >i
488         // test for when the input string is all blanks; we don't want to back
489         // past the input.
490         while (end > begin && shouldTrimFromURL(spec[end - 1]))
491             --end;
492     }
493
494     // Counts the number of consecutive slashes starting at the given offset
495     // in the given string of the given length.
496     static inline int consecutiveSlashes(const CharacterType *string, int beginOffset, int stringLength)
497     {
498         int count = 0;
499         while (beginOffset + count < stringLength && isURLSlash(string[beginOffset + count]))
500             ++count;
501         return count;
502     }
503
504 private:
505     // URLParser cannot be constructed.
506     URLParser();
507
508     // Returns true if the given character is a valid digit to use in a port.
509     static inline bool isPortDigit(CharacterType ch)
510     {
511         return ch >= '0' && ch <= '9';
512     }
513
514     // Returns the offset of the next authority terminator in the input starting
515     // from startOffset. If no terminator is found, the return value will be equal
516     // to specLength.
517     static int nextAuthorityTerminator(const CharacterType* spec, int startOffset, int specLength)
518     {
519         for (int i = startOffset; i < specLength; i++) {
520             if (isPossibleAuthorityTerminator(spec[i]))
521                 return i;
522         }
523         return specLength; // Not found.
524     }
525
526     static void parseUserInfo(const CharacterType* spec, const URLComponent& user, URLComponent& username, URLComponent& password)
527     {
528         // Find the first colon in the user section, which separates the
529         // username and password.
530         int colonOffset = 0;
531         while (colonOffset < user.length() && spec[user.begin() + colonOffset] != ':')
532             ++colonOffset;
533
534         if (colonOffset < user.length()) {
535             // Found separator: <username>:<password>
536             username = URLComponent(user.begin(), colonOffset);
537             password = URLComponent::fromRange(user.begin() + colonOffset + 1, user.begin() + user.length());
538         } else {
539             // No separator, treat everything as the username
540             username = user;
541             password = URLComponent();
542         }
543     }
544
545     static void parseServerInfo(const CharacterType* spec, const URLComponent& serverInfo, URLComponent& host, URLComponent& port)
546     {
547         if (!serverInfo.length()) {
548             // No server info, host name is empty.
549             host.reset();
550             port.reset();
551             return;
552         }
553
554         // If the host starts with a left-bracket, assume the entire host is an
555         // IPv6 literal. Otherwise, assume none of the host is an IPv6 literal.
556         // This assumption will be overridden if we find a right-bracket.
557         //
558         // Our IPv6 address canonicalization code requires both brackets to
559         // exist, but the ability to locate an incomplete address can still be
560         // useful.
561         int ipv6Terminator = spec[serverInfo.begin()] == '[' ? serverInfo.end() : -1;
562         int colon = -1;
563
564         // Find the last right-bracket, and the last colon.
565         for (int i = serverInfo.begin(); i < serverInfo.end(); i++) {
566             switch (spec[i]) {
567             case ']':
568                 ipv6Terminator = i;
569                 break;
570             case ':':
571                 colon = i;
572                 break;
573             default:
574                 break;
575             }
576         }
577
578         if (colon > ipv6Terminator) {
579             // Found a port number: <hostname>:<port>
580             host = URLComponent::fromRange(serverInfo.begin(), colon);
581             if (!host.length())
582                 host.reset();
583             port = URLComponent::fromRange(colon + 1, serverInfo.end());
584         } else {
585             // No port: <hostname>
586             host = serverInfo;
587             port.reset();
588         }
589     }
590 };
591
592 } // namespace WTF
593
594 #endif // USE(WTFURL)
595
596 #endif // URLParser_h