chiark / gitweb /
013c110f07c072426615bd3b9f226038b2605d49
[elogind.git] / src / shared / utf8.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2008-2011 Kay Sievers
7   Copyright 2012 Lennart Poettering
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 /* Parts of this file are based on the GLIB utf8 validation functions. The
24  * original license text follows. */
25
26 /* gutf8.c - Operations on UTF-8 strings.
27  *
28  * Copyright (C) 1999 Tom Tromey
29  * Copyright (C) 2000 Red Hat, Inc.
30  *
31  * This library is free software; you can redistribute it and/or
32  * modify it under the terms of the GNU Library General Public
33  * License as published by the Free Software Foundation; either
34  * version 2 of the License, or (at your option) any later version.
35  *
36  * This library is distributed in the hope that it will be useful,
37  * but WITHOUT ANY WARRANTY; without even the implied warranty of
38  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
39  * Library General Public License for more details.
40  *
41  * You should have received a copy of the GNU Library General Public
42  * License along with this library; if not, write to the Free Software
43  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
44  */
45
46 #include <errno.h>
47 #include <stdlib.h>
48 #include <inttypes.h>
49 #include <string.h>
50 #include <stdbool.h>
51
52 #include "utf8.h"
53 #include "util.h"
54
55 static inline bool is_unicode_valid(uint32_t ch) {
56
57         if (ch >= 0x110000) /* End of unicode space */
58                 return false;
59         if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
60                 return false;
61         if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
62                 return false;
63         if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
64                 return false;
65
66         return true;
67 }
68
69 static bool is_unicode_control(uint32_t ch) {
70
71         /*
72           0 to ' '-1 is the C0 range.
73           DEL=0x7F, and DEL+1 to 0x9F is C1 range.
74           '\t' is in C0 range, but more or less harmless and commonly used.
75         */
76
77         return (ch < ' ' && ch != '\t' && ch != '\n') ||
78                 (0x7F <= ch && ch <= 0x9F);
79 }
80
81 /* count of characters used to encode one unicode char */
82 static int utf8_encoded_expected_len(const char *str) {
83         unsigned char c;
84
85         assert(str);
86
87         c = (unsigned char) str[0];
88         if (c < 0x80)
89                 return 1;
90         if ((c & 0xe0) == 0xc0)
91                 return 2;
92         if ((c & 0xf0) == 0xe0)
93                 return 3;
94         if ((c & 0xf8) == 0xf0)
95                 return 4;
96         if ((c & 0xfc) == 0xf8)
97                 return 5;
98         if ((c & 0xfe) == 0xfc)
99                 return 6;
100
101         return 0;
102 }
103
104 /* decode one unicode char */
105 int utf8_encoded_to_unichar(const char *str) {
106         int unichar, len, i;
107
108         assert(str);
109
110         len = utf8_encoded_expected_len(str);
111
112         switch (len) {
113         case 1:
114                 return (int)str[0];
115         case 2:
116                 unichar = str[0] & 0x1f;
117                 break;
118         case 3:
119                 unichar = (int)str[0] & 0x0f;
120                 break;
121         case 4:
122                 unichar = (int)str[0] & 0x07;
123                 break;
124         case 5:
125                 unichar = (int)str[0] & 0x03;
126                 break;
127         case 6:
128                 unichar = (int)str[0] & 0x01;
129                 break;
130         default:
131                 return -EINVAL;
132         }
133
134         for (i = 1; i < len; i++) {
135                 if (((int)str[i] & 0xc0) != 0x80)
136                         return -EINVAL;
137                 unichar <<= 6;
138                 unichar |= (int)str[i] & 0x3f;
139         }
140
141         return unichar;
142 }
143
144 bool utf8_is_printable_newline(const char* str, size_t length, bool newline) {
145         const char *p;
146
147         assert(str);
148
149         for (p = str; length;) {
150                 int encoded_len, val;
151
152                 encoded_len = utf8_encoded_valid_unichar(p);
153                 if (encoded_len < 0 ||
154                     (size_t) encoded_len > length)
155                         return false;
156
157                 val = utf8_encoded_to_unichar(p);
158                 if (val < 0 ||
159                     is_unicode_control(val) ||
160                     (!newline && val == '\n'))
161                         return false;
162
163                 length -= encoded_len;
164                 p += encoded_len;
165         }
166
167         return true;
168 }
169
170 const char *utf8_is_valid(const char *str) {
171         const uint8_t *p;
172
173         assert(str);
174
175         for (p = (const uint8_t*) str; *p; ) {
176                 int len;
177
178                 len = utf8_encoded_valid_unichar((const char *)p);
179                 if (len < 0)
180                         return NULL;
181
182                 p += len;
183         }
184
185         return str;
186 }
187
188 char *utf8_escape_invalid(const char *str) {
189         char *p, *s;
190
191         assert(str);
192
193         p = s = malloc(strlen(str) * 4 + 1);
194         if (!p)
195                 return NULL;
196
197         while (*str) {
198                 int len;
199
200                 len = utf8_encoded_valid_unichar(str);
201                 if (len > 0) {
202                         s = mempcpy(s, str, len);
203                         str += len;
204                 } else {
205                         s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
206                         str += 1;
207                 }
208         }
209
210         *s = '\0';
211
212         return p;
213 }
214
215 char *utf8_escape_non_printable(const char *str) {
216         char *p, *s;
217
218         assert(str);
219
220         p = s = malloc(strlen(str) * 4 + 1);
221         if (!p)
222                 return NULL;
223
224         while (*str) {
225                 int len;
226
227                 len = utf8_encoded_valid_unichar(str);
228                 if (len > 0) {
229                         if (utf8_is_printable(str, len)) {
230                                 s = mempcpy(s, str, len);
231                                 str += len;
232                         } else {
233                                 while (len > 0) {
234                                         *(s++) = '\\';
235                                         *(s++) = 'x';
236                                         *(s++) = hexchar((int) *str >> 4);
237                                         *(s++) = hexchar((int) *str);
238
239                                         str += 1;
240                                         len --;
241                                 }
242                         }
243                 } else {
244                         s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
245                         str += 1;
246                 }
247         }
248
249         *s = '\0';
250
251         return p;
252 }
253
254 char *ascii_is_valid(const char *str) {
255         const char *p;
256
257         assert(str);
258
259         for (p = str; *p; p++)
260                 if ((unsigned char) *p >= 128)
261                         return NULL;
262
263         return (char*) str;
264 }
265
266 /**
267  * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8
268  * @out_utf8: output buffer of at least 4 bytes or NULL
269  * @g: UCS-4 character to encode
270  *
271  * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8.
272  * The length of the character is returned. It is not zero-terminated! If the
273  * output buffer is NULL, only the length is returned.
274  *
275  * Returns: The length in bytes that the UTF-8 representation does or would
276  *          occupy.
277  */
278 size_t utf8_encode_unichar(char *out_utf8, uint32_t g) {
279         if (g < (1 << 7)) {
280                 if (out_utf8)
281                         out_utf8[0] = g & 0x7f;
282                 return 1;
283         } else if (g < (1 << 11)) {
284                 if (out_utf8) {
285                         out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f);
286                         out_utf8[1] = 0x80 | (g & 0x3f);
287                 }
288                 return 2;
289         } else if (g < (1 << 16)) {
290                 if (out_utf8) {
291                         out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f);
292                         out_utf8[1] = 0x80 | ((g >> 6) & 0x3f);
293                         out_utf8[2] = 0x80 | (g & 0x3f);
294                 }
295                 return 3;
296         } else if (g < (1 << 21)) {
297                 if (out_utf8) {
298                         out_utf8[0] = 0xf0 | ((g >> 18) & 0x07);
299                         out_utf8[1] = 0x80 | ((g >> 12) & 0x3f);
300                         out_utf8[2] = 0x80 | ((g >> 6) & 0x3f);
301                         out_utf8[3] = 0x80 | (g & 0x3f);
302                 }
303                 return 4;
304         } else {
305                 return 0;
306         }
307 }
308
309 char *utf16_to_utf8(const void *s, size_t length) {
310         const uint8_t *f;
311         char *r, *t;
312
313         r = new(char, (length * 4 + 1) / 2 + 1);
314         if (!r)
315                 return NULL;
316
317         f = s;
318         t = r;
319
320         while (f < (const uint8_t*) s + length) {
321                 uint16_t w1, w2;
322
323                 /* see RFC 2781 section 2.2 */
324
325                 w1 = f[1] << 8 | f[0];
326                 f += 2;
327
328                 if (!utf16_is_surrogate(w1)) {
329                         t += utf8_encode_unichar(t, w1);
330
331                         continue;
332                 }
333
334                 if (utf16_is_trailing_surrogate(w1))
335                         continue;
336                 else if (f >= (const uint8_t*) s + length)
337                         break;
338
339                 w2 = f[1] << 8 | f[0];
340                 f += 2;
341
342                 if (!utf16_is_trailing_surrogate(w2)) {
343                         f -= 2;
344                         continue;
345                 }
346
347                 t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2));
348         }
349
350         *t = 0;
351         return r;
352 }
353
354 /* expected size used to encode one unicode char */
355 static int utf8_unichar_to_encoded_len(int unichar) {
356
357         if (unichar < 0x80)
358                 return 1;
359         if (unichar < 0x800)
360                 return 2;
361         if (unichar < 0x10000)
362                 return 3;
363         if (unichar < 0x200000)
364                 return 4;
365         if (unichar < 0x4000000)
366                 return 5;
367
368         return 6;
369 }
370
371 /* validate one encoded unicode char and return its length */
372 int utf8_encoded_valid_unichar(const char *str) {
373         int len, unichar, i;
374
375         assert(str);
376
377         len = utf8_encoded_expected_len(str);
378         if (len == 0)
379                 return -EINVAL;
380
381         /* ascii is valid */
382         if (len == 1)
383                 return 1;
384
385         /* check if expected encoded chars are available */
386         for (i = 0; i < len; i++)
387                 if ((str[i] & 0x80) != 0x80)
388                         return -EINVAL;
389
390         unichar = utf8_encoded_to_unichar(str);
391
392         /* check if encoded length matches encoded value */
393         if (utf8_unichar_to_encoded_len(unichar) != len)
394                 return -EINVAL;
395
396         /* check if value has valid range */
397         if (!is_unicode_valid(unichar))
398                 return -EINVAL;
399
400         return len;
401 }