Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(369)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 167703010: Write custom URI-parser, to avoid using regexp. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/corelib/uri_file_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.core; 5 part of dart.core;
6 6
7 /** 7 /**
8 * A parsed URI, such as a URL. 8 * A parsed URI, such as a URL.
9 * 9 *
10 * **See also:** 10 * **See also:**
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
111 List<String> _pathSegments; 111 List<String> _pathSegments;
112 112
113 /** 113 /**
114 * Cache the computed return value of [queryParameters]. 114 * Cache the computed return value of [queryParameters].
115 */ 115 */
116 Map<String, String> _queryParameters; 116 Map<String, String> _queryParameters;
117 117
118 /** 118 /**
119 * Creates a new URI object by parsing a URI string. 119 * Creates a new URI object by parsing a URI string.
120 */ 120 */
121 static Uri parse(String uri) => new Uri._fromMatch(_splitRe.firstMatch(uri)); 121 static Uri parse(String uri) {
122 122 // This parsing will not validate percent-encoding, IPv6, etc. When done
123 Uri._fromMatch(Match m) : 123 // it will call `new Uri(...)` which will perform these validations.
124 this(scheme: _makeScheme(_emptyIfNull(m[_COMPONENT_SCHEME])), 124 // This is purely splitting up the URI string into components.
125 userInfo: _emptyIfNull(m[_COMPONENT_USER_INFO]), 125 //
126 host: _eitherOf( 126 // Important parts of the RFC 3986 used here:
127 m[_COMPONENT_HOST], m[_COMPONENT_HOST_IPV6]), 127 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
128 port: _parseIntOrZero(m[_COMPONENT_PORT]), 128 //
129 path: _emptyIfNull(m[_COMPONENT_PATH]), 129 // hier-part = "//" authority path-abempty
130 query: _emptyIfNull(m[_COMPONENT_QUERY_DATA]), 130 // / path-absolute
131 fragment: _emptyIfNull(m[_COMPONENT_FRAGMENT])); 131 // / path-rootless
132 // / path-empty
133 //
134 // URI-reference = URI / relative-ref
135 //
136 // absolute-URI = scheme ":" hier-part [ "?" query ]
137 //
138 // relative-ref = relative-part [ "?" query ] [ "#" fragment ]
139 //
140 // relative-part = "//" authority path-abempty
141 // / path-absolute
142 // / path-noscheme
143 // / path-empty
144 //
145 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
146 //
147 // authority = [ userinfo "@" ] host [ ":" port ]
148 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
149 // host = IP-literal / IPv4address / reg-name
150 // port = *DIGIT
151 // reg-name = *( unreserved / pct-encoded / sub-delims )
152 //
153 // path = path-abempty ; begins with "/" or is empty
154 // / path-absolute ; begins with "/" but not "//"
155 // / path-noscheme ; begins with a non-colon segment
156 // / path-rootless ; begins with a segment
157 // / path-empty ; zero characters
158 //
159 // path-abempty = *( "/" segment )
160 // path-absolute = "/" [ segment-nz *( "/" segment ) ]
161 // path-noscheme = segment-nz-nc *( "/" segment )
162 // path-rootless = segment-nz *( "/" segment )
163 // path-empty = 0<pchar>
164 //
165 // segment = *pchar
166 // segment-nz = 1*pchar
167 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
168 // ; non-zero-length segment without any colon ":"
169 //
170 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
171 //
172 // query = *( pchar / "/" / "?" )
173 //
174 // fragment = *( pchar / "/" / "?" )
175 bool isRegName(int ch) {
176 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
177 }
178
179 int ipV6Address(List<int> codeUnits, int index) {
180 // IPv6. Skip to ']'.
181 index = codeUnits.indexOf(_RIGHT_BRACKET, index);
182 if (index == -1) {
183 throw new FormatException("Bad end of IPv6 host");
184 }
185 return index + 1;
186 }
187
188 List<int> codeUnits = uri.codeUnits;
189 int length = codeUnits.length;
190 int index = 0;
191
192 int schemeEndIndex = 0;
193
194 if (length == 0) {
195 return new Uri();
196 }
197
198 if (codeUnits[0] != _SLASH) {
199 // Can be scheme.
200 while (index < length) {
201 // Look for ':'. If found, continue from the post of ':'. If not (end
202 // reached or invalid scheme char found) back up one char, and continue
203 // to path.
204 // Note that scheme-chars is contained in path-chars.
205 int codeUnit = codeUnits[index++];
206 if (!_isSchemeCharacter(codeUnit)) {
207 if (codeUnit == _COLON) {
208 schemeEndIndex = index;
209 } else {
210 // Back up one char, since we met an invalid scheme char.
211 index--;
212 }
213 break;
214 }
215 }
216 }
217
218 int userInfoEndIndex = -1;
219 int portIndex = -1;
220 int authorityEndIndex = schemeEndIndex;
221 // If we see '//', there must be an authority.
222 if (authorityEndIndex == index &&
223 authorityEndIndex + 1 < length &&
224 codeUnits[authorityEndIndex] == _SLASH &&
225 codeUnits[authorityEndIndex + 1] == _SLASH) {
226 // Skip '//'.
227 authorityEndIndex += 2;
228 // It can both be host and userInfo.
229 while (authorityEndIndex < length) {
230 int codeUnit = codeUnits[authorityEndIndex++];
231 if (!isRegName(codeUnit)) {
232 if (codeUnit == _LEFT_BRACKET) {
233 authorityEndIndex = ipV6Address(codeUnits, authorityEndIndex);
234 } else if (portIndex == -1 && codeUnit == _COLON) {
235 // First time ':'.
236 portIndex = authorityEndIndex;
237 } else if (codeUnit == _AT_SIGN || codeUnit == _COLON) {
238 // Second time ':' or first '@'. Must be userInfo.
239 userInfoEndIndex = codeUnits.indexOf('@'.codeUnitAt(0),
240 authorityEndIndex - 1);
241 // Not found. Must be path then.
242 if (userInfoEndIndex == -1) {
243 authorityEndIndex = index;
244 break;
245 }
246 portIndex = -1;
247 authorityEndIndex = userInfoEndIndex + 1;
248 // Now it can only be host:port.
249 while (authorityEndIndex < length) {
250 int codeUnit = codeUnits[authorityEndIndex++];
251 if (!isRegName(codeUnit)) {
252 if (codeUnit == _LEFT_BRACKET) {
253 authorityEndIndex = ipV6Address(codeUnits, authorityEndIndex);
254 } else if (codeUnit == _COLON) {
255 if (portIndex != -1) {
256 throw new FormatException("Double port in host");
257 }
258 portIndex = authorityEndIndex;
259 } else {
260 authorityEndIndex--;
261 break;
262 }
263 }
264 }
265 break;
266 } else {
267 authorityEndIndex--;
268 break;
269 }
270 }
271 }
272 } else {
273 authorityEndIndex = schemeEndIndex;
274 }
275
276 // At path now.
277 int pathEndIndex = authorityEndIndex;
278 while (pathEndIndex < length) {
279 int codeUnit = codeUnits[pathEndIndex++];
280 if (codeUnit == _QUESTION || codeUnit == _NUMBER_SIGN) {
281 pathEndIndex--;
282 break;
283 }
284 }
285
286 // Maybe query.
287 int queryEndIndex = pathEndIndex;
288 if (queryEndIndex < length && codeUnits[queryEndIndex] == _QUESTION) {
289 while (queryEndIndex < length) {
290 int codeUnit = codeUnits[queryEndIndex++];
291 if (codeUnit == _NUMBER_SIGN) {
292 queryEndIndex--;
293 break;
294 }
295 }
296 }
297
298 var scheme = null;
299 if (schemeEndIndex > 0) {
300 scheme = uri.substring(0, schemeEndIndex - 1);
301 }
302
303 var host = "";
304 var userInfo = "";
305 var port = 0;
306 if (schemeEndIndex != authorityEndIndex) {
307 int startIndex = schemeEndIndex + 2;
308 if (userInfoEndIndex > 0) {
309 userInfo = uri.substring(startIndex, userInfoEndIndex);
310 startIndex = userInfoEndIndex + 1;
311 }
312 if (portIndex > 0) {
313 var portStr = uri.substring(portIndex, authorityEndIndex);
314 try {
315 port = int.parse(portStr);
316 } catch (_) {
317 throw new FormatException("Invalid port: '$portStr'");
318 }
319 host = uri.substring(startIndex, portIndex - 1);
320 } else {
321 host = uri.substring(startIndex, authorityEndIndex);
322 }
323 }
324
325 var path = uri.substring(authorityEndIndex, pathEndIndex);
326 var query = "";
327 if (pathEndIndex < queryEndIndex) {
328 query = uri.substring(pathEndIndex + 1, queryEndIndex);
329 }
330 var fragment = "";
331 // If queryEndIndex is not at end (length), there is a fragment.
332 if (queryEndIndex < length) {
333 fragment = uri.substring(queryEndIndex + 1, length);
334 }
335
336 return new Uri(scheme: scheme,
337 userInfo: userInfo,
338 host: host,
339 port: port,
340 path: path,
341 query: query,
342 fragment: fragment);
343 }
132 344
133 /** 345 /**
134 * Creates a new URI from its components. 346 * Creates a new URI from its components.
135 * 347 *
136 * Each component is set through a named argument. Any number of 348 * Each component is set through a named argument. Any number of
137 * components can be provided. The default value for the components 349 * components can be provided. The default value for the components
138 * not provided is the empry string, except for [port] which has a 350 * not provided is the empry string, except for [port] which has a
139 * default value of 0. The [path] and [query] components can be set 351 * default value of 0. The [path] and [query] components can be set
140 * using two different named arguments. 352 * using two different named arguments.
141 * 353 *
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
538 } 750 }
539 return host; 751 return host;
540 } 752 }
541 753
542 static String _makeScheme(String scheme) { 754 static String _makeScheme(String scheme) {
543 bool isSchemeLowerCharacter(int ch) { 755 bool isSchemeLowerCharacter(int ch) {
544 return ch < 128 && 756 return ch < 128 &&
545 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 757 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
546 } 758 }
547 759
548 bool isSchemeCharacter(int ch) {
549 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
550 }
551
552 if (scheme == null) return ""; 760 if (scheme == null) return "";
553 bool allLowercase = true; 761 bool allLowercase = true;
554 int length = scheme.length; 762 int length = scheme.length;
555 for (int i = 0; i < length; i++) { 763 for (int i = 0; i < length; i++) {
556 int codeUnit = scheme.codeUnitAt(i); 764 int codeUnit = scheme.codeUnitAt(i);
557 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) { 765 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) {
558 // First code unit must be an alphabetic character. 766 // First code unit must be an alphabetic character.
559 throw new ArgumentError('Illegal scheme: $scheme'); 767 throw new ArgumentError('Illegal scheme: $scheme');
560 } 768 }
561 if (!isSchemeLowerCharacter(codeUnit)) { 769 if (!isSchemeLowerCharacter(codeUnit)) {
562 if (isSchemeCharacter(codeUnit)) { 770 if (_isSchemeCharacter(codeUnit)) {
563 allLowercase = false; 771 allLowercase = false;
564 } else { 772 } else {
565 throw new ArgumentError('Illegal scheme: $scheme'); 773 throw new ArgumentError('Illegal scheme: $scheme');
566 } 774 }
567 } 775 }
568 } 776 }
569 777
570 return allLowercase ? scheme : scheme.toLowerCase(); 778 return allLowercase ? scheme : scheme.toLowerCase();
571 } 779 }
572 780
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
712 index++; 920 index++;
713 } 921 }
714 } 922 }
715 if (result != null && prevIndex != index) fillResult(); 923 if (result != null && prevIndex != index) fillResult();
716 assert(index == length); 924 assert(index == length);
717 925
718 if (result == null) return component; 926 if (result == null) return component;
719 return result.toString(); 927 return result.toString();
720 } 928 }
721 929
722 static String _emptyIfNull(String val) => val != null ? val : ''; 930 static bool _isSchemeCharacter(int ch) {
723 931 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
724 static int _parseIntOrZero(String val) {
725 if (val != null && val != '') {
726 return int.parse(val);
727 } else {
728 return 0;
729 }
730 } 932 }
731 933
732 static String _eitherOf(String val1, String val2) {
733 if (val1 != null) return val1;
734 if (val2 != null) return val2;
735 return '';
736 }
737
738 // NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js
739 static final RegExp _splitRe = new RegExp(
740 '^'
741 '(?:'
742 '([^:/?#]+)' // scheme - ignore special characters
743 // used by other URL parts such as :,
744 // ?, /, #, and .
745 ':)?'
746 '(?://'
747 '(?:([^/?#]*)@)?' // userInfo
748 '(?:'
749 r'([\w\d\-\u0100-\uffff.%]*)'
750 // host - restrict to letters,
751 // digits, dashes, dots, percent
752 // escapes, and unicode characters.
753 '|'
754 // TODO(ajohnsen): Only allow a max number of parts?
755 r'\[([A-Fa-f0-9:.]*)\])'
756 // IPv6 host - restrict to hex,
757 // dot and colon.
758 '(?::([0-9]+))?' // port
759 ')?'
760 r'([^?#[]+)?' // path
761 r'(?:\?([^#]*))?' // query
762 '(?:#(.*))?' // fragment
763 r'$');
764
765 static const _COMPONENT_SCHEME = 1;
766 static const _COMPONENT_USER_INFO = 2;
767 static const _COMPONENT_HOST = 3;
768 static const _COMPONENT_HOST_IPV6 = 4;
769 static const _COMPONENT_PORT = 5;
770 static const _COMPONENT_PATH = 6;
771 static const _COMPONENT_QUERY_DATA = 7;
772 static const _COMPONENT_FRAGMENT = 8;
773 934
774 /** 935 /**
775 * Returns whether the URI is absolute. 936 * Returns whether the URI is absolute.
776 */ 937 */
777 bool get isAbsolute => scheme != "" && fragment == ""; 938 bool get isAbsolute => scheme != "" && fragment == "";
778 939
779 String _merge(String base, String reference) { 940 String _merge(String base, String reference) {
780 if (base == "") return "/$reference"; 941 if (base == "") return "/$reference";
781 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; 942 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference";
782 } 943 }
(...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after
1362 } else { 1523 } else {
1363 return [(value >> 8) & 0xFF, value & 0xFF]; 1524 return [(value >> 8) & 0xFF, value & 0xFF];
1364 } 1525 }
1365 }) 1526 })
1366 .toList(); 1527 .toList();
1367 } 1528 }
1368 1529
1369 // Frequently used character codes. 1530 // Frequently used character codes.
1370 static const int _SPACE = 0x20; 1531 static const int _SPACE = 0x20;
1371 static const int _DOUBLE_QUOTE = 0x22; 1532 static const int _DOUBLE_QUOTE = 0x22;
1533 static const int _NUMBER_SIGN = 0x23;
1372 static const int _PERCENT = 0x25; 1534 static const int _PERCENT = 0x25;
1373 static const int _ASTERISK = 0x2A; 1535 static const int _ASTERISK = 0x2A;
1374 static const int _PLUS = 0x2B; 1536 static const int _PLUS = 0x2B;
1375 static const int _SLASH = 0x2F; 1537 static const int _SLASH = 0x2F;
1376 static const int _ZERO = 0x30; 1538 static const int _ZERO = 0x30;
1377 static const int _NINE = 0x39; 1539 static const int _NINE = 0x39;
1378 static const int _COLON = 0x3A; 1540 static const int _COLON = 0x3A;
1379 static const int _LESS = 0x3C; 1541 static const int _LESS = 0x3C;
1380 static const int _GREATER = 0x3E; 1542 static const int _GREATER = 0x3E;
1381 static const int _QUESTION = 0x3F; 1543 static const int _QUESTION = 0x3F;
(...skipping 236 matching lines...) Expand 10 before | Expand all | Expand 10 after
1618 0x2bff, // 0x30 - 0x3f 1111111111010100 1780 0x2bff, // 0x30 - 0x3f 1111111111010100
1619 // ABCDEFGHIJKLMNO 1781 // ABCDEFGHIJKLMNO
1620 0xfffe, // 0x40 - 0x4f 0111111111111111 1782 0xfffe, // 0x40 - 0x4f 0111111111111111
1621 // PQRSTUVWXYZ _ 1783 // PQRSTUVWXYZ _
1622 0x87ff, // 0x50 - 0x5f 1111111111100001 1784 0x87ff, // 0x50 - 0x5f 1111111111100001
1623 // abcdefghijklmno 1785 // abcdefghijklmno
1624 0xfffe, // 0x60 - 0x6f 0111111111111111 1786 0xfffe, // 0x60 - 0x6f 0111111111111111
1625 // pqrstuvwxyz ~ 1787 // pqrstuvwxyz ~
1626 0x47ff]; // 0x70 - 0x7f 1111111111100010 1788 0x47ff]; // 0x70 - 0x7f 1111111111100010
1627 1789
1790 // Characters allowed in the reg-name as of RFC 3986.
1791 // RFC 3986 Apendix A
1792 // reg-name = *( unreserved / pct-encoded / sub-delims )
1793 static const _regNameTable = const [
1794 // LSB MSB
1795 // | |
1796 0x0000, // 0x00 - 0x0f 0000000000000000
1797 0x0000, // 0x10 - 0x1f 0000000000000000
1798 // ! $%&'()*+,-.
1799 0x7ff2, // 0x20 - 0x2f 0100111111111110
1800 // 0123456789 ; =
1801 0x2bff, // 0x30 - 0x3f 1111111111010100
1802 // ABCDEFGHIJKLMNO
1803 0xfffe, // 0x40 - 0x4f 0111111111111111
1804 // PQRSTUVWXYZ _
1805 0x87ff, // 0x50 - 0x5f 1111111111100001
1806 // abcdefghijklmno
1807 0xfffe, // 0x60 - 0x6f 0111111111111111
1808 // pqrstuvwxyz ~
1809 0x47ff]; // 0x70 - 0x7f 1111111111100010
1810
1628 // Characters allowed in the path as of RFC 3986. 1811 // Characters allowed in the path as of RFC 3986.
1629 // RFC 3986 section 3.3. 1812 // RFC 3986 section 3.3.
1630 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1813 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1631 static const _pathCharTable = const [ 1814 static const _pathCharTable = const [
1632 // LSB MSB 1815 // LSB MSB
1633 // | | 1816 // | |
1634 0x0000, // 0x00 - 0x0f 0000000000000000 1817 0x0000, // 0x00 - 0x0f 0000000000000000
1635 0x0000, // 0x10 - 0x1f 0000000000000000 1818 0x0000, // 0x10 - 0x1f 0000000000000000
1636 // ! $ &'()*+,-. 1819 // ! $ &'()*+,-.
1637 0x7fd2, // 0x20 - 0x2f 0100101111111110 1820 0x7fd2, // 0x20 - 0x2f 0100101111111110
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
1690 void clear() { 1873 void clear() {
1691 throw new UnsupportedError("Cannot modify an unmodifiable map"); 1874 throw new UnsupportedError("Cannot modify an unmodifiable map");
1692 } 1875 }
1693 void forEach(void f(K key, V value)) => _map.forEach(f); 1876 void forEach(void f(K key, V value)) => _map.forEach(f);
1694 Iterable<K> get keys => _map.keys; 1877 Iterable<K> get keys => _map.keys;
1695 Iterable<V> get values => _map.values; 1878 Iterable<V> get values => _map.values;
1696 int get length => _map.length; 1879 int get length => _map.length;
1697 bool get isEmpty => _map.isEmpty; 1880 bool get isEmpty => _map.isEmpty;
1698 bool get isNotEmpty => _map.isNotEmpty; 1881 bool get isNotEmpty => _map.isNotEmpty;
1699 } 1882 }
OLDNEW
« no previous file with comments | « no previous file | tests/corelib/uri_file_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698