module dinc.mime.Part; //debug = Part; class Part { private import std.stream; private import std.stdio; private import std.gc; private import dinc.mime.Document; private import dinc.mime.Source; private import dinc.mime.Header; private import dinc.mime.HeaderItem; private import dinc.mime.HeaderDetails; bool multipart = false; bool messagerfc822 = false; char[] maintype; char[] subtype; char[] boundary; char[] partName; char[] charset = ""; char[] format = ""; char[] delsp = ""; char[] name = ""; char[] filename = ""; char[] creation_date = ""; char[] modification_date = ""; char[] read_date = ""; char[] filesize = ""; char[] content_disposition = ""; uint headerstartoffsetcrlf; uint headerlength; uint bodystartoffsetcrlf; uint bodylength; uint nlines = 0; uint nbodylines = 0; uint size = 0; enum FetchType { FetchBody, FetchHeader, FetchMime }; Header h; Part[] members; Part parent; Source source; this(Source s) { this.h = new Header; this.source = s; } void clear() { this.members.length = 0; this.h.clear(); } void dumpFunc(void delegate(Part) fp) { fp(this); for (int i = 0; i < members.length; i++ ){ this.members[i].dumpFunc(fp); } } void dumpOut(int offs = 0) { char[] ind = std.string.repeat(" ", ((offs+1) * 4)); std.stdio.writefln(ind ~ "Part: dump()"); std.stdio.writefln(ind ~ "multipart = %s" , (this.multipart ? " YES" : "NO") ); std.stdio.writefln(ind ~ "messagerfc822 = %s" , (this.messagerfc822 ? " YES" : "NO") ); std.stdio.writefln(ind ~ "maintype = %s" , this.maintype ); std.stdio.writefln(ind ~ "subtype = %s" , this.subtype ); std.stdio.writefln(ind ~ "boundary = %s" , this.boundary ); std.stdio.writefln(ind ~ "headerstartoffsetcrlf = %d" , this.headerstartoffsetcrlf ); std.stdio.writefln(ind ~ "headerlength = %d" , this.headerlength ); std.stdio.writefln(ind ~ "bodystartoffsetcrlf = %d" , this.bodystartoffsetcrlf ); std.stdio.writefln(ind ~ "bodylength = %d" , this.bodylength ); std.stdio.writefln(ind ~ "nlines = %d" , this.nlines ); std.stdio.writefln(ind ~ "nbodylines = %d" , this.nbodylines ); std.stdio.writefln(ind ~ "size = %d" , this.size ); std.stdio.writefln(ind ~ "Headers: " ); this.h.dumpOut(offs); for (int i = 0; i < members.length; i++ ){ this.members[i].dumpOut(offs +1); } std.stdio.writefln(ind ~ "-------------\n"); } void setPartName(char[] b, int n) { this.partName = b ~ "." ~ std.string.toString(n); foreach(i,p; members) { p.setPartName(partName, i); } } void printBody( Stream output, uint startoffset, uint len =0) { source.reset(); source.seek(this.bodystartoffsetcrlf + startoffset, SeekPos.Set); if (len== 0) { //writefln("thisbodylength is : %d", bodylength); len = this.bodylength; //writefln("setting length to : %d", len); } if (startoffset + len > this.bodylength) { len = this.bodylength - startoffset; //writefln("modifying length to : %d", len); } void straightThru() { char c = '\0'; for (uint i = 0; i < len; ++i) { if (!source.getChar(c)) { return; } output.write(c); } } void quotedPrintable() { char nc = '\0'; char c = '\0'; char c1 = '\0'; char c2 = '\0'; int c1p = 0; int c2p = 0; for (uint i = 0; i < len; ++i) { if (!source.getChar(c)) { return; } if (c != '=') { output.write(c); continue; } if (!source.getChar(c1)) { output.write(c); return; } if (!source. getChar(c2)) { output.write(c); output.write(c1); return; } c1p = std.string.find(std.string.hexdigits, c1); c2p = std.string.find(std.string.hexdigits, c2); if ((c1p > -1) && (c2p > -1)) { output.write((c1p << 4) + c2p); i+=2; continue; } // unget the two chars.. source.ungetc(nc); source.ungetc(nc); int eaten = 0; while (true) { if (!source.getChar(c1)) { return; } i++; eaten++; if (i >= len) { // end of readable area! return; } if ((c1 == 32) || (c1 == 9)) { continue; } break; } // we have now eaten the white space... if (c1 == 10) { continue; } if (c1 != 13) { output.write('='); for(int ii =0; ii < eaten; ii++) { // convert them all to spaces?!?!? output.write(' '); } // not a line break... - we have eaten the = and the spaces after it!!! // should be push them onto the sttream??? continue; } // eat the next char... if (!source.getChar(c1)) { return; } if (c1 != 10) { source.ungetc(nc); continue; } i++; } } void base64() // must be wrapped in try/catch! { int i=0; // cant tell the lenght!!! //if(estr.length % 4) // throw new Base64Exception("Invalid encoded base64 string"); uint arrayIndex(char ch) { if(ch >= 'A' && ch <= 'Z') return ch - 'A'; if(ch >= 'a' && ch <= 'z') return 'Z' - 'A' + 1 + ch - 'a'; if(ch >= '0' && ch <= '9') return 'Z' - 'A' + 1 + 'z' - 'a' + 1 + ch - '0'; if(ch == '+') return 'Z' - 'A' + 1 + 'z' - 'a' + 1 + '9' - '0' + 1; if(ch == '/') return 'Z' - 'A' + 1 + 'z' - 'a' + 1 + '9' - '0' + 1 + 1; throw new Exception("invalid encoding"); } bool sgetChar(out char c) { if (i > len) { // end of what we are suppsed to read.. return false; } if (!source.getChar(c)) { return false; } if ((c == 32) || (c == 10) || (c == 13)) { return sgetChar(c); } i++; return true; } ///uint estrmax = estr.length / 4; uint x; char ch; char sp1; char sp2; while(true) { if (!sgetChar(sp1)) { return; // end!!! } if (!sgetChar(sp2)) { return; // end!!! } x = arrayIndex(sp1) << 18 | arrayIndex(sp2) << 12; if (!sgetChar(ch)) { return; // end!!! } if(ch == '=') { if (!sgetChar(ch)) { return; // end!!! } if(ch!= '=') { return; // invalid but we allow it.. } output.write(cast(char) (x >> 16)); return; } x |= arrayIndex(ch) << 6; if (!sgetChar(ch)) { return; // end!!! } if(ch == '=') { output.write(cast(char) (x >> 16)); output.write(cast(char) ((x >> 8) & 0xFF)); return; } x |= arrayIndex(ch); output.write(cast(char) (x >> 16)); output.write(cast(char) ((x >> 8) & 0xFF)); output.write(cast(char) (x & 0xFF)); } } // find out the transfer encoding. char[] encoding = ""; auto ctype = this.h.getFirstHeader("content-transfer-encoding"); if (!(ctype is null)) { encoding = ctype.value; //ctype = "text/plain"; } switch(std.string.tolower(encoding)) { case "quoted-printable": quotedPrintable(); break; case "base64": try { base64(); } catch(Exception e) { } break; case "": case "7bit": default: straightThru(); break; } } void printHeader( Stream output, char[][] headers, bool includeheaders, uint startoffset, uint length, char[] storage ) { char nc = '\0'; source.seek(this.headerstartoffsetcrlf,SeekPos.Set); char[] name = ""; char[] content = ""; char cqueue[2] = "\0\0\0"; // memset(cqueue, 0, sizeof(cqueue)); bool quit = false; char c = '\0'; uint wrotebytes = 0; uint processedbytes = 0; bool hasHeaderSeparator = false; while (!quit) { // read name while (1) { // allow EOF to end the header if (!source.getChar(c)) { quit = true; break; } // assume this character is part of the header name. name ~= c; // break on the first colon if (c == ':') { break; } // break if a '\n' turned up. if (c == '\n') { // end of headers detected if (name == "\r\n") { hasHeaderSeparator = true; quit = true; break; } // put all data back in the buffer to the beginning of this // line. for (int i = name.length; i >= 0; --i) { source.ungetc(nc); } // abort printing of header. note that in this case, the // headers will not end with a seperate \r\n. quit = true; name = ""; break; } } if (quit) { break; } // at this point, we have a name, that is - the start of a // header. we'll read until the end of the header. while (!quit) { // allow EOF to end the header. if (!source.getChar(c)) { quit = true; break; } if (c == '\n') { ++nlines; } // make a fifo queue of the last 4 characters. cqueue[0] = cqueue[1]; cqueue[1] = c; // print header if (cqueue[0] == '\n' && cqueue[1] != '\t' && cqueue[1] != ' ') { // it wasn't a space, so put it back as it is most likely // the start of a header name. in any case it terminates the // content part of this header. source.ungetc(nc); char[] lowername = std.string.tolower(name); lowername = std.string.chomp(lowername, ": \t"); bool foundMatch = false; for (int i = 0; i < headers.length ; i++ ) { char[] nametmp = std.string.tolower(headers[i]); if (nametmp == lowername) { foundMatch = true; break; } } // ---------- Got this far ------------- if (foundMatch == includeheaders || headers.length == 0) { char[] outs = name ~ content; for (int i = 0; i < outs.length; i++) { if ((processedbytes >= startoffset) && (wrotebytes < length)) { if (processedbytes >= startoffset) { storage ~= outs[i]; wrotebytes++; } } else { processedbytes++; } } // move on to the next header content = ""; name = ""; break; } content ~= c; } } if (name != "") { char[] lowername = std.string.tolower(name); std.string.chomp(lowername, ": \t"); // might work.. bool foundMatch = false; for (int i = 0 ; i < headers.length; i++ ) { char[] nametmp = std.string.tolower(headers[i]); if (nametmp == lowername) { foundMatch = true; break; } } if (hasHeaderSeparator || (foundMatch == includeheaders) || headers.length == 0) { char[] outs = name ~ content; for (int i = 0; i < outs.length; i++) { if ((processedbytes >= startoffset) && (wrotebytes < length)) { if (processedbytes >= startoffset) { storage ~= outs[i]; wrotebytes++; } } else { processedbytes++; } } } } } } void printDoc( Stream output, uint startoffset, uint length) { source.reset(); source.seek(headerstartoffsetcrlf, SeekPos.Set); char c; for (uint i = 0; i < length; ++i) { if (!source.getChar(c)) { break; } output.write(c); } } Part getPart(char[] findpart, char[] genpart, FetchType fetchType = FetchType.FetchBody) { if (findpart == genpart) { return this; } if (this.multipart) { if (this.members.length != 0) { for(int i=0;i< this.members.length; i++) { char[] ss; ss.length = 256; ss.length = 0; ss = genpart; if (genpart != "") { ss ~= "."; } int part = i +1; ss ~= std.string.toString(part); Part m; m = this.members[i].getPart(findpart,ss); if (m is null) { continue; } return m; } } } else if (this.messagerfc822) { if (this.members.length== 1) { return this.members[0].getPart(findpart, genpart); } else { return null; } } else { // Singlepart if (genpart != "") { genpart ~= "."; } genpart ~= "1"; if (findpart == genpart) { return this; } } return null; } int parseOnlyHeaderWithBoundary(char[] toboundary) { //char[] name = ""; MemoryStream nameStream = source.ms1; // should this help??/ MemoryStream contentStream = source.ms2; // should this help??/ nameStream.len = 0; nameStream.cur = 0; contentStream.len = 0; contentStream.cur = 0; //char[] content = ""; char[4] cqueue; //memset(cqueue, 0, sizeof(cqueue)); this.headerstartoffsetcrlf = source.position(); bool quit = false; char c = '\0'; while (!quit) { // read name while (1) { if (!source.getChar(c)) { quit = true; break; } nlines += c == '\n' ? 1 : 0; if (c == ':') { break; } if (c == '\n') { source.seek(-nameStream.len, SeekPos.Current); quit = true; nameStream.len = 0; nameStream.cur = 0; break; } nameStream.write(c); if ((nameStream.len == 2) && (nameStream.buf[0] == 'r') && (nameStream.buf[0] == '\n')) { nameStream.len = 0; nameStream.cur = 0; quit = true; break; } } if (nameStream.len == 1 && (nameStream.buf[0] == '\r')) { nameStream.len = 0; nameStream.cur = 0; break; } if (quit) { break; } while (!quit) { if (!source.getChar(c)) { quit = true; break; } nlines += c == '\n' ? 1 : 0; for (int i = 0; i < 3; ++i) { cqueue[i] = cqueue[i + 1]; } cqueue[3] = c; if (cqueue[0..3] == "\r\n\r\n") { quit = true; break; } if (cqueue[2..3] == "\n\n") { quit = true; break; } if (cqueue[2] == '\n') { // guess the mime rfc says what can not appear on the beginning // of a line. if (!std.string.iswhite(cqueue[3])) { if (contentStream.len > 2) { // reduce by 2 chars... contentStream.len -= 2; } this.h.add(nameStream.toString().dup, std.string.strip(contentStream.toString())); //name = "" ~ c; nameStream.len =0; nameStream.cur =0; nameStream.write(c); // start the new name stream... contentStream.len = 0; contentStream.cur = 0; //content = ""; break; } } contentStream.write(c); //content ~= c; } } if (nameStream.len) { if (contentStream.len > 2) { contentStream.len -= 2; } this.h.add(nameStream.toString().dup, std.string.strip(contentStream.toString())); } this.headerlength = source.position() - this.headerstartoffsetcrlf; //source.fireHeaderEnd(this.h); return 1; } int parseFullWithBoundary(inout char[] toboundary, inout int boundarysize) { //scope (exit) { // as we create a bit of memory, that leaks... // std.gc.genCollect(); //} debug(Part) std.stdio.writefln("parseFullWithBoundary --%s--", toboundary); this.headerstartoffsetcrlf = source.position(); // Parse the header of this mime part. this.h.parseHeader(source, nlines); // returns into nlines.. //source.fireHeaderEnd(this.h); debug(Part) std.stdio.writefln("Got nlines in header %d", nlines); // Headerlength includes the seperating CRLF. Body starts after the // CRLF. this.headerlength = source.position() - headerstartoffsetcrlf; this.bodystartoffsetcrlf = source.position(); this.bodylength = 0; // Determine the type of mime part by looking at fields in the // header. //multipart, messagerfc822, subtype, boundary HeaderDetails hd = this.h.analyzeHeader(); if (hd is null) { debug(Part) std.stdio.writefln("Anaylise header failed --%s--", toboundary); return 0; // error condition! } this.multipart = hd.multipart; this.messagerfc822 = hd.messagerfc822; this.maintype= hd.maintype; this.subtype = hd.subtype; this.boundary = hd.boundary; this.charset = hd.charset; this.format = hd.format; this.delsp = hd.delsp; this.name = hd.name; content_disposition = hd.content_disposition; filename = hd.filename; creation_date = hd.creation_date; modification_date = hd.modification_date ; read_date = hd.read_date; filesize = hd.filesize; debug(Part) hd.dumpOut(); bool eof = false; bool foundendofpart = false; if (hd.messagerfc822) { auto d = new Document(source); d.parseMessageRFC822(foundendofpart, toboundary); d.parent = this; source.firePartEnd(cast(Part)d); this.members ~= cast(Part)d; } else if (hd.multipart) { auto d = new Document(source); d.parseMultipart( this.boundary, toboundary, eof, // should be on the stream?! this.nlines, boundarysize, foundendofpart, this.bodylength, this.members, this); } else { auto d = new Document(source); d.parseSinglePart( toboundary, boundarysize, this.nbodylines, this.nlines, eof, foundendofpart, this.bodylength ); //writefln("AFTER parseSinglepart - bodylen is %d", this.bodylength); } debug(Part) std.stdio.writefln("Part.parseFullWithBoundary(%d) EOF:%d EOP:%d", nlines, eof,foundendofpart ); return (eof || foundendofpart) ? 1 : 0; } bool is_coverletter() { if (!parent) { return true; } char[] subtypel = std.string.tolower(subtype); if (parent.multipart && ( ( subtypel == "alternative") || ( subtypel == "related"))) { return true; } if (parent.multipart && this == parent.members[0]) { return true; } //if (parent.multipart) { // return false; //} // not sure about attchment/multipart.. return false; } }