ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
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());
}