diff --git a/include/lgi/common/DataDlg.h b/include/lgi/common/DataDlg.h --- a/include/lgi/common/DataDlg.h +++ b/include/lgi/common/DataDlg.h @@ -1,314 +1,314 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief Tools for getting data into and out of the UI #ifndef __DATA_DLG_TOOLS #define __DATA_DLG_TOOLS #include "lgi/common/Properties.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/TextLabel.h" enum DataDlgType { DATA_STR = 1, DATA_BOOL, DATA_INT, DATA_FLOAT, DATA_PASSWORD, DATA_STR_SYSTEM, // Operating system specific string. Has the OS // tag automatically appended to the option name // e.g. SomeProperty-Win32 DATA_FILENAME, }; class DataDlgTools; /// UI <-> properties field class DataDlgField { friend class DataDlgTools; protected: int Type; int CtrlId; char *Option; char *Desc; public: DataDlgField(int type, int ctrlid, char *opt, char *desc = 0); ~DataDlgField(); int GetType() { return Type; } int GetCtrl() { return CtrlId; } char *GetOption() { return Option; } char *GetDesc() { return Desc; } void SetOption(char *o) { char *Opt = NewStr(o); DeleteArray(Option); Option = Opt; } }; typedef List DataDlgFieldList; /// UI <-> properties mapping class class DataDlgTools { protected: bool DeleteEmptyStrings; LView *Dlg; ObjProperties *Options; public: DataDlgTools(); void Set(LView *Dlg, ObjProperties *Options); ObjProperties *GetOptions(); bool ProcessField(DataDlgField *f, bool Write, char *OptionOverride = 0); bool ProcessFields(DataDlgFieldList &Field, bool Write); bool LoadFields(DataDlgFieldList &Field); bool SaveFields(DataDlgFieldList &Field); }; class DRecordSetObj { public: virtual bool Serialize(ObjProperties &f, bool Write) { return false; } }; template class DRecordSetCtrls : public DataDlgTools { // Controls LTextLabel *Description; LScrollBar *Scroll; int NewRecordId; int DeleteRecordId; DataDlgFieldList *Fields; protected: Record *Current; Lst *Records; public: char *RecordStringTemplate; DRecordSetCtrls( LView *window, DataDlgFieldList *fields, Lst *records, int DescId, int ScrollId, int NewId, int DelId, char *Template) { Current = 0; RecordStringTemplate = NewStr(Template); Dlg = window; Fields = fields; Records = records; NewRecordId = NewId; DeleteRecordId = DelId; if (Dlg) { Description = dynamic_cast(Dlg->FindControl(DescId)); Scroll = dynamic_cast(Dlg->FindControl(ScrollId)); OnMoveRecord(dynamic_cast(Records->First())); } } ~DRecordSetCtrls() { DeleteArray(RecordStringTemplate); } virtual Record *NewObj() { return 0; } virtual void DelObj(Record *Obj) {} int GetCurrentIndex() { return (Records && Current) ? Records->IndexOf(Current) : -1; } void Serialize(bool Write) { if (Fields && Dlg && Current) { ObjProperties Temp; Options = &Temp; if (Write) { // Move data from controls to propertiy list SaveFields(*Fields); // Move data from property list to record Current->Serialize(Temp, false); } else { // Move data from record to property list Current->Serialize(Temp, true); // Load the new data LoadFields(*Fields); } Options = 0; } } void OnMoveRecord(Record *r) { if (Dlg && Fields) { Serialize(true); Current = r; Serialize(false); // Set controls enabled flag correctly - for (auto f: Fields) + for (auto f: *Fields) { LView *Ctrl = dynamic_cast(Dlg->FindControl(f->GetCtrl())); if (Ctrl) { if (!Current) { // clear controls } Ctrl->Enabled(Current != 0); } } // Set the scrollbar if (Scroll) { Scroll->SetRange(Records->Length()); Scroll->Value((Current) ? Records->IndexOf(Current) : 0); Scroll->SetPage(1); } // Set the description if (Description) { char Str[256]; sprintf(Str, RecordStringTemplate ? RecordStringTemplate : (char*)"Record: %i of %i", GetCurrentIndex()+1, (Records) ? Records->Length() : 0); Description->Name(Str); } } } int OnNotify(LViewI *Col, int Flags) { if (Fields && Dlg && Records) { if (Col == Scroll) { int CurrentIndex = (Current) ? Records->IndexOf(Current) : -1; int NewIndex = Col->Value(); if (CurrentIndex != NewIndex) { OnMoveRecord(dynamic_cast((*Records)[NewIndex])); } } if (Col->GetId() == NewRecordId) { Record *r = NewObj(); if (r) { Records->Insert(r); OnMoveRecord(r); } } if (Col->GetId() == DeleteRecordId) { int Index = GetCurrentIndex(); LAssert(Index >= 0); Record *r = dynamic_cast((*Records)[Index]); if (r) { Records->Delete(r); Index = limit(Index, 0, Records->Length()-1); OnMoveRecord(Index >= 0 ? dynamic_cast((*Records)[Index]) : 0); DelObj(r); } } } return 0; } }; template class DRecordSet : public DRecordSetCtrls { public: typedef Record *(*Allocator)(LView *); DRecordSet ( LView *window, DataDlgFieldList *fields, Lst *records, int DescId, int ScrollId, int NewId, int DelId, char *Template, Allocator a ) : DRecordSetCtrls ( window, fields, records, DescId, ScrollId, NewId, DelId, Template ) { Alloc = a; } Record *NewObj() { return Alloc ? Alloc(DRecordSetCtrls::Dlg) : 0; } void DelObj(Record *Obj) { if (Obj) { DeleteObj(Obj); } } Record *ItemAt(int i=-1) { return (i<0 || !DRecordSetCtrls::Records) ? dynamic_cast(DRecordSetCtrls::Current) : dynamic_cast( (*DRecordSetCtrls::Records)[i] ); } void OnMoveRecord(Record *r) { DRecordSetCtrls::OnMoveRecord(r); } private: Allocator Alloc; }; #endif diff --git a/src/common/Net/Mail.cpp b/src/common/Net/Mail.cpp --- a/src/common/Net/Mail.cpp +++ b/src/common/Net/Mail.cpp @@ -1,2694 +1,2694 @@ /*hdr ** FILE: Mail.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Mail app ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mail.h" #include "lgi/common/Base64.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" #include "lgi/common/Store3Defs.h" #include "lgi/common/LgiRes.h" #include "lgi/common/TextConvert.h" #include "lgi/common/Mime.h" #include "../Hash/md5/md5.h" const char *sTextPlain = "text/plain"; const char *sTextHtml = "text/html"; const char *sTextXml = "text/xml"; const char *sApplicationInternetExplorer = "application/internet-explorer"; const char sMultipartMixed[] = "multipart/mixed"; const char sMultipartEncrypted[] = "multipart/encrypted"; const char sMultipartSigned[] = "multipart/signed"; const char sMultipartAlternative[] = "multipart/alternative"; const char sMultipartRelated[] = "multipart/related"; const char sAppOctetStream[] = "application/octet-stream"; ////////////////////////////////////////////////////////////////////////////////////////////////// LogEntry::LogEntry(LColour col) { c = col; } bool LogEntry::Add(const char *t, ssize_t len) { if (!t) return false; if (len < 0) len = strlen(t); /* // Strip off any whitespace on the end of the line. while (len > 0 && strchr(" \t\r\n", t[len-1])) len--; */ LAutoWString w(Utf8ToWide(t, len)); if (!w) return false; size_t ch = StrlenW(w); return Txt.Add(w, ch); } bool Base64Str(LString &s) { LString b64; ssize_t Base64Len = BufferLen_BinTo64(s.Length()); if (!b64.Set(NULL, Base64Len)) return false; #ifdef _DEBUG ssize_t Ch = #endif ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length()); LAssert(Ch == b64.Length()); s = b64; return true; } bool UnBase64Str(LString &s) { LString Bin; ssize_t BinLen = BufferLen_64ToBin(s.Length()); if (!Bin.Set(NULL, BinLen)) return false; ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length()); LAssert(Ch <= (int)Bin.Length()); s = Bin; s.Get()[Ch] = 0; return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// // returns the maximum length of the lines contained in the string int MaxLineLen(char *Text) { if (!Text) return false; int Max = 0; int i = 0; for (char *c = Text; *c; c++) { if (*c == '\r') { // return } else if (*c == '\n') { // eol Max = MAX(i, Max); i = 0; } else { // normal char i++; } } return Max; } bool IsDotLined(char *Text) { if (Text) { for (char *l = Text; l && *l; ) { if (l[0] == '.') { if (l[1] == '\n' || l[1] == 0) { return true; } } l = strchr(l, '\n'); if (l) l++; } } return false; } // Is s a valid non-whitespace string? bool ValidNonWSStr(const char *s) { if (s && *s) { while (*s && strchr(" \r\t\n", *s)) { s++; } if (*s) { return true; } } return false; } void TokeniseStrList(char *Str, List &Output, const char *Delim) { if (Str && Delim) { char *s = Str; while (*s) { while (*s && strchr(WhiteSpace, *s)) s++; char *e = s; for (; *e; e++) { if (strchr("\'\"", *e)) { // handle string constant char delim = *e++; e = strchr(e, delim); } else if (*e == '<') { e = strchr(e, '>'); } else { while (*e && *e != '<' && !IsWhiteSpace(*e) && !strchr(Delim, *e)) e++; } if (!e || !*e || strchr(Delim, *e)) { break; } } ssize_t Len = e ? e - s : strlen(s); if (Len > 0) { char *Temp = new char[Len+1]; if (Temp) { memcpy(Temp, s, Len); Temp[Len] = 0; Output.Insert(Temp); } } if (e) { s = e; for (; *s && strchr(Delim, *s); s++); } else break; } } } //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void DeNullText(char *in, ssize_t &len) { char *out = in; char *end = in + len; while (in < end) { if (*in) { *out++ = *in; } else { len--; } in++; } } ////////////////////////////////////////////////////////////////////////////// typedef char CharPair[2]; static CharPair Pairs[] = { {'<', '>'}, {'(', ')'}, {'\'', '\''}, {'\"', '\"'}, {0, 0}, }; struct MailAddrPart { LAutoString Part; bool Brackets; bool ValidEmail; LAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs) { char *s = Str; if (Len < 0) Len = strlen(s); while (*s && strchr(WhiteSpace, *s)) { s++; Len--; } if (!*s) return LAutoString(); // Get the end of the string... char *e = s; if (Len < 0) e += strlen(s); else e += Len; // Seek back over any trailing whitespace while (e > s && strchr(WhiteSpace, e[-1])) e--; for (CharPair *p = Pairs; (*p)[0]; p++) { if ((*p)[0] == *s && (*p)[1] == e[-1]) { s++; e--; if (s < e) { // reset search p = Pairs - 1; } else break; } } Len = e - s; if (Len < 0) return LAutoString(); return LAutoString(NewStr(s, Len)); } MailAddrPart(char *s, ssize_t len) { ValidEmail = false; Brackets = false; if (s) { if (len < 0) len = strlen(s); while (strchr(WhiteSpace, *s) && len > 0) { s++; len--; } Brackets = *s == '<'; Part = RemovePairs(s, len, Pairs); // ValidEmail = IsValidEmail(Part); } } int Score() { if (!Part) return 0; return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0); } }; int PartCmp(LAutoPtr *a, LAutoPtr *b) { return (*b)->Score() - (*a)->Score(); } bool IsAngleBrackets(LString &s) { if (s(0) == '<' && s(-1) == '>') return true; return false; } void DecodeAddrName(const char *Str, std::function Cb, const char *DefaultDomain) { if (!Str) return; LString s = Str; LString non; LString email; LString::Array a; auto startBracket = s.Find("<"); auto endBracket = s.Find(">", startBracket); if (startBracket >= 0 && endBracket >= 0) { // Keep the angle brackets for the time being... a.New() = s(0, startBracket) + s(++endBracket, -1); a.New() = s(startBracket, endBracket); } else a.New() = s; for (unsigned i=0; i"); } else { non += a[i]; } } if (!email) { a = s.SplitDelimit("()"); non.Empty(); for (unsigned i=0; i 0) { const char *ChSet = " \t\r\n\'\"<>"; do { non = non.Strip(ChSet); } while (non.Length() > 0 && strchr(ChSet, non(0))); } Cb(non, email.Strip()); } void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain) { DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){ Name.Reset(NewStr(n)); Addr.Reset(NewStr(a)); }, DefaultDomain); } void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain) { DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){ Name = n; Addr = a; }, DefaultDomain); } #if 0 struct LDecodeAddrNameTest { LDecodeAddrNameTest() { // Testing code char *Input[] = { "\"Sound&Secure@speedytechnical.com\" ", "\"@MM-Social Mailman List\" ", "'Matthew Allen (fret)' ", "Matthew Allen (fret) ", "\"'Matthew Allen'\" ", "Matthew Allen", "fret@memecode.com", "\"\" ", " (fret@memecode.com)", "Matthew Allen ", "\"Matthew, Allen\" (fret@memecode.com)", "Matt'hew Allen ", "john.omalley ", "Bankers' Association (ABA)", "'Amy's Mum' ", "\"Philip Doggett (JIRA)\" ", "\"group name\" ", NULL }; LAutoString Name, Addr; for (char **i = Input; *i; i++) { Name.Reset(); Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); LgiTrace("N=%-#32s A=%-32s\n", Name, Addr); } int asd=0; } } DecodeAddrNameTest; #endif void StrCopyToEOL(char *d, char *s) { if (d && s) { while (*s && *s != '\r' && *s != '\n') { *d++ = *s++; } *d = 0; } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailTransaction::MailTransaction() { Index = -1; Flags = 0; Status = false; Oversize = false; Stream = 0; UserData = 0; } MailTransaction::~MailTransaction() { } ////////////////////////////////////////////////////////////////////////////////////////////////// FileDescriptor::FileDescriptor() { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; ContentId = 0; Lock = 0; OwnEmbeded = false; } FileDescriptor::FileDescriptor(LStreamI *embed, int64 offset, int64 size, char *name) { Embeded = embed; Offset = offset; Size = size; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); } } FileDescriptor::FileDescriptor(char *name) { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); if (File.Open(name, O_READ)) { Size = File.GetSize(); File.Close(); } } } FileDescriptor::FileDescriptor(char *data, int64 len) { Embeded = 0; Offset = 0; MimeType = 0; Lock = 0; ContentId = 0; Size = len; OwnEmbeded = false; Data = data ? new uchar[(size_t)Size] : 0; if (Data) { memcpy(Data, data, (size_t)Size); } } FileDescriptor::~FileDescriptor() { if (OwnEmbeded) { DeleteObj(Embeded); } DeleteArray(MimeType); DeleteArray(ContentId); DeleteArray(Data); } void FileDescriptor::SetOwnEmbeded(bool i) { OwnEmbeded = i; } void FileDescriptor::SetLock(LMutex *l) { Lock = l; } LMutex *FileDescriptor::GetLock() { return Lock; } LStreamI *FileDescriptor::GotoObject() { if (Embeded) { Embeded->SetPos(Offset); return Embeded; } else if (Name() && File.Open(Name(), O_READ)) { return &File; } else if (Data && Size > 0) { DataStream.Reset(new LMemStream(Data, Size, false)); return DataStream; } return 0; } int FileDescriptor::Sizeof() { return (int)Size; } uchar *FileDescriptor::GetData() { return Data; } bool FileDescriptor::Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLen) { bool Status = false; int Content = CONTENT_NONE; if (ContentType && ContentTransferEncoding) { // Content-Type: application/octet-stream; name="Scribe.opt" Content = CONTENT_OCTET_STREAM; if (strnistr(ContentTransferEncoding, "base64", 1000)) { Content = CONTENT_BASE64; } if (strnistr(ContentTransferEncoding, "quoted-printable", 1000)) { Content = CONTENT_QUOTED_PRINTABLE; } if (Content != CONTENT_NONE) { const char *NameKey = "name"; char *n = strnistr(ContentType, NameKey, 1000); if (n) { char *Equal = strchr(n, '='); if (Equal) { Equal++; while (*Equal && *Equal == '\"') { Equal++; } char *End = strchr(Equal, '\"'); if (End) { *End = 0; } Name(Equal); Status = true; } } } } if (Status && MimeData && MimeDataLen > 0 && Content != CONTENT_NONE) { Status = false; char *Base64 = new char[MimeDataLen]; switch (Content) { case CONTENT_OCTET_STREAM: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen]; if (Data) { Size = MimeDataLen; memcpy(Data, MimeData, (size_t)Size); Status = true; } break; } case CONTENT_QUOTED_PRINTABLE: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen+1]; if (Data) { char *Out = (char*) Data; for (int i=0; i= Size - 3; if (Status) { Size = Converted; } else { DeleteArray(Data); Size = 0; } } break; } } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// AddressDescriptor::AddressDescriptor(const AddressDescriptor *Copy) { if (Copy) { Status = Copy->Status; CC = Copy->CC; sAddr = Copy->sAddr; sName = Copy->sName; } } AddressDescriptor::~AddressDescriptor() { _Delete(); } void AddressDescriptor::_Delete() { Status = false; CC = MAIL_ADDR_CC; sName.Empty(); sAddr.Empty(); } LString AddressDescriptor::Print() { LString s; char delim = '\''; if (sName) { bool hasSingle = sName.Find("\'") >= 0; bool hasDouble = sName.Find("\"") >= 0; if (hasSingle && !hasDouble) delim = '\"'; } if (sAddr && sName) s.Printf("%c%s%c <%s>", delim, sAddr.Get(), delim, sName.Get()); else if (sAddr) s.Printf("<%s>", sAddr.Get()); else if (sName) s.Printf("%c%s%c", delim, sName.Get(), delim); return s; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailProtocol::MailProtocol() : SocketLock("MailProtocol") { } MailProtocol::~MailProtocol() { } void MailProtocol::Log(const char *Str, LSocketI::SocketMsgType type) { if (Logger && Str) { char s[1024]; char *e = s + sizeof(s) - 2; const char *i = Str; char *o = s; while (*i && o < e) { *o++ = *i++; } while (o > s && (o[-1] == '\r' || o[-1] == '\n')) o--; *o++ = '\n'; *o = 0; Logger->Write(s, o - s, type); } } bool MailProtocol::Error(const char *file, int line, const char *msg, ...) { char s[1024]; va_list a; va_start(a, msg); vsprintf_s(s, sizeof(s), msg, a); va_end(a); Log(s, LSocketI::SocketMsgError); LgiTrace("%s:%i - Error: %s", file, line, s); return false; } bool MailProtocol::Read() { bool Status = false; if (Socket) { Status = Socket->Read(Buffer, sizeof(Buffer), 0) > 0; } return Status; } bool MailProtocol::Write(const char *Buf, bool LogWrite) { bool Status = false; if (Socket) { const char *p = Buf ? Buf : Buffer; Status = Socket->Write(p, strlen(p), 0) > 0; if (LogWrite) { Log(p, LSocketI::SocketMsgSend); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// #define VERIFY_RET_VAL(Arg) \ { \ if (!Arg) \ { \ - LMutex::Auto Lck(&SocketLock, _FL); \ - Socket.Reset(0); \ - return NULL; \ + LMutex::Auto Lck(&SocketLock, _FL); \ + Socket.Reset(); \ + return 0; \ } \ } #define VERIFY_ONERR(Arg) \ { \ if (!Arg) \ { \ - LMutex::Auto Lck(&SocketLock, _FL); \ - Socket.Reset(0); \ + LMutex::Auto Lck(&SocketLock, _FL); \ + Socket.Reset() ; \ goto CleanUp; \ } \ } void Reorder(LArray &a, const char *s) { for (unsigned i=0; i 0) { a.DeleteAt(i, true); a.AddAt(0, s); break; } } } MailSmtp::MailSmtp() { } MailSmtp::~MailSmtp() { } bool MailSmtp::Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { char Str[256] = ""; bool Status = false; if (!RemoteHost) Error(_FL, "No remote SMTP host.\n"); else { strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (Port == 0) { if (Flags & MAIL_SSL) Port = SMTP_SSL_PORT; else Port = SMTP_PORT; } LAutoString Server(TrimStr(Str)); if (Server) { if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } Socket->SetTimeout(30 * 1000); char Msg[256]; sprintf_s(Msg, sizeof(Msg), "Connecting to %s:%i...", Server.Get(), Port); Log(Msg, LSocketI::SocketMsgInfo); if (!Socket->Open(Server, Port)) Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port); else { LStringPipe Str; // receive signon message VERIFY_RET_VAL(ReadReply("220")); // Rfc 2554 ESMTP authentication SmtpHello: sprintf_s(Buffer, sizeof(Buffer), "EHLO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default"); VERIFY_RET_VAL(Write(0, true)); /*bool HasSmtpExtensions =*/ ReadReply("250", &Str); bool Authed = false; bool NoAuthTypes = false; bool SupportsStartTLS = false; LArray AuthTypes; // Look through the response for the auth line LString Response = Str.NewLStr(); if (Response) { auto Lines = Response.SplitDelimit("\n"); for (auto &l: Lines) { char *AuthStr = stristr(l, "AUTH"); if (AuthStr) { // walk through AUTH types auto Types = LString(AuthStr + 4).SplitDelimit(" ,;"); for (auto &t: Types) AuthTypes.Add(t); } if (stristr(l, "STARTTLS")) SupportsStartTLS = true; } } if (SupportsStartTLS && TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("220", &Str)); LVariant v; if (Socket->SetValue(LSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? return false; } } if (TestFlag(Flags, MAIL_USE_AUTH)) { if (!ValidStr(UserName)) { // We need a user name in all authentication types. SetError(L_ERROR_ESMTP_NO_USERNAME, "No username for authentication."); return false; } if (AuthTypes.Length() == 0) { // No auth types? huh? if (TestFlag(Flags, MAIL_USE_PLAIN)) // Force plain type AuthTypes.Add("PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) // Force login type AuthTypes.Add("LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) // Force CRAM MD5 type AuthTypes.Add("CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) // Force OAUTH2 type AuthTypes.Add("XOAUTH2"); else { // Try all AuthTypes.Add("PLAIN"); AuthTypes.Add("LOGIN"); AuthTypes.Add("CRAM-MD5"); AuthTypes.Add("XOAUTH2"); } } else { // Force user preference if (TestFlag(Flags, MAIL_USE_PLAIN)) Reorder(AuthTypes, "PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) Reorder(AuthTypes, "LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) Reorder(AuthTypes, "CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) Reorder(AuthTypes, "XOAUTH2"); } for (auto Auth : AuthTypes) { // Try all their auth types against our internally support types if (Auth.Equals("LOGIN")) { VERIFY_RET_VAL(Write("AUTH LOGIN\r\n", true)); VERIFY_RET_VAL(ReadReply("334")); ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)UserName, strlen(UserName)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334") && Password) { ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)Password, strlen(Password)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } else if (Auth.Equals("PLAIN")) { char Ascii[512]; int ch = sprintf_s(Ascii, sizeof(Ascii), "%c%s%c%s", 0, UserName, 0, Password); char Base64[512] = {0}; ConvertBinaryToBase64(Base64, sizeof(Base64), (uint8_t*)Ascii, ch); sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", Base64); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } else if (Auth.Equals("CRAM-MD5")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH CRAM-MD5\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { auto Sp = strchr(Buffer, ' '); if (Sp) { Sp++; // Decode the server response: uint8_t Txt[128]; auto InLen = strlen(Sp); ssize_t TxtLen = ConvertBase64ToBinary(Txt, sizeof(Txt), Sp, InLen); // Calc the hash: // https://tools.ietf.org/html/rfc2104 char Key[64] = {0}; memcpy(Key, Password, MIN(strlen(Password), sizeof(Key))); uint8_t iKey[256]; char oKey[256]; for (unsigned i=0; i<64; i++) { iKey[i] = Key[i] ^ 0x36; oKey[i] = Key[i] ^ 0x5c; } memcpy(iKey+64, Txt, TxtLen); md5_state_t md5; md5_init(&md5); md5_append(&md5, iKey, 64 + TxtLen); md5_finish(&md5, oKey + 64); md5_init(&md5); md5_append(&md5, (uint8_t*)oKey, 64 + 16); char digest[16]; md5_finish(&md5, digest); char r[256]; int ch = sprintf_s(r, sizeof(r), "%s ", UserName); for (unsigned i=0; i<16; i++) ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8_t)digest[i]); // Base64 encode ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8_t*)r, ch); Buffer[Len++] = '\r'; Buffer[Len++] = '\n'; Buffer[Len++] = 0; VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } } else if (Auth.Equals("XOAUTH2")) { auto Log = dynamic_cast(Socket->GetLog()); LOAuth2 Authenticator(OAuth2, UserName, SettingStore, Socket->GetCancel(), Log); auto Tok = Authenticator.GetAccessToken(); if (Tok) { LString s; s.Printf("user=%s\001auth=Bearer %s\001\001\0", UserName, Tok.Get()); Base64Str(s); sprintf_s(Buffer, sizeof(Buffer), "AUTH %s %s\r\n", Auth.Get(), s.Get()); VERIFY_RET_VAL(Write(0, true)); Authed = ReadReply("235"); if (!Authed) { Authenticator.Refresh(); } } } else { LgiTrace("%s:%i - Unsupported auth type '%s'\n", _FL, Auth.Get()); } if (Authed) break; } if (!Authed) { if (NoAuthTypes) SetError(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports."); else { LString p; for (auto i : AuthTypes) { if (p.Get()) p += ", "; p += i; } SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", p); } } Status = Authed; } else { Status = true; } } } } return Status; } bool MailSmtp::WriteText(const char *Str) { // we have to convert all strings to CRLF in here bool Status = false; if (Str) { LMemQueue Temp; const char *Start = Str; while (*Str) { if (*Str == '\n') { // send a string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, Size); Temp.Write((uchar*) "\r\n", 2); Start = Str + 1; } Str++; } // send the final string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, (int)Size); Size = (int)Temp.GetSize(); char *Data = new char[(size_t)Size]; if (Data) { Temp.Read((uchar*) Data, Size); Status = Socket->Write(Data, (int)Size, 0) == Size; DeleteArray(Data); } } return Status; } void StripChars(LString &s) { s = s.Strip("\r\n"); } char *CreateAddressTag(List &l, int Type, LString::Array *CharsetPrefs) { char *Result = 0; List Addr; for (auto a: l) { if (a->CC == Type) { Addr.Insert(a); } } if (Addr.Length() > 0) { LStringPipe StrBuf; StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: "); for (auto It = Addr.begin(); It != Addr.end(); ) { auto a = *It; AddressDescriptor *NextA = *(++It); char Buffer[256] = ""; StripChars(a->sName); StripChars(a->sAddr); if (a->sAddr && strchr(a->sAddr, ',')) { // Multiple address format auto t = a->sAddr.SplitDelimit(","); for (uint32_t i=0; i", t[i].Get()); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->sName) { // Name and addr auto Name = a->sName; if (Is8Bit(Name)) Name = LEncodeRfc2047(Name, NULL/*charset*/, CharsetPrefs); if (strchr(Name, '\"')) sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name.Get(), a->sAddr.Get()); else sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name.Get(), a->sAddr.Get()); } else if (a->sAddr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->sAddr.Get()); } if (NextA) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); a = NextA; } StrBuf.Push("\r\n"); Result = StrBuf.NewStr(); } return Result; } // This class implements a pipe that writes to a socket class SocketPipe : public LStringPipe { LSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(LSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } ssize_t Read(void *Ptr, ssize_t Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LCurrentTime(); p->Range = (int)Size; return Size; } return -1; } ssize_t Write(const void *InPtr, ssize_t Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { ssize_t w = s->Write(Ptr, e - Ptr, 0); if (w > 0) { Ptr += w; if (p && p->Range && w > 0) p->Value += w; } else break; } return Ptr - (char*)InPtr; } }; bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err) { bool AddrOk = false; if (To.Length() == 0) { ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT; ErrMsgFmt = "No recipients to send to."; ErrMsgParam.Empty(); LgiTrace("%s:%i - No recipients.\n", _FL); return false; } // send MAIL message if (From && ValidStr(From->sAddr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->sAddr.Get()); } else { ErrMsgId = L_ERROR_ESMTP_NO_FROM; ErrMsgFmt = "No 'from' address in email."; ErrMsgParam.Empty(); LgiTrace("%s:%i - Invalid from '%s'.\n", _FL, From->sAddr.Get()); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.begin(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { LString Addr = ValidStr(a->sAddr) ? a->sAddr : a->sName; if (ValidStr(Addr)) { auto Parts = Addr.SplitDelimit(","); for (auto p: Parts) { sprintf_s(Buffer, sizeof(Buffer), "RCPT TO: <%s>\r\n", p.Get()); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk |= a->Status != 0; // at least one address is ok } } else if (Err) { ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT; ErrMsgFmt = "Invalid recipient '%s'."; ErrMsgParam = Addr; } } return AddrOk; } LStringPipe *MailSmtp::SendData(MailProtocolError *Err) { // send DATA message sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("354", 0, Err)); return new SocketPipe(Socket, Transfer); } LStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : NULL; } bool MailSmtp::SendEnd(LStringPipe *m) { bool Status = false; SocketPipe *Msg = dynamic_cast(m); if (Msg) { // send message terminator and receive reply if (Msg->Status && Msg->Write((void*)"\r\n.\r\n", 5, 0)) { Status = ReadReply("250"); } // else // just close the connection on them // so nothing gets sent } DeleteObj(m); return Status; } /* bool MailSmtp::Send(MailMessage *Msg, bool Mime) { bool Status = false; if (Socket && Msg) { LStringPipe *Sink = SendStart(Msg->To, Msg->From); if (Sink) { // setup a gui progress meter to send the email, // the length is just a guesstimate as we won't know the exact // size until we encode it all, and I don't want it hanging around // in memory at once, so we encode and send on the fly. int Length = 1024 + (Msg->GetBody() ? strlen(Msg->GetBody()) : 0); for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next()) { Length += f->Sizeof() * 4 / 3; } // encode and send message for transport Msg->Encode(*Sink, 0, this); Status = SendEnd(Sink); } } return Status; } */ bool MailSmtp::Close() { if (Socket) { // send QUIT message sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("221")); LMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, LStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { ssize_t Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Len > 0) { char *Eol = strstr(Start, "\r\n"); while (Eol) { // wipe EOL chars *Eol++ = 0; *Eol++ = 0; // process if (Pipe) { if (Pipe->GetSize()) Pipe->Push("\n"); Pipe->Push(Start); } if (Start[3] == ' ') { // end of response if (!strncmp(Start, Str, strlen(Str))) { Status = true; } if (Err) { Err->Code = atoi(Start); char *Sp = strchr(Start, ' '); Err->ErrMsg = Sp ? Sp + 1 : Start; } // Log Log(Start, atoi(Start) >= 400 ? LSocketI::SocketMsgError : LSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, LSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public LStringPipe { char File[256]; LFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LRand()); LMakePath(File, sizeof(File), Path, n); } while (LFileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (auto a: To) { a->Status = true; auto Addrs = a->sAddr.SplitDelimit(","); for (unsigned n=0; n", Addrs[n].Get()); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { return F.Write(Buffer, Size, Flags); } }; class MailPostFolderPrivate { public: char *Path; MailPostFolderPrivate() { Path = 0; } ~MailPostFolderPrivate() { DeleteArray(Path); } }; MailSendFolder::MailSendFolder(char *Path) { d = new MailPostFolderPrivate; d->Path = NewStr(Path); } MailSendFolder::~MailSendFolder() { DeleteObj(d); } bool MailSendFolder::Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return LDirExists(d->Path); } bool MailSendFolder::Close() { return true; } LStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(LStringPipe *Sink) { DeleteObj(Sink); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// class MailItem { public: char *File; bool Delete; MailItem(char *f) { File = NewStr(f); Delete = false; } ~MailItem() { DeleteArray(File); } }; class MailReceiveFolderPrivate { public: char *Path; List Mail; MailReceiveFolderPrivate() { Path = 0; } ~MailReceiveFolderPrivate() { DeleteArray(Path); Mail.DeleteObjects(); } void Empty() { for (auto m: Mail) { if (m->Delete) { FileDev->Delete(m->File, false); } } Mail.DeleteObjects(); } }; MailReceiveFolder::MailReceiveFolder(char *Path) { d = new MailReceiveFolderPrivate; d->Path = NewStr(Path); } MailReceiveFolder::~MailReceiveFolder() { DeleteObj(d); } bool MailReceiveFolder::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!LDirExists(d->Path)) return false; LDirectory Dir; // Loop through files, looking for email for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next()) { if (!Dir.IsDir()) { if (MatchStr("*.eml", Dir.GetName()) || MatchStr("*.mail", Dir.GetName())) { char p[300]; Dir.Path(p, sizeof(p)); d->Mail.Insert(new MailItem(p)); } } } return true; } bool MailReceiveFolder::Close() { d->Empty(); return true; } ssize_t MailReceiveFolder::GetMessages() { return d->Mail.Length(); } bool MailReceiveFolder::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { LFile i; if (i.Open(m->File, O_READ)) { LCopyStreamer c; if (c.Copy(&i, t->Stream)) { Status = t->Status = true; if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(t, Callbacks->CallbackData); } } } } } } return Status; } bool MailReceiveFolder::Delete(int Message) { MailItem *m = d->Mail[Message]; if (m) { m->Delete = true; return false; } return false; } int MailReceiveFolder::Sizeof(int Message) { MailItem *m = d->Mail[Message]; if (m) { return (int)LFileSize(m->File); } return 0; } bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen) { if (Id) { MailItem *m = d->Mail[Message]; if (m) { char *s = strrchr(m->File, DIR_CHAR); if (s++) { char *e = strchr(s, '.'); if (!e) e = s + strlen(s); ssize_t Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(LString::Array &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.New() = Uid; } else { Status = false; break; } } return Status; } LString MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (!m) - return NULL; + return LString(); LFile i; if (!i.Open(m->File, O_READ)) - return NULL; + return LString(); LStringPipe o; LCopyStreamer c; LHtmlLinePrefix e("", false); if (!c.Copy(&i, &o, &e)) - return NULL; + return LString(); return o.NewLStr(); } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } ssize_t MailPop3::GetMessages() { if (Messages < 0) { if (Socket && Socket->IsOpen()) { // see how many messages there are VERIFY_ONERR(Write("STAT\r\n", true)); VERIFY_ONERR(ReadReply()); Messages = GetInt(); } else LAssert(!"No socket to get message count."); } CleanUp: return Messages; } int MailPop3::GetInt() { char Buf[32]; char *Start = strchr(Buffer, ' '); if (Start) { Start++; char *End = strchr(Start, ' '); if (End) { int Len = (int) (End - Start); memcpy(Buf, Start, Len); Buf[Len] = 0; return atoi(Buf); } } return 0; } bool MailPop3::ReadReply() { bool Status = false; if (Socket) { ssize_t Pos = 0; ZeroObj(Buffer); do { ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Result <= 0) // an error? { // Leave the loop... break; } Pos += Result; } while ( !strstr(Buffer, "\r\n") && sizeof(Buffer)-Pos > 0); Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n"); char *Cr = strchr(Buffer, '\r'); if (Cr) *Cr = 0; if (ValidStr(Buffer)) Log(Buffer, (Status) ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; ssize_t r; while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0) { b += r; if (Strnstr(Buffer, "\r\n.\r\n", b-Buffer)) break; } if (r <= 0) return false; auto Lines = LString(Buffer).SplitDelimit("\r\n"); for (unsigned i=1; iGetValue("IsSSL", IsSsl) && IsSsl.CastInt32()) Port = POP3_SSL_PORT; else Port = POP3_PORT; } strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (S && User && Password && (Server = TrimStr(Str))) { S->SetTimeout(30 * 1000); ReStartConnection: if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } if (Socket && Socket->Open(Server, Port) && ReadReply()) { LVariant NoAPOP = false; if (SettingStore) SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP); if (!NoAPOP.CastInt32()) { char *s = strchr(Buffer + 3, '<'); if (s) { char *e = strchr(s + 1, '>'); if (e) { Apop = NewStr(s, e - s + 1); } } } // login bool Authed = false; char *user = (char*) LNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LNewConvertCp("iso-8859-1", Password, "utf-8"); if (user && (pass || SecureAuth)) { bool SecurityError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); LVariant v; if (Socket->SetValue(LSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; } else { SecurityError = true; } } if (!SecurityError && Apop) // GotKey, not implemented { // using encrypted password unsigned char Digest[16]; char HexDigest[33]; // append password char Key[256]; sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass); ZeroObj(Digest); MDStringToDigest(Digest, Key); for (int i = 0; i < 16; i++) sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]); HexDigest[32] = 0; sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest); VERIFY_ONERR(Write(0, true)); Authed = ReadReply(); if (!Authed) { DeleteArray(Apop); LVariant NoAPOP = true; if (SettingStore) SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { LHashTbl, bool> AuthTypes, Capabilities; if (ListCmd("AUTH", AuthTypes) && ListCmd("CAPA", Capabilities)) { if (AuthTypes.Find("GSSAPI")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n"); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); // http://www.faqs.org/rfcs/rfc2743.html } } } else if (!SecurityError && !Authed) { // have to use non-key method sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass); VERIFY_ONERR(Write(0, false)); Log("PASS *******", LSocketI::SocketMsgSend); Authed = ReadReply(); } DeleteArray(user); DeleteArray(pass); } if (Authed) { Status = true; } else { if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } LgiTrace("%s:%i - Failed auth.\n", _FL); } } else Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port); } else Error(_FL, "No user/pass.\n"); } CleanUp: DeleteArray(Apop); DeleteArray(Server); return Status; } bool MailPop3::MailIsEnd(LString &s) { ssize_t Len = s.Length(); for (auto c = s.Get(); c && Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; LStreamI *Msg = Trans[n]->Stream; if (Msg) { int Size = 0; // Transfer is not null when the caller wants info on the bytes comming in if (Transfer || Callbacks) { // get message size sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } MailSrcStatus Action = DownloadAll; int TopLines = 100; if (Callbacks && Callbacks->OnSrc) { Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData); } if (Action == DownloadAbort) { break; } if (Action == DownloadAll || Action == DownloadTop) { if (Action == DownloadAll) { sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1); } else { sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines); } VERIFY_RET_VAL(Write(0, true)); LHtmlLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LCurrentTime(); } // Read status line ZeroObj(Buffer); ssize_t Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0); if (r > 0) { DeNullText(Buffer + Used, r); if (Transfer) { Transfer->Value += r; } char *Eol = strchr(Buffer, '\n'); if (Eol) { Eol++; Ok = Buffer[0] == '+'; if (Ok) { // Log(Buffer, LSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination size_t Len = strlen(Eol); ssize_t EndPos = End.IsEnd(Eol, Len); if (EndPos >= 0) { Msg->Write(Eol, EndPos - 3); Status = Trans[n]->Status = true; Finished = true; } else { Msg->Write(Eol, Len); DataPos += Len; } } else { Log(Buffer, LSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } ssize_t EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { ssize_t Actual = EndPos - DataPos - 3; if (Actual > 0) { #ifdef _DEBUG ssize_t w = #endif Msg->Write(Buffer, Actual); LAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { #ifdef _DEBUG ssize_t w = #endif Msg->Write(Buffer, r); LAssert(w == r); DataPos += r; } } else { break; } } if (!Status) { LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL); } } else { LgiTrace("%s:%i - Didn't get Ok.\n", _FL); break; } } if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(Trans[n], Callbacks->CallbackData); } if (Transfer) { Transfer->Empty(); } } else { Trans[n]->Oversize = Status = true; } if (Items) { Items->Value++; } } else { LgiTrace("%s:%i - No stream.\n", _FL); } } } else { LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get()); } return Status; } bool MailPop3::GetSizes(LArray &Sizes) { if (!Socket) return false; strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); auto s = ReadMultiLineReply(); if (!s) return false; for (auto ln: s.SplitDelimit("\r\n")) { auto p = ln.SplitDelimit(); if (p.Length() > 1) Sizes.Add((int)p.Last().Int()); } return Sizes.Length() > 0; } int MailPop3::Sizeof(int Message) { int Size = 0; if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); - VERIFY_RET_VAL(Write(0, true)); + VERIFY_RET_VAL(Write(NULL, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } return Size; } bool MailPop3::Delete(int Message) { if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); return true; } return false; } bool MailPop3::GetUid(int Index, char *Id, int IdLen) { if (Socket && Id) { sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *Space = strchr(Buffer, ' '); if (Space) { Space = strchr(Space+1, ' '); if (Space) { for (char *s = Space+1; *s; s++) { if (*s == '\r' || *s == '\n') { *s = 0; break; } } strcpy_s(Id, IdLen, Space+1); return true; } } } return false; } bool MailPop3::GetUidList(LString::Array &Id) { if (!Socket) return false; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); auto Str = ReadMultiLineReply(); if (!Str) return false; auto lines = Str.SplitDelimit("\r\n"); for (auto s: lines) { if (s(0) != '.') { char *Space = strchr(s, ' '); if (Space++) Id.New() = Space; } } return true; } LString MailPop3::GetHeaders(int Message) { if (!Socket) - return NULL; + return LString(); sprintf_s(Buffer, sizeof(Buffer), "TOP %i 0\r\n", Message + 1); if (!Write(NULL, true)) - return NULL; + return LString(); return ReadMultiLineReply(); } LString MailPop3::ReadMultiLineReply() { if (!Socket) { LAssert(!"No socket."); return false; } LString a; do { auto s = Socket->Read(); if (!s) break; a += s; if (!a || a[0] != '+') - return NULL; + return LString(); } while (!MailIsEnd(a)); // Strip off the first line... auto FirstNewLen = a.Find("\n"); - return FirstNewLen >= 0 ? a(FirstNewLen, -1) : NULL; + return FirstNewLen >= 0 ? a(FirstNewLen, -1) : LString(); } bool MailPop3::Close() { if (Socket) { // logout VERIFY_RET_VAL(Write("QUIT\r\n", true)); // 2 sec timeout, we don't really care about the server's response Socket->SetTimeout(2000); ReadReply(); if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } Messages = 0; return true; } return false; } diff --git a/src/common/Net/WebSocket.cpp b/src/common/Net/WebSocket.cpp --- a/src/common/Net/WebSocket.cpp +++ b/src/common/Net/WebSocket.cpp @@ -1,469 +1,470 @@ // https://tools.ietf.org/html/rfc6455 #include "lgi/common/Lgi.h" #include "lgi/common/WebSocket.h" -#include "lgi/common/NetTools.h" #include "../Hash/sha1/sha1.h" #include "lgi/common/Base64.h" #ifdef LINUX #include #include #include + #ifndef htonll #define htonll LgiSwap64 + #endif #endif #define LOG_ALL 0 //////////////////////////////////////////////////////////////////////////// LSelect::LSelect(LSocket *sock) { if (sock) *this += sock; } LSelect &LSelect::operator +=(LSocket *sock) { if (sock) s.Add(sock); return *this; } int LSelect::Select(LArray &Results, bool Rd, bool Wr, int TimeoutMs) { if (s.Length() == 0) return 0; #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? ::LArray fds; fds.Length(s.Length()); for (unsigned i=0; iHandle(); fds[i].events = (Wr ? POLLOUT : 0) | (Rd ? POLLIN : 0) | POLLRDHUP | POLLERR; fds[i].revents = 0; } int r = poll(fds.AddressOf(), fds.Length(), TimeoutMs); int Signalled = 0; if (r > 0) { for (unsigned i=0; iHandle()) { // printf("Poll[%i] = %x (flags=%x)\n", i, f.revents, Flags); Results.Add(s[i]); } else LAssert(0); } } } return Signalled; #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); OsSocket Max = 0; for (auto Sock : s) { auto h = Sock->Handle(); if (Max < h) Max = h; FD_SET(h, &r); } int v = select( (int)Max+1, Rd ? &r : NULL, Wr ? &r : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (v > 0) { for (auto Sock : s) { if (FD_ISSET(Sock->Handle(), &r)) Results.Add(Sock); } } return v; #endif } LArray LSelect::Readable(int TimeoutMs) { LArray r; Select(r, true, false, TimeoutMs); return r; } LArray LSelect::Writeable(int TimeoutMs) { LArray r; Select(r, false, true, TimeoutMs); return r; } //////////////////////////////////////////////////////////////////////////// enum WebSocketState { WsReceiveHdr, WsMessages, WsClosed, }; enum WsOpCode { WsContinue = 0, WsText = 1, WsBinary = 2, WsClose = 8, WsPing = 9, WsPong = 10, }; struct LWebSocketPriv { LWebSocket *Ws; bool Server; WebSocketState State; LString InHdr, OutHdr; LArray Data; size_t Start, Used; char Buf[512]; LArray Msg; LWebSocket::OnMsg onMsg; LWebSocketPriv(LWebSocket *ws, bool server) : Ws(ws), Server(server) { State = WsReceiveHdr; Used = 0; Start = 0; } template ssize_t Write(T *p, size_t len) { size_t i = 0; while (i < len) { auto Wr = Ws->Write(p + i, len - i); if (Wr <= 0) return i; i += Wr; } return i; } bool AddData(char *p, size_t Len) { #if LOG_ALL LgiTrace("Websocket Read: %i\n", (int)Len); for (unsigned i=0; i=' '?p[i]:' '); s[ch++] = '\n'; s[ch] = 0; LgiTrace("%.*s", ch, s); } #endif if (Used + Len > Data.Length()) Data.Length(Used + Len + 1024); memcpy(Data.AddressOf(Used), p, Len); Used += Len; if (State != WsMessages) return false; return CheckMsg(); } void OnMsg(char *p, uint64 Len) { if (onMsg) onMsg(p, Len); } bool CheckMsg() { // Check for valid message.. if (Used < 2) return false; // too short uint8_t *p = (uint8_t*)Data.AddressOf(Start); uint8_t *End = p + Used; bool Fin = (p[0] & 0x80) != 0; WsOpCode OpCode = (WsOpCode) (p[0] & 0xf); bool Masked = (p[1] & 0x80) != 0; uint64 Len = p[1] & 0x7f; if (Len == 126) { if (Used < 4) return false; // Too short Len = (p[2] << 8) | p[3]; p += 4; } else if (Len == 127) { if (Used < 10) return false; // Too short p += 2; Len = ((uint64)p[0] << 54) | ((uint64)p[1] << 48) | ((uint64)p[2] << 40) | ((uint64)p[3] << 32) | ((uint64)p[4] << 24) | ((uint64)p[5] << 16) | ((uint64)p[6] << 8) | p[7]; p += 8; } else p += 2; uint8_t Mask[4]; if (Masked) { if (p > End - 4) return false; // Too short Mask[0] = *p++; Mask[1] = *p++; Mask[2] = *p++; Mask[3] = *p++; if (End - p < (ssize_t)Len) return false; // Too short // Unmask for (uint64 i=0; i 0) memcpy(Data.AddressOf(), p, Remaining); Used = Remaining; } else LAssert(!"Impl me"); } else { auto Pos = (char*)p - Data.AddressOf(); Msg.New().Set(Pos, Len); Start = Pos + Len; } if (OpCode == WsClose) { Ws->Close(); State = WsClosed; } return Status; } bool Error(const char *Msg) { return false; } bool SendResponse() { // Create the response hdr and send it... static const char *Key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; LAutoString Upgrade(InetGetHeaderField(InHdr, "Upgrade", InHdr.Length())); if (!Upgrade || stricmp(Upgrade, "websocket")) return false; LAutoString SecWebSocketKey(InetGetHeaderField(InHdr, "Sec-WebSocket-Key", InHdr.Length())); if (!SecWebSocketKey) return Error("No Sec-WebSocket-Key header"); LString s = SecWebSocketKey.Get(); s += Key; SHA1Context Ctx; SHA1Reset(&Ctx); SHA1Input(&Ctx, (const uchar*) s.Get(), (unsigned) s.Length()); if (!SHA1Result(&Ctx)) return Error("SHA1Result failed"); uint32_t Digest[5]; for (int i=0; ionMsg = onMsg; } bool LWebSocket::SendMessage(char *Data, uint64 Len) { if (d->State != WsMessages) return false; uint8_t Masked = d->Server ? 0 : 0x80; int8 Hdr[2 + 8 + 4]; LPointer p = {Hdr}; *p.u8++ = 0x80 | // Fin WsText; if (Len < 126) // 1 byte { // Direct len *p.u8++ = Masked | (uint8_t)Len; } else if (Len <= 0xffff) // 2 byte { // 126 + 2 bytes *p.u8++ = Masked | 126; *p.u16++ = htons((u_short)Len); } else { // 127 + 8 bytes *p.u8++ = Masked | 127; *p.u64++ = htonll(Len); } LAutoPtr MaskData; if (Masked) { uint8_t *Mask = p.u8; *p.u32++ = LRand(); if (!MaskData.Reset(new uint8_t[Len])) return d->Error("Alloc failed."); uint8_t *Out = MaskData.Get(); for (uint64 i=0; iWrite(Hdr, Sz); if (Wr != Sz) return d->Error("SendMessage.Hdr failed to write to socket"); Wr = d->Write(MaskData ? MaskData.Get() : (uint8_t*)Data, Len); if (Wr != Len) return d->Error("SendMessage.Body failed to write to socket"); return true; } bool LWebSocket::InitFromHeaders(LString Data, OsSocket Sock) { bool HasMsg = false; if (d->State == WsReceiveHdr) { d->InHdr = Data; Handle(Sock); auto End = d->InHdr.Find("\r\n\r\n"); if (End >= 0) { if (d->InHdr.Length() > (size_t)End + 4) { d->AddData(d->InHdr.Get() + End + 4, d->InHdr.Length() - End - 4); d->InHdr.Length(End); } HasMsg = d->SendResponse(); } } return HasMsg; } bool LWebSocket::OnData() { bool GotData = false; auto Rd = Read(d->Buf, sizeof(d->Buf)); if (Rd > 0) { if (d->State == WsReceiveHdr) { d->InHdr += LString(d->Buf, Rd); auto End = d->InHdr.Find("\r\n\r\n"); if (End >= 0) { if (d->InHdr.Length() > (size_t)End + 4) { d->AddData(d->InHdr.Get() + End + 4, d->InHdr.Length() - End - 4); d->InHdr.Length(End); } GotData = d->SendResponse(); } } else { GotData = d->AddData(d->Buf, Rd); } } return GotData; } diff --git a/utils/SlogViewer/Makefile.linux b/utils/SlogViewer/Makefile.linux --- a/utils/SlogViewer/Makefile.linux +++ b/utils/SlogViewer/Makefile.linux @@ -1,373 +1,372 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./slogviewer ifndef Build Build = Debug endif MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -w -fno-inline -fpermissive +Flags = -fPIC -fno-inline -fpermissive -Wno-format-truncation ifeq ($(Build),Debug) Flags += -MMD -MP -g -std=c++14 Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -static-libgcc \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I./resources \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include else Flags += -MMD -MP -s -Os -std=c++14 Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -static-libgcc \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I./resources \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include endif # Dependencies Source = src/SlogViewerMain.cpp \ ../../src/common/Lgi/LgiMain.cpp \ ../../src/common/Lgi/DocApp.cpp SourceC := $(filter %.c,$(Source)) ObjectsC := $(SourceC:.c=.o) SourceCpp := $(filter %.cpp,$(Source)) ObjectsCpp := $(SourceCpp:.cpp=.o) Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) Objects := $(addprefix $(BuildDir)/,$(Objects)) Deps := $(patsubst %.o,%.d,$(Objects)) $(BuildDir)/%.o: %.c mkdir -p $(@D) echo $(notdir $<) [$(Build)] $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@ $(BuildDir)/%.o: %.cpp mkdir -p $(@D) echo $(notdir $<) [$(Build)] $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@ # Target # Executable target $(Target) : ../../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../../$(BuildDir)/liblgi-gtk3$(Tag).so : ../../include/lgi/common/App.h \ ../../include/lgi/common/Array.h \ ../../include/lgi/common/AutoPtr.h \ ../../include/lgi/common/Base64.h \ ../../include/lgi/common/Bitmap.h \ ../../include/lgi/common/Box.h \ ../../include/lgi/common/Button.h \ ../../include/lgi/common/CairoSurface.h \ ../../include/lgi/common/Cancel.h \ ../../include/lgi/common/Capabilities.h \ ../../include/lgi/common/Charset.h \ ../../include/lgi/common/CheckBox.h \ ../../include/lgi/common/ClipBoard.h \ ../../include/lgi/common/Colour.h \ ../../include/lgi/common/ColourSpace.h \ ../../include/lgi/common/Com.h \ ../../include/lgi/common/Combo.h \ ../../include/lgi/common/Containers.h \ ../../include/lgi/common/Core.h \ ../../include/lgi/common/Css.h \ ../../include/lgi/common/CssTools.h \ ../../include/lgi/common/CurrentTime.h \ ../../include/lgi/common/DataDlg.h \ ../../include/lgi/common/DateTime.h \ ../../include/lgi/common/Dialog.h \ ../../include/lgi/common/DisplayString.h \ ../../include/lgi/common/DocView.h \ ../../include/lgi/common/Dom.h \ ../../include/lgi/common/DomFields.h \ ../../include/lgi/common/DragAndDrop.h \ ../../include/lgi/common/DropFiles.h \ ../../include/lgi/common/Edit.h \ + ../../include/lgi/common/Emoji.h \ ../../include/lgi/common/Error.h \ ../../include/lgi/common/EventTargetThread.h \ ../../include/lgi/common/File.h \ ../../include/lgi/common/FileSelect.h \ ../../include/lgi/common/Filter.h \ ../../include/lgi/common/FindReplaceDlg.h \ ../../include/lgi/common/Font.h \ ../../include/lgi/common/FontCache.h \ ../../include/lgi/common/FontSelect.h \ ../../include/lgi/common/Gdc2.h \ ../../include/lgi/common/GdcTools.h \ ../../include/lgi/common/GdiLeak.h \ ../../include/lgi/common/HashTable.h \ ../../include/lgi/common/ImageList.h \ ../../include/lgi/common/Input.h \ ../../include/lgi/common/ItemContainer.h \ ../../include/lgi/common/Json.h \ ../../include/lgi/common/Layout.h \ ../../include/lgi/common/Lgi.h \ ../../include/lgi/common/LgiClasses.h \ ../../include/lgi/common/LgiCommon.h \ ../../include/lgi/common/LgiDefs.h \ ../../include/lgi/common/LgiInc.h \ ../../include/lgi/common/LgiInterfaces.h \ ../../include/lgi/common/LgiMsgs.h \ - ../../include/lgi/common/LgiNetInc.h \ ../../include/lgi/common/LgiRes.h \ ../../include/lgi/common/LgiString.h \ ../../include/lgi/common/LgiUiBase.h \ ../../include/lgi/common/Library.h \ ../../include/lgi/common/LibraryUtils.h \ ../../include/lgi/common/List.h \ ../../include/lgi/common/ListItemCheckBox.h \ ../../include/lgi/common/ListItemRadioBtn.h \ ../../include/lgi/common/LMallocArray.h \ ../../include/lgi/common/Mail.h \ ../../include/lgi/common/Matrix.h \ ../../include/lgi/common/Mem.h \ ../../include/lgi/common/Menu.h \ ../../include/lgi/common/Message.h \ ../../include/lgi/common/Mime.h \ ../../include/lgi/common/Mru.h \ ../../include/lgi/common/Mutex.h \ ../../include/lgi/common/Net.h \ - ../../include/lgi/common/NetTools.h \ ../../include/lgi/common/Notifications.h \ ../../include/lgi/common/OAuth2.h \ ../../include/lgi/common/OptionsFile.h \ ../../include/lgi/common/Palette.h \ ../../include/lgi/common/Panel.h \ ../../include/lgi/common/Password.h \ ../../include/lgi/common/Path.h \ ../../include/lgi/common/PixelRops.h \ ../../include/lgi/common/Point.h \ ../../include/lgi/common/Popup.h \ ../../include/lgi/common/PopupList.h \ ../../include/lgi/common/PopupNotification.h \ ../../include/lgi/common/Printer.h \ ../../include/lgi/common/Profile.h \ ../../include/lgi/common/Progress.h \ ../../include/lgi/common/ProgressDlg.h \ ../../include/lgi/common/ProgressView.h \ ../../include/lgi/common/Properties.h \ ../../include/lgi/common/RadioGroup.h \ ../../include/lgi/common/Range.h \ ../../include/lgi/common/Rect.h \ ../../include/lgi/common/RectF.h \ ../../include/lgi/common/RefCount.h \ ../../include/lgi/common/RegKey.h \ ../../include/lgi/common/Res.h \ ../../include/lgi/common/Rops.h \ ../../include/lgi/common/ScrollBar.h \ ../../include/lgi/common/SkinEngine.h \ ../../include/lgi/common/Slider.h \ ../../include/lgi/common/Splitter.h \ ../../include/lgi/common/StatusBar.h \ ../../include/lgi/common/Store3Defs.h \ ../../include/lgi/common/Stream.h \ ../../include/lgi/common/StringClass.h \ ../../include/lgi/common/StringLayout.h \ ../../include/lgi/common/StructuredIo.h \ ../../include/lgi/common/StructuredLog.h \ ../../include/lgi/common/SubProcess.h \ ../../include/lgi/common/TableLayout.h \ ../../include/lgi/common/TabView.h \ ../../include/lgi/common/TextFile.h \ ../../include/lgi/common/TextLabel.h \ ../../include/lgi/common/TextLog.h \ ../../include/lgi/common/TextView3.h \ ../../include/lgi/common/Thread.h \ ../../include/lgi/common/ThreadEvent.h \ ../../include/lgi/common/Token.h \ ../../include/lgi/common/ToolBar.h \ ../../include/lgi/common/ToolTip.h \ ../../include/lgi/common/TrayIcon.h \ ../../include/lgi/common/Tree.h \ ../../include/lgi/common/Undo.h \ ../../include/lgi/common/Unicode.h \ ../../include/lgi/common/UnicodeString.h \ ../../include/lgi/common/UnrolledList.h \ + ../../include/lgi/common/Uri.h \ ../../include/lgi/common/Variant.h \ ../../include/lgi/common/View.h \ ../../include/lgi/common/Widgets.h \ ../../include/lgi/common/Window.h \ ../../include/lgi/common/XmlTree.h \ ../../include/lgi/linux/Gtk/LgiOsClasses.h \ ../../include/lgi/linux/Gtk/LgiOsDefs.h \ ../../include/lgi/linux/Gtk/LgiWidget.h \ ../../include/lgi/linux/Gtk/LgiWinManGlue.h \ ../../include/lgi/linux/SymLookup.h \ ../../include/lgi/mac/cocoa/LCocoaView.h \ ../../include/lgi/mac/cocoa/LgiMac.h \ ../../include/lgi/mac/cocoa/LgiOs.h \ ../../include/lgi/mac/cocoa/LgiOsClasses.h \ ../../include/lgi/mac/cocoa/LgiOsDefs.h \ ../../include/lgi/mac/cocoa/ObjCWrapper.h \ ../../include/lgi/mac/cocoa/SymLookup.h \ ../../private/common/FontPriv.h \ ../../private/common/ViewPriv.h \ ../../private/linux/AppPriv.h \ ../../src/common/Gdc2/15Bit.cpp \ ../../src/common/Gdc2/16Bit.cpp \ ../../src/common/Gdc2/24Bit.cpp \ ../../src/common/Gdc2/32Bit.cpp \ ../../src/common/Gdc2/8Bit.cpp \ ../../src/common/Gdc2/Alpha.cpp \ ../../src/common/Gdc2/Colour.cpp \ ../../src/common/Gdc2/Filters/Filter.cpp \ ../../src/common/Gdc2/Font/Charset.cpp \ ../../src/common/Gdc2/Font/DisplayString.cpp \ ../../src/common/Gdc2/Font/Font.cpp \ ../../src/common/Gdc2/Font/FontSystem.cpp \ ../../src/common/Gdc2/Font/FontType.cpp \ ../../src/common/Gdc2/Font/StringLayout.cpp \ ../../src/common/Gdc2/Font/TypeFace.cpp \ ../../src/common/Gdc2/GdcCommon.cpp \ ../../src/common/Gdc2/Path/Path.cpp \ ../../src/common/Gdc2/Rect.cpp \ ../../src/common/Gdc2/RopsCases.cpp \ ../../src/common/Gdc2/Surface.cpp \ ../../src/common/Gdc2/Tools/ColourReduce.cpp \ ../../src/common/Gdc2/Tools/GdcTools.cpp \ ../../src/common/General/Containers.cpp \ ../../src/common/General/DateTime.cpp \ ../../src/common/General/ExeCheck.cpp \ ../../src/common/General/FileCommon.cpp \ ../../src/common/General/Password.cpp \ ../../src/common/General/Properties.cpp \ ../../src/common/Hash/md5/md5.c \ ../../src/common/Hash/md5/md5.h \ ../../src/common/Hash/sha1/sha1.c \ ../../src/common/Hash/sha1/sha1.h \ ../../src/common/Lgi/Alert.cpp \ ../../src/common/Lgi/AppCommon.cpp \ ../../src/common/Lgi/Css.cpp \ ../../src/common/Lgi/CssTools.cpp \ ../../src/common/Lgi/DataDlg.cpp \ ../../src/common/Lgi/DragAndDropCommon.cpp \ ../../src/common/Lgi/FileSelect.cpp \ ../../src/common/Lgi/FindReplace.cpp \ ../../src/common/Lgi/FontSelect.cpp \ ../../src/common/Lgi/GuiUtils.cpp \ ../../src/common/Lgi/Input.cpp \ ../../src/common/Lgi/LgiCommon.cpp \ ../../src/common/Lgi/Library.cpp \ ../../src/common/Lgi/LMsg.cpp \ ../../src/common/Lgi/MemStream.cpp \ ../../src/common/Lgi/MenuCommon.cpp \ ../../src/common/Lgi/Mru.cpp \ ../../src/common/Lgi/Mutex.cpp \ ../../src/common/Lgi/Object.cpp \ ../../src/common/Lgi/OptionsFile.cpp \ ../../src/common/Lgi/Rand.cpp \ ../../src/common/Lgi/Stream.cpp \ ../../src/common/Lgi/SubProcess.cpp \ ../../src/common/Lgi/ThreadCommon.cpp \ ../../src/common/Lgi/ThreadEvent.cpp \ ../../src/common/Lgi/ToolTip.cpp \ ../../src/common/Lgi/TrayIcon.cpp \ ../../src/common/Lgi/Variant.cpp \ ../../src/common/Lgi/ViewCommon.cpp \ ../../src/common/Lgi/WindowCommon.cpp \ ../../src/common/Net/Base64.cpp \ ../../src/common/Net/MDStringToDigest.cpp \ ../../src/common/Net/Net.cpp \ - ../../src/common/Net/NetTools.cpp \ ../../src/common/Net/Uri.cpp \ ../../src/common/Resource/LgiRes.cpp \ ../../src/common/Resource/Res.cpp \ ../../src/common/Skins/Gel/Gel.cpp \ ../../src/common/Text/DocView.cpp \ ../../src/common/Text/String.cpp \ ../../src/common/Text/TextView3.cpp \ ../../src/common/Text/Token.cpp \ ../../src/common/Text/Unicode.cpp \ ../../src/common/Text/Utf8.cpp \ ../../src/common/Text/XmlTree.cpp \ ../../src/common/Widgets/Bitmap.cpp \ ../../src/common/Widgets/Box.cpp \ ../../src/common/Widgets/Button.cpp \ ../../src/common/Widgets/CheckBox.cpp \ ../../src/common/Widgets/Combo.cpp \ ../../src/common/Widgets/Edit.cpp \ ../../src/common/Widgets/ItemContainer.cpp \ ../../src/common/Widgets/List.cpp \ ../../src/common/Widgets/Panel.cpp \ ../../src/common/Widgets/Popup.cpp \ ../../src/common/Widgets/Progress.cpp \ ../../src/common/Widgets/ProgressDlg.cpp \ ../../src/common/Widgets/RadioGroup.cpp \ ../../src/common/Widgets/ScrollBar.cpp \ ../../src/common/Widgets/Slider.cpp \ ../../src/common/Widgets/Splitter.cpp \ ../../src/common/Widgets/StatusBar.cpp \ ../../src/common/Widgets/TableLayout.cpp \ ../../src/common/Widgets/TabView.cpp \ ../../src/common/Widgets/TextLabel.cpp \ ../../src/common/Widgets/ToolBar.cpp \ ../../src/common/Widgets/Tree.cpp \ ../../src/linux/General/File.cpp \ ../../src/linux/General/Mem.cpp \ ../../src/linux/General/ShowFileProp_Linux.cpp \ ../../src/linux/Gtk/Gdc2.cpp \ ../../src/linux/Gtk/LgiWidget.cpp \ ../../src/linux/Gtk/MemDC.cpp \ ../../src/linux/Gtk/PrintDC.cpp \ ../../src/linux/Gtk/ScreenDC.cpp \ ../../src/linux/Lgi/App.cpp \ ../../src/linux/Lgi/ClipBoard.cpp \ ../../src/linux/Lgi/DragAndDrop.cpp \ ../../src/linux/Lgi/General.cpp \ ../../src/linux/Lgi/Layout.cpp \ ../../src/linux/Lgi/Menu.cpp \ ../../src/linux/Lgi/Printer.cpp \ ../../src/linux/Lgi/Thread.cpp \ ../../src/linux/Lgi/View.cpp \ ../../src/linux/Lgi/Widgets.cpp \ ../../src/linux/Lgi/Window.cpp export Build=$(Build); \ - $(MAKE) -C ../../ -f Makefile.linux + $(MAKE) -C ../../ -f ./linux/Makefile.linux -include $(Objects:.o=.d) # Clean just this target clean : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) # Clean all targets cleanall : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) +make -C "../../" -f "Makefile.linux" clean VPATH=$(BuildDir) \ ./src \ ../../src/common/Lgi diff --git a/utils/SlogViewer/src/SlogViewerMain.cpp b/utils/SlogViewer/src/SlogViewerMain.cpp --- a/utils/SlogViewer/src/SlogViewerMain.cpp +++ b/utils/SlogViewer/src/SlogViewerMain.cpp @@ -1,430 +1,430 @@ #define _CRT_SECURE_NO_WARNINGS #include "lgi/common/Lgi.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/DocApp.h" #include "lgi/common/Box.h" #include "lgi/common/List.h" #include "lgi/common/TextView3.h" #include "lgi/common/TabView.h" #include "lgi/common/StructuredLog.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" ////////////////////////////////////////////////////////////////// const char *AppName = "SlogViewer"; enum DisplayMode { DisplayHex, DisplayEscaped }; enum Ctrls { IDC_BOX = 100, IDC_LOG, IDC_HEX, IDC_ESCAPED, IDC_TABS, IDC_STATUS, }; struct Context { LList *log = NULL; LTabView *tabs = NULL; LTextView3 *hex = NULL; LTextView3 *escaped = NULL; }; class Entry : public LListItem { Context *c; struct Value { LVariantType type; LArray data; LString name; }; LArray values; LString cache; public: Entry(Context *ctx) : c(ctx) { } void add(LVariantType type, size_t sz, void *ptr, const char *name) { auto &v = values.New(); v.type = type; v.data.Add((uint8_t*)ptr, sz); v.name = name; } const char *GetText(int i) { if (!cache) { LStringPipe p; for (auto &v: values) { switch (v.type) { case GV_INT32: case GV_INT64: { if (v.data.Length() == 4) p.Print("%i", *(int*)v.data.AddressOf()); else if (v.data.Length() == 8) p.Print(LPrintfInt64, *(int64_t*)v.data.AddressOf()); else LAssert(!"Unknown int size."); break; } case GV_STRING: { if (v.data.Length() >= 32) p.Print(" (...)"); else p.Print("%.*s", (int)v.data.Length(), v.data.AddressOf()); break; } case GV_CUSTOM: { p.Print("Object: %s", v.name.Get()); break; } case GV_VOID_PTR: { p.Print("/Object"); break; } default: { LAssert(!"Unknown type."); break; } } if (p.GetSize() > 64) break; } cache = p.NewLStr(); } return cache; } void Select(bool b) override { LListItem::Select(b); auto mode = (DisplayMode)c->tabs->Value(); auto ctrl = mode ? c->escaped : c->hex; if (b) { LStringPipe p; LString Obj; int Idx = 0; auto PrintEscaped = [&](char *s, char *e) { for (auto c = s; c < e; c++) { switch (*c) { case '\\': p.Print("\\\\"); break; case '\t': p.Print("\\t"); break; case '\r': p.Print("\\r"); break; case '\n': p.Print("\\n\n"); break; case 0x07: p.Print("\\b"); break; case 0x1b: p.Print("\\e"); break; default: if (*c < ' ' || *c >= 128) { LString hex; hex.Printf("\\x%x", (uint8_t)*c); p.Write(hex); } else p.Write(c, 1); break; } } }; for (auto &v: values) { auto sType = LVariant::TypeToString(v.type); switch (v.type) { case GV_CUSTOM: { p.Print("object %s {\n\n", v.name.Get()); Obj = v.name; Idx = 0; continue; } case GV_VOID_PTR: { p.Print("}\n"); Obj.Empty(); continue; } case GV_STRING: { if (Obj == "LConsole") { auto s = (char*)v.data.AddressOf(); auto e = s + v.data.Length(); p.Print("[%i]=", Idx++); PrintEscaped(s, e); p.Print("\n"); continue; } break; } } p.Print("%s, %i bytes%s%s:\n", sType, (int)v.data.Length(), v.name ? ", " : "", v.name ? v.name.Get() : ""); switch (v.type) { case GV_INT32: case GV_INT64: { if (v.data.Length() == 4) { auto i = (int*) v.data.AddressOf(); p.Print("%i 0x%x\n", *i, *i); } else if (v.data.Length() == 8) { auto i = (int64_t*) v.data.AddressOf(); p.Print(LPrintfInt64 " 0x" LPrintfHex64 "\n", *i, *i); } else LAssert(!"Impl me."); break; } case GV_STRING: { if (mode == DisplayHex) { char line[300]; int ch = 0; const int rowSize = 16; const int colHex = 10; const int colAscii = colHex + (rowSize * 3) + 2; const int colEnd = colAscii + rowSize; for (size_t addr = 0; addr < v.data.Length() ; addr += rowSize) { ZeroObj(line); - sprintf(line, "%08.8x", (int)addr); + sprintf(line, "%8.8x", (int)addr); auto rowBytes = MIN(v.data.Length() - addr, rowSize); LAssert(rowBytes <= rowSize); auto rowPtr = v.data.AddressOf(addr); for (int i=0; i= ' ' && rowPtr[i] < 128 ? rowPtr[i] : '.'; } for (int i=0; i s && e[-1] != '\n') p.Write("\n"); } break; } default: { LAssert(!"Impl me."); break; } } p.Print("\n"); } ctrl->Name(p.NewLStr()); } } }; class ReaderThread : public LThread { Context *Ctx; LString FileName; LList *Lst = NULL; Progress *Prog = NULL; public: ReaderThread(Context *ctx, const char *filename, LList *lst, Progress *prog) : LThread("ReaderThread") { Ctx = ctx; FileName = filename; Lst = lst; Prog = prog; Run(); } ~ReaderThread() { Prog->Cancel(); WaitForExit(10000); } int Main() { LStructuredLog file(FileName, false); LAutoPtr cur; List items; while (file.Read([this, &cur, &items](auto type, auto size, auto ptr, auto name) { if (type == LStructuredIo::EndRow) { items.Insert(cur.Release()); if (items.Length() > 100) { Lst->Insert(items); items.Empty(); } return; } if (!cur && !cur.Reset(new Entry(Ctx))) return; cur->add(type, size, ptr, name); }, Prog)) ; Lst->Insert(items); return 0; } }; class App : public LDocApp, public Context { LBox *box = NULL; LAutoPtr Reader; LStatusBar *Status = NULL; LProgressStatus *Prog = NULL; public: App() : LDocApp(AppName) { Name(AppName); LRect r(0, 0, 1000, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); if (_Create()) { _LoadMenu(); AddView(Status = new LStatusBar(IDC_STATUS)); Status->AppendPane("Some text"); Status->AppendPane(Prog = new LProgressStatus()); Prog->GetCss(true)->Width("200px"); Prog->GetCss()->TextAlign(LCss::AlignRight); AddView(box = new LBox(IDC_BOX)); box->AddView(log = new LList(IDC_LOG, 0, 0, 200, 200)); log->GetCss(true)->Width("40%"); log->ShowColumnHeader(false); log->AddColumn("Items", 1000); box->AddView(tabs = new LTabView(IDC_TABS)); auto tab = tabs->Append("Hex"); tab->Append(hex = new LTextView3(IDC_HEX)); hex->Sunken(true); hex->SetPourLargest(true); tab = tabs->Append("Escaped"); tab->Append(escaped = new LTextView3(IDC_ESCAPED)); escaped->Sunken(true); escaped->SetPourLargest(true); AttachChildren(); Visible(true); } } void OnReceiveFiles(LArray &Files) { if (Files.Length()) OpenFile(Files[0]); } bool OpenFile(const char *FileName, bool ReadOnly = false) { return Reader.Reset(new ReaderThread(this, FileName, log, Prog)); } void SaveFile(const char *FileName, std::function Callback) { if (Callback) Callback(FileName, false); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_TABS) { auto item = log->GetSelected(); if (item) item->Select(true); } return 0; } }; ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { a.AppWnd = new App; a.Run(); } return 0; }