commit 62c0f697ddc7396384280f2f8bac7a092700f1ba from: Omar Polo date: Wed Feb 21 11:57:05 2024 UTC fix handling of gopher selectors Much of telescope works in terms of URI, so we convert each gopher link to an URI. However, we didn't urlencode the path parameter, thus failing on every link that contains spaces or similar invalid (in URI paths) characters. Issue reported by hryjksn on github, thanks! https://github.com/omar-polo/telescope/issues/14 commit - 6acf1bb453dd5584d4567d939b3cc148c300f187 commit + 62c0f697ddc7396384280f2f8bac7a092700f1ba blob - ef1c974197379078a2b295149504fc5bfa103738 blob + 9a44d9597949d1e4c46f5778bb4d34da0f552753 --- iri.c +++ iri.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Omar Polo + * Copyright (c) 2022, 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -761,4 +761,76 @@ iri_setquery(struct iri *iri, const char *p) err: errno = ENOBUFS; return (-1); +} + +int +iri_urlescape(const char *path, char *buf, size_t len) +{ + const char *hex = "0123456789abcdef"; + const uint8_t *p = path; + + while (*p) { + if (len == 0) + break; + + if (unreserved(*p) || sub_delims(*p) || + *p == ':' || *p == '@' || + *p == '/') { + *buf++ = *p++; + len--; + continue; + } + + if (len < 3) + break; + *buf++ = '%'; + *buf++ = hex[*p >> 4]; + *buf++ = hex[*p & 0xf]; + len -= 3; + p++; + } + + if (len == 0 || *p) + return (-1); + + *buf = '\0'; + return (0); +} + +int +iri_urlunescape(const char *str, char *buf, size_t len) +{ + char t[3]; + unsigned long l; + + t[2] = '\0'; + + while (*str) { + if (len == 0) + return (-1); + + if (*str != '%') { + *buf++ = *str++; + len--; + continue; + } + + if (!isxdigit((unsigned char)str[1]) || + !isxdigit((unsigned char)str[2])) + return (-1); + + t[0] = str[1]; + t[1] = str[2]; + + /* we know it's a proper number and will fit a char */ + l = strtol(t, NULL, 16); + *buf++ = (unsigned char)l; + len--; + str += 3; + } + + if (len == 0) + return (-1); + *buf = '\0'; + return (0); } blob - 03ea7a8b0a497bc4ec9b9924db76cda1c03af23c blob + 164785cba484bbb4faf96b930929940ae8537fbb --- iri.h +++ iri.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Omar Polo + * Copyright (c) 2022, 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -44,4 +44,7 @@ int iri_human(const struct iri *, char *, size_t); int iri_setport(struct iri *, const char *); int iri_setquery(struct iri *, const char *); +int iri_urlescape(const char *, char *, size_t); +int iri_urlunescape(const char *, char *, size_t); + #endif /* IRI_H */ blob - efd08aea46d99986a0dcae05a99a738784ac5a27 blob + ba333328471a966bb716d45587b30befead9bf0d --- parser_gophermap.c +++ parser_gophermap.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Omar Polo + * Copyright (c) 2021, 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,6 +21,7 @@ #include #include "parser.h" +#include "iri.h" #include "utils.h" #ifndef LINE_MAX @@ -84,13 +85,28 @@ static int gm_parse(struct parser *p, const char *buf, size_t size) { return parser_foreach_line(p, buf, size, gm_foreach_line); +} + +static int +selector2uri(struct gm_selector *s, char *buf, size_t len) +{ + int r; + + r = snprintf(buf, len, "gopher://%s:%s/%c%s", + s->addr, s->port, s->type, *s->selector != '/' ? "/" : ""); + if (r < 0 || (size_t)r >= len) + return (-1); + + buf += r; + len -= r; + return (iri_urlescape(s->selector, buf, len)); } static inline int emit_line(struct parser *p, enum line_type type, struct gm_selector *s) { struct line *l; - char buf[LINE_MAX], b[2] = {0}; + char buf[LINE_MAX]; if ((l = calloc(1, sizeof(*l))) == NULL) goto err; @@ -102,18 +118,8 @@ emit_line(struct parser *p, enum line_type type, struc case LINE_LINK: if (s->type == 'h' && !strncmp(s->selector, "URL:", 4)) { strlcpy(buf, s->selector+4, sizeof(buf)); - } else { - strlcpy(buf, "gopher://", sizeof(buf)); - strlcat(buf, s->addr, sizeof(buf)); - strlcat(buf, ":", sizeof(buf)); - strlcat(buf, s->port, sizeof(buf)); - strlcat(buf, "/", sizeof(buf)); - b[0] = s->type; - strlcat(buf, b, sizeof(buf)); - if (*s->selector != '/') - strlcat(buf, "/", sizeof(buf)); - strlcat(buf, s->selector, sizeof(buf)); - } + } else if (selector2uri(s, buf, sizeof(buf)) == -1) + goto err; if ((l->alt = strdup(buf)) == NULL) goto err; blob - 4e71ecbaff97f7964d344324aed229fd8d1cf793 blob + 80420dcfbd8080e2692413ee333b8a0fb9339839 --- telescope.c +++ telescope.c @@ -676,7 +676,8 @@ load_gopher_url(struct tab *tab, const char *url) return; } - strlcpy(req.req, path, sizeof(req.req)); + if (iri_urlunescape(path, req.req, sizeof(req.req)) == -1) + strlcpy(req.req, path, sizeof(req.req)); if (tab->iri.iri_flags & IH_QUERY) { strlcat(req.req, "?", sizeof(req.req)); strlcat(req.req, tab->iri.iri_query, sizeof(req.req));