/* * Copyright (C) 2015, 2016 "IoT.bzh" * Author: José Bollo <jose.bollo@iot.bzh> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include <stdint.h> #include <stdlib.h> #include <string.h> /* * Test if 'c' is to be escaped or not. * Any character that is not in [-.0-9A-Z_a-z~] * must be escaped. * Note that space versus + is not managed here. */ static inline int should_escape(char c) { /* ASCII CODES OF UNESCAPED CHARS car hx/oct idx '-' 2d/055 1 '.' 2e/056 2 '0' 30/060 3 ... .. '9' 39/071 12 'A' 41/101 13 ... .. 'Z' 5a/132 38 '_' 5f/137 39 'a' 61/141 40 ... .. 'z' 7a/172 65 '~' 7e/176 66 */ /* [-.0-9A-Z_a-z~] */ if (c <= 'Z') { /* [-.0-9A-Z] */ if (c < '0') { /* [-.] */ return c != '-' && c != '.'; } else { /* [0-9A-Z] */ return c < 'A' && c > '9'; } } else { /* [_a-z~] */ if (c <= 'z') { /* [_a-z] */ return c < 'a' && c != '_'; } else { /* [~] */ return c != '~'; } } } /* * returns the ASCII char for the hexadecimal * digit of the binary value 'f'. * returns 0 if f isn't in [0 ... 15]. */ static inline char bin2hex(int f) { if ((f & 15) != f) f = 0; else if (f < 10) f += '0'; else f += 'A' - 10; return (char)f; } /* * returns the binary value for the hexadecimal * digit whose char is 'c'. * returns -1 if c isn't i n[0-9A-Fa-f] */ static inline int hex2bin(char c) { /* [0-9A-Fa-f] */ if (c <= 'F') { /* [0-9A-F] */ if (c <= '9') { /* [0-9] */ if (c >= '0') { return (int)(c - '0'); } } else if (c >= 'A') { /* [A-F] */ return (int)(c - ('A' - 10)); } } else { /* [a-f] */ if (c >= 'a' && c <= 'f') { return (int)(c - ('a' - 10)); } } return -1; } /* * returns the length that will have the text 'itext' of length * 'ilen' when escaped. When 'ilen' == 0, strlen is used to * compute the size of 'itext'. */ static size_t escaped_length(const char *itext, size_t ilen) { char c; size_t i, r; if (!ilen) ilen = strlen(itext); c = itext[i = r = 0]; while (i < ilen) { r += c != ' ' && should_escape(c) ? 3 : 1; c = itext[++i]; } return r; } /* * Escapes the text 'itext' of length 'ilen'. * When 'ilen' == 0, strlen is used to compute the size of 'itext'. * The escaped text is put in 'otext' of length 'olen'. * Returns the length of the escaped text (it can be greater than 'olen'). * When 'olen' is greater than the needed length, an extra null terminator * is appened to the escaped string. */ static size_t escape_to(const char *itext, size_t ilen, char *otext, size_t olen) { char c; size_t i, r; if (!ilen) ilen = strlen(itext); c = itext[i = r = 0]; while (i < ilen) { if (c == ' ') c = '+'; else if (should_escape(c)) { if (r < olen) otext[r] = '%'; r++; if (r < olen) otext[r] = bin2hex((c >> 4) & 15); r++; c = bin2hex(c & 15); } if (r < olen) otext[r] = c; r++; c = itext[++i]; } if (r < olen) otext[r] = 0; return r; } /* * returns the length of 'itext' of length 'ilen' that can be unescaped. * compute the size of 'itext' when 'ilen' == 0. */ static size_t unescapable_length(const char *itext, size_t ilen) { char c; size_t i; c = itext[i = 0]; while (i < ilen) { if (c != '%') i++; else { if (i + 3 > ilen || hex2bin(itext[i + 1]) < 0 || hex2bin(itext[i + 2]) < 0) break; i += 3; } c = itext[i]; } return i; } /* * returns the length that will have the text 'itext' of length * 'ilen' when escaped. When 'ilen' == 0, strlen is used to * compute the size of 'itext'. */ static size_t unescaped_length(const char *itext, size_t ilen) { char c; size_t i, r; c = itext[i = r = 0]; while (i < ilen) { i += (size_t)(1 + ((c == '%') << 1)); r++; c = itext[i]; } return r; } static size_t unescape_to(const char *itext, size_t ilen, char *otext, size_t olen) { char c; size_t i, r; int h, l; ilen = unescapable_length(itext, ilen); c = itext[i = r = 0]; while (i < ilen) { if (c != '%') { if (c == '+') c = ' '; i++; } else { if (i + 2 >= ilen) break; h = hex2bin(itext[i + 1]); l = hex2bin(itext[i + 2]); c = (char)((h << 4) | l); i += 3; } if (r < olen) otext[r] = c; r++; c = itext[i]; } if (r < olen) otext[r] = 0; return r; } /* create an url */ char *escape_url(const char *base, const char *path, const char * const *args, size_t *length) { int i; size_t lb, lp, lq, l, L; const char *null; char *result; /* ensure args */ if (!args) { null = NULL; args = &null; } /* compute lengths */ lb = base ? strlen(base) : 0; lp = path ? strlen(path) : 0; lq = 0; i = 0; while (args[i]) { lq += 1 + escaped_length(args[i], strlen(args[i])); i++; if (args[i]) lq += 1 + escaped_length(args[i], strlen(args[i])); i++; } /* allocation */ L = lb + lp + lq + 1; result = malloc(L + 1); if (result) { /* make the resulting url */ l = lb; if (lb) { memcpy(result, base, lb); if (result[l - 1] != '/' && path && path[0] != '/') result[l++] = '/'; } if (lp) { memcpy(result + l, path, lp); l += lp; } i = 0; while (args[i]) { if (i) { result[l++] = '&'; } else if (base || path) { result[l] = memchr(result, '?', l) ? '&' : '?'; l++; } l += escape_to(args[i], strlen(args[i]), result + l, L - l); i++; if (args[i]) { result[l++] = '='; l += escape_to(args[i], strlen(args[i]), result + l, L - l); } i++; } result[l] = 0; if (length) *length = l; } return result; } char *escape_args(const char * const *args, size_t *length) { return escape_url(NULL, NULL, args, length); } const char *escape_str(const char* str, size_t *length) { const char *a[2] = { str, NULL }; return escape_args(a, length); } const char **unescape_args(const char *args) { const char **r, **q; char c, *p; size_t j, z, l, n, lt; lt = n = 0; if (args[0]) { z = 0; do { l = strcspn(&args[z], "&="); j = 1 + unescaped_length(&args[z], l); lt += j; z += l; c = args[z++]; if (c == '=') { l = strcspn(&args[z], "&"); j = 1 + unescaped_length(&args[z], l); lt += j; z += l; c = args[z++]; } n++; } while(c); } l = lt + (2 * n + 1) * sizeof(char *); r = malloc(l); if (!r) return r; q = r; p = (void*)&r[2 * n + 1]; if (args[0]) { z = 0; do { q[0] = p; l = strcspn(&args[z], "&="); j = 1 + unescape_to(&args[z], l, p, lt); lt -= j; p += j; z += l; c = args[z++]; if (c != '=') q[1] = NULL; else { q[1] = p; l = strcspn(&args[z], "&"); j = 1 + unescape_to(&args[z], l, p, lt); lt -= j; p += j; z += l; c = args[z++]; } q = &q[2]; } while(c); } q[0] = NULL; return r; } char *escape(const char *text, size_t textlen, size_t *reslength) { size_t len; char *result; len = 1 + escaped_length(text, textlen); result = malloc(len); if (result) escape_to(text, textlen, result, len); if (reslength) *reslength = len - 1; return result; } char *unescape(const char *text, size_t textlen, size_t *reslength) { size_t len; char *result; len = 1 + unescaped_length(text, textlen); result = malloc(len); if (result) unescape_to(text, textlen, result, len); if (reslength) *reslength = len - 1; return result; } #if 0 #include <stdio.h> int main(int ac, char **av) { int i; char *x = escape_args((void*)++av, NULL); char *y = escape(x, strlen(x), NULL); char *z = unescape(y, strlen(y), NULL); const char **v = unescape_args(x); printf("%s\n%s\n%s\n", x, y, z); free(x); free(y); free(z); i = 0; while(v[i]) { printf("%s=%s / %s=%s\n", av[i], av[i+1], v[i], v[i+1]); i += 2; } free(v); return 0; } #endif /* vim: set colorcolumn=80: */