]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/file.c
file: detect torrent files
[plan9front.git] / sys / src / cmd / file.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ctype.h>
5 #include <mach.h>
6
7 /*
8  * file - determine type of file
9  */
10 #define LENDIAN(p)      ((p)[0] | ((p)[1]<<8) | ((p)[2]<<16) | ((p)[3]<<24))
11
12 uchar   buf[6001];
13 short   cfreq[140];
14 short   wfreq[50];
15 int     nbuf;
16 Dir*    mbuf;
17 int     fd;
18 char    *fname;
19 char    *slash;
20
21 enum
22 {
23         Cword,
24         Fword,
25         Aword,
26         Alword,
27         Lword,
28         I1,
29         I2,
30         I3,
31         Clatin  = 128,
32         Cbinary,
33         Cnull,
34         Ceascii,
35         Cutf,
36 };
37 struct
38 {
39         char*   word;
40         int     class;
41 } dict[] =
42 {
43         "PATH",         Lword,
44         "TEXT",         Aword,
45         "adt",          Alword,
46         "aggr",         Alword,
47         "alef",         Alword,
48         "array",        Lword,
49         "block",        Fword,
50         "char",         Cword,
51         "common",       Fword,
52         "con",          Lword,
53         "data",         Fword,
54         "dimension",    Fword,
55         "double",       Cword,
56         "extern",       Cword,
57         "bio",          I2,
58         "float",        Cword,
59         "fn",           Lword,
60         "function",     Fword,
61         "h",            I3,
62         "implement",    Lword,
63         "import",       Lword,
64         "include",      I1,
65         "int",          Cword,
66         "integer",      Fword,
67         "iota",         Lword,
68         "libc",         I2,
69         "long",         Cword,
70         "module",       Lword,
71         "real",         Fword,
72         "ref",          Lword,
73         "register",     Cword,
74         "self",         Lword,
75         "short",        Cword,
76         "static",       Cword,
77         "stdio",        I2,
78         "struct",       Cword,
79         "subroutine",   Fword,
80         "u",            I2,
81         "void",         Cword,
82 };
83
84 /* codes for 'mode' field in language structure */
85 enum    {
86                 Normal  = 0,
87                 First,          /* first entry for language spanning several ranges */
88                 Multi,          /* later entries "   "       "  ... */
89                 Shared,         /* codes used in several languages */
90         };
91
92 struct
93 {
94         int     mode;           /* see enum above */
95         int     count;
96         int     low;
97         int     high;
98         char    *name;
99
100 } language[] =
101 {
102         Normal, 0,      0x0100, 0x01FF, "Extended Latin",
103         Normal, 0,      0x0370, 0x03FF, "Greek",
104         Normal, 0,      0x0400, 0x04FF, "Cyrillic",
105         Normal, 0,      0x0530, 0x058F, "Armenian",
106         Normal, 0,      0x0590, 0x05FF, "Hebrew",
107         Normal, 0,      0x0600, 0x06FF, "Arabic",
108         Normal, 0,      0x0900, 0x097F, "Devanagari",
109         Normal, 0,      0x0980, 0x09FF, "Bengali",
110         Normal, 0,      0x0A00, 0x0A7F, "Gurmukhi",
111         Normal, 0,      0x0A80, 0x0AFF, "Gujarati",
112         Normal, 0,      0x0B00, 0x0B7F, "Oriya",
113         Normal, 0,      0x0B80, 0x0BFF, "Tamil",
114         Normal, 0,      0x0C00, 0x0C7F, "Telugu",
115         Normal, 0,      0x0C80, 0x0CFF, "Kannada",
116         Normal, 0,      0x0D00, 0x0D7F, "Malayalam",
117         Normal, 0,      0x0E00, 0x0E7F, "Thai",
118         Normal, 0,      0x0E80, 0x0EFF, "Lao",
119         Normal, 0,      0x1000, 0x105F, "Tibetan",
120         Normal, 0,      0x10A0, 0x10FF, "Georgian",
121         Normal, 0,      0x3040, 0x30FF, "Japanese",
122         Normal, 0,      0x3100, 0x312F, "Chinese",
123         First,  0,      0x3130, 0x318F, "Korean",
124         Multi,  0,      0x3400, 0x3D2F, "Korean",
125         Shared, 0,      0x4e00, 0x9fff, "CJK",
126         Normal, 0,      0,      0,      0,              /* terminal entry */
127 };
128
129
130 enum
131 {
132         Fascii,         /* printable ascii */
133         Flatin,         /* latin 1*/
134         Futf,           /* UTF character set */
135         Fbinary,        /* binary */
136         Feascii,        /* ASCII with control chars */
137         Fnull,          /* NULL in file */
138 } guess;
139
140 void    bump_utf_count(Rune);
141 int     cistrncmp(char*, char*, int);
142 void    filetype(int);
143 int     getfontnum(uchar*, uchar**);
144 int     isas(void);
145 int     isc(void);
146 int     iscint(void);
147 int     isenglish(void);
148 int     ishp(void);
149 int     ishtml(void);
150 int     isrfc822(void);
151 int     ismbox(void);
152 int     islimbo(void);
153 int     ismung(void);
154 int     isp9bit(void);
155 int     isp9font(void);
156 int     isrtf(void);
157 int     ismsdos(void);
158 int     iself(void);
159 int     istring(void);
160 int     isoffstr(void);
161 int     iff(void);
162 int     long0(void);
163 int     longoff(void);
164 int     istar(void);
165 int     isface(void);
166 int     isexec(void);
167 int     p9bitnum(uchar*);
168 int     p9subfont(uchar*);
169 void    print_utf(void);
170 void    type(char*, int);
171 int     utf_count(void);
172 void    wordfreq(void);
173
174 int     (*call[])(void) =
175 {
176         long0,          /* recognizable by first 4 bytes */
177         istring,        /* recognizable by first string */
178         iself,          /* ELF (foreign) executable */
179         isexec,         /* native executables */
180         iff,            /* interchange file format (strings) */
181         longoff,        /* recognizable by 4 bytes at some offset */
182         isoffstr,       /* recognizable by string at some offset */
183         isrfc822,       /* email file */
184         ismbox,         /* mail box */
185         istar,          /* recognizable by tar checksum */
186         ishtml,         /* html keywords */
187         iscint,         /* compiler/assembler intermediate */
188         islimbo,        /* limbo source */
189         isc,            /* c & alef compiler key words */
190         isas,           /* assembler key words */
191         isp9font,       /* plan 9 font */
192         isp9bit,        /* plan 9 image (as from /dev/window) */
193         isrtf,          /* rich text format */
194         ismsdos,        /* msdos exe (virus file attachement) */
195         isface,         /* ascii face file */
196
197         /* last resorts */
198         ismung,         /* entropy compressed/encrypted */
199         isenglish,      /* char frequency English */
200         0
201 };
202
203 int mime;
204
205 char OCTET[] =  "application/octet-stream\n";
206 char PLAIN[] =  "text/plain\n";
207
208 void
209 main(int argc, char *argv[])
210 {
211         int i, j, maxlen;
212         char *cp;
213         Rune r;
214
215         ARGBEGIN{
216         case 'm':
217                 mime = 1;
218                 break;
219         default:
220                 fprint(2, "usage: file [-m] [file...]\n");
221                 exits("usage");
222         }ARGEND;
223
224         maxlen = 0;
225         if(mime == 0 || argc > 1){
226                 for(i = 0; i < argc; i++) {
227                         for (j = 0, cp = argv[i]; *cp; j++, cp += chartorune(&r, cp))
228                                         ;
229                         if(j > maxlen)
230                                 maxlen = j;
231                 }
232         }
233         if (argc <= 0) {
234                 if(!mime)
235                         print ("stdin: ");
236                 filetype(0);
237         }
238         else {
239                 for(i = 0; i < argc; i++)
240                         type(argv[i], maxlen);
241         }
242         exits(0);
243 }
244
245 void
246 type(char *file, int nlen)
247 {
248         Rune r;
249         int i;
250         char *p;
251
252         if(nlen > 0){
253                 slash = 0;
254                 for (i = 0, p = file; *p; i++) {
255                         if (*p == '/')                  /* find rightmost slash */
256                                 slash = p;
257                         p += chartorune(&r, p);         /* count runes */
258                 }
259                 print("%s:%*s",file, nlen-i+1, "");
260         }
261         fname = file;
262         if ((fd = open(file, OREAD)) < 0) {
263                 print("cannot open: %r\n");
264                 return;
265         }
266         filetype(fd);
267         close(fd);
268 }
269
270 /*
271  * Unicode 4.0 4-byte runes.
272  */
273 typedef int Rune1;
274
275 enum {
276         UTFmax1 = 4,
277 };
278
279 int
280 fullrune1(char *p, int n)
281 {
282         int c;
283
284         if(n >= 1) {
285                 c = *(uchar*)p;
286                 if(c < 0x80)
287                         return 1;
288                 if(n >= 2 && c < 0xE0)
289                         return 1;
290                 if(n >= 3 && c < 0xF0)
291                         return 1;
292                 if(n >= 4)
293                         return 1;
294         }
295         return 0;
296 }
297
298 int
299 chartorune1(Rune1 *rune, char *str)
300 {
301         int c, c1, c2, c3, n;
302         Rune r;
303
304         c = *(uchar*)str;
305         if(c < 0xF0){
306                 r = 0;
307                 n = chartorune(&r, str);
308                 *rune = r;
309                 return n;
310         }
311         c &= ~0xF0;
312         c1 = *(uchar*)(str+1) & ~0x80;
313         c2 = *(uchar*)(str+2) & ~0x80;
314         c3 = *(uchar*)(str+3) & ~0x80;
315         n = (c<<18) | (c1<<12) | (c2<<6) | c3;
316         if(n < 0x10000 || n > 0x10FFFF){
317                 *rune = Runeerror;
318                 return 1;
319         }
320         *rune = n;
321         return 4;
322 }
323
324 void
325 filetype(int fd)
326 {
327         Rune1 r;
328         int i, f, n;
329         char *p, *eob;
330
331         free(mbuf);
332         mbuf = dirfstat(fd);
333         if(mbuf == nil){
334                 print("cannot stat: %r\n");
335                 return;
336         }
337         if(mbuf->mode & DMDIR) {
338                 print(mime ? OCTET : "directory\n");
339                 return;
340         }
341         if(mbuf->type != 'M' && mbuf->type != '|') {
342                 print(mime ? OCTET : "special file #%C/%s\n",
343                         mbuf->type, mbuf->name);
344                 return;
345         }
346         /* may be reading a pipe on standard input */
347         nbuf = readn(fd, buf, sizeof(buf)-1);
348         if(nbuf < 0) {
349                 print("cannot read: %r\n");
350                 return;
351         }
352         if(nbuf == 0) {
353                 print(mime ? PLAIN : "empty file\n");
354                 return;
355         }
356         buf[nbuf] = 0;
357
358         /*
359          * build histogram table
360          */
361         memset(cfreq, 0, sizeof(cfreq));
362         for (i = 0; language[i].name; i++)
363                 language[i].count = 0;
364         eob = (char *)buf+nbuf;
365         for(n = 0, p = (char *)buf; p < eob; n++) {
366                 if (!fullrune1(p, eob-p) && eob-p < UTFmax1)
367                         break;
368                 p += chartorune1(&r, p);
369                 if (r == 0)
370                         f = Cnull;
371                 else if (r <= 0x7f) {
372                         if (!isprint(r) && !isspace(r))
373                                 f = Ceascii;    /* ASCII control char */
374                         else f = r;
375                 } else if (r == 0x80) {
376                         bump_utf_count(r);
377                         f = Cutf;
378                 } else if (r < 0xA0)
379                         f = Cbinary;    /* Invalid Runes */
380                 else if (r <= 0xff)
381                         f = Clatin;     /* Latin 1 */
382                 else {
383                         bump_utf_count(r);
384                         f = Cutf;               /* UTF extension */
385                 }
386                 cfreq[f]++;                     /* ASCII chars peg directly */
387         }
388         /*
389          * gross classify
390          */
391         if (cfreq[Cbinary])
392                 guess = Fbinary;
393         else if (cfreq[Cutf])
394                 guess = Futf;
395         else if (cfreq[Clatin])
396                 guess = Flatin;
397         else if (cfreq[Ceascii])
398                 guess = Feascii;
399         else if (cfreq[Cnull])
400                 guess = Fbinary;
401         else
402                 guess = Fascii;
403         /*
404          * lookup dictionary words
405          */
406         memset(wfreq, 0, sizeof(wfreq));
407         if(guess == Fascii || guess == Flatin || guess == Futf)
408                 wordfreq();
409         /*
410          * call individual classify routines
411          */
412         for(i=0; call[i]; i++)
413                 if((*call[i])())
414                         return;
415
416         /*
417          * if all else fails,
418          * print out gross classification
419          */
420         if (nbuf < 100 && !mime)
421                 print(mime ? PLAIN : "short ");
422         if (guess == Fascii)
423                 print(mime ? PLAIN : "Ascii\n");
424         else if (guess == Feascii)
425                 print(mime ? PLAIN : "extended ascii\n");
426         else if (guess == Flatin)
427                 print(mime ? PLAIN : "latin ascii\n");
428         else if (guess == Futf && utf_count() < 4)
429                 print_utf();
430         else print(mime ? OCTET : "binary\n");
431 }
432
433 void
434 bump_utf_count(Rune r)
435 {
436         int low, high, mid;
437
438         high = sizeof(language)/sizeof(language[0])-1;
439         for (low = 0; low < high;) {
440                 mid = (low+high)/2;
441                 if (r >= language[mid].low) {
442                         if (r <= language[mid].high) {
443                                 language[mid].count++;
444                                 break;
445                         } else low = mid+1;
446                 } else high = mid;
447         }
448 }
449
450 int
451 utf_count(void)
452 {
453         int i, count;
454
455         count = 0;
456         for (i = 0; language[i].name; i++)
457                 if (language[i].count > 0)
458                         switch (language[i].mode) {
459                         case Normal:
460                         case First:
461                                 count++;
462                                 break;
463                         default:
464                                 break;
465                         }
466         return count;
467 }
468
469 int
470 chkascii(void)
471 {
472         int i;
473
474         for (i = 'a'; i < 'z'; i++)
475                 if (cfreq[i])
476                         return 1;
477         for (i = 'A'; i < 'Z'; i++)
478                 if (cfreq[i])
479                         return 1;
480         return 0;
481 }
482
483 int
484 find_first(char *name)
485 {
486         int i;
487
488         for (i = 0; language[i].name != 0; i++)
489                 if (language[i].mode == First
490                         && strcmp(language[i].name, name) == 0)
491                         return i;
492         return -1;
493 }
494
495 void
496 print_utf(void)
497 {
498         int i, printed, j;
499
500         if(mime){
501                 print(PLAIN);
502                 return;
503         }
504         if (chkascii()) {
505                 printed = 1;
506                 print("Ascii");
507         } else
508                 printed = 0;
509         for (i = 0; language[i].name; i++)
510                 if (language[i].count) {
511                         switch(language[i].mode) {
512                         case Multi:
513                                 j = find_first(language[i].name);
514                                 if (j < 0)
515                                         break;
516                                 if (language[j].count > 0)
517                                         break;
518                                 /* Fall through */
519                         case Normal:
520                         case First:
521                                 if (printed)
522                                         print(" & ");
523                                 else printed = 1;
524                                 print("%s", language[i].name);
525                                 break;
526                         case Shared:
527                         default:
528                                 break;
529                         }
530                 }
531         if(!printed)
532                 print("UTF");
533         print(" text\n");
534 }
535
536 void
537 wordfreq(void)
538 {
539         int low, high, mid, r;
540         uchar *p, *p2, c;
541
542         p = buf;
543         for(;;) {
544                 while (p < buf+nbuf && !isalpha(*p))
545                         p++;
546                 if (p >= buf+nbuf)
547                         return;
548                 p2 = p;
549                 while(p < buf+nbuf && isalpha(*p))
550                         p++;
551                 c = *p;
552                 *p = 0;
553                 high = sizeof(dict)/sizeof(dict[0]);
554                 for(low = 0;low < high;) {
555                         mid = (low+high)/2;
556                         r = strcmp(dict[mid].word, (char*)p2);
557                         if(r == 0) {
558                                 wfreq[dict[mid].class]++;
559                                 break;
560                         }
561                         if(r < 0)
562                                 low = mid+1;
563                         else
564                                 high = mid;
565                 }
566                 *p++ = c;
567         }
568 }
569
570 typedef struct Filemagic Filemagic;
571 struct Filemagic {
572         ulong x;
573         ulong mask;
574         char *desc;
575         char *mime;
576 };
577
578 /*
579  * integers in this table must be as seen on a little-endian machine
580  * when read from a file.
581  */
582 Filemagic long0tab[] = {
583         0xF16DF16D,     0xFFFFFFFF,     "pac1 audio file\n",    OCTET,
584         /* "pac1" */
585         0x31636170,     0xFFFFFFFF,     "pac3 audio file\n",    OCTET,
586         /* "pXc2 */
587         0x32630070,     0xFFFF00FF,     "pac4 audio file\n",    OCTET,
588         0xBA010000,     0xFFFFFFFF,     "mpeg system stream\n", OCTET,
589         0x43614c66,     0xFFFFFFFF,     "FLAC audio file\n",    OCTET,
590         0x30800CC0,     0xFFFFFFFF,     "inferno .dis executable\n", OCTET,
591         0x04034B50,     0xFFFFFFFF,     "zip archive\n", "application/zip",
592         070707,         0xFFFF,         "cpio archive\n", "application/x-cpio",
593         0x2F7,          0xFFFF,         "tex dvi\n", "application/dvi",
594         0xfaff,         0xfeff,         "mp3 audio\n",  "audio/mpeg",
595         0xfeff0000,     0xffffffff,     "utf-32be\n",   "text/plain charset=utf-32be",
596         0xfffe,         0xffffffff,     "utf-32le\n",   "text/plain charset=utf-32le",
597         0xfeff,         0xffff,         "utf-16be\n",   "text/plain charset=utf-16be",
598         0xfffe,         0xffff,         "utf-16le\n",   "text/plain charset=utf-16le",
599         /* 0xfeedface: this could alternately be a Next Plan 9 boot image */
600         0xcefaedfe,     0xFFFFFFFF,     "32-bit power Mach-O executable\n", OCTET,
601         /* 0xfeedfacf */
602         0xcffaedfe,     0xFFFFFFFF,     "64-bit power Mach-O executable\n", OCTET,
603         /* 0xcefaedfe */
604         0xfeedface,     0xFFFFFFFF,     "386 Mach-O executable\n", OCTET,
605         /* 0xcffaedfe */
606         0xfeedfacf,     0xFFFFFFFF,     "amd64 Mach-O executable\n", OCTET,
607         /* 0xcafebabe */
608         0xbebafeca,     0xFFFFFFFF,     "Mach-O universal executable\n", OCTET,
609         /*
610          * venti & fossil magic numbers are stored big-endian on disk,
611          * thus the numbers appear reversed in this table.
612          */
613         0xad4e5cd1,     0xFFFFFFFF,     "venti arena\n", OCTET,
614 };
615
616 int
617 filemagic(Filemagic *tab, int ntab, ulong x)
618 {
619         int i;
620
621         for(i=0; i<ntab; i++)
622                 if((x&tab[i].mask) == tab[i].x){
623                         print(mime ? tab[i].mime : tab[i].desc);
624                         return 1;
625                 }
626         return 0;
627 }
628
629 int
630 long0(void)
631 {
632         return filemagic(long0tab, nelem(long0tab), LENDIAN(buf));
633 }
634
635 typedef struct Fileoffmag Fileoffmag;
636 struct Fileoffmag {
637         ulong   off;
638         Filemagic;
639 };
640
641 /*
642  * integers in this table must be as seen on a little-endian machine
643  * when read from a file.
644  */
645 Fileoffmag longofftab[] = {
646         /*
647          * venti & fossil magic numbers are stored big-endian on disk,
648          * thus the numbers appear reversed in this table.
649          */
650         256*1024, 0xe7a5e4a9, 0xFFFFFFFF, "venti arenas partition\n", OCTET,
651         256*1024, 0xc75e5cd1, 0xFFFFFFFF, "venti index section\n", OCTET,
652         128*1024, 0x89ae7637, 0xFFFFFFFF, "fossil write buffer\n", OCTET,
653         4,        0x31647542, 0xFFFFFFFF, "OS X finder properties\n", OCTET,
654 };
655
656 int
657 fileoffmagic(Fileoffmag *tab, int ntab)
658 {
659         int i;
660         ulong x;
661         Fileoffmag *tp;
662         uchar buf[sizeof(long)];
663
664         for(i=0; i<ntab; i++) {
665                 tp = tab + i;
666                 seek(fd, tp->off, 0);
667                 if (readn(fd, buf, sizeof buf) != sizeof buf)
668                         continue;
669                 x = LENDIAN(buf);
670                 if((x&tp->mask) == tp->x){
671                         print(mime? tp->mime: tp->desc);
672                         return 1;
673                 }
674         }
675         return 0;
676 }
677
678 int
679 longoff(void)
680 {
681         return fileoffmagic(longofftab, nelem(longofftab));
682 }
683
684 int
685 isexec(void)
686 {
687         Fhdr f;
688
689         seek(fd, 0, 0);         /* reposition to start of file */
690         if(crackhdr(fd, &f)) {
691                 print(mime ? OCTET : "%s\n", f.name);
692                 return 1;
693         }
694         return 0;
695 }
696
697
698 /* from tar.c */
699 enum { NAMSIZ = 100, TBLOCK = 512 };
700
701 union   hblock
702 {
703         char    dummy[TBLOCK];
704         struct  header
705         {
706                 char    name[NAMSIZ];
707                 char    mode[8];
708                 char    uid[8];
709                 char    gid[8];
710                 char    size[12];
711                 char    mtime[12];
712                 char    chksum[8];
713                 char    linkflag;
714                 char    linkname[NAMSIZ];
715                 /* rest are defined by POSIX's ustar format; see p1003.2b */
716                 char    magic[6];       /* "ustar" */
717                 char    version[2];
718                 char    uname[32];
719                 char    gname[32];
720                 char    devmajor[8];
721                 char    devminor[8];
722                 char    prefix[155];  /* if non-null, path = prefix "/" name */
723         } dbuf;
724 };
725
726 int
727 checksum(union hblock *hp)
728 {
729         int i;
730         char *cp;
731         struct header *hdr = &hp->dbuf;
732
733         for (cp = hdr->chksum; cp < &hdr->chksum[sizeof hdr->chksum]; cp++)
734                 *cp = ' ';
735         i = 0;
736         for (cp = hp->dummy; cp < &hp->dummy[TBLOCK]; cp++)
737                 i += *cp & 0xff;
738         return i;
739 }
740
741 int
742 istar(void)
743 {
744         int chksum;
745         char tblock[TBLOCK];
746         union hblock *hp = (union hblock *)tblock;
747         struct header *hdr = &hp->dbuf;
748
749         seek(fd, 0, 0);         /* reposition to start of file */
750         if (readn(fd, tblock, sizeof tblock) != sizeof tblock)
751                 return 0;
752         chksum = strtol(hdr->chksum, 0, 8);
753         if (hdr->name[0] != '\0' && checksum(hp) == chksum) {
754                 if (strcmp(hdr->magic, "ustar") == 0)
755                         print(mime? "application/x-ustar\n": "posix tar archive\n");
756                 else
757                         print(mime? "application/x-tar\n": "tar archive\n");
758                 return 1;
759         }
760         return 0;
761 }
762
763 /*
764  * initial words to classify file
765  */
766 struct  FILE_STRING
767 {
768         char    *key;
769         char    *filetype;
770         int     length;
771         char    *mime;
772 } file_string[] =
773 {
774         "\x1f\x9d",             "compressed",                   2,      "application/x-compress",
775         "\x1f\x8b",             "gzip compressed",              2,      "application/x-gzip",
776         "BZh",                  "bzip2 compressed",             3,      "application/x-bzip2",
777         "!<arch>\n__.SYMDEF",   "archive random library",       16,     "application/octet-stream",
778         "!<arch>\n",            "archive",                      8,      "application/octet-stream",
779         "070707",               "cpio archive - ascii header",  6,      "application/octet-stream",
780         "#!/bin/rc",            "rc executable file",           9,      "text/plain",
781         "#!/bin/sh",            "sh executable file",           9,      "text/plain",
782         "%!",                   "postscript",                   2,      "application/postscript",
783         "\004%!",               "postscript",                   3,      "application/postscript",
784         "x T post",             "troff output for post",        8,      "application/troff",
785         "x T Latin1",           "troff output for Latin1",      10,     "application/troff",
786         "x T utf",              "troff output for UTF",         7,      "application/troff",
787         "x T 202",              "troff output for 202",         7,      "application/troff",
788         "x T aps",              "troff output for aps",         7,      "application/troff",
789         "GIF",                  "GIF image",                    3,      "image/gif",
790         "\0PC Research, Inc\0", "ghostscript fax file",         18,     "application/ghostscript",
791         "%PDF",                 "PDF",                          4,      "application/pdf",
792         "<!DOCTYPE",            "HTML file",                    9,      "text/html",
793         "<!doctype",            "HTML file",                    9,      "text/html",
794         "<!--",                 "HTML file",                    4,      "text/html",
795         "<html>",               "HTML file",                    6,      "text/html",
796         "<HTML>",               "HTML file",                    6,      "text/html",
797         "<?xml",                "HTML file",                    5,      "text/html",
798         "\111\111\052\000",     "tiff",                         4,      "image/tiff",
799         "\115\115\000\052",     "tiff",                         4,      "image/tiff",
800         "\377\330\377\340",     "jpeg",                         4,      "image/jpeg",
801         "\377\330\377\341",     "jpeg",                         4,      "image/jpeg",
802         "\377\330\377\333",     "jpeg",                         4,      "image/jpeg",
803         "BM",                   "bmp",                          2,      "image/bmp", 
804         "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1",     "microsoft office document",    8,      "application/doc",
805         "<MakerFile ",          "FrameMaker file",              11,     "application/framemaker",
806         "\033E\033",    "HP PCL printer data",          3,      OCTET,
807         "\033%-12345X", "HPJCL file",           9,      "application/hpjcl",
808         "ID3",                  "mp3 audio with id3",   3,      "audio/mpeg",
809         "\211PNG",              "PNG image",            4,      "image/png",
810         "P3\n",                 "ppm",                          3,      "image/ppm",
811         "P6\n",                 "ppm",                          3,      "image/ppm",
812         "/* XPM */\n",  "xbm",                          10,     "image/xbm",
813         ".HTML ",               "troff -ms input",      6,      "text/troff",
814         ".LP",                  "troff -ms input",      3,      "text/troff",
815         ".ND",                  "troff -ms input",      3,      "text/troff",
816         ".PP",                  "troff -ms input",      3,      "text/troff",
817         ".TL",                  "troff -ms input",      3,      "text/troff",
818         ".TR",                  "troff -ms input",      3,      "text/troff",
819         ".TH",                  "manual page",          3,      "text/troff",
820         ".\\\"",                "troff input",          3,      "text/troff",
821         ".de",                  "troff input",          3,      "text/troff",
822         ".if",                  "troff input",          3,      "text/troff",
823         ".nr",                  "troff input",          3,      "text/troff",
824         ".tr",                  "troff input",          3,      "text/troff",
825         "vac:",                 "venti score",          4,      "text/plain",
826         "-----BEGIN CERTIFICATE-----\n",
827                                 "pem certificate",      -1,     "text/plain",
828         "-----BEGIN TRUSTED CERTIFICATE-----\n",
829                                 "pem trusted certificate", -1,  "text/plain",
830         "-----BEGIN X509 CERTIFICATE-----\n",
831                                 "pem x.509 certificate", -1,    "text/plain",
832         "subject=/C=",          "pem certificate with header", -1, "text/plain",
833         "process snapshot ",    "process snapshot",     -1,     "application/snapfs",
834         "d8:announce",          "torrent file",         11,     "application/x-bittorrent",
835         0,0,0,0
836 };
837
838 int
839 istring(void)
840 {
841         int i, l;
842         struct FILE_STRING *p;
843
844         for(p = file_string; p->key; p++) {
845                 l = p->length;
846                 if(l == -1)
847                         l = strlen(p->key);
848                 if(nbuf >= l && memcmp(buf, p->key, l) == 0) {
849                         if(mime)
850                                 print("%s\n", p->mime);
851                         else
852                                 print("%s\n", p->filetype);
853                         return 1;
854                 }
855         }
856         if(strncmp((char*)buf, "TYPE=", 5) == 0) {      /* td */
857                 for(i = 5; i < nbuf; i++)
858                         if(buf[i] == '\n')
859                                 break;
860                 if(mime)
861                         print(OCTET);
862                 else
863                         print("%.*s picture\n", utfnlen((char*)buf+5, i-5), (char*)buf+5);
864                 return 1;
865         }
866         return 0;
867 }
868
869 struct offstr
870 {
871         ulong   off;
872         struct FILE_STRING;
873 } offstrs[] = {
874         32*1024, "\001CD001\001",       "ISO9660 CD image",     7,      OCTET,
875         0, 0, 0, 0, 0
876 };
877
878 int
879 isoffstr(void)
880 {
881         int n;
882         char buf[256];
883         struct offstr *p;
884
885         for(p = offstrs; p->key; p++) {
886                 seek(fd, p->off, 0);
887                 n = p->length;
888                 if (n > sizeof buf)
889                         n = sizeof buf;
890                 if (readn(fd, buf, n) != n)
891                         continue;
892                 if(memcmp(buf, p->key, n) == 0) {
893                         if(mime)
894                                 print("%s\n", p->mime);
895                         else
896                                 print("%s\n", p->filetype);
897                         return 1;
898                 }
899         }
900         return 0;
901 }
902
903 int
904 iff(void)
905 {
906         if (strncmp((char*)buf, "FORM", 4) == 0 &&
907             strncmp((char*)buf+8, "AIFF", 4) == 0) {
908                 print("%s\n", mime? "audio/x-aiff": "aiff audio");
909                 return 1;
910         }
911         if (strncmp((char*)buf, "RIFF", 4) == 0) {
912                 if (strncmp((char*)buf+8, "WAVE", 4) == 0)
913                         print("%s\n", mime? "audio/wave": "wave audio");
914                 else if (strncmp((char*)buf+8, "AVI ", 4) == 0)
915                         print("%s\n", mime? "video/avi": "avi video");
916                 else
917                         print("%s\n", mime? "application/octet-stream":
918                                 "riff file");
919                 return 1;
920         }
921         return 0;
922 }
923
924 char*   html_string[] = {
925         "blockquote",
926         "!DOCTYPE", "![CDATA[", "basefont", "frameset", "noframes", "textarea",
927         "caption",
928         "button", "center", "iframe", "object", "option", "script",
929         "select", "strong",
930         "blink", "embed", "frame", "input", "label", "param", "small",
931         "style", "table", "tbody", "tfoot", "thead", "title",
932         "?xml", "body", "code", "font", "form", "head", "html",
933         "link", "menu", "meta", "span",
934         "!--", "big", "dir", "div", "img", "pre", "sub", "sup",
935         "br", "dd", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5",
936         "h6", "hr", "li", "ol", "td", "th", "tr", "tt", "ul",
937         "a", "b", "i", "p", "q", "u",
938         0,
939 };
940
941 int
942 ishtml(void)
943 {
944         int i, n, count;
945         uchar *p;
946
947         count = 0;
948         p = buf;
949         for(;;) {
950                 while(p < buf+nbuf && *p != '<')
951                         p++;
952                 p++;
953                 if (p >= buf+nbuf)
954                         break;
955                 if(*p == '/')
956                         p++;
957                 if(p >= buf+nbuf)
958                         break;
959                 for(i = 0; html_string[i]; i++){
960                         n = strlen(html_string[i]);
961                         if(p + n > buf+nbuf)
962                                 continue;
963                         if(cistrncmp(html_string[i], (char*)p, n) == 0) {
964                                 p += n;
965                                 if(p < buf+nbuf && strchr("\t\r\n />", *p)){
966                                         if(++count > 2) {
967                                                 print(mime ? "text/html\n" : "HTML file\n");
968                                                 return 1;
969                                         }
970                                 }
971                                 break;
972                         }
973                 }
974         }
975         return 0;
976 }
977
978 char*   rfc822_string[] =
979 {
980         "from:",
981         "date:",
982         "to:",
983         "subject:",
984         "received:",
985         "reply to:",
986         "sender:",
987         0,
988 };
989
990 int
991 isrfc822(void)
992 {
993
994         char *p, *q, *r;
995         int i, count;
996
997         count = 0;
998         p = (char*)buf;
999         for(;;) {
1000                 q = strchr(p, '\n');
1001                 if(q == nil)
1002                         break;
1003                 *q = 0;
1004                 if(p == (char*)buf && strncmp(p, "From ", 5) == 0 && strstr(p, " remote from ")){
1005                         count++;
1006                         *q = '\n';
1007                         p = q+1;
1008                         continue;
1009                 }
1010                 *q = '\n';
1011                 if(*p != '\t' && *p != ' '){
1012                         r = strchr(p, ':');
1013                         if(r == 0 || r > q)
1014                                 break;
1015                         for(i = 0; rfc822_string[i]; i++) {
1016                                 if(cistrncmp(p, rfc822_string[i], strlen(rfc822_string[i])) == 0){
1017                                         count++;
1018                                         break;
1019                                 }
1020                         }
1021                 }
1022                 p = q+1;
1023         }
1024         if(count >= 3){
1025                 print(mime ? "message/rfc822\n" : "email file\n");
1026                 return 1;
1027         }
1028         return 0;
1029 }
1030
1031 int
1032 ismbox(void)
1033 {
1034         char *p, *q;
1035
1036         p = (char*)buf;
1037         q = strchr(p, '\n');
1038         if(q == nil)
1039                 return 0;
1040         *q = 0;
1041         if(strncmp(p, "From ", 5) == 0 && strstr(p, " remote from ") == nil){
1042                 print(mime ? "text/plain\n" : "mail box\n");
1043                 return 1;
1044         }
1045         *q = '\n';
1046         return 0;
1047 }
1048
1049 int
1050 iscint(void)
1051 {
1052         int type;
1053         char *name;
1054         Biobuf b;
1055
1056         if(Binit(&b, fd, OREAD) == Beof)
1057                 return 0;
1058         seek(fd, 0, 0);
1059         type = objtype(&b, &name);
1060         if(type < 0)
1061                 return 0;
1062         if(mime)
1063                 print(OCTET);
1064         else
1065                 print("%s intermediate\n", name);
1066         return 1;
1067 }
1068
1069 int
1070 isc(void)
1071 {
1072         int n;
1073
1074         n = wfreq[I1];
1075         /*
1076          * includes
1077          */
1078         if(n >= 2 && wfreq[I2] >= n && wfreq[I3] >= n && cfreq['.'] >= n)
1079                 goto yes;
1080         if(n >= 1 && wfreq[Alword] >= n && wfreq[I3] >= n && cfreq['.'] >= n)
1081                 goto yes;
1082         /*
1083          * declarations
1084          */
1085         if(wfreq[Cword] >= 5 && cfreq[';'] >= 5)
1086                 goto yes;
1087         /*
1088          * assignments
1089          */
1090         if(cfreq[';'] >= 10 && cfreq['='] >= 10 && wfreq[Cword] >= 1)
1091                 goto yes;
1092         return 0;
1093
1094 yes:
1095         if(mime){
1096                 print(PLAIN);
1097                 return 1;
1098         }
1099         if(wfreq[Alword] > 0)
1100                 print("alef program\n");
1101         else
1102                 print("c program\n");
1103         return 1;
1104 }
1105
1106 int
1107 islimbo(void)
1108 {
1109
1110         /*
1111          * includes
1112          */
1113         if(wfreq[Lword] < 4)
1114                 return 0;
1115         print(mime ? PLAIN : "limbo program\n");
1116         return 1;
1117 }
1118
1119 int
1120 isas(void)
1121 {
1122
1123         /*
1124          * includes
1125          */
1126         if(wfreq[Aword] < 2)
1127                 return 0;
1128         print(mime ? PLAIN : "as program\n");
1129         return 1;
1130 }
1131
1132 /*
1133  * low entropy means encrypted
1134  */
1135 int
1136 ismung(void)
1137 {
1138         int i, bucket[8];
1139         float cs;
1140
1141         if(nbuf < 64)
1142                 return 0;
1143         memset(bucket, 0, sizeof(bucket));
1144         for(i=nbuf-64; i<nbuf; i++)
1145                 bucket[(buf[i]>>5)&07] += 1;
1146
1147         cs = 0.;
1148         for(i=0; i<8; i++)
1149                 cs += (bucket[i]-8)*(bucket[i]-8);
1150         cs /= 8.;
1151         if(cs <= 24.322) {
1152                 if(buf[0]==0x1f && buf[1]==0x9d)
1153                         print(mime ? "application/x-compress" : "compressed\n");
1154                 else
1155                 if(buf[0]==0x1f && buf[1]==0x8b)
1156                         print(mime ? "application/x-gzip" : "gzip compressed\n");
1157                 else
1158                 if(buf[0]=='B' && buf[1]=='Z' && buf[2]=='h')
1159                         print(mime ? "application/x-bzip2" : "bzip2 compressed\n");
1160                 else
1161                 if(buf[0]==0x78 && buf[1]==0x9c)
1162                         print(mime ? "application/x-deflate" : "zlib compressed\n");
1163                 else
1164                         print(mime ? OCTET : "encrypted\n");
1165                 return 1;
1166         }
1167         return 0;
1168 }
1169
1170 /*
1171  * english by punctuation and frequencies
1172  */
1173 int
1174 isenglish(void)
1175 {
1176         int vow, comm, rare, badpun, punct;
1177         char *p;
1178
1179         if(guess != Fascii && guess != Feascii)
1180                 return 0;
1181         badpun = 0;
1182         punct = 0;
1183         for(p = (char *)buf; p < (char *)buf+nbuf-1; p++)
1184                 switch(*p) {
1185                 case '.':
1186                 case ',':
1187                 case ')':
1188                 case '%':
1189                 case ';':
1190                 case ':':
1191                 case '?':
1192                         punct++;
1193                         if(p[1] != ' ' && p[1] != '\n')
1194                                 badpun++;
1195                 }
1196         if(badpun*5 > punct)
1197                 return 0;
1198         if(cfreq['>']+cfreq['<']+cfreq['/'] > cfreq['e'])       /* shell file test */
1199                 return 0;
1200         if(2*cfreq[';'] > cfreq['e'])
1201                 return 0;
1202
1203         vow = 0;
1204         for(p="AEIOU"; *p; p++) {
1205                 vow += cfreq[*p];
1206                 vow += cfreq[tolower(*p)];
1207         }
1208         comm = 0;
1209         for(p="ETAION"; *p; p++) {
1210                 comm += cfreq[*p];
1211                 comm += cfreq[tolower(*p)];
1212         }
1213         rare = 0;
1214         for(p="VJKQXZ"; *p; p++) {
1215                 rare += cfreq[*p];
1216                 rare += cfreq[tolower(*p)];
1217         }
1218         if(vow*5 >= nbuf-cfreq[' '] && comm >= 10*rare) {
1219                 print(mime ? PLAIN : "English text\n");
1220                 return 1;
1221         }
1222         return 0;
1223 }
1224
1225 /*
1226  * pick up a number with
1227  * syntax _*[0-9]+_
1228  */
1229 #define P9BITLEN        12
1230 int
1231 p9bitnum(uchar *bp)
1232 {
1233         int n, c, len;
1234
1235         len = P9BITLEN;
1236         while(*bp == ' ') {
1237                 bp++;
1238                 len--;
1239                 if(len <= 0)
1240                         return -1;
1241         }
1242         n = 0;
1243         while(len > 1) {
1244                 c = *bp++;
1245                 if(!isdigit(c))
1246                         return -1;
1247                 n = n*10 + c-'0';
1248                 len--;
1249         }
1250         if(*bp != ' ')
1251                 return -1;
1252         return n;
1253 }
1254
1255 int
1256 depthof(char *s, int *newp)
1257 {
1258         char *es;
1259         int d;
1260
1261         *newp = 0;
1262         es = s+12;
1263         while(s<es && *s==' ')
1264                 s++;
1265         if(s == es)
1266                 return -1;
1267         if('0'<=*s && *s<='9')
1268                 return 1<<strtol(s, 0, 0);
1269
1270         *newp = 1;
1271         d = 0;
1272         while(s<es && *s!=' '){
1273                 s++;                    /* skip letter */
1274                 d += strtoul(s, &s, 10);
1275         }
1276
1277         if(d % 8 == 0 || 8 % d == 0)
1278                 return d;
1279         else
1280                 return -1;
1281 }
1282
1283 int
1284 isp9bit(void)
1285 {
1286         int dep, lox, loy, hix, hiy, px, new, cmpr;
1287         ulong t;
1288         long len;
1289         char *newlabel;
1290         uchar *cp;
1291
1292         cp = buf;
1293         cmpr = 0;
1294         newlabel = "old ";
1295
1296         if(memcmp(cp, "compressed\n", 11) == 0) {
1297                 cmpr = 1;
1298                 cp = buf + 11;
1299         }
1300
1301         dep = depthof((char*)cp + 0*P9BITLEN, &new);
1302         if(new)
1303                 newlabel = "";
1304         lox = p9bitnum(cp + 1*P9BITLEN);
1305         loy = p9bitnum(cp + 2*P9BITLEN);
1306         hix = p9bitnum(cp + 3*P9BITLEN);
1307         hiy = p9bitnum(cp + 4*P9BITLEN);
1308         if(dep < 0 || lox < 0 || loy < 0 || hix < 0 || hiy < 0)
1309                 return 0;
1310
1311         if(dep < 8){
1312                 px = 8/dep;             /* pixels per byte */
1313                 /* set l to number of bytes of data per scan line */
1314                 if(lox >= 0)
1315                         len = (hix+px-1)/px - lox/px;
1316                 else{                   /* make positive before divide */
1317                         t = (-lox)+px-1;
1318                         t = (t/px)*px;
1319                         len = (t+hix+px-1)/px;
1320                 }
1321         }else
1322                 len = (hix-lox)*dep/8;
1323         len *= hiy - loy;               /* col length */
1324         len += 5 * P9BITLEN;            /* size of initial ascii */
1325
1326         /*
1327          * for compressed images, don't look any further. otherwise:
1328          * for image file, length is non-zero and must match calculation above.
1329          * for /dev/window and /dev/screen the length is always zero.
1330          * for subfont, the subfont header should follow immediately.
1331          */
1332         if (cmpr) {
1333                 print(mime ? "image/p9bit\n" : "Compressed %splan 9 image or subfont, depth %d\n",
1334                         newlabel, dep);
1335                 return 1;
1336         }
1337         /*
1338          * mbuf->length == 0 probably indicates reading a pipe.
1339          * Ghostscript sometimes produces a little extra on the end.
1340          */
1341         if (len != 0 && (mbuf->length == 0 || mbuf->length == len ||
1342             mbuf->length > len && mbuf->length < len+P9BITLEN)) {
1343                 print(mime ? "image/p9bit\n" : "%splan 9 image, depth %d\n", newlabel, dep);
1344                 return 1;
1345         }
1346         if (p9subfont(buf+len)) {
1347                 print(mime ? "image/p9bit\n" : "%ssubfont file, depth %d\n", newlabel, dep);
1348                 return 1;
1349         }
1350         return 0;
1351 }
1352
1353 int
1354 p9subfont(uchar *p)
1355 {
1356         int n, h, a;
1357
1358         /* if image too big, assume it's a subfont */
1359         if (p+3*P9BITLEN > buf+sizeof(buf))
1360                 return 1;
1361
1362         n = p9bitnum(p + 0*P9BITLEN);   /* char count */
1363         if (n < 0)
1364                 return 0;
1365         h = p9bitnum(p + 1*P9BITLEN);   /* height */
1366         if (h < 0)
1367                 return 0;
1368         a = p9bitnum(p + 2*P9BITLEN);   /* ascent */
1369         if (a < 0)
1370                 return 0;
1371         return 1;
1372 }
1373
1374 #define WHITESPACE(c)           ((c) == ' ' || (c) == '\t' || (c) == '\n')
1375
1376 int
1377 isp9font(void)
1378 {
1379         uchar *cp, *p;
1380         int i, n;
1381         char pathname[1024];
1382
1383         cp = buf;
1384         if (!getfontnum(cp, &cp))       /* height */
1385                 return 0;
1386         if (!getfontnum(cp, &cp))       /* ascent */
1387                 return 0;
1388         for (i = 0; cp=(uchar*)strchr((char*)cp, '\n'); i++) {
1389                 if (!getfontnum(cp, &cp))       /* min */
1390                         break;
1391                 if (!getfontnum(cp, &cp))       /* max */
1392                         return 0;
1393                 getfontnum(cp, &cp);    /* optional offset */
1394                 while (WHITESPACE(*cp))
1395                         cp++;
1396                 for (p = cp; *cp && !WHITESPACE(*cp); cp++)
1397                                 ;
1398                         /* construct a path name, if needed */
1399                 n = 0;
1400                 if (*p != '/' && slash) {
1401                         n = slash-fname+1;
1402                         if (n < sizeof(pathname))
1403                                 memcpy(pathname, fname, n);
1404                         else n = 0;
1405                 }
1406                 if (n+cp-p+4 < sizeof(pathname)) {
1407                         memcpy(pathname+n, p, cp-p);
1408                         n += cp-p;
1409                         pathname[n] = 0;
1410                         if (access(pathname, AEXIST) < 0) {
1411                                 strcpy(pathname+n, ".0");
1412                                 if (access(pathname, AEXIST) < 0)
1413                                         return 0;
1414                         }
1415                 }
1416         }
1417         if (i) {
1418                 print(mime ? "text/plain\n" : "font file\n");
1419                 return 1;
1420         }
1421         return 0;
1422 }
1423
1424 int
1425 getfontnum(uchar *cp, uchar **rp)
1426 {
1427         while (WHITESPACE(*cp))         /* extract ulong delimited by whitespace */
1428                 cp++;
1429         if (*cp < '0' || *cp > '9')
1430                 return 0;
1431         strtoul((char *)cp, (char **)rp, 0);
1432         if (!WHITESPACE(**rp)) {
1433                 *rp = cp;
1434                 return 0;
1435         }
1436         return 1;
1437 }
1438
1439 int
1440 isrtf(void)
1441 {
1442         if(strstr((char *)buf, "\\rtf1")){
1443                 print(mime ? "application/rtf\n" : "rich text format\n");
1444                 return 1;
1445         }
1446         return 0;
1447 }
1448
1449 int
1450 ismsdos(void)
1451 {
1452         if (buf[0] == 0x4d && buf[1] == 0x5a){
1453                 print(mime ? "application/x-msdownload\n" : "MSDOS executable\n");
1454                 return 1;
1455         }
1456         return 0;
1457 }
1458
1459 int
1460 iself(void)
1461 {
1462         static char *cpu[] = {          /* NB: incomplete and arbitary list */
1463         [1]     "WE32100",
1464         [2]     "SPARC",
1465         [3]     "i386",
1466         [4]     "M68000",
1467         [5]     "M88000",
1468         [6]     "i486",
1469         [7]     "i860",
1470         [8]     "R3000",
1471         [9]     "S370",
1472         [10]    "R4000",
1473         [15]    "HP-PA",
1474         [18]    "sparc v8+",
1475         [19]    "i960",
1476         [20]    "PPC-32",
1477         [21]    "PPC-64",
1478         [40]    "ARM",
1479         [41]    "Alpha",
1480         [43]    "sparc v9",
1481         [50]    "IA-64",
1482         [62]    "AMD64",
1483         [75]    "VAX",
1484         };
1485         static char *type[] = {
1486         [1]     "relocatable object",
1487         [2]     "executable",
1488         [3]     "shared library",
1489         [4]     "core dump",
1490         };
1491
1492         if (memcmp(buf, "\x7fELF", 4) == 0){
1493                 if (!mime){
1494                         int isdifend = 0;
1495                         int n = (buf[19] << 8) | buf[18];
1496                         char *p = "unknown";
1497                         char *t = "unknown";
1498
1499                         if (n > 0 && n < nelem(cpu) && cpu[n])
1500                                 p = cpu[n];
1501                         else {
1502                                 /* try the other byte order */
1503                                 isdifend = 1;
1504                                 n = (buf[18] << 8) | buf[19];
1505                                 if (n > 0 && n < nelem(cpu) && cpu[n])
1506                                         p = cpu[n];
1507                         }
1508                         if(isdifend)
1509                                 n = (buf[16]<< 8) | buf[17];
1510                         else
1511                                 n = (buf[17]<< 8) | buf[16];
1512
1513                         if(n>0 && n < nelem(type) && type[n])
1514                                 t = type[n];
1515                         print("%s ELF %s\n", p, t);
1516                 }
1517                 else
1518                         print("application/x-elf-executable");
1519                 return 1;
1520         }
1521
1522         return 0;
1523 }
1524
1525 int
1526 isface(void)
1527 {
1528         int i, j, ldepth, l;
1529         char *p;
1530
1531         ldepth = -1;
1532         for(j = 0; j < 3; j++){
1533                 for(p = (char*)buf, i=0; i<3; i++){
1534                         if(p[0] != '0' || p[1] != 'x')
1535                                 return 0;
1536                         if(buf[2+8] == ',')
1537                                 l = 2;
1538                         else if(buf[2+4] == ',')
1539                                 l = 1;
1540                         else
1541                                 return 0;
1542                         if(ldepth == -1)
1543                                 ldepth = l;
1544                         if(l != ldepth)
1545                                 return 0;
1546                         strtoul(p, &p, 16);
1547                         if(*p++ != ',')
1548                                 return 0;
1549                         while(*p == ' ' || *p == '\t')
1550                                 p++;
1551                 }
1552                 if (*p++ != '\n')
1553                         return 0;
1554         }
1555
1556         if(mime)
1557                 print("application/x-face\n");
1558         else
1559                 print("face image depth %d\n", ldepth);
1560         return 1;
1561 }
1562