Coverage Report

Created: 2026-10-15 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/escape.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
/* Escape and unescape URL encoding in strings. The functions return a new
25
 * allocated string or NULL if an error occurred. */
26
#include "curl_setup.h"
27
28
struct Curl_easy;
29
30
#include "urldata.h"
31
#include "escape.h"
32
#include "curlx/strparse.h"
33
#include "curl_printf.h"
34
35
/* for ABI-compatibility with previous versions */
36
char *curl_escape(const char *string, int length)
37
2.48k
{
38
2.48k
  return curl_easy_escape(NULL, string, length);
39
2.48k
}
40
41
/* for ABI-compatibility with previous versions */
42
char *curl_unescape(const char *string, int length)
43
0
{
44
0
  return curl_easy_unescape(NULL, string, length, NULL);
45
0
}
46
47
#define NOPE 0xff
48
#define _OK_ 0x1f /* octet not needing encoding but not a hexadecimal
49
                     character */
50
51
static const unsigned char hextable[256] = {
52
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 00 */
53
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 08 */
54
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 10 */
55
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 18 */
56
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 20 */
57
  NOPE, NOPE, NOPE, NOPE, NOPE, _OK_, _OK_, NOPE, /* 28 */
58
  0,    1,    2,    3,    4,    5,    6,    7,    /* 30 */
59
  8,    9,    NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 38 */
60
  NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 40 */
61
  _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 48 */
62
  _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 50 */
63
  _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, NOPE, _OK_, /* 58 */
64
  NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 60 */
65
  _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 68 */
66
  _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 70 */
67
  _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, _OK_, NOPE, /* 78 */
68
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 80 - not ASCII */
69
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
70
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
71
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
72
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
73
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
74
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
75
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
76
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
77
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
78
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
79
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
80
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
81
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
82
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE,
83
  NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE
84
};
85
86
static const char hexpairs[][2] = {
87
  {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'},
88
  {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'},
89
  {'0', '8'}, {'0', '9'}, {'0', 'A'}, {'0', 'B'},
90
  {'0', 'C'}, {'0', 'D'}, {'0', 'E'}, {'0', 'F'},
91
  {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'},
92
  {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
93
  {'1', '8'}, {'1', '9'}, {'1', 'A'}, {'1', 'B'},
94
  {'1', 'C'}, {'1', 'D'}, {'1', 'E'}, {'1', 'F'},
95
  {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
96
  {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'},
97
  {'2', '8'}, {'2', '9'}, {'2', 'A'}, {'2', 'B'},
98
  {'2', 'C'}, {'2', 'D'}, {'2', 'E'}, {'2', 'F'},
99
  {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'},
100
  {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'},
101
  {'3', '8'}, {'3', '9'}, {'3', 'A'}, {'3', 'B'},
102
  {'3', 'C'}, {'3', 'D'}, {'3', 'E'}, {'3', 'F'},
103
  {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'},
104
  {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
105
  {'4', '8'}, {'4', '9'}, {'4', 'A'}, {'4', 'B'},
106
  {'4', 'C'}, {'4', 'D'}, {'4', 'E'}, {'4', 'F'},
107
  {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
108
  {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'},
109
  {'5', '8'}, {'5', '9'}, {'5', 'A'}, {'5', 'B'},
110
  {'5', 'C'}, {'5', 'D'}, {'5', 'E'}, {'5', 'F'},
111
  {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'},
112
  {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'},
113
  {'6', '8'}, {'6', '9'}, {'6', 'A'}, {'6', 'B'},
114
  {'6', 'C'}, {'6', 'D'}, {'6', 'E'}, {'6', 'F'},
115
  {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'},
116
  {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
117
  {'7', '8'}, {'7', '9'}, {'7', 'A'}, {'7', 'B'},
118
  {'7', 'C'}, {'7', 'D'}, {'7', 'E'}, {'7', 'F'},
119
  {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
120
  {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'},
121
  {'8', '8'}, {'8', '9'}, {'8', 'A'}, {'8', 'B'},
122
  {'8', 'C'}, {'8', 'D'}, {'8', 'E'}, {'8', 'F'},
123
  {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'},
124
  {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'},
125
  {'9', '8'}, {'9', '9'}, {'9', 'A'}, {'9', 'B'},
126
  {'9', 'C'}, {'9', 'D'}, {'9', 'E'}, {'9', 'F'},
127
  {'A', '0'}, {'A', '1'}, {'A', '2'}, {'A', '3'},
128
  {'A', '4'}, {'A', '5'}, {'A', '6'}, {'A', '7'},
129
  {'A', '8'}, {'A', '9'}, {'A', 'A'}, {'A', 'B'},
130
  {'A', 'C'}, {'A', 'D'}, {'A', 'E'}, {'A', 'F'},
131
  {'B', '0'}, {'B', '1'}, {'B', '2'}, {'B', '3'},
132
  {'B', '4'}, {'B', '5'}, {'B', '6'}, {'B', '7'},
133
  {'B', '8'}, {'B', '9'}, {'B', 'A'}, {'B', 'B'},
134
  {'B', 'C'}, {'B', 'D'}, {'B', 'E'}, {'B', 'F'},
135
  {'C', '0'}, {'C', '1'}, {'C', '2'}, {'C', '3'},
136
  {'C', '4'}, {'C', '5'}, {'C', '6'}, {'C', '7'},
137
  {'C', '8'}, {'C', '9'}, {'C', 'A'}, {'C', 'B'},
138
  {'C', 'C'}, {'C', 'D'}, {'C', 'E'}, {'C', 'F'},
139
  {'D', '0'}, {'D', '1'}, {'D', '2'}, {'D', '3'},
140
  {'D', '4'}, {'D', '5'}, {'D', '6'}, {'D', '7'},
141
  {'D', '8'}, {'D', '9'}, {'D', 'A'}, {'D', 'B'},
142
  {'D', 'C'}, {'D', 'D'}, {'D', 'E'}, {'D', 'F'},
143
  {'E', '0'}, {'E', '1'}, {'E', '2'}, {'E', '3'},
144
  {'E', '4'}, {'E', '5'}, {'E', '6'}, {'E', '7'},
145
  {'E', '8'}, {'E', '9'}, {'E', 'A'}, {'E', 'B'},
146
  {'E', 'C'}, {'E', 'D'}, {'E', 'E'}, {'E', 'F'},
147
  {'F', '0'}, {'F', '1'}, {'F', '2'}, {'F', '3'},
148
  {'F', '4'}, {'F', '5'}, {'F', '6'}, {'F', '7'},
149
  {'F', '8'}, {'F', '9'}, {'F', 'A'}, {'F', 'B'},
150
  {'F', 'C'}, {'F', 'D'}, {'F', 'E'}, {'F', 'F'}
151
};
152
153
/* Escapes for URL the given unescaped string of given length.
154
 * 'data' is ignored since 7.82.0.
155
 */
156
char *curl_easy_escape(CURL *curl, const char *string, int length)
157
2.53k
{
158
2.53k
  size_t len;
159
2.53k
  const char *f = string;
160
2.53k
  size_t nappend = 0;
161
2.53k
  char *target;
162
2.53k
  char *encoded;
163
2.53k
  (void)curl;
164
165
2.53k
  if(!string || (length < 0))
166
0
    return NULL;
167
168
2.53k
  len = (length ? (size_t)length : strlen(string));
169
2.53k
  if(!len)
170
2.22k
    return curlx_strdup("");
171
172
311
  if(len > SIZE_MAX / 16)
173
0
    return NULL;
174
175
311
  encoded = target = curlx_malloc((len * 3) + 1);
176
311
  if(!target)
177
1
    return NULL;
178
179
23.4k
  while(len--) {
180
23.1k
    uint8_t in = (uint8_t)*string++;
181
182
23.1k
    if(!(hextable[in] & 0x80))
183
      /* append this */
184
2.37k
      nappend++;
185
20.7k
    else {
186
20.7k
      if(nappend) {
187
536
        memcpy(target, f, nappend);
188
536
        target += nappend;
189
536
        nappend = 0;
190
536
      }
191
20.7k
      f = string;
192
      /* encode it */
193
20.7k
      target[0] = '%';
194
20.7k
      memcpy(target + 1, hexpairs[in], 2);
195
20.7k
      target += 3;
196
20.7k
    }
197
23.1k
  }
198
310
  if(nappend) {
199
144
    memcpy(target, f, nappend);
200
144
    target += nappend;
201
144
  }
202
310
  *target = '\0'; /* null-terminate */
203
204
310
  return encoded;
205
311
}
206
207
/*
208
 * Curl_urldecode() URL decodes the given string.
209
 *
210
 * Returns a pointer to a malloced string in *ostring with length given in
211
 * *olen. If length == 0, the length is assumed to be strlen(string).
212
 *
213
 * ctrl options:
214
 * - REJECT_NADA: accept everything
215
 * - REJECT_CTRL: rejects control characters (byte codes lower than 32) in
216
 *                the data
217
 * - REJECT_ZERO: rejects decoded zero bytes
218
 *
219
 * The values for the enum starts at 2, to make the assert detect legacy
220
 * invokes that used TRUE/FALSE (0 and 1).
221
 */
222
223
CURLcode Curl_urldecode(const char *string, size_t length,
224
                        char **ostring, size_t *olen,
225
                        enum urlreject ctrl)
226
435
{
227
435
  size_t alloc;
228
435
  char *ns;
229
435
  uint8_t reject_limit;
230
231
435
  DEBUGASSERT(string);
232
435
  DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
233
234
435
  alloc = (length ? length : strlen(string));
235
435
  ns = curlx_malloc(alloc + 1);
236
237
435
  if(!ns)
238
0
    return CURLE_OUT_OF_MEMORY;
239
240
  /* store output string */
241
435
  *ostring = ns;
242
243
435
  reject_limit = (ctrl == REJECT_CTRL) ? 0x20 :
244
435
    (ctrl == REJECT_ZERO) ? 1 : 0;
245
246
2.14k
  while(alloc) {
247
1.76k
    uint8_t in;
248
249
1.76k
    if(*string == '%') {
250
1.06k
      if(alloc > 2) {
251
949
        uint8_t h1 = hextable[(uint8_t)string[1]];
252
949
        uint8_t h2 = hextable[(uint8_t)string[2]];
253
949
        if(!((h1 | h2) & 0xf0)) {
254
282
          in = (uint8_t)((h1 << 4) | h2);
255
282
          string += 3;
256
282
          alloc -= 3;
257
282
          if(in < reject_limit)
258
22
            goto error;
259
260
          *ns++ = (char)in;
260
260
          continue;
261
282
        }
262
949
      }
263
780
      in = '%';
264
780
      string++;
265
780
      alloc--;
266
780
      *ns++ = (char)in;
267
780
      continue;
268
1.06k
    }
269
702
    else {
270
702
      const char *p = memchr(string + 1, '%', alloc - 1);
271
702
      size_t n = p ? (size_t)(p - string) : alloc;
272
273
702
      if(reject_limit) {
274
702
        if(reject_limit == 1) {
275
0
          if(memchr(string, 0, n))
276
0
            goto error;
277
0
        }
278
702
        else {
279
702
          size_t i;
280
3.61k
          for(i = 0; i < n; i++) {
281
2.94k
            if((uint8_t)string[i] < 0x20)
282
32
              goto error;
283
2.94k
          }
284
702
        }
285
702
      }
286
287
670
      memcpy(ns, string, n);
288
670
      ns += n;
289
670
      string += n;
290
670
      alloc -= n;
291
670
    }
292
1.76k
  }
293
381
  *ns = 0; /* terminate it */
294
295
381
  if(olen)
296
    /* store output size */
297
381
    *olen = (size_t)(ns - *ostring);
298
299
381
  return CURLE_OK;
300
54
error:
301
54
  curlx_safefree(*ostring);
302
54
  return CURLE_URL_MALFORMAT;
303
435
}
304
305
/*
306
 * Unescapes the given URL escaped string of given length. Returns a
307
 * pointer to a malloced string with length given in *olen.
308
 * If length == 0, the length is assumed to be strlen(string).
309
 * If olen == NULL, no output length is stored.
310
 * 'data' is ignored since 7.82.0.
311
 */
312
char *curl_easy_unescape(CURL *curl, const char *string, int inlength,
313
                         int *outlength)
314
0
{
315
0
  char *str = NULL;
316
0
  (void)curl;
317
0
  if(string && (inlength >= 0)) {
318
0
    size_t inputlen = (size_t)inlength;
319
0
    size_t outputlen;
320
0
    CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen,
321
0
                                  REJECT_NADA);
322
0
    if(res)
323
0
      return NULL;
324
325
0
    if(outlength) {
326
0
      if(outputlen <= (size_t)INT_MAX)
327
0
        *outlength = curlx_uztosi(outputlen);
328
0
      else
329
        /* too large to return in an int, fail! */
330
0
        curlx_safefree(str);
331
0
    }
332
0
  }
333
0
  return str;
334
0
}
335
336
/* For operating systems/environments that use different malloc/free
337
   systems for the app and for this library, we provide a free that uses
338
   the library's memory system */
339
void curl_free(void *p)
340
129k
{
341
129k
  curlx_free(p);
342
129k
}
343
344
/*
345
 * Curl_hexencode()
346
 *
347
 * Converts binary input to lowercase hex-encoded ASCII output.
348
 * null-terminated.
349
 */
350
void Curl_hexencode(const unsigned char *src, size_t len, /* input length */
351
                    unsigned char *out, size_t olen) /* output buffer size */
352
7.51k
{
353
7.51k
  DEBUGASSERT(src && len && (olen >= 3));
354
7.51k
  if(src && len && (olen >= 3)) {
355
247k
    while(len-- && (olen >= 3)) {
356
240k
      out[0] = Curl_ldigits[*src >> 4];
357
240k
      out[1] = Curl_ldigits[*src & 0x0F];
358
240k
      ++src;
359
240k
      out += 2;
360
240k
      olen -= 2;
361
240k
    }
362
7.51k
    *out = 0;
363
7.51k
  }
364
0
  else if(olen)
365
0
    *out = 0;
366
7.51k
}
367
368
/* Curl_hexbyte
369
 *
370
 * Output a single unsigned char as a two-digit UPPERCASE hex number.
371
 */
372
void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */
373
                  unsigned char val)
374
49.4M
{
375
49.4M
  dest[0] = Curl_udigits[val >> 4];
376
49.4M
  dest[1] = Curl_udigits[val & 0x0F];
377
49.4M
}