encoding-guesser: New library to guess the encoding of a text file.
[pspp-builds.git] / src / libpspp / encoding-guesser.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "libpspp/encoding-guesser.h"
20
21 #include <errno.h>
22 #include <iconv.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <unistr.h>
28
29 #include "libpspp/cast.h"
30 #include "libpspp/i18n.h"
31
32 #include "gl/localcharset.h"
33 #include "gl/c-strcase.h"
34
35 /* http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info is a useful source
36    of information about encoding detection.
37 */
38
39 /* Parses and returns the fallback encoding from ENCODING, which must be in one
40    of the forms described at the top of encoding-guesser.h.  The returned
41    string might be ENCODING itself or a suffix of it, or it might be a
42    statically allocated string. */
43 const char *
44 encoding_guess_parse_encoding (const char *encoding)
45 {
46   if (encoding == NULL
47       || !c_strcasecmp (encoding, "auto")
48       || !c_strcasecmp (encoding, "auto,locale")
49       || !c_strcasecmp (encoding, "locale"))
50     return locale_charset ();
51   else if (!c_strncasecmp (encoding, "auto,", 5))
52     return encoding + 5;
53   else
54     return encoding;
55 }
56
57 /* Returns true if ENCODING, which must be in one of the forms described at the
58    top of encoding-guesser.h, is one that performs encoding autodetection,
59    false otherwise. */
60 bool
61 encoding_guess_encoding_is_auto (const char *encoding)
62 {
63   return (encoding == NULL
64           || (!c_strncasecmp (encoding, "auto", 4)
65               && (encoding[4] == ',' || encoding[4] == '\0')));
66 }
67
68 static uint16_t
69 get_be16 (const uint8_t *data)
70 {
71   return (data[0] << 8) | data[1];
72 }
73
74 static uint16_t
75 get_le16 (const uint8_t *data)
76 {
77   return (data[1] << 8) | data[0];
78 }
79
80 static uint32_t
81 get_be32 (const uint8_t *data)
82 {
83   return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
84
85 }
86
87 static uint32_t
88 get_le32 (const uint8_t *data)
89 {
90   return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
91
92 }
93
94 static const char *
95 guess_utf16 (const uint8_t *data, size_t n)
96 {
97   size_t even_nulls, odd_nulls;
98
99   if (n < ENCODING_GUESS_MIN && n % 2 != 0)
100     return NULL;
101
102   even_nulls = odd_nulls = 0;
103   while (n >= 2)
104     {
105       even_nulls += data[0] == 0;
106       odd_nulls += data[1] == 0;
107       if (data[0] == 0 && data[1] == 0)
108         return NULL;
109
110       data += 2;
111       n -= 2;
112     }
113
114   if (odd_nulls > even_nulls)
115     return "UTF-16LE";
116   else if (even_nulls > 0)
117     return "UTF-16BE";
118   else
119     return NULL;
120 }
121
122 static bool
123 is_utf32 (const uint8_t *data, size_t n, uint32_t (*get_u32) (const uint8_t *))
124 {
125   if (n < ENCODING_GUESS_MIN && n % 4 != 0)
126     return false;
127
128   while (n >= 4)
129     {
130       uint32_t uc = get_u32 (data);
131
132       if (uc < 0x09 || uc > 0x10ffff)
133         return false;
134
135       data += 4;
136       n -= 4;
137     }
138
139   return true;
140 }
141
142 /* Counts and returns the number of bytes, but no more than N, starting at S
143    that are ASCII text characters. */
144 size_t
145 encoding_guess_count_ascii (const void *s_, size_t n)
146 {
147   const uint8_t *s = s_;
148   size_t ofs;
149
150   for (ofs = 0; ofs < n; ofs++)
151     if (!encoding_guess_is_ascii_text (s[ofs]))
152       break;
153   return ofs;
154 }
155
156 static bool
157 is_all_utf8_text (const void *s_, size_t n)
158 {
159   const uint8_t *s = s_;
160   size_t ofs;
161
162   ofs = 0;
163   while (ofs < n)
164     {
165       uint8_t c = s[ofs];
166       if (c < 0x80)
167         {
168           if (!encoding_guess_is_ascii_text (c))
169             return false;
170           ofs++;
171         }
172       else
173         {
174           ucs4_t uc;
175           int mblen;
176
177           mblen = u8_mbtoucr (&uc, s + ofs, n - ofs);
178           if (mblen < 0)
179             return mblen == -2;
180
181           ofs += mblen;
182         }
183     }
184   return true;
185 }
186
187 /* Attempts to guess the encoding of a text file based on ENCODING, an encoding
188    name in one of the forms described at the top of encoding-guesser.h, and
189    DATA, which contains the first N bytes of the file.  Returns the guessed
190    encoding, which might be ENCODING itself or a suffix of it or a statically
191    allocated string.
192
193    Encoding autodetection only takes place if ENCODING actually specifies
194    autodetection.  See encoding-guesser.h for details.
195
196    UTF-8 cannot be distinguished from other ASCII-based encodings until a
197    non-ASCII text character is encountered.  If ENCODING specifies
198    autodetection and this function returns "ASCII", then the client should
199    process the input until it encounters an non-ASCII character (as returned by
200    encoding_guess_is_ascii_text()) and then use encoding_guess_tail_encoding()
201    to make a final encoding guess.  See encoding-guesser.h for details.
202
203    N must be at least ENCODING_GUESS_MIN, unless the file is shorter than
204    that. */
205 const char *
206 encoding_guess_head_encoding (const char *encoding,
207                               const void *data_, size_t n)
208 {
209   const uint8_t *data = data_;
210   const char *fallback_encoding;
211   const char *guess;
212
213   fallback_encoding = encoding_guess_parse_encoding (encoding);
214   if (!encoding_guess_encoding_is_auto (encoding))
215     return fallback_encoding;
216
217   if (n == 0)
218     return fallback_encoding;
219
220   if ((n >= ENCODING_GUESS_MIN || n % 4 == 0)
221       && (get_be32 (data) == 0xfeff || get_le32 (data) == 0xfeff))
222     return "UTF-32";
223
224   if (n >= 4)
225     {
226       uint32_t x = get_be32 (data);
227       if (x == 0x84319533)
228         return "GB-18030";
229       else if (x == 0xdd736673)
230         return "UTF-EBCDIC";
231     }
232
233   if ((n >= ENCODING_GUESS_MIN || n % 2 == 0)
234       && (get_be16 (data) == 0xfeff || get_le16 (data) == 0xfeff))
235     return "UTF-16";
236
237   if (n >= 3 && data[0] == 0xef && data[1] == 0xbb && data[2] == 0xbf)
238     return "UTF-8";
239
240   guess = guess_utf16 (data, n);
241   if (guess != NULL)
242     return guess;
243
244   if (is_utf32 (data, n, get_be32))
245     return "UTF-32BE";
246   if (is_utf32 (data, n, get_le32))
247     return "UTF-32LE";
248
249   if (!is_encoding_ascii_compatible (fallback_encoding)
250       || !encoding_guess_tail_is_utf8 (data, n))
251     return fallback_encoding;
252
253   if (!c_strcasecmp (fallback_encoding, "UTF-8")
254       || !c_strcasecmp (fallback_encoding, "UTF8"))
255     return "UTF-8";
256
257   return "ASCII";
258 }
259
260 /* Returns an encoding guess based on ENCODING and the N bytes of text starting
261    at DATA.  DATA should start with the first non-ASCII text character (as
262    determined by encoding_guess_is_ascii_text()) found in the input.
263
264    The return value will either be "UTF-8" or the fallback encoding for
265    ENCODING.
266
267    See encoding-guesser.h for intended use of this function.
268
269    N must be at least ENCODING_GUESS_MIN, unless the file has fewer bytes than
270    that starting with the first non-ASCII text character. */
271 const char *
272 encoding_guess_tail_encoding (const char *encoding,
273                               const void *data, size_t n)
274 {
275   return (encoding_guess_tail_is_utf8 (data, n)
276           ? "UTF-8"
277           : encoding_guess_parse_encoding (encoding));
278 }
279
280 /* Same as encoding_guess_tail_encoding() but returns true for UTF-8 or false
281    for the fallback encoding. */
282 bool
283 encoding_guess_tail_is_utf8 (const void *data, size_t n)
284 {
285   return (n < ENCODING_GUESS_MIN
286           ? u8_check (data, n) == NULL
287           : is_all_utf8_text (data, n));
288 }
289