module dinc.mime.Header; /* something to manage the header key=>value items */ private import dinc.mime.debugs; private import std.stdio; private import std.stream; //debug = Header; class Header { private import std.string; private import std.gc; private import dinc.mime.Source; private import dinc.mime.HeaderItem; private import dinc.mime.HeaderDetails; private: HeaderItem[] content; public: // reading HeaderItem getFirstHeader(char[] key) { char[] k = std.string.tolower(key); //debug(Header) std.stdio.writefln("getFirstHeader:looking for to %s", k); foreach(int i, HeaderItem hi; this.content) { //debug(Header) std.stdio.writefln("getFirstHeader:comparing %s to %s", std.string.tolower(hi.key), k); if (std.string.tolower(hi.key) == k) { //debug(Header) std.stdio.writefln("getFirstHeader:got it"); return hi; } } return null; } char[] getFirstHeaderString(char[] key) { auto r = getFirstHeader(key); return (r is null) ? "" : r.value; } char[] getFirstHeaderStringUTF8(char[] key) { auto r = getFirstHeader(key); return (r is null) ? "" : r.valueAsUTF8(); } char[][] getAddressListUTF8(char[] key) { HeaderItem[] hi = getAllHeaders(key); char[][] ret; if (!hi.length) { return ret; } foreach(h; hi ) { ret ~= h.extractAddressListUTF8(); } return ret; } HeaderItem[] getHeaderItems() { return content; } HeaderItem[] getAllHeaders(char[] key) { HeaderItem[] ret; ret.length = this.content.length; ret.length =0; char[] k = std.string.tolower(key); foreach(int i, HeaderItem hi; this.content) { if (std.string.tolower(hi.key()) == k) { ret ~= hi; // this may be a little ineffecient...? } } return ret; } ubyte[] toUbyte() { ubyte[] r = cast(ubyte[]) ""; foreach(i,hi; this.content) { if (i > 0) { r ~= cast(ubyte[])"\r\n"; } r~= cast(ubyte[])(hi.key ~ ": " ~ hi.value); // no wrapping!!??!?!?! } return r; } char[][] getReplyTos() { char[][] ret; if (!getFirstHeader("In-Reply-To") && !getFirstHeader("References")) { return null; } void addR(char[] str) { if (!str.length) { return; } int start = -1; for(int i=0;i < str.length; i++) { if (str[i] == '<') { start = i; continue; } if (start < 0) { continue; } if (str[i] != '>') { continue; } // dupes.? ret ~= str[start..i+1]; // dup? start = -1; } } addR(getFirstHeaderString("In-Reply-To")); addR(getFirstHeaderString("References")); return ret; } // writing void add(char[] name, char[] content) { this.content ~= new HeaderItem(name,content); //debug(Header) std.stdio.writefln("add:content size now=%d", this.content.length); } void clear() { this.content.length = 0; } this() { this.content.length = 60; this.content.length = 0; } bool parseOneHeaderLine(Source mimeSource, inout uint nlines ) { //scope (exit) { // as we create a bit of memory, that leaks... // std.gc.genCollect(); //} //debug(Header) std.stdio.writefln("parseOneHeaderLine: start"); auto name = mimeSource.ms1; // should this help??/ auto content = mimeSource.ms2; // should this help??/ name.len = 0; name.cur = 0; content.len = 0; content.cur = 0; char c; bool eof = false; char[4] cqueue; //char[] name; //name.length = 256; //name.length = 0; //char[] content; while (mimeSource.getChar(c)) { //q If we encounter a \r before we got to the first ':', then // rewind back to the start of the line and assume we're at the // start of the body. //debug(Header) std.stdio.writefln("parseOneHeaderLine: start=%x", c); if (c == '\r') { mimeSource.seek(-1 * (name.len + 1), SeekPos.Current); debug(Header) std.stdio.writefln("parseOneHeaderLine: get \\r = returning false!!"); return false; } // A colon marks the end of the header name if (c == ':') { break; } if (c == ' ') { // 'From' lines..??? name.write('*'); // add a * to indicate it's a non coloned' line.. break; } // Otherwise add to the header name name.write(c); } debug(Header) std.stdio.writefln("parseOneHeaderLine: got name=%s",name.toString()); cqueue[0] = '\0'; cqueue[1] = '\0'; cqueue[2] = '\0'; cqueue[3] = '\0'; // Read until the end of the header. bool endOfHeaders = false; while (!endOfHeaders) { if (!mimeSource.getChar(c)) { eof = true; break; } nlines += (c == '\n') ? 1 : 0; // shift the cqueue.. //debug(Header) std.stdio.writefln("parseOneHeaderLine: cqueue=%x,%x,%x,%x", cqueue[0], cqueue[1], cqueue[2], cqueue[3]); //cqueue[0..2] = cqueue[1..3]; for (int i = 0; i < 3; ++i) { cqueue[i] = cqueue[i + 1]; } cqueue[3] = c; if (cqueue == "\r\n\r\n") { endOfHeaders = true; break; } // If the last character was a newline, and the first now is not // whitespace, then rewind one character and store the current // key,value pair. if (cqueue[2] == '\n' && c != ' ' && c != '\t') { //if (content.len > 2) { // content.len -= 2; //} debug(Header) std.stdio.writefln("parseOneHeaderLine: adding name=%s, content=%s",name.toString(),content.toString()); this.add(name.toString().dup, std.string.strip(content.toString().dup)); // last char was \n -> next was \r or \n // assume it was the end of the header!!! if (c == '\r' || c == '\n' ) { debug(Header) std.stdio.writefln("parseOneHeaderLine: last char was CR -return false"); if (c == '\r') { // if it was \r then eat the next char.. mimeSource.getChar(c); } return false; } debug(Header) std.stdio.writefln("parseOneHeaderLine: last char not CR -return true"); mimeSource.seek(-1, SeekPos.Current); //mimeSource.ungetc(nc); //mimeSource.ungetc(nc); nlines += (c == '\n') ? 1 : 0; return true; } content.write(c); } if (name.len != 0) { //if (content.len > 2) { // content.len -= 2; //} //content = std.string.strip(content.toString()); debug(Header) std.stdio.writefln("parseOneHeaderLine: adding name=%s, content=%s",name.toString(),content.toString()); this.add(name.toString().dup, std.string.strip(content.toString)); } return !(eof || endOfHeaders); } void parseHeader(Source mimeSource, inout uint nlines ) { while(this.parseOneHeaderLine(mimeSource,nlines)) { } ; mimeSource.fireHeaderEnd(this); } /* returns NULL|HeaderDetails */ HeaderDetails analyzeHeader() { auto ret = new HeaderDetails ; HeaderItem ctype; ctype = this.getFirstHeader("content-type"); if (ctype is null) { debug(Header) writefln("analyzeHeader: no content-type"); return ret; //ctype = "text/plain"; } char[][char[]] types = ctype.extractParts(); if (!(":" in types)) { debug(Header) writefln("analyzeHeader: got NO types?"); foreach(key,value;types) { debug(Header) writefln("analyzeHeader: got types '%s'='%s' ", key,value); } } if (":" in types) { // first element should describe content type char[] tmp = types[":"]; tmp = std.string.strip(tmp); char[][] v = std.string.split(tmp, "/"); char[] key, value; key = (v.length > 0) ? std.string.tolower(v[0]) : "text"; value = (v.length > 1) ? std.string.tolower(v[1]) : "plain"; ret.maintype = key; ret.subtype = value; if (key == "multipart") { ret.multipart = true; ret.subtype = value; } else if (key == "message") { if (value == "rfc822") { ret.messagerfc822 = true; } } foreach(key,value;types) { debug(Header) writefln("analyzeHeader: got types '%s'='%s' ", key,value); switch(key) { case ":": continue; case "boundary": ret.boundary = std.string.strip(this.strip(value," \"\t")); continue; case "charset": ret.charset = std.string.strip(this.strip(value," \"\t"));; continue; case "format": ret.format = std.string.strip(this.strip(value," \"\t"));; continue; case "delsp": ret.delsp = std.string.strip(this.strip(value," \"\t"));; continue; case "name": ret.name = std.string.strip(this.strip(value," \"\t"));; continue; default: continue; } } } ctype = this.getFirstHeader("content-disposition"); if (ctype is null) { return ret; //ctype = "text/plain"; } types = ctype.extractParts(); if (":" in types) { foreach(key,value;types) { // ignore disposition - inline/ attachment.. ret.content_disposition = std.string.strip(this.strip(types[":"]," \"\t")); switch(key) { case ":": continue; case "filename": ret.filename = std.string.strip(this.strip(value," \"\t"));; continue; case "creation-date": ret.creation_date = std.string.strip(this.strip(value," \"\t"));; continue; case "modification-date": ret.modification_date = std.string.strip(this.strip(value," \"\t"));; continue; case "read-date": ret.read_date = std.string.strip(this.strip(value," \"\t"));; continue; case "size": ret.filesize = std.string.strip(this.strip(value," \"\t"));; continue; default: continue; } } } return ret; } void dumpOut(int offs = 0) { //std.stdio.writefln("HEADERS:"); char[] ind = std.string.repeat(" ", ((offs+1) * 4)); foreach(int i, HeaderItem hi; this.content) { std.stdio.writefln(ind ~ " %s: %s", hi.key, hi.value); } } char[] toString() { char[] ret = ""; foreach(int i, HeaderItem hi; this.content) { ret ~= hi.key ~ ": " ~ hi.value ~"\n"; } return ret; } char[] stripl(char[] s, char[] c) { uint i; for (i = 0; i < s.length; i++) { bool found = false; for(uint ii =0;ii < c.length; ii++) { if (c[ii] == s[i]) { found = true; break; } } if (!found) break; } return s[i .. s.length]; } char[] stripr(char[] s, char[] c) /// ditto { uint i; for (i = s.length ; i > 0; i--) { bool found = false; for(uint ii =0;ii < c.length; ii++) { if (c[ii] == s[i-1]) { found = true; break; } } if (!found) break; } return s[0 .. i]; } char[] strip(char[] s, char[] c) /// ditto { return stripr(stripl(s,c),c); } }