/src/mercurial/mercurial/cext/charencode.c
Line | Count | Source |
1 | | /* |
2 | | charencode.c - miscellaneous character encoding |
3 | | |
4 | | Copyright 2008 Olivia Mackall <olivia@selenic.com> and others |
5 | | |
6 | | This software may be used and distributed according to the terms of |
7 | | the GNU General Public License, incorporated herein by reference. |
8 | | */ |
9 | | |
10 | | #define PY_SSIZE_T_CLEAN |
11 | | #include <Python.h> |
12 | | #include <assert.h> |
13 | | |
14 | | #include "charencode.h" |
15 | | #include "compat.h" |
16 | | #include "util.h" |
17 | | |
18 | | /* clang-format off */ |
19 | | static const char lowertable[128] = { |
20 | | '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', |
21 | | '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', |
22 | | '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', |
23 | | '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', |
24 | | '\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', |
25 | | '\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f', |
26 | | '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', |
27 | | '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f', |
28 | | '\x40', |
29 | | '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */ |
30 | | '\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */ |
31 | | '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */ |
32 | | '\x78', '\x79', '\x7a', /* X-Z */ |
33 | | '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', |
34 | | '\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', |
35 | | '\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', |
36 | | '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', |
37 | | '\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f' |
38 | | }; |
39 | | |
40 | | static const char uppertable[128] = { |
41 | | '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', |
42 | | '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', |
43 | | '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', |
44 | | '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', |
45 | | '\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', |
46 | | '\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f', |
47 | | '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', |
48 | | '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f', |
49 | | '\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', |
50 | | '\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', |
51 | | '\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', |
52 | | '\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', |
53 | | '\x60', |
54 | | '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', /* a-g */ |
55 | | '\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', /* h-o */ |
56 | | '\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', /* p-w */ |
57 | | '\x58', '\x59', '\x5a', /* x-z */ |
58 | | '\x7b', '\x7c', '\x7d', '\x7e', '\x7f' |
59 | | }; |
60 | | |
61 | | /* 1: no escape, 2: \<c>, 6: \u<x> */ |
62 | | static const uint8_t jsonlentable[256] = { |
63 | | 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */ |
64 | | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
65 | | 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */ |
66 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
67 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
68 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */ |
69 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
70 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */ |
71 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
72 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
73 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
74 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
75 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
76 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
77 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
78 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
79 | | }; |
80 | | |
81 | | static const uint8_t jsonparanoidlentable[128] = { |
82 | | 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */ |
83 | | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
84 | | 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */ |
85 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 6, 1, /* <, > */ |
86 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
87 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */ |
88 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
89 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */ |
90 | | }; |
91 | | |
92 | | static const char hexchartable[16] = { |
93 | | '0', '1', '2', '3', '4', '5', '6', '7', |
94 | | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', |
95 | | }; |
96 | | /* clang-format on */ |
97 | | |
98 | | /* |
99 | | * Turn a hex-encoded string into binary. |
100 | | */ |
101 | | PyObject *unhexlify(const char *str, Py_ssize_t len) |
102 | 0 | { |
103 | 0 | PyObject *ret; |
104 | 0 | char *d; |
105 | 0 | Py_ssize_t i; |
106 | |
|
107 | 0 | ret = PyBytes_FromStringAndSize(NULL, len / 2); |
108 | |
|
109 | 0 | if (!ret) { |
110 | 0 | return NULL; |
111 | 0 | } |
112 | | |
113 | 0 | d = PyBytes_AsString(ret); |
114 | |
|
115 | 0 | for (i = 0; i < len;) { |
116 | 0 | int hi = hexdigit(str, i++); |
117 | 0 | int lo = hexdigit(str, i++); |
118 | 0 | *d++ = (hi << 4) | lo; |
119 | 0 | } |
120 | |
|
121 | 0 | return ret; |
122 | 0 | } |
123 | | |
124 | | PyObject *isasciistr(PyObject *self, PyObject *args) |
125 | 968 | { |
126 | 968 | const char *buf; |
127 | 968 | Py_ssize_t i, len; |
128 | 968 | if (!PyArg_ParseTuple(args, "y#:isasciistr", &buf, &len)) { |
129 | 0 | return NULL; |
130 | 0 | } |
131 | 968 | i = 0; |
132 | | /* char array in PyStringObject should be at least 4-byte aligned */ |
133 | 968 | if (((uintptr_t)buf & 3) == 0) { |
134 | 968 | const uint32_t *p = (const uint32_t *)buf; |
135 | 1.71M | for (; i < len / 4; i++) { |
136 | 1.71M | if (p[i] & 0x80808080U) { |
137 | 259 | Py_RETURN_FALSE; |
138 | 259 | } |
139 | 1.71M | } |
140 | 709 | i *= 4; |
141 | 709 | } |
142 | 1.82k | for (; i < len; i++) { |
143 | 1.20k | if (buf[i] & 0x80) { |
144 | 96 | Py_RETURN_FALSE; |
145 | 96 | } |
146 | 1.20k | } |
147 | 709 | Py_RETURN_TRUE; |
148 | 709 | } |
149 | | |
150 | | static inline PyObject * |
151 | | _asciitransform(PyObject *str_obj, const char table[128], PyObject *fallback_fn) |
152 | 1.93k | { |
153 | 1.93k | char *str, *newstr; |
154 | 1.93k | Py_ssize_t i, len; |
155 | 1.93k | PyObject *newobj = NULL; |
156 | 1.93k | PyObject *ret = NULL; |
157 | | |
158 | 1.93k | str = PyBytes_AS_STRING(str_obj); |
159 | 1.93k | len = PyBytes_GET_SIZE(str_obj); |
160 | | |
161 | 1.93k | newobj = PyBytes_FromStringAndSize(NULL, len); |
162 | 1.93k | if (!newobj) { |
163 | 0 | goto quit; |
164 | 0 | } |
165 | | |
166 | 1.93k | newstr = PyBytes_AS_STRING(newobj); |
167 | | |
168 | 13.6M | for (i = 0; i < len; i++) { |
169 | 13.6M | char c = str[i]; |
170 | 13.6M | if (c & 0x80) { |
171 | 710 | if (fallback_fn != NULL) { |
172 | 0 | ret = PyObject_CallFunctionObjArgs( |
173 | 0 | fallback_fn, str_obj, NULL); |
174 | 710 | } else { |
175 | 710 | PyObject *err = PyUnicodeDecodeError_Create( |
176 | 710 | "ascii", str, len, i, (i + 1), |
177 | 710 | "unexpected code byte"); |
178 | 710 | PyErr_SetObject(PyExc_UnicodeDecodeError, err); |
179 | 710 | Py_XDECREF(err); |
180 | 710 | } |
181 | 710 | goto quit; |
182 | 710 | } |
183 | 13.6M | newstr[i] = table[(unsigned char)c]; |
184 | 13.6M | } |
185 | | |
186 | 1.22k | ret = newobj; |
187 | 1.22k | Py_INCREF(ret); |
188 | 1.93k | quit: |
189 | 1.93k | Py_XDECREF(newobj); |
190 | 1.93k | return ret; |
191 | 1.22k | } |
192 | | |
193 | | PyObject *asciilower(PyObject *self, PyObject *args) |
194 | 968 | { |
195 | 968 | PyObject *str_obj; |
196 | 968 | if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj)) { |
197 | 0 | return NULL; |
198 | 0 | } |
199 | 968 | return _asciitransform(str_obj, lowertable, NULL); |
200 | 968 | } |
201 | | |
202 | | PyObject *asciiupper(PyObject *self, PyObject *args) |
203 | 968 | { |
204 | 968 | PyObject *str_obj; |
205 | 968 | if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj)) { |
206 | 0 | return NULL; |
207 | 0 | } |
208 | 968 | return _asciitransform(str_obj, uppertable, NULL); |
209 | 968 | } |
210 | | |
211 | | PyObject *make_file_foldmap(PyObject *self, PyObject *args) |
212 | 0 | { |
213 | 0 | PyObject *dmap, *spec_obj, *normcase_fallback; |
214 | 0 | PyObject *file_foldmap = NULL; |
215 | 0 | enum normcase_spec spec; |
216 | 0 | PyObject *k, *v; |
217 | 0 | dirstateItemObject *tuple; |
218 | 0 | Py_ssize_t pos = 0; |
219 | 0 | const char *table; |
220 | |
|
221 | 0 | if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap", &PyDict_Type, |
222 | 0 | &dmap, &PyLong_Type, &spec_obj, &PyFunction_Type, |
223 | 0 | &normcase_fallback)) { |
224 | 0 | goto quit; |
225 | 0 | } |
226 | | |
227 | 0 | spec = (int)PyLong_AS_LONG(spec_obj); |
228 | 0 | switch (spec) { |
229 | 0 | case NORMCASE_LOWER: |
230 | 0 | table = lowertable; |
231 | 0 | break; |
232 | 0 | case NORMCASE_UPPER: |
233 | 0 | table = uppertable; |
234 | 0 | break; |
235 | 0 | case NORMCASE_OTHER: |
236 | 0 | table = NULL; |
237 | 0 | break; |
238 | 0 | default: |
239 | 0 | PyErr_SetString(PyExc_TypeError, "invalid normcasespec"); |
240 | 0 | goto quit; |
241 | 0 | } |
242 | | |
243 | | /* Add some more entries to deal with additions outside this |
244 | | function. */ |
245 | 0 | file_foldmap = _PyDict_NewPresized((PyDict_Size(dmap) / 10) * 11); |
246 | 0 | if (file_foldmap == NULL) { |
247 | 0 | goto quit; |
248 | 0 | } |
249 | | |
250 | 0 | while (PyDict_Next(dmap, &pos, &k, &v)) { |
251 | 0 | if (!dirstate_tuple_check(v)) { |
252 | 0 | PyErr_SetString(PyExc_TypeError, |
253 | 0 | "expected a dirstate tuple"); |
254 | 0 | goto quit; |
255 | 0 | } |
256 | | |
257 | 0 | tuple = (dirstateItemObject *)v; |
258 | 0 | if (tuple->flags | dirstate_flag_wc_tracked) { |
259 | 0 | PyObject *normed; |
260 | 0 | if (table != NULL) { |
261 | 0 | normed = _asciitransform(k, table, |
262 | 0 | normcase_fallback); |
263 | 0 | } else { |
264 | 0 | normed = PyObject_CallFunctionObjArgs( |
265 | 0 | normcase_fallback, k, NULL); |
266 | 0 | } |
267 | |
|
268 | 0 | if (normed == NULL) { |
269 | 0 | goto quit; |
270 | 0 | } |
271 | 0 | if (PyDict_SetItem(file_foldmap, normed, k) == -1) { |
272 | 0 | Py_DECREF(normed); |
273 | 0 | goto quit; |
274 | 0 | } |
275 | 0 | Py_DECREF(normed); |
276 | 0 | } |
277 | 0 | } |
278 | 0 | return file_foldmap; |
279 | 0 | quit: |
280 | 0 | Py_XDECREF(file_foldmap); |
281 | 0 | return NULL; |
282 | 0 | } |
283 | | |
284 | | /* calculate length of JSON-escaped string; returns -1 if unsupported */ |
285 | | static Py_ssize_t jsonescapelen(const char *buf, Py_ssize_t len, bool paranoid) |
286 | 350 | { |
287 | 350 | Py_ssize_t i, esclen = 0; |
288 | | |
289 | 350 | if (paranoid) { |
290 | | /* don't want to process multi-byte escapes in C */ |
291 | 1.45M | for (i = 0; i < len; i++) { |
292 | 1.45M | char c = buf[i]; |
293 | 1.45M | if (c & 0x80) { |
294 | 54 | PyErr_SetString(PyExc_ValueError, |
295 | 54 | "cannot process non-ascii str"); |
296 | 54 | return -1; |
297 | 54 | } |
298 | 1.45M | esclen += jsonparanoidlentable[(unsigned char)c]; |
299 | 1.45M | if (esclen < 0) { |
300 | 0 | PyErr_SetString(PyExc_MemoryError, |
301 | 0 | "overflow in jsonescapelen"); |
302 | 0 | return -1; |
303 | 0 | } |
304 | 1.45M | } |
305 | 199 | } else { |
306 | 26.2M | for (i = 0; i < len; i++) { |
307 | 26.2M | char c = buf[i]; |
308 | 26.2M | esclen += jsonlentable[(unsigned char)c]; |
309 | 26.2M | if (esclen < 0) { |
310 | 0 | PyErr_SetString(PyExc_MemoryError, |
311 | 0 | "overflow in jsonescapelen"); |
312 | 0 | return -1; |
313 | 0 | } |
314 | 26.2M | } |
315 | 199 | } |
316 | | |
317 | 296 | return esclen; |
318 | 350 | } |
319 | | |
320 | | /* map '\<c>' escape character */ |
321 | | static char jsonescapechar2(char c) |
322 | 323k | { |
323 | 323k | switch (c) { |
324 | 1.30k | case '\b': |
325 | 1.30k | return 'b'; |
326 | 197k | case '\t': |
327 | 197k | return 't'; |
328 | 75.1k | case '\n': |
329 | 75.1k | return 'n'; |
330 | 1.45k | case '\f': |
331 | 1.45k | return 'f'; |
332 | 44.9k | case '\r': |
333 | 44.9k | return 'r'; |
334 | 1.32k | case '"': |
335 | 1.32k | return '"'; |
336 | 1.82k | case '\\': |
337 | 1.82k | return '\\'; |
338 | 323k | } |
339 | 0 | return '\0'; /* should not happen */ |
340 | 323k | } |
341 | | |
342 | | /* convert 'origbuf' to JSON-escaped form 'escbuf'; 'origbuf' should only |
343 | | include characters mappable by json(paranoid)lentable */ |
344 | | static void encodejsonescape(char *escbuf, Py_ssize_t esclen, |
345 | | const char *origbuf, Py_ssize_t origlen, |
346 | | bool paranoid) |
347 | 270 | { |
348 | 270 | const uint8_t *lentable = |
349 | 270 | (paranoid) ? jsonparanoidlentable : jsonlentable; |
350 | 270 | Py_ssize_t i, j; |
351 | | |
352 | 27.6M | for (i = 0, j = 0; i < origlen; i++) { |
353 | 27.6M | char c = origbuf[i]; |
354 | 27.6M | uint8_t l = lentable[(unsigned char)c]; |
355 | 27.6M | assert(j + l <= esclen); |
356 | 27.6M | switch (l) { |
357 | 1.36M | case 1: |
358 | 1.36M | escbuf[j] = c; |
359 | 1.36M | break; |
360 | 323k | case 2: |
361 | 323k | escbuf[j] = '\\'; |
362 | 323k | escbuf[j + 1] = jsonescapechar2(c); |
363 | 323k | break; |
364 | 25.9M | case 6: |
365 | 25.9M | memcpy(escbuf + j, "\\u00", 4); |
366 | 25.9M | escbuf[j + 4] = hexchartable[(unsigned char)c >> 4]; |
367 | 25.9M | escbuf[j + 5] = hexchartable[(unsigned char)c & 0xf]; |
368 | 25.9M | break; |
369 | 27.6M | } |
370 | 27.6M | j += l; |
371 | 27.6M | } |
372 | 270 | } |
373 | | |
374 | | PyObject *jsonescapeu8fast(PyObject *self, PyObject *args) |
375 | 350 | { |
376 | 350 | PyObject *origstr, *escstr; |
377 | 350 | const char *origbuf; |
378 | 350 | Py_ssize_t origlen, esclen; |
379 | 350 | int paranoid; |
380 | 350 | if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast", &PyBytes_Type, |
381 | 350 | &origstr, ¶noid)) { |
382 | 0 | return NULL; |
383 | 0 | } |
384 | | |
385 | 350 | origbuf = PyBytes_AS_STRING(origstr); |
386 | 350 | origlen = PyBytes_GET_SIZE(origstr); |
387 | 350 | esclen = jsonescapelen(origbuf, origlen, paranoid); |
388 | 350 | if (esclen < 0) { |
389 | 54 | return NULL; /* unsupported char found or overflow */ |
390 | 54 | } |
391 | 296 | if (origlen == esclen) { |
392 | 26 | Py_INCREF(origstr); |
393 | 26 | return origstr; |
394 | 26 | } |
395 | | |
396 | 270 | escstr = PyBytes_FromStringAndSize(NULL, esclen); |
397 | 270 | if (!escstr) { |
398 | 0 | return NULL; |
399 | 0 | } |
400 | 270 | encodejsonescape(PyBytes_AS_STRING(escstr), esclen, origbuf, origlen, |
401 | 270 | paranoid); |
402 | | |
403 | 270 | return escstr; |
404 | 270 | } |