Import file-5.05.
[dragonfly.git] / contrib / file / src / ascmagic.c
1 /*
2  * Copyright (c) Ian F. Darwin 1986-1995.
3  * Software written by Ian F. Darwin and others;
4  * maintained 1995-present by Christos Zoulas and others.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice immediately at the beginning of the file, without modification,
11  *    this list of conditions, and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 /*
29  * ASCII magic -- file types that we know based on keywords
30  * that can appear anywhere in the file.
31  *
32  * Extensively modified by Eric Fischer <enf@pobox.com> in July, 2000,
33  * to handle character codes other than ASCII on a unified basis.
34  */
35
36 #include "file.h"
37
38 #ifndef lint
39 FILE_RCSID("@(#)$File: ascmagic.c,v 1.77 2010/11/30 14:58:53 rrt Exp $")
40 #endif  /* lint */
41
42 #include "magic.h"
43 #include <string.h>
44 #include <memory.h>
45 #include <ctype.h>
46 #include <stdlib.h>
47 #ifdef HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50 #include "names.h"
51
52 #define MAXLINELEN 300  /* longest sane line length */
53 #define ISSPC(x) ((x) == ' ' || (x) == '\t' || (x) == '\r' || (x) == '\n' \
54                   || (x) == 0x85 || (x) == '\f')
55
56 private int ascmatch(const unsigned char *, const unichar *, size_t);
57 private unsigned char *encode_utf8(unsigned char *, size_t, unichar *, size_t);
58 private size_t trim_nuls(const unsigned char *, size_t);
59
60 /*
61  * Undo the NUL-termination kindly provided by process()
62  * but leave at least one byte to look at
63  */
64 private size_t
65 trim_nuls(const unsigned char *buf, size_t nbytes)
66 {
67         while (nbytes > 1 && buf[nbytes - 1] == '\0')
68                 nbytes--;
69
70         return nbytes;
71 }
72
73 protected int
74 file_ascmagic(struct magic_set *ms, const unsigned char *buf, size_t nbytes)
75 {
76         unichar *ubuf = NULL;
77         size_t ulen;
78         int rv = 1;
79
80         const char *code = NULL;
81         const char *code_mime = NULL;
82         const char *type = NULL;
83
84         if (ms->flags & MAGIC_APPLE)
85                 return 0;
86
87         nbytes = trim_nuls(buf, nbytes);
88
89         /* If file doesn't look like any sort of text, give up. */
90         if (file_encoding(ms, buf, nbytes, &ubuf, &ulen, &code, &code_mime,
91             &type) == 0) {
92                 rv = 0;
93                 goto done;
94         }
95
96         rv = file_ascmagic_with_encoding(ms, buf, nbytes, ubuf, ulen, code,
97             type);
98
99  done:
100         if (ubuf)
101                 free(ubuf);
102
103         return rv;
104 }
105
106 protected int
107 file_ascmagic_with_encoding(struct magic_set *ms, const unsigned char *buf,
108     size_t nbytes, unichar *ubuf, size_t ulen, const char *code,
109     const char *type)
110 {
111         unsigned char *utf8_buf = NULL, *utf8_end;
112         size_t mlen, i;
113         const struct names *p;
114         int rv = -1;
115         int mime = ms->flags & MAGIC_MIME;
116
117         const char *subtype = NULL;
118         const char *subtype_mime = NULL;
119
120         int has_escapes = 0;
121         int has_backspace = 0;
122         int seen_cr = 0;
123
124         int n_crlf = 0;
125         int n_lf = 0;
126         int n_cr = 0;
127         int n_nel = 0;
128         int score, curtype;
129
130         size_t last_line_end = (size_t)-1;
131         int has_long_lines = 0;
132
133         if (ms->flags & MAGIC_APPLE)
134                 return 0;
135
136         nbytes = trim_nuls(buf, nbytes);
137
138         /* If we have fewer than 2 bytes, give up. */
139         if (nbytes <= 1) {
140                 rv = 0;
141                 goto done;
142         }
143
144         if ((ms->flags & MAGIC_NO_CHECK_SOFT) == 0) {
145                 /* Convert ubuf to UTF-8 and try text soft magic */
146                 /* malloc size is a conservative overestimate; could be
147                    improved, or at least realloced after conversion. */
148                 mlen = ulen * 6;
149                 if ((utf8_buf = CAST(unsigned char *, malloc(mlen))) == NULL) {
150                         file_oomem(ms, mlen);
151                         goto done;
152                 }
153                 if ((utf8_end = encode_utf8(utf8_buf, mlen, ubuf, ulen)) == NULL)
154                         goto done;
155                 if ((rv = file_softmagic(ms, utf8_buf, (size_t)(utf8_end - utf8_buf),
156                                          TEXTTEST)) != 0)
157                         goto done;
158                 else
159                         rv = -1;
160         }
161
162         /* look for tokens from names.h - this is expensive! */
163         if ((ms->flags & MAGIC_NO_CHECK_TOKENS) != 0)
164                 goto subtype_identified;
165
166         i = 0;
167         score = 0;
168         curtype = -1;
169         while (i < ulen) {
170                 size_t end;
171
172                 /* skip past any leading space */
173                 while (i < ulen && ISSPC(ubuf[i]))
174                         i++;
175                 if (i >= ulen)
176                         break;
177
178                 /* find the next whitespace */
179                 for (end = i + 1; end < nbytes; end++)
180                         if (ISSPC(ubuf[end]))
181                                 break;
182
183                 /* compare the word thus isolated against the token list */
184                 for (p = names; p < names + NNAMES; p++) {
185                         if (ascmatch((const unsigned char *)p->name, ubuf + i,
186                             end - i)) {
187                                 if (curtype == -1)
188                                         curtype = p->type;
189                                 else if (curtype != p->type) {
190                                         score = p->score;
191                                         curtype = p->type;
192                                 } else
193                                         score += p->score;
194                                 if (score > 1) {
195                                         subtype = types[p->type].human;
196                                         subtype_mime = types[p->type].mime;
197                                         goto subtype_identified;
198                                 }
199                         }
200                 }
201
202                 i = end;
203         }
204
205 subtype_identified:
206
207         /* Now try to discover other details about the file. */
208         for (i = 0; i < ulen; i++) {
209                 if (ubuf[i] == '\n') {
210                         if (seen_cr)
211                                 n_crlf++;
212                         else
213                                 n_lf++;
214                         last_line_end = i;
215                 } else if (seen_cr)
216                         n_cr++;
217
218                 seen_cr = (ubuf[i] == '\r');
219                 if (seen_cr)
220                         last_line_end = i;
221
222                 if (ubuf[i] == 0x85) { /* X3.64/ECMA-43 "next line" character */
223                         n_nel++;
224                         last_line_end = i;
225                 }
226
227                 /* If this line is _longer_ than MAXLINELEN, remember it. */
228                 if (i > last_line_end + MAXLINELEN)
229                         has_long_lines = 1;
230
231                 if (ubuf[i] == '\033')
232                         has_escapes = 1;
233                 if (ubuf[i] == '\b')
234                         has_backspace = 1;
235         }
236
237         /* Beware, if the data has been truncated, the final CR could have
238            been followed by a LF.  If we have HOWMANY bytes, it indicates
239            that the data might have been truncated, probably even before
240            this function was called. */
241         if (seen_cr && nbytes < HOWMANY)
242                 n_cr++;
243
244         if (strcmp(type, "binary") == 0) {
245                 rv = 0;
246                 goto done;
247         }
248         if (mime) {
249                 if ((mime & MAGIC_MIME_TYPE) != 0) {
250                         if (subtype_mime) {
251                                 if (file_printf(ms, "%s", subtype_mime) == -1)
252                                         goto done;
253                         } else {
254                                 if (file_printf(ms, "text/plain") == -1)
255                                         goto done;
256                         }
257                 }
258         } else {
259                 if (file_printf(ms, "%s", code) == -1)
260                         goto done;
261
262                 if (subtype) {
263                         if (file_printf(ms, " %s", subtype) == -1)
264                                 goto done;
265                 }
266
267                 if (file_printf(ms, " %s", type) == -1)
268                         goto done;
269
270                 if (has_long_lines)
271                         if (file_printf(ms, ", with very long lines") == -1)
272                                 goto done;
273
274                 /*
275                  * Only report line terminators if we find one other than LF,
276                  * or if we find none at all.
277                  */
278                 if ((n_crlf == 0 && n_cr == 0 && n_nel == 0 && n_lf == 0) ||
279                     (n_crlf != 0 || n_cr != 0 || n_nel != 0)) {
280                         if (file_printf(ms, ", with") == -1)
281                                 goto done;
282
283                         if (n_crlf == 0 && n_cr == 0 && n_nel == 0 && n_lf == 0) {
284                                 if (file_printf(ms, " no") == -1)
285                                         goto done;
286                         } else {
287                                 if (n_crlf) {
288                                         if (file_printf(ms, " CRLF") == -1)
289                                                 goto done;
290                                         if (n_cr || n_lf || n_nel)
291                                                 if (file_printf(ms, ",") == -1)
292                                                         goto done;
293                                 }
294                                 if (n_cr) {
295                                         if (file_printf(ms, " CR") == -1)
296                                                 goto done;
297                                         if (n_lf || n_nel)
298                                                 if (file_printf(ms, ",") == -1)
299                                                         goto done;
300                                 }
301                                 if (n_lf) {
302                                         if (file_printf(ms, " LF") == -1)
303                                                 goto done;
304                                         if (n_nel)
305                                                 if (file_printf(ms, ",") == -1)
306                                                         goto done;
307                                 }
308                                 if (n_nel)
309                                         if (file_printf(ms, " NEL") == -1)
310                                                 goto done;
311                         }
312
313                         if (file_printf(ms, " line terminators") == -1)
314                                 goto done;
315                 }
316
317                 if (has_escapes)
318                         if (file_printf(ms, ", with escape sequences") == -1)
319                                 goto done;
320                 if (has_backspace)
321                         if (file_printf(ms, ", with overstriking") == -1)
322                                 goto done;
323         }
324         rv = 1;
325 done:
326         if (utf8_buf)
327                 free(utf8_buf);
328
329         return rv;
330 }
331
332 private int
333 ascmatch(const unsigned char *s, const unichar *us, size_t ulen)
334 {
335         size_t i;
336
337         for (i = 0; i < ulen; i++) {
338                 if (s[i] != us[i])
339                         return 0;
340         }
341
342         if (s[i])
343                 return 0;
344         else
345                 return 1;
346 }
347
348 /*
349  * Encode Unicode string as UTF-8, returning pointer to character
350  * after end of string, or NULL if an invalid character is found.
351  */
352 private unsigned char *
353 encode_utf8(unsigned char *buf, size_t len, unichar *ubuf, size_t ulen)
354 {
355         size_t i;
356         unsigned char *end = buf + len;
357
358         for (i = 0; i < ulen; i++) {
359                 if (ubuf[i] <= 0x7f) {
360                         if (end - buf < 1)
361                                 return NULL;
362                         *buf++ = (unsigned char)ubuf[i];
363                 } else if (ubuf[i] <= 0x7ff) {
364                         if (end - buf < 2)
365                                 return NULL;
366                         *buf++ = (unsigned char)((ubuf[i] >> 6) + 0xc0);
367                         *buf++ = (unsigned char)((ubuf[i] & 0x3f) + 0x80);
368                 } else if (ubuf[i] <= 0xffff) {
369                         if (end - buf < 3)
370                                 return NULL;
371                         *buf++ = (unsigned char)((ubuf[i] >> 12) + 0xe0);
372                         *buf++ = (unsigned char)(((ubuf[i] >> 6) & 0x3f) + 0x80);
373                         *buf++ = (unsigned char)((ubuf[i] & 0x3f) + 0x80);
374                 } else if (ubuf[i] <= 0x1fffff) {
375                         if (end - buf < 4)
376                                 return NULL;
377                         *buf++ = (unsigned char)((ubuf[i] >> 18) + 0xf0);
378                         *buf++ = (unsigned char)(((ubuf[i] >> 12) & 0x3f) + 0x80);
379                         *buf++ = (unsigned char)(((ubuf[i] >>  6) & 0x3f) + 0x80);
380                         *buf++ = (unsigned char)((ubuf[i] & 0x3f) + 0x80);
381                 } else if (ubuf[i] <= 0x3ffffff) {
382                         if (end - buf < 5)
383                                 return NULL;
384                         *buf++ = (unsigned char)((ubuf[i] >> 24) + 0xf8);
385                         *buf++ = (unsigned char)(((ubuf[i] >> 18) & 0x3f) + 0x80);
386                         *buf++ = (unsigned char)(((ubuf[i] >> 12) & 0x3f) + 0x80);
387                         *buf++ = (unsigned char)(((ubuf[i] >>  6) & 0x3f) + 0x80);
388                         *buf++ = (unsigned char)((ubuf[i] & 0x3f) + 0x80);
389                 } else if (ubuf[i] <= 0x7fffffff) {
390                         if (end - buf < 6)
391                                 return NULL;
392                         *buf++ = (unsigned char)((ubuf[i] >> 30) + 0xfc);
393                         *buf++ = (unsigned char)(((ubuf[i] >> 24) & 0x3f) + 0x80);
394                         *buf++ = (unsigned char)(((ubuf[i] >> 18) & 0x3f) + 0x80);
395                         *buf++ = (unsigned char)(((ubuf[i] >> 12) & 0x3f) + 0x80);
396                         *buf++ = (unsigned char)(((ubuf[i] >>  6) & 0x3f) + 0x80);
397                         *buf++ = (unsigned char)((ubuf[i] & 0x3f) + 0x80);
398                 } else /* Invalid character */
399                         return NULL;
400         }
401
402         return buf;
403 }