| 1 | http://git.busybox.net/busybox/commit/?id=dd1061b6a79b0161597799e825bfefc27993ace5 |
| 2 | |
| 3 | From dd1061b6a79b0161597799e825bfefc27993ace5 Mon Sep 17 00:00:00 2001 |
| 4 | From: Denys Vlasenko <vda.linux@googlemail.com> |
| 5 | Date: Sun, 11 Sep 2011 21:04:02 +0200 |
| 6 | Subject: [PATCH] wget: URL-decode user:password before base64-encoding it into auth hdr. Closes 3625. |
| 7 | |
| 8 | function old new delta |
| 9 | percent_decode_in_place - 152 +152 |
| 10 | parse_url 304 317 +13 |
| 11 | handle_incoming_and_exit 2795 2798 +3 |
| 12 | httpd_main 763 760 -3 |
| 13 | decodeString 152 - -152 |
| 14 | ------------------------------------------------------------------------------ |
| 15 | (add/remove: 2/1 grow/shrink: 2/1 up/down: 168/-155) Total: 13 bytes |
| 16 | |
| 17 | Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com> |
| 18 | |
| 19 | --- a/include/libbb.h |
| 20 | +++ b/include/libbb.h |
| 21 | @@ -1570,6 +1570,15 @@ int starts_with_cpu(const char *str) FAS |
| 22 | unsigned get_cpu_count(void) FAST_FUNC; |
| 23 | |
| 24 | |
| 25 | +/* Use strict=1 if you process input from untrusted source: |
| 26 | + * it will return NULL on invalid %xx (bad hex chars) |
| 27 | + * and str + 1 if decoded char is / or NUL. |
| 28 | + * In non-strict mode, it always succeeds (returns str), |
| 29 | + * and also it additionally decoded '+' to space. |
| 30 | + */ |
| 31 | +char *percent_decode_in_place(char *str, int strict) FAST_FUNC; |
| 32 | + |
| 33 | + |
| 34 | extern const char bb_uuenc_tbl_base64[]; |
| 35 | extern const char bb_uuenc_tbl_std[]; |
| 36 | void bb_uuencode(char *store, const void *s, int length, const char *tbl) FAST_FUNC; |
| 37 | --- /dev/null |
| 38 | +++ b/libbb/percent_decode.c |
| 39 | @@ -0,0 +1,69 @@ |
| 40 | +/* vi: set sw=4 ts=4: */ |
| 41 | +/* |
| 42 | + * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| 43 | + */ |
| 44 | + |
| 45 | +//kbuild:lib-y += percent_decode.o |
| 46 | + |
| 47 | +#include "libbb.h" |
| 48 | + |
| 49 | +static unsigned hex_to_bin(unsigned char c) |
| 50 | +{ |
| 51 | + unsigned v; |
| 52 | + |
| 53 | + v = c - '0'; |
| 54 | + if (v <= 9) |
| 55 | + return v; |
| 56 | + /* c | 0x20: letters to lower case, non-letters |
| 57 | + * to (potentially different) non-letters */ |
| 58 | + v = (unsigned)(c | 0x20) - 'a'; |
| 59 | + if (v <= 5) |
| 60 | + return v + 10; |
| 61 | + return ~0; |
| 62 | +/* For testing: |
| 63 | +void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); } |
| 64 | +int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f'); |
| 65 | +t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; } |
| 66 | +*/ |
| 67 | +} |
| 68 | + |
| 69 | +char* FAST_FUNC percent_decode_in_place(char *str, int strict) |
| 70 | +{ |
| 71 | + /* note that decoded string is always shorter than original */ |
| 72 | + char *src = str; |
| 73 | + char *dst = str; |
| 74 | + char c; |
| 75 | + |
| 76 | + while ((c = *src++) != '\0') { |
| 77 | + unsigned v; |
| 78 | + |
| 79 | + if (!strict && c == '+') { |
| 80 | + *dst++ = ' '; |
| 81 | + continue; |
| 82 | + } |
| 83 | + if (c != '%') { |
| 84 | + *dst++ = c; |
| 85 | + continue; |
| 86 | + } |
| 87 | + v = hex_to_bin(src[0]); |
| 88 | + if (v > 15) { |
| 89 | + bad_hex: |
| 90 | + if (strict) |
| 91 | + return NULL; |
| 92 | + *dst++ = '%'; |
| 93 | + continue; |
| 94 | + } |
| 95 | + v = (v * 16) | hex_to_bin(src[1]); |
| 96 | + if (v > 255) |
| 97 | + goto bad_hex; |
| 98 | + if (strict && (v == '/' || v == '\0')) { |
| 99 | + /* caller takes it as indication of invalid |
| 100 | + * (dangerous wrt exploits) chars */ |
| 101 | + return str + 1; |
| 102 | + } |
| 103 | + *dst++ = v; |
| 104 | + src += 2; |
| 105 | + } |
| 106 | + *dst = '\0'; |
| 107 | + return str; |
| 108 | +} |
| 109 | --- a/networking/httpd.c |
| 110 | +++ b/networking/httpd.c |
| 111 | @@ -820,78 +820,6 @@ static char *encodeString(const char *st |
| 112 | } |
| 113 | #endif |
| 114 | |
| 115 | -/* |
| 116 | - * Given a URL encoded string, convert it to plain ascii. |
| 117 | - * Since decoding always makes strings smaller, the decode is done in-place. |
| 118 | - * Thus, callers should xstrdup() the argument if they do not want the |
| 119 | - * argument modified. The return is the original pointer, allowing this |
| 120 | - * function to be easily used as arguments to other functions. |
| 121 | - * |
| 122 | - * string The first string to decode. |
| 123 | - * option_d 1 if called for httpd -d |
| 124 | - * |
| 125 | - * Returns a pointer to the decoded string (same as input). |
| 126 | - */ |
| 127 | -static unsigned hex_to_bin(unsigned char c) |
| 128 | -{ |
| 129 | - unsigned v; |
| 130 | - |
| 131 | - v = c - '0'; |
| 132 | - if (v <= 9) |
| 133 | - return v; |
| 134 | - /* c | 0x20: letters to lower case, non-letters |
| 135 | - * to (potentially different) non-letters */ |
| 136 | - v = (unsigned)(c | 0x20) - 'a'; |
| 137 | - if (v <= 5) |
| 138 | - return v + 10; |
| 139 | - return ~0; |
| 140 | -/* For testing: |
| 141 | -void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); } |
| 142 | -int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f'); |
| 143 | -t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; } |
| 144 | -*/ |
| 145 | -} |
| 146 | -static char *decodeString(char *orig, int option_d) |
| 147 | -{ |
| 148 | - /* note that decoded string is always shorter than original */ |
| 149 | - char *string = orig; |
| 150 | - char *ptr = string; |
| 151 | - char c; |
| 152 | - |
| 153 | - while ((c = *ptr++) != '\0') { |
| 154 | - unsigned v; |
| 155 | - |
| 156 | - if (option_d && c == '+') { |
| 157 | - *string++ = ' '; |
| 158 | - continue; |
| 159 | - } |
| 160 | - if (c != '%') { |
| 161 | - *string++ = c; |
| 162 | - continue; |
| 163 | - } |
| 164 | - v = hex_to_bin(ptr[0]); |
| 165 | - if (v > 15) { |
| 166 | - bad_hex: |
| 167 | - if (!option_d) |
| 168 | - return NULL; |
| 169 | - *string++ = '%'; |
| 170 | - continue; |
| 171 | - } |
| 172 | - v = (v * 16) | hex_to_bin(ptr[1]); |
| 173 | - if (v > 255) |
| 174 | - goto bad_hex; |
| 175 | - if (!option_d && (v == '/' || v == '\0')) { |
| 176 | - /* caller takes it as indication of invalid |
| 177 | - * (dangerous wrt exploits) chars */ |
| 178 | - return orig + 1; |
| 179 | - } |
| 180 | - *string++ = v; |
| 181 | - ptr += 2; |
| 182 | - } |
| 183 | - *string = '\0'; |
| 184 | - return orig; |
| 185 | -} |
| 186 | - |
| 187 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
| 188 | /* |
| 189 | * Decode a base64 data stream as per rfc1521. |
| 190 | @@ -1949,7 +1877,7 @@ static void handle_incoming_and_exit(con |
| 191 | } |
| 192 | |
| 193 | /* Decode URL escape sequences */ |
| 194 | - tptr = decodeString(urlcopy, 0); |
| 195 | + tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1); |
| 196 | if (tptr == NULL) |
| 197 | send_headers_and_exit(HTTP_BAD_REQUEST); |
| 198 | if (tptr == urlcopy + 1) { |
| 199 | @@ -2408,7 +2336,7 @@ int httpd_main(int argc UNUSED_PARAM, ch |
| 200 | , &verbose |
| 201 | ); |
| 202 | if (opt & OPT_DECODE_URL) { |
| 203 | - fputs(decodeString(url_for_decode, 1), stdout); |
| 204 | + fputs(percent_decode_in_place(url_for_decode, /*strict:*/ 0), stdout); |
| 205 | return 0; |
| 206 | } |
| 207 | #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR |
| 208 | --- a/networking/wget.c |
| 209 | +++ b/networking/wget.c |
| 210 | @@ -298,8 +298,13 @@ static void parse_url(const char *src_ur |
| 211 | |
| 212 | sp = strrchr(h->host, '@'); |
| 213 | if (sp != NULL) { |
| 214 | - h->user = h->host; |
| 215 | + // URL-decode "user:password" string before base64-encoding: |
| 216 | + // wget http://test:my%20pass@example.com should send |
| 217 | + // Authorization: Basic dGVzdDpteSBwYXNz |
| 218 | + // which decodes to "test:my pass". |
| 219 | + // Standard wget and curl do this too. |
| 220 | *sp = '\0'; |
| 221 | + h->user = percent_decode_in_place(h->host, /*strict:*/ 0); |
| 222 | h->host = sp + 1; |
| 223 | } |
| 224 | |
| 225 | @@ -661,12 +666,6 @@ static void download_one_url(const char |
| 226 | |
| 227 | #if ENABLE_FEATURE_WGET_AUTHENTICATION |
| 228 | if (target.user) { |
| 229 | -//TODO: URL-decode "user:password" string before base64-encoding: |
| 230 | -//wget http://test:my%20pass@example.com should send |
| 231 | -// Authorization: Basic dGVzdDpteSBwYXNz |
| 232 | -//which decodes to "test:my pass", instead of what we send now: |
| 233 | -// Authorization: Basic dGVzdDpteSUyMHBhc3M= |
| 234 | -//Can reuse decodeString() from httpd.c |
| 235 | fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6, |
| 236 | base64enc(target.user)); |
| 237 | } |
| 238 | |