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