Drop global "image", _outof() -> a_sendout_file_a_pipe()..
[s-mailx.git] / mime_types.c
blobf124c539c2bf2bc9d3ae36e68a9419d97f5d7af8
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?mimetype' and other mime.types(5) related facilities.
3 *@ "Keep in sync with" ./mime.types.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #undef n_FILE
20 #define n_FILE mime_types
22 #ifndef HAVE_AMALGAMATION
23 # include "nail.h"
24 #endif
26 enum mime_type {
27 _MT_APPLICATION,
28 _MT_AUDIO,
29 _MT_IMAGE,
30 _MT_MESSAGE,
31 _MT_MULTIPART,
32 _MT_TEXT,
33 _MT_VIDEO,
34 _MT_OTHER,
35 __MT_TMIN = 0,
36 __MT_TMAX = _MT_OTHER,
37 __MT_TMASK = 0x07,
39 _MT_CMD = 1<< 8, /* Via `mimetype' (not struct mtbltin) */
40 _MT_USR = 1<< 9, /* MIME_TYPES_USR */
41 _MT_SYS = 1<<10, /* MIME_TYPES_SYS */
42 _MT_FSPEC = 1<<11, /* Loaded via f= *mimetypes-load-control* spec. */
44 _MT_PLAIN = 1<<16, /* Without pipe handler display as text */
45 _MT_SOUP_h = 2<<16, /* Ditto, but HTML tagsoup parser if possible */
46 _MT_SOUP_H = 3<<16, /* HTML tagsoup parser, else NOT plain text */
47 __MT_MARKMASK = _MT_SOUP_H
50 enum mime_type_class {
51 _MT_C_CLEAN = 0, /* Plain RFC 5322 message */
52 _MT_C_NCTT = 1<<0, /* *contenttype == NULL */
53 _MT_C_ISTXT = 1<<1, /* *contenttype =~ text\/ */
54 _MT_C_ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
55 _MT_C_HIGHBIT = 1<<3, /* Not 7bit clean */
56 _MT_C_LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
57 _MT_C_CTRLCHAR = 1<<5, /* Control characters seen */
58 _MT_C_HASNUL = 1<<6, /* Contains \0 characters */
59 _MT_C_NOTERMNL = 1<<7, /* Lacks a final newline */
60 _MT_C_FROM_ = 1<<8, /* ^From_ seen */
61 _MT_C_FROM_1STLINE = 1<<9, /* From_ line seen */
62 _MT_C_SUGGEST_DONE = 1<<16, /* Inspector suggests to stop further parse */
63 _MT_C__1STLINE = 1<<17 /* .. */
66 struct mtbltin {
67 ui32_t mtb_flags;
68 ui32_t mtb_mtlen;
69 char const *mtb_line;
72 struct mtnode {
73 struct mtnode *mt_next;
74 ui32_t mt_flags;
75 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
76 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
77 * that could already store character data here */
78 char const *mt_line;
81 struct mtlookup {
82 char const *mtl_name;
83 size_t mtl_nlen;
84 struct mtnode const *mtl_node;
85 char *mtl_result; /* If requested, salloc()ed MIME type */
88 struct mt_class_arg {
89 char const *mtca_buf;
90 size_t mtca_len;
91 ssize_t mtca_curlen;
92 char mtca_lastc;
93 char mtca_c;
94 enum mime_type_class mtca_mtc;
97 static struct mtbltin const _mt_bltin[] = {
98 #include "mime_types.h"
101 static char const _mt_typnames[][16] = {
102 "application/", "audio/", "image/",
103 "message/", "multipart/", "text/",
104 "video/"
106 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
107 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
108 _MT_VIDEO == 6);
110 /* */
111 static bool_t _mt_is_init;
112 static struct mtnode *_mt_list;
114 /* Initialize MIME type list in order */
115 static void _mt_init(void);
116 static bool_t __mt_load_file(ui32_t orflags,
117 char const *file, char **line, size_t *linesize);
119 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
120 * for `mimetype' */
121 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
122 char const *line, size_t len);
124 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
125 * if with_result >mtl_result will be created upon success for the former */
126 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
127 char const *name, bool_t with_result);
128 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
129 char const *mtname);
131 /* In-depth inspection of raw content: call _round() repeatedly, last time with
132 * a 0 length buffer, finally check .mtca_mtc for result.
133 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
134 * as the resulting classification is unambiguous */
135 SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
136 enum mime_type_class initval);
137 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
139 /* We need an in-depth inspection of an application/octet-stream part */
140 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp);
142 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
143 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
144 * isn't changed if mhp doesn't apply */
145 static enum mime_handler_flags _mt_pipe_check(struct mime_handler *mhp);
147 static void
148 _mt_init(void)
150 struct mtnode *tail;
151 char c, *line; /* TODO line pool (below) */
152 size_t linesize;
153 ui32_t i, j;
154 char const *srcs_arr[10], *ccp, **srcs;
155 NYD_ENTER;
157 /*if (_mt_is_init)
158 * goto jleave;*/
160 /* Always load our builtins */
161 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
162 struct mtbltin const *mtbp = _mt_bltin + i;
163 struct mtnode *mtnp = smalloc(sizeof *mtnp);
165 if (tail != NULL)
166 tail->mt_next = mtnp;
167 else
168 _mt_list = mtnp;
169 tail = mtnp;
170 mtnp->mt_next = NULL;
171 mtnp->mt_flags = mtbp->mtb_flags;
172 mtnp->mt_mtlen = mtbp->mtb_mtlen;
173 mtnp->mt_line = mtbp->mtb_line;
176 /* Decide which files sources have to be loaded */
177 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
178 ccp = "US";
179 else if (*ccp == '\0')
180 goto jleave;
182 srcs = srcs_arr + 2;
183 srcs[-1] = srcs[-2] = NULL;
185 if (strchr(ccp, '=') != NULL) {
186 line = savestr(ccp);
188 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
189 switch ((c = *ccp)) {
190 case 'S': case 's':
191 srcs_arr[1] = MIME_TYPES_SYS;
192 if (0) {
193 /* FALLTHRU */
194 case 'U': case 'u':
195 srcs_arr[0] = MIME_TYPES_USR;
197 if (ccp[1] != '\0')
198 goto jecontent;
199 break;
200 case 'F': case 'f':
201 if (*++ccp == '=' && *++ccp != '\0') {
202 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
203 *srcs++ = ccp;
204 else
205 n_err(_("*mimetypes-load-control*: too many sources, "
206 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
207 continue;
209 /* FALLTHRU */
210 default:
211 goto jecontent;
214 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
215 switch (c) {
216 case 'S': case 's': srcs_arr[1] = MIME_TYPES_SYS; break;
217 case 'U': case 'u': srcs_arr[0] = MIME_TYPES_USR; break;
218 default:
219 jecontent:
220 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
221 goto jleave;
224 /* Load all file-based sources in the desired order */
225 line = NULL;
226 linesize = 0;
227 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
228 i > 0; ++j, ++srcs, --i)
229 if (*srcs == NULL)
230 continue;
231 else if (!__mt_load_file((j == 0 ? _MT_USR
232 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
233 if ((n_poption & n_PO_D_V) || j > 1)
234 n_err(_("*mimetypes-load-control*: can't open or load %s\n"),
235 n_shexp_quote_cp(*srcs, FAL0));
237 if (line != NULL)
238 free(line);
239 jleave:
240 _mt_is_init = TRU1;
241 NYD_LEAVE;
244 static bool_t
245 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
247 char const *cp;
248 FILE *fp;
249 struct mtnode *head, *tail, *mtnp;
250 size_t len;
251 NYD_ENTER;
253 if ((cp = fexpand(file, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
254 (fp = Fopen(cp, "r")) == NULL) {
255 cp = NULL;
256 goto jleave;
259 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
260 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
261 if (head == NULL)
262 head = tail = mtnp;
263 else
264 tail->mt_next = mtnp;
265 tail = mtnp;
267 if (head != NULL) {
268 tail->mt_next = _mt_list;
269 _mt_list = head;
272 Fclose(fp);
273 jleave:
274 NYD_LEAVE;
275 return (cp != NULL);
278 static struct mtnode *
279 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
281 struct mtnode *mtnp = NULL;
282 char const *typ, *subtyp;
283 size_t tlen, i;
284 NYD_ENTER;
286 /* Drop anything after a comment first */
287 if ((typ = memchr(line, '#', len)) != NULL)
288 len = PTR2SIZE(typ - line);
290 /* Then trim any trailing whitespace from line (including NL/CR) */
291 while (len > 0 && spacechar(line[len - 1]))
292 --len;
294 /* Isolate MIME type, trim any whitespace from it */
295 while (len > 0 && blankchar(*line))
296 ++line, --len;
297 typ = line;
299 /* (But wait - is there a type marker?) */
300 if (!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@') {
301 if (len < 2)
302 goto jeinval;
303 if (typ[1] == ' ') {
304 orflags |= _MT_PLAIN;
305 typ += 2;
306 len -= 2;
307 line += 2;
308 } else if (len > 4 && typ[2] == '@' && typ[3] == ' ') {
309 switch (typ[1]) {
310 case 't': orflags |= _MT_PLAIN; goto jexttypmar;
311 case 'h': orflags |= _MT_SOUP_h; goto jexttypmar;
312 case 'H': orflags |= _MT_SOUP_H;
313 jexttypmar:
314 typ += 4;
315 len -= 4;
316 line += 4;
317 break;
318 default:
319 goto jeinval;
321 } else
322 goto jeinval;
325 while (len > 0 && !blankchar(*line))
326 ++line, --len;
327 /* Ignore empty lines and even incomplete specifications (only MIME type)
328 * because this is quite common in mime.types(5) files */
329 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
330 if (cmdcalled)
331 n_err(_("Empty MIME type or no extensions given: %s\n"),
332 (len == 0 ? _("(no value)") : line));
333 goto jleave;
336 if ((subtyp = memchr(typ, '/', tlen)) == NULL) {
337 jeinval:
338 if (cmdcalled || (n_poption & n_PO_D_V))
339 n_err(_("%s MIME type: %s\n"),
340 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")), typ);
341 goto jleave;
343 ++subtyp;
345 /* Map to mime_type */
346 tlen = PTR2SIZE(subtyp - typ);
347 for (i = __MT_TMIN;;) {
348 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
349 orflags |= i;
350 tlen = PTR2SIZE(line - subtyp);
351 typ = subtyp;
352 break;
354 if (++i == __MT_TMAX) {
355 orflags |= _MT_OTHER;
356 tlen = PTR2SIZE(line - typ);
357 break;
361 /* Strip leading whitespace from the list of extensions;
362 * trailing WS has already been trimmed away above.
363 * Be silent on slots which define a mimetype without any value */
364 while (len > 0 && blankchar(*line))
365 ++line, --len;
366 if (len == 0)
367 goto jleave;
369 /* */
370 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
371 mtnp->mt_next = NULL;
372 mtnp->mt_flags = orflags;
373 mtnp->mt_mtlen = (ui32_t)tlen;
374 { char *l = (char*)(mtnp + 1);
375 mtnp->mt_line = l;
376 memcpy(l, typ, tlen);
377 memcpy(l + tlen, line, len);
378 tlen += len;
379 l[tlen] = '\0';
382 jleave:
383 NYD_LEAVE;
384 return mtnp;
387 static struct mtlookup *
388 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
390 struct mtnode *mtnp;
391 size_t nlen, i, j;
392 char const *ext, *cp;
393 NYD2_ENTER;
395 memset(mtlp, 0, sizeof *mtlp);
397 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
398 goto jnull_leave;
399 /* We need a period TODO we should support names like README etc. */
400 for (i = nlen; name[--i] != '.';)
401 if (i == 0 || name[i] == '/') /* XXX no magics */
402 goto jnull_leave;
403 /* While here, basename() it */
404 while (i > 0 && name[i - 1] != '/')
405 --i;
406 name += i;
407 nlen -= i;
408 mtlp->mtl_name = name;
409 mtlp->mtl_nlen = nlen;
411 if (!_mt_is_init)
412 _mt_init();
414 /* ..all the MIME types */
415 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
416 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
417 cp = ext;
418 while (whitechar(*cp))
419 ++cp;
420 ext = cp;
421 while (!whitechar(*cp) && *cp != '\0')
422 ++cp;
424 if ((i = PTR2SIZE(cp - ext)) == 0)
425 break;
426 /* Don't allow neither of ".txt" or "txt" to match "txt" */
427 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
428 ascncasecmp(name + j, ext, i))
429 continue;
431 /* Found it */
432 mtlp->mtl_node = mtnp;
434 if (!with_result)
435 goto jleave;
437 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
438 name = n_empty;
439 j = 0;
440 } else {
441 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
442 j = strlen(name);
444 i = mtnp->mt_mtlen;
445 mtlp->mtl_result = salloc(i + j +1);
446 if (j > 0)
447 memcpy(mtlp->mtl_result, name, j);
448 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
449 mtlp->mtl_result[j += i] = '\0';
450 goto jleave;
452 jnull_leave:
453 mtlp = NULL;
454 jleave:
455 NYD2_LEAVE;
456 return mtlp;
459 static struct mtlookup *
460 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
462 struct mtnode *mtnp;
463 size_t nlen, i, j;
464 char const *cp;
465 NYD2_ENTER;
467 memset(mtlp, 0, sizeof *mtlp);
469 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
470 goto jnull_leave;
472 if (!_mt_is_init)
473 _mt_init();
475 /* ..all the MIME types */
476 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
477 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
478 cp = n_empty;
479 j = 0;
480 } else {
481 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
482 j = strlen(cp);
484 i = mtnp->mt_mtlen;
486 if (i + j == mtlp->mtl_nlen) {
487 char *xmt = ac_alloc(i + j +1);
488 if (j > 0)
489 memcpy(xmt, cp, j);
490 memcpy(xmt + j, mtnp->mt_line, i);
491 xmt[j += i] = '\0';
492 i = asccasecmp(mtname, xmt);
493 ac_free(xmt);
495 if (!i) {
496 /* Found it */
497 mtlp->mtl_node = mtnp;
498 goto jleave;
502 jnull_leave:
503 mtlp = NULL;
504 jleave:
505 NYD2_LEAVE;
506 return mtlp;
509 SINLINE struct mt_class_arg *
510 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
512 NYD2_ENTER;
513 memset(mtcap, 0, sizeof *mtcap);
514 mtcap->mtca_lastc = mtcap->mtca_c = EOF;
515 mtcap->mtca_mtc = initval | _MT_C__1STLINE;
516 NYD2_LEAVE;
517 return mtcap;
520 static enum mime_type_class
521 _mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
523 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
524 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
525 * TODO and report that state to the outer world */
526 #define F_ "From "
527 #define F_SIZEOF (sizeof(F_) -1)
528 char f_buf[F_SIZEOF], *f_p = f_buf;
529 char const *buf;
530 size_t blen;
531 ssize_t curlen;
532 int c, lastc;
533 enum mime_type_class mtc;
534 NYD2_ENTER;
536 buf = mtcap->mtca_buf;
537 blen = mtcap->mtca_len;
538 curlen = mtcap->mtca_curlen;
539 c = mtcap->mtca_c;
540 lastc = mtcap->mtca_lastc;
541 mtc = mtcap->mtca_mtc;
543 for (;; ++curlen) {
544 lastc = c;
545 if (blen == 0) {
546 /* Real EOF, or only current buffer end? */
547 if (mtcap->mtca_len == 0)
548 c = EOF;
549 else
550 break;
551 } else
552 c = (uc_i)*buf++;
553 --blen;
555 if (c == '\0') {
556 mtc |= _MT_C_HASNUL;
557 if (!(mtc & _MT_C_ISTXTCOK)) {
558 mtc |= _MT_C_SUGGEST_DONE;
559 break;
561 continue;
563 if (c == '\n' || c == EOF) {
564 mtc &= ~_MT_C__1STLINE;
565 if (curlen >= MIME_LINELEN_LIMIT)
566 mtc |= _MT_C_LONGLINES;
567 if (c == EOF) {
568 break;
570 f_p = f_buf;
571 curlen = -1;
572 continue;
574 /* A bit hairy is handling of \r=\x0D=CR.
575 * RFC 2045, 6.7:
576 * Control characters other than TAB, or CR and LF as parts of CRLF
577 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
578 * we cannot peek the next character. Thus right here, inspect the last
579 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
580 /*else*/ if (lastc == '\r')
581 mtc |= _MT_C_CTRLCHAR;
583 /* Control character? XXX this is all ASCII here */
584 if (c < 0x20 || c == 0x7F) {
585 /* RFC 2045, 6.7, as above ... */
586 if (c != '\t' && c != '\r')
587 mtc |= _MT_C_CTRLCHAR;
588 /* If there is a escape sequence in backslash notation defined for
589 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
590 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
591 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
592 * \e=\x1B=ESC */
593 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
594 continue;
595 mtc |= _MT_C_HASNUL; /* Force base64 */
596 if (!(mtc & _MT_C_ISTXTCOK)) {
597 mtc |= _MT_C_SUGGEST_DONE;
598 break;
600 } else if ((ui8_t)c & 0x80) {
601 mtc |= _MT_C_HIGHBIT;
602 /* TODO count chars with HIGHBIT? libmagic?
603 * TODO try encode part - base64 if bails? */
604 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
605 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
606 break;
608 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
609 *f_p++ = (char)c;
610 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
611 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
612 !memcmp(f_buf, F_, F_SIZEOF)){
613 mtc |= _MT_C_FROM_;
614 if (mtc & _MT_C__1STLINE)
615 mtc |= _MT_C_FROM_1STLINE;
619 if (c == EOF && lastc != '\n')
620 mtc |= _MT_C_NOTERMNL;
622 mtcap->mtca_curlen = curlen;
623 mtcap->mtca_lastc = lastc;
624 mtcap->mtca_c = c;
625 mtcap->mtca_mtc = mtc;
626 NYD2_LEAVE;
627 return mtc;
628 #undef F_
629 #undef F_SIZEOF
632 static enum mimecontent
633 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
635 struct str in = {NULL, 0}, outrest, inrest, dec;
636 struct mt_class_arg mtca;
637 bool_t did_inrest;
638 enum mime_type_class mtc;
639 int lc, c;
640 size_t cnt, lsz;
641 FILE *ibuf;
642 off_t start_off;
643 enum mimecontent mc;
644 NYD2_ENTER;
646 assert(mpp->m_mime_enc != MIMEE_BIN);
648 outrest = inrest = dec = in;
649 mc = MIME_UNKNOWN;
650 n_UNINIT(mtc, 0);
652 /* TODO v15-compat Note we actually bypass our usual file handling by
653 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
654 * TODO all of this, and until then doing it like this is the only option
655 * TODO to integrate nicely into whoever calls us */
656 start_off = ftell(mb.mb_itf);
657 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
658 jos_leave:
659 fseek(mb.mb_itf, start_off, SEEK_SET);
660 goto jleave;
662 cnt = mpp->m_size;
664 /* Skip part headers */
665 for (lc = '\0'; cnt > 0; lc = c, --cnt)
666 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
667 break;
668 if (cnt == 0 || ferror(ibuf))
669 goto jos_leave;
671 /* So now let's inspect the part content, decoding content-transfer-encoding
672 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
673 _mt_classify_init(&mtca, _MT_C_ISTXT);
675 for (lsz = 0;;) {
676 bool_t dobuf;
678 c = (--cnt == 0) ? EOF : getc(ibuf);
679 if ((dobuf = (c == '\n'))) {
680 /* Ignore empty lines */
681 if (lsz == 0)
682 continue;
683 } else if ((dobuf = (c == EOF))) {
684 if (lsz == 0 && outrest.l == 0)
685 break;
688 if (in.l + 1 >= lsz)
689 in.s = srealloc(in.s, lsz += LINESIZE);
690 if (c != EOF)
691 in.s[in.l++] = (char)c;
692 if (!dobuf)
693 continue;
695 jdobuf:
696 switch (mpp->m_mime_enc) {
697 case MIMEE_B64:
698 if (!b64_decode_part(&dec, &in, &outrest,
699 (did_inrest ? NULL : &inrest))) {
700 mtca.mtca_mtc = _MT_C_HASNUL;
701 goto jstopit; /* break;break; */
703 break;
704 case MIMEE_QP:
705 /* Drin */
706 if (!qp_decode_part(&dec, &in, &outrest, &inrest)) {
707 mtca.mtca_mtc = _MT_C_HASNUL;
708 goto jstopit; /* break;break; */
710 if (dec.l == 0 && c != EOF) {
711 in.l = 0;
712 continue;
714 break;
715 default:
716 /* Temporarily switch those two buffers.. */
717 dec = in;
718 in.s = NULL;
719 in.l = 0;
720 break;
723 mtca.mtca_buf = dec.s;
724 mtca.mtca_len = (ssize_t)dec.l;
725 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
726 mtc = _MT_C_HASNUL;
727 break;
730 if (c == EOF)
731 break;
732 /* ..and restore switched */
733 if (in.s == NULL) {
734 in = dec;
735 dec.s = NULL;
737 in.l = dec.l = 0;
740 if ((in.l = inrest.l) > 0) {
741 in.s = inrest.s;
742 did_inrest = TRU1;
743 goto jdobuf;
745 if (outrest.l > 0)
746 goto jdobuf;
747 jstopit:
748 if (in.s != NULL)
749 free(in.s);
750 if (dec.s != NULL)
751 free(dec.s);
752 if (outrest.s != NULL)
753 free(outrest.s);
754 if (inrest.s != NULL)
755 free(inrest.s);
757 fseek(mb.mb_itf, start_off, SEEK_SET);
759 if (!(mtc & (_MT_C_HASNUL | _MT_C_CTRLCHAR))) {
760 mc = MIME_TEXT_PLAIN;
761 if (mce & MIMECE_ALL_OVWR)
762 mpp->m_ct_type_plain = "text/plain";
763 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
764 mpp->m_ct_type_usr_ovwr = "text/plain";
766 jleave:
767 NYD2_LEAVE;
768 return mc;
771 static enum mime_handler_flags
772 _mt_pipe_check(struct mime_handler *mhp)
774 enum mime_handler_flags rv_orig, rv;
775 char const *cp;
776 NYD2_ENTER;
778 rv_orig = rv = mhp->mh_flags;
780 /* Do we have any handler for this part? */
781 if (*(cp = mhp->mh_shell_cmd) == '\0')
782 goto jleave;
783 else if (*cp++ != '@') {
784 rv |= MIME_HDL_CMD;
785 goto jleave;
786 } else if (*cp == '\0') {
787 rv |= MIME_HDL_TEXT;
788 goto jleave;
791 jnextc:
792 switch (*cp) {
793 case '*': rv |= MIME_HDL_ALWAYS; ++cp; goto jnextc;
794 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
795 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
796 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
797 case '+':
798 if (rv & MIME_HDL_TMPF)
799 rv |= MIME_HDL_TMPF_UNLINK;
800 rv |= MIME_HDL_TMPF;
801 ++cp;
802 goto jnextc;
803 case '=':
804 rv |= MIME_HDL_TMPF_FILL;
805 ++cp;
806 goto jnextc;
807 case '@':
808 ++cp;
809 /* FALLTHRU */
810 default:
811 break;
813 mhp->mh_shell_cmd = cp;
815 /* Implications */
816 if (rv & MIME_HDL_TMPF_FILL)
817 rv |= MIME_HDL_TMPF;
819 /* Exceptions */
820 if (rv & MIME_HDL_ISQUOTE) {
821 if (rv & MIME_HDL_NOQUOTE)
822 goto jerr;
824 /* Cannot fetch data back from asynchronous process */
825 if (rv & MIME_HDL_ASYNC)
826 goto jerr;
828 /* TODO Can't use a "needsterminal" program for quoting */
829 if (rv & MIME_HDL_NEEDSTERM)
830 goto jerr;
833 if (rv & MIME_HDL_NEEDSTERM) {
834 if (rv & MIME_HDL_ASYNC) {
835 n_err(_("MIME type handlers: can't use needsterminal and "
836 "x-nail-async together\n"));
837 goto jerr;
840 /* needsterminal needs a terminal */
841 if (!(n_psonce & n_PSO_INTERACTIVE))
842 goto jerr;
845 if (!(rv & MIME_HDL_ALWAYS) && !(n_pstate & n_PS_MSGLIST_DIRECT)) {
846 /* Viewing multiple messages in one go, don't block system */
847 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
848 _("[-- Directly address message only for display --]\n")));
849 rv |= MIME_HDL_MSG;
850 goto jleave;
853 rv |= MIME_HDL_CMD;
854 jleave:
855 mhp->mh_flags = rv;
856 NYD2_LEAVE;
857 return rv;
858 jerr:
859 rv = rv_orig;
860 goto jleave;
863 FL int
864 c_mimetype(void *v)
866 char **argv = v;
867 struct mtnode *mtnp;
868 NYD_ENTER;
870 if (!_mt_is_init)
871 _mt_init();
873 if (*argv == NULL) {
874 FILE *fp;
875 size_t l;
877 if (_mt_list == NULL) {
878 fprintf(n_stdout,
879 _("*mimetypes-load-control*: no mime.types(5) available\n"));
880 goto jleave;
883 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
884 NULL) {
885 n_perr(_("tmpfile"), 0);
886 v = NULL;
887 goto jleave;
890 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
891 char const *tmark, *typ;
893 switch (mtnp->mt_flags & __MT_MARKMASK) {
894 case _MT_PLAIN: tmark = "/t"; break;
895 case _MT_SOUP_h: tmark = "/h"; break;
896 case _MT_SOUP_H: tmark = "/H"; break;
897 default: tmark = " "; break;
899 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
900 ? n_empty : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
902 fprintf(fp, "%c%s %s%.*s %s\n",
903 (mtnp->mt_flags & _MT_USR ? 'U'
904 : (mtnp->mt_flags & _MT_SYS ? 'S'
905 : (mtnp->mt_flags & _MT_FSPEC ? 'F'
906 : (mtnp->mt_flags & _MT_CMD ? 'C' : 'B')))),
907 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
908 mtnp->mt_line + mtnp->mt_mtlen);
911 page_or_print(fp, l);
912 Fclose(fp);
913 } else {
914 for (; *argv != NULL; ++argv) {
915 mtnp = _mt_create(TRU1, _MT_CMD, *argv, strlen(*argv));
916 if (mtnp != NULL) {
917 mtnp->mt_next = _mt_list;
918 _mt_list = mtnp;
919 } else
920 v = NULL;
923 jleave:
924 NYD_LEAVE;
925 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
928 FL int
929 c_unmimetype(void *v)
931 char **argv = v;
932 struct mtnode *lnp, *mtnp;
933 bool_t match;
934 NYD_ENTER;
936 /* Need to load that first as necessary */
937 if (!_mt_is_init)
938 _mt_init();
940 for (; *argv != NULL; ++argv) {
941 if (!asccasecmp(*argv, "reset")) {
942 _mt_is_init = FAL0;
943 goto jdelall;
946 if (argv[0][0] == '*' && argv[0][1] == '\0') {
947 jdelall:
948 while ((mtnp = _mt_list) != NULL) {
949 _mt_list = mtnp->mt_next;
950 free(mtnp);
952 continue;
955 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
956 char const *typ;
957 char *val;
958 size_t i;
960 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
961 typ = n_empty;
962 i = 0;
963 } else {
964 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
965 i = strlen(typ);
968 val = ac_alloc(i + mtnp->mt_mtlen +1);
969 memcpy(val, typ, i);
970 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
971 val[i += mtnp->mt_mtlen] = '\0';
972 i = asccasecmp(val, *argv);
973 ac_free(val);
975 if (!i) {
976 struct mtnode *nnp = mtnp->mt_next;
977 if (lnp == NULL)
978 _mt_list = nnp;
979 else
980 lnp->mt_next = nnp;
981 free(mtnp);
982 mtnp = nnp;
983 match = TRU1;
984 } else
985 lnp = mtnp, mtnp = mtnp->mt_next;
987 if (!match) {
988 if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
989 n_err(_("No such MIME type: %s\n"), *argv);
990 v = NULL;
993 NYD_LEAVE;
994 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
997 FL bool_t
998 mime_type_check_mtname(char const *name)
1000 struct mtlookup mtl;
1001 bool_t rv;
1002 NYD_ENTER;
1004 rv = (_mt_by_mtname(&mtl, name) != NULL);
1005 NYD_LEAVE;
1006 return rv;
1009 FL char *
1010 mime_type_classify_filename(char const *name)
1012 struct mtlookup mtl;
1013 NYD_ENTER;
1015 _mt_by_filename(&mtl, name, TRU1);
1016 NYD_LEAVE;
1017 return mtl.mtl_result;
1020 FL enum conversion
1021 mime_type_classify_file(FILE *fp, char const **contenttype,
1022 char const **charset, int *do_iconv)
1024 /* TODO classify once only PLEASE PLEASE PLEASE */
1025 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1026 * TODO binary according to RFC 2046, 5.2.1
1027 * TODO The handling of which is a hack */
1028 bool_t rfc822;
1029 enum mime_type_class mtc;
1030 enum mime_enc menc;
1031 off_t fpsz;
1032 enum conversion c;
1033 NYD_ENTER;
1035 assert(ftell(fp) == 0x0l);
1037 *do_iconv = 0;
1039 if (*contenttype == NULL) {
1040 mtc = _MT_C_NCTT;
1041 rfc822 = FAL0;
1042 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1043 mtc = ok_blook(mime_allow_text_controls)
1044 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1045 rfc822 = FAL0;
1046 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1047 mtc = _MT_C_ISTXT;
1048 rfc822 = TRU1;
1049 } else {
1050 mtc = _MT_C_CLEAN;
1051 rfc822 = FAL0;
1054 menc = mime_enc_target();
1056 if ((fpsz = fsize(fp)) == 0)
1057 goto j7bit;
1058 else {
1059 char buf[BUFFER_SIZE];
1060 struct mt_class_arg mtca;
1062 _mt_classify_init(&mtca, mtc);
1063 for (;;) {
1064 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1065 mtca.mtca_buf = buf;
1066 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1067 break;
1068 if (mtca.mtca_len == 0)
1069 break;
1071 /* TODO ferror(fp) ! */
1072 rewind(fp);
1075 if (mtc & _MT_C_HASNUL) {
1076 menc = MIMEE_B64;
1077 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1078 * on request; else enforce what file(1)/libmagic(3) would suggest */
1079 if (mtc & _MT_C_ISTXTCOK)
1080 goto jcharset;
1081 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1082 *contenttype = "application/octet-stream";
1083 if (*charset == NULL)
1084 *charset = "binary";
1085 goto jleave;
1088 if (mtc &
1089 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1090 if (menc != MIMEE_B64)
1091 menc = MIMEE_QP;
1092 goto jstepi;
1094 if (mtc & _MT_C_HIGHBIT) {
1095 jstepi:
1096 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1097 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1098 } else
1099 j7bit:
1100 menc = MIMEE_7B;
1101 if (mtc & _MT_C_NCTT)
1102 *contenttype = "text/plain";
1104 /* Not an attachment with specified charset? */
1105 jcharset:
1106 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1107 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1108 : ok_vlook(charset_7bit);
1109 jleave:
1110 /* TODO mime_type_file_classify() shouldn't return conversion */
1111 if (rfc822) {
1112 if (mtc & _MT_C_FROM_1STLINE) {
1113 n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
1114 "indeed is a RFC 4155 MBOX!\n"
1115 " Forcing a content-type of application/mbox!\n"),
1116 n_uagent);
1117 *contenttype = "application/mbox";
1118 goto jnorfc822;
1120 c = (menc == MIMEE_7B ? CONV_7BIT
1121 : (menc == MIMEE_8B ? CONV_8BIT
1122 : CONV_NONE));
1123 } else
1124 jnorfc822:
1125 c = (menc == MIMEE_7B ? CONV_7BIT
1126 : (menc == MIMEE_8B ? CONV_8BIT
1127 : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1128 NYD_LEAVE;
1129 return c;
1132 FL enum mimecontent
1133 mime_type_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1135 struct mtlookup mtl;
1136 enum mimecontent mc;
1137 char const *ct;
1138 union {char const *cp; ui32_t f;} mce;
1139 bool_t is_os;
1140 NYD_ENTER;
1142 mc = MIME_UNKNOWN;
1143 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1144 ct = n_empty;
1146 if((mce.cp = ok_vlook(mime_counter_evidence)) != NULL && *mce.cp != '\0'){
1147 if((n_idec_ui32_cp(&mce.f, mce.cp, 0, NULL
1148 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1149 ) != n_IDEC_STATE_CONSUMED){
1150 n_err(_("Invalid *mime-counter-evidence* value content\n"));
1151 is_os = FAL0;
1152 }else{
1153 mce.f |= MIMECE_SET;
1154 is_os = !asccasecmp(ct, "application/octet-stream");
1156 if(mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))){
1157 if(_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL){
1158 if(is_os)
1159 goto jos_content_check;
1160 }else if(is_os || asccasecmp(ct, mtl.mtl_result)){
1161 if(mce.f & MIMECE_ALL_OVWR)
1162 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1163 if(mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1164 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1168 }else
1169 is_os = FAL0;
1171 if(strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1172 mc = MIME_TEXT;
1173 else if(is_asccaseprefix("text/", ct)){
1174 ct += sizeof("text/") -1;
1175 if(!asccasecmp(ct, "plain"))
1176 mc = MIME_TEXT_PLAIN;
1177 else if(!asccasecmp(ct, "html"))
1178 mc = MIME_TEXT_HTML;
1179 else
1180 mc = MIME_TEXT;
1181 }else if(is_asccaseprefix("message/", ct)){
1182 ct += sizeof("message/") -1;
1183 if(!asccasecmp(ct, "rfc822"))
1184 mc = MIME_822;
1185 else
1186 mc = MIME_MESSAGE;
1187 }else if(is_asccaseprefix("multipart/", ct)){
1188 struct multi_types{
1189 char mt_name[12];
1190 enum mimecontent mt_mc;
1191 } const mta[] = {
1192 {"alternative\0", MIME_ALTERNATIVE},
1193 {"related", MIME_RELATED},
1194 {"digest", MIME_DIGEST},
1195 {"signed", MIME_SIGNED},
1196 {"encrypted", MIME_ENCRYPTED}
1197 }, *mtap;
1199 for(ct += sizeof("multipart/") -1, mtap = mta;;)
1200 if(!asccasecmp(ct, mtap->mt_name)){
1201 mc = mtap->mt_mc;
1202 break;
1203 }else if(++mtap == mta + n_NELEM(mta)){
1204 mc = MIME_MULTI;
1205 break;
1207 }else if(is_asccaseprefix("application/", ct)){
1208 if(is_os)
1209 goto jos_content_check;
1210 ct += sizeof("application/") -1;
1211 if(!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1212 mc = MIME_PKCS7;
1214 jleave:
1215 NYD_LEAVE;
1216 return mc;
1218 jos_content_check:
1219 if((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1220 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1221 mc = _mt_classify_os_part(mce.f, mpp);
1222 goto jleave;
1225 FL enum mime_handler_flags
1226 mime_type_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1227 enum sendaction action)
1229 #define __S "pipe-"
1230 #define __L (sizeof(__S) -1)
1231 struct mtlookup mtl;
1232 char *buf, *cp;
1233 enum mime_handler_flags rv;
1234 char const *es, *cs, *ccp;
1235 size_t el, cl, l;
1236 NYD_ENTER;
1238 memset(mhp, 0, sizeof *mhp);
1239 buf = NULL;
1241 rv = MIME_HDL_NULL;
1242 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1243 rv |= MIME_HDL_ISQUOTE;
1244 else if (action != SEND_TODISP && action != SEND_TODISP_ALL)
1245 goto jleave;
1247 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1248 *++es != '\0') ? strlen(es) : 0;
1249 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1250 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1251 if ((l = n_MAX(el, cl)) == 0) {
1252 /* TODO this should be done during parse time! */
1253 goto jleave;
1256 /* We don't pass the flags around, so ensure carrier is up-to-date */
1257 mhp->mh_flags = rv;
1259 buf = ac_alloc(__L + l +1);
1260 memcpy(buf, __S, __L);
1262 /* File-extension handlers take precedence.
1263 * Yes, we really "fail" here for file extensions which clash MIME types */
1264 if (el > 0) {
1265 memcpy(buf + __L, es, el +1);
1266 for (cp = buf + __L; *cp != '\0'; ++cp)
1267 *cp = lowerconv(*cp);
1269 if ((mhp->mh_shell_cmd = ccp = n_var_vlook(buf, FAL0)) != NULL) {
1270 rv = _mt_pipe_check(mhp);
1271 goto jleave;
1275 /* Then MIME Content-Type:, if any */
1276 if (cl == 0)
1277 goto jleave;
1279 memcpy(buf + __L, cs, cl +1);
1280 for (cp = buf + __L; *cp != '\0'; ++cp)
1281 *cp = lowerconv(*cp);
1283 if ((mhp->mh_shell_cmd = n_var_vlook(buf, FAL0)) != NULL) {
1284 rv = _mt_pipe_check(mhp);
1285 goto jleave;
1288 if (_mt_by_mtname(&mtl, cs) != NULL)
1289 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
1290 #ifndef HAVE_FILTER_HTML_TAGSOUP
1291 case _MT_SOUP_H:
1292 break;
1293 #endif
1294 case _MT_SOUP_h:
1295 #ifdef HAVE_FILTER_HTML_TAGSOUP
1296 case _MT_SOUP_H:
1297 mhp->mh_ptf = &htmlflt_process_main;
1298 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1299 n_UNCONST(_("Builtin HTML tagsoup filter")));
1300 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1301 goto jleave;
1302 #endif
1303 /* FALLTHRU */
1304 case _MT_PLAIN:
1305 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1306 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1307 goto jleave;
1308 default:
1309 break;
1312 jleave:
1313 if (buf != NULL)
1314 ac_free(buf);
1316 mhp->mh_flags = rv;
1317 if ((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL)
1318 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1319 _("[-- No MIME handler installed or none applicable --]")));
1320 NYD_LEAVE;
1321 return rv;
1322 #undef __L
1323 #undef __S
1326 /* s-it-mode */