helpers.ha (3512B)
1 use fs; 2 use io; 3 use os; 4 use slices; 5 use strings; 6 7 // All bundled files must be within this directory so that malicious modules 8 // cannot require arbitrary files on the file system. 9 let basepath: str = ""; 10 @fini fn fini() void = free(basepath); 11 12 // Cut a string to the last "/". 13 // Return value is borrowed from the input. 14 fn parent_dir(path: str) str = { 15 const bytes = strings::toutf8(path); 16 let i = len(bytes) - 1; 17 for (bytes[i] != '/') i -= 1; 18 return strings::fromutf8(bytes[..(i+1)]); 19 }; 20 21 // Apply os::realpath and os::resolve. 22 fn realpath_resolve(path: str) str = { 23 const p = match (os::realpath(path)) { 24 case let p: str => yield p; 25 case let p: fs::error => 26 fixed_fatalf("{}: file does not exist.", path); 27 yield ""; // unreachable 28 }; 29 return os::resolve(p); 30 }; 31 32 // path: to be resolved 33 // from: path to the file (or directory) where the reference was found. 34 // Return value has to be freed. 35 fn resolve_path(path: str, from: str) str = { 36 if (strings::hasprefix(path, "http://") || 37 strings::hasprefix(path, "https://")) { 38 fixed_fatalf("{}: bundling of external resources is not allowed.", 39 path); 40 }; 41 // directory path is relativ to base 42 // ends with "/" 43 const base = if (strings::hasprefix(path, "./") || 44 strings::hasprefix(path, "../")) { 45 yield parent_dir(from); 46 } else { 47 yield basepath; 48 }; 49 const r = strings::join("", base, path); 50 defer free(r); 51 const r = strings::dup(realpath_resolve(r)); 52 if (!strings::hasprefix(r, basepath)) 53 fixed_fatalf("{}: file path violates the base path \"{}\".", 54 r, basepath); 55 return r; 56 }; 57 58 // Works like resolve_path() but adds a .js extension if there is none 59 fn resolve_path_require(path: str, from: str) str = { 60 return if (strings::hassuffix(path, ".js")) 61 resolve_path(path, from) 62 else { 63 const p = strings::join("", path, ".js"); 64 const res = resolve_path(p, from); 65 free(p); 66 yield res; 67 }; 68 }; 69 70 // Return index of the last dot in the filename or -1 if the file contains no 71 // dot. 72 fn lastdotindex(filename: str) int = { 73 const filename = strings::toutf8(filename); 74 let index = (len(filename) - 1): int; 75 for (index >= 0 && filename[index] != '.') { 76 if (filename[index] == '/') return -1; 77 index -= 1; 78 }; 79 return index; 80 }; 81 82 // return value has to be freed. 83 fn file_name_bundled(filename: str) str = { 84 let lastdot = lastdotindex(filename); 85 // files without extension get the .bundle at the end 86 if (lastdot == -1) lastdot = len(filename): int; 87 88 const output = strings::dup(filename); 89 const output = strings::toutf8(output); 90 91 const ext = strings::toutf8(".bundle"); 92 let bptr: [7]*void = [&ext: *void ...]; 93 for (let i = 0z; i < len(ext); i += 1) { 94 bptr[i] = &ext[i]; 95 }; 96 slices::insertinto(&output: *[]void, size(u8), lastdot: size, bptr...); 97 return strings::fromutf8(output); 98 }; 99 100 @test fn file_name_bundled() void = { 101 assert(file_name_bundled("test.js") == "test.bundle.js"); 102 assert(file_name_bundled("test.dot.js") == "test.dot.bundle.js"); 103 assert(file_name_bundled("no-ext") == "no-ext.bundle"); 104 assert(file_name_bundled("./dir.a/no-ext") == "./dir.a/no-ext.bundle"); 105 assert(file_name_bundled("./test.dir/ütf8.html") == 106 "./test.dir/ütf8.bundle.html"); 107 }; 108 109 // A file to write useless output to (like /dev/null) 110 const black_hole: io::handle = &black_hole_s; 111 const black_hole_s: io::stream = &black_hole_v; 112 const black_hole_v: io::vtable = io::vtable { 113 reader = null, 114 writer = &black_hole_write, 115 ... 116 }; 117 fn black_hole_write(s: *io::stream, buf: const []u8) (size | io::error) = { 118 return len(buf); 119 };