commit: 9af1893dcdd8792a67c0c63507d16398355595c7
parent d10ec7c13b7f2024b69e38c08c267762bf4a701e
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Tue, 10 Mar 2026 07:46:08 +0100
Add -d option
Diffstat:
| M | urlencode.1 | 6 | ++++++ |
| M | urlencode.c | 133 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
2 files changed, 134 insertions(+), 5 deletions(-)
diff --git a/urlencode.1 b/urlencode.1
@@ -9,10 +9,16 @@
.Nd percent-encode stdin for making URLs
.Sh SYNOPSIS
.Nm
+.Op Fl d
.Sh DESCRIPTION
.Nm
reads bytes from standard input, prints it as-is if unreserved,
and otherwise percent encodes it.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl d
+Decode percent-encoded bytes instead of encoding them.
+.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
diff --git a/urlencode.c b/urlencode.c
@@ -6,9 +6,56 @@
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
+#include <stdlib.h>
-int
-main(void)
+static int
+isxdigit(int c)
+{
+ return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
+}
+
+static int
+isalnum(int c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
+}
+
+static uint8_t
+hexdec(int in[static 2])
+{
+ uint8_t out = 0;
+
+ for(int i = 0; i < 2; i++)
+ {
+ int h = in[i];
+ out *= 0x10;
+
+ if(h >= 'A' && h <= 'F')
+ {
+ out += 10 + (h - 'A');
+ continue;
+ }
+
+ if(h >= 'a' && h <= 'f')
+ {
+ out += 10 + (h - 'a');
+ continue;
+ }
+
+ if(h >= '0' && h <= '9')
+ {
+ out += h - '0';
+ continue;
+ }
+
+ abort(); // Caller should have pre-validated input with isxdigit()
+ }
+
+ return out;
+}
+
+static int
+urlencode(void)
{
for(;;)
{
@@ -30,9 +77,7 @@ main(void)
/* A-Za-z0-9[\-._~] */
if(
- (c >= 'A' && c <= 'Z') ||
- (c >= 'a' && c <= 'z') ||
- (c >= '0' && c <= '9') ||
+ isalnum(c) ||
c == '-' || c == '.' || c == '_' || c == '~' ||
c == '\n'
)
@@ -55,3 +100,81 @@ main(void)
return 0;
}
+
+static int
+urldecode(void)
+{
+ for(;;)
+ {
+ errno = 0;
+ int c = fgetc(stdin);
+ if(c == EOF)
+ {
+ if(errno == 0) break;
+
+ perror("urlencode: error: Failed reading from stdin");
+ return 1;
+ }
+
+ if(c == '%')
+ {
+ int h[2];
+
+ for(int i = 0; i < 2; i++)
+ {
+ h[i] = fgetc(stdin);
+ if(h[i] == EOF)
+ {
+ if(errno == 0) break;
+
+ perror("urlencode: error: Failed reading from stdin");
+ return 1;
+ }
+ if(!isxdigit(h[i]))
+ {
+ fprintf(stderr, "urlencode: error: Invalid character after %% '%c', expected A-Fa-f0-9\n", h[i]);
+ return 1;
+ }
+ }
+
+ fputc(hexdec(h), stdout);
+
+ continue;
+ }
+
+ fputc(c, stdout);
+
+ if(c == '\n')
+ fflush(stdout);
+ }
+
+ fflush(stdout);
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int (*func)(void) = &urlencode;
+
+ for(int c = -1; (c = getopt(argc, argv, ":d")) != -1;)
+ {
+ switch(c)
+ {
+ case 'd':
+ func = &urldecode;
+ break;
+ case '?':
+ fprintf(stderr, "urlencode: error: Unknown option -%c\n", optopt);
+ return 1;
+ case ':':
+ fprintf(stderr, "urlencode: error: Missing option-argument for option -%c\n", optopt);
+ return 1;
+ default:
+ abort();
+ }
+ }
+
+ return func();
+}