Last active
August 1, 2025 02:11
-
-
Save skeeto/0fe0e5b57d2c4d506b7a8d1c3ac9492a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#define S(s) (Str){s, sizeof(s)-1} | |
#define new(a, n, t) (t *)alloc(a, n, sizeof(t), _Alignof(t)) | |
typedef struct { | |
char *beg; | |
char *end; | |
} Arena; | |
static void *alloc(Arena *a, ptrdiff_t count, ptrdiff_t size, ptrdiff_t align) | |
{ | |
ptrdiff_t pad = -(uintptr_t)a->beg & (align - 1); | |
assert(count < (a->end - a->beg - pad)/size); // TODO: OOM handler | |
char *r = a->beg + pad; | |
a->beg += pad + count*size; | |
return memset(r, 0, count*size); | |
} | |
typedef struct { | |
char *data; | |
ptrdiff_t len; | |
} Str; | |
static bool equals(Str a, Str b) | |
{ | |
return a.len==b.len && !memcmp(a.data, b.data, a.len); | |
} | |
static Str takehead(Str s, ptrdiff_t i) | |
{ | |
assert(i <= s.len); | |
s.len = i; | |
return s; | |
} | |
static Str drophead(Str s, ptrdiff_t i) | |
{ | |
assert(i <= s.len); | |
s.data += i; | |
s.len -= i; | |
return s; | |
} | |
static Str clone(Arena *a, Str s) | |
{ | |
Str r = s; | |
r.data = new(a, s.len, char); | |
__builtin_memcpy(r.data, s.data, r.len); | |
return r; | |
} | |
static Str concat(Arena *a, Str head, Str tail) | |
{ | |
if (head.data+head.len != a->beg) { | |
head = clone(a, head); | |
} | |
head.len += clone(a, tail).len; | |
return head; | |
} | |
typedef struct { | |
Str head; | |
Str tail; | |
bool ok; | |
} Cut; | |
static Cut cut(Str s, char c) | |
{ | |
ptrdiff_t i = 0; | |
for (; i<s.len && s.data[i]!=c; i++) {} | |
Cut r = {}; | |
r.ok = i < s.len; | |
r.head = takehead(s, i); | |
r.tail = drophead(s, i+r.ok); | |
return r; | |
} | |
static bool isvalidpath_strict(Str path) | |
{ | |
if (!path.len || path.data[0]!='/') { | |
return false; | |
} | |
for (Cut c = {.tail = drophead(path, 1)}; c.tail.len;) { | |
c = cut(c.tail, '/'); | |
Str seg = c.head; | |
if (!seg.len || equals(seg, S(".")) || equals(seg, S(".."))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
static bool isvalidpath(Str path) | |
{ | |
if (!path.len || path.data[0]!='/') { | |
return false; | |
} | |
ptrdiff_t depth = 0; | |
for (Cut c = {.tail = drophead(path, 1)}; c.tail.len;) { | |
c = cut(c.tail, '/'); | |
Str seg = c.head; | |
if (equals(seg, S(".."))) { | |
if (--depth < 0) { | |
return false; | |
} | |
} else if (equals(seg, S("")) || equals(seg, S("."))) { | |
// do not count | |
} else { | |
depth++; | |
} | |
} | |
return true; | |
} | |
typedef struct { | |
Str *data; | |
ptrdiff_t len; | |
ptrdiff_t cap; | |
} Strs; | |
static Strs split(Str s, char delim, Arena *a) | |
{ | |
Strs r = {}; | |
for (Cut c = {.tail = s, .ok = true}; c.ok;) { | |
c = cut(c.tail, delim); | |
r.cap++; | |
} | |
r.data = new(a, r.cap, Str); | |
for (Cut c = {.tail = s, .ok = true}; c.ok;) { | |
c = cut(c.tail, delim); | |
r.data[r.len++] = c.head; | |
} | |
return r; | |
} | |
static Str resolvepath(Str path, Arena *a) | |
{ | |
Str r = {}; | |
Strs segs = split(path, '/', a); | |
if (segs.data[0].len) { | |
return r; // invalid (no starting /) | |
} | |
ptrdiff_t len = 0; | |
for (ptrdiff_t i = 0; i < segs.len; i++) { | |
if (!segs.data[i].len || equals(segs.data[i], S("."))) { | |
// skip | |
} else if (equals(segs.data[i], S(".."))) { | |
if (--len < 0) { | |
return r; // invalid (traversed above root) | |
} | |
} else { | |
segs.data[len++] = segs.data[i]; // keep | |
} | |
} | |
// Construct the new path | |
for (ptrdiff_t i = 0; i < len; i++) { | |
r = concat(a, r, S("/")); | |
r = concat(a, r, segs.data[i]); | |
} | |
return r; | |
} | |
int main(int argc, char **argv) | |
{ | |
int cap = 1<<21; | |
char *mem = malloc(cap); | |
Arena a = {mem, mem+cap}; | |
Str path = S("/foo/bar/../baz/index.html"); | |
Str resolved = resolvepath(path, &a); | |
printf("%.*s\n", (int)resolved.len, resolved.data); | |
assert(!isvalidpath_strict(path)); | |
assert( isvalidpath(path)); | |
assert(!isvalidpath(S("/a/b/../../../c"))); | |
assert(!isvalidpath(S("/a/b/./../../../c"))); | |
assert(!isvalidpath(S("/a/b//../../../c"))); | |
for (int i = 1; i < argc; i++) { | |
Arena scratch = a; | |
Str path = {argv[i], strlen(argv[i])}; | |
Str resolved = resolvepath(path, &scratch); | |
printf("%s -> %.*s\n", argv[i], (int)resolved.len, resolved.data); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment