Coverage Report

Created: 2026-09-14 07:05

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
0
{
38
0
  return curl_easy_escape(NULL, string, length);
39
0
}
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
/* Escapes for URL the given unescaped string of given length.
87
 * 'data' is ignored since 7.82.0.
88
 */
89
char *curl_easy_escape(CURL *curl, const char *string, int length)
90
0
{
91
0
  size_t len;
92
0
  const char *f = string;
93
0
  size_t nappend = 0;
94
0
  char *target;
95
0
  char *encoded;
96
0
  (void)curl;
97
98
0
  if(!string || (length < 0))
99
0
    return NULL;
100
101
0
  len = (length ? (size_t)length : strlen(string));
102
0
  if(!len)
103
0
    return curlx_strdup("");
104
105
0
  if(len > SIZE_MAX / 16)
106
0
    return NULL;
107
108
0
  encoded = target = curlx_malloc((len * 3) + 1);
109
0
  if(!target)
110
0
    return NULL;
111
112
0
  while(len--) {
113
0
    uint8_t in = (uint8_t)*string++;
114
115
0
    if(!(hextable[in] & 0x80))
116
      /* append this */
117
0
      nappend++;
118
0
    else {
119
0
      if(nappend) {
120
0
        memcpy(target, f, nappend);
121
0
        target += nappend;
122
0
        nappend = 0;
123
0
      }
124
0
      f = string;
125
      /* encode it */
126
0
      target[0] = '%';
127
0
      target[1] = Curl_udigits[in >> 4];
128
0
      target[2] = Curl_udigits[in & 0x0F];
129
0
      target += 3;
130
0
    }
131
0
  }
132
0
  if(nappend) {
133
0
    memcpy(target, f, nappend);
134
0
    target += nappend;
135
0
  }
136
0
  *target = '\0'; /* null terminate */
137
138
0
  return encoded;
139
0
}
140
141
/*
142
 * Curl_urldecode() URL decodes the given string.
143
 *
144
 * Returns a pointer to a malloced string in *ostring with length given in
145
 * *olen. If length == 0, the length is assumed to be strlen(string).
146
 *
147
 * ctrl options:
148
 * - REJECT_NADA: accept everything
149
 * - REJECT_CTRL: rejects control characters (byte codes lower than 32) in
150
 *                the data
151
 * - REJECT_ZERO: rejects decoded zero bytes
152
 *
153
 * The values for the enum starts at 2, to make the assert detect legacy
154
 * invokes that used TRUE/FALSE (0 and 1).
155
 */
156
157
CURLcode Curl_urldecode(const char *string, size_t length,
158
                        char **ostring, size_t *olen,
159
                        enum urlreject ctrl)
160
899
{
161
899
  size_t alloc;
162
899
  char *ns;
163
899
  uint8_t reject_limit;
164
165
899
  DEBUGASSERT(string);
166
899
  DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
167
168
899
  alloc = (length ? length : strlen(string));
169
899
  ns = curlx_malloc(alloc + 1);
170
171
899
  if(!ns)
172
0
    return CURLE_OUT_OF_MEMORY;
173
174
  /* store output string */
175
899
  *ostring = ns;
176
177
899
  reject_limit = (ctrl == REJECT_CTRL) ? 0x20 :
178
899
    (ctrl == REJECT_ZERO) ? 1 : 0;
179
180
18.4k
  while(alloc) {
181
17.5k
    uint8_t in;
182
183
17.5k
    if(*string == '%') {
184
13.6k
      if(alloc > 2) {
185
13.5k
        uint8_t h1 = hextable[(uint8_t)string[1]];
186
13.5k
        uint8_t h2 = hextable[(uint8_t)string[2]];
187
13.5k
        if(!((h1 | h2) & 0xf0)) {
188
1.13k
          in = (uint8_t)((h1 << 4) | h2);
189
1.13k
          string += 3;
190
1.13k
          alloc -= 3;
191
1.13k
          if(in < reject_limit)
192
7
            goto error;
193
1.12k
          *ns++ = (char)in;
194
1.12k
          continue;
195
1.13k
        }
196
13.5k
      }
197
12.4k
      in = '%';
198
12.4k
      string++;
199
12.4k
      alloc--;
200
12.4k
      *ns++ = (char)in;
201
12.4k
      continue;
202
13.6k
    }
203
3.94k
    else {
204
3.94k
      const char *p = memchr(string + 1, '%', alloc - 1);
205
3.94k
      size_t n = p ? (size_t)(p - string) : alloc;
206
207
3.94k
      if(reject_limit) {
208
3.94k
        if(reject_limit == 1) {
209
2.77k
          if(memchr(string, 0, n))
210
0
            goto error;
211
2.77k
        }
212
1.16k
        else {
213
1.16k
          size_t i;
214
28.9k
          for(i = 0; i < n; i++) {
215
27.8k
            if((uint8_t)string[i] < 0x20)
216
0
              goto error;
217
27.8k
          }
218
1.16k
        }
219
3.94k
      }
220
221
3.94k
      memcpy(ns, string, n);
222
3.94k
      ns += n;
223
3.94k
      string += n;
224
3.94k
      alloc -= n;
225
3.94k
    }
226
17.5k
  }
227
892
  *ns = 0; /* terminate it */
228
229
892
  if(olen)
230
    /* store output size */
231
297
    *olen = (size_t)(ns - *ostring);
232
233
892
  return CURLE_OK;
234
7
error:
235
7
  curlx_safefree(*ostring);
236
7
  return CURLE_URL_MALFORMAT;
237
899
}
238
239
/*
240
 * Unescapes the given URL escaped string of given length. Returns a
241
 * pointer to a malloced string with length given in *olen.
242
 * If length == 0, the length is assumed to be strlen(string).
243
 * If olen == NULL, no output length is stored.
244
 * 'data' is ignored since 7.82.0.
245
 */
246
char *curl_easy_unescape(CURL *curl, const char *string, int inlength,
247
                         int *outlength)
248
0
{
249
0
  char *str = NULL;
250
0
  (void)curl;
251
0
  if(string && (inlength >= 0)) {
252
0
    size_t inputlen = (size_t)inlength;
253
0
    size_t outputlen;
254
0
    CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen,
255
0
                                  REJECT_NADA);
256
0
    if(res)
257
0
      return NULL;
258
259
0
    if(outlength) {
260
0
      if(outputlen <= (size_t)INT_MAX)
261
0
        *outlength = curlx_uztosi(outputlen);
262
0
      else
263
        /* too large to return in an int, fail! */
264
0
        curlx_safefree(str);
265
0
    }
266
0
  }
267
0
  return str;
268
0
}
269
270
/* For operating systems/environments that use different malloc/free
271
   systems for the app and for this library, we provide a free that uses
272
   the library's memory system */
273
void curl_free(void *p)
274
39.2k
{
275
39.2k
  curlx_free(p);
276
39.2k
}
277
278
/*
279
 * Curl_hexencode()
280
 *
281
 * Converts binary input to lowercase hex-encoded ASCII output.
282
 * null-terminated.
283
 */
284
void Curl_hexencode(const unsigned char *src, size_t len, /* input length */
285
                    unsigned char *out, size_t olen) /* output buffer size */
286
0
{
287
0
  DEBUGASSERT(src && len && (olen >= 3));
288
0
  if(src && len && (olen >= 3)) {
289
0
    while(len-- && (olen >= 3)) {
290
0
      out[0] = Curl_ldigits[*src >> 4];
291
0
      out[1] = Curl_ldigits[*src & 0x0F];
292
0
      ++src;
293
0
      out += 2;
294
0
      olen -= 2;
295
0
    }
296
0
    *out = 0;
297
0
  }
298
0
  else if(olen)
299
0
    *out = 0;
300
0
}
301
302
/* Curl_hexbyte
303
 *
304
 * Output a single unsigned char as a two-digit UPPERCASE hex number.
305
 */
306
void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */
307
                  unsigned char val)
308
3.08M
{
309
3.08M
  dest[0] = Curl_udigits[val >> 4];
310
3.08M
  dest[1] = Curl_udigits[val & 0x0F];
311
3.08M
}