ref: db1eb844461b07a25ca49117851fa874fd88e065
dir: /appl/lib/rfc822.b/
implement RFC822; include "sys.m"; sys: Sys; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "rfc822.m"; include "string.m"; str: String; include "daytime.m"; daytime: Daytime; Tm: import daytime; Minrequest: con 512; # more than enough for most requests Suffix: adt { suffix: string; generic: string; specific: string; encoding: string; }; SuffixFile: con "/lib/mimetype"; mtime := 0; qid: Sys->Qid; suffixes: list of ref Suffix; nomod(s: string) { raise sys->sprint("internal: can't load %s: %r", s); } init(b: Bufio) { sys = load Sys Sys->PATH; bufio = b; str = load String String->PATH; if(str == nil) nomod(String->PATH); daytime = load Daytime Daytime->PATH; if(daytime == nil) nomod(Daytime->PATH); readsuffixfile(); } readheaders(fd: ref Iobuf, limit: int): array of (string, array of byte) { n := 0; s := 0; b := array[Minrequest] of byte; nline := 0; lines: list of array of byte; while((c := fd.getb()) >= 0){ if(c == '\r'){ c = fd.getb(); if(c < 0) break; if(c != '\n'){ fd.ungetb(); c = '\r'; } } if(n >= len b){ if(len b >= limit) return nil; ab := array[n+512] of byte; ab[0:] = b; b = ab; } b[n++] = byte c; if(c == '\n'){ if(n == 1 || b[n-2] == byte '\n') break; # empty line c = fd.getb(); if(c < 0) break; if(c != ' ' && c != '\t'){ # not continued fd.ungetb(); lines = b[s: n] :: lines; nline++; s = n; }else b[n-1] = byte ' '; } } if(n == 0) return nil; b = b[0: n]; if(n != s){ lines = b[s:n] :: lines; nline++; } a := array[nline] of (string, array of byte); for(; lines != nil; lines = tl lines){ b = hd lines; name := ""; for(i := 0; i < len b; i++) if(b[i] == byte ':'){ name = str->tolower(string b[0:i]); b = b[i+1:]; break; } a[--nline] = (name, b); } return a; } # # *(";" parameter) used in transfer-extension, media-type and media-range # parameter = attribute "=" value # attribute = token # value = token | quoted-string # parseparams(ps: ref Rfclex): list of (string, string) { l: list of (string, string); do{ if(ps.lex() != Word) break; attr := ps.wordval; if(ps.lex() != '=' || ps.lex() != Word && ps.tok != QString) break; l = (attr, ps.wordval) :: l; }while(ps.lex() == ';'); ps.unlex(); return rev(l); } # # 1#transfer-coding # mimefields(ps: ref Rfclex): list of (string, list of (string, string)) { rf: list of (string, list of (string, string)); do{ if(ps.lex() == Word){ w := ps.wordval; if(ps.lex() == ';'){ rf = (w, parseparams(ps)) :: rf; ps.lex(); }else rf = (w, nil) :: rf; } }while(ps.tok == ','); ps.unlex(); f: list of (string, list of (string, string)); for(; rf != nil; rf = tl rf) f = hd rf :: f; return f; } # #(media-type | (media-range [accept-params])) ; Content-Type and Accept # # media-type = type "/" subtype *( ";" parameter ) # type = token # subtype = token # LWS must not be used between type and subtype, nor between attribute and value (in parameter) # # media-range = ("*/*" | type "/*" | type "/" subtype ) *(";' parameter) # accept-params = ";" "q" "=" qvalue *( accept-extension ) # accept-extension = ";" token [ "=" ( token | quoted-string ) ] # # 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] ) ; Accept-Charset # 1#( codings [ ";" "q" "=" qvalue ] ) ; Accept-Encoding # 1#( language-range [ ";" "q" "=" qvalue ] ) ; Accept-Language # # codings = ( content-coding | "*" ) # parsecontent(ps: ref Rfclex, multipart: int, head: list of ref Content): list of ref Content { do{ if(ps.lex() == Word){ generic := ps.wordval; specific := "*"; if(ps.lex() == '/'){ if(ps.lex() != Word) break; specific = ps.wordval; if(!multipart && specific != "*") break; }else if(multipart) break; # syntax error else ps.unlex(); params: list of (string, string) = nil; if(ps.lex() == ';'){ params = parseparams(ps); ps.lex(); } head = Content.mk(generic, specific, params) :: head; # order reversed, but doesn't matter } }while(ps.tok == ','); ps.unlex(); return head; } rev(l: list of (string, string)): list of (string, string) { rl: list of (string, string); for(; l != nil; l = tl l) rl = hd l :: rl; return rl; } Rfclex.mk(a: array of byte): ref Rfclex { ps := ref Rfclex; ps.fd = bufio->aopen(a); ps.tok = '\n'; ps.eof = 0; return ps; } Rfclex.getc(ps: self ref Rfclex): int { c := ps.fd.getb(); if(c < 0) ps.eof = 1; return c; } Rfclex.ungetc(ps: self ref Rfclex) { if(!ps.eof) ps.fd.ungetb(); } Rfclex.lex(ps: self ref Rfclex): int { if(ps.seen != nil){ (ps.tok, ps.wordval) = hd ps.seen; ps.seen = tl ps.seen; }else ps.tok = lex1(ps, 0); return ps.tok; } Rfclex.unlex(ps: self ref Rfclex) { ps.seen = (ps.tok, ps.wordval) :: ps.seen; } Rfclex.skipws(ps: self ref Rfclex): int { return lex1(ps, 1); } # # rfc 2822/rfc 1521 lexical analyzer # lex1(ps: ref Rfclex, skipwhite: int): int { ps.wordval = nil; while((c := ps.getc()) >= 0){ case c { '(' => level := 1; while((c = ps.getc()) != Bufio->EOF && c != '\n'){ if(c == '\\'){ c = ps.getc(); if(c == Bufio->EOF) return '\n'; continue; } if(c == '(') level++; else if(c == ')' && --level == 0) break; } ' ' or '\t' or '\r' or 0 => ; '\n' => return '\n'; ')' or '<' or '>' or '[' or ']' or '@' or '/' or ',' or ';' or ':' or '?' or '=' => if(skipwhite){ ps.ungetc(); return c; } return c; '"' => if(skipwhite){ ps.ungetc(); return c; } word(ps,"\""); ps.getc(); # skip the closing quote return QString; * => ps.ungetc(); if(skipwhite) return c; word(ps,"\"()<>@,;:/[]?={}\r\n \t"); return Word; } } return '\n'; } # return the rest of an rfc 822 line, not including \r or \n # do not map to lower case Rfclex.line(ps: self ref Rfclex): string { s := ""; while((c := ps.getc()) != Bufio->EOF && c != '\n' && c != '\r'){ if(c == '\\'){ c = ps.getc(); if(c == Bufio->EOF) break; } s[len s] = c; } ps.tok = '\n'; ps.wordval = s; return s; } word(ps: ref Rfclex, stop: string) { w := ""; while((c := ps.getc()) != Bufio->EOF){ if(c == '\r') c = ' '; if(c == '\\'){ c = ps.getc(); if(c == Bufio->EOF) break; }else if(str->in(c,stop)){ ps.ungetc(); break; } if(c >= 'A' && c <= 'Z') c += 'a' - 'A'; w[len w] = c; } ps.wordval = w; } readsuffixfile(): string { iob := bufio->open(SuffixFile, Bufio->OREAD); if(iob == nil) return sys->sprint("cannot open %s: %r", SuffixFile); for(n := 1; (line := iob.gets('\n')) != nil; n++){ (s, nil) := parsesuffix(line); if(s != nil) suffixes = s :: suffixes; } return nil; } parsesuffix(line: string): (ref Suffix, string) { (line, nil) = str->splitstrl(line, "#"); if(line == nil) return (nil, nil); (n, slist) := sys->tokenize(line,"\n\t "); if(n == 0) return (nil, nil); if(n < 4) return (nil, "too few fields"); s := ref Suffix; s.suffix = hd slist; slist = tl slist; s.generic = hd slist; if (s.generic == "-") s.generic = ""; slist = tl slist; s.specific = hd slist; if (s.specific == "-") s.specific = ""; slist = tl slist; s.encoding = hd slist; if (s.encoding == "-") s.encoding = ""; if((s.generic == nil || s.specific == nil) && s.encoding == nil) return (nil, nil); return (s, nil); } # # classify by file suffix # suffixclass(name: string): (ref Content, ref Content) { typ, enc: ref Content; p := str->splitstrr(name, "/").t1; if(p != nil) name = p; for(;;){ (name, p) = suffix(name); # TO DO: match below is case sensitive if(p == nil) break; for(l := suffixes; l != nil; l = tl l){ s := hd l; if(p == s.suffix){ if(s.generic != nil && typ == nil) typ = Content.mk(s.generic, s.specific, nil); if(s.encoding != nil && enc == nil) enc = Content.mk(s.encoding, "", nil); if(typ != nil && enc != nil) break; } } } return (typ, enc); } suffix(s: string): (string, string) { for(n := len s; --n >= 0;) if(s[n] == '.') return (s[0: n], s[n:]); return (s, nil); } # # classify by initial contents of file # dataclass(a: array of byte): (ref Content, ref Content) { utf8 := 0; for(i := 0; i < len a;){ c := int a[i]; if(c < 16r80){ if(c < 32 && c != '\n' && c != '\r' && c != '\t' && c != '\v' && c != '\f') return (nil, nil); i++; }else{ utf8 = 1; (r, l, nil) := sys->byte2char(a, i); if(r == Sys->UTFerror) return (nil, nil); i += l; } } if(utf8) params := ("charset", "utf-8") :: nil; return (Content.mk("text", "plain", params), nil); } Content.mk(generic, specific: string, params: list of (string, string)): ref Content { c := ref Content; c.generic = generic; c.specific = specific; c.params = params; return c; } Content.check(me: self ref Content, oks: list of ref Content): int { if(oks == nil) return 1; g := str->tolower(me.generic); s := str->tolower(me.specific); for(; oks != nil; oks = tl oks){ ok := hd oks; if((ok.generic == g || ok.generic=="*") && (s == nil || ok.specific == s || ok.specific=="*")) return 1; } return 0; } Content.text(c: self ref Content): string { if((s := c.specific) != nil) s = c.generic+"/"+s; else s = c.generic; for(l := c.params; l != nil; l = tl l){ (n, v) := hd l; s += sys->sprint(";%s=%s", n, quote(v)); } return s; } # # should probably be in a Mime or HTTP module # Quotable: con "()<>@,;:\\\"/[]?={} \t"; quotable(s: string): int { for(i := 0; i < len s; i++) if(str->in(s[i], Quotable)) return 1; return 0; } quote(s: string): string { if(!quotable(s)) return s; q := "\""; for(i := 0; i < len s; i++){ if(str->in(s[i], Quotable)) q[len q] = '\\'; q[len q] = s[i]; } q[len q] = '"'; return q; } weekdays := array[] of { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; months := array[] of { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; # print dates in the format # Wkd, DD Mon YYYY HH:MM:SS GMT sec2date(t: int): string { tm := daytime->gmt(t); return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", weekdays[tm.wday], tm.mday, months[tm.mon], tm.year+1900, tm.hour, tm.min, tm.sec); } # parse dates of formats # Wkd, DD Mon YYYY HH:MM:SS GMT # Weekday, DD-Mon-YY HH:MM:SS GMT # Wkd Mon ( D|DD) HH:MM:SS YYYY # plus anything similar date2sec(date: string): int { tm := daytime->string2tm(date); if(tm == nil || tm.year < 70 || tm.zone != "GMT") t := 0; else t = daytime->tm2epoch(tm); return t; } now(): int { return daytime->now(); } time(): string { return sec2date(daytime->now()); }