diff --git a/HtmlEditor/Rich.cpp b/HtmlEditor/Rich.cpp --- a/HtmlEditor/Rich.cpp +++ b/HtmlEditor/Rich.cpp @@ -1,117 +1,117 @@ #include "Lgi.h" #include "GHtmlEdit.h" #include "GTextView3.h" enum Ctrls { IDC_EDITOR = 100, IDC_HTML, }; #if 1 char Src[] = "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "--
\n" "Matthew Allen\n"; #else char Src[] = "\n" "\n" " First test. This is bold. This is a test. This is a test. This is a test.\n" " \n" " bbbbb bbbbbb bbbbbbb bbbbbbb bbbbbbb bbbbbb. This is a test. This is a test.\n" " This is a test. This is a test. This is a test. This is a test. This is a test.\n" "\n" ""; #endif class App : public GWindow { GHtmlEdit *Edit; GSplitter *Split; GTextView3 *Txt; public: App() { Edit = 0; Txt = 0; Name("Rich Text Testbed"); GRect r(0, 0, 1200, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); if (Attach(0)) { Split = new GSplitter; if (Split) { Split->Value(GetClient().X()/2); Split->Attach(this); Edit = new GHtmlEdit; if (Edit) { Edit->SetId(IDC_EDITOR); Split->SetViewA(Edit, false); #if 1 Edit->Name(Src); #else char *s = ReadTextFile("C:\\Documents and Settings\\matthew\\Desktop\\paypal.html"); Edit->Name(s); DeleteArray(s); #endif } Txt = new GTextView3(IDC_HTML, 0, 0, 100, 100); if (Txt) { Split->SetViewB(Txt, true); } } Pour(); Visible(true); } } int OnNotify(GViewI *c, int f) { - if (c->GetId() == IDC_EDITOR AND f == GTVN_DOC_CHANGED) + if (c->GetId() == IDC_EDITOR AND f == GNotifyDocChanged) { if (Txt AND Edit) { Txt->Name(Edit->Name()); } } return 0; } }; int LgiMain(OsAppArguments &AppArgs) { GApp a("app", AppArgs); a.AppWnd = new App; a.Run(); return 0; } diff --git a/include/common/LgiCommon.h b/include/common/LgiCommon.h --- a/include/common/LgiCommon.h +++ b/include/common/LgiCommon.h @@ -1,394 +1,403 @@ /** \file \author Matthew Allen \date 27/3/2000 \brief Common LGI include Copyright (C) 2000-2004, Matthew Allen */ /** * \defgroup Base Foundation tools * \ingroup Lgi */ /** * \defgroup Text Text handling * \ingroup Lgi */ #ifndef _LGI_COMMON_H #define _LGI_COMMON_H #if defined LINUX #include #endif #include "GMem.h" #include "GArray.h" #include "LgiClass.h" +#include "GStringClass.h" + +// C++ function definitions... + +/// Removes escaping from the string +LgiClass GString LgiUnEscapeString(const char *Chars, const char *In, int Len = -1); + +/// Escapes all characters in 'In' specified by the set 'Chars' +LgiClass GString LgiEscapeString(const char *Chars, const char *In, int Len = -1); #ifdef __cplusplus extern "C" { #endif ///////////////////////////////////////////////////////////// // Externs // Codepages /// Converts a buffer of text to a different charset /// \ingroup Text LgiFunc int LgiBufConvertCp(void *Out, const char *OutCp, int OutLen, const void *&In, const char *InCp, int &InLen); /// \brief Converts a string to a new charset /// \return A dynamically allocate, null terminated string in the new charset /// \ingroup Text LgiFunc void *LgiNewConvertCp ( /// Output charset const char *OutCp, /// Input buffer const void *In, /// The input data's charset const char *InCp, /// Bytes of valid data in the input int InLen = -1 ); /// Converts a utf-8 string into a wide character string /// \ingroup Text LgiFunc char16 *LgiNewUtf8To16(const char *In, int InLen = -1); /// Converts a wide character string into a utf-8 string /// \ingroup Text LgiFunc char *LgiNewUtf16To8 ( /// Input string const char16 *In, /// Number of bytes in the input or -1 for NULL terminated int InLen = -1 ); /// Return true if Lgi support the charset /// \ingroup Text LgiFunc bool LgiIsCpImplemented(const char *Cp); /// Converts the ANSI code page to a charset name /// \ingroup Text LgiFunc const char *LgiAnsiToLgiCp(int AnsiCodePage = -1); /// Calculate the byte length of a string /// \ingroup Text LgiFunc int LgiByteLen(const void *Str, const char *Cp); /// Calculate the number of characters in a string /// \ingroup Text LgiFunc int LgiCharLen(const void *Str, const char *Cp, int Bytes = -1); /// Move a pointer along a utf-8 string by characters /// \ingroup Text LgiFunc char *LgiSeekUtf8 ( /// Pointer to the current character const char *Ptr, /// The number of characters to move forward or back int D, /// The start of the memory buffer if you known char *Start = 0 ); /// Return true if the string is valid utf-8 /// \ingroup Text LgiFunc bool LgiIsUtf8(const char *s, int len = -1); /// Converts a string to the native 8bit charset of the OS from utf-8 /// \ingroup Text LgiFunc char *LgiToNativeCp(const char *In, int InLen = -1); /// Converts a string from the native 8bit charset of the OS to utf-8 /// \ingroup Text LgiFunc char *LgiFromNativeCp(const char *In, int InLen = -1); /// Returns the next token in a string, leaving the argument pointing to the end of the token /// \ingroup Text LgiFunc char *LgiTokStr(const char *&s); /// Formats a data size into appropriate units /// \ingroup Base LgiFunc void LgiFormatSize ( /// Output string char *Str, /// Output string buffer length int SLen, /// Input size in bytes uint64 Size ); /// Converts a string from URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LgiDecodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); /// Converts a string to URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LgiEncodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); // Path /// Gets the path and file name of the currently running executable /// \ingroup Base LgiFunc bool LgiGetExeFile(char *Dst, int DstSize); /// Gets the path of the currently running executable /// \ingroup Base LgiFunc bool LgiGetExePath(char *Dst, int DstSize); /// Gets the path of the temporary file directory /// \ingroup Base LgiFunc bool LgiGetTempPath(char *Dst, int DstSize); /// Returns the system path specified /// \ingroup Base LgiFunc bool LgiGetSystemPath ( /// Which path to retreive LgiSystemPath Which, /// The buffer to receive the path into char *Dst, /// The size of the receive buffer in bytes int DstSize ); /// Finds a file in the applications directory or nearby /// \ingroup Base LgiFunc char *LgiFindFile(const char *Name); /// Returns 0 to end search /// \ingroup Base typedef bool (*RecursiveFileSearch_Callback)(void *UserData, char *Path, class GDirectory *Dir); /// \brief Recursively search for files /// \return Non zero if something was found /// \ingroup Base LgiFunc bool LgiRecursiveFileSearch ( /// Start search in this dir const char *Root, /// Extensions to match GArray *Ext = NULL, /// [optional] Output filenames GArray *Files = NULL, /// [optional] Output total size uint64 *Size = NULL, /// [optional] File count uint64 *Count = NULL, /// [optional] Callback for match RecursiveFileSearch_Callback Callback = NULL, /// [options] Callback user data void *UserData = NULL ); // Resources /// Gets the currently selected language /// \ingroup Resources LgiFunc struct GLanguage *LgiGetLanguageId(); /// Loads a string from the resource file /// \ingroup Resources LgiFunc const char *LgiLoadString(int Res, const char *Default = 0); // Os version functions /// Gets the current operating system and optionally it's version. /// \returns One of the defines starting with #LGI_OS_UNKNOWN in LgiDefs.h /// \ingroup Base LgiFunc int LgiGetOs(GArray *Ver = 0); /// Gets the current operation systems name. /// \ingroup Base LgiFunc const char *LgiGetOsName(); // System /// \brief Opens a file or directory. /// /// If the input is an executable then it is run. If the input file /// is a document then an appropriate application is found to open the /// file and the file is passed to that application. If the input is /// a directory then the OS's file manager is openned to browse the /// directory. /// /// \ingroup Base LgiFunc bool LgiExecute ( /// The file to open const char *File, /// The arguments to pass to the program const char *Arguments="", /// The directory to run in const char *Dir = 0, /// An error message GAutoString *ErrorMsg = NULL ); /// Initializes the random number generator /// \ingroup Base LgiFunc void LgiRandomize(uint Seed); /// Returns a random number between 0 and Max-1 /// \ingroup Base LgiFunc uint LgiRand(uint Max = 0); LgiFunc bool _lgi_read_colour_config(const char *Tag, uint32 *c); /// Plays a sound /// \ingroup Base LgiFunc bool LgiPlaySound ( /// File name of the sound to play const char *FileName, /// 0 or SND_ASYNC. If 0 the function blocks till the sound finishes. int Flags ); /** * \defgroup Mime Mime handling support. * \ingroup Lgi */ /// Returns the file extensions associated with the mimetype /// \ingroup Mime LgiFunc bool LgiGetMimeTypeExtensions ( /// The returned mime type const char *Mime, /// The extensions GArray &Ext ); /// Returns the mime type of the file /// \ingroup Mime LgiFunc bool LgiGetFileMimeType ( /// File to file type of const char *File, /// Pointer to buffer to receive mime-type char *MimeType, /// Buffer length int BufLen ); /// Returns the application associated with the mime type /// \ingroup Mime LgiFunc bool LgiGetAppForMimeType ( /// Type of the file to find and app for const char *Mime, /// Path to the executable of the app that can handle the file type. char *AppPath, /// Size of the 'AppPath' buffer int BufSize ); /// Returns the all applications that can open a given mime type. /// \ingroup Mime LgiFunc bool LgiGetAppsForMimeType ( /// The type of files to match apps to. /// /// Two special cases exist: /// - application/email gets the default email client /// - application/browser get the default web browser const char *Mime, /// The applications that can handle the GArray &Apps, /// Limit the length of the results, i.e. stop looking after 'Limit' matches. /// -1 means return all matches. int Limit = -1 ); /// Gets the current clock in milli-seconds. (1,000th of a second) /// \ingroup Time LgiFunc uint64 LgiCurrentTime(); /// Get the current clock in micro-seconds (1,000,000th of a second) LgiFunc uint64 LgiMicroTime(); // Debug /// Returns true if the build is for release. /// \ingroup Base LgiFunc int LgiIsReleaseBuild(); #if defined WIN32 /// Registers an active x control LgiFunc bool RegisterActiveXControl(const char *Dll); enum HWBRK_TYPE { HWBRK_TYPE_CODE, HWBRK_TYPE_READWRITE, HWBRK_TYPE_WRITE, }; enum HWBRK_SIZE { HWBRK_SIZE_1, HWBRK_SIZE_2, HWBRK_SIZE_4, HWBRK_SIZE_8, }; /// Set a hardware breakpoint. LgiFunc HANDLE SetHardwareBreakpoint ( /// Use GetCurrentThread() HANDLE hThread, /// Type of breakpoint HWBRK_TYPE Type, /// Size of breakpoint HWBRK_SIZE Size, /// The pointer to the data to break on void *s ); /// Deletes a hardware breakpoint LgiFunc bool RemoveHardwareBreakpoint(HANDLE hBrk); #elif defined LINUX /// Window managers enum WindowManager { WM_Unknown, WM_Kde, WM_Gnome }; /// Returns the currently running window manager WindowManager LgiGetWindowManager(); #endif #ifdef __cplusplus } #endif #endif diff --git a/readme.txt b/readme.txt --- a/readme.txt +++ b/readme.txt @@ -1,125 +1,105 @@ Lightweight GUI Library ----------------------- The primary aim of LGI is to abstract away the differences between -operating system's and provide a consistant API that applications +operating system's and provide a consistent API that applications can target. As a secondary goal the library should strive to be compact for easy distribution with an application without bloating out the download. Also the API is designed to be as simple as possible for the programmer to use, without sacrificing functionality on the various platforms supported. The library is not intended to be shared between applications -as there is too much change in the API and object size to warrent a +as there is too much change in the API and object size to warrant a shared library approach. In the future this may change if compatibility can be improved. Compiling LGI ------------- - Open '[Lgi]/include/common/Lgi.h' and check through any compile time + Open '[lgi]/include/common/Lgi.h' and check through any compile time options there. You may want to switch things on or off to get it to compile. Win32: - Load Lgi/Lgi.dsp into Visual C++ and build it. + Load [lgi]/Lgi_vc9.sln into Visual C++ 2008 and build it. Linux: - make -f Lgi/Makefile.linux + make -f [lgi]/Makefile.linux -or- - make -f Lgi/LgiIde/Makefile.linux (builds both Lgi and the IDE) + make -f [lgi]/LgiIde/Makefile.linux (builds both Lgi and the IDE) Cygwin: - make -f Lgi/Makefile.win32 + make -f [lgi]/Makefile.win32 -or- - make -f Lgi/LgiIde/Makefile.win32 (builds both Lgi and the IDE) + make -f [lgi]/LgiIde/Makefile.win32 (builds both Lgi and the IDE) Mac: - Open Lgi.xcode in XCode and run the build command. + Open [lgi]/src/mac/carbon/Lgi.xcode in XCode and run the build command. Add build folders so the OS can find the shared libraries: * For Windows add these to your path: - - Lgi/Debug - - Lgi/Release - - Lgi/Gel/Debug - - Lgi/Gel/Release + - [lgi]/lib * For Cygwin add these to your path: - - Lgi/DebugX - - Lgi/ReleaseX - - Lgi/Gel/DebugX - - Lgi/Gel/ReleaseX + - [lgi]/DebugX + - [lgi]/ReleaseX * On Linux, create symlinks in /usr/lib to the files: - - Lgi/DebugX/liblgid.so - - Lgi/ReleaseX/liblgi.so - - Lgi/Gel/DebugX/liblgiskind.so - - Lgi/Gel/ReleaseX/liblgiskin.so + - [lgi]/DebugX/liblgid.so + - [lgi]/ReleaseX/liblgi.so Building Your App ----------------- - Win32: Add the project Lgi/Lgi.dsp to your workspace. Then in your + Win32: Add the project [lgi]/Lgi_vc9.vxproj to your workspace. Then in your new project you'll need to set these settings: * C/C++ tab - C++ Language - RTTI on. - Code Generation - [Debug] Run time library: Debug Multithreaded DLL - [Release] Run time library: Multithreaded DLL - Preprocessor - Define: LGI_STATIC (if your using LgiStatic version) - Include path: [Lgi]/include/common - Include path: [Lgi]/include/win32 * Link tab - Object/library modules: - Add 'imm32.lib' (if you use GTextView3.cpp) Linux/Cygwin: * Add to your library string: - [Debug] '-L[Lgi]/DebugX -llgid' - [Release] '-L[Lgi]/ReleaseX -llgi' * Add to your compile flags: - '-i[Lgi]/include/common -i[Lgi]/include/linux/X' Mac: * Include the lgi.framework into your app * Add the these folders to your include path: - [Lgi]/include/common - [Lgi]/include/mac Usage ----- In most cases just: #include "Lgi.h" But you can also include "Gdc2.h" for just graphics support without all the GUI stuff. - To utilise the built in memory debugging features, encase all C++ memory - allocation with these macros: - - Obj *o = NEW(Obj(a, b, c)); // instead of "new Obj(a, b, c);" - DeleteObj(o); // instead of "delete o;" - - And for arrays: - - Obj *o = NEW(Obj[10]); - DeleteArray(o); - - The List class has these built in: - - List o; - o.DeleteObjects(); // or o.DeleteArrays(); etc... - The naming conventions for container methods are: - "Delete(...)" removes from the container and frees the object. - "Remove(...)" just removes from the container. Documentation ------------- - See: [Lgi]/docs/html/index.html. + See: [Lgi]/docs/html/index.html. + + Which you may have to generate with doxygen if you are building from the + repository. diff --git a/src/common/INet/Mail.cpp b/src/common/INet/Mail.cpp --- a/src/common/INet/Mail.cpp +++ b/src/common/INet/Mail.cpp @@ -1,4106 +1,4091 @@ /*hdr ** FILE: Mail.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Mail app ** ** Copyright (C) 1998, Matthew Allen ** fret@ozemail.com.au */ #include #include #include #include #include "Lgi.h" #include "Mail.h" #include "GToken.h" #include "Base64.h" #include "INetTools.h" #include "GDateTime.h" #include "GDocView.h" ////////////////////////////////////////////////////////////////////////////////////////////////// LogEntry::LogEntry(const char *t, int len, COLOUR col) { c = col; Text = 0; if (t) { 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--; #if 0 // Debug weird characters in log file. GStringPipe p(256); for (char *s = t; *s; s++) { if (IsAlpha(*s) || IsDigit(*s)) { p.Write(s, 1); } else { p.Print("%%%.2x", *s); } } Text = p.NewStr(); #else Text = NewStr(t, len); #endif } } LogEntry::~LogEntry() { DeleteArray(Text); } ////////////////////////////////////////////////////////////////////////////////////////////////// // return true if there are any characters with the 0x80 bit set bool Is8Bit(char *Text) { if (!Text) return false; while (*Text) { if (*Text & 0x80) return true; Text++; } return false; } // 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; } char ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } // 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 EndChar = *e++; while (*e && *e != EndChar) e++; } if (strchr(Delim, *e)) { break; } } int Len = e - s; if (Len > 0) { char *Temp = new char[Len+1]; if (Temp) { memcpy(Temp, s, Len); Temp[Len] = 0; Output.Insert(Temp); } } s = e; for (; *s && strchr(Delim, *s); s++); } } } //////////////////////////////////////////////////////////////////////////////// char *DecodeBase64Str(char *Str, int Len) { if (Str) { int B64Len = (Len < 0) ? strlen(Str) : Len; int BinLen = BufferLen_64ToBin(B64Len); char *s = new char[BinLen+1]; if (s) { int Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); s[Converted] = 0; DeleteArray(Str); Str = s; } } return Str; } char *DecodeQuotedPrintableStr(char *Str, int Len) { if (Str) { if (Len < 0) Len = strlen(Str); uchar *s = new uchar[Len+1]; if (s) { char *Out = (char*) s; char *Text = Str; for (int i=0; i='a'&&(c)<='z')?(c)-'a'+'A':(c) ) #endif char *DecodeRfc2047(char *Str) { if (Str && strstr(Str, "=?")) { GMemQueue Temp; char *s = Str; while (*s) { // is there a word remaining bool Encoded = false; char *p = strstr(s, "=?"); char *First = 0; char *Second = 0; char *End = 0; char *Cp = 0; if (p) { char *CpStart = p + 2; First = strchr(CpStart, '?'); if (First) { Cp = NewStr(CpStart, First-CpStart); Second = strchr(First+1, '?'); if (Second) { End = strstr(Second+1, "?="); Encoded = End != 0; } } } if (Encoded) { int Type = CONTENT_NONE; bool StripUnderscores = false; if (ToUpper(First[1]) == 'B') { // Base64 encoding Type = CONTENT_BASE64; } else if (ToUpper(First[1]) == 'Q') { // Quoted printable Type = CONTENT_QUOTED_PRINTABLE; StripUnderscores = true; } if (Type != CONTENT_NONE) { char *Block = NewStr(Second+1, End-Second-1); if (Block) { switch (Type) { case CONTENT_BASE64: Block = DecodeBase64Str(Block); break; case CONTENT_QUOTED_PRINTABLE: Block = DecodeQuotedPrintableStr(Block); break; } int Len = strlen(Block); if (StripUnderscores) { for (int i=0; i *CharsetPrefs, int LineLength) { if (!CodePage) { CodePage = "utf-8"; } GStringPipe p(256); if (!Str) return NULL; if (Is8Bit(Str)) { // pick an encoding bool Base64 = false; const char *DestCp = "utf-8"; int Len = strlen(Str);; if (_stricmp(CodePage, "utf-8") == 0) { DestCp = LgiDetectCharset(Str, Len, CharsetPrefs); } int Chars = 0; for (int i=0; i 0.4)) { Base64 = true; } char *Buf = (char*)LgiNewConvertCp(DestCp, Str, CodePage, Len); if (Buf) { // encode the word char Prefix[64]; int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); p.Write(Prefix, Ch); LineLength += Ch; if (Base64) { // Base64 int InLen = strlen(Buf); int EstBytes = BufferLen_BinTo64(InLen); char Temp[512]; int Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); p.Push(Temp, Bytes); } else { // Quoted printable for (char *w = Buf; *w; w++) { if (*w == ' ') { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write((char*)"_", 1); LineLength++; } else if (*w & 0x80 || *w == '_' || *w == '?' || *w == '=') { if (LineLength > MIME_MAX_LINE - 5) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } char Temp[16]; Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); p.Write(Temp, Ch); LineLength += Ch; } else { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write(w, 1); LineLength++; } } } p.Push("?="); DeleteArray(Buf); } DeleteArray(Str); Str = p.NewStr(); } else { bool RecodeNewLines = false; for (char *s = Str; *s; s++) { if (*s == '\n' && (s == Str || s[-1] != '\r')) { RecodeNewLines = true; break; } } if (RecodeNewLines) { for (char *s = Str; *s; s++) { if (*s == '\r') ; else if (*s == '\n') p.Write("\r\n", 2); else p.Write(s, 1); } DeleteArray(Str); Str = p.NewStr(); } } return Str; } ////////////////////////////////////////////////////////////////////////////// void DeNullText(char *in, int &len) { char *out = in; char *end = in + len; while (in < end) { if (*in) { *out++ = *in; } else { len--; } in++; } } ////////////////////////////////////////////////////////////////////////////// bool IsValidEmail(GAutoString &Email) { // Local part char buf[321]; char *o = buf; char *e = Email; if (!Email || *e == '.') return false; #define OutputChar() \ if (o - buf >= sizeof(buf) - 1) \ return false; \ *o++ = *e++ // Local part while (*e) { if (strchr("!#$%&\'*+-/=?^_`.{|}~", *e) || IsAlpha((uchar)*e) || IsDigit((uchar)*e)) { OutputChar(); } else if (*e == '\"') { // Quoted string OutputChar(); bool quote = false; while (*e && !quote) { quote = *e == '\"'; OutputChar(); } } else if (*e == '\\') { // Quoted character e++; if (*e < ' ' || *e >= 0x7f) return false; OutputChar(); } else if (*e == '@') { break; } else { // Illegal character return false; } } // Process the '@' if (*e != '@' || o - buf > 64) return false; OutputChar(); // Domain part... if (*e == '[') { // IP addr OutputChar(); // Initial char must by a number if (!IsDigit(*e)) return false; // Check the rest... char *Start = e; while (*e) { if (IsDigit(*e) || *e == '.') { OutputChar(); } else { return false; } } // Not a valid IP if (e - Start > 15) return false; if (*e != ']') return false; OutputChar(); } else { // Hostname, check initial char if (!IsAlpha(*e) && !IsDigit(*e)) return false; // Check the rest. while (*e) { if (IsAlpha(*e) || IsDigit(*e) || strchr(".-", *e)) { OutputChar(); } else { return false; } } } // Remove any trailing dot/dash while (strchr(".-", o[-1])) o--; // Output *o = 0; LgiAssert(o - buf <= sizeof(buf)); if (strcmp(Email, buf)) Email.Reset(NewStr(buf, o - buf)); return true; } struct MailAddrPart { GAutoString Part; bool Brackets; bool ValidEmail; typedef char CharPair[2]; GAutoString RemovePairs(char *Str, int Len, CharPair *Pairs) { char *s = Str; if (Len < 0) Len = strlen(s); while (*s && strchr(WhiteSpace, *s)) { s++; Len--; } if (!*s) return GAutoString(); // 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 GAutoString(); return GAutoString(NewStr(s, Len)); } MailAddrPart(char *s, int len) { ValidEmail = false; Brackets = false; if (s) { static CharPair Pairs[] = { {'<', '>'}, {'(', ')'}, {'\'', '\''}, {'\"', '\"'}, {0, 0}, }; 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(GAutoPtr *a, GAutoPtr *b) { return (*b)->Score() - (*a)->Score(); } void DecodeAddrName(char *Start, GAutoString &Name, GAutoString &Addr, char *DefaultDomain) { /* Testing code char *Input[] = { - "\"Sound&Secure@speedytechnical.com\" ", + "\"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 ", - " Australian Bankers' Association (ABA)", + "Bankers' Association (ABA)", + "'Amy's Mum' ", 0 }; GAutoString Name, Addr; for (char **i = Input; *i; i++) { + Name.Reset(); + Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); - LgiTrace("N=%-24s A=%-24s\n", Name, Addr); + LgiTrace("N=%-#32s A=%-32s\n", Name, Addr); } */ if (!Start) return; GArray< GAutoPtr > Parts; for (char *c = Start; *c; ) { if (strchr(WhiteSpace, *c)) { // skip whitespace } + /* This was removed becuase it works better for cases of mismatched single quotes. + e.g. 'Amy's Mum' else if (strchr("\"'", *c)) { // string delim char End = *c++; char *s = c; for (; *c && *c != End; c++) { if (*c == '\\') c++; } Parts.New().Reset(new MailAddrPart(s, c - s)); } - else if (strchr("<", *c)) + */ + else if (strchr("<(", *c)) { // brackets char Delim = (*c == '<') ? '>' : ')'; char *End = strchr(c + 1, Delim); if (End) { End++; } else { End = c + 1; while (*End && *End != ' ') End++; } Parts.New().Reset(new MailAddrPart(c, End - c)); c = End - 1; } else { // Some string char *s = c; - for (; *c && !strchr("<\"", *c); c++); + for (; *c && !strchr("<(", *c); c++); LgiAssert(c - s > 0); Parts.New().Reset(new MailAddrPart(s, c - s)); continue; } if (*c) c++; else break; } // Process the email address if (!Parts.Length()) return; // Look for the highest scoring part... that'll be the email address. int MaxScore = -1; uint32 i; for (i=0; iValidEmail && + p->ValidEmail && ( MaxScore < 0 || p->Score() > Parts[MaxScore]->Score() ) ) { MaxScore = i; } } if (MaxScore >= 0) { Addr = Parts[MaxScore]->Part; Parts.DeleteAt(MaxScore, true); } - /* - if (!Addr) - { - // This code checks through the strings again for - // something bracketed by <> which would normally - // be the address, even if it's not formatted correctly - // Scribe uses this to store group names in the email - // address part of an address descriptor. - for (i=0; iBrackets && !strchr(s, '@')) - { - Addr = RemovePairs(Str[i], Pairs); - DeleteArray(Str[i]); - Str.DeleteAt(i, true); - } - } - } - */ - // Process the remaining parts into the name GStringPipe n(256); for (i=0; iPart, *e; for (e = Name->Part; e && *e; ) { if (*e == '\\') { n.Write(s, e - s); s = ++e; } else e++; } n.Write(s, e - s); } } Name.Reset(n.NewStr()); if (Name) { char *In = Name; char *Out = Name; while (*In) { if (!(*In == '\\' && *In == '\"')) { *Out++ = *In; } In++; } *Out = 0; } } 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(GStreamI *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(GMutex *l) { Lock = l; } GMutex *FileDescriptor::GetLock() { return Lock; } GStreamI *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 GMemStream(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(AddressDescriptor *Copy) { Data = 0; Status = Copy ? Copy->Status : false; CC = Copy ? Copy->CC : false; Addr = Copy ? NewStr(Copy->Addr) : 0; Name = Copy ? NewStr(Copy->Name) : 0; } AddressDescriptor::~AddressDescriptor() { _Delete(); } void AddressDescriptor::_Delete() { Data = 0; Status = false; CC = 0; DeleteArray(Name); DeleteArray(Addr); } void AddressDescriptor::Print(char *Str, int Len) { if (!Str) { LgiAssert(0); return; } if (Addr && Name) { sprintf_s(Str, Len, "%s (%s)", Addr, Name); } else if (Addr) { strcpy_s(Str, Len, Addr); } else if (Name) { sprintf_s(Str, Len, "(%s)", Name); } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailProtocol::MailProtocol() { Buffer[0] = 0; Logger = 0; Items = 0; Transfer = 0; ProgramName = 0; DefaultDomain = 0; ExtraOutgoingHeaders = 0; } MailProtocol::~MailProtocol() { CharsetPrefs.DeleteArrays(); DeleteArray(ExtraOutgoingHeaders); } void MailProtocol::Log(const char *Str, GSocketI::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, GSocketI::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, GSocketI::SocketMsgSend); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// #define VERIFY_RET_VAL(Arg) \ { \ if (!Arg) \ { \ GMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ return NULL; \ } \ } #define VERIFY_ONERR(Arg) \ { \ if (!Arg) \ { \ GMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ goto CleanUp; \ } \ } MailSmtp::MailSmtp() { ProgramName = 0; } MailSmtp::~MailSmtp() { } bool MailSmtp::Open(GSocketI *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; } GAutoString 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, GSocketI::SocketMsgInfo); if (!Socket->Open(Server, Port)) Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port); else { GStringPipe 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; GHashTbl TheirAuthTypes; // Look through the response for the auth line char *Response = Str.NewStr(); if (Response) { GToken Lines(Response, "\n"); for (uint32 i=0; iSetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? } } if (ValidStr(UserName) && ValidStr(Password)) { GHashTbl MyAuthTypes(16); MyAuthTypes.Add("PLAIN", true); MyAuthTypes.Add("LOGIN", true); if (TheirAuthTypes.Length() == 0) { // No auth types? huh? if (TestFlag(Flags, MAIL_USE_AUTH)) { if (TestFlag(Flags, MAIL_USE_PLAIN)) { // Force plain type TheirAuthTypes.Add("PLAIN", true); } else if (TestFlag(Flags, MAIL_USE_LOGIN)) { // Force login type TheirAuthTypes.Add("LOGIN", true); } else { // Oh well, we'll just try all types const char *a; for (bool b=MyAuthTypes.First(&a); b; b=MyAuthTypes.Next(&a)) { TheirAuthTypes.Add(a, true); } } } else { NoAuthTypes = true; } } // Try all their auth types against our internally support types if (TheirAuthTypes.Find("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")) { 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; } } } if (!Authed && TheirAuthTypes.Find("PLAIN")) { char Tmp[256]; ZeroObj(Tmp); int ch = 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", UserName) + 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", Password) + 1; char B64[256]; ZeroObj(B64); ConvertBinaryToBase64(B64, sizeof(B64), (uint8*)Tmp, ch); sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", B64); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } if (!Authed) { if (NoAuthTypes) { ServerMsg.Reset(NewStr(LgiLoadString(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports."))); } else { int ch = sprintf_s( Buffer, sizeof(Buffer), LgiLoadString( L_ERROR_ESMTP_UNSUPPORTED_AUTHS, "The server doesn't support any compatible authentication types:\n\t")); const char *a = 0; for (bool p = TheirAuthTypes.First(&a); p; p = TheirAuthTypes.Next(&a)) { ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "%s ", a); } ServerMsg.Reset(NewStr(Buffer)); } } Status = Authed; } else { /* NormalLogin: // Normal SMTP login // send HELO message sprintf_s(Buffer, sizeof(Buffer), "HELO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250")); if (Flags & MAIL_SOURCE_STARTTLS) { if (SupportsStartTLS && TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy(Buffer, "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("220", &Str)); GVariant v; if (Socket->SetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? } } } */ 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) { GMemQueue Temp; const char *Start = Str; while (*Str) { if (*Str == '\n') { // send a string int 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 int 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; } char *StripChars(char *Str, const char *Chars = "\r\n") { if (Str) { char *i = Str; char *o = Str; while (*i) { if (strchr(Chars, *i)) i++; else *o++ = *i++; } *o++ = 0; } return Str; } char *CreateAddressTag(List &l, int Type, List *CharsetPrefs) { char *Result = 0; List Addr; AddressDescriptor *a; for (a = l.First(); a; a = l.Next()) { if (a->CC == Type) { Addr.Insert(a); } } if (Addr.Length() > 0) { GStringPipe StrBuf; StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: "); for (a = Addr.First(); a; ) { AddressDescriptor *NextA = Addr.Next(); char Buffer[256] = ""; StripChars(a->Name); StripChars(a->Addr); if (a->Addr && strchr(a->Addr, ',')) { // Multiple address format GToken t(a->Addr, ","); for (uint32 i=0; i", t[i]); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->Name) { // Name and addr char *Mem = 0; char *Name = a->Name; if (Is8Bit(Name)) { Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs); } if (strchr(Name, '\"')) sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->Addr); else sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->Addr); DeleteArray(Mem); } else if (a->Addr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->Addr); } 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 GStringPipe { GSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(GSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } int Read(void *Ptr, int Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LgiCurrentTime(); p->Range = (int)Size; return Size; } return -1; } int Write(const void *InPtr, int Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { int 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.First() && From) { // send MAIL message if (From && ValidStr(From->Addr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->Addr); } else { ServerMsg.Reset(NewStr("No 'from' address in email.")); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.Start(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { char *Addr = ValidStr(a->Addr) ? a->Addr : a->Name; if (ValidStr(Addr)) { GToken Parts(Addr, ","); for (unsigned p=0; p\r\n", Parts[p]); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk &= a->Status != 0; // at least one address is ok } } else { LgiTrace("%s:%i - Send Addr wasn't valid\n", _FL); } } } return AddrOk; } GStringPipe *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); } GStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : 0; } bool MailSmtp::SendEnd(GStringPipe *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) { GStringPipe *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")); GMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, GStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { int Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { int 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->Msg = NewStr(Sp ? Sp + 1 : Start); } // Log Log(Start, atoi(Start) >= 400 ? GSocketI::SocketMsgError : GSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, GSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { ServerMsg.Reset(NewStr(Buffer)); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public GStringPipe { char File[256]; GFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LgiRand()); LgiMakePath(File, sizeof(File), Path, n); } while (FileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (AddressDescriptor *a=To.First(); a; a=To.Next()) { a->Status = true; GToken Addrs(a->Addr, ","); for (unsigned n=0; n", Addrs[n]); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } int Read(void *Buffer, int Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } int Write(const void *Buffer, int 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(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return DirExists(d->Path); } bool MailSendFolder::Close() { return true; } GStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(GStringPipe *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 (MailItem *m = Mail.First(); m; m = Mail.Next()) { 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(GSocketI *S, char *RemoteHost, int Port, char *User, char *Password, char *&Cookie, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!DirExists(d->Path)) return false; GDirectory 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; } int MailReceiveFolder::GetMessages() { return d->Mail.Length(); } bool MailReceiveFolder::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GCopyStreamer 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)LgiFileSize(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); int Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(List &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.Insert(NewStr(Uid)); } else { Id.DeleteArrays(); Status = false; break; } } return Status; } char *MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GStringPipe o; GCopyStreamer c; GLinePrefix e("", false); if (c.Copy(&i, &o, &e)) { return o.NewStr(); } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } int 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 LgiAssert(!"No socket to get message count."); } CleanUp: if (Messages == 0) { int asd=0; } 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) { int Pos = 0; ZeroObj(Buffer); do { int 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) ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { ServerMsg.Reset(NewStr(Buffer)); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, GHashTbl &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; int 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; GToken t(Buffer, "\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()) { bool NoAPOP = Cookie ? stristr(Cookie, "NoAPOP") != NULL : 0; if (!NoAPOP) { 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*) LgiNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LgiNewConvertCp("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()); GVariant v; if (Socket->SetValue(GSocket_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(Cookie); DeleteArray(Apop); Cookie = NewStr("NoAPOP"); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { GHashTbl 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 *******", GSocketI::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(char *Ptr, int Len) { for (char *c = Ptr; Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; GStreamI *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)); GLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LgiCurrentTime(); } // Read status line ZeroObj(Buffer); int Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { int 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, GSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination int Len = strlen(Eol); int 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, GSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { int r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } int EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { int Actual = EndPos - (int)DataPos - 3; if (Actual > 0) { int w = Msg->Write(Buffer, Actual); LgiAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { int w = Msg->Write(Buffer, r); LgiAssert(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(GArray &Sizes) { if (Socket) { strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); char *s = 0; if (ReadMultiLineReply(s)) { GToken l(s, "\r\n"); DeleteArray(s); for (unsigned i=0; i 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(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(List &Id) { bool Status = false; if (Socket) { char *Str = 0; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadMultiLineReply(Str)); if (Str) { Status = true; GToken T(Str, "\r\n"); for (unsigned i=0; iRead(Buffer, sizeof(Buffer), 0); if (ReadLen > 0 && Buffer[0] == '+') { // positive response char *Eol = strchr(Buffer, '\n'); if (Eol) { char *Ptr = Eol + 1; ReadLen -= Ptr-Buffer; memmove(Buffer, Ptr, ReadLen); Temp.Write((uchar*) Buffer, ReadLen); while (!MailIsEnd(Buffer, ReadLen)) { ReadLen = Socket->Read(Buffer, sizeof(Buffer), 0); if (ReadLen > 0) { Temp.Write((uchar*) Buffer, ReadLen); } else break; } int Len = (int)Temp.GetSize(); Str = new char[Len+1]; if (Str) { Temp.Read((uchar*)Str, Len); Str[Len] = 0; Status = true; } } } } return Status; } 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; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailMessage::MailMessage() { Private = 0; From = 0; Reply = 0; InternetHeader = 0; Priority = MAIL_PRIORITY_NORMAL; Text = 0; TextCharset = 0; Html = 0; HtmlCharset = 0; MarkColour = -1; DispositionNotificationTo = false; Raw = 0; } MailMessage::~MailMessage() { Empty(); DeleteObj(From); DeleteObj(Reply); DeleteObj(Raw); } void MailMessage::Empty() { Subject.Reset(); MessageID.Reset(); References.Reset(); FwdMsgId.Reset(); BounceMsgId.Reset(); DeleteArray(InternetHeader); DeleteArray(Text); DeleteArray(TextCharset); DeleteArray(Html); DeleteArray(HtmlCharset); To.DeleteObjects(); FileDesc.DeleteObjects(); } char *MailMessage::GetBody() { return Text; } char *MailMessage::GetBodyCharset() { return TextCharset; } bool MailMessage::SetBodyCharset(const char *Cs) { DeleteArray(TextCharset); TextCharset = NewStr(Cs); return true; } bool MailMessage::SetBody(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Text) { DeleteArray(Text); Text = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Text) return false; } if (Cs != TextCharset) { DeleteArray(TextCharset); if (!(TextCharset = NewStr(Cs))) return false; } return true; } char *MailMessage::GetHtml() { return Html; } char *MailMessage::GetHtmlCharset() { return HtmlCharset; } bool MailMessage::SetHtmlCharset(const char *Cs) { DeleteArray(HtmlCharset); HtmlCharset = NewStr(Cs); return true; } bool MailMessage::SetHtml(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Html) { DeleteArray(Html); Html = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Html) return false; } if (Cs != HtmlCharset) { DeleteArray(HtmlCharset); if (!(HtmlCharset = NewStr(Cs))) return false; } return true; } int MailMessage::EncodeBase64(GStreamI &Out, GStreamI &In) { int64 Start = LgiCurrentTime(); int Status = 0; int BufSize = 4 << 10; char *InBuf = new char[BufSize]; char *OutBuf = new char[BufSize]; if (InBuf && OutBuf) { int InLen = (int)In.GetSize(); // file remaining to read int InUsed = 0; int InDone = 0; int OutUsed = 0; do { if (InUsed - InDone < 256 && InLen > 0) { // Move any bit left over down to the start memmove(InBuf, InBuf + InDone, InUsed - InDone); InUsed -= InDone; InDone = 0; // Read in as much data as we can int Max = min(BufSize-InUsed, InLen); int r = In.Read(InBuf + InUsed, Max); if (r <= 0) break; // FilePos += r; InUsed += r; InLen -= r; } if (OutUsed > BufSize - 256) { int w = Out.Write(OutBuf, OutUsed); if (w > 0) { OutUsed = 0; Status += w; } else { break; } } int OutLen = ConvertBinaryToBase64( OutBuf + OutUsed, 76, (uchar*)InBuf + InDone, InUsed - InDone); int In = OutLen * 3 / 4; InDone += In; OutUsed += OutLen; OutBuf[OutUsed++] = '\r'; OutBuf[OutUsed++] = '\n'; } while (InDone < InUsed); if (OutUsed > 0) { int w = Out.Write(OutBuf, OutUsed); if (w >= 0) Status += w; w = Out.Write((char*)"\r\n", 2); if (w >= 0) Status += w; } #if 0 double Sec = (double)((int64)LgiCurrentTime() - Start) / 1000.0; double Kb = (double)FileDes->Sizeof() / 1024.0; LgiTrace("rate: %ikb/s\n", (int)(Kb / Sec)); #endif } else { LgiTrace("%s:%i - Error allocating buffers\n", _FL); } DeleteArray(InBuf); DeleteArray(OutBuf); return Status; } int MailMessage::EncodeQuotedPrintable(GStreamI &Out, GStreamI &In) { int Status = 0; char OutBuf[100], InBuf[1024]; int ch = 0; int InLen; // Read the input data one chunk at a time while ((InLen = In.Read(InBuf, sizeof(InBuf))) > 0) { // For all the input bytes we just got for (char *s = InBuf; s - InBuf < InLen; ) { if (*s == '\n') { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } else if (*s == '.') { // If the '.' character happens to fall at the // end of a paragraph and gets pushed onto the next line it // forms the magic \r\n.\r\n sequence that ends an SMTP data // session. Which is bad. The solution taken here is to // hex encode it if it falls at the start of the line. // Otherwise allow it through unencoded. if (ch == 0) { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else { OutBuf[ch++] = *s; } } else if (*s & 0x80 || *s == '=') { // Require hex encoding of 8-bit chars and the equals itself. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else if (*s != '\r') { OutBuf[ch++] = *s; } s++; if (ch > 73) { // time for a new line. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } } } ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w > 0) Status += w; return Status; } int MailMessage::EncodeText(GStreamI &Out, GStreamI &In) { int Status = 0; char InBuf[4096]; int InLen, InUsed = 0; const char *Eol = "\r\n"; while ((InLen = In.Read(InBuf+InUsed, sizeof(InBuf)-InUsed)) > 0) { InUsed += InLen; char *s; for (s = InBuf; s - InBuf < InUsed; ) { // Do we have a complete line? int RemainingBytes = InUsed - (s - InBuf); char *NewLine = strnchr(s, '\n', RemainingBytes); if (NewLine) { // Yes... write that out. int Len = NewLine - s; if (Len > 0 && s[Len-1] == '\r') Len--; if (Len == 1 && s[0] == '.') { // this removes the sequence ".\n" // which is the END OF MAIL in the SMTP protocol. int w = Out.Write((char*)". ", 2); if (w <= 0) break; Status += w; } else if (Len) { int w = Out.Write(s, Len); if (w <= 0) break; Status += w; } int w = Out.Write(Eol, 2); if (w <= 0) break; s = NewLine + 1; Status += w; } else { // No... move the data down to the start of the buffer memmove(InBuf, s, RemainingBytes); InUsed = RemainingBytes; s = 0; break; } } if (s) InUsed -= s - InBuf; } if (InUsed) { int w = Out.Write(InBuf, InUsed); if (w > 0) Status += w; w = Out.Write(Eol, 2); if (w > 0) Status += w; } return Status; } #define SEND_BUF_SIZE (16 << 10) class SendBuf : public GStream { GStreamI *Out; int Used; uchar Buf[SEND_BUF_SIZE]; public: bool Status; SendBuf(GStreamI *out) { Out = out; Used = 0; Status = true; } ~SendBuf() { Flush(); } int64 GetSize() { return Used; } int64 SetSize(int64 Size) { return Out->SetSize(Size); } int Read(void *Buffer, int Size, int Flags = 0) { return -1; } void Flush() { if (Used > 0) { int w = Out->Write(Buf, Used, 0); if (w < Used) { Status = false; } Used = 0; } } int Write(const void *Buffer, int Size, int Flags = 0) { int64 w = 0; uchar *Ptr = (uchar*)Buffer; while (Ptr && Size > 0) { if (Size + Used >= SEND_BUF_SIZE) { int Chunk = SEND_BUF_SIZE - Used; memcpy(Buf + Used, Ptr, Chunk); int s = Out->Write(Buf, SEND_BUF_SIZE, 0); if (s < SEND_BUF_SIZE) { return -1; break; } Ptr += Chunk; Size -= Chunk; w += Chunk; Used = 0; } else { memcpy(Buf + Used, Ptr, Size); Used += Size; w += Size; Size = 0; } } return (int)w; } }; // Encode the whole email bool MailMessage::Encode(GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime) { GStringPipe p; bool Status = EncodeHeaders(p, Protocol, Mime); if (Status) { int Len = (int)p.GetSize(); char *Headers = p.NewStr(); if (HeadersSink) { HeadersSink->Write(Headers, Len); } else { InternetHeader = NewStr(Headers); } if (Headers && Out.Write(Headers, Len)) { SendBuf *Buf = new SendBuf(&Out); if (Buf) { Status = EncodeBody(*Buf, Protocol, Mime); if (Status) { Buf->Flush(); Status = Buf->Status; if (!Status) { LgiTrace("%s:%i - Buffer status failed.\n", _FL); } } else { LgiTrace("%s:%i - EncodeBody failed.\n", _FL); } DeleteObj(Buf); } } else { LgiTrace("%s:%i - Headers output failed.\n", _FL); } DeleteArray(Headers); } else { LgiTrace("%s:%i - EncodeHeaders failed.\n", _FL); } return Status; } // This encodes the main headers but not the headers relating to the // actual content. Thats done by the ::EncodeBody function. bool MailMessage::EncodeHeaders(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; // Setup char Buffer[1025]; // Construct date const char *Weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *Month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; GDateTime Dt; int TimeZone = Dt.SystemTimeZone(); Dt.SetNow(); sprintf_s(Buffer, sizeof(Buffer), "Date: %s, %i %s %i %i:%2.2i:%2.2i %s%2.2d%2.2d\r\n", Weekday[Dt.DayOfWeek()], Dt.Day(), Month[Dt.Month()-1], Dt.Year(), Dt.Hours(), Dt.Minutes(), Dt.Seconds(), (TimeZone >= 0) ? "+" : "", TimeZone / 60, abs(TimeZone) % 60); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; if (Protocol && Protocol->ProgramName) { // X-Mailer: sprintf_s(Buffer, sizeof(Buffer), "X-Mailer: %s\r\n", Protocol->ProgramName); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } if (Protocol && Protocol->ExtraOutgoingHeaders) { for (char *s=Protocol->ExtraOutgoingHeaders; s && *s; ) { char *e = s; while (*e && *e != '\r' && *e != '\n') e++; int l = e-s; if (l > 0) { Status &= Out.Write(s, l) > 0; Status &= Out.Write((char*)"\r\n", 2) > 0; } while (*e && (*e == '\r' || *e == '\n')) e++; s = e; } } if (Priority != MAIL_PRIORITY_NORMAL) { // X-Priority: sprintf_s(Buffer, sizeof(Buffer), "X-Priority: %i\r\n", Priority); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } if (MarkColour >= 0) { // X-Color (HTML Colour Ref for email marking) sprintf_s(Buffer, sizeof(Buffer), "X-Color: #%2.2X%2.2X%2.2X\r\n", R24(MarkColour), G24(MarkColour), B24(MarkColour)); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } // Message-ID: if (MessageID) { for (char *m=MessageID; *m; m++) { if (*m <= ' ') { printf("%s:%i - Bad message ID '%s'\n", _FL, MessageID.Get()); return false; } } int Ch = sprintf_s(Buffer, sizeof(Buffer), "Message-ID: %s\r\n", MessageID.Get()); Status &= Out.Write(Buffer, Ch) > 0; } // References: if (ValidStr(References)) { char *Dir = strrchr(References, '/'); GAutoString a; char *Ref = 0; if (Dir) { GUri u; a = u.Decode(Dir + 1); Ref = a; } else Ref = References; sprintf_s(Buffer, sizeof(Buffer), "References: <%s>\r\n", Ref); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } // To: char *ToAddr = CreateAddressTag(To, 0, &Protocol->CharsetPrefs); if (ToAddr) { Status &= Out.Write(ToAddr, strlen(ToAddr)) > 0; DeleteArray(ToAddr); } char *CcAddr = CreateAddressTag(To, 1, &Protocol->CharsetPrefs); if (CcAddr) { Status &= Out.Write(CcAddr, strlen(CcAddr)) > 0; DeleteArray(CcAddr); } // From: if (From && From->Addr) { int ch = sprintf_s(Buffer, sizeof(Buffer), "From: "); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "'%s' ", Nme); else ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "\"%s\" ", Nme); DeleteArray(Nme); } ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "<%s>\r\n", From->Addr); } else { LgiTrace("%s:%i - No from address.\n", _FL); return false; } Status &= Out.Write(Buffer, strlen(Buffer)) > 0; // Reply-To: if (Reply && ValidStr(Reply->Addr)) { int ch = sprintf_s(Buffer, sizeof(Buffer), "Reply-To: "); char *Nme = EncodeRfc2047(NewStr(Reply->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "'%s' ", Nme); else ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "\"%s\" ", Nme); DeleteArray(Nme); } ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, "<%s>\r\n", Reply->Addr); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } // Subject: char *Subj = EncodeRfc2047(NewStr(Subject), 0, &Protocol->CharsetPrefs, 9); sprintf_s(Buffer, sizeof(Buffer), "Subject: %s\r\n", (Subj) ? Subj : ""); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; DeleteArray(Subj); // DispositionNotificationTo if (DispositionNotificationTo) { int ch = sprintf_s(Buffer, sizeof(Buffer), "Disposition-Notification-To:"); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, " \"%s\"", Nme); DeleteArray(Nme); } ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, " <%s>\r\n", From->Addr); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } return Status; } bool MailMessage::EncodeBody(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; char Buffer[1025]; if (Mime) { bool MultiPart = ((Text ? 1 : 0) + (Html ? 1 : 0) + FileDesc.Length()) > 1; bool MultipartAlternate = ValidStr(Text) && ValidStr(Html); bool MultipartMixed = FileDesc.Length() > 0; uint64 Now = LgiCurrentTime(); char Separator[256]; sprintf_s(Separator, sizeof(Separator), "----=_NextPart_%8.8X.%8.8X", (uint32)Now, (unsigned)LgiGetCurrentThread()); sprintf_s(Buffer, sizeof(Buffer), "MIME-Version: 1.0\r\n"); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; char StrMultipartAlternative[] = "multipart/alternative"; if (MultiPart) { char *Type = MultipartMixed ? (char*)"multipart/mixed" : StrMultipartAlternative; sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", Type, Separator); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } if (ValidStr(Text) || ValidStr(Html)) { char AlternateBoundry[128] = ""; if (MultiPart) { sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", Separator); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; if (MultipartMixed && MultipartAlternate) { sprintf_s(AlternateBoundry, sizeof(AlternateBoundry), "----=_NextPart_%8.8X.%8.8X", (uint32)++Now, (uint32)LgiGetCurrentThread()); sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", StrMultipartAlternative, AlternateBoundry); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); if (ValidStr(Text)) { const char *Cs = 0; char *Txt = Text, *Mem = 0; // Detect charset if (!TextCharset || _stricmp(TextCharset, "utf-8") == 0) { Cs = LgiDetectCharset(Text, -1, Protocol ? &Protocol->CharsetPrefs : 0); if (Cs) { Mem = Txt = (char*)LgiNewConvertCp(Cs, Text, "utf-8", -1); } } if (!Cs) Cs = TextCharset; // Content type sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", Cs ? Cs : "us-ascii"); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); // Transfer encoding if (Txt && (Is8Bit(Txt) || MaxLineLen(Txt) >= 80)) { char QuotPrint[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(QuotPrint, strlen(QuotPrint)) > 0; if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeQuotedPrintable(Out, TxtStr) > 0; if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); } else { char Cte[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Cte, strlen(Cte)) > 0; if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeText(Out, TxtStr) > 0; if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); } DeleteArray(Mem); } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); // Break alternate part if (AlternateBoundry[0]) { sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } else if (MultipartAlternate) { sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } if (ValidStr(Html)) { // Content type sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/html; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; // Transfer encoding if (Is8Bit(Html) || MaxLineLen(Html) >= 80) { char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeQuotedPrintable(Out, HtmlStr) > 0; } else { char Sb[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Sb, strlen(Sb)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeText(Out, HtmlStr) > 0; } } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); if (AlternateBoundry[0]) { // End alternate part sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s--\r\n", AlternateBoundry); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); int SizeEst = 1024; FileDescriptor *FileDes = FileDesc.First(); for (; FileDes; FileDes=FileDesc.Next()) { SizeEst += FileDes->Sizeof() * 4 / 3; } Out.SetSize(SizeEst); FileDes = FileDesc.First(); while (FileDes) { GStreamI *F = FileDes->GotoObject(); // write a MIME segment for this attachment if (MultiPart) { sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } char FileName[256]; char *s = FileDes->Name(), *d = FileName; if (!s) LgiTrace("%s:%i - File descriptor has no name.\n", _FL); else { while (*s) { if (*s != '\\') { *d++ = *s++; } else { *d++ = '\\'; *d++ = '\\'; s++; } } *d = 0; char *FName = EncodeRfc2047(NewStr(FileName), 0, &Protocol->CharsetPrefs); sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s; name=\"%s\"\r\n" "Content-Disposition: attachment\r\n", FileDes->GetMimeType() ? FileDes->GetMimeType() : "application/x-zip-compressed", (FName) ? FName : FileName); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; DeleteArray(FName); if (FileDes->GetContentId()) { sprintf_s(Buffer, sizeof(Buffer), "Content-Id: %s\r\n", FileDes->GetContentId()); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } sprintf_s(Buffer, sizeof(Buffer), "Content-Transfer-Encoding: base64\r\n\r\n"); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; Status &= F ? EncodeBase64(Out, *F) > 0 : false; } FileDes = FileDesc.Next(); } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); if (MultiPart) { // write final separator sprintf_s(Buffer, sizeof(Buffer), "--%s--\r\n\r\n", Separator); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; } } else { // send content type sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; if (Is8Bit(Text)) { // send the encoding and a blank line char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeQuotedPrintable(Out, TextStr) > 0; } else { // send a blank line Status &= Out.Write((char*)"\r\n", 2) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeText(Out, TextStr) > 0; } } if (!Status) LgiTrace("%s:%i - Status=%i\n", _FL, Status); return true; } diff --git a/src/common/Text/GString.cpp b/src/common/Text/GString.cpp --- a/src/common/Text/GString.cpp +++ b/src/common/Text/GString.cpp @@ -1,989 +1,1049 @@ #include #include #include #include "GMem.h" #include "LgiOsDefs.h" #include "GString.h" #include "GContainers.h" #include "LgiCommon.h" char WhiteSpace[] = " \t\r\n"; #if !defined(_WINDOWS) char *strncpy_s(char *dest, size_t dest_size, const char *src, size_t src_size) { if (dest && src) { char *end = dest + dest_size - 1; while (dest < end && *src && src_size-- > 0) { *dest++ = *src++; } *dest++ = 0; } return dest; } char *strcpy_s(char *dest, size_t dest_size, const char *src) { if (!dest || !src) return NULL; char *Start = dest; // Copy the string while (*src && dest_size > 1) { *dest++ = *src++; dest_size--; } // NULL terminate if (dest_size > 0) { *dest++ = 0; dest_size--; } // Assert if we ran out of buffer. if (*src != 0) LgiAssert(!"Buffer too small"); return Start; } char *strcat_s(char *dest, size_t dest_size, const char *src) { if (!dest || !src) return NULL; char *Start = dest; // Scan over the existing string while (*dest && dest_size > 0) { dest++; dest_size--; } // Write as much as we can while (*src && dest_size > 1) { *dest++ = *src++; dest_size--; } // NULL terminate... if (dest_size > 0) { *dest++ = NULL; dest_size--; } // Assert if we ran out of buffer. if (*src != 0) LgiAssert(!"Buffer too small"); return Start; } #endif // 8 Bit char *strnchr(const char *s, char c, NativeInt Len) { if (s && Len >= 0) { for (size_t i=0; i= SLen) { int i; for (i=0; i= SLen) { int i; for (i=0; a[i] && i 0) { for (Cmp = 0; i-- && Cmp == 0; ) { Cmp += tolower(*a) - tolower(*b); if (!*a || !*b) break; a++; b++; } } return Cmp; } char *stristr(const char *a, const char *b) { if (a && b) { while (a && *a) { if (tolower(*a) == tolower(*b)) { int i; for (i=0; a[i] && tolower(a[i]) == tolower(b[i]); i++) ; if (b[i] == 0) return (char*)a; } a++; } } return NULL; } int strcmp(char *a, char *b) { int c = -1; if (a && b) { c = 0; while (!c) { c = *a - *b; if (!*a || !*b) break; a++; b++; } } return c; } #ifndef WIN32 int stricmp(const char *a, const char *b) { int c = -1; if (a && b) { c = 0; while (!c) { c = tolower(*a) - tolower(*b); if (!*a || !*b) break; a++; b++; } } return c; } char *strupr(char *a) { for (char *s = a; s && *s; s++) { *s = toupper(*s); } return a; } char *strlwr(char *a) { for (char *s = a; s && *s; s++) { *s = tolower(*s); } return a; } #endif char *TrimStr(const char *s, const char *Delim) { if (s) { const char *Start = s; while (*Start && strchr(Delim, *Start)) { Start++; } size_t StartLen = strlen(Start); if (StartLen > 0) { const char *End = Start + strlen(Start) - 1; while (*End && End > Start && strchr(Delim, *End)) { End--; } if (*Start) { size_t Len = (End - Start) + 1; char *n = new char[Len+1]; if (n) { memcpy(n, Start, Len); n[Len] = 0; return n; } } } } return NULL; } bool ValidStr(const char *s) { if (s) { while (*s) { if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n' && ((uchar)*s) != 0xa0) { return true; } s++; } } return false; } char *NewStr(const char *s, NativeInt Len) { if (s) { if (Len < 0) Len = strlen(s); int Bytes = Len + 1; char *Ret = new char[LGI_ALLOC_ALIGN(Bytes)]; if (Ret) { if (Len > 0) memcpy(Ret, s, Len); Ret[Len] = 0; return Ret; } } return NULL; } #ifdef BEOS #define toupper(c) ( ((c)>='a' && (c)<='z') ? (c)-'a'+'A' : (c) ) #define tolower(c) ( ((c)>='A' && (c)<='Z') ? (c)-'A'+'a' : (c) ) #endif bool MatchStr(const char *Template, const char *Data) { if (!Template) { return false; } if (_stricmp(Template, (char*)"*") == 0) { // matches anything return true; } if (!Data) { return false; } while (*Template && *Data) { if (*Template == '*') { Template++; if (*Template) { const char *EndA; for (EndA = Template; *EndA && *EndA!='?' && *EndA!='*'; EndA++); size_t SegLen = EndA - Template; char *Seg = NewStr(Template, SegLen); if (!Seg) return false; // find end of non match while (*Data) { if (_strnicmp(Data, Seg, SegLen) == 0) { break; } Data++; } DeleteArray(Seg); if (*Data) { Template += SegLen; Data += SegLen; } else { // can't find any matching part in Data return false; } } else { // '*' matches everything return true; } } else if (*Template == '?') { Template++; Data++; } else { if (tolower(*Template) != tolower(*Data)) { return false; } Template++; Data++; } } return ((*Template == 0) || (_stricmp(Template, (char*)"*") == 0)) && (*Data == 0); } int htoi(const char *a) { int Status = 0; for (; a && *a; a++) { if (*a >= '0' && *a <= '9') { Status <<= 4; Status |= *a - '0'; } else if (*a >= 'a' && *a <= 'f') { Status <<= 4; Status |= *a - 'a' + 10; } else if (*a >= 'A' && *a <= 'F') { Status <<= 4; Status |= *a - 'A' + 10; } else break; } return Status; } int64 htoi64(const char *a) { int64 Status = 0; for (; a && *a; a++) { if (*a >= '0' && *a <= '9') { Status <<= 4; Status |= *a - '0'; } else if (*a >= 'a' && *a <= 'f') { Status <<= 4; Status |= *a - 'a' + 10; } else if (*a >= 'A' && *a <= 'F') { Status <<= 4; Status |= *a - 'A' + 10; } else break; } return Status; } ////////////////////////////////////////////////////////////////////////// // UTF-16 #define CompareDefault (a && b ? *a - *b : -1) char16 *StrchrW(const char16 *s, char16 c) { if (s) { while (*s) { if (*s == c) return (char16*) s; s++; } } return 0; } char16 *StrnchrW(char16 *s, char16 c, int Len) { if (s) { while (*s && Len > 0) { if (*s == c) return s; s++; Len--; } } return 0; } char16 *StrrchrW(char16 *s, char16 c) { char16 *Last = 0; while (s && *s) { if (*s == c) Last = s; s++; } return Last; } char16 *StrstrW(char16 *a, const char16 *b) { if (a && b) { int Len = StrlenW(b); for (char16 *s=a; *s; s++) { if (*s == *b) { // check match if (StrncmpW(s+1, b+1, Len-1) == 0) return s; } } } return 0; } char16 *StristrW(char16 *a, const char16 *b) { if (a && b) { int Len = StrlenW(b); for (char16 *s=a; *s; s++) { if (tolower(*s) == tolower(*b)) { // check match if (StrnicmpW(s+1, b+1, Len-1) == 0) return s; } } } return 0; } char16 *StrnstrW(char16 *a, const char16 *b, int n) { if (a && b) { int Len = StrlenW(b); for (char16 *s=a; n >= Len && *s; s++, n--) { if (*s == *b) { // check match if (StrncmpW(s+1, b+1, Len-1) == 0) return s; } } } return 0; } char16 *StrnistrW(char16 *a, const char16 *b, int n) { if (a && b) { int Len = StrlenW(b); for (char16 *s=a; n >= Len && *s; s++, n--) { if (*s == *b) { // check match if (StrnicmpW(s+1, b+1, Len-1) == 0) return s; } } } return 0; } int StrcmpW(const char16 *a, const char16 *b) { if (a && b) { while (true) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; } return 0; } return -1; } int StricmpW(const char16 *a, const char16 *b) { if (a && b) { while (true) { char16 A = tolower(*a); char16 B = tolower(*b); if (!A || !B || A != B) return A - B; a++; b++; } return 0; } return -1; } int StrncmpW(const char16 *a, const char16 *b, int n) { if (a && b) { while (n > 0) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; n--; } return 0; } return -1; } int StrnicmpW(const char16 *a, const char16 *b, int n) { if (a && b) { while (n > 0) { char16 A = tolower(*a); char16 B = tolower(*b); if (!A || !B || A != B) return A - B; a++; b++; n--; } return 0; } return -1; } char16 *StrcpyW(char16 *a, const char16 *b) { if (a && b) { do { *a++ = *b++; } while (*b); *a++ = 0; } return a; } char16 *StrncpyW(char16 *a, const char16 *b, int n) { if (a && b && n > 0) { while (*b && --n > 0) { *a++ = *b++; } *a++ = 0; // always NULL terminate } return a; } void StrcatW(char16 *a, const char16 *b) { if (a && b) { // Seek to end of string while (*a) { a++; } // Append 'b' while (*b) { *a++ = *b++; } *a++ = 0; } } int StrlenW(const char16 *a) { if (!a) return 0; int i = 0; while (*a++) { i++; } return i; } int AtoiW(const char16 *a) { int i = 0; while (a && *a >= '0' && *a <= '9') { i *= 10; i += *a - '0'; a++; } return i; } int HtoiW(const char16 *a) { int i = 0; if (a) { while (*a) { if (*a >= '0' && *a <= '9') { i <<= 4; i |= *a - '0'; } else if (*a >= 'a' && *a <= 'f') { i <<= 4; i |= *a - 'a' + 10; } else if (*a >= 'A' && *a <= 'F') { i <<= 4; i |= *a - 'A' + 10; } else break; } } return i; } char16 *TrimStrW(const char16 *s, const char16 *Delim) { if (!Delim) { static char16 Def[] = {' ', '\r', '\n', '\t', 0}; Delim = Def; } if (s) { // Leading delim while (StrchrW(Delim, *s)) { s++; } // Trailing delim int i = StrlenW(s); while (i > 0 && StrchrW(Delim, s[i-1])) { i--; } return NewStrW(s, i); } return 0; } bool MatchStrW(const char16 *a, const char16 *b) { LgiAssert(0); return 0; } char16 *NewStrW(const char16 *Str, int l) { char16 *s = 0; if (Str) { if (l < 0) l = StrlenW(Str); s = new char16[l+1]; if (s) { memcpy(s, Str, l * sizeof(char16)); s[l] = 0; } } return s; } bool ValidStrW(const char16 *s) { if (s) { for (const char16 *c=s; *c; c++) { if (*c != ' ' && *c != '\t' && *c != 0xfeff && *c != 0xfffe) return true; } } return false; } char *LgiDecodeUri(const char *uri, int len) { GStringPipe p; if (uri) { const char *end = len >= 0 ? uri + len : 0; for (const char *s=uri; s && *s; ) { int Len; const char *e = s; for (Len = 0; *e && *e != '%' && (!end || e < end); e++) Len++; p.Push(s, Len); if ((!end || e < end) && *e) { e++; if (e[0] && e[1]) { char h[3] = { e[0], e[1], 0 }; char c = htoi(h); p.Push(&c, 1); e += 2; s = e; } else break; } else { break; } } } return p.NewStr(); } char *LgiEncodeUri(const char *uri, int len) { GStringPipe p; if (uri) { const char *end = len >= 0 ? uri + len : 0; for (const char *s=uri; s && *s; ) { int Len; const char *e = s; for ( Len = 0; *e && (!end || e < end) && *e > ' ' && (uchar)*e < 0x7f && strchr("$&+,/:;=?@\'\"<>#%{}|\\^~[]`", *e) == 0; e++ ) { Len++; } p.Push(s, Len); if ((!end || e < end) && *e) { char h[4]; sprintf_s(h, sizeof(h), "%%%2.2X", (uchar)*e); p.Push(h, 3); s = ++e; } else { break; } } } return p.NewStr(); } +GString LgiUnEscapeString(const char *Chars, const char *In, int Len) +{ + GString s; + if (Chars && In) + { + char Buf[256]; + int Ch = 0; + if (Len < 0) + Len = strlen(In); + + while (Len-- > 0) + { + if (Ch > sizeof(Buf)-4) + { + // Buffer full, add substring to 's' + Buf[Ch] = 0; + s += Buf; + Ch = 0; + } + if (*In == '\\') + { + if (strchr(Chars, In[1])) + In++; + } + Buf[Ch++] = *In++; + } + } + + return s; +} + +GString LgiEscapeString(const char *Chars, const char *In, int Len) +{ + GString s; + + if (In && Chars) + { + char Buf[256]; + int Ch = 0; + if (Len < 0) + Len = strlen(In); + + while (Len-- > 0) + { + if (Ch > sizeof(Buf)-4) + { + // Buffer full, add substring to 's' + Buf[Ch] = 0; + s += Buf; + Ch = 0; + } + if (strchr(Chars, *In)) + Buf[Ch++] = '\\'; + Buf[Ch++] = *In++; + } + } + + return s; +} +