module dinc.mime.Document; private import dinc.mime.Part; private import dinc.mime.debugs; //debug = Document; class Document : Part { private import dinc.mime.Source; private import std.stdio; private: bool _headerIsParsed= false; bool _allIsParsed = false; public: /* getters */ bool headerIsParsed() { return this._headerIsParsed; } bool allIsParsed() { return this._allIsParsed; } public: this(Source s) { super(s); } void clear() { this.members.length = 0; this.h.clear(); this._allIsParsed = false; this._headerIsParsed = false; } void parseOnlyHeader() { if (this._allIsParsed || this._headerIsParsed) { return; } this._headerIsParsed = true; // where is mimeSource (and what is it..) //source.reset(); source.seek(0,SeekPos.Set); this.headerstartoffsetcrlf = 0; this.headerlength = 0; this.bodystartoffsetcrlf = 0; this.bodylength = 0; this.messagerfc822 = false; this.multipart = false; this.nlines = 0; this.nbodylines = 0; this.parseOnlyHeaderWithBoundary("".dup); } void parseFull() { if (this._allIsParsed) { return; } this._allIsParsed = true; //source.reset(); source.seek(0,SeekPos.Set); this.headerstartoffsetcrlf = 0; this.headerlength = 0; this.bodystartoffsetcrlf = 0; this.bodylength = 0; this.size = 0; this.messagerfc822 = false; this.multipart = false; int bsize = 0; char[] bound; this.parseFullWithBoundary( bound, bsize); // eat any trailing junk to get the correct size char c; while (source.getChar(c)) { } size = source.getOffset(); this.partName = "1"; foreach(i,p; members) { this.setPartName(partName, i); } } // message rfc822 means a completely enclosed mime document. we // call the parser recursively, and pass on the boundary string // that we got. when parse() finds this boundary, it returns 0. if // it finds the end boundary (boundary + "--"), it returns != 0. void parseMessageRFC822( inout bool foundendofpart, inout char[] toboundary // needed??? ) { debug(Document) writefln("parseMessageRFC822: boundary: %s ",toboundary ); //uint bodystartoffsetcrlf = source.getOffset(); uint bodystartoffsetcrlf = source.position(); // parsefull returns the number of bytes that need to be removed // from the body because of the terminating boundary string. int bsize = 0; if (this.parseFullWithBoundary( toboundary, bsize)) { foundendofpart = true; } // should be set by the parser... debug(Document) writefln("parseMessageRFC822: rfc822bodylength: %d ",bodylength ); if (this.members || !this.bodylength) { // only recalc if we have members (otherwise it's a plaintext message?) // make sure bodylength doesn't overflow this.bodylength = source.position(); if (this.bodylength >= bodystartoffsetcrlf) { this.bodylength -= bodystartoffsetcrlf; // bsize is from parseFull inout if (this.bodylength >= cast(uint) bsize) { this.bodylength -= cast(uint) bsize; } else { this.bodylength = 0; } } else { this.bodylength = 0; } } debug(Document) writefln("parseMessageRFC822: bodylength: %d ",bodylength ); } // really stream util... void parseMultipart( char[] boundary, inout char[] toboundary, inout bool eof, // should be on the stream?! inout uint nlines, inout int boundarysize, inout bool foundendofpart, inout uint bodylength, inout Part[] members, Part owner) { debug(Document) writefln("parseMultipart ENTER"); uint bodystartoffsetcrlf = source.position(); // call on return... scope(exit) { // make sure bodylength doesn't overflow bodylength = source.position(); if (bodylength >= bodystartoffsetcrlf) { bodylength -= bodystartoffsetcrlf; if (bodylength >= cast(uint) boundarysize) { bodylength -= cast(uint) boundarysize; } else { bodylength = 0; } } else { bodylength = 0; } } char c1,c2,c3,c4; // multipart parsing starts with skipping to the first // boundary. then we call parse() for all parts. the last parse() // command will return a code indicating that it found the last // boundary of this multipart. Note that the first boundary does // not have to start with CRLF. char[] delimiter = "--" ~ boundary; source.skipUntilBoundary(delimiter, nlines, eof); debug(Document) writefln("got boundary(%s) on line %d", delimiter, nlines); if (!eof) { boundarysize = delimiter.length; } // Read two more characters. This may be CRLF, it may be "--" and // it may be any other two characters. if (!source.getChar(c1)) { eof = true; return; } nlines += c1 == '\n' ? 1 : 0; if (!source.getChar(c2)) { eof = true; return; } nlines += c2 == '\n' ? 1 : 0; // If we find two dashes after the boundary, then this is the end // of boundary marker. if (c1 == '-' && c2 == '-') { foundendofpart = true; boundarysize += 2; if (!source.getChar(c1)) { eof = true; return; } nlines += c1 == '\n' ? 1 : 0; if (!source.getChar(c2)) { eof = true; return; } nlines += c2 == '\n' ? 1 : 0; } if (c1 == '\n' || (c1 == '\r' && c2 == '\n')) { // This exception is to handle a special case where the // delimiter of one part is not followed by CRLF, but // immediately followed by a CRLF prefixed delimiter. // if we have \n not \r\n -- remove the second char... int back = 3; if (c1 == '\n') { source.ungetc(c2); back = 2; } if (!source.getChar(c1) || !source.getChar(c2)) { eof = true; return; } if (c1 == '-' && c2 == '-') { source.seek(-(back+2), SeekPos.Current); // go back 4...??? why??? } else { source.seek(-back, SeekPos.Current); } boundarysize += back-1; } else { // not followed by \r\n.. (or \n) source.seek(-2, SeekPos.Current); } if (foundendofpart) { return; } source.getChar(c1); debug(Document) writefln("parseMultipart READ ALL MIME PARTS %s", ""~c1); source.ungetc(c1); // read all mime parts. bool quit = false; do { Part m = new Part(source); // If parseFull returns != 0, then it encountered the multipart's // final boundary. int bsize = 0; if (m.parseFullWithBoundary( boundary, bsize)) { debug(Document) writefln("parseMultipart parseFullWithBoundary END "); quit = true; boundarysize = bsize; } m.parent = owner; members ~= m; source.firePartEnd(m); } while (!quit); if (eof) { // not sure why this would happen... return; } // multipart parsing starts with skipping to the first // boundary. then we call parse() for all parts. the last parse() // command will return a code indicating that it found the last // boundary of this multipart. Note that the first boundary does // not have to start with CRLF. //char[] delimiterA = "\r\n--" ~ toboundary; char[] delimiterA = "\n--" ~ toboundary; source.skipUntilBoundary(delimiterA, nlines, eof); if (eof) { return; // no point in parsing!!!! } boundarysize = delimiterA.length; // Read two more characters. This may be CRLF, it may be "--" and // it may be any other two characters. if (!source.getChar(c1)) { eof = true; return; } nlines += c1 == '\n' ? 1 : 0; if (!source.getChar(c2)) { eof = true; return; } nlines += c2 == '\n' ? 1 : 0; // If we find two dashes after the boundary, then this is the end // of boundary marker. if (c1 == '-' && c2 == '-') { foundendofpart = true; boundarysize += 2; if (!source.getChar(c1)) { eof = true; return; } nlines += c1 == '\n' ? 1 : 0; if (!source.getChar(c2)) { eof = true; return; } nlines += c2 == '\n' ? 1 : 0; } if (c1 == '\n' || (c1 == '\r' && c2 == '\n')) { int back2 = 3; if (c1 == '\n') { source.ungetc(c2); back2 = 2; } // This exception is to handle a special case where the // delimiter of one part is not followed by CRLF, but // immediately followed by a CRLF prefixed delimiter. if (!source.getChar(c3) || !source.getChar(c4)) { eof = true; } else if (c3 == '-' && c4 == '-') { source.seek(-(2+back2), SeekPos.Current); } else { source.seek(-back2, SeekPos.Current); } boundarysize += back2-1; } else { source.seek(-2, SeekPos.Current); } } void parseSinglePart( char[] toboundary, inout int boundarysize, inout uint nbodylines, inout uint nlines, inout bool eof, inout bool foundendofpart, inout uint bodylength ) { debug(Document) writefln("parseSinglePart ENTER"); uint bodystartoffsetcrlf = source.position(); char a,b; scope(exit) { //writefln("boudnarysize: %d", boundarysize); //writefln("bodylen: %d", source.getOffset()); // make sure bodylength doesn't overflow bodylength = source.position(); if (bodylength >= bodystartoffsetcrlf) { bodylength -= bodystartoffsetcrlf; if (bodylength >= cast(uint) boundarysize) { bodylength -= cast(uint) boundarysize; } else { bodylength = 0; } } else { bodylength = 0; } } debug(Document) writefln("parseSinglePart : (%d) with boundary: %s", source.position(), toboundary); // If toboundary is empty, then we read until the end of the // file. Otherwise we will read until we encounter toboundary. char[] _toboundary; if (toboundary.length) { //_toboundary = "\r\n--" ~ toboundary; _toboundary = "\n--" ~ toboundary; } if (toboundary.length && source.skipUntilBoundary(_toboundary, nlines, eof)) { boundarysize = _toboundary.length; } debug(Document) writefln("parseSinglePart(%d) : with boundary length: %d", source.position(), toboundary.length); if (!toboundary.length) { // read until end of file! while(source.getChar(a)) { } eof = true; return; } if (!source.getChar(a)) { eof = true; return; } debug(Document) writefln("parseSinglePart : First char after = '%s'", "" ~ a ); nlines += a == '\n' ? 1 : 0; if (!source.getChar(b)) { eof = true; return; } nlines += b == '\n' ? 1 : 0; if (a != '-' || b != '-') { // uneat???? if (a == '\n') { source.ungetc(b); } //source.ungetc(a); return; } boundarysize += 2; foundendofpart = true; if (!source.getChar(a)) { eof = true; return; } nlines += a == '\n' ? 1 : 0; if (!source.getChar(b)) { eof = true; return; } nlines += b == '\n' ? 1 : 0; if (a == '\n' || (a == '\r' && b == '\n')) { int back = 3; if (a == '\n') { source.ungetc(b); back = 2; } // This exception is to handle a special case where the // delimiter of one part is not followed by CRLF, but // immediately followed by a CRLF prefixed delimiter. if (!source.getChar(a) || !source.getChar(b)) { eof = true; } else if (a == '-' && b == '-') { source.seek(-(back+2), SeekPos.Current); } else { source.seek(-back, SeekPos.Current); } boundarysize += back-1; } else { source.seek(-2, SeekPos.Current); } debug(Document) writefln("parseSinglePart : EXIT" ); //writefln("bodylen final: %d", bodylength); } }