]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/file.c
d42f99d47f0570da3baa436ac84d8b71d6bbed15
[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         0,0,0,0
835 };
836
837 int
838 istring(void)
839 {
840         int i, l;
841         struct FILE_STRING *p;
842
843         for(p = file_string; p->key; p++) {
844                 l = p->length;
845                 if(l == -1)
846                         l = strlen(p->key);
847                 if(nbuf >= l && memcmp(buf, p->key, l) == 0) {
848                         if(mime)
849                                 print("%s\n", p->mime);
850                         else
851                                 print("%s\n", p->filetype);
852                         return 1;
853                 }
854         }
855         if(strncmp((char*)buf, "TYPE=", 5) == 0) {      /* td */
856                 for(i = 5; i < nbuf; i++)
857                         if(buf[i] == '\n')
858                                 break;
859                 if(mime)
860                         print(OCTET);
861                 else
862                         print("%.*s picture\n", utfnlen((char*)buf+5, i-5), (char*)buf+5);
863                 return 1;
864         }
865         return 0;
866 }
867
868 struct offstr
869 {
870         ulong   off;
871         struct FILE_STRING;
872 } offstrs[] = {
873         32*1024, "\001CD001\001",       "ISO9660 CD image",     7,      OCTET,
874         0, 0, 0, 0, 0
875 };
876
877 int
878 isoffstr(void)
879 {
880         int n;
881         char buf[256];
882         struct offstr *p;
883
884         for(p = offstrs; p->key; p++) {
885                 seek(fd, p->off, 0);
886                 n = p->length;
887                 if (n > sizeof buf)
888                         n = sizeof buf;
889                 if (readn(fd, buf, n) != n)
890                         continue;
891                 if(memcmp(buf, p->key, n) == 0) {
892                         if(mime)
893                                 print("%s\n", p->mime);
894                         else
895                                 print("%s\n", p->filetype);
896                         return 1;
897                 }
898         }
899         return 0;
900 }
901
902 int
903 iff(void)
904 {
905         if (strncmp((char*)buf, "FORM", 4) == 0 &&
906             strncmp((char*)buf+8, "AIFF", 4) == 0) {
907                 print("%s\n", mime? "audio/x-aiff": "aiff audio");
908                 return 1;
909         }
910         if (strncmp((char*)buf, "RIFF", 4) == 0) {
911                 if (strncmp((char*)buf+8, "WAVE", 4) == 0)
912                         print("%s\n", mime? "audio/wave": "wave audio");
913                 else if (strncmp((char*)buf+8, "AVI ", 4) == 0)
914                         print("%s\n", mime? "video/avi": "avi video");
915                 else
916                         print("%s\n", mime? "application/octet-stream":
917                                 "riff file");
918                 return 1;
919         }
920         return 0;
921 }
922
923 char*   html_string[] =
924 {
925         "?xml",
926         "!doctype",
927         "html",
928         "head",
929         "title",
930         "link",
931         "meta",
932         "body",
933         "script",
934         "strong",
935         "input",
936         "table",
937         "form",
938         "font",
939         "div",
940         "h1",
941         "h2",
942         "h3",
943         "h4",
944         "h5",
945         "h6",
946         "ol",
947         "ul",
948         "li",
949         "dl",
950         "br",
951         "hr",
952         "em",
953         "th",
954         "tr",
955         "td",
956         "p",
957         "b",
958         "i",
959         "a",
960         0,
961 };
962
963 int
964 ishtml(void)
965 {
966         uchar *p, *q;
967         int i, count;
968
969                 /* compare strings between '<' and '>' to html table */
970         count = 0;
971         p = buf;
972         for(;;) {
973                 while (p < buf+nbuf && *p != '<')
974                         p++;
975                 p++;
976                 if (p >= buf+nbuf)
977                         break;
978                 if(*p == '/')
979                         p++;
980                 q = p;
981                 while(p < buf+nbuf && isalpha(*p))
982                         p++;
983                 if (p >= buf+nbuf)
984                         break;
985                 for(i = 0; html_string[i]; i++) {
986                         if(cistrncmp(html_string[i], (char*)q, p-q) == 0) {
987                                 if(++count > 2) {
988                                         print(mime ? "text/html\n" : "HTML file\n");
989                                         return 1;
990                                 }
991                                 break;
992                         }
993                 }
994                 p++;
995         }
996         return 0;
997 }
998
999 char*   rfc822_string[] =
1000 {
1001         "from:",
1002         "date:",
1003         "to:",
1004         "subject:",
1005         "received:",
1006         "reply to:",
1007         "sender:",
1008         0,
1009 };
1010
1011 int
1012 isrfc822(void)
1013 {
1014
1015         char *p, *q, *r;
1016         int i, count;
1017
1018         count = 0;
1019         p = (char*)buf;
1020         for(;;) {
1021                 q = strchr(p, '\n');
1022                 if(q == nil)
1023                         break;
1024                 *q = 0;
1025                 if(p == (char*)buf && strncmp(p, "From ", 5) == 0 && strstr(p, " remote from ")){
1026                         count++;
1027                         *q = '\n';
1028                         p = q+1;
1029                         continue;
1030                 }
1031                 *q = '\n';
1032                 if(*p != '\t' && *p != ' '){
1033                         r = strchr(p, ':');
1034                         if(r == 0 || r > q)
1035                                 break;
1036                         for(i = 0; rfc822_string[i]; i++) {
1037                                 if(cistrncmp(p, rfc822_string[i], strlen(rfc822_string[i])) == 0){
1038                                         count++;
1039                                         break;
1040                                 }
1041                         }
1042                 }
1043                 p = q+1;
1044         }
1045         if(count >= 3){
1046                 print(mime ? "message/rfc822\n" : "email file\n");
1047                 return 1;
1048         }
1049         return 0;
1050 }
1051
1052 int
1053 ismbox(void)
1054 {
1055         char *p, *q;
1056
1057         p = (char*)buf;
1058         q = strchr(p, '\n');
1059         if(q == nil)
1060                 return 0;
1061         *q = 0;
1062         if(strncmp(p, "From ", 5) == 0 && strstr(p, " remote from ") == nil){
1063                 print(mime ? "text/plain\n" : "mail box\n");
1064                 return 1;
1065         }
1066         *q = '\n';
1067         return 0;
1068 }
1069
1070 int
1071 iscint(void)
1072 {
1073         int type;
1074         char *name;
1075         Biobuf b;
1076
1077         if(Binit(&b, fd, OREAD) == Beof)
1078                 return 0;
1079         seek(fd, 0, 0);
1080         type = objtype(&b, &name);
1081         if(type < 0)
1082                 return 0;
1083         if(mime)
1084                 print(OCTET);
1085         else
1086                 print("%s intermediate\n", name);
1087         return 1;
1088 }
1089
1090 int
1091 isc(void)
1092 {
1093         int n;
1094
1095         n = wfreq[I1];
1096         /*
1097          * includes
1098          */
1099         if(n >= 2 && wfreq[I2] >= n && wfreq[I3] >= n && cfreq['.'] >= n)
1100                 goto yes;
1101         if(n >= 1 && wfreq[Alword] >= n && wfreq[I3] >= n && cfreq['.'] >= n)
1102                 goto yes;
1103         /*
1104          * declarations
1105          */
1106         if(wfreq[Cword] >= 5 && cfreq[';'] >= 5)
1107                 goto yes;
1108         /*
1109          * assignments
1110          */
1111         if(cfreq[';'] >= 10 && cfreq['='] >= 10 && wfreq[Cword] >= 1)
1112                 goto yes;
1113         return 0;
1114
1115 yes:
1116         if(mime){
1117                 print(PLAIN);
1118                 return 1;
1119         }
1120         if(wfreq[Alword] > 0)
1121                 print("alef program\n");
1122         else
1123                 print("c program\n");
1124         return 1;
1125 }
1126
1127 int
1128 islimbo(void)
1129 {
1130
1131         /*
1132          * includes
1133          */
1134         if(wfreq[Lword] < 4)
1135                 return 0;
1136         print(mime ? PLAIN : "limbo program\n");
1137         return 1;
1138 }
1139
1140 int
1141 isas(void)
1142 {
1143
1144         /*
1145          * includes
1146          */
1147         if(wfreq[Aword] < 2)
1148                 return 0;
1149         print(mime ? PLAIN : "as program\n");
1150         return 1;
1151 }
1152
1153 /*
1154  * low entropy means encrypted
1155  */
1156 int
1157 ismung(void)
1158 {
1159         int i, bucket[8];
1160         float cs;
1161
1162         if(nbuf < 64)
1163                 return 0;
1164         memset(bucket, 0, sizeof(bucket));
1165         for(i=nbuf-64; i<nbuf; i++)
1166                 bucket[(buf[i]>>5)&07] += 1;
1167
1168         cs = 0.;
1169         for(i=0; i<8; i++)
1170                 cs += (bucket[i]-8)*(bucket[i]-8);
1171         cs /= 8.;
1172         if(cs <= 24.322) {
1173                 if(buf[0]==0x1f && buf[1]==0x9d)
1174                         print(mime ? "application/x-compress" : "compressed\n");
1175                 else
1176                 if(buf[0]==0x1f && buf[1]==0x8b)
1177                         print(mime ? "application/x-gzip" : "gzip compressed\n");
1178                 else
1179                 if(buf[0]=='B' && buf[1]=='Z' && buf[2]=='h')
1180                         print(mime ? "application/x-bzip2" : "bzip2 compressed\n");
1181                 else
1182                         print(mime ? OCTET : "encrypted\n");
1183                 return 1;
1184         }
1185         return 0;
1186 }
1187
1188 /*
1189  * english by punctuation and frequencies
1190  */
1191 int
1192 isenglish(void)
1193 {
1194         int vow, comm, rare, badpun, punct;
1195         char *p;
1196
1197         if(guess != Fascii && guess != Feascii)
1198                 return 0;
1199         badpun = 0;
1200         punct = 0;
1201         for(p = (char *)buf; p < (char *)buf+nbuf-1; p++)
1202                 switch(*p) {
1203                 case '.':
1204                 case ',':
1205                 case ')':
1206                 case '%':
1207                 case ';':
1208                 case ':':
1209                 case '?':
1210                         punct++;
1211                         if(p[1] != ' ' && p[1] != '\n')
1212                                 badpun++;
1213                 }
1214         if(badpun*5 > punct)
1215                 return 0;
1216         if(cfreq['>']+cfreq['<']+cfreq['/'] > cfreq['e'])       /* shell file test */
1217                 return 0;
1218         if(2*cfreq[';'] > cfreq['e'])
1219                 return 0;
1220
1221         vow = 0;
1222         for(p="AEIOU"; *p; p++) {
1223                 vow += cfreq[*p];
1224                 vow += cfreq[tolower(*p)];
1225         }
1226         comm = 0;
1227         for(p="ETAION"; *p; p++) {
1228                 comm += cfreq[*p];
1229                 comm += cfreq[tolower(*p)];
1230         }
1231         rare = 0;
1232         for(p="VJKQXZ"; *p; p++) {
1233                 rare += cfreq[*p];
1234                 rare += cfreq[tolower(*p)];
1235         }
1236         if(vow*5 >= nbuf-cfreq[' '] && comm >= 10*rare) {
1237                 print(mime ? PLAIN : "English text\n");
1238                 return 1;
1239         }
1240         return 0;
1241 }
1242
1243 /*
1244  * pick up a number with
1245  * syntax _*[0-9]+_
1246  */
1247 #define P9BITLEN        12
1248 int
1249 p9bitnum(uchar *bp)
1250 {
1251         int n, c, len;
1252
1253         len = P9BITLEN;
1254         while(*bp == ' ') {
1255                 bp++;
1256                 len--;
1257                 if(len <= 0)
1258                         return -1;
1259         }
1260         n = 0;
1261         while(len > 1) {
1262                 c = *bp++;
1263                 if(!isdigit(c))
1264                         return -1;
1265                 n = n*10 + c-'0';
1266                 len--;
1267         }
1268         if(*bp != ' ')
1269                 return -1;
1270         return n;
1271 }
1272
1273 int
1274 depthof(char *s, int *newp)
1275 {
1276         char *es;
1277         int d;
1278
1279         *newp = 0;
1280         es = s+12;
1281         while(s<es && *s==' ')
1282                 s++;
1283         if(s == es)
1284                 return -1;
1285         if('0'<=*s && *s<='9')
1286                 return 1<<strtol(s, 0, 0);
1287
1288         *newp = 1;
1289         d = 0;
1290         while(s<es && *s!=' '){
1291                 s++;                    /* skip letter */
1292                 d += strtoul(s, &s, 10);
1293         }
1294
1295         if(d % 8 == 0 || 8 % d == 0)
1296                 return d;
1297         else
1298                 return -1;
1299 }
1300
1301 int
1302 isp9bit(void)
1303 {
1304         int dep, lox, loy, hix, hiy, px, new, cmpr;
1305         ulong t;
1306         long len;
1307         char *newlabel;
1308         uchar *cp;
1309
1310         cp = buf;
1311         cmpr = 0;
1312         newlabel = "old ";
1313
1314         if(memcmp(cp, "compressed\n", 11) == 0) {
1315                 cmpr = 1;
1316                 cp = buf + 11;
1317         }
1318
1319         dep = depthof((char*)cp + 0*P9BITLEN, &new);
1320         if(new)
1321                 newlabel = "";
1322         lox = p9bitnum(cp + 1*P9BITLEN);
1323         loy = p9bitnum(cp + 2*P9BITLEN);
1324         hix = p9bitnum(cp + 3*P9BITLEN);
1325         hiy = p9bitnum(cp + 4*P9BITLEN);
1326         if(dep < 0 || lox < 0 || loy < 0 || hix < 0 || hiy < 0)
1327                 return 0;
1328
1329         if(dep < 8){
1330                 px = 8/dep;             /* pixels per byte */
1331                 /* set l to number of bytes of data per scan line */
1332                 if(lox >= 0)
1333                         len = (hix+px-1)/px - lox/px;
1334                 else{                   /* make positive before divide */
1335                         t = (-lox)+px-1;
1336                         t = (t/px)*px;
1337                         len = (t+hix+px-1)/px;
1338                 }
1339         }else
1340                 len = (hix-lox)*dep/8;
1341         len *= hiy - loy;               /* col length */
1342         len += 5 * P9BITLEN;            /* size of initial ascii */
1343
1344         /*
1345          * for compressed images, don't look any further. otherwise:
1346          * for image file, length is non-zero and must match calculation above.
1347          * for /dev/window and /dev/screen the length is always zero.
1348          * for subfont, the subfont header should follow immediately.
1349          */
1350         if (cmpr) {
1351                 print(mime ? OCTET : "Compressed %splan 9 image or subfont, depth %d\n",
1352                         newlabel, dep);
1353                 return 1;
1354         }
1355         /*
1356          * mbuf->length == 0 probably indicates reading a pipe.
1357          * Ghostscript sometimes produces a little extra on the end.
1358          */
1359         if (len != 0 && (mbuf->length == 0 || mbuf->length == len ||
1360             mbuf->length > len && mbuf->length < len+P9BITLEN)) {
1361                 print(mime ? OCTET : "%splan 9 image, depth %d\n", newlabel, dep);
1362                 return 1;
1363         }
1364         if (p9subfont(buf+len)) {
1365                 print(mime ? OCTET : "%ssubfont file, depth %d\n", newlabel, dep);
1366                 return 1;
1367         }
1368         return 0;
1369 }
1370
1371 int
1372 p9subfont(uchar *p)
1373 {
1374         int n, h, a;
1375
1376         /* if image too big, assume it's a subfont */
1377         if (p+3*P9BITLEN > buf+sizeof(buf))
1378                 return 1;
1379
1380         n = p9bitnum(p + 0*P9BITLEN);   /* char count */
1381         if (n < 0)
1382                 return 0;
1383         h = p9bitnum(p + 1*P9BITLEN);   /* height */
1384         if (h < 0)
1385                 return 0;
1386         a = p9bitnum(p + 2*P9BITLEN);   /* ascent */
1387         if (a < 0)
1388                 return 0;
1389         return 1;
1390 }
1391
1392 #define WHITESPACE(c)           ((c) == ' ' || (c) == '\t' || (c) == '\n')
1393
1394 int
1395 isp9font(void)
1396 {
1397         uchar *cp, *p;
1398         int i, n;
1399         char pathname[1024];
1400
1401         cp = buf;
1402         if (!getfontnum(cp, &cp))       /* height */
1403                 return 0;
1404         if (!getfontnum(cp, &cp))       /* ascent */
1405                 return 0;
1406         for (i = 0; cp=(uchar*)strchr((char*)cp, '\n'); i++) {
1407                 if (!getfontnum(cp, &cp))       /* min */
1408                         break;
1409                 if (!getfontnum(cp, &cp))       /* max */
1410                         return 0;
1411                 getfontnum(cp, &cp);    /* optional offset */
1412                 while (WHITESPACE(*cp))
1413                         cp++;
1414                 for (p = cp; *cp && !WHITESPACE(*cp); cp++)
1415                                 ;
1416                         /* construct a path name, if needed */
1417                 n = 0;
1418                 if (*p != '/' && slash) {
1419                         n = slash-fname+1;
1420                         if (n < sizeof(pathname))
1421                                 memcpy(pathname, fname, n);
1422                         else n = 0;
1423                 }
1424                 if (n+cp-p+4 < sizeof(pathname)) {
1425                         memcpy(pathname+n, p, cp-p);
1426                         n += cp-p;
1427                         pathname[n] = 0;
1428                         if (access(pathname, AEXIST) < 0) {
1429                                 strcpy(pathname+n, ".0");
1430                                 if (access(pathname, AEXIST) < 0)
1431                                         return 0;
1432                         }
1433                 }
1434         }
1435         if (i) {
1436                 print(mime ? "text/plain\n" : "font file\n");
1437                 return 1;
1438         }
1439         return 0;
1440 }
1441
1442 int
1443 getfontnum(uchar *cp, uchar **rp)
1444 {
1445         while (WHITESPACE(*cp))         /* extract ulong delimited by whitespace */
1446                 cp++;
1447         if (*cp < '0' || *cp > '9')
1448                 return 0;
1449         strtoul((char *)cp, (char **)rp, 0);
1450         if (!WHITESPACE(**rp)) {
1451                 *rp = cp;
1452                 return 0;
1453         }
1454         return 1;
1455 }
1456
1457 int
1458 isrtf(void)
1459 {
1460         if(strstr((char *)buf, "\\rtf1")){
1461                 print(mime ? "application/rtf\n" : "rich text format\n");
1462                 return 1;
1463         }
1464         return 0;
1465 }
1466
1467 int
1468 ismsdos(void)
1469 {
1470         if (buf[0] == 0x4d && buf[1] == 0x5a){
1471                 print(mime ? "application/x-msdownload\n" : "MSDOS executable\n");
1472                 return 1;
1473         }
1474         return 0;
1475 }
1476
1477 int
1478 iself(void)
1479 {
1480         static char *cpu[] = {          /* NB: incomplete and arbitary list */
1481         [1]     "WE32100",
1482         [2]     "SPARC",
1483         [3]     "i386",
1484         [4]     "M68000",
1485         [5]     "M88000",
1486         [6]     "i486",
1487         [7]     "i860",
1488         [8]     "R3000",
1489         [9]     "S370",
1490         [10]    "R4000",
1491         [15]    "HP-PA",
1492         [18]    "sparc v8+",
1493         [19]    "i960",
1494         [20]    "PPC-32",
1495         [21]    "PPC-64",
1496         [40]    "ARM",
1497         [41]    "Alpha",
1498         [43]    "sparc v9",
1499         [50]    "IA-64",
1500         [62]    "AMD64",
1501         [75]    "VAX",
1502         };
1503         static char *type[] = {
1504         [1]     "relocatable object",
1505         [2]     "executable",
1506         [3]     "shared library",
1507         [4]     "core dump",
1508         };
1509
1510         if (memcmp(buf, "\x7fELF", 4) == 0){
1511                 if (!mime){
1512                         int isdifend = 0;
1513                         int n = (buf[19] << 8) | buf[18];
1514                         char *p = "unknown";
1515                         char *t = "unknown";
1516
1517                         if (n > 0 && n < nelem(cpu) && cpu[n])
1518                                 p = cpu[n];
1519                         else {
1520                                 /* try the other byte order */
1521                                 isdifend = 1;
1522                                 n = (buf[18] << 8) | buf[19];
1523                                 if (n > 0 && n < nelem(cpu) && cpu[n])
1524                                         p = cpu[n];
1525                         }
1526                         if(isdifend)
1527                                 n = (buf[16]<< 8) | buf[17];
1528                         else
1529                                 n = (buf[17]<< 8) | buf[16];
1530
1531                         if(n>0 && n < nelem(type) && type[n])
1532                                 t = type[n];
1533                         print("%s ELF %s\n", p, t);
1534                 }
1535                 else
1536                         print("application/x-elf-executable");
1537                 return 1;
1538         }
1539
1540         return 0;
1541 }
1542
1543 int
1544 isface(void)
1545 {
1546         int i, j, ldepth, l;
1547         char *p;
1548
1549         ldepth = -1;
1550         for(j = 0; j < 3; j++){
1551                 for(p = (char*)buf, i=0; i<3; i++){
1552                         if(p[0] != '0' || p[1] != 'x')
1553                                 return 0;
1554                         if(buf[2+8] == ',')
1555                                 l = 2;
1556                         else if(buf[2+4] == ',')
1557                                 l = 1;
1558                         else
1559                                 return 0;
1560                         if(ldepth == -1)
1561                                 ldepth = l;
1562                         if(l != ldepth)
1563                                 return 0;
1564                         strtoul(p, &p, 16);
1565                         if(*p++ != ',')
1566                                 return 0;
1567                         while(*p == ' ' || *p == '\t')
1568                                 p++;
1569                 }
1570                 if (*p++ != '\n')
1571                         return 0;
1572         }
1573
1574         if(mime)
1575                 print("application/x-face\n");
1576         else
1577                 print("face image depth %d\n", ldepth);
1578         return 1;
1579 }
1580