diff --git a/src/common/Net/Mime.cpp b/src/common/Net/Mime.cpp --- a/src/common/Net/Mime.cpp +++ b/src/common/Net/Mime.cpp @@ -1,1676 +1,1693 @@ #include #include "lgi/common/LgiNetInc.h" #include "lgi/common/Lgi.h" #include "lgi/common/Mime.h" #include "lgi/common/Base64.h" #define DEBUG_MIME 0 #if DEBUG_MIME #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif static const char *MimeEol = "\r\n"; static const char *MimeWs = " \t\r\n"; static const char *MimeStr = "\'\""; static const char *MimeQuotedPrintable = "quoted-printable"; static const char *MimeBase64 = "base64"; #define MimeMagic ( ('M'<<24) | ('I'<<24) | ('M'<<24) | ('E'<<24) ) #define SkipWs(s) while (*s && strchr(MimeWs, *s)) s++ #define SkipNonWs(s) while (*s && !strchr(MimeWs, *s)) s++ const char *LMime::DefaultCharset = "text/plain"; template int CastInt(T in) { int out = (int)in; LAssert(out == in); return out; } int AltScore(char *Mt) { int Score = 0; if (Mt) { if (stristr(Mt, "/html")) Score = 1; else if (stristr(Mt, "/related")) Score = 2; } // printf("Score '%s' = %i\n", Mt, Score); DeleteArray(Mt); return Score; } int AltSortCmp(LMime **a, LMime **b) { int a_score = AltScore((*a)->GetMimeType()); int b_score = AltScore((*b)->GetMimeType()); return a_score - b_score; } /////////////////////////////////////////////////////////////////////// enum MimeBoundary { MimeData = 0, MimeNextSeg = 1, MimeEndSeg = 2 }; MimeBoundary IsMimeBoundary(char *Boundary, char *Line) { - if (Boundary) + if (!Boundary || !Line) + return MimeData; + + auto BoundaryLen = strlen(Boundary); + if (Line[0] != '-' || + Line[1] != '-') + return MimeData; + + if (strncmp(Line + 2, Boundary, BoundaryLen) == 0) { - size_t BoundaryLen = strlen(Boundary); - if (Line && - *Line++ == '-' && - *Line++ == '-' && - strncmp(Line, Boundary, strlen(Boundary)) == 0) + printf("matched prefix: %s\n", Line); + + // MIME segment boundary + Line += 2 + BoundaryLen; + if (Line[0] == '-' && + Line[1] == '-') { - // MIME segment boundary - Line += BoundaryLen; - if (Line[0] == '-' && - Line[1] == '-') - { - return MimeEndSeg; - } - else - { - return MimeNextSeg; - } + return MimeEndSeg; } + else + { + return MimeNextSeg; + } + } + else + { + printf("no match: line='%s' doesn't match boundry='%s'\n", Line + 2, Boundary); } return MimeData; } void CreateMimeBoundary(char *Buf, int BufLen) { if (Buf) { static int Count = 1; sprintf_s(Buf, BufLen, "--%x-%x-%x--", (int)LCurrentTime(), (int)(uint64)LGetCurrentThread(), Count++); } } /////////////////////////////////////////////////////////////////////// class LCoderStream : public LStream { protected: LStreamI *Out; public: LCoderStream(LStreamI *o) { Out = o; } }; class LMimeTextEncode : public LCoderStream { // This code needs to make sure it writes an end-of-line at // the end, otherwise a following MIME boundary could be missed. bool LastEol; public: LMimeTextEncode(LStreamI *o) : LCoderStream(o) { LastEol = false; } ~LMimeTextEncode() { if (!LastEol) { #ifdef _DEBUG ssize_t w = #endif Out->Write(MimeEol, 2); LAssert(w == 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { // Make sure any new lines are \r\n char *s = (char*)p, *e = s + size; ssize_t wr = 0; while (s < e) { char *c = s; while (c < e && *c != '\r' && *c != '\n') c++; if (c > s) { ptrdiff_t bytes = c - s; ssize_t w = Out->Write(s, (int)bytes); if (w != bytes) return wr; wr += w; LastEol = false; } while (c < e && (*c == '\r' || *c == '\n')) { if (*c == '\n') { ssize_t w = Out->Write(MimeEol, 2); if (w != 2) return wr; LastEol = true; } wr++; c++; } s = c; } return wr; } }; class LMimeQuotedPrintableEncode : public LCoderStream { // 'd' is our current position in 'Buf' char *d; // A buffer for a line of quoted printable text char Buf[128]; public: LMimeQuotedPrintableEncode(LStreamI *o) : LCoderStream(o) { Buf[0] = 0; d = Buf; } ~LMimeQuotedPrintableEncode() { if (d > Buf) { // Write partial line *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; Out->Write(Buf, CastInt(Len)); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { char *s = (char*)p; char *e = s + size; while (s < e) { if (*s == '\n' || !*s) { *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } if (!*s) { break; } d = Buf; s++; } else if (*s & 0x80 || *s == '.' || *s == '=') { int Ch = sprintf_s(d, sizeof(Buf)-(d-Buf), "=%2.2X", (uchar)*s); if (Ch < 0) { LAssert(!"printf error"); break; } d += Ch; s++; } else if (*s != '\r') { *d++ = *s++; } else { // Consume any '\r' without outputting them s++; } if (d-Buf > 73) { // time for a new line. *d++ = '='; *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d-Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } d = Buf; } } return CastInt(s - (const char*)p); } }; class LMimeQuotedPrintableDecode : public LCoderStream { uint8_t ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } public: LMimeQuotedPrintableDecode(LStreamI *o) : LCoderStream(o) {} ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Written = 0; char Line[1024]; char *o = Line; const char *s = (const char*) p; const char *e = s + size; #define NEXT(ptr) if (++ptr >= e) break for (const char *s = (const char*)p; s < e; ) { if (*s == '=') { NEXT(s); // skip '=' if (*s == '\r' || *s == '\n') { if (*s == '\r') NEXT(s); if (*s == '\n') NEXT(s); } else if (*s) { uint8_t hi = ConvHexToBin(*s++); if (s >= e) break; uint8_t low = ConvHexToBin(*s++); if (s >= e) break; *o++ = (hi << 4) | (low & 0xF); } else break; } else { *o++ = *s++; } if (o - Line > 1000) { auto w = Out->Write(Line, o - Line); if (w > 0) { Written += w; o = Line; } else break; // Error } } if (o > Line) { auto w = Out->Write(Line, o - Line); if (w > 0) Written += w; } return Written; } }; #define BASE64_LINE_SZ 76 #define BASE64_READ_SZ (BASE64_LINE_SZ*3/4) class LMimeBase64Encode : public LCoderStream { LMemQueue Buf; public: LMimeBase64Encode(LStreamI *o) : LCoderStream(o) {} ~LMimeBase64Encode() { uchar b[100]; int64 Len = Buf.GetSize(); LAssert(Len < sizeof(b)); ssize_t r = Buf.Read(b, CastInt(Len)); if (r > 0) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); Out->Write(t, w); Out->Write(MimeEol, 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { Buf.Write((uchar*)p, size); int64 Sz; while ((Sz = Buf.GetSize()) >= BASE64_READ_SZ) { uchar b[100]; ssize_t r = Buf.Read(b, BASE64_READ_SZ); if (r) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); if (w > 0) { Out->Write(t, w); Out->Write(MimeEol, 2); } else LAssert(0); } else return 0; } return size; } }; class LMimeBase64Decode : public LCoderStream { LStringPipe Buf; uint8_t Lut[256]; public: LMimeBase64Decode(LStreamI *o) : LCoderStream(o) { ZeroObj(Lut); memset(Lut+(int)'a', 1, 'z'-'a'+1); memset(Lut+(int)'A', 1, 'Z'-'A'+1); memset(Lut+(int)'0', 1, '9'-'0'+1); Lut[(int)'+'] = 1; Lut[(int)'/'] = 1; Lut[(int)'='] = 1; } ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Status = 0; // Push non whitespace into the memory buf char *s = (char*)p; char *e = s + size; while (s < e) { while (*s && s < e && !Lut[(int)*s]) s++; char *Start = s; while (*s && s < e && Lut[(int)*s]) s++; if (s-Start > 0) Buf.Push(Start, CastInt(s-Start)); else break; } // While there is at least one run of base64 (4 bytes) convert it to text // and write it to the output stream int Size; while ((Size = CastInt(Buf.GetSize())) > 3) { Size &= ~3; char t[256]; ssize_t r = MIN(sizeof(t), Size); if ((r = Buf.LMemQueue::Read((uchar*)t, r)) > 0) { uchar b[256]; ssize_t w = ConvertBase64ToBinary(b, sizeof(b), t, r); Out->Write(b, w); Status += w; } } return Status; } }; /////////////////////////////////////////////////////////////////////// LMimeBuf::LMimeBuf(LStreamI *src, LStreamEnd *end) : LStringPipe(BlockSz) { Src = src; End = end; if (Src) Src->SetPos(0); } // Read some data from 'src' // \returns true if some data was received. bool LMimeBuf::ReadSrc() { if (!Src) return false; auto b = GetBuffer(); if (!b) return false; auto rd = Src->Read(b.ptr, b.len); if (rd <= 0) return false; b.Commit(rd); return true; } ssize_t LMimeBuf::Pop(LArray &Out) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Out))) { if (Src) { char Buf[2048]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { if ((int64)Out.Length() < Sz) Out.Length(Sz); Ret = LStringPipe::Read(Out.AddressOf(), Sz); } break; } } return Ret; } ssize_t LMimeBuf::Pop(char *Str, ssize_t BufSize) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Str, BufSize))) { if (Src) { char Buf[1024]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { Ret = LStringPipe::Read(Str, BufSize); } break; } } return Ret; } /////////////////////////////////////////////////////////////////////// // Mime Object LMime::LMime(const char *tmp) { Parent = 0; TmpPath = NewStr(tmp); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; Text.Decode.Mime = this; Text.Encode.Mime = this; Binary.Read.Mime = this; Binary.Write.Mime = this; } LMime::~LMime() { Remove(); Empty(); DeleteArray(TmpPath); } char *LMime::GetFileName() { char *n = GetSub("Content-Type", "Name"); if (!n) n = GetSub("Content-Disposition", "Filename"); if (!n) { n = Get("Content-Location"); if (n) { char *trim = TrimStr(n, "\'\""); if (trim) { DeleteArray(n); n = trim; } } } return n; } LMime *LMime::NewChild() { LMime *n = new LMime(GetTmpPath()); if (n) Insert(n); return n; } bool LMime::Insert(LMime *m, int Pos) { LAssert(m != NULL); if (!m) return false; if (m->Parent) { LAssert(m->Parent->Children.HasItem(m)); m->Parent->Children.Delete(m, true); } m->Parent = this; LAssert(!Children.HasItem(m)); if (Pos >= 0) Children.AddAt(Pos, m); else Children.Add(m); return true; } void LMime::Remove() { if (Parent) { LAssert(Parent->Children.HasItem(this)); Parent->Children.Delete(this, true); Parent = 0; } } LMime *LMime::operator[](uint32_t i) { if (i >= Children.Length()) return 0; return Children[i]; } char *LMime::GetTmpPath() { for (LMime *m = this; m; m = m->Parent) { if (m->TmpPath) return m->TmpPath; } return 0; } bool LMime::SetHeaders(const char *h) { Headers = h; return Headers != 0; } bool LMime::Lock() { bool Lock = true; if (DataLock) { Lock = DataLock->Lock(_FL); } return Lock; } void LMime::Unlock() { if (DataLock) { DataLock->Unlock(); } } bool LMime::CreateTempData() { bool Status = false; DataPos = 0; DataSize = 0; OwnDataStore = true; if ((DataStore = new LTempStream(GetTmpPath(), 4 << 20))) { Status = true; } return Status; } LStreamI *LMime::GetData(bool Detach) { LStreamI *Ds = DataStore; if (Ds) { Ds->SetPos(DataPos); if (Detach) { LOG("%s:%i - Detaching %p from %p\n", _FL, DataStore, this); DataStore = 0; } } return Ds; } bool LMime::SetData(bool OwnStream, LStreamI *d, int Pos, int Size, LMutex *l) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (d) { OwnDataStore = OwnStream; DataPos = Pos; DataSize = Size >= 0 ? Size : (int)d->GetSize(); DataLock = l; DataStore = d; } return true; } bool LMime::SetData(char *Str, int Len) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (Str) { if (Len < 0) { Len = (int)strlen(Str); } DataLock = 0; DataPos = 0; DataSize = Len; DataStore = new LTempStream(GetTmpPath(), 4 << 20); if (DataStore) { DataStore->Write(Str, Len); } } return true; } void LMime::Empty() { if (OwnDataStore) { if (Lock()) { DeleteObj(DataStore); Unlock(); } } while (Children.Length()) delete Children[0]; Headers.Empty(); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; } char *LMime::NewValue(char *&s, bool Alloc) { char *Status = 0; int Inc = 0; char *End; if (strchr(MimeStr, *s)) { // Delimited string char Delim = *s++; End = strchr(s, Delim); Inc = 1; } else { // Raw string End = s; while (*End && *End != ';' && *End != '\n' && *End != '\r') End++; while (strchr(MimeWs, End[-1])) End--; } if (End) { if (Alloc) { Status = NewStr(s, End-s); } s = End + Inc; } SkipWs(s); return Status; } char *LMime::StartOfField(char *s, const char *Field) { if (s && Field) { size_t FieldLen = strlen(Field); while (s && *s) { if (strchr(MimeWs, *s)) { s = strchr(s, '\n'); if (s) s++; } else { char *f = s; while (*s && *s != ':' && !strchr(MimeWs, *s)) s++; int fLen = CastInt(s - f); if (*s++ == ':' && fLen == FieldLen && _strnicmp(f, Field, FieldLen) == 0) { return f; break; } else { s = strchr(s, '\n'); if (s) s++; } } } } return 0; } char *LMime::NextField(char *s) { while (s) { while (*s && *s != '\n') s++; if (*s == '\n') { s++; if (!strchr(MimeWs, *s)) { break; } } else { break; } } return s; } char *LMime::Get(const char *Name, bool Short, const char *Default) { char *Status = 0; if (Name && Headers) { char *s = StartOfField(Headers, Name); if (s) { s = strchr(s, ':'); if (s) { s++; SkipWs(s); if (Short) { Status = NewValue(s); } else { char *e = NextField(s); while (strchr(MimeWs, e[-1])) e--; Status = NewStr(s, e-s); } } } if (!Status && Default) { Status = NewStr(Default); } } return Status; } bool LMime::Set(const char *Name, const char *Value) { if (!Name) return false; LStringPipe p; char *h = Headers; if (h) { char *f = StartOfField(h, Name); if (f) { // 'Name' exists, push out pre 'Name' header text p.Push(h, CastInt(f - Headers)); h = NextField(f); } else { if (!Value) { // Nothing to do here... return true; } // 'Name' doesn't exist, push out all the headers p.Push(Headers); h = 0; } } if (Value) { // Push new field int Vlen = CastInt(strlen(Value)); while (Vlen > 0 && strchr(MimeWs, Value[Vlen-1])) Vlen--; p.Push(Name); p.Push(": "); p.Push(Value, Vlen); p.Push(MimeEol); } // else we're deleting the feild if (h) { // Push out any header text post the 'Name' field. p.Push(h); } Headers = p.NewLStr(); return Headers != NULL; } char *LMime::GetSub(const char *Field, const char *Sub) { - char *Status = 0; + if (!Field || !Sub) + return NULL; + + auto v = Get(Field, false); + if (!v) + return NULL; + + auto SubLen = strlen(Sub); + char *Status = NULL; - if (Field && Sub) + // Move past the field value into the sub fields + char *s = v; + + SkipWs(s); + while (*s && *s != ';' && !strchr(MimeWs, *s)) + s++; + + SkipWs(s); + + while (s && *s++ == ';') { - int SubLen = CastInt(strlen(Sub)); - char *v = Get(Field, false); - if (v) + // Parse each name=value pair + SkipWs(s); + auto Name = s; + while (*s && *s != '=' && !strchr(MimeWs, *s)) + s++; + + auto NameLen = s - Name; + SkipWs(s); + + // printf("found field '%.*s'\n", (int)NameLen, Name); + if (*s++ == '=') { - // Move past the field value into the sub fields - char *s = v; - SkipWs(s); - while (*s && *s != ';' && !strchr(MimeWs, *s)) s++; + bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0; SkipWs(s); - while (s && *s++ == ';') - { - // Parse each name=value pair - SkipWs(s); - char *Name = s; - while (*s && *s != '=' && !strchr(MimeWs, *s)) s++; - int NameLen = CastInt(s - Name); - SkipWs(s); - if (*s++ == '=') - { - bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0; - SkipWs(s); - Status = NewValue(s, Found); - if (Found) break; - } - else break; - } + Status = NewValue(s, Found); + if (Found) + break; + } + else break; + } - DeleteArray(v); - } - } + DeleteArray(v); return Status; } bool LMime::SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue) { if (Field && Sub) { char Buf[256]; char *s = StartOfField(Headers, Field); if (s) { // Header already exists s = strchr(s, ':'); if (s++) { SkipWs(s); LStringPipe p; // Push the field data char *e = s; while (*e && !strchr("; \t\r\n", *e)) e++; p.Push(s, CastInt(e-s)); SkipWs(e); // Loop through the subfields and push all those that are not 'Sub' s = e; while (*s++ == ';') { SkipWs(s); char *e = s; while (*e && *e != '=' && !strchr(MimeWs, *e)) e++; char *Name = NewStr(s, e-s); if (Name) { s = e; SkipWs(s); if (*s++ == '=') { char *v = NewValue(s); if (_stricmp(Name, Sub) != 0) { sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Name, v); p.Push(Buf); } DeleteArray(v); } else break; } else break; } if (Value) { // Push the new sub field sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Sub, Value); p.Push(Buf); } char *Data = p.NewStr(); if (Data) { Set(Field, Data); DeleteArray(Data); } } } else if (DefaultValue) { // Header doesn't exist at all if (Value) { // Set sprintf_s(Buf, sizeof(Buf), "%s;\r\n\t%s=\"%s\"", DefaultValue, Sub, Value); return Set(Field, Buf); } else { // Remove return Set(Field, DefaultValue); } } } return false; } ///////////////////////////////////////////////////////////////////////// // Mime Text Conversion // Rfc822 Text -> Object ssize_t LMime::LMimeText::LMimeDecode::Pull(LStreamI *Source, LStreamEnd *End) { LMimeBuf Buf(Source, End); // Stream -> Lines return Parse(&Buf); } class ParentState { public: char *Boundary = NULL; MimeBoundary Type; ParentState() { Type = MimeData; } }; ssize_t LMime::LMimeText::LMimeDecode::Parse(LMimeBuf *Source, ParentState *State) { ssize_t Status = 0; if (!Mime || !Source) { LOG("%s:%i - Arg error %p %p.\n", _FL, Mime, Source); return Status; } Mime->Empty(); if (!Mime->CreateTempData()) { LOG("CreateTempData failed.\n"); LAssert(!"CreateTempData failed."); return Status; } LAssert(Mime->DataStore != NULL); // Read the headers.. if (Buffer.Length() == 0) Buffer.Length(1 << 10); LOG("%s:%i - Reading headers...\n", _FL); ssize_t r; while ((r = Source->Find("\r\n\r\n")) < 0) { if (!Source->ReadSrc()) break; } if (r < 0) // No break between headers and body found. return Status; // Not an error Mime->Headers = Source->ReadStr(r + 4); LOG("%s:%i - Mime->Headers=%i\n", _FL, Mime->Headers?(int)Mime->Headers.Length():-1); // Get various bits out of the header LAutoString Encoding(Mime->GetEncoding()); LAutoString Boundary(Mime->GetBoundary()); LAutoString MimeType(Mime->GetMimeType()); - LOG("%s:%i - Encoding=%s, MimeType=%s, Boundary=%s\n", _FL, Encoding, MimeType.Get(), Boundary); + LOG("%s:%i - Encoding=%s, MimeType=%s, Boundary=%s\n", + _FL, Encoding.Get(), MimeType.Get(), Boundary.Get()); LStream *Decoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Decoder = new LMimeQuotedPrintableDecode(Mime->DataStore); LOG("%s:%i - Using LMimeQuotedPrintableDecode\n", _FL); } else if (_stricmp(Encoding, MimeBase64) == 0) { Decoder = new LMimeBase64Decode(Mime->DataStore); LOG("%s:%i - Using LMimeBase64Decode\n", _FL); } else { LOG("%s:%i - Unknown encoding '%s'\n", _FL, Encoding); } } Encoding.Reset(); // Read in the rest of the MIME segment bool Done = false; // int64 StartPos = Mime->DataStore->GetPos(); while (!Done) { // Process existing lines ssize_t Len; ssize_t Written = 0; Status = true; while ((Len = Source->Pop(Buffer)) > 0) { // Check for boundary MimeBoundary Type = MimeData; auto b = Buffer.AddressOf(); if (Boundary) { bool CouldBe = Buffer.Length() > 2 && b[0] == '-' && b[1] == '-'; Type = IsMimeBoundary(Boundary, b); - + if (Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, Type); } else if (CouldBe) { LOG("%s:%i - CouldBe '%s'\n", _FL, b); } } if (State) { State->Type = IsMimeBoundary(State->Boundary, b); if (State->Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, State->Type); Status = Done = true; break; } } DoSegment: if (Type == MimeNextSeg) { ParentState MyState; MyState.Boundary = Boundary; LMime *Seg = new LMime(Mime->GetTmpPath()); if (Seg && Seg->Text.Decode.Parse(Source, &MyState)) { LOG("%s:%i - Inserting child seg.\n", _FL); Mime->Insert(Seg); if (MyState.Type) { Type = MyState.Type; goto DoSegment; } } else { LOG("%s:%i - Text.Decode.Parse failed.\n", _FL); break; } } else if (Type == MimeEndSeg) { Done = true; LOG("%s:%i - MimeEndSeg.\n", _FL); break; } else { // Process data if (Decoder) { Written += Decoder->Write(Buffer.AddressOf(), Len); } else { ssize_t w = Mime->DataStore->Write(Buffer.AddressOf(), Len); if (w > 0) { Written += w; } else { LOG("%s:%i - w 0\n", _FL); Done = true; Status = false; break; } } } } Mime->DataSize = Written; if (Len == 0) { LOG("%s:%i - Len 0\n", _FL); Done = true; } } LOG("%s:%i - Finished\n", _FL); return Status; } void LMime::LMimeText::LMimeDecode::Empty() { } // Object -> Rfc822 Text ssize_t LMime::LMimeText::LMimeEncode::Push(LStreamI *Dest, LStreamEnd *End) { int Status = 0; if (Mime) { char Buf[1024]; int Ch; // Check boundary char *Boundary = Mime->GetBoundary(); if (Mime->Children.Length()) { // Boundary required if (!Boundary) { // Create one char b[256]; CreateMimeBoundary(b, sizeof(b)); Mime->SetBoundary(b); Boundary = Mime->GetBoundary(); } } else if (Boundary) { // Remove boundary Mime->SetBoundary(0); DeleteArray(Boundary); } // Check encoding char *Encoding = Mime->GetEncoding(); if (!Encoding) { // Detect an appropriate encoding int MaxLine = 0; bool Has8Bit = false; bool HasBin = false; if (Mime->DataStore && Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); int x = 0; for (ssize_t i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize - i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { for (int n=0; nUnlock(); } if (HasBin) { Encoding = NewStr(MimeBase64); } else if (Has8Bit || MaxLine > 70) { Encoding = NewStr(MimeQuotedPrintable); } if (Encoding) { Mime->SetEncoding(Encoding); } } // Write the headers auto h = Mime->Headers.SplitDelimit(MimeEol); for (unsigned i=0; iWrite(h[i], CastInt(strlen(h[i]))); Dest->Write(MimeEol, 2); } Dest->Write(MimeEol, 2); // Write data LStream *Encoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Encoder = new LMimeQuotedPrintableEncode(Dest); } else if (_stricmp(Encoding, MimeBase64) == 0) { Encoder = new LMimeBase64Encode(Dest); } } if (!Encoder) { Encoder = new LMimeTextEncode(Dest); } if (Mime->DataStore) { if (Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); Status = Mime->DataSize == 0; // Nothing is a valid segment?? for (int i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize-i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { Encoder->Write(Buf, r); Status = true; } else break; } Mime->Unlock(); } } else { Status = true; } DeleteObj(Encoder); // Write children if (Mime->Children.Length() && Boundary) { LAutoString Mt(Mime->GetMimeType()); if (Mt && !_stricmp(Mt, "multipart/alternative")) { // Sort the children to order richer content at the bottom... Mime->Children.Sort(AltSortCmp); } for (unsigned i=0; iChildren.Length(); i++) { Ch = sprintf_s(Buf, sizeof(Buf), "--%s\r\n", Boundary); Dest->Write(Buf, Ch); if (!Mime->Children[i]->Text.Encode.Push(Dest, End)) { break; } Status = 1; } Ch = sprintf_s(Buf, sizeof(Buf), "--%s--\r\n", Boundary); Dest->Write(Buf, Ch); } // Clean up DeleteArray(Encoding); DeleteArray(Boundary); } return Status; } void LMime::LMimeText::LMimeEncode::Empty() { } ///////////////////////////////////////////////////////////////////////// // Mime Binary Serialization // Source -> Object ssize_t LMime::LMimeBinary::LMimeRead::Pull(LStreamI *Source, LStreamEnd *End) { if (Source) { int32 Header[4]; Mime->Empty(); // Read header block (Magic, HeaderSize, DataSize, # of Children) // and check magic if (Source->Read(Header, sizeof(Header)) == sizeof(Header) && Header[0] == MimeMagic) { // Read header data Mime->Headers.Length(Header[1]+1); if (Mime->Headers && Source->Read(Mime->Headers, Header[1]) == Header[1]) { // NUL terminate Mime->Headers.Get()[Mime->Headers.Length()] = 0; // Skip body data if (Source->SetPos(Source->GetPos() + Header[2]) > 0) { // Read the children in for (int i=0; iGetTmpPath()); if (c && c->Binary.Read.Pull(Source, End)) { Mime->Insert(c); } else break; } } } return 1; // success } } return 0; // failure } void LMime::LMimeBinary::LMimeRead::Empty() { } // Object -> Dest int64 LMime::LMimeBinary::LMimeWrite::GetSize() { int64 Size = 0; if (Mime) { Size = (sizeof(int32) * 4) + // Header magic + block sizes (Mime->Headers ? Mime->Headers.Length() : 0) + // Headers (Mime->DataStore ? Mime->DataSize : 0); // Data // Children for (unsigned i=0; iChildren.Length(); i++) { Size += Mime->Children[i]->Binary.Write.GetSize(); } } return Size; } ssize_t LMime::LMimeBinary::LMimeWrite::Push(LStreamI *Dest, LStreamEnd *End) { if (Dest && Mime) { int32 Header[4] = { MimeMagic, Mime->Headers ? (int32)Mime->Headers.Length() : 0, Mime->DataStore ? (int32)Mime->DataSize : 0, (int32) Mime->Children.Length() }; if (Dest->Write(Header, sizeof(Header)) == sizeof(Header)) { if (Mime->Headers) { Dest->Write(Mime->Headers, Header[1]); } if (Mime->DataStore) { char Buf[1024]; ssize_t Written = 0; ssize_t Read = 0; ssize_t r; while ((r = Mime->DataStore->Read(Buf, MIN(sizeof(Buf), Header[2]-Read) )) > 0) { ssize_t w; if ((w = Dest->Write(Buf, r)) <= 0) { // Write error break; } Written += w; Read += r; } // Check we've written out all the data LAssert(Written < Header[2]); if (Written < Header[2]) { return 0; } } for (unsigned i=0; iChildren.Length(); i++) { Mime->Children[i]->Binary.Write.Push(Dest, End); } return 1; } } return 0; } void LMime::LMimeBinary::LMimeWrite::Empty() { }