logo

cmd-urlencode

percent-encode stdin for making URLs git clone https://anongit.hacktivis.me/git/cmd-urlencode.git
commit: 9af1893dcdd8792a67c0c63507d16398355595c7
parent d10ec7c13b7f2024b69e38c08c267762bf4a701e
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Tue, 10 Mar 2026 07:46:08 +0100

Add -d option

Diffstat:

Murlencode.16++++++
Murlencode.c133++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
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(); +}