MIRROR: javascript for 🐜's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

uri component

+387 -1
+6
include/modules/uri.h
··· 1 + #ifndef URI_H 2 + #define URI_H 3 + 4 + void init_uri_module(void); 5 + 6 + #endif
+1 -1
meson.build
··· 74 74 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 75 75 76 76 version_conf = configuration_data() 77 - version_conf.set('ANT_VERSION', '0.1.0.14') 77 + version_conf.set('ANT_VERSION', '0.1.0.15') 78 78 version_conf.set('ANT_GIT_HASH', git_hash) 79 79 version_conf.set('ANT_BUILD_DATE', build_date) 80 80
+2
src/main.c
··· 30 30 #include "modules/ffi.h" 31 31 #include "modules/events.h" 32 32 #include "modules/performance.h" 33 + #include "modules/uri.h" 33 34 34 35 int js_result = EXIT_SUCCESS; 35 36 ··· 195 196 init_process_module(); 196 197 init_events_module(); 197 198 init_performance_module(); 199 + init_uri_module(); 198 200 199 201 ant_register_library(shell_library, "ant:shell", NULL); 200 202 ant_register_library(ffi_library, "ant:ffi", NULL);
+277
src/modules/uri.c
··· 1 + #include <stdlib.h> 2 + #include <stdio.h> 3 + #include <string.h> 4 + 5 + #include "ant.h" 6 + #include "runtime.h" 7 + #include "modules/uri.h" 8 + 9 + static int hex_digit(char c) { 10 + if (c >= '0' && c <= '9') return c - '0'; 11 + if (c >= 'A' && c <= 'F') return c - 'A' + 10; 12 + if (c >= 'a' && c <= 'f') return c - 'a' + 10; 13 + return -1; 14 + } 15 + 16 + static int is_uri_unreserved(unsigned char c) { 17 + return (c >= 'A' && c <= 'Z') || 18 + (c >= 'a' && c <= 'z') || 19 + (c >= '0' && c <= '9') || 20 + c == '-' || c == '_' || c == '.' || c == '!' || 21 + c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'; 22 + } 23 + 24 + static int is_uri_reserved(unsigned char c) { 25 + return c == ';' || c == '/' || c == '?' || c == ':' || 26 + c == '@' || c == '&' || c == '=' || c == '+' || 27 + c == '$' || c == ',' || c == '#'; 28 + } 29 + 30 + static int utf8_sequence_length(unsigned char first_byte) { 31 + if ((first_byte & 0x80) == 0) return 1; 32 + if ((first_byte & 0xE0) == 0xC0) return 2; 33 + if ((first_byte & 0xF0) == 0xE0) return 3; 34 + if ((first_byte & 0xF8) == 0xF0) return 4; 35 + return -1; 36 + } 37 + 38 + static int is_valid_continuation(unsigned char c) { 39 + return (c & 0xC0) == 0x80; 40 + } 41 + 42 + static int decode_escape_sequence(const char *str, size_t len, size_t *pos, unsigned char *out_byte) { 43 + if (*pos + 2 >= len) return -1; 44 + if (str[*pos] != '%') return -1; 45 + 46 + int high = hex_digit(str[*pos + 1]); 47 + int low = hex_digit(str[*pos + 2]); 48 + if (high < 0 || low < 0) return -1; 49 + 50 + *out_byte = (unsigned char)((high << 4) | low); 51 + *pos += 3; 52 + return 0; 53 + } 54 + 55 + // encodeURIComponent() 56 + static jsval_t js_encodeURIComponent(struct js *js, jsval_t *args, int nargs) { 57 + jsval_t result; 58 + char *out = NULL; 59 + 60 + if (nargs < 1) return js_mkstr(js, "undefined", 9); 61 + 62 + char *str = js_getstr(js, args[0], NULL); 63 + if (!str) return js_mkstr(js, "", 0); 64 + 65 + size_t len = strlen(str); 66 + size_t out_cap = len * 12 + 1; 67 + out = malloc(out_cap); 68 + if (!out) return js_mkerr(js, "out of memory"); 69 + 70 + size_t out_len = 0; 71 + size_t i = 0; 72 + 73 + while (i < len) { 74 + unsigned char c = (unsigned char)str[i]; 75 + 76 + if (is_uri_unreserved(c)) { 77 + out[out_len++] = (char)c; 78 + i++; 79 + continue; 80 + } 81 + 82 + int seq_len = utf8_sequence_length(c); 83 + if (seq_len < 0) goto malformed; 84 + if (i + seq_len > len) goto malformed; 85 + 86 + for (int j = 1; j < seq_len; j++) { 87 + if (!is_valid_continuation((unsigned char)str[i + j])) goto malformed; 88 + } 89 + 90 + for (int j = 0; j < seq_len; j++) { 91 + out_len += sprintf(out + out_len, "%%%02X", (unsigned char)str[i + j]); 92 + } 93 + i += seq_len; 94 + } 95 + 96 + out[out_len] = '\0'; 97 + result = js_mkstr(js, out, out_len); 98 + free(out); 99 + return result; 100 + 101 + malformed: 102 + free(out); 103 + return js_mkerr(js, "URIError: URI malformed"); 104 + } 105 + 106 + // encodeURI() 107 + static jsval_t js_encodeURI(struct js *js, jsval_t *args, int nargs) { 108 + jsval_t result; 109 + char *out = NULL; 110 + 111 + if (nargs < 1) return js_mkstr(js, "undefined", 9); 112 + 113 + char *str = js_getstr(js, args[0], NULL); 114 + if (!str) return js_mkstr(js, "", 0); 115 + 116 + size_t len = strlen(str); 117 + size_t out_cap = len * 12 + 1; 118 + out = malloc(out_cap); 119 + if (!out) return js_mkerr(js, "out of memory"); 120 + 121 + size_t out_len = 0; 122 + size_t i = 0; 123 + 124 + while (i < len) { 125 + unsigned char c = (unsigned char)str[i]; 126 + 127 + if (is_uri_unreserved(c) || is_uri_reserved(c)) { 128 + out[out_len++] = (char)c; 129 + i++; 130 + continue; 131 + } 132 + 133 + int seq_len = utf8_sequence_length(c); 134 + if (seq_len < 0) goto malformed; 135 + if (i + seq_len > len) goto malformed; 136 + 137 + for (int j = 1; j < seq_len; j++) { 138 + if (!is_valid_continuation((unsigned char)str[i + j])) goto malformed; 139 + } 140 + 141 + for (int j = 0; j < seq_len; j++) { 142 + out_len += sprintf(out + out_len, "%%%02X", (unsigned char)str[i + j]); 143 + } 144 + i += seq_len; 145 + } 146 + 147 + out[out_len] = '\0'; 148 + result = js_mkstr(js, out, out_len); 149 + free(out); 150 + return result; 151 + 152 + malformed: 153 + free(out); 154 + return js_mkerr(js, "URIError: URI malformed"); 155 + } 156 + 157 + // decodeURIComponent() 158 + static jsval_t js_decodeURIComponent(struct js *js, jsval_t *args, int nargs) { 159 + jsval_t result; 160 + char *out = NULL; 161 + 162 + if (nargs < 1) return js_mkstr(js, "undefined", 9); 163 + 164 + char *str = js_getstr(js, args[0], NULL); 165 + if (!str) return js_mkstr(js, "", 0); 166 + 167 + size_t len = strlen(str); 168 + out = malloc(len + 1); 169 + if (!out) return js_mkerr(js, "out of memory"); 170 + 171 + size_t out_len = 0; 172 + size_t i = 0; 173 + 174 + while (i < len) { 175 + if (str[i] != '%') { 176 + out[out_len++] = str[i++]; 177 + continue; 178 + } 179 + 180 + unsigned char first_byte; 181 + if (decode_escape_sequence(str, len, &i, &first_byte) < 0) goto malformed; 182 + 183 + int seq_len = utf8_sequence_length(first_byte); 184 + if (seq_len < 0) goto malformed; 185 + 186 + out[out_len++] = (char)first_byte; 187 + 188 + for (int j = 1; j < seq_len; j++) { 189 + unsigned char cont_byte; 190 + if (decode_escape_sequence(str, len, &i, &cont_byte) < 0) goto malformed; 191 + if (!is_valid_continuation(cont_byte)) goto malformed; 192 + out[out_len++] = (char)cont_byte; 193 + } 194 + } 195 + 196 + out[out_len] = '\0'; 197 + result = js_mkstr(js, out, out_len); 198 + free(out); 199 + return result; 200 + 201 + malformed: 202 + free(out); 203 + return js_mkerr(js, "URIError: URI malformed"); 204 + } 205 + 206 + // decodeURI() 207 + static jsval_t js_decodeURI(struct js *js, jsval_t *args, int nargs) { 208 + jsval_t result; 209 + char *out = NULL; 210 + 211 + if (nargs < 1) return js_mkstr(js, "undefined", 9); 212 + 213 + char *str = js_getstr(js, args[0], NULL); 214 + if (!str) return js_mkstr(js, "", 0); 215 + 216 + size_t len = strlen(str); 217 + out = malloc(len + 1); 218 + if (!out) return js_mkerr(js, "out of memory"); 219 + 220 + size_t out_len = 0; 221 + size_t i = 0; 222 + 223 + while (i < len) { 224 + if (str[i] != '%') { 225 + out[out_len++] = str[i++]; 226 + continue; 227 + } 228 + 229 + if (i + 2 >= len) goto malformed; 230 + 231 + int high = hex_digit(str[i + 1]); 232 + int low = hex_digit(str[i + 2]); 233 + if (high < 0 || low < 0) goto malformed; 234 + 235 + unsigned char first_byte = (unsigned char)((high << 4) | low); 236 + 237 + if (first_byte < 128 && is_uri_reserved((char)first_byte)) { 238 + out[out_len++] = str[i++]; 239 + out[out_len++] = str[i++]; 240 + out[out_len++] = str[i++]; 241 + continue; 242 + } 243 + 244 + i += 3; 245 + 246 + int seq_len = utf8_sequence_length(first_byte); 247 + if (seq_len < 0) goto malformed; 248 + 249 + out[out_len++] = (char)first_byte; 250 + 251 + for (int j = 1; j < seq_len; j++) { 252 + unsigned char cont_byte; 253 + if (decode_escape_sequence(str, len, &i, &cont_byte) < 0) goto malformed; 254 + if (!is_valid_continuation(cont_byte)) goto malformed; 255 + out[out_len++] = (char)cont_byte; 256 + } 257 + } 258 + 259 + out[out_len] = '\0'; 260 + result = js_mkstr(js, out, out_len); 261 + free(out); 262 + return result; 263 + 264 + malformed: 265 + free(out); 266 + return js_mkerr(js, "URIError: URI malformed"); 267 + } 268 + 269 + void init_uri_module(void) { 270 + struct js *js = rt->js; 271 + jsval_t glob = js_glob(js); 272 + 273 + js_set(js, glob, "encodeURI", js_mkfun(js_encodeURI)); 274 + js_set(js, glob, "encodeURIComponent", js_mkfun(js_encodeURIComponent)); 275 + js_set(js, glob, "decodeURI", js_mkfun(js_decodeURI)); 276 + js_set(js, glob, "decodeURIComponent", js_mkfun(js_decodeURIComponent)); 277 + }
+101
tests/test_uri.js
··· 1 + console.log('=== URI Encoding/Decoding Tests ===\n'); 2 + 3 + let passed = 0; 4 + let failed = 0; 5 + 6 + function test(name, actual, expected) { 7 + if (actual === expected) { 8 + console.log(`βœ“ ${name}`); 9 + passed++; 10 + } else { 11 + console.log(`βœ— ${name}`); 12 + console.log(` Expected: ${expected}`); 13 + console.log(` Actual: ${actual}`); 14 + failed++; 15 + } 16 + } 17 + 18 + function testThrows(name, fn) { 19 + try { 20 + fn(); 21 + console.log(`βœ— ${name} (expected to throw)`); 22 + failed++; 23 + } catch (e) { 24 + console.log(`βœ“ ${name} (threw)`); 25 + passed++; 26 + } 27 + } 28 + 29 + // encodeURIComponent tests 30 + console.log('\n--- encodeURIComponent ---'); 31 + test('encodes space', encodeURIComponent(' '), '%20'); 32 + test('encodes special chars', encodeURIComponent('hello world!'), 'hello%20world!'); 33 + test('preserves unreserved', encodeURIComponent('abc123'), 'abc123'); 34 + test('preserves unreserved marks', encodeURIComponent("-_.!~*'()"), "-_.!~*'()"); 35 + test('encodes reserved chars', encodeURIComponent(';/?:@&=+$,#'), '%3B%2F%3F%3A%40%26%3D%2B%24%2C%23'); 36 + test('encodes Cyrillic', encodeURIComponent('ΡˆΠ΅Π»Π»Ρ‹'), '%D1%88%D0%B5%D0%BB%D0%BB%D1%8B'); 37 + test('encodes Chinese', encodeURIComponent('δΈ­ζ–‡'), '%E4%B8%AD%E6%96%87'); 38 + test('encodes emoji', encodeURIComponent('πŸ˜€'), '%F0%9F%98%80'); 39 + test('empty string', encodeURIComponent(''), ''); 40 + 41 + // encodeURI tests 42 + console.log('\n--- encodeURI ---'); 43 + test('preserves URI structure', encodeURI('https://example.com/path?q=hello world'), 'https://example.com/path?q=hello%20world'); 44 + test('preserves reserved chars', encodeURI(';/?:@&=+$,#'), ';/?:@&=+$,#'); 45 + test('encodes space', encodeURI('hello world'), 'hello%20world'); 46 + test('encodes Cyrillic in URL', encodeURI('https://mozilla.org/?x=ΡˆΠ΅Π»Π»Ρ‹'), 'https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B'); 47 + test('empty string', encodeURI(''), ''); 48 + 49 + // decodeURIComponent tests 50 + console.log('\n--- decodeURIComponent ---'); 51 + test('decodes space', decodeURIComponent('%20'), ' '); 52 + test('decodes special chars', decodeURIComponent('hello%20world%21'), 'hello world!'); 53 + test('decodes Cyrillic', decodeURIComponent('%D1%88%D0%B5%D0%BB%D0%BB%D1%8B'), 'ΡˆΠ΅Π»Π»Ρ‹'); 54 + test('decodes Chinese', decodeURIComponent('%E4%B8%AD%E6%96%87'), 'δΈ­ζ–‡'); 55 + test('decodes emoji', decodeURIComponent('%F0%9F%98%80'), 'πŸ˜€'); 56 + test('decodes reserved chars', decodeURIComponent('%3B%2F%3F%3A%40%26%3D%2B%24%2C%23'), ';/?:@&=+$,#'); 57 + test('passes through plain text', decodeURIComponent('hello'), 'hello'); 58 + test('empty string', decodeURIComponent(''), ''); 59 + test('mixed encoded/plain', decodeURIComponent('hello%20world'), 'hello world'); 60 + 61 + // decodeURI tests 62 + console.log('\n--- decodeURI ---'); 63 + test( 64 + 'decodes URL with Cyrillic', 65 + decodeURI('https://developer.mozilla.org/ru/docs/JavaScript_%D1%88%D0%B5%D0%BB%D0%BB%D1%8B'), 66 + 'https://developer.mozilla.org/ru/docs/JavaScript_ΡˆΠ΅Π»Π»Ρ‹' 67 + ); 68 + test('preserves encoded reserved', decodeURI('https://example.com/docs/JavaScript%3A%20test'), 'https://example.com/docs/JavaScript%3A test'); 69 + test('decodes non-reserved', decodeURI('hello%20world'), 'hello world'); 70 + test('empty string', decodeURI(''), ''); 71 + 72 + // decodeURI vs decodeURIComponent comparison 73 + console.log('\n--- decodeURI vs decodeURIComponent ---'); 74 + const encoded = 'https://developer.mozilla.org/docs/JavaScript%3A%20a_scripting_language'; 75 + test('decodeURI preserves %3A', decodeURI(encoded), 'https://developer.mozilla.org/docs/JavaScript%3A a_scripting_language'); 76 + test('decodeURIComponent decodes %3A', decodeURIComponent(encoded), 'https://developer.mozilla.org/docs/JavaScript: a_scripting_language'); 77 + 78 + // Error cases 79 + console.log('\n--- Error cases ---'); 80 + testThrows('decodeURIComponent invalid sequence', () => decodeURIComponent('%E0%A4%A')); 81 + testThrows('decodeURI invalid sequence', () => decodeURI('%E0%A4%A')); 82 + testThrows('decodeURIComponent incomplete %', () => decodeURIComponent('%')); 83 + testThrows('decodeURIComponent incomplete %X', () => decodeURIComponent('%2')); 84 + testThrows('decodeURIComponent invalid hex', () => decodeURIComponent('%GG')); 85 + 86 + // Round-trip tests 87 + console.log('\n--- Round-trip tests ---'); 88 + const testStrings = ['hello world', 'foo=bar&baz=qux', 'ΡˆΠ΅Π»Π»Ρ‹', 'δΈ­ζ–‡ζ΅‹θ―•', 'emoji: πŸ˜€πŸŽ‰', 'special: !@#$%^&*()', 'path/to/file.txt']; 89 + 90 + for (const str of testStrings) { 91 + const encoded = encodeURIComponent(str); 92 + const decoded = decodeURIComponent(encoded); 93 + test(`round-trip: "${str}"`, decoded, str); 94 + } 95 + 96 + // Summary 97 + console.log('\n=== Summary ==='); 98 + console.log(`Passed: ${passed}`); 99 + console.log(`Failed: ${failed}`); 100 + 101 + if (failed > 0) process.exit(1);