Coverage Report

Created: 2026-09-14 07:10

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
1.56k
{
38
1.56k
  return curl_easy_escape(NULL, string, length);
39
1.56k
}
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
1.56k
{
91
1.56k
  size_t len;
92
1.56k
  const char *f = string;
93
1.56k
  size_t nappend = 0;
94
1.56k
  char *target;
95
1.56k
  char *encoded;
96
1.56k
  (void)curl;
97
98
1.56k
  if(!string || (length < 0))
99
0
    return NULL;
100
101
1.56k
  len = (length ? (size_t)length : strlen(string));
102
1.56k
  if(!len)
103
1.18k
    return curlx_strdup("");
104
105
373
  if(len > SIZE_MAX / 16)
106
0
    return NULL;
107
108
373
  encoded = target = curlx_malloc((len * 3) + 1);
109
373
  if(!target)
110
0
    return NULL;
111
112
234k
  while(len--) {
113
234k
    uint8_t in = (uint8_t)*string++;
114
115
234k
    if(!(hextable[in] & 0x80))
116
      /* append this */
117
140k
      nappend++;
118
93.5k
    else {
119
93.5k
      if(nappend) {
120
12.7k
        memcpy(target, f, nappend);
121
12.7k
        target += nappend;
122
12.7k
        nappend = 0;
123
12.7k
      }
124
93.5k
      f = string;
125
      /* encode it */
126
93.5k
      target[0] = '%';
127
93.5k
      target[1] = Curl_udigits[in >> 4];
128
93.5k
      target[2] = Curl_udigits[in & 0x0F];
129
93.5k
      target += 3;
130
93.5k
    }
131
234k
  }
132
373
  if(nappend) {
133
271
    memcpy(target, f, nappend);
134
271
    target += nappend;
135
271
  }
136
373
  *target = '\0'; /* null terminate */
137
138
373
  return encoded;
139
373
}
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
1.08k
{
161
1.08k
  size_t alloc;
162
1.08k
  char *ns;
163
1.08k
  uint8_t reject_limit;
164
165
1.08k
  DEBUGASSERT(string);
166
1.08k
  DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
167
168
1.08k
  alloc = (length ? length : strlen(string));
169
1.08k
  ns = curlx_malloc(alloc + 1);
170
171
1.08k
  if(!ns)
172
0
    return CURLE_OUT_OF_MEMORY;
173
174
  /* store output string */
175
1.08k
  *ostring = ns;
176
177
1.08k
  reject_limit = (ctrl == REJECT_CTRL) ? 0x20 :
178
1.08k
    (ctrl == REJECT_ZERO) ? 1 : 0;
179
180
7.28k
  while(alloc) {
181
6.21k
    uint8_t in;
182
183
6.21k
    if(*string == '%') {
184
3.96k
      if(alloc > 2) {
185
3.86k
        uint8_t h1 = hextable[(uint8_t)string[1]];
186
3.86k
        uint8_t h2 = hextable[(uint8_t)string[2]];
187
3.86k
        if(!((h1 | h2) & 0xf0)) {
188
428
          in = (uint8_t)((h1 << 4) | h2);
189
428
          string += 3;
190
428
          alloc -= 3;
191
428
          if(in < reject_limit)
192
3
            goto error;
193
425
          *ns++ = (char)in;
194
425
          continue;
195
428
        }
196
3.86k
      }
197
3.54k
      in = '%';
198
3.54k
      string++;
199
3.54k
      alloc--;
200
3.54k
      *ns++ = (char)in;
201
3.54k
      continue;
202
3.96k
    }
203
2.24k
    else {
204
2.24k
      const char *p = memchr(string + 1, '%', alloc - 1);
205
2.24k
      size_t n = p ? (size_t)(p - string) : alloc;
206
207
2.24k
      if(reject_limit) {
208
2.24k
        if(reject_limit == 1) {
209
1.49k
          if(memchr(string, 0, n))
210
0
            goto error;
211
1.49k
        }
212
744
        else {
213
744
          size_t i;
214
161k
          for(i = 0; i < n; i++) {
215
160k
            if((uint8_t)string[i] < 0x20)
216
0
              goto error;
217
160k
          }
218
744
        }
219
2.24k
      }
220
221
2.24k
      memcpy(ns, string, n);
222
2.24k
      ns += n;
223
2.24k
      string += n;
224
2.24k
      alloc -= n;
225
2.24k
    }
226
6.21k
  }
227
1.07k
  *ns = 0; /* terminate it */
228
229
1.07k
  if(olen)
230
    /* store output size */
231
130
    *olen = (size_t)(ns - *ostring);
232
233
1.07k
  return CURLE_OK;
234
3
error:
235
3
  curlx_safefree(*ostring);
236
3
  return CURLE_URL_MALFORMAT;
237
1.08k
}
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
46.2k
{
275
46.2k
  curlx_free(p);
276
46.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
4.66k
{
287
4.66k
  DEBUGASSERT(src && len && (olen >= 3));
288
4.66k
  if(src && len && (olen >= 3)) {
289
153k
    while(len-- && (olen >= 3)) {
290
149k
      out[0] = Curl_ldigits[*src >> 4];
291
149k
      out[1] = Curl_ldigits[*src & 0x0F];
292
149k
      ++src;
293
149k
      out += 2;
294
149k
      olen -= 2;
295
149k
    }
296
4.66k
    *out = 0;
297
4.66k
  }
298
0
  else if(olen)
299
0
    *out = 0;
300
4.66k
}
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
51.5k
{
309
51.5k
  dest[0] = Curl_udigits[val >> 4];
310
51.5k
  dest[1] = Curl_udigits[val & 0x0F];
311
51.5k
}