summaryrefslogtreecommitdiffstats
path: root/src/cmd/upas/smtp/rfc822.y
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/smtp/rfc822.y
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/smtp/rfc822.y')
-rw-r--r--src/cmd/upas/smtp/rfc822.y778
1 files changed, 778 insertions, 0 deletions
diff --git a/src/cmd/upas/smtp/rfc822.y b/src/cmd/upas/smtp/rfc822.y
new file mode 100644
index 00000000..be77d2bd
--- /dev/null
+++ b/src/cmd/upas/smtp/rfc822.y
@@ -0,0 +1,778 @@
+%{
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+
+char *yylp; /* next character to be lex'd */
+int yydone; /* tell yylex to give up */
+char *yybuffer; /* first parsed character */
+char *yyend; /* end of buffer to be parsed */
+Node *root;
+Field *firstfield;
+Field *lastfield;
+Node *usender;
+Node *usys;
+Node *udate;
+char *startfield, *endfield;
+int originator;
+int destination;
+int date;
+int received;
+int messageid;
+%}
+
+%term WORD
+%term DATE
+%term RESENT_DATE
+%term RETURN_PATH
+%term FROM
+%term SENDER
+%term REPLY_TO
+%term RESENT_FROM
+%term RESENT_SENDER
+%term RESENT_REPLY_TO
+%term SUBJECT
+%term TO
+%term CC
+%term BCC
+%term RESENT_TO
+%term RESENT_CC
+%term RESENT_BCC
+%term REMOTE
+%term PRECEDENCE
+%term MIMEVERSION
+%term CONTENTTYPE
+%term MESSAGEID
+%term RECEIVED
+%term MAILER
+%term BADTOKEN
+%start msg
+%%
+
+msg : fields
+ | unixfrom '\n' fields
+ ;
+fields : '\n'
+ { yydone = 1; }
+ | field '\n'
+ | field '\n' fields
+ ;
+field : dates
+ { date = 1; }
+ | originator
+ { originator = 1; }
+ | destination
+ { destination = 1; }
+ | subject
+ | optional
+ | ignored
+ | received
+ | precedence
+ | error '\n' field
+ ;
+unixfrom : FROM route_addr unix_date_time REMOTE FROM word
+ { freenode($1); freenode($4); freenode($5);
+ usender = $2; udate = $3; usys = $6;
+ }
+ ;
+originator : REPLY_TO ':' address_list
+ { newfield(link3($1, $2, $3), 1); }
+ | RETURN_PATH ':' route_addr
+ { newfield(link3($1, $2, $3), 1); }
+ | FROM ':' mailbox_list
+ { newfield(link3($1, $2, $3), 1); }
+ | SENDER ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_REPLY_TO ':' address_list
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_SENDER ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_FROM ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ ;
+dates : DATE ':' date_time
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_DATE ':' date_time
+ { newfield(link3($1, $2, $3), 0); }
+ ;
+destination : TO ':'
+ { newfield(link2($1, $2), 0); }
+ | TO ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_TO ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_TO ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | CC ':'
+ { newfield(link2($1, $2), 0); }
+ | CC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_CC ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_CC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | BCC ':'
+ { newfield(link2($1, $2), 0); }
+ | BCC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_BCC ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_BCC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ ;
+subject : SUBJECT ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | SUBJECT ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+received : RECEIVED ':' things
+ { newfield(link3($1, $2, $3), 0); received++; }
+ | RECEIVED ':'
+ { newfield(link2($1, $2), 0); received++; }
+ ;
+precedence : PRECEDENCE ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | PRECEDENCE ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+ignored : ignoredhdr ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | ignoredhdr ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+ignoredhdr : MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER
+ ;
+optional : fieldwords ':' things
+ { /* hack to allow same lex for field names and the rest */
+ if(badfieldname($1)){
+ freenode($1);
+ freenode($2);
+ freenode($3);
+ return 1;
+ }
+ newfield(link3($1, $2, $3), 0);
+ }
+ | fieldwords ':'
+ { /* hack to allow same lex for field names and the rest */
+ if(badfieldname($1)){
+ freenode($1);
+ freenode($2);
+ return 1;
+ }
+ newfield(link2($1, $2), 0);
+ }
+ ;
+address_list : address
+ | address_list ',' address
+ { $$ = link3($1, $2, $3); }
+ ;
+address : mailbox
+ | group
+ ;
+group : phrase ':' address_list ';'
+ { $$ = link2($1, link3($2, $3, $4)); }
+ | phrase ':' ';'
+ { $$ = link3($1, $2, $3); }
+ ;
+mailbox_list : mailbox
+ | mailbox_list ',' mailbox
+ { $$ = link3($1, $2, $3); }
+ ;
+mailbox : route_addr
+ | phrase brak_addr
+ { $$ = link2($1, $2); }
+ | brak_addr
+ ;
+brak_addr : '<' route_addr '>'
+ { $$ = link3($1, $2, $3); }
+ | '<' '>'
+ { $$ = nobody($2); freenode($1); }
+ ;
+route_addr : route ':' at_addr
+ { $$ = address(concat($1, concat($2, $3))); }
+ | addr_spec
+ ;
+route : '@' domain
+ { $$ = concat($1, $2); }
+ | route ',' '@' domain
+ { $$ = concat($1, concat($2, concat($3, $4))); }
+ ;
+addr_spec : local_part
+ { $$ = address($1); }
+ | at_addr
+ ;
+at_addr : local_part '@' domain
+ { $$ = address(concat($1, concat($2, $3)));}
+ | at_addr '@' domain
+ { $$ = address(concat($1, concat($2, $3)));}
+ ;
+local_part : word
+ ;
+domain : word
+ ;
+phrase : word
+ | phrase word
+ { $$ = link2($1, $2); }
+ ;
+things : thing
+ | things thing
+ { $$ = link2($1, $2); }
+ ;
+thing : word | '<' | '>' | '@' | ':' | ';' | ','
+ ;
+date_time : things
+ ;
+unix_date_time : word word word unix_time word word
+ { $$ = link3($1, $3, link3($2, $6, link2($4, $5))); }
+ ;
+unix_time : word
+ | unix_time ':' word
+ { $$ = link3($1, $2, $3); }
+ ;
+word : WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER
+ | REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO
+ | TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT
+ | PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER
+ ;
+fieldwords : fieldword
+ | WORD
+ | fieldwords fieldword
+ { $$ = link2($1, $2); }
+ | fieldwords word
+ { $$ = link2($1, $2); }
+ ;
+fieldword : '<' | '>' | '@' | ';' | ','
+ ;
+%%
+
+/*
+ * Initialize the parsing. Done once for each header field.
+ */
+void
+yyinit(char *p, int len)
+{
+ yybuffer = p;
+ yylp = p;
+ yyend = p + len;
+ firstfield = lastfield = 0;
+ received = 0;
+}
+
+/*
+ * keywords identifying header fields we care about
+ */
+typedef struct Keyword Keyword;
+struct Keyword {
+ char *rep;
+ int val;
+};
+
+/* field names that we need to recognize */
+Keyword key[] = {
+ { "date", DATE },
+ { "resent-date", RESENT_DATE },
+ { "return_path", RETURN_PATH },
+ { "from", FROM },
+ { "sender", SENDER },
+ { "reply-to", REPLY_TO },
+ { "resent-from", RESENT_FROM },
+ { "resent-sender", RESENT_SENDER },
+ { "resent-reply-to", RESENT_REPLY_TO },
+ { "to", TO },
+ { "cc", CC },
+ { "bcc", BCC },
+ { "resent-to", RESENT_TO },
+ { "resent-cc", RESENT_CC },
+ { "resent-bcc", RESENT_BCC },
+ { "remote", REMOTE },
+ { "subject", SUBJECT },
+ { "precedence", PRECEDENCE },
+ { "mime-version", MIMEVERSION },
+ { "content-type", CONTENTTYPE },
+ { "message-id", MESSAGEID },
+ { "received", RECEIVED },
+ { "mailer", MAILER },
+ { "who-the-hell-cares", WORD }
+};
+
+/*
+ * Lexical analysis for an rfc822 header field. Continuation lines
+ * are handled in yywhite() when skipping over white space.
+ *
+ */
+int
+yylex(void)
+{
+ String *t;
+ int quoting;
+ int escaping;
+ char *start;
+ Keyword *kp;
+ int c, d;
+
+/* print("lexing\n"); /**/
+ if(yylp >= yyend)
+ return 0;
+ if(yydone)
+ return 0;
+
+ quoting = escaping = 0;
+ start = yylp;
+ yylval = malloc(sizeof(Node));
+ yylval->white = yylval->s = 0;
+ yylval->next = 0;
+ yylval->addr = 0;
+ yylval->start = yylp;
+ for(t = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping) {
+ escaping = 0;
+ } else if(quoting) {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '\n':
+ d = (*(yylp+1))&0xff;
+ if(d != ' ' && d != '\t'){
+ quoting = 0;
+ yylp--;
+ continue;
+ }
+ break;
+ case '"':
+ quoting = 0;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ case ' ':
+ case '\t':
+ case '\r':
+ goto out;
+ case '\n':
+ if(yylp == start){
+ yylp++;
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '@':
+ case '>':
+ case '<':
+ case ':':
+ case ',':
+ case ';':
+ if(yylp == start){
+ yylp++;
+ yylval->white = yywhite();
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '"':
+ quoting = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if(t == 0)
+ t = s_new();
+ s_putc(t, c);
+ }
+out:
+ yylval->white = yywhite();
+ if(t) {
+ s_terminate(t);
+ } else /* message begins with white-space! */
+ return yylval->c = '\n';
+ yylval->s = t;
+ for(kp = key; kp->val != WORD; kp++)
+ if(cistrcmp(s_to_c(t), kp->rep)==0)
+ break;
+/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+ yylval->end = yylp;
+ return yylval->c = kp->val;
+}
+
+void
+yyerror(char *x)
+{
+ USED(x);
+
+ /*fprint(2, "parse err: %s\n", x);/**/
+}
+
+/*
+ * parse white space and comments
+ */
+String *
+yywhite(void)
+{
+ String *w;
+ int clevel;
+ int c;
+ int escaping;
+
+ escaping = clevel = 0;
+ for(w = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping){
+ escaping = 0;
+ } else if(clevel) {
+ switch(c){
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ')':
+ clevel--;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ' ':
+ case '\t':
+ case '\r':
+ break;
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ default:
+ goto out;
+ }
+ }
+ if(w == 0)
+ w = s_new();
+ s_putc(w, c);
+ }
+out:
+ if(w)
+ s_terminate(w);
+ return w;
+}
+
+/*
+ * link two parsed entries together
+ */
+Node*
+link2(Node *p1, Node *p2)
+{
+ Node *p;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+ return p1;
+}
+
+/*
+ * link three parsed entries together
+ */
+Node*
+link3(Node *p1, Node *p2, Node *p3)
+{
+ Node *p;
+
+ for(p = p2; p->next; p = p->next)
+ ;
+ p->next = p3;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+
+ return p1;
+}
+
+/*
+ * make a:b, move all white space after both
+ */
+Node*
+colon(Node *p1, Node *p2)
+{
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ s_append(p1->s, ":");
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * concatenate two fields, move all white space after both
+ */
+Node*
+concat(Node *p1, Node *p2)
+{
+ char buf[2];
+
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ if(p1->s == nil){
+ buf[0] = p1->c;
+ buf[1] = 0;
+ p1->s = s_new();
+ s_append(p1->s, buf);
+ }
+
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+ else {
+ buf[0] = p2->c;
+ buf[1] = 0;
+ s_append(p1->s, buf);
+ }
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * look for disallowed chars in the field name
+ */
+int
+badfieldname(Node *p)
+{
+ for(; p; p = p->next){
+ /* field name can't contain white space */
+ if(p->white && p->next)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * mark as an address
+ */
+Node *
+address(Node *p)
+{
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * case independent string compare
+ */
+int
+cistrcmp(char *s1, char *s2)
+{
+ int c1, c2;
+
+ for(; *s1; s1++, s2++){
+ c1 = isupper(*s1) ? tolower(*s1) : *s1;
+ c2 = isupper(*s2) ? tolower(*s2) : *s2;
+ if (c1 != c2)
+ return -1;
+ }
+ return *s2;
+}
+
+/*
+ * free a node
+ */
+void
+freenode(Node *p)
+{
+ Node *tp;
+
+ while(p){
+ tp = p->next;
+ if(p->s)
+ s_free(p->s);
+ if(p->white)
+ s_free(p->white);
+ free(p);
+ p = tp;
+ }
+}
+
+
+/*
+ * an anonymous user
+ */
+Node*
+nobody(Node *p)
+{
+ if(p->s)
+ s_free(p->s);
+ p->s = s_copy("pOsTmAsTeR");
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * add anything that was dropped because of a parse error
+ */
+void
+missing(Node *p)
+{
+ Node *np;
+ char *start, *end;
+ Field *f;
+ String *s;
+
+ start = yybuffer;
+ if(lastfield != nil){
+ for(np = lastfield->node; np; np = np->next)
+ start = np->end+1;
+ }
+
+ end = p->start-1;
+
+ if(end <= start)
+ return;
+
+ if(strncmp(start, "From ", 5) == 0)
+ return;
+
+ np = malloc(sizeof(Node));
+ np->start = start;
+ np->end = end;
+ np->white = nil;
+ s = s_copy("BadHeader: ");
+ np->s = s_nappend(s, start, end-start);
+ np->next = nil;
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = np;
+ f->source = 0;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+}
+
+/*
+ * create a new field
+ */
+void
+newfield(Node *p, int source)
+{
+ Field *f;
+
+ missing(p);
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = p;
+ f->source = source;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+ endfield = startfield;
+ startfield = yylp;
+}
+
+/*
+ * fee a list of fields
+ */
+void
+freefield(Field *f)
+{
+ Field *tf;
+
+ while(f){
+ tf = f->next;
+ freenode(f->node);
+ free(f);
+ f = tf;
+ }
+}
+
+/*
+ * add some white space to a node
+ */
+Node*
+whiten(Node *p)
+{
+ Node *tp;
+
+ for(tp = p; tp->next; tp = tp->next)
+ ;
+ if(tp->white == 0)
+ tp->white = s_copy(" ");
+ return p;
+}
+
+void
+yycleanup(void)
+{
+ Field *f, *fnext;
+ Node *np, *next;
+
+ for(f = firstfield; f; f = fnext){
+ for(np = f->node; np; np = next){
+ if(np->s)
+ s_free(np->s);
+ if(np->white)
+ s_free(np->white);
+ next = np->next;
+ free(np);
+ }
+ fnext = f->next;
+ free(f);
+ }
+ firstfield = lastfield = 0;
+}