diff --git a/include/common/GStringClass.h b/include/common/GStringClass.h new file mode 100644 --- /dev/null +++ b/include/common/GStringClass.h @@ -0,0 +1,593 @@ +/* + * A mis-guided attempt to make a string class look and feel like a python string. + * + * Author: Matthew Allen + * Email: fret@memecode.com + * Created: 16 Sept 2014 + */ +#ifndef _GSTRING_CLASS_H_ +#define _GSTRING_CLASS_H_ + +#include +#ifdef _MSC_VER + // This fixes compile errors in VS2008/Gtk + #undef _SIGN_DEFINED + #undef abs +#endif +#include +#ifdef _MSC_VER + #include +#endif +#include "GUtf8.h" +#include "GString.h" + +LgiExtern int LgiPrintf(class GString &Str, const char *Format, va_list &Arg); + +/// A pythonic string class. +class GString +{ +protected: + /// This structure holds the string's data itself and is shared + /// between one or more GString instances. + struct RefStr + { + /// A reference count + int32 Refs; + /// The bytes in 'Str' not including the NULL terminator + uint32 Len; + /// The first byte of the string. Further bytes are allocated + /// off the end of the structure using malloc. This must always + /// be the last element in the struct. + char Str[1]; + } *Str; + + inline void _strip(GString &ret, const char *set, bool left, bool right) + { + if (!Str) return; + + char *s = Str->Str; + char *e = s + Str->Len; + + if (!set) set = " \t\r\n"; + + while (left && *s && strchr(set, *s)) + s++; + + while (right && e > s && strchr(set, e[-1])) + e--; + + ret.Set(s, e - s); + } + +public: + #ifdef LGI_UNIT_TESTS + static int32 RefStrCount; + #endif + + /// A copyable array of strings + class Array : public GArray + { + public: + Array() {} + Array(const Array &a) + { + *this = (Array&)a; + } + + Array &operator =(const Array &a) + { + SetFixedLength(false); + *((GArray*)this) = a; + SetFixedLength(true); + return *this; + } + }; + + /// Empty constructor + GString() + { + Str = NULL; + } + + /// String constructor + GString(const char *str, int len) + { + Str = NULL; + Set(str, len); + } + + /// const char* constructor + GString(const char *str) + { + Str = NULL; + Set(str); + } + + /// GString constructor + GString(const GString &s) + { + Str = s.Str; + if (Str) + Str->Refs++; + } + + ~GString() + { + Empty(); + } + + /// Removes a reference to the string and deletes if needed + void Empty() + { + if (!Str) return; + Str->Refs--; + LgiAssert(Str->Refs >= 0); + if (Str->Refs == 0) + { + free(Str); + #ifdef LGI_UNIT_TESTS + RefStrCount--; + #endif + } + Str = NULL; + } + + /// Returns the pointer to the string data + char *Get() const + { + return Str ? Str->Str : NULL; + } + + /// Sets the string to a new value + bool Set + ( + /// Can be a pointer to string data or NULL to create an empty buffer (requires valid length) + const char *str, + /// Byte length of input string or -1 to copy till the NULL terminator. + NativeInt len = -1 + ) + { + Empty(); + + if (len < 0) + { + if (str) + len = strlen(str); + else + return false; + } + + Str = (RefStr*)malloc(sizeof(RefStr) + len); + if (!Str) + return false; + + Str->Refs = 1; + Str->Len = len; + #ifdef LGI_UNIT_TESTS + RefStrCount++; + #endif + + if (str) + memcpy(Str->Str, str, len); + + Str->Str[len] = 0; + return true; + } + + /// Assignment operator to copy one string to another + GString &operator =(const GString &s) + { + if (this != &s) + { + Empty(); + Str = s.Str; + if (Str) + Str->Refs++; + } + return *this; + } + + /// Assignment operator for a C string + GString &operator =(const char *s) + { + Empty(); + Set(s); + return *this; + } + + /// Cast to C string operator + operator char *() + { + return Str ? Str->Str : NULL; + } + + /// Concatenation operator + GString operator +(const GString &s) + { + GString Ret; + int Len = Length() + s.Length(); + if (Ret.Set(NULL, Len)) + { + char *p = Ret.Get(); + if (p) + { + if (Str) + { + memcpy(p, Str->Str, Str->Len); + p += Str->Len; + } + if (s.Str) + { + memcpy(p, s.Str->Str, s.Str->Len); + p += s.Str->Len; + } + *p++ = 0; + } + } + + return Ret; + } + + /// Concatenation / assignment operator + GString &operator +=(const GString &s) + { + int Len = Length() + s.Length(); + int Alloc = sizeof(RefStr) + Len; + RefStr *rs = (RefStr*)malloc(Alloc); + if (rs) + { + rs->Refs = 1; + rs->Len = Len; + #ifdef LGI_UNIT_TESTS + RefStrCount++; + #endif + + char *p = rs->Str; + if (Str) + { + memcpy(p, Str->Str, Str->Len); + p += Str->Len; + } + if (s.Str) + { + memcpy(p, s.Str->Str, s.Str->Len); + p += s.Str->Len; + } + *p++ = 0; + LgiAssert(p - (char*)rs <= Alloc); + + Empty(); + Str = rs; + } + + return *this; + } + + /// Gets the length in bytes + uint32 Length() const + { + return Str ? Str->Len : 0; + } + + /// Splits the string into parts using a separator + Array Split(const char *Sep, int Count = -1, bool CaseSen = false) + { + Array a; + + if (Str && Sep) + { + const char *s = Get(), *Prev = s; + int SepLen = strlen(Sep); + + while ((s = CaseSen ? strstr(s, Sep) : stristr(s, Sep))) + { + a.New().Set(Prev, s - Prev); + s += SepLen; + Prev = s; + if (Count > 0 && a.Length() >= (uint32)Count) + break; + } + + if (*Prev) + a.New().Set(Prev); + + } + + a.SetFixedLength(); + return a; + } + + /// Splits the string into parts using a separator + Array RSplit(const char *Sep, int Count = -1, bool CaseSen = false) + { + Array a; + + if (Str && Sep) + { + const char *s = Get(); + int SepLen = strlen(Sep); + + GArray seps; + + while ((s = CaseSen ? strstr(s, Sep) : stristr(s, Sep))) + { + seps.Add(s); + s += SepLen; + } + + int i; + int Last = seps.Length() - 1; + GString p; + for (i=Last; i>=0; i--) + { + const char *part = seps[i] + SepLen; + + if (i == Last) + p.Set(part); + else + p.Set(part, seps[i+1]-part); + a.AddAt(0, p); + + if (Count > 0 && a.Length() >= (uint32)Count) + break; + } + + const char *End = seps[i > 0 ? i : 0]; + p.Set(Get(), End - Get()); + a.AddAt(0, p); + } + + a.SetFixedLength(); + return a; + } + + /// Splits the string into parts using delimiter chars + Array SplitDelimit(const char *Delimiters = NULL, int Count = -1, bool GroupDelimiters = true) const + { + Array a; + + if (Str) + { + const char *delim = Delimiters ? Delimiters : " \t\r\n"; + const char *s = Get(); + while (*s) + { + // Skip over non-delimiters + const char *e = s; + while (*e && !strchr(delim, *e)) + e++; + + if (e > s || !GroupDelimiters) + a.New().Set(s, e - s); + + s = e; + if (*s) s++; + if (GroupDelimiters) + { + // Skip any delimiters + while (*s && strchr(delim, *s)) + s++; + } + + // Create the string + if (Count > 0 && a.Length() >= (uint32)Count) + break; + } + + if + ( + *s || + ( + !GroupDelimiters && + s > Get() && + strchr(delim, s[-1]) + ) + ) + a.New().Set(s); + } + + a.SetFixedLength(); + return a; + } + + /// Joins an array of strings using a separator + GString Join(Array &a) + { + GString ret; + + char *Sep = Get(); + int SepLen = Sep ? strlen(Sep) : 0; + int Bytes = SepLen * (a.Length() - 1); + GArray ALen; + for (unsigned i=0; iStr) : NAN; + } + + /// Convert to integer + int Int() + { + return Str ? atoi(Str->Str) : -1; + } + + /// Find a sub-string + int Find(const char *needle, int start = 0, int end = -1) + { + if (!needle) return -1; + char *c = Get(); + if (!c) return -1; + + char *pos = c + start; + while (c < pos) + { + if (!*c) + return -1; + c++; + } + + char *found = (end > 0) ? strnstr(c, needle, end - start) : strstr(c, needle); + return (found) ? found - Get() : -1; + } + + /// Reverse find a string (starting from the end) + int RFind(const char *needle, int start = 0, int end = -1) + { + if (!needle) return -1; + char *c = Get(); + if (!c) return -1; + + char *pos = c + start; + while (c < pos) + { + if (!*c) + return -1; + c++; + } + + char *found, *prev = NULL; + int str_len = strlen(needle); + while + (( + found = + ( + (end > 0) ? + strnstr(c, needle, end - start) : + strstr(c, needle) + ) + )) + { + prev = found; + c = found + str_len; + } + return (prev) ? prev - Get() : -1; + } + + /// Returns a copy of the string with all the characters converted to lower case + GString Lower() + { + GString s; + s.Set(Get()); + #ifdef _MSC_VER + _strlwr_s(s.Get(), s.Length()); + #else + strlwr(s.Get()); + #endif + return s; + } + + /// Returns a copy of the string with all the characters converted to upper case + GString Upper() + { + GString s; + s.Set(Get()); + #ifdef _MSC_VER + _strupr_s(s.Get(), s.Length()); + #else + strupr(s.Get()); + #endif + return s; + } + + /// Gets the character at 'index' + int operator() (int index) + { + if (!Str) + return 0; + + char *c = Str->Str; + if (index < 0) + { + int idx = Str->Len + index; + if (idx >= 0) + { + return c[idx]; + } + } + else if (index < (int)Str->Len) + { + return c[index]; + } + + return 0; + } + + /// Gets the string between at 'start' and 'end' (not including the end'th character) + GString operator() (int start, int end) + { + GString s; + if (Str) + { + char *c = Str->Str; + int start_idx = start < 0 ? Str->Len + start + 1 : start; + int end_idx = end < 0 ? Str->Len + end + 1 : end; + if (start_idx >= 0 && end_idx > start_idx) + s.Set(c + start_idx, end_idx - start_idx); + } + return s; + } + + /// Strip off any leading and trailing whitespace + GString Strip(const char *set = NULL) + { + GString ret; + _strip(ret, set, true, true); + return ret; + } + + /// Strip off any leading whitespace + GString LStrip(const char *set = NULL) + { + GString ret; + _strip(ret, set, true, false); + return ret; + } + + /// Strip off any trailing whitespace + GString RStrip(const char *set = NULL) + { + GString ret; + _strip(ret, set, false, true); + return ret; + } + + /// Prints a formatted string to this object + int Printf(const char *Fmt, ...) + { + Empty(); + va_list Arg; + va_start(Arg, Fmt); + int Bytes = LgiPrintf(*this, Fmt, Arg); + va_end(Arg); + return Bytes; + } +}; + +#endif \ No newline at end of file diff --git a/include/common/Mail.h b/include/common/Mail.h --- a/include/common/Mail.h +++ b/include/common/Mail.h @@ -1,898 +1,931 @@ /** \file \author Matthew Allen */ #ifndef __MAIL_H #define __MAIL_H #include "INet.h" #include "Base64.h" #include "Progress.h" #include "GVariant.h" +#include "GStringClass.h" #ifndef GPL_COMPATIBLE #define GPL_COMPATIBLE 0 #endif // Defines #define MAX_LINE_SIZE 1024 #define MAX_NAME_SIZE 64 #define EMAIL_LINE_SIZE 76 // #define IsDigit(c) ((c) >= '0' AND (c) <= '9') // MIME content types #define CONTENT_NONE 0 #define CONTENT_BASE64 1 #define CONTENT_QUOTED_PRINTABLE 2 #define CONTENT_OCTET_STREAM 3 // Mail logging defines #define MAIL_SEND_COLOUR Rgb24(0, 0, 0xff) #define MAIL_RECEIVE_COLOUR Rgb24(0, 0x8f, 0) #define MAIL_ERROR_COLOUR Rgb24(0xff, 0, 0) #define MAIL_WARNING_COLOUR Rgb24(0xff, 0x7f, 0) #define MAIL_INFO_COLOUR Rgb24(0, 0, 0) // Helper functions extern void TokeniseStrList(char *Str, List &Output, const char *Delim); extern char ConvHexToBin(char c); #define ConvBinToHex(i) (((i)<10)?'0'+(i):'A'+(i)-10) extern void DecodeAddrName(char *Start, GAutoString &Name, GAutoString &Addr, char *DefaultDomain); extern bool IsValidEmail(GAutoString &Email); extern char *DecodeRfc2047(char *Str); extern char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, int LineLength = 0); extern char *DecodeBase64Str(char *Str, int Len = -1); extern char *DecodeQuotedPrintableStr(char *Str, int Len = -1); extern bool Is8Bit(char *Text); extern int MaxLineLen(char *Text); extern char *EncodeImapString(char *s); extern char *DecodeImapString(char *s); // Classes class MailProtocol; struct MailProtocolError { int Code; char *Msg; MailProtocolError() { Code = 0; Msg = 0; } ~MailProtocolError() { DeleteArray(Msg); } }; class MailProtocolProgress { public: uint64 Start; int Value; int Range; MailProtocolProgress() { Empty(); } void Empty() { Start = 0; Value = 0; Range = 0; } }; class LogEntry { public: char *Text; COLOUR c; LogEntry(const char *t, int len, COLOUR col); ~LogEntry(); }; /// Attachment descriptor class FileDescriptor : public GBase { protected: // Global int64 Size; char *MimeType; char *ContentId; // Read from file GFile File; GStreamI *Embeded; bool OwnEmbeded; int64 Offset; GMutex *Lock; // Write to memory uchar *Data; GAutoPtr DataStream; public: FileDescriptor(GStreamI *embed, int64 Offset, int64 Size, char *Name); FileDescriptor(char *name); FileDescriptor(char *data, int64 len); FileDescriptor(); ~FileDescriptor(); void SetLock(GMutex *l); GMutex *GetLock(); void SetOwnEmbeded(bool i); // Access functions GStreamI *GotoObject(); // Get data to read uchar *GetData(); // Get data from write int Sizeof(); char *GetMimeType() { return MimeType; } void SetMimeType(char *s) { DeleteArray(MimeType); MimeType = NewStr(s); } char *GetContentId() { return ContentId; } void SetContentId(char *s) { DeleteArray(ContentId); ContentId = NewStr(s); } // Decode MIME data to memory bool Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLength); }; #define MAIL_ADDR_TO 0 #define MAIL_ADDR_CC 1 #define MAIL_ADDR_BCC 2 #define MAIL_ADDR_FROM 3 /// Address dscriptor class AddressDescriptor : public GBase { public: uint8 Status; uchar CC; // MAIL_ADDR_?? char *Name; char *Addr; void *Data; // application defined AddressDescriptor(AddressDescriptor *Copy = 0); ~AddressDescriptor(); void _Delete(); void Print(char *Str, int Len); int Sizeof() { return SizeofStr(Name) + SizeofStr(Addr); } bool Serialize(GFile &f, bool Write) { bool Status = true; if (Write) { WriteStr(f, Name); WriteStr(f, Addr); } else { DeleteArray(Name); Name = ReadStr(f PassDebugArgs); DeleteArray(Addr); Addr = ReadStr(f PassDebugArgs); } return Status; } }; // FIELD_PRIORITY is equivilent to the header field: X-Priority // 1 - High // ... // 5 - Low #define MAIL_PRIORITY_HIGH 1 #define MAIL_PRIORITY_NORMAL 3 // ??? #define MAIL_PRIORITY_LOW 5 class MailMessage { char* Text; char* TextCharset; char* Html; char* HtmlCharset; public: List To; AddressDescriptor *From; AddressDescriptor *Reply; GAutoString Subject; GAutoString MessageID; GAutoString FwdMsgId; GAutoString BounceMsgId; List FileDesc; char* InternetHeader; char Priority; int MarkColour; uint8 DispositionNotificationTo; // read receipt GAutoString References; // Protocol specific void *Private; // Class MailMessage(); virtual ~MailMessage(); void Empty(); virtual char *GetBody(); virtual bool SetBody(const char *Txt, int Bytes = -1, bool Copy = true, const char *Cs = 0); virtual char *GetBodyCharset(); virtual bool SetBodyCharset(const char *Cs); virtual char *GetHtml(); virtual bool SetHtml(const char *Txt, int Bytes = -1, bool Copy = true, const char *Cs = 0); virtual char *GetHtmlCharset(); virtual bool SetHtmlCharset(const char *Cs); // Conversion to/from MIME GStringPipe *Raw; // High level encoding functions bool Encode (GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime = true); bool EncodeHeaders (GStreamI &Out, MailProtocol *Protocol, bool Mime = true); bool EncodeBody (GStreamI &Out, MailProtocol *Protocol, bool Mime = true); // Encoding mime segment data int EncodeText (GStreamI &Out, GStreamI &In); int EncodeQuotedPrintable (GStreamI &Out, GStreamI &In); int EncodeBase64 (GStreamI &Out, GStreamI &In); }; /// Base class for mail protocol implementations class MailProtocol { protected: char Buffer[4<<10]; GMutex SocketLock; GAutoPtr Socket; bool Error(const char *file, int line, const char *msg, ...); bool Read(); bool Write(const char *Buf = NULL, bool Log = false); virtual void OnUserMessage(char *Str) {} public: // Logging GStreamI *Logger; void Log(const char *Str, GSocketI::SocketMsgType type); // Task Progress MailProtocolProgress *Items; MailProtocolProgress *Transfer; // Settings GAutoString ServerMsg; char *ProgramName; char *DefaultDomain; char *ExtraOutgoingHeaders; List CharsetPrefs; // Object MailProtocol(); virtual ~MailProtocol(); // Methods // GSocketI *GetSocket() { return Socket; } /// Thread safe hard close (quit now) bool CloseSocket() { GMutex::Auto l(&SocketLock, _FL); if (Socket != NULL) return Socket->Close() != 0; return false; } }; ///////////////////////////////////////////////////////////////////// // Mail IO parent classes /// Enable STARTTLS support (requires an SSL capable socket) #define MAIL_USE_STARTTLS 0x01 /// Use authentication #define MAIL_USE_AUTH 0x02 /// Force the use of PLAIN type authentication #define MAIL_USE_PLAIN 0x04 /// Force the use of LOGIN type authentication #define MAIL_USE_LOGIN 0x08 /// Force the use of NTLM type authentication #define MAIL_USE_NTLM 0x10 /// Secure auth #define MAIL_SECURE_AUTH 0x20 /// Use SSL #define MAIL_SSL 0x40 /// Mail sending protocol class MailSink : public MailProtocol { public: /// Connection setup/shutdown virtual bool Open ( /// The transport layer to use GSocketI *S, /// The host to connect to const char *RemoteHost, /// The local domain const char *LocalDomain, /// The sink username (or NULL) const char *UserName, /// The sink password (or NULL) const char *Password, /// The port to connect with or 0 for default. int Port, /// Options: Use any of #MAIL_SSL, #MAIL_USE_STARTTLS, #MAIL_SECURE_AUTH, #MAIL_USE_PLAIN, #MAIL_USE_LOGIN etc or'd together. int Flags ) = 0; /// Close the connection virtual bool Close() = 0; // Commands available while connected /// Write the email's contents into the GStringPipe returned from /// SendStart and then call SendEnd to finish the transaction virtual GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0) = 0; /// Finishes the mail send virtual bool SendEnd(GStringPipe *Sink) = 0; // Deprecated virtual bool Send(MailMessage *Msg, bool Mime) { return false; } }; struct ImapMailFlags { uint8 ImapAnswered : 1; uint8 ImapDeleted : 1; uint8 ImapDraft : 1; uint8 ImapFlagged : 1; uint8 ImapRecent : 1; uint8 ImapSeen : 1; uint8 ImapExpunged :1; ImapMailFlags(char *init = 0) { ImapAnswered = 0; ImapDeleted = 0; ImapDraft = 0; ImapFlagged = 0; ImapRecent = 0; ImapSeen = 0; ImapExpunged = 0; if (init) Set(init); } char *Get() { char s[256] = ""; int ch = 0; if (ImapAnswered) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\answered "); if (ImapDeleted) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\deleted "); if (ImapDraft) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\draft "); if (ImapFlagged) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\flagged "); if (ImapRecent) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\recent "); if (ImapSeen) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\seen "); if (ch == 0) return NULL; LgiAssert(ch < sizeof(s)); s[--ch] = 0; return NewStr(s); } void Set(const char *s) { ImapAnswered = false; ImapDeleted = false; ImapDraft = false; ImapFlagged = false; ImapRecent = false; ImapSeen = false; ImapExpunged = false; if (!s) s = ""; while (*s) { if (*s == '/' || *s == '\\') { while (*s == '/' || *s == '\\') s++; const char *e = s; while (*e && isalpha(*e)) e++; if (!_strnicmp(s, "answered", e-s)) ImapAnswered = true; else if (!_strnicmp(s, "deleted", e-s)) ImapDeleted = true; else if (!_strnicmp(s, "draft", e-s)) ImapDraft = true; else if (!_strnicmp(s, "flagged", e-s)) ImapFlagged = true; else if (!_strnicmp(s, "recent", e-s)) ImapRecent = true; else if (!_strnicmp(s, "seen", e-s)) ImapSeen = true; s = e; } else s++; } } bool operator ==(ImapMailFlags &f) { return ImapAnswered == f.ImapAnswered && ImapDeleted == f.ImapDeleted && ImapDraft == f.ImapDraft && ImapFlagged == f.ImapFlagged && ImapRecent == f.ImapRecent && ImapSeen == f.ImapSeen && ImapExpunged == f.ImapExpunged; } bool operator !=(ImapMailFlags &f) { return !(ImapAnswered == f.ImapAnswered && ImapDeleted == f.ImapDeleted && ImapDraft == f.ImapDraft && ImapFlagged == f.ImapFlagged && ImapRecent == f.ImapRecent && ImapSeen == f.ImapSeen && ImapExpunged == f.ImapExpunged); } }; /// A bulk mail handling class class MailTransaction { public: /// The index of the mail in the folder int Index; /// \sa #MAIL_POSTED_TO_GUI, #MAIL_EXPLICIT int Flags; // bool Delete; bool Status; bool Oversize; /// The mail protocol handler writes the email to this stream GStreamI *Stream; /// Flags used on the IMAP protocolf ImapMailFlags Imap; /// The user app can use this for whatever void *UserData; MailTransaction(); ~MailTransaction(); }; /// Return code from MailSrcCallback enum MailSrcStatus { /// Download the whole email DownloadAll, /// Download just the top part DownloadTop, /// Skip this email DownloadNone, /// About the whole receive DownloadAbort }; /// The callback function used by MailSource::Receive typedef MailSrcStatus (*MailSrcCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The size of the email about to be downloaded uint64 Size, /// If DownloadTop is returned, you can set the number of lines to retreive here int *LinesToDownload, /// The data cookie passed into MailSource::Receive void *Data ); /// The callback function used by MailSource::Receive typedef bool (*MailReceivedCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The data cookie passed into MailSource::Receive void *Data ); /// Collection of callbacks called during mail receive. You should zero this /// entire object before using it. Because if someone adds new callbacks after /// you write the calling code you wouldn't want to leave some callbacks un- /// initialized. A NULL callback is ignored. struct MailCallbacks { /// The callback data void *CallbackData; /// Called before receiving mail MailSrcCallback OnSrc; /// Called after mail received MailReceivedCallback OnReceive; }; /* /// Enable STARTTLS support (requires an SSL capable socket) #define MAIL_SOURCE_STARTTLS 0x01 /// Use authentication #define MAIL_SOURCE_AUTH 0x02 /// Force the use of PLAIN type authentication #define MAIL_SOURCE_USE_PLAIN 0x04 /// Force the use of LOGIN type authentication #define MAIL_SOURCE_USE_LOGIN 0x08 */ /// A generic mail source object class MailSource : public MailProtocol { public: /// Opens a connection to the server virtual bool Open ( /// The transport socket GSocketI *S, /// The hostname or IP of the server char *RemoteHost, /// The port on the host to connect to int Port, /// The username for authentication char *User, /// The password for authentication char *Password, /// A cookie that the implementation can store things in, which persists across sessions. (Dynamically allocated string) char *&Cookie, /// Any optional flags: #MAIL_SOURCE_STARTTLS, #MAIL_SOURCE_AUTH, #MAIL_SOURCE_USE_PLAIN, #MAIL_SOURCE_USE_LOGIN int Flags = 0) = 0; /// Closes the connection virtual bool Close() = 0; /// Returns the number of messages available on the server virtual int GetMessages() = 0; /// Receives a list of messages from the server. virtual bool Receive ( /// An array of messages to receive. The MailTransaction objects contains the index of the message to receive /// and various status values returned after the operation. GArray &Trans, /// An optional set of callback functions. MailCallbacks *Callbacks = 0 ) = 0; /// Deletes a message on the server virtual bool Delete(int Message) = 0; /// Gets the size of the message on the server virtual int Sizeof(int Message) = 0; /// Gets the size of all the messages on the server virtual bool GetSizes(GArray &Sizes) { return false; } /// Gets the unique identifier of the message virtual bool GetUid(int Message, char *Id, int IdLen) = 0; /// Gets the unique identifiers of a list of messages virtual bool GetUidList(List &Id) = 0; /// Gets the headers associated with a given message virtual char *GetHeaders(int Message) = 0; /// Sets the proxy server. e.g. HTTP mail. virtual void SetProxy(char *Server, int Port) {} }; ///////////////////////////////////////////////////////////////////// // Mail IO implementations /// SMTP implementation class MailSmtp : public MailSink { protected: bool ReadReply(const char *Str, GStringPipe *Pipe = 0, MailProtocolError *Err = 0); bool WriteText(const char *Str); public: MailSmtp(); ~MailSmtp(); bool Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); bool SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); GStringPipe *SendData(MailProtocolError *Err = 0); GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(GStringPipe *Sink); bool Send(MailMessage *Msg, bool Mime = false); }; class MailSendFolder : public MailSink { class MailPostFolderPrivate *d; public: MailSendFolder(char *Path); ~MailSendFolder(); bool Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(GStringPipe *Sink); }; class MailPop3 : public MailSource { protected: bool ReadReply(); bool ReadMultiLineReply(char *&Str); int GetInt(); bool MailIsEnd(char *Ptr, int Len); bool ListCmd(const char *Cmd, GHashTbl &Results); const char *End; const char *Marker; int Messages; public: MailPop3(); ~MailPop3(); // Connection bool Open(GSocketI *S, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); }; class MailReceiveFolder : public MailSource { protected: class MailReceiveFolderPrivate *d; public: MailReceiveFolder(char *Path); ~MailReceiveFolder(); // Connection bool Open(GSocketI *S, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); }; class MailPhp : public MailSource { protected: class MailPhpPrivate *d; bool Get(GSocketI *S, char *Uri, GStream &Out, bool ChopDot); public: MailPhp(); ~MailPhp(); // Connection bool Open(GSocketI *S, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); void SetProxy(char *Server, int Port); }; class MailImapFolder { friend class MailIMap; friend struct ImapThreadPrivate; char Sep; char *Path; public: bool NoSelect; bool NoInferiors; bool Marked; int Exists; int Recent; int Deleted; // int UnseenIndex; MailImapFolder(); virtual ~MailImapFolder(); char *GetPath(); void SetPath(char *s); char *GetName(); void SetName(char *s); void operator =(GHashTbl &v); }; class MailIMap : public MailSource, public GMutex { protected: class MailIMapPrivate *d; char Buf[1024]; List Uid; GStringPipe ReadBuf; List Dialog; void ClearDialog(); void ClearUid(); bool FillUidList(); bool WriteBuf(bool ObsurePass = false, const char *Buffer = 0); bool ReadResponse(int Cmd = -1, bool Plus = false); bool Read(GStreamI *Out = 0); bool ReadLine(); + bool IsResponse(const char *Buf, int Cmd, bool &Ok); public: // Typedefs struct Untagged { GAutoString Cmd; GAutoString Param; int Id; }; + struct OAuthParams + { + GString ClientID; + GString ClientSecret; + GString RedirURIs; + GString AuthUri; + GString ApiUri; + GString RevokeUri; + GString TokenUri; + GString Scope; + GUri Proxy; + + GString AccessToken; + GString RefreshToken; + int ExpiresIn; + + bool IsValid() + { + return ClientID && + ClientSecret && + RedirURIs && + AuthUri && + RevokeUri && + TokenUri && + Scope && + ApiUri; + } + }; + /// This callback is used to notify the application using this object of IMAP fetch responses. /// \returns true if the application wants to continue reading and has taken ownership of the strings in "Parts". typedef bool (*FetchCallback) ( /// The IMAP object class MailIMap *Imap, /// The message sequence number char *Msg, /// The fetch parts (which the callee needs to own if returning true) GHashTbl &Parts, /// The user data passed to the Fetch function void *UserData ); // Object MailIMap(); ~MailIMap(); // General char GetFolderSep(); char *EncodePath(const char *Path); char *GetCurrentPath(); bool GetExpungeOnExit(); void SetExpungeOnExit(bool b); bool ServerOption(char *Opt); bool IsOnline(); + const char *GetWebLoginUri(); + void SetOAuthParams(OAuthParams &p); + void SetParentWindow(GViewI *wnd); // Connection bool Open(GSocketI *S, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags = 0); bool Close(); // Non-threadsafe soft close (normal operation) bool GetCapabilities(GArray &s); // Commands available while connected bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); - // bool GetParts(int Message, GStreamI &Out, const char *Parts, char **Flags = 0); int GetMessages(); bool Delete(int Message); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); char *SequenceToString(GArray *Seq); // Imap specific commands /// This method wraps the imap FETCH command bool Fetch ( /// True if 'Seq' is a UID, otherwise it's a sequence bool ByUid, /// The sequence number or UID const char *Seq, /// The parts to retrieve const char *Parts, /// Data is returned to the caller via this callback function FetchCallback Callback, /// A user defined param to pass back to the 'Callback' function. void *UserData, /// [Optional] The raw data received will be written to this stream if provided, else NULL. GStreamI *RawCopy = 0 ); /// Appends a message to the specified folder bool Append ( /// The folder to write to char *Folder, /// [Optional] Flags for the message ImapMailFlags *Flags, /// The rfc822 body of the message char *Msg, /// [Out] The UID of the message appended (if known, can be empty if not known) GAutoString &NewUid ); bool GetFolders(List &Folders); bool SelectFolder(const char *Path, GHashTbl *Values = 0); char *GetSelectedFolder(); int GetMessages(const char *Path); bool CreateFolder(MailImapFolder *f); bool DeleteFolder(char *Path); bool RenameFolder(char *From, char *To); bool SetFolderFlags(MailImapFolder *f); /// Expunges (final delete) any deleted messages the current folder. bool ExpungeFolder(); // Uid methods bool CopyByUid(GArray &InUids, char *DestFolder); bool SetFlagsByUid(GArray &Uids, const char *Flags); /// Idle processing... /// \returns true if something happened bool StartIdle(); bool OnIdle(int Timeout, GArray &Resp); bool FinishIdle(); bool Poll(int *Recent = 0, GArray *New = 0); bool Status(char *Path, int *Recent); bool Search(bool Uids, GArray &SeqNumbers, const char *Filter); // Utility static bool Http(GSocketI *S, GAutoString *OutHeaders, GAutoString *OutBody, int *StatusCode, const char *InMethod, const char *InUri, const char *InHeaders, const char *InBody); }; #endif diff --git a/src/common/INet/MailImap.cpp b/src/common/INet/MailImap.cpp --- a/src/common/INet/MailImap.cpp +++ b/src/common/INet/MailImap.cpp @@ -1,2852 +1,3243 @@ #include #ifdef LINUX #include #endif #include "Lgi.h" #include "GToken.h" #include "Mail.h" #include "Base64.h" #include "INetTools.h" #include "GUtf8.h" #include "GDocView.h" +#include "IHttp.h" +#include "HttpTools.h" +#include "OpenSSLSocket.h" + +#define OPT_ImapOAuth2AuthCode "OAuth2.Code" //////////////////////////////////////////////////////////////////////////// #if GPL_COMPATIBLE #include "AuthNtlm/Ntlm.h" #else #include "../../src/common/INet/libntlm-0.4.2/ntlm.h" #endif #if HAS_LIBGSASL #include "gsasl.h" #endif static const char *sRfc822Header = "RFC822.HEADER"; static const char *sRfc822Size = "RFC822.SIZE"; #define Ws(s) while (*s && strchr(WhiteSpace, *s)) s++ #define SkipWhite(s) while ((s - Buffer) < Used && strchr(" \t", *s)) s++; #define SkipNonWhite(s) while ((s - Buffer) < Used && !strchr(WhiteSpace, *s)) s++; #define ExpectChar(ch) { if ((s - Buffer) >= Used || *s != ch) return 0; s++; } +bool Base64Str(GString &s) +{ + GString b64; + int Base64Len = BufferLen_BinTo64(s.Length()); + if (!b64.Set(NULL, Base64Len)) + return false; + + int Ch = ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length()); + LgiAssert(Ch == b64.Length()); + s = b64; + return true; +} + +bool UnBase64Str(GString &s) +{ + GString Bin; + int BinLen = BufferLen_64ToBin(s.Length()); + if (!Bin.Set(NULL, BinLen)) + return false; + + int Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length()); + LgiAssert(Ch <= (int)Bin.Length()); + s = Bin; + s.Get()[Ch] = 0; + return true; +} + +#define SkipWhiteSpace(s) while (*s && IsWhiteSpace(*s)) s++; +bool JsonDecode(GXmlTag &t, const char *s) +{ + if (*s != '{') + return false; + s++; + while (*s) + { + SkipWhiteSpace(s); + if (*s != '\"') + break; + GAutoString Variable(LgiTokStr(s)); + SkipWhiteSpace(s); + if (*s != ':') + return false; + s++; + SkipWhiteSpace(s); + GAutoString Value(LgiTokStr(s)); + SkipWhiteSpace(s); + + t.SetAttr(Variable, Value); + if (*s != ',') + break; + s++; + } + + if (*s != '}') + return false; + s++; + return true; +} + struct StrRange { int Start, End; void Set(int s, int e) { Start = s; End = e; } int Len() { return End - Start; } }; int ParseImapResponse(GArray &Buf, int Used, GArray &Ranges, int Names) { Ranges.Length(0); if (Used <= 0) return 0; char *Buffer = &Buf[0]; if (*Buffer != '*') return 0; char *s = Buffer + 1; char *Start; for (int n=0; n= Used) return 0; Ranges.New().Set(Start - Buffer, s - Buffer); } else { // Parse single if (*s == '(') { s++; Start = s; int Depth = 1; while ((s - Buffer) < Used) { if (*s == '\"') { s++; while ((s - Buffer) < Used && *s != '\"') { s++; } } if (*s == '(') Depth++; else if (*s == ')') { Depth--; if (Depth == 0) break; } s++; } if (*s != ')') return 0; Ranges.New().Set(Start - Buffer, s - Buffer); s++; } else { if (*s == '\'' || *s == '\"') { char *Begin = s; char Delim = *s++; Start = s; while (*s && (s - Buffer) < Used && *s != Delim) s++; if (*s == Delim) { Ranges.New().Set(Start - Buffer, s - Buffer); s++; } else { // Parse error; return 0; } } else { Start = s; while (*s && (s - Buffer) < Used) { if (strchr(WhiteSpace, *s) || *s == ')') break; s++; } Ranges.New().Set(Start - Buffer, s - Buffer); } } } } return MsgSize; } //////////////////////////////////////////////////////////////////////////// bool MailIMap::Http(GSocketI *S, GAutoString *OutHeaders, GAutoString *OutBody, int *StatusCode, const char *InMethod, const char *InUri, const char *InHeaders, const char *InBody) { if (!S || !InUri) return false; GStringPipe p(256); GUri u(InUri); p.Print("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-length: %i\r\n" "\r\n%s", u.Path, u.Host, InBody?strlen(InBody):0, InBody?InBody:""); GAutoString Req(p.NewStr()); int ReqLen = strlen(Req); if (!S->Open(u.Host, u.Port?u.Port:443)) return false; int w = S->Write(Req, ReqLen); if (w != ReqLen) return false; char Buf[256]; GArray Res; int r; int ContentLen = 0; uint32 HdrLen = 0; while ((r = S->Read(Buf, sizeof(Buf))) > 0) { int Old = Res.Length(); Res.Length(Old + r); memcpy(&Res[Old], Buf, r); if (ContentLen) { if (Res.Length() >= HdrLen + ContentLen) break; } else { char *Eoh = strnstr(&Res[0], "\r\n\r\n", Res.Length()); if (Eoh) { HdrLen = Eoh - &Res[0]; GAutoString c(InetGetHeaderField(&Res[0], "Content-Length", HdrLen)); if (c) { ContentLen = atoi(c); } } } } char *Rp = &Res[0]; char *Eoh = strnstr(Rp, "\r\n\r\n", Res.Length()); if (Eoh) { if (OutHeaders) OutHeaders->Reset(NewStr(Rp, Eoh-Rp)); if (OutBody) OutBody->Reset(NewStr(Eoh + 4, Res.Length() - (Eoh-Rp) - 4)); if (StatusCode) { *StatusCode = 0; char *Eol = strchr(Rp, '\n'); if (Eol) { GToken t(Rp, " \t\r\n", true, Eol - Rp); if (t.Length() > 2) { *StatusCode = atoi(t[1]); } } } } else return false; #ifndef _DEBUG GFile f; if (f.Open("c:\\temp\\http.html", O_WRITE)) { f.SetSize(0); f.Write(&Res[0], Res.Length()); f.Close(); } #endif return true; } GAutoString ImapBasicTokenize(char *&s) { if (s) { while (*s && strchr(WhiteSpace, *s)) s++; char start = 0, end = 0; if (*s == '\'' || *s == '\"') start = end = *s; else if (*s == '[') { start = '['; end = ']'; } else if (*s == '(') { start = '('; end = ')'; } else if (*s == '{') { start = '{'; end = '}'; } if (start && end) { s++; char *e = strchr(s, end); if (e) { char *n = NewStr(s, e - s); s = e + 1; return GAutoString(n); } } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; char *n = NewStr(s, e - s); s = e + (*e != 0); return GAutoString(n); } } s += strlen(s); return GAutoString(); } char *Tok(char *&s) { char *Ret = 0; while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '=' || *s == ',') { Ret = NewStr(s++, 1); } else if (*s == '\'' || *s == '\"') { char d = *s++; char *e = strchr(s, d); if (e) { Ret = NewStr(s, e - s); s = e + 1; } } else if (*s) { char *e; for (e=s; *e && (IsDigit(*e) || IsAlpha(*e) || *e == '-'); e++); Ret = NewStr(s, e - s); s = e; } return Ret; } char *DecodeImapString(char *s) { GStringPipe p; while (s && *s) { if (*s == '&') { char Escape = *s++; char *e = s; while (*e && *e != '-') { e++; } int Len = e - s; if (Len) { char *Base64 = new char[Len + 4]; if (Base64) { memcpy(Base64, s, Len); char *n = Base64 + Len; for (int i=Len; i%4; i++) *n++ = '='; *n++ = 0; Len = strlen(Base64); int BinLen = BufferLen_64ToBin(Len); uint16 *Bin = new uint16[(BinLen/2)+1]; if (Bin) { BinLen = ConvertBase64ToBinary((uchar*)Bin, BinLen, Base64, Len); if (BinLen) { int Chars = BinLen / 2; Bin[Chars] = 0; for (int i=0; i>8) | ((Bin[i]&0xff)<<8); } char *c8 = LgiNewUtf16To8((char16*)Bin, BinLen); if (c8) { p.Push(c8); DeleteArray(c8); } } DeleteArray(Bin); } DeleteArray(Base64); } } else { p.Push(&Escape, 1); } s = e + 1; } else { p.Push(s, 1); s++; } } return p.NewStr(); } char *EncodeImapString(char *s) { GStringPipe p; int Len = s ? strlen(s) : 0; while (s && *s) { int c = LgiUtf8To32((uint8*&)s, Len); DoNextChar: if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { // Literal char ch = c; p.Push(&ch, 1); } else { // Encoded GArray Str; Str[0] = c; while ((c = LgiUtf8To32((uint8*&)s, Len))) { if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { break; } else { Str[Str.Length()] = c; } } for (uint32 i=0; i>8) | ((Str[i]&0xff)<<8); } int BinLen = Str.Length() << 1; int BaseLen = BufferLen_BinTo64(BinLen); char *Base64 = new char[BaseLen+1]; if (Base64) { int Bytes = ConvertBinaryToBase64(Base64, BaseLen, (uchar*)&Str[0], BinLen); while (Bytes > 0 && Base64[Bytes-1] == '=') { Base64[Bytes-1] = 0; Bytes--; } Base64[Bytes] = 0; p.Print("&%s-", Base64); DeleteArray(Base64); } goto DoNextChar; } } return p.NewStr(); } void ChopNewLine(char *Str) { char *End = Str+strlen(Str)-1; if (*End == '\n') { *End-- = 0; } if (*End == '\r') { *End-- = 0; } } MailImapFolder::MailImapFolder() { Sep = '/'; Path = 0; NoSelect = false; NoInferiors = false; Marked = false; Exists = -1; Recent = -1; // UnseenIndex = -1; Deleted = 0; } MailImapFolder::~MailImapFolder() { DeleteArray(Path); } void MailImapFolder::operator =(GHashTbl &v) { int o = v.Find("exists"); if (o >= 0) Exists = o; o = v.Find("recent"); if (o >= 0) Recent = o; } char *MailImapFolder::GetPath() { return Path; } void MailImapFolder::SetPath(char *s) { char *NewPath = DecodeImapString(s); DeleteArray(Path); Path = NewPath; } char *MailImapFolder::GetName() { if (Path) { char *s = strrchr(Path, Sep); if (s) { return s + 1; } else { return Path; } } return 0; } void MailImapFolder::SetName(char *s) { if (s) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), Path?Path:(char*)""); DeleteArray(Path); char *Last = strrchr(Buf, Sep); if (Last) { Last++; strcpy_s(Last, sizeof(Buf)-(Last-Buf), s); Path = NewStr(Buf); } else { Path = NewStr(s); } } } ///////////////////////////////////////////// class MailIMapPrivate { public: int NextCmd; bool Logging; bool ExpungeOnExit; char FolderSep; char *Current; char *Flags; GHashTable Capability; + GString WebLoginUri; + MailIMap::OAuthParams OAuth; + GViewI *ParentWnd; MailIMapPrivate() { + ParentWnd = NULL; FolderSep = '/'; NextCmd = 1; Logging = true; ExpungeOnExit = true; Current = 0; Flags = 0; } ~MailIMapPrivate() { DeleteArray(Current); DeleteArray(Flags); } }; MailIMap::MailIMap() : GMutex("MailImapSem") { d = new MailIMapPrivate; Buffer[0] = 0; } MailIMap::~MailIMap() { if (Lock(_FL)) { ClearDialog(); ClearUid(); DeleteObj(d); } } +void MailIMap::SetParentWindow(GViewI *wnd) +{ + d->ParentWnd = wnd; +} + +void MailIMap::SetOAuthParams(OAuthParams &p) +{ + d->OAuth = p; +} + +const char *MailIMap::GetWebLoginUri() +{ + return d->WebLoginUri; +} + bool MailIMap::IsOnline() { return Socket ? Socket->IsOpen() : 0; } char MailIMap::GetFolderSep() { return d->FolderSep; } char *MailIMap::GetCurrentPath() { return d->Current; } bool MailIMap::GetExpungeOnExit() { return d->ExpungeOnExit; } void MailIMap::SetExpungeOnExit(bool b) { d->ExpungeOnExit = b; } void MailIMap::ClearUid() { if (Lock(_FL)) { Uid.DeleteArrays(); Unlock(); } } void MailIMap::ClearDialog() { if (Lock(_FL)) { Dialog.DeleteArrays(); Unlock(); } } bool MailIMap::WriteBuf(bool ObsurePass, const char *Buffer) { if (Socket) { if (!Buffer) Buffer = Buf; int Len = strlen(Buffer); if (Socket->Write((void*)Buffer, Len, 0) == Len) { if (ObsurePass) { char *Sp = (char*)strrchr(Buffer, ' '); if (Sp) { Sp++; strcpy_s(Sp, sizeof(Buffer)-(Sp-Buffer), "********\r\n"); } } Log(Buffer, GSocketI::SocketMsgSend); return true; } else Log("Failed to write data to socket.", GSocketI::SocketMsgError); } else Log("Not connected.", GSocketI::SocketMsgError); return false; } bool MailIMap::Read(GStreamI *Out) { int Lines = 0; while (!Lines && Socket) { int r = Socket->Read(Buffer, sizeof(Buffer)); if (r > 0) { ReadBuf.Push(Buffer, r); while (ReadBuf.Pop(Buffer, sizeof(Buffer))) { // Trim trailing whitespace char *e = Buffer + strlen(Buffer) - 1; while (e > Buffer && strchr(WhiteSpace, *e)) *e-- = 0; Lines++; if (Out) { Out->Write(Buffer, strlen(Buffer)); Out->Write((char*)"\r\n", 2); } else { Dialog.Add(NewStr(Buffer)); } } } else { // LgiTrace("%s:%i - Socket->Read failed: %i\n", _FL, r); break; } } return Lines > 0; } +bool MailIMap::IsResponse(const char *Buf, int Cmd, bool &Ok) +{ + char Num[8]; + int Ch = sprintf_s(Num, sizeof(Num), "A%4.4i ", Cmd); + if (!Buf || _strnicmp(Buf, Num, Ch) != 0) + return false; + + Ok = _strnicmp(Buf+Ch, "OK", 2) == 0; + if (!Ok) + ServerMsg.Reset(NewStr(Buf+Ch)); + + return true; +} + bool MailIMap::ReadResponse(int Cmd, bool Plus) { bool Done = false; bool Status = false; if (Socket) { int Pos = Dialog.Length(); while (!Done) { if (Read(NULL)) { for (char *Dlg = Dialog[Pos]; !Done && Dlg; Dlg = Dialog.Next()) { Pos++; if (Cmd < 0 || (Plus && *Dlg == '+')) { Status = Done = true; } - char Num[8]; - sprintf_s(Num, sizeof(Num), "A%4.4i ", Cmd); - if (_strnicmp(Dlg, Num, 6) == 0) - { + if (IsResponse(Dlg, Cmd, Status)) Done = true; - Status = _strnicmp(Dlg+6, "OK", 2) == 0; - if (!Status) - ServerMsg.Reset(NewStr(Dlg+6)); - } if (d->Logging) Log(Dlg, GSocketI::SocketMsgReceive); } } else { // LgiTrace("%s:%i - 'Read' failed.\n", _FL); break; } } } return Status; } void Hex(char *Out, int OutLen, uchar *In, int InLen = -1) { if (Out && In) { if (InLen < 0) InLen = strlen((char*)In); for (int i=0; i 0) { Out += ch; OutLen -= ch; } else break; } } } void _unpack(void *ptr, int ptrsize, char *b64) { int c = ConvertBase64ToBinary((uchar*) ptr, ptrsize, b64, strlen(b64)); } bool MailIMap::ReadLine() { int Len = 0; Buf[0] = 0; do { int r = Socket->Read(Buf+Len, sizeof(Buf)-Len); if (r < 1) return false; Len += r; } while (!stristr(Buf, "\r\n")); Log(Buf, GSocketI::SocketMsgReceive); return true; } #if HAS_LIBGSASL int GsaslCallback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { return 0; } #endif +void StrFormEncode(GStream &p, char *s, bool InValue) +{ + for (char *c = s; *c; c++) + { + if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') + { + p.Write(c, 1); + } + else if (*c == ' ') + { + p.Write((char*)"+", 1); + } + else + { + p.Print("%%%02.2X", *c); + } + } +} + +class OAuthWebServer : public GThread, public GMutex +{ + bool Loop; + int Port; + GSocket Listen; + GAutoString Req; + GString Resp; + +public: + OAuthWebServer() : + GThread("OAuthWebServerThread"), + GMutex("OAuthWebServerMutex") + { + Loop = true; + if (Listen.Listen()) + { + Port = Listen.GetLocalPort(); + Run(); + } + else Port = 0; + } + + ~OAuthWebServer() + { + Loop = false; + while (!IsExited()) + LgiSleep(10); + } + + int GetPort() + { + return Port; + } + + GString GetRequest() + { + GString r; + + while (!r) + { + if (Lock(_FL)) + { + if (Req) + r = Req; + Unlock(); + } + } + + return r; + } + + void SetResponse(const char *r) + { + if (Lock(_FL)) + { + Resp = r; + Unlock(); + } + } + + int Main() + { + GAutoPtr s; + while (Loop) + { + if (Listen.CanAccept(100)) + { + s.Reset(new GSocket); + + if (!Listen.Accept(s)) + s.Reset(); + else + { + GArray Mem; + int r; + char buf[512]; + do + { + r = s->Read(buf, sizeof(buf)); + if (r > 0) + { + Mem.Add(buf, r); + bool End = strnstr(&Mem[0], "\r\n\r\n", Mem.Length()) != NULL; + if (End) + break; + } + } + while (r > 0); + + if (Lock(_FL)) + { + Mem.Add(0); + Req.Reset(Mem.Release()); + Unlock(); + } + + GString Response; + do + { + if (Lock(_FL)) + { + if (Resp) + Response = Resp; + Unlock(); + } + if (!Response) + LgiSleep(10); + } + while (Loop && !Response); + + if (Response) + { + s->Write(Response, Response.Length()); + } + } + } + else LgiSleep(10); + } + return 0; + } +}; + bool MailIMap::Open(GSocketI *s, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags) { bool Status = false; if (SocketLock.Lock(_FL)) { Socket.Reset(s); SocketLock.Unlock(); } if (Socket && ValidStr(RemoteHost) && ValidStr(User) && ValidStr(Password) && Lock(_FL)) { // prepare address if (Port < 1) { if (Flags & MAIL_SSL) Port = IMAP_SSL_PORT; else Port = IMAP_PORT; } char Remote[256]; strcpy_s(Remote, sizeof(Remote), RemoteHost); char *Colon = strchr(Remote, ':'); if (Colon) { *Colon++ = 0; Port = atoi(Colon); } // Set SSL mode GVariant v; if (Flags == MAIL_SSL) v = "SSL"; Socket->SetValue(GSocket_Protocol, v); // connect if (Socket->Open(Remote, Port)) { bool IMAP4Server = false; GArray Auths; // check capability int CapCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CAPABILITY\r\n", CapCmd); if (WriteBuf()) { if (ReadResponse(CapCmd)) { for (char *r=Dialog.First(); r; r=Dialog.Next()) { GToken T(r, " "); if (T.Length() > 1 && _stricmp(T[1], "CAPABILITY") == 0) { for (unsigned i=2; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STARTTLS\r\n", CapCmd); if (WriteBuf()) { if (ReadResponse(CapCmd)) { GVariant v; TlsError = !Socket->SetValue(GSocket_Protocol, v="SSL"); } else { TlsError = true; } } else LgiAssert(0); if (TlsError) { Log("STARTTLS failed", GSocketI::SocketMsgError); } } // login bool LoggedIn = false; char AuthTypeStr[256] = ""; for (unsigned i=0; i 0) { strconcat(AuthTypeStr, ", "); } strconcat(AuthTypeStr, AuthType); } // Do auth #if HAS_LIBGSASL if (!_stricmp(AuthType, "GSSAPI")) { int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE GSSAPI\r\n", AuthCmd); if (WriteBuf() && ReadLine() && Buf[0] == '+') { // Start GSSAPI Gsasl *ctx = NULL; Gsasl_session *sess = NULL; int rc = gsasl_init(&ctx); if (rc == GSASL_OK) { char *mechs; rc = gsasl_client_mechlist(ctx, &mechs); gsasl_callback_set(ctx, GsaslCallback); rc = gsasl_client_start(ctx, AuthType, &sess); if (rc != GSASL_OK) { Log("gsasl_client_start failed", GSocketI::SocketMsgError); } // gsasl_step(ctx, gsasl_done(ctx); } else Log("gsasl_init failed", GSocketI::SocketMsgError); } else Log("AUTHENTICATE GSSAPI failed", GSocketI::SocketMsgError); } else #endif if (_stricmp(AuthType, "LOGIN") == 0 || _stricmp(AuthType, "OTP") == 0) { // clear text authentication int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGIN %s %s\r\n", AuthCmd, User, Password); if (WriteBuf(true)) { LoggedIn = ReadResponse(AuthCmd); } } else if (_stricmp(AuthType, "PLAIN") == 0) { // plain auth type char s[256]; char *e = s; *e++ = 0; strcpy_s(e, sizeof(s)-(e-s), User); e += strlen(e); e++; strcpy_s(e, sizeof(s)-(e-s), Password); e += strlen(e); *e++ = '\r'; *e++ = '\n'; int Len = e - s - 2; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE PLAIN\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { int b = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)s, Len); strcpy_s(Buf+b, sizeof(Buf)-b, "\r\n"); if (WriteBuf()) { LoggedIn = ReadResponse(AuthCmd); } } } } #if (GPL_COMPATIBLE || defined(_LIBNTLM_H)) && defined(WIN32NATIVE) else if (_stricmp(AuthType, "NTLM") == 0) { // NT Lan Man authentication OSVERSIONINFO ver; ZeroObj(ver); ver.dwOSVersionInfoSize = sizeof(ver); if (!GetVersionEx(&ver)) { DWORD err = GetLastError(); Log("Couldn't get OS version", GSocketI::SocketMsgError); } else { // Username is in the format: User[@Domain] char UserDom[256]; strcpy_s(UserDom, sizeof(UserDom), User); char *Domain = strchr(UserDom, '@'); if (Domain) *Domain++ = 0; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE NTLM\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { tSmbNtlmAuthNegotiate negotiate; tSmbNtlmAuthChallenge challenge; tSmbNtlmAuthResponse response; buildSmbNtlmAuthNegotiate(&negotiate, 0, 0); if (NTLM_VER(&negotiate) == 2) { negotiate.v2.version.major = (uint8) ver.dwMajorVersion; negotiate.v2.version.minor = (uint8) ver.dwMinorVersion; negotiate.v2.version.buildNumber = (uint16) ver.dwBuildNumber; negotiate.v2.version.ntlmRevisionCurrent = 0x0f; } ZeroObj(Buf); int negotiateLen = SmbLength(&negotiate); int c = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)&negotiate, negotiateLen); strcpy_s(Buf+c, sizeof(Buf)-c, "\r\n"); WriteBuf(); /* read challange data from server, convert from base64 */ Buf[0] = 0; ClearDialog(); if (ReadResponse()) { /* buffer should contain the string "+ [base 64 data]" */ #if 1 ZeroObj(challenge); char *Line = Dialog.First(); LgiAssert(Line != NULL); ChopNewLine(Line); int LineLen = strlen(Line); int challengeLen = sizeof(challenge); c = ConvertBase64ToBinary((uchar*) &challenge, sizeof(challenge), Line+2, LineLen-2); if (NTLM_VER(&challenge) == 2) challenge.v2.bufIndex = c - (challenge.v2.buffer-(uint8*)&challenge); else challenge.v1.bufIndex = c - (challenge.v1.buffer-(uint8*)&challenge); #endif /* prepare response, convert to base64, send to server */ ZeroObj(response); FILETIME time = {0, 0}; SYSTEMTIME stNow; GetSystemTime(&stNow); SystemTimeToFileTime(&stNow, &time); char HostName[256] = ""; gethostname(HostName, sizeof(HostName)); buildSmbNtlmAuthResponse(&challenge, &response, UserDom, HostName, Domain, Password, (uint8*)&time); if (NTLM_VER(&response) == 2) { response.v2.version.major = (uint8) ver.dwMajorVersion; response.v2.version.minor = (uint8) ver.dwMinorVersion; response.v2.version.buildNumber = (uint16) ver.dwBuildNumber; response.v2.version.ntlmRevisionCurrent = 0x0f; } #if 0 { uint8 *r1 = (uint8*)&response; uint8 *r2 = (uint8*)&response_good; for (int i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE DIGEST-MD5\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd)) { char *TestCnonce = 0; #if 0 // Test case strcpy(Buf, "+ cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hhcnNldD11dGYtOA=="); RemoteHost = "elwood.innosoft.com"; User = "chris"; Password = "secret"; TestCnonce = "OA6MHXh6VqTrRk"; #endif char *In = (char*)Buf; if (In[0] == '+' && In[1] == ' ') { In += 2; uchar Out[2048]; int b = ConvertBase64ToBinary(Out, sizeof(Out), In, strlen(In)); Out[b] = 0; GHashTbl Map; char *s = (char*)Out; while (s && *s) { char *Var = Tok(s); char *Eq = Tok(s); char *Val = Tok(s); char *Comma = Tok(s); if (Var && Eq && Val && strcmp(Eq, "=") == 0) { Map.Add(Var, Val); Val = 0; } DeleteArray(Var); DeleteArray(Eq); DeleteArray(Val); DeleteArray(Comma); } int32 CnonceI[2] = { LgiRand(), LgiRand() }; char Cnonce[32]; if (TestCnonce) strcpy_s(Cnonce, sizeof(Cnonce), TestCnonce); else Cnonce[ConvertBinaryToBase64(Cnonce, sizeof(Cnonce), (uchar*)&CnonceI, sizeof(CnonceI))] = 0; s = strchr(Cnonce, '='); if (s) *s = 0; int Nc = 1; char *Realm = Map.Find("realm"); char DigestUri[256]; sprintf_s(DigestUri, sizeof(DigestUri), "imap/%s", Realm ? Realm : RemoteHost); GStringPipe p; p.Print("username=\"%s\"", User); p.Print(",nc=%08.8i", Nc); p.Print(",digest-uri=\"%s\"", DigestUri); p.Print(",cnonce=\"%s\"", Cnonce); char *Nonce = Map.Find("nonce"); if (Nonce) { p.Print(",nonce=\"%s\"", Nonce); } if (Realm) { p.Print(",realm=\"%s\"", Realm); } char *Charset = Map.Find("charset"); if (Charset) { p.Print(",charset=%s", Charset); } char *Qop = Map.Find("qop"); if (Qop) { p.Print(",qop=%s", Qop); } // Calculate A1 char a1[256]; uchar md5[16]; sprintf_s(Buf, sizeof(Buf), "%s:%s:%s", User, Realm ? Realm : (char*)"", Password); MDStringToDigest((uchar*)a1, Buf); char *Authzid = Map.Find("authzid"); int ch = 16; if (Authzid) ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s:%s", Nonce, Cnonce, Authzid); else ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s", Nonce, Cnonce); MDStringToDigest(md5, a1, ch); char a1hex[256]; Hex(a1hex, sizeof(a1hex), (uchar*)md5, sizeof(md5)); // Calculate char a2[256]; if (Qop && (_stricmp(Qop, "auth-int") == 0 || _stricmp(Qop, "auth-conf") == 0)) sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s:00000000000000000000000000000000", DigestUri); else sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s", DigestUri); MDStringToDigest(md5, a2); char a2hex[256]; Hex(a2hex, sizeof(a2hex), (uchar*)md5, sizeof(md5)); // Calculate the final response sprintf_s(Buf, sizeof(Buf), "%s:%s:%8.8i:%s:%s:%s", a1hex, Nonce, Nc, Cnonce, Qop, a2hex); MDStringToDigest(md5, Buf); Hex(Buf, sizeof(Buf), (uchar*)md5, sizeof(md5)); p.Print(",response=%s", Buf); if ((s = p.NewStr())) { int Chars = ConvertBinaryToBase64(Buf, sizeof(Buf) - 4, (uchar*)s, strlen(s)); LgiAssert(Chars < sizeof(Buf)); strcpy_s(Buf+Chars, sizeof(Buf)-Chars, "\r\n"); if (WriteBuf() && Read()) { for (char *Dlg = Dialog.First(); Dlg; Dlg=Dialog.Next()) { if (Dlg[0] == '+' && Dlg[1] == ' ') { Log(Dlg, GSocketI::SocketMsgReceive); strcpy_s(Buf, sizeof(Buf), "\r\n"); if (WriteBuf()) { LoggedIn = ReadResponse(AuthCmd); } } else { Log(Dlg, GSocketI::SocketMsgError); break; } } } DeleteArray(s); } } } } } - /* - else if (!_stricmp(AuthType, "XOAUTH")) + else if (!_stricmp(AuthType, "XOAUTH2")) { - GAutoPtr Ssl(dynamic_cast(Socket->Clone())); - if (Ssl) + if (!d->OAuth.IsValid()) + { + sprintf_s(Buf, sizeof(Buf), "Error: Unknown OAUTH2 server '%s' (ask fret@memecode.com to fix)", RemoteHost); + Log(Buf, GSocketI::SocketMsgError); + continue; + } + + GString Uri; + GString RedirUri; + GVariant AuthCode; + /* FIXME + if (SettingStore) + SettingStore->GetValue(OPT_ImapOAuth2AuthCode, AuthCode); + */ + + if (!ValidStr(AuthCode.Str())) { - GStringPipe p; - GAutoString Hdr, Body; - int StatusCode = 0; - char Url[256]; - sprintf_s(Url, sizeof(Url), "http://mail.google.com/mail/b/%s/imap/", User); - if (Http(Ssl, &Hdr, &Body, &StatusCode, "POST", Url, 0, 0)) + OAuthWebServer WebServer; + + // Launch browser to get Access Token + bool UsingLocalhost = WebServer.GetPort() > 0; + if (UsingLocalhost) { - bool Status = true; - for (int i=0; i<5 && StatusCode == 302; i++) + // In this case the local host webserver is successfully listening + // on a port and ready to receive the redirect. + RedirUri.Printf("http://localhost:%i", WebServer.GetPort()); + } + else + { + // Something went wrong with the localhost webserver and we need to + // provide an alternateive way of getting the AuthCode + GString::Array a = d->OAuth.RedirURIs.Split("\n"); + for (unsigned i=0; iOAuth.AuthUri.Get(), + d->OAuth.ClientID.Get(), + RedirUri.Get(), + d->OAuth.Scope.Get()); + LgiExecute(Uri); + + if (UsingLocalhost) + { + // Wait for localhost webserver to receive the response + GString Req = WebServer.GetRequest(); + if (Req) { - GUri u; - GAutoString UriUserName = u.Encode(User); + GXmlTag t; + GString::Array a = Req.Split("\r\n"); + if (a.Length() > 0) + { + GString::Array p = a[0].Split(" "); + if (p.Length() > 1) + { + int Q = p[1].Find("?"); + if (Q >= 0) + { + GString Params = p[1](Q+1, -1); + a = Params.Split("&"); + for (unsigned i=0; iNextCmd++; - p.Print("A%04.4i AUTHENTICATE XOAUTH %s\r\n", AuthCmd, B64.Get()); - GAutoString Cmd(p.NewStr()); - int w = Socket->Write(Cmd, strlen(Cmd)); - Log(Cmd, GSocketI::SocketMsgSend); - if (w > 0) + GString Resp; + Resp.Printf("HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "\n" + "\n" + "\n" + "\n" + "OAuth2Client: %s\n" + "\n", + AuthCode.Str() ? "Received auth code OK" : "Failed to get auth code"); + + WebServer.SetResponse(Resp); + } + } + else + { + // Allow the user to paste the Auth Token in. + GInput Dlg(d->ParentWnd, "", "Enter Authorisation Token:", "IMAP OAuth2 Authentication"); + if (Dlg.DoModal()) + AuthCode = Dlg.Str.Get(); + } + } + + if (ValidStr(AuthCode.Str())) + { + // Now exchange the Auth Token for an Access Token (omg this is so complicated). + Uri = d->OAuth.ApiUri; + GUri u(Uri); + + IHttp Http; + GStringPipe In, Out; + In.Print("code="); + StrFormEncode(In, AuthCode.Str(), true); + In.Print("&redirect_uri="); + StrFormEncode(In, RedirUri, true); + In.Print("&client_id="); + StrFormEncode(In, d->OAuth.ClientID, true); + In.Print("&scope="); + In.Print("&client_secret="); + StrFormEncode(In, d->OAuth.ClientSecret, true); + In.Print("&grant_type=authorization_code"); + + if (d->OAuth.Proxy.Host) + Http.SetProxy( d->OAuth.Proxy.Host, + d->OAuth.Proxy.Port ? d->OAuth.Proxy.Port : HTTP_PORT); + + SslSocket *ssl; + GStringPipe Log; + GAutoPtr Ssl(ssl = new SslSocket(&Log)); + if (Ssl) + { + ssl->SetSslOnConnect(true); + Ssl->SetTimeout(10 * 1000); + if (Http.Open( Ssl, + u.Host, + u.Port ? u.Port : HTTPS_PORT)) + { + int StatusCode = 0; + int ContentLength = (int)In.GetSize(); + char Hdrs[256]; + sprintf_s(Hdrs, sizeof(Hdrs), + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %i\r\n", + ContentLength); + bool Result = Http.Post(Uri, &In, &StatusCode, &Out, NULL, Hdrs); + GAutoString sOut(Out.NewStr()); + GXmlTag t; + if (Result && JsonDecode(t, sOut)) { - LoggedIn = ReadResponse(AuthCmd); + d->OAuth.AccessToken = t.GetAttr("access_token"); + d->OAuth.RefreshToken = t.GetAttr("refresh_token"); + d->OAuth.ExpiresIn = t.GetAsInt("expires_in"); + } + } + } + } + + // Bail if there is no access token + if (!ValidStr(d->OAuth.AccessToken)) + { + sprintf_s(Buf, sizeof(Buf), "Warning: No OAUTH2 Access Token."); + Log(Buf, GSocketI::SocketMsgWarning); + continue; + } + + // Construct the XOAUTH2 parameter + GString s; + s.Printf("user=%s\001auth=Bearer %s\001\001", User, d->OAuth.AccessToken.Get()); + Base64Str(s); + + // Issue the IMAP command + int AuthCmd = d->NextCmd++; + sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE XOAUTH2 %s\r\n", AuthCmd, s.Get()); + if (WriteBuf()) + { + Dialog.DeleteArrays(); + if (Read(NULL)) + { + for (char *l = Dialog.First(); l; l = Dialog.Next()) + { + if (*l == '+') + { + l++; + while (*l && strchr(WhiteSpace, *l)) + l++; + s = l; + UnBase64Str(s); + Log(s, GSocketI::SocketMsgError); + + GXmlTag t; + JsonDecode(t, s); + int StatusCode = t.GetAsInt("status"); + + sprintf_s(Buf, sizeof(Buf), "\r\n"); + WriteBuf(); + + if (StatusCode == 400) + { + // Refresh the token...? + } + } + else if (*l == '*') + { + } + else + { + if (IsResponse(l, AuthCmd, LoggedIn) && + LoggedIn) + { + /* FIXME + if (SettingStore) + { + // Login successful, so persist the AuthCode for next time + SettingStore->SetValue(OPT_ImapOAuth2AuthCode, AuthCode); + } + */ + break; + } } } } } } - */ else { char s[256]; sprintf_s(s, sizeof(s), "Warning: Unsupport auth type '%s'", AuthType); Log(s, GSocketI::SocketMsgWarning); } } if (LoggedIn) { Status = true; // Ask server for it's heirarchy (folder) separator. int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { for (char *Dlg = Dialog.First(); Dlg; Dlg=Dialog.Next()) { GArray t; char *s = Dlg; while (*s) { GAutoString a = ImapBasicTokenize(s); if (a) t.New() = a; else break; } if (t.Length() >= 5 && strcmp(t[0], "*") == 0 && _stricmp(t[1], "list") == 0) { for (unsigned i=2; iFolderSep = *s; break; } } break; } } } } } else { char Str[300]; sprintf_s(Str, sizeof(Str), "Authentication failed, types available:\n%s", (strlen(AuthTypeStr)>0) ? AuthTypeStr : "(none)"); ServerMsg.Reset(NewStr(Str)); } } Auths.DeleteArrays(); } Unlock(); } return Status; } bool MailIMap::Close() { bool Status = false; if (Socket && Lock(_FL)) { if (d->ExpungeOnExit) { ExpungeFolder(); } int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGOUT\r\n", Cmd); if (WriteBuf()) { Status = true; } Unlock(); } return Status; } bool MailIMap::GetCapabilities(GArray &s) { char *k = 0; for (void *p=d->Capability.First(&k); p; p=d->Capability.Next(&k)) { s.Add(k); } return s.Length() > 0; } bool MailIMap::ServerOption(char *Opt) { return d->Capability.Find(Opt) != 0; } char *MailIMap::GetSelectedFolder() { return d->Current; } bool MailIMap::SelectFolder(const char *Path, GHashTbl *Values) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i SELECT \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { DeleteArray(d->Current); ClearDialog(); if (ReadResponse(Cmd)) { Uid.DeleteArrays(); if (Values) { Values->IsCase(false); for (char *Dlg = Dialog.First(); Dlg; Dlg=Dialog.Next()) { GToken t(Dlg, " []"); if (!_stricmp(t[0], "*") && t.Length() > 2) { char *key = t[2]; char *sValue = t[1]; int iValue = atoi(sValue); if (_stricmp(key, "exists") == 0) { Values->Add(key, iValue); } else if (_stricmp(key, "recent") == 0) { Values->Add(key, iValue); } else if (_stricmp(key, "unseen") == 0) { Values->Add(key, iValue); } } } } Status = true; d->Current = NewStr(Path); ClearDialog(); } } Unlock(); } return Status; } int MailIMap::GetMessages(const char *Path) { int Status = 0; if (Socket && Lock(_FL)) { GHashTbl f(0, false, NULL, -1); if (SelectFolder(Path, &f)) { int Exists = f.Find("exists"); if (Exists >= 0) Status = Exists; else LgiTrace("%s:%i - Failed to get 'exists' value.\n", _FL); } Unlock(); } return Status; } int MailIMap::GetMessages() { return GetMessages("INBOX"); } char *MailIMap::SequenceToString(GArray *Seq) { if (!Seq) return NewStr("1:*"); GStringPipe p; int Last = 0; for (unsigned s=0; sLength(); ) { unsigned e = s; while (eLength()-1 && (*Seq)[e] == (*Seq)[e+1]-1) e++; if (s) p.Print(","); if (e == s) p.Print("%i", (*Seq)[s]); else p.Print("%i:%i", (*Seq)[s], (*Seq)[e]); s = e + 1; } return p.NewStr(); } static void RemoveBytes(GArray &a, int &Used, int Bytes) { int Remain = Used - Bytes; if (Remain > 0) memmove(&a[0], &a[Bytes], Remain); Used -= Bytes; } static bool PopLine(GArray &a, int &Used, GAutoString &Line) { for (int i=0; iNextCmd++; - if (sprintf_s(Buf, sizeof(Buf), "A%4.4i %sFETCH %s (%s)\r\n", Cmd, ByUid ? "UID " : "", Seq, Parts) > 0 && - WriteBuf()) + GStringPipe p(256); + p.Print("A%4.4i %sFETCH ", Cmd, ByUid ? "UID " : ""); + p.Write(Seq, strlen(Seq)); + p.Print(" (%s)\r\n", Parts); + GAutoString WrBuf(p.NewStr()); + if (WriteBuf(false, WrBuf)) { ClearDialog(); GArray Buf; Buf.Length(1024); int Used = 0, MsgSize; int64 Start = LgiCurrentTime(); int64 Bytes = 0; bool Done = false; while (!Done && Socket->IsOpen()) { // Extend the buffer if getting used up if (Buf.Length()-Used < 256) { Buf.Length(Buf.Length() + 1024); } // Try and read bytes from server. int r = Socket->Read(&Buf[Used], Buf.Length()-Used); if (r > 0) { if (RawCopy) RawCopy->Write(&Buf[Used], r); Used += r; Bytes += r; #if 0 int64 Time = LgiCurrentTime() - Start; double Rate = (double)Bytes / ((double)Time / 1000.0); LgiTrace("Starting fetch loop used=%ikb (%.0fkb/s)\n", Used>>10, Rate/1024.0); #endif // See if we can parse out a single response GArray Ranges; while ((MsgSize = ParseImapResponse(Buf, Used, Ranges, 2))) { char *b = &Buf[0]; LgiAssert(Ranges.Length() >= 2); // Setup strings for callback char *Param = b + Ranges[0].Start; Param[Ranges[0].Len()] = 0; char *Name = b + Ranges[1].Start; Name[Ranges[1].Len()] = 0; if (_stricmp(Name, "FETCH")) { // Not the response we're looking for, save it in the untagged data. /* Untagged *u = new Untagged; u->Name = Name; u->Param = Param; Untag.Add(u); */ } else { // Process ranges into a hash table GHashTbl Parts; for (unsigned i=2; i 0 && Buf[0] != '*') { GAutoString Line; while (PopLine(Buf, Used, Line)) { GToken t(Line, " \r\n"); if (t.Length() >= 2) { char *r = t[0]; if (*r == 'A') { bool Status = !_stricmp(t[1], "Ok"); int Response = atoi(r + 1); Log(Line, Status ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Response == Cmd) { Done = true; break; } } else Log(&Buf[0], GSocketI::SocketMsgError); } else { LgiAssert(!"What happened?"); Done = true; break; } } } } else break; } } Unlock(); } return Status; } /* Deprecated in favor of ::Fetch bool MailIMap::GetParts(int Message, GStreamI &Out, const char *Parts, char **Flags) { bool Status = false; if (Parts) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i FETCH %i (%s)\r\n", Cmd, Message+1, Parts); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; d->Logging = false; GStringPipe Buf(4096); ///////////////////////////////// // Debug int64 Start = LgiCurrentTime(); ///////////////////////////////// LgiTrace("Starting GetParts ReadResponse msg='%i'\n", Message); bool ReadOk = ReadResponse(Cmd, &Buf); ///////////////////////////////// // Debug char Size[64]; LgiFormatSize(Size, sizeof(Size), Buf.GetSize()); // LgiTrace("ReadResponse time %i for %s\n", (int) (LgiCurrentTime() - Start), Size); ///////////////////////////////// if (ReadOk) { int Chars = 0; int DialogItems = Dialog.Length(); int Pos = 0; char *Response = Buf.NewStr(); if (Response) { // FIXME.. // Log(d, MAIL_RECEIVE_COLOUR); bool Error = false; char *s = Response; int Depth = 0; for (int i=0; i<10 && s[i]; i++, s++); while (!Error && s && *s) { Ws(s); if (*s == '(') { s++; Depth++; } else if (*s == ')') { s++; Depth--; } else { char *Field = LgiTokStr((const char*&)s); if (!Field) break; if (_stricmp(Field, "Flags") == 0) { char *Start = strchr(s, '('); if (Start) { char *End = strchr(++Start, ')'); if (End) { if (Flags) { *Flags = NewStr(Start, End-Start); } s = End + 1; } else Error = true; } else Error = true; } else { char *Start = strchr(s, '{'); if (Start) { char *End = strchr(Start, '}'); if (End) { *End = 0; Chars = atoi(++Start); s = End + 1; Ws(s); int i = 0; for (; i 0) { Ws(s); Start = s; char *End = strchr(Start, ' '); if (End) { Out.Write(s, End - s); s = End; } else Error = true; } } DeleteArray(Field); } } DeleteArray(Response); } Status = Out.GetSize() > 0; char *Last = Dialog.Last(); if (Last) { Log(Last, GSocketI::SocketMsgReceive); } } d->Logging = true; } } return Status; } */ bool IMapHeadersCallback(MailIMap *Imap, char *Msg, GHashTbl &Parts, void *UserData) { char *s = Parts.Find(sRfc822Header); if (s) { Parts.Delete(sRfc822Header); GAutoString *Hdrs = (GAutoString*)UserData; Hdrs->Reset(s); } Parts.DeleteArrays(); return true; } char *MailIMap::GetHeaders(int Message) { GAutoString Text; if (Lock(_FL)) { char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", Message + 1); Fetch( false, Seq, sRfc822Header, IMapHeadersCallback, &Text, NULL); Unlock(); } return Text.Release(); } struct ReceiveCallbackState { MailTransaction *Trans; MailCallbacks *Callbacks; }; static bool IMapReceiveCallback(MailIMap *Imap, char *Msg, GHashTbl &Parts, void *UserData) { ReceiveCallbackState *State = (ReceiveCallbackState*) UserData; char *Flags = Parts.Find("FLAGS"); if (Flags) { State->Trans->Imap.Set(Flags); } char *Hdrs = Parts.Find(sRfc822Header); if (Hdrs) { int Len = strlen(Hdrs); State->Trans->Stream->Write(Hdrs, Len); } char *Body = Parts.Find("BODY[TEXT]"); if (Body) { int Len = strlen(Body); State->Trans->Stream->Write(Body, Len); } State->Trans->Status = Hdrs != NULL || Body != NULL; if (Imap->Items) Imap->Items->Value++; Parts.DeleteArrays(); if (State->Callbacks) { bool Ret = State->Callbacks->OnReceive(State->Trans, State->Callbacks->CallbackData); if (!Ret) return false; } return true; } bool MailIMap::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Lock(_FL)) { int Errors = 0; ReceiveCallbackState State; State.Callbacks = Callbacks; for (unsigned i=0; iStatus = false; char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", State.Trans->Index + 1); Fetch ( false, Seq, "FLAGS RFC822.HEADER BODY[TEXT]", IMapReceiveCallback, &State, NULL ); if (State.Trans->Status) { Status = true; } else if (Errors++ > 5) { // Yeah... not feelin' it Status = false; break; } } Unlock(); } return Status; } bool MailIMap::Append(char *Folder, ImapMailFlags *Flags, char *Msg, GAutoString &NewUid) { bool Status = false; if (Folder && Msg && Lock(_FL)) { GAutoString Flag(Flags ? Flags->Get() : 0); GAutoString Path(EncodePath(Folder)); int Cmd = d->NextCmd++; int Len = 0; for (char *m = Msg; *m; m++) { if (*m == '\n') Len += 2; else if (*m != '\r') Len++; } // Append on the end of the mailbox int c = sprintf_s(Buf, sizeof(Buf), "A%4.4i APPEND \"%s\"", Cmd, Path.Get()); if (Flag) c += sprintf_s(Buf+c, sizeof(Buf)-c, " (%s)", Flag.Get()); c += sprintf_s(Buf+c, sizeof(Buf)-c, " {%i}\r\n", Len); if (WriteBuf() && Read()) { bool GotPlus = false; for (char *Dlg = Dialog.First(); Dlg; Dlg = Dialog.Next()) { if (Dlg[0] == '+') { Dialog.Delete(Dlg); DeleteArray(Dlg); GotPlus = true; break; } } if (GotPlus) { int Wrote = 0; for (char *m = Msg; *m; ) { while (*m == '\r' || *m == '\n') { if (*m == '\n') { Wrote += Socket->Write((char*)"\r\n", 2); } m++; } char *e = m; while (*e && *e != '\r' && *e != '\n') e++; if (e > m) { Wrote += Socket->Write(m, e-m); m = e + (*e != 0); } else break; } LgiAssert(Wrote == Len); Wrote += Socket->Write((char*)"\r\n", 2); // Read response.. ClearDialog(); if ((Status = ReadResponse(Cmd))) { char Tmp[16]; sprintf_s(Tmp, sizeof(Tmp), "A%4.4i", Cmd); for (char *Line = Dialog.First(); Line; Line = Dialog.Next()) { GAutoString c = ImapBasicTokenize(Line); if (!c) break; if (!strcmp(Tmp, c)) { GAutoString a; while ((a = ImapBasicTokenize(Line)).Get()) { GToken t(a, " "); if (t.Length() > 2 && !_stricmp(t[0], "APPENDUID")) { NewUid.Reset(NewStr(t[2])); break; } } } } } } } Unlock(); } return Status; } bool MailIMap::Delete(int Message) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STORE %i FLAGS (\\deleted)\r\n", Cmd, Message+1); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } Unlock(); } return Status; } int MailIMap::Sizeof(int Message) { int Status = 0; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i FETCH %i (%s)\r\n", Cmd, Message+1, sRfc822Size); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char *d = Dialog.First(); if (d) { char *t = strstr(d, sRfc822Size); if (t) { t += strlen(sRfc822Size) + 1; Status = atoi(t); } } } } Unlock(); } return Status; } bool ImapSizeCallback(MailIMap *Imap, char *Msg, GHashTbl &Parts, void *UserData) { GArray *Sizes = (GArray*) UserData; int Index = atoi(Msg); if (Index < 1) return false; char *Sz = Parts.Find(sRfc822Size); if (!Sz) return false; (*Sizes)[Index - 1] = atoi(Sz); return true; } bool MailIMap::GetSizes(GArray &Sizes) { return Fetch(false, "1:*", sRfc822Size, ImapSizeCallback, &Sizes); } bool MailIMap::GetUid(int Message, char *Id, int IdLen) { bool Status = false; if (Lock(_FL)) { if (FillUidList()) { char *s = Uid.ItemAt(Message); if (s && Id) { strcpy_s(Id, IdLen, s); Status = true; } } Unlock(); } return Status; } bool MailIMap::FillUidList() { bool Status = false; if (Socket && Lock(_FL)) { if (Uid.Length() == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i UID SEARCH ALL\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d = Dialog.First(); d && !Status; d=Dialog.Next()) { GToken T(d, " "); if (T[1] && strcmp(T[1], "SEARCH") == 0) { for (unsigned i=2; i &Id) { bool Status = false; if (Lock(_FL)) { if (FillUidList()) { for (char *s=Uid.First(); s; s=Uid.Next()) { Id.Insert(NewStr(s)); } Status = true; } Unlock(); } return Status; } bool MailIMap::GetFolders(List &Folders) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"*\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char Sep[] = { GetFolderSep(), 0 }; for (char *d = Dialog.First(); d; d=Dialog.Next()) { GArray t; char *s; while ((s = LgiTokStr((const char*&)d))) { t[t.Length()].Reset(s); } if (t.Length() >= 5) { if (strcmp(t[0], "*") == 0 && _stricmp(t[1], "LIST") == 0) { char *Folder = t[t.Length()-1]; MailImapFolder *f = new MailImapFolder; if (f) { Folders.Insert(f); f->Sep = Sep[0]; // Check flags f->NoSelect = stristr(t[2], "NoSelect") != 0; f->NoInferiors = stristr(t[2], "NoInferiors") != 0; // LgiTrace("Imap folder '%s' %s\n", Folder, t[2].Get()); // Alloc name if (Folder[0] == '\"') { char *p = TrimStr(Folder, "\""); f->Path = DecodeImapString(p); DeleteArray(p); } else { f->Path = DecodeImapString(Folder); } } } } } Status = true; ClearDialog(); } } Unlock(); } return Status; } bool MailIMap::CreateFolder(MailImapFolder *f) { bool Status = false; char Dir[2] = { d->FolderSep, 0 }; if (f && f->GetPath() && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(f->GetPath()); sprintf_s(Buf, sizeof(Buf), "A%4.4i CREATE \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); if (Status) { char *End = f->Path + strlen(f->Path) - 1; if (*End == GetFolderSep()) { f->NoSelect = true; *End = 0; } else { f->NoInferiors = true; } } } Unlock(); } return Status; } char *MailIMap::EncodePath(const char *Path) { if (!Path) return 0; char Sep = GetFolderSep(); char Native[MAX_PATH], *o = Native, *e = Native + sizeof(Native) - 1; for (const char *i = *Path == '/' ? Path + 1 : Path; *i && o < e; i++) { if (*i == '/') *o++ = Sep; else *o++ = *i; } *o++ = 0; return EncodeImapString(Native); } bool MailIMap::DeleteFolder(char *Path) { bool Status = false; if (Path && Lock(_FL)) { // Close the current folder if required. if (d->Current && _stricmp(Path, d->Current) == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CLOSE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); ReadResponse(Cmd); DeleteArray(d->Current); } } // Delete the folder int Cmd = d->NextCmd++; char *NativePath = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i DELETE \"%s\"\r\n", Cmd, NativePath); DeleteArray(NativePath); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } Unlock(); } return Status; } bool MailIMap::RenameFolder(char *From, char *To) { bool Status = false; if (From && To && Lock(_FL)) { int Cmd = d->NextCmd++; char *f = EncodeImapString(From); char *t = EncodeImapString(To); sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd, f, t); DeleteArray(f); DeleteArray(t); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } Unlock(); } return Status; } bool MailIMap::SetFolderFlags(MailImapFolder *f) { bool Status = false; if (f && Lock(_FL)) { /* int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } */ Unlock(); } return Status; } bool MailIMap::SetFlagsByUid(GArray &Uids, const char *Flags) { bool Status = false; if (Flags && Lock(_FL)) { int Cmd = d->NextCmd++; GStringPipe p; p.Print("A%04.4i UID STORE ", Cmd); if (Uids.Length()) { for (unsigned i=0; i &InUids, char *DestFolder) { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; GAutoString Dest(EncodePath(DestFolder)); GStringPipe p(1024); p.Print("A%04.4i UID COPY ", Cmd); for (unsigned i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i EXPUNGE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } Unlock(); } return Status; } bool MailIMap::Search(bool Uids, GArray &SeqNumbers, const char *Filter) { bool Status = false; if (ValidStr(Filter) && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSEARCH %s\r\n", Cmd, Uids?"UID ":"", Filter); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d = Dialog.First(); d; d = Dialog.Next()) { if (*d != '*') continue; d++; GAutoString s(Tok(d)); if (!s || _stricmp(s, "Search")) continue; while (s.Reset(Tok(d))) { SeqNumbers.New() = s; Status = true; } } } } Unlock(); } return Status; } bool MailIMap::Status(char *Path, int *Recent) { bool Status = false; if (Path && Recent && Lock(_FL)) { GAutoString Dest(EncodePath(Path)); int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STATUS %s (RECENT)\r\n", Cmd, Dest.Get()); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d=Dialog.First(); d; d=Dialog.Next()) { if (*d != '*') continue; d++; GAutoString Cmd = ImapBasicTokenize(d); GAutoString Folder = ImapBasicTokenize(d); GAutoString Fields = ImapBasicTokenize(d); if (Cmd && Folder && Fields && !_stricmp(Cmd, "status") && !_stricmp(Folder, Dest)) { char *f = Fields; GAutoString Field = ImapBasicTokenize(f); GAutoString Value = ImapBasicTokenize(f); if (Field && Value && !_stricmp(Field, "recent")) { *Recent = atoi(Value); Status = true; break; } } } } else LgiTrace("%s:%i - STATUS cmd failed.\n", _FL); } Unlock(); } return Status; } bool MailIMap::Poll(int *Recent, GArray *New) { bool Status = true; if (Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i NOOP\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if ((Status = ReadResponse(Cmd))) { int LocalRecent; if (!Recent) Recent = &LocalRecent; *Recent = 0; for (char *Dlg=Dialog.First(); Dlg; Dlg=Dialog.Next()) { if (Recent && stristr(Dlg, " RECENT")) { *Recent = atoi(Dlg + 2); } } if (*Recent && New) { Search(false, *New, "new"); } } } Unlock(); } return Status; } bool MailIMap::StartIdle() { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i IDLE\r\n", Cmd); Status = WriteBuf(); Unlock(); } return Status; } bool MailIMap::OnIdle(int Timeout, GArray &Resp) { bool Status = false; if (Lock(_FL)) { if (Socket->IsReadable(Timeout)) { Read(); } char *Dlg; while ((Dlg = Dialog.First())) { Dialog.Delete(Dlg); Log(Dlg, GSocketI::SocketMsgReceive); if (Dlg[0] == '*' && Dlg[1] == ' ') { char *s = Dlg + 2; GAutoString a = ImapBasicTokenize(s); GAutoString b = ImapBasicTokenize(s); if (a && b) { Untagged &u = Resp.New(); if (IsDigit(a[0])) { u.Cmd = b; u.Id = atoi(a); if (ValidStr(s)) u.Param.Reset(NewStr(s)); } else { u.Param.Reset(NewStr(Dlg+2)); } Status = true; } } DeleteArray(Dlg); } Unlock(); } return Status; } bool MailIMap::FinishIdle() { bool Status = false; if (Lock(_FL)) { if (WriteBuf(false, "DONE\r\n")) Status = ReadResponse(); Unlock(); } return Status; } diff --git a/src/common/Lgi/GStream.cpp b/src/common/Lgi/GStream.cpp --- a/src/common/Lgi/GStream.cpp +++ b/src/common/Lgi/GStream.cpp @@ -1,232 +1,224 @@ #include #include #include #include "Lgi.h" #include "GStream.h" - -#if defined(WIN32) -p_vscprintf lgi_vscprintf = 0; -#endif +#include "GStringClass.h" ////////////////////////////////////////////////////////////////////// -int GStreamPrintf(GStreamI *Stream, int Flags, const char *Format, va_list &Arg) +int LgiPrintf(GString &Str, const char *Format, va_list &Arg) { - int Chars = 0; + int Bytes = 0; - if (Stream) + if (Format) { #ifndef WIN32 + va_list Cpy; + va_copy(Cpy, Arg); + Bytes = vsnprintf(0, 0, Format, Cpy); + #else + Bytes = _vscprintf(Format, Arg); + #endif - int Size = vsnprintf(0, 0, Format, Arg); - if (Size > 0) - { - GAutoString Buffer(new char[Size+1]); - if (Buffer) - { - vsprintf(Buffer, Format, Arg); - Stream->Write(Buffer, Size, Flags); - } + if (Bytes > 0 && Str.Set(NULL, Bytes)) + vsprintf(Str.Get(), Format, Arg); } - #else + return Bytes; +} - if (lgi_vscprintf) - { - // WinXP and up... - Chars = lgi_vscprintf(Format, Arg); - if (Chars > 0) - { - GAutoString Buf(new char[Chars+1]); - if (Buf) +int LgiPrintf(GAutoString &Str, const char *Format, va_list &Arg) +{ + int Bytes = 0; + + if (Format) { - vsprintf(Buf, Format, Arg); - Stream->Write(Buf, Chars, Flags); - } - } - } - else - { - // Win2k or earlier, no _vscprintf for us... - // so we'll just have to bump the buffer size - // until it fits; - for (int Size = 256; Size <= (256 << 10); Size <<= 2) - { - GAutoString Buf(new char[Size]); - if (!Buf) - break; + #ifndef WIN32 + va_list Cpy; + va_copy(Cpy, Arg); + Bytes = vsnprintf(0, 0, Format, Cpy); + #else + Bytes = _vscprintf(Format, Arg); + #endif - int c = vsnprintf(Buf, Size, Format, Arg); - if (c > 0) - { - Stream->Write(Buf, Chars = c, Flags); - break; - } - } - } - #endif + if (Bytes > 0 && Str.Reset(new char[Bytes+1])) + vsprintf(Str, Format, Arg); } - return Chars; + return Bytes; +} + +int GStreamPrintf(GStreamI *Stream, int Flags, const char *Format, va_list &Arg) +{ + if (!Stream || !Format) + return 0; + + GAutoString a; + int Bytes = LgiPrintf(a, Format, Arg); + if (!a || Bytes == 0) + return 0; + + return Stream->Write(a, Bytes, Flags); } int GStreamPrint(GStreamI *s, const char *fmt, ...) { va_list Arg; va_start(Arg, fmt); int Ch = GStreamPrintf(s, 0, fmt, Arg); va_end(Arg); return Ch; } int GStream::Print(const char *Format, ...) { va_list Arg; va_start(Arg, Format); int Ch = GStreamPrintf(this, 0, Format, Arg); va_end(Arg); return Ch; } ///////////////////////////////////////////////////////////////// GStreamer::GStreamer(int BufSize) { StartTime = 0; EndTime = 0; Total = 0; Size = min(BufSize, 256); Buf = new char[Size]; } GStreamer::~GStreamer() { DeleteArray(Buf); } int GStreamer::GetRate() { return (Total * GetElapsedTime()) / 1000; } int GStreamer::GetTotal() { return Total; } int GStreamer::GetElapsedTime() { if (EndTime) { return EndTime - StartTime; } return LgiCurrentTime() - StartTime; } //////////////////////////////////////////////////////////////// // Stateful parser that matches the start of lines to the 'prefix' GLinePrefix::GLinePrefix(const char *p, bool eol) { Start = true; At = 0; Pos = 0; Eol = eol; Prefix = NewStr(p); } GLinePrefix::~GLinePrefix() { DeleteArray(Prefix); } void GLinePrefix::Reset() { Start = true; At = 0; Pos = 0; } int GLinePrefix::IsEnd(void *v, int Len) { if (Prefix) { char *s = (char*)v; for (int i=0; iRead(Buf, sizeof(Buf))) > 0) { if (End) { e = End->IsEnd(Buf, r); } if ((w = Dest->Write(Buf, e >= 0 ? min(e, r) : r)) > 0) { Bytes += w; } else break; } else break; } } return Bytes; }