Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
uri.cc
Go to the documentation of this file.
1// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
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.
4
5#include "vm/uri.h"
6
7#include "vm/zone.h"
8
9namespace dart {
10
11static bool IsUnreservedChar(intptr_t value) {
12 return ((value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') ||
13 (value >= '0' && value <= '9') || value == '-' || value == '.' ||
14 value == '_' || value == '~');
15}
16
17static bool IsDelimiter(intptr_t value) {
18 switch (value) {
19 case ':':
20 case '/':
21 case '?':
22 case '#':
23 case '[':
24 case ']':
25 case '@':
26 case '!':
27 case '$':
28 case '&':
29 case '\'':
30 case '(':
31 case ')':
32 case '*':
33 case '+':
34 case ',':
35 case ';':
36 case '=':
37 return true;
38 default:
39 return false;
40 }
41}
42
43static bool IsHexDigit(char value) {
44 return ((value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') ||
45 (value >= 'a' && value <= 'f'));
46}
47
48static int HexValue(char digit) {
49 if ((digit >= '0' && digit <= '9')) {
50 return digit - '0';
51 }
52 if ((digit >= 'A' && digit <= 'F')) {
53 return digit - 'A' + 10;
54 }
55 if ((digit >= 'a' && digit <= 'f')) {
56 return digit - 'a' + 10;
57 }
59 return 0;
60}
61
62static int GetEscapedValue(const char* str, intptr_t pos, intptr_t len) {
63 if (pos + 2 >= len) {
64 // Not enough room for a valid escape sequence.
65 return -1;
66 }
67 if (str[pos] != '%') {
68 // Escape sequences start with '%'.
69 return -1;
70 }
71
72 char digit1 = str[pos + 1];
73 char digit2 = str[pos + 2];
74 if (!IsHexDigit(digit1) || !IsHexDigit(digit2)) {
75 // Invalid escape sequence. Ignore it.
76 return -1;
77 }
78 return HexValue(digit1) * 16 + HexValue(digit2);
79}
80
81static char* NormalizeEscapes(const char* str, intptr_t len) {
82 // Allocate the buffer.
83 Zone* zone = ThreadState::Current()->zone();
84 // We multiply len by three because a percent-escape sequence is
85 // three characters long (e.g. ' ' -> '%20). +1 for '\0'. We could
86 // take two passes through the string and avoid the excess
87 // allocation, but it's zone-memory so it doesn't seem necessary.
88 char* buffer = zone->Alloc<char>(len * 3 + 1);
89
90 // Copy the string, normalizing as we go.
91 intptr_t buffer_pos = 0;
92 intptr_t pos = 0;
93 while (pos < len) {
94 int escaped_value = GetEscapedValue(str, pos, len);
95 if (escaped_value >= 0) {
96 // If one of the special "unreserved" characters has been
97 // escaped, revert the escaping. Otherwise preserve the
98 // escaping.
99 if (IsUnreservedChar(escaped_value)) {
100 buffer[buffer_pos] = escaped_value;
101 buffer_pos++;
102 } else {
103 Utils::SNPrint(buffer + buffer_pos, 4, "%%%02X", escaped_value);
104 buffer_pos += 3;
105 }
106 pos += 3;
107 } else {
108 char c = str[pos];
109 // If a delimiter or unreserved character is currently not
110 // escaped, preserve that. If there is a busted %-sequence in
111 // the input, preserve that too.
112 if (c == '%' || IsDelimiter(c) || IsUnreservedChar(c)) {
113 buffer[buffer_pos] = c;
114 buffer_pos++;
115 } else {
116 // Escape funky characters.
117 Utils::SNPrint(buffer + buffer_pos, 4, "%%%02X", c);
118 buffer_pos += 3;
119 }
120 pos++;
121 }
122 }
123 buffer[buffer_pos] = '\0';
124 return buffer;
125}
126
127// Lower-case a string in place.
128static void StringLower(char* str) {
129 const intptr_t len = strlen(str);
130 intptr_t i = 0;
131 while (i < len) {
132 int escaped_value = GetEscapedValue(str, i, len);
133 if (escaped_value >= 0) {
134 // Don't lowercase escape sequences.
135 i += 3;
136 } else {
137 // I don't use tolower() because I don't want the locale
138 // transforming any non-ascii characters.
139 char c = str[i];
140 if (c >= 'A' && c <= 'Z') {
141 str[i] = c + ('a' - 'A');
142 }
143 i++;
144 }
145 }
146}
147
148static void ClearParsedUri(ParsedUri* parsed_uri) {
149 parsed_uri->scheme = nullptr;
150 parsed_uri->userinfo = nullptr;
151 parsed_uri->host = nullptr;
152 parsed_uri->port = nullptr;
153 parsed_uri->path = nullptr;
154 parsed_uri->query = nullptr;
155 parsed_uri->fragment = nullptr;
156}
157
158static intptr_t ParseAuthority(const char* authority, ParsedUri* parsed_uri) {
159 Zone* zone = ThreadState::Current()->zone();
160 const char* current = authority;
161 intptr_t len = 0;
162
163 size_t userinfo_len = strcspn(current, "@/");
164 if (current[userinfo_len] == '@') {
165 // The '@' character follows the optional userinfo string.
166 parsed_uri->userinfo = NormalizeEscapes(current, userinfo_len);
167 current += userinfo_len + 1;
168 len += userinfo_len + 1;
169 } else {
170 parsed_uri->userinfo = nullptr;
171 }
172
173 size_t host_len = strcspn(current, ":/");
174 char* host = NormalizeEscapes(current, host_len);
175 StringLower(host);
176 parsed_uri->host = host;
177 len += host_len;
178
179 if (current[host_len] == ':') {
180 // The ':' character precedes the optional port string.
181 const char* port_start = current + host_len + 1; // +1 for ':'
182 size_t port_len = strcspn(port_start, "/");
183 parsed_uri->port = zone->MakeCopyOfStringN(port_start, port_len);
184 len += 1 + port_len; // +1 for ':'
185 } else {
186 parsed_uri->port = nullptr;
187 }
188 return len;
189}
190
191// Performs a simple parse of a uri into its components.
192// See RFC 3986 Section 3: Syntax.
193bool ParseUri(const char* uri, ParsedUri* parsed_uri) {
194 Zone* zone = ThreadState::Current()->zone();
195
196 // The first ':' separates the scheme from the rest of the uri. If
197 // a ':' occurs after the first '/' it doesn't count.
198 size_t scheme_len = strcspn(uri, ":/");
199 const char* rest = uri;
200 if (uri[scheme_len] == ':') {
201 char* scheme = zone->MakeCopyOfStringN(uri, scheme_len);
202 StringLower(scheme);
203 parsed_uri->scheme = scheme;
204 rest = uri + scheme_len + 1;
205 } else {
206 parsed_uri->scheme = nullptr;
207 }
208
209 // The first '#' separates the optional fragment
210 const char* hash_pos = rest + strcspn(rest, "#");
211 if (*hash_pos == '#') {
212 // There is a fragment part.
213 const char* fragment_start = hash_pos + 1;
214 parsed_uri->fragment =
215 NormalizeEscapes(fragment_start, strlen(fragment_start));
216 } else {
217 parsed_uri->fragment = nullptr;
218 }
219
220 // The first '?' or '#' separates the hierarchical part from the
221 // optional query.
222 const char* question_pos = rest + strcspn(rest, "?#");
223 if (*question_pos == '?') {
224 // There is a query part.
225 const char* query_start = question_pos + 1;
226 parsed_uri->query = NormalizeEscapes(query_start, (hash_pos - query_start));
227 } else {
228 parsed_uri->query = nullptr;
229 }
230
231 const char* path_start = rest;
232 if (rest[0] == '/' && rest[1] == '/') {
233 // There is an authority part.
234 const char* authority_start = rest + 2; // 2 for '//'.
235
236 intptr_t authority_len = ParseAuthority(authority_start, parsed_uri);
237 if (authority_len < 0) {
238 ClearParsedUri(parsed_uri);
239 return false;
240 }
241 path_start = authority_start + authority_len;
242 } else {
243 parsed_uri->userinfo = nullptr;
244 parsed_uri->host = nullptr;
245 parsed_uri->port = nullptr;
246 }
247
248 // The path is the substring between the authority and the query.
249 parsed_uri->path = NormalizeEscapes(path_start, (question_pos - path_start));
250 return true;
251}
252
253static char* RemoveLastSegment(char* current, char* base) {
254 if (current == base) {
255 return current;
256 }
257 ASSERT(current > base);
258 for (current--; current > base; current--) {
259 if (*current == '/') {
260 // We have found the beginning of the last segment.
261 return current;
262 }
263 }
264 ASSERT(current == base);
265 return current;
266}
267
268static intptr_t SegmentLength(const char* input) {
269 const char* cp = input;
270
271 // Include initial slash in the segment, if any.
272 if (*cp == '/') {
273 cp++;
274 }
275
276 // Don't include trailing slash in the segment.
277 cp += strcspn(cp, "/");
278 return cp - input;
279}
280
281// See RFC 3986 Section 5.2.4: Remove Dot Segments.
282static const char* RemoveDotSegments(const char* path) {
283 const char* input = path;
284
285 // The output path will always be less than or equal to the size of
286 // the input path.
287 Zone* zone = ThreadState::Current()->zone();
288 char* buffer = zone->Alloc<char>(strlen(path) + 1); // +1 for '\0'
289 char* output = buffer;
290
291 while (*input != '\0') {
292 if (strncmp("../", input, 3) == 0) {
293 // Discard initial "../" from the input. It's junk.
294 input += 3;
295
296 } else if (strncmp("./", input, 3) == 0) {
297 // Discard initial "./" from the input. It's junk.
298 input += 2;
299
300 } else if (strncmp("/./", input, 3) == 0) {
301 // Advance past the "/." part of the input.
302 input += 2;
303
304 } else if (strcmp("/.", input) == 0) {
305 // Pretend the input just contains a "/".
306 input = "/";
307
308 } else if (strncmp("/../", input, 4) == 0) {
309 // Advance past the "/.." part of the input and remove one
310 // segment from the output.
311 input += 3;
312 output = RemoveLastSegment(output, buffer);
313
314 } else if (strcmp("/..", input) == 0) {
315 // Pretend the input contains a "/" and remove one segment from
316 // the output.
317 input = "/";
318 output = RemoveLastSegment(output, buffer);
319
320 } else if (strcmp("..", input) == 0) {
321 // The input has been reduced to nothing useful.
322 input += 2;
323
324 } else if (strcmp(".", input) == 0) {
325 // The input has been reduced to nothing useful.
326 input += 1;
327
328 } else {
329 intptr_t segment_len = SegmentLength(input);
330 if (input[0] != '/' && output != buffer) {
331 *output = '/';
332 output++;
333 }
334 strncpy(output, input, segment_len);
335 output += segment_len;
336 input += segment_len;
337 }
338 }
339 *output = '\0';
340 return buffer;
341}
342
343// See RFC 3986 Section 5.2.3: Merge Paths.
344static const char* MergePaths(const char* base_path, const char* ref_path) {
345 Zone* zone = ThreadState::Current()->zone();
346 if (base_path[0] == '\0') {
347 // If the base_path is empty, we prepend '/'.
348 return zone->PrintToString("/%s", ref_path);
349 }
350
351 // We need to find the last '/' in base_path.
352 const char* last_slash = strrchr(base_path, '/');
353 if (last_slash == nullptr) {
354 // There is no slash in the base_path. Return the ref_path unchanged.
355 return ref_path;
356 }
357
358 // We found a '/' in the base_path. Cut off everything after it and
359 // add the ref_path.
360 intptr_t truncated_base_len = last_slash - base_path;
361 intptr_t ref_path_len = strlen(ref_path);
362 intptr_t len = truncated_base_len + ref_path_len + 1; // +1 for '/'
363 char* buffer = zone->Alloc<char>(len + 1); // +1 for '\0'
364
365 // Copy truncated base.
366 strncpy(buffer, base_path, truncated_base_len);
367
368 // Add a slash.
369 buffer[truncated_base_len] = '/';
370
371 // Copy the ref_path.
372 strncpy((buffer + truncated_base_len + 1), ref_path, ref_path_len + 1);
373
374 return buffer;
375}
376
377static char* BuildUri(const ParsedUri& uri) {
378 Zone* zone = ThreadState::Current()->zone();
379 ASSERT(uri.path != nullptr);
380
381 const char* fragment = uri.fragment == nullptr ? "" : uri.fragment;
382 const char* fragment_separator = uri.fragment == nullptr ? "" : "#";
383 const char* query = uri.query == nullptr ? "" : uri.query;
384 const char* query_separator = uri.query == nullptr ? "" : "?";
385
386 // If there is no scheme for this uri, just build a relative uri of
387 // the form: "path[?query][#fragment]". This occurs when we resolve
388 // relative urls inside a "dart:" library.
389 if (uri.scheme == nullptr) {
390 ASSERT(uri.userinfo == nullptr && uri.host == nullptr &&
391 uri.port == nullptr);
392 return zone->PrintToString("%s%s%s%s%s", uri.path, query_separator, query,
393 fragment_separator, fragment);
394 }
395
396 // Uri with no authority: "scheme:path[?query][#fragment]"
397 if (uri.host == nullptr) {
398 ASSERT(uri.userinfo == nullptr && uri.port == nullptr);
399 return zone->PrintToString("%s:%s%s%s%s%s", uri.scheme, uri.path,
400 query_separator, query, fragment_separator,
401 fragment);
402 }
403
404 const char* user = uri.userinfo == nullptr ? "" : uri.userinfo;
405 const char* user_separator = uri.userinfo == nullptr ? "" : "@";
406 const char* port = uri.port == nullptr ? "" : uri.port;
407 const char* port_separator = uri.port == nullptr ? "" : ":";
408
409 // If the path doesn't start with a '/', add one. We need it to
410 // separate the path from the authority.
411 const char* path_separator =
412 ((uri.path[0] == '\0' || uri.path[0] == '/') ? "" : "/");
413
414 // Uri with authority:
415 // "scheme://[userinfo@]host[:port][/]path[?query][#fragment]"
416 return zone->PrintToString(
417 "%s://%s%s%s%s%s%s%s%s%s%s%s", // There is *nothing* wrong with this.
418 uri.scheme, user, user_separator, uri.host, port_separator, port,
419 path_separator, uri.path, query_separator, query, fragment_separator,
420 fragment);
421}
422
423// See RFC 3986 Section 5: Reference Resolution
424bool ResolveUri(const char* ref_uri,
425 const char* base_uri,
426 const char** target_uri) {
427 // Parse the reference uri.
428 ParsedUri ref;
429 if (!ParseUri(ref_uri, &ref)) {
430 *target_uri = nullptr;
431 return false;
432 }
433
435 if (ref.scheme != nullptr) {
436 if (strcmp(ref.scheme, "dart") == 0) {
437 Zone* zone = ThreadState::Current()->zone();
438 *target_uri = zone->MakeCopyOfString(ref_uri);
439 return true;
440 }
441
442 // When the ref_uri specifies a scheme, the base_uri is ignored.
443 target.scheme = ref.scheme;
444 target.userinfo = ref.userinfo;
445 target.host = ref.host;
446 target.port = ref.port;
447 target.path = RemoveDotSegments(ref.path);
448 target.query = ref.query;
449 target.fragment = ref.fragment;
450 *target_uri = BuildUri(target);
451 return true;
452 }
453
454 // Parse the base uri.
456 if (!ParseUri(base_uri, &base)) {
457 *target_uri = nullptr;
458 return false;
459 }
460
461 if ((base.scheme != nullptr) && strcmp(base.scheme, "dart") == 0) {
462 Zone* zone = ThreadState::Current()->zone();
463 *target_uri = zone->MakeCopyOfString(ref_uri);
464 return true;
465 }
466
467 if (ref.host != nullptr) {
468 // When the ref_uri specifies an authority, we only use the base scheme.
469 target.scheme = base.scheme;
470 target.userinfo = ref.userinfo;
471 target.host = ref.host;
472 target.port = ref.port;
473 target.path = RemoveDotSegments(ref.path);
474 target.query = ref.query;
475 target.fragment = ref.fragment;
476 *target_uri = BuildUri(target);
477 return true;
478 }
479
480 if (ref.path[0] == '\0') {
481 // Empty path. Use most parts of base_uri.
482 target.scheme = base.scheme;
483 target.userinfo = base.userinfo;
484 target.host = base.host;
485 target.port = base.port;
486 target.path = base.path;
487 target.query = ((ref.query == nullptr) ? base.query : ref.query);
488 target.fragment = ref.fragment;
489 *target_uri = BuildUri(target);
490 return true;
491
492 } else if (ref.path[0] == '/') {
493 // Absolute path. ref_path wins.
494 target.scheme = base.scheme;
495 target.userinfo = base.userinfo;
496 target.host = base.host;
497 target.port = base.port;
498 target.path = RemoveDotSegments(ref.path);
499 target.query = ref.query;
500 target.fragment = ref.fragment;
501 *target_uri = BuildUri(target);
502 return true;
503
504 } else {
505 // Relative path. We need to merge the base path and the ref path.
506
507 if (base.scheme == nullptr && base.host == nullptr && base.path[0] != '/') {
508 // The dart:core Uri class handles resolving a relative uri
509 // against a second relative uri specially, in a way not
510 // described in the RFC. We do not need to support this for
511 // library resolution. If we need to implement this later, we
512 // can.
513 *target_uri = nullptr;
514 return false;
515 }
516
517 target.scheme = base.scheme;
518 target.userinfo = base.userinfo;
519 target.host = base.host;
520 target.port = base.port;
521 target.path = RemoveDotSegments(MergePaths(base.path, ref.path));
522 target.query = ref.query;
523 target.fragment = ref.fragment;
524 *target_uri = BuildUri(target);
525 return true;
526 }
527}
528
529} // namespace dart
SkPoint pos
#define UNREACHABLE()
Definition assert.h:248
Zone * zone() const
static ThreadState * Current()
static int SNPrint(char *str, size_t size, const char *format,...) PRINTF_ATTRIBUTE(3
char * PrintToString(const char *format,...) PRINTF_ATTRIBUTE(2
Definition zone.cc:313
char * MakeCopyOfStringN(const char *str, intptr_t len)
Definition zone.cc:277
char * MakeCopyOfString(const char *str)
Definition zone.cc:270
ElementType * Alloc(intptr_t length)
#define ASSERT(E)
static const uint8_t buffer[]
uint8_t value
uint32_t * target
static int GetEscapedValue(const char *str, intptr_t pos, intptr_t len)
Definition uri.cc:62
bool ResolveUri(const char *ref_uri, const char *base_uri, const char **target_uri)
Definition uri.cc:424
static intptr_t HexValue(uint32_t c)
static bool IsHexDigit(char value)
Definition uri.cc:43
bool ParseUri(const char *uri, ParsedUri *parsed_uri)
Definition uri.cc:193
static const char * RemoveDotSegments(const char *path)
Definition uri.cc:282
static bool IsDelimiter(intptr_t value)
Definition uri.cc:17
static void StringLower(char *str)
Definition uri.cc:128
static bool IsUnreservedChar(intptr_t value)
Definition uri.cc:11
static char * NormalizeEscapes(const char *str, intptr_t len)
Definition uri.cc:81
static const char * MergePaths(const char *base_path, const char *ref_path)
Definition uri.cc:344
static char * RemoveLastSegment(char *current, char *base)
Definition uri.cc:253
static intptr_t ParseAuthority(const char *authority, ParsedUri *parsed_uri)
Definition uri.cc:158
static intptr_t SegmentLength(const char *input)
Definition uri.cc:268
static char * BuildUri(const ParsedUri &uri)
Definition uri.cc:377
static void ClearParsedUri(ParsedUri *parsed_uri)
Definition uri.cc:148
const char * query
Definition uri.h:19
const char * path
Definition uri.h:18
const char * userinfo
Definition uri.h:15
const char * scheme
Definition uri.h:14
const char * host
Definition uri.h:16
const char * port
Definition uri.h:17
const char * fragment
Definition uri.h:20