home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / digest.c < prev    next >
C/C++ Source or Header  |  1996-08-04  |  13KB  |  561 lines

  1. /*
  2.  *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  3.  *
  4.  *    Digest article handling
  5.  *
  6.  *    The code to do the selective parsing of mail and mmdf formats,
  7.  *    mail from lines and determining folder types is based on patches
  8.  *    contributed by Bernd Wechner (bernd@bhpcpd.kembla.oz.au).
  9.  */
  10.  
  11. #include "config.h"
  12. #include "news.h"
  13. #include "debug.h"
  14.  
  15. /* digest.c */
  16.  
  17. static char **dg_hdr_field __APROTO((register char *lp, int all));
  18.  
  19. export int strict_from_parse = 2;
  20.  
  21. #ifdef DG_TEST
  22.  
  23. #define TEST0(fmt)     
  24. #define TEST1(fmt, x)    if (Debug & DG_TEST) printf(fmt, x)
  25. #define TEST2(fmt, x, y) if (Debug & DG_TEST) printf(fmt, x, y)
  26.  
  27. #else
  28.  
  29. #define TEST0(fmt, x)
  30. #define TEST1(fmt, x)
  31. #define TEST2(fmt, x, y)
  32.  
  33. #endif
  34.  
  35. #define UNIFY 040
  36.  
  37. static char digest_pattern[] = "igest";
  38.  
  39. void
  40. init_digest_parsing()
  41. {
  42.     register char *m;
  43.  
  44.     for (m = digest_pattern; *m; m++) *m |= UNIFY;
  45. }
  46.  
  47. int
  48. is_digest(subject)
  49. register char *subject;
  50. {
  51.     register char c, *q, *m;
  52.  
  53.     if (subject == NULL) return 0;
  54.  
  55.     while ((c = *subject++)) {
  56.     if ((c | UNIFY) != ('d' | UNIFY)) continue;
  57.  
  58.     q = subject; m = digest_pattern;
  59.     while ((c = *m++) && (*q++ | UNIFY) == c);
  60.     if (c == NUL) return 1;
  61.     }
  62.     return 0;
  63. }
  64.  
  65.  
  66. /*
  67.  * is_mail_from_line - Is this a legal unix mail "From " line?
  68.  *
  69.  * Given a line of input will check to see if it matches the standard
  70.  * unix mail "from " header format. Returns 0 if it does and <0 if not.
  71.  *
  72.  * The check may be very lax or very strict depending upon
  73.  * the value of "strict-mail-from-parse":
  74.  *
  75.  * 0 - Lax, checks only for the string "From ".
  76.  * 1 - Strict, checks that the correct number of fields are present.
  77.  * 2 - Very strict, also checks that each field contains a legal value.
  78.  *
  79.  * Assumptions: Not having the definitive unix mailbox reference I have
  80.  * assumed that unix mailbox headers follow this format:
  81.  *
  82.  * From <person> <date> <garbage>
  83.  *
  84.  * Where <person> is the address of the sender, being an ordinary
  85.  * string with no white space imbedded in it, and <date> is the date of
  86.  * posting, in ctime(3C) format.
  87.  *
  88.  * This would, on the face of it, seem valid. I (Bernd) have yet to find a
  89.  * unix mailbox header which doesn't follow this format.
  90.  *
  91.  * From: Bernd Wechner (bernd@bhpcpd.kembla.oz.au)
  92.  * Obfuscated by: KFS (as usual)
  93.  */
  94.  
  95. #define MAX_FIELDS 10
  96.  
  97. static char legal_day[]        = "SunMonTueWedThuFriSat";
  98. static char legal_month[]    = "JanFebMarAprMayJunJulAugSepOctNovDec";
  99. static int  legal_numbers[]    = { 1, 31, 0, 23, 0, 59, 0, 60, 1969, 2199 };
  100.  
  101. int is_mail_from_line(line, namebuf)
  102. char *line;    /* Line of text to be checked */
  103. char *namebuf;    /* Optional buffer to place packed sender info */
  104. {
  105.     char *fields[MAX_FIELDS];
  106.     char *sender_tail = NULL;
  107.     register char *lp, **fp;
  108.     register int n, i;
  109.  
  110.     if (strncmp(line, "From ", 5)) return -100;
  111.     if (strict_from_parse == 0) return 0;
  112.  
  113.     lp = line + 5;
  114.     /* sender day mon dd hh:mm:ss year */
  115.     for (n = 0, fp = fields; n < MAX_FIELDS; n++) {
  116.     while (*lp && *lp != NL && isascii(*lp) && isspace(*lp)) lp++;
  117.     if (*lp == NUL || *lp == NL) break;
  118.     *fp++ = lp;
  119.     while (*lp && isascii(*lp) && !isspace(*lp))
  120.         if (*lp++ == ':' && (n == 4 || n == 5)) break;
  121.     if (n == 0) sender_tail = lp;
  122.     }
  123.  
  124.     if (n < 8) return -200-n;
  125.  
  126.     fp = fields;
  127.  
  128.     if (n > 8 && !isdigit(fp[7][0])) fp[7] = fp[8]; /* ... TZ year */
  129.     if (n > 9 && !isdigit(fp[7][0])) fp[7] = fp[9]; /* ... TZ DST year */
  130.  
  131.     if (namebuf != NULL) {
  132.     char x = *sender_tail;
  133.     *sender_tail = NUL;
  134.     pack_name(namebuf, *fp, NAME_LENGTH);
  135.     *sender_tail = x;
  136.     }
  137.  
  138.     if (strict_from_parse == 1) return 0;
  139.  
  140.     fp++;
  141.     for (i = 0; i < 21; i += 3)
  142.     if (strncmp(*fp, &legal_day[i], 3) == 0) break;
  143.     if (i == 21) return -1;
  144.  
  145.     fp++;
  146.     for (i = 0; i < 36; i += 3)
  147.     if (strncmp(*fp, &legal_month[i], 3) == 0) break;
  148.     if (i == 36) return -2;
  149.  
  150.     for (i = 0; i < 10; i += 2) {
  151.     lp = *++fp;
  152.     if (!isdigit(*lp)) return -20-i;
  153.     n = atoi(lp);
  154.     if (n < legal_numbers[i] || legal_numbers[i+1] < n) return -10-i;
  155.     }
  156.     return 0;
  157. }
  158.  
  159. /*
  160.  * expect that f is positioned at header of an article
  161.  */
  162.  
  163. static int is_mmdf_folder = 0;
  164. static int is_mail_folder = 0;
  165.  
  166. /*
  167.  * get_folder_type
  168.  *
  169.  * Given a file descriptor f, will check what type of folder it is.
  170.  * Must be called at zero offset and caller must reposition if necessary.
  171.  * Side-effects: sets is_mail_folder, is_mmdf_folder, and current_folder_type.
  172.  * Return values:
  173.  *    -1: folder is empty,
  174.  *     0: normal digest,
  175.  *     1: UNIX mail format
  176.  *     2: MMDF format
  177.  */                                        
  178.  
  179. export int current_folder_type;
  180.  
  181. int get_folder_type(f)
  182. FILE *f;
  183. {
  184.     char line[1024];
  185.  
  186.     is_mail_folder = 0;
  187.     is_mmdf_folder = 0;
  188.  
  189.     if (fgets(line, 1024, f) == NULL)    
  190.     return current_folder_type = -1;
  191.  
  192.     if (strncmp(line, "\001\001\001\001\n", 5) == 0) {
  193.     is_mmdf_folder = 1;
  194.     return current_folder_type = 2;
  195.     }
  196.  
  197.     if (is_mail_from_line(line, (char *)NULL) == 0) {
  198.     is_mail_folder = 1;
  199.     return current_folder_type = 1;
  200.     }
  201.  
  202.     return current_folder_type = 0;
  203. }
  204.  
  205. int
  206. get_digest_article(f, hdrbuf)
  207. FILE *f;
  208. news_header_buffer hdrbuf;
  209. {
  210.     int cont;
  211.  
  212.     digest.dg_hpos = ftell(f);
  213.     TEST1("GET DIGEST hp=%ld\n", digest.dg_hpos);
  214.  
  215.     do {
  216.     if (!parse_digest_header(f, 0, hdrbuf)) return -1;
  217.     digest.dg_fpos = ftell(f);
  218.     TEST2("END HEADER hp=%ld fp=%ld\n", digest.dg_hpos, digest.dg_fpos);
  219.     } while ((cont = skip_digest_body(f)) < 0);
  220.  
  221.     TEST2("END BODY lp=%ld next=%ld\n", digest.dg_lpos, ftell(f));
  222.  
  223.     return cont;
  224. }
  225.  
  226. #define BACKUP_LINES     50    /* remember class + offset for parsed lines */
  227.  
  228. #define    LN_BLANK    0x01    /* blank line */
  229. #define    LN_DASHED    0x02    /* dash line */
  230. #define    LN_HEADER    0x04    /* (possible) header line */
  231. #define    LN_ASTERISK    0x08    /* asterisk line (near end) */
  232. #define    LN_END_OF    0x10    /* End of ... line */
  233. #define    LN_TEXT        0x20    /* unclassified line */
  234.  
  235.  
  236. /*
  237.  * skip until 'Subject: ' (or End of digest) line is found
  238.  * then backup till start of header
  239.  */
  240.  
  241. /*
  242.  * Tuning parameters:
  243.  *
  244.  *    MIN_HEADER_LINES:    number of known header lines that must
  245.  *                be found in a block to identify a new
  246.  *                header
  247.  *
  248.  *    MAX_BLANKS_DASH        max no of blanks on a 'dash line'
  249.  *
  250.  *    MIN_DASHES        min no of dashes on a 'dash line'
  251.  *
  252.  *    MAX_BLANKS_ASTERISKS    max no of blanks on an 'asterisk line'
  253.  *
  254.  *    MIN_ASTERISKS        min no of asterisks on an 'asterisk line'
  255.  *
  256.  *    MAX_BLANKS_END_OF    max no of blanks before "End of "
  257.  */
  258.  
  259. #define    MIN_HEADER_LINES    2
  260. #define    MAX_BLANKS_DASH        3
  261. #define    MIN_DASHES        16
  262. #define    MAX_BLANKS_ASTERISK    1
  263. #define    MIN_ASTERISKS        10
  264. #define    MAX_BLANKS_END_OF    1
  265.  
  266. int
  267. skip_digest_body(f)
  268. register FILE *f;
  269. {
  270.     off_t  backup_p[BACKUP_LINES];
  271.     int       line_type[BACKUP_LINES];
  272.     register int backup_index, backup_count;
  273.     int    more_header_lines, end_or_asterisks, blanks;
  274.     int    colon_lines;
  275.     char   line[1024];
  276.     register char *cp;
  277.  
  278. #define    decrease_index()    \
  279.     if (--backup_index < 0) backup_index = BACKUP_LINES - 1
  280.  
  281.     backup_index = -1;
  282.     backup_count = 0;
  283.     end_or_asterisks = 0;
  284.  
  285.     digest.dg_lines = 0;
  286.  
  287.  
  288.  next_line:
  289.     more_header_lines = 0;
  290.     colon_lines = 0;
  291.  
  292.  next_possible_header_line:
  293.     digest.dg_lines++;
  294.  
  295.     if (++backup_index == BACKUP_LINES) backup_index = 0;
  296.     if (backup_count < BACKUP_LINES) backup_count++;
  297.  
  298.     backup_p[backup_index] = ftell(f);
  299.     line_type[backup_index] = LN_TEXT;
  300.  
  301.     if (fgets(line, 1024, f) == NULL) {
  302.     TEST2("end_of_file, bc=%d, lines=%d\n", backup_count, digest.dg_lines);
  303.  
  304.     if (is_mmdf_folder) {
  305.         digest.dg_lpos = backup_p[backup_index];
  306.         is_mmdf_folder = 0;
  307.         return 0;
  308.     }
  309.  
  310.     /* end of file => look for "****" or "End of" line */
  311.  
  312.     if (end_or_asterisks)
  313.         while (--backup_count >= 0) {
  314.         --digest.dg_lines;
  315.         decrease_index();
  316.         if (line_type[backup_index] & (LN_ASTERISK | LN_END_OF)) break;
  317.         }
  318.  
  319.     digest.dg_lpos = backup_p[backup_index];
  320.  
  321.     if (digest.dg_lines == 0) return 0;
  322.  
  323.     while (--backup_count >= 0) {
  324.         --digest.dg_lines;
  325.         digest.dg_lpos = backup_p[backup_index];
  326.         decrease_index();
  327.         if ((line_type[backup_index] &
  328.         (LN_ASTERISK | LN_END_OF | LN_BLANK | LN_DASHED)) == 0)
  329.         break;
  330.     }
  331.  
  332.     return 0;    /* no article follows */
  333.     }
  334.  
  335.     TEST1("\n>>%-.50s ==>>", line);
  336.  
  337.     if (is_mmdf_folder) {
  338.     /* in an mmdf folder we simply look for the next ^A^A^A^A line */
  339.     if (line[0] != '\001' || strcmp(line, "\001\001\001\001\n"))
  340.         goto next_line;
  341.  
  342.     digest.dg_lpos = backup_p[backup_index];
  343.     --digest.dg_lines;
  344.     return (digest.dg_lines <= 0) ? -1 : 1;
  345.     }
  346.  
  347.     for (cp = line; *cp && isascii(*cp) && isspace(*cp); cp++);
  348.  
  349.     if (*cp == NUL) {
  350.     TEST0("BLANK");
  351.     line_type[backup_index] = LN_BLANK;
  352.     goto next_line;
  353.     }
  354.  
  355.     if (is_mail_folder) {
  356.     /* in a mail folder we simply look for the next "From " line */
  357.     if (line[0] != 'F' || is_mail_from_line(line, (char *)NULL) < 0)
  358.         goto next_line;
  359.  
  360.     line_type[backup_index] = LN_HEADER;
  361.     fseek(f, backup_p[backup_index], 0);
  362.     goto found_mail_header;
  363.     }
  364.  
  365.     blanks = cp - line;
  366.  
  367.     if (*cp == '-') {
  368.     if (blanks > MAX_BLANKS_DASH) goto next_line;
  369.  
  370.     while (*cp == '-') cp++;
  371.     if (cp - line - blanks > MIN_DASHES) {
  372.         while (*cp && (*cp == '-' || (isascii(*cp) && isspace(*cp)))) cp++;
  373.         if (*cp == NUL) {
  374.         TEST0("DASHED");
  375.  
  376.         line_type[backup_index] = LN_DASHED;
  377.         }
  378.  
  379.     }
  380.     goto next_line;
  381.     }
  382.  
  383.     if (*cp == '*') {
  384.     if (blanks > MAX_BLANKS_ASTERISK) goto next_line;
  385.  
  386.     while (*cp == '*') cp++;
  387.     if (cp - line - blanks > MIN_ASTERISKS) {
  388.         while (*cp && (*cp == '*' || (isascii(*cp) && isspace(*cp)))) cp++;
  389.         if (*cp == NUL) {
  390.         TEST0("ASTERISK");
  391.         line_type[backup_index] = LN_ASTERISK;
  392.         end_or_asterisks++;
  393.         }
  394.     }
  395.     goto next_line;
  396.     }
  397.  
  398.     if (blanks <= MAX_BLANKS_END_OF &&
  399.     *cp == 'E' && strncmp(cp, "End of ", 7) == 0) {
  400.     TEST0("END_OF_");
  401.     line_type[backup_index] = LN_END_OF;
  402.     end_or_asterisks++;
  403.     goto next_line;
  404.     }
  405.  
  406.     if (blanks)
  407.     goto next_possible_header_line;
  408. /* must be able to handle continued lines in sub-digest headers...
  409.     goto next_line;
  410. */
  411.  
  412.     if (!dg_hdr_field(line, 0))
  413.     {
  414.     char *colon;
  415.     if ((colon = strchr(line, ':'))) {
  416.         for (cp = line; cp < colon; cp++)
  417.         if (!isascii(*cp) || isspace(*cp)) break;
  418.         if (cp == colon) {
  419.         TEST0("COLON");
  420.         colon_lines++;
  421.         line_type[backup_index] = LN_HEADER;
  422.         goto next_possible_header_line;
  423.         }
  424.     }
  425.     if (is_mail_from_line(line, (char *)NULL) == 0) {
  426.         TEST0("FROM_");
  427.         colon_lines++;
  428.         line_type[backup_index] = LN_HEADER;
  429.     }
  430.  
  431.     goto next_possible_header_line;
  432.     }
  433.  
  434.     TEST0("HEADER");
  435.  
  436.     line_type[backup_index] = LN_HEADER;
  437.     if (++more_header_lines < MIN_HEADER_LINES)
  438.     goto next_possible_header_line;
  439.  
  440.     /* found block with MIN_HEADER_LINES */
  441.  
  442.     TEST0("\nSearch for start of header\n");
  443.  
  444.     colon_lines += more_header_lines;
  445.     for (;;) {
  446.     fseek(f, backup_p[backup_index], 0);
  447.     if (line_type[backup_index] == LN_HEADER)
  448.         if (--colon_lines <= 0) break;
  449.     --digest.dg_lines;
  450.     if (--backup_count == 0) break;
  451.     decrease_index();
  452.     if ((line_type[backup_index] & (LN_HEADER | LN_TEXT)) == 0)
  453.         break;
  454.     }
  455.  
  456.     if (digest.dg_lines == 0) {
  457.     TEST0("Skipped empty article\n");
  458.     return -1;
  459.     }
  460.  
  461.  found_mail_header:
  462.  
  463.     for (;;) {
  464.     digest.dg_lpos = backup_p[backup_index];
  465.     if (--backup_count < 0) break;
  466.     decrease_index();
  467.     if ((line_type[backup_index] & (LN_BLANK | LN_DASHED)) == 0)
  468.         break;
  469.     --digest.dg_lines;
  470.     }
  471.  
  472.     return (digest.dg_lines == 0) ? -1 : 1;
  473. }
  474.  
  475. int
  476. parse_digest_header(f, all, hdrbuf)
  477. FILE *f;
  478. int all;
  479. news_header_buffer hdrbuf;
  480. {
  481.     digest.dg_date = digest.dg_from = digest.dg_subj = digest.dg_to = NULL;
  482.  
  483.     parse_header(f, dg_hdr_field, all, hdrbuf);
  484.  
  485.     return digest.dg_from || digest.dg_subj;
  486. }
  487.  
  488.  
  489. static char **dg_hdr_field(lp, all)
  490. register char *lp;
  491. int all;
  492. {
  493.     static char *dummy;
  494.     static char namebuf[NAME_LENGTH+1];
  495.  
  496. #ifdef __STDC__
  497. #define check(name, lgt, field) \
  498.     if (isascii(lp[lgt]) && isspace(lp[lgt]) && strncmp(name, lp, lgt) == 0) {\
  499.     TEST0("MATCH: " #field " "); \
  500.     return &digest.field; \
  501.     }
  502. #else /* crock old K&R compiler */
  503. #define check(name, lgt, field) \
  504.     if (isascii(lp[lgt]) && isspace(lp[lgt]) && strncmp(name, lp, lgt) == 0) {\
  505.     TEST0("MATCH: field "); \
  506.     return &digest.field; \
  507.     }
  508. #endif
  509.  
  510.     TEST1("\nPARSE[%.20s] ==>> ", lp);
  511.  
  512.     switch (*lp++) {
  513.  
  514.      case '\001':
  515.     /* In an mmdf folder ^A^A^A^A is skipped at beginning of header */
  516.     if (!is_mmdf_folder) break;
  517.     if (strncmp(lp, "\001\001\001\n", 4)) break;
  518.     digest.dg_hpos += 5;
  519.     return NULL;
  520.  
  521.      case 'D':
  522.      case 'd':
  523.     check("ate:",    4, dg_date);
  524.     break;
  525.  
  526.      case 'F':
  527.      case 'f':
  528.     check("rom:",    4, dg_from);
  529.     if (!is_mail_folder) break;
  530.     if (*--lp != 'F') break;
  531.     if (is_mail_from_line(lp, namebuf) < 0) break;
  532.     /* Store packed sender in dg_from here and return dummy to parser */
  533.     if (digest.dg_from == NULL) digest.dg_from = namebuf;
  534.     return &dummy;
  535.  
  536.      case 'R':
  537.      case 'r':
  538.     if (!all) break;
  539.     check("e:",    2, dg_subj);
  540.     break;
  541.  
  542.      case 'S':
  543.      case 's':
  544.     check("ubject:", 7, dg_subj);
  545.     check("ubject",    6, dg_subj);
  546.     break;
  547.  
  548.      case 'T':
  549.      case 't':
  550.     check("itle:",    5, dg_subj);
  551.     if (!all) break;
  552.     check("o:",    2, dg_to);
  553.     break;
  554.     }
  555.  
  556. #undef check
  557.     TEST0("NOT MATCHED ");
  558.  
  559.     return NULL;
  560. }
  561.