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/include/mac/carbon/LgiOsDefs.h b/include/mac/carbon/LgiOsDefs.h --- a/include/mac/carbon/LgiOsDefs.h +++ b/include/mac/carbon/LgiOsDefs.h @@ -1,340 +1,342 @@ // // FILE: LgiOsDefs.h (Mac) // AUTHOR: Matthew Allen // DATE: 1/12/2005 // DESCRIPTION: Lgi Mac OS X defines // // Copyright (C) 2005, Matthew Allen // fret@memecode.com // #ifndef __LGI_MAC_OS_DEFS_H #define __LGI_MAC_OS_DEFS_H #include #include #include #include #include "LgiInc.h" #include "LgiDefs.h" #include "GAutoPtr.h" #include "LgiClass.h" #include "pthread.h" ////////////////////////////////////////////////////////////////// // Includes #include "LgiInc.h" ////////////////////////////////////////////////////////////////// // Typedefs typedef WindowRef OsWindow; typedef ControlRef OsView; typedef pthread_t OsThread; typedef UniChar OsChar; typedef ATSUStyle OsFont; typedef CGContextRef OsPainter; typedef CGContextRef OsBitmap; typedef int OsProcessId; class LgiClass GMessage { public: typedef NativeInt Param; typedef NativeInt Result; int m; Param a; Param b; GMessage() { m = a = b = 0; } GMessage(int M, Param A = 0, Param B = 0) { m = M; a = A; b = B; } }; class OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; char **Arg; OsAppArguments(int args = 0, char **arg = 0); ~OsAppArguments(); void Set(char *CmdLine); OsAppArguments &operator =(OsAppArguments &a); }; ////////////////////////////////////////////////////////////////// // Defines #define MsgCode(msg) (msg->m) #define MsgA(msg) (msg->a) #define MsgB(msg) (msg->b) extern GMessage CreateMsg(int m, int a = 0, int b = 0); #define _stricmp strcasecmp #define _strnicmp strncasecmp // Posix system #define POSIX 1 // Process typedef int OsProcess; typedef int OsProcessId; // Threads typedef pthread_t OsThreadId; typedef pthread_mutex_t OsSemaphore; #define LgiGetCurrentThread() pthread_self() // Sockets #define ValidSocket(s) ((s)>=0) #define INVALID_SOCKET -1 typedef int OsSocket; // Sleep the current thread LgiFunc void LgiSleep(int i); // Run the message loop to process any pending messages #define LgiYield() GApp::ObjInstance()->Run(false) #define LGI_GViewMagic 0x14412662 #define LGI_FileDropFormat "furl" // typeFileURL #define LGI_LgiDropFormat "lgi " #define LGI_WideCharset "utf-16" #define LGI_PrintfInt64 "%lli" #define atoi64 atoll #define sprintf_s snprintf #define vsprintf_s vsnprintf #define swprintf_s swprintf #define LGI_IllegalFileNameChars "/:" // FIXME: what other characters should be in here? // Window flags #define GWF_VISIBLE 0x00000001 #define GWF_DISABLED 0x00000002 #define GWF_FOCUS 0x00000004 #define GWF_OVER 0x00000008 #define GWF_DROP_TARGET 0x00000010 #define GWF_SUNKEN 0x00000020 #define GWF_FLAT 0x00000040 #define GWF_RAISED 0x00000080 #define GWF_BORDER 0x00000100 #define GWF_DIALOG 0x00000200 #define GWF_DESTRUCTOR 0x00000400 #define GWF_QUIT_WND 0x00000800 // Menu flags #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 /// The directory separator character on Linux as a char #define DIR_CHAR '/' /// The directory separator character on Linux as a string #define DIR_STR "/" /// The standard end of line string for Linux #define EOL_SEQUENCE "\n" /// Tests a char for being a slash #define IsSlash(c) (((c)=='/')||((c)=='\\')) /// Tests a char for being a quote #define IsQuote(c) (((c)=='\"')||((c)=='\'')) /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ":" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*" /// The stardard extension for dynamically linked code #define LGI_LIBRARY_EXT "dylib" +/// The standard executable extension +#define LGI_EXECUTABLE_EXT "" // Carbon user events #define GViewThisPtr 'gvtp' #define kEventClassUser 'user' #define kEventUser 1 #define kEventParamLgiEvent 'Lgie' #define kEventParamLgiA 'Lgia' #define kEventParamLgiB 'Lgib' /// Base point for system messages. #define M_SYSTEM 0 /// Message that indicates the user is trying to close a top level window. #define M_CLOSE (M_SYSTEM+92) /// Minimum value for application defined message ID's #define M_USER (M_SYSTEM+1000) /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location #define M_MOUSEENTER (M_USER+100) /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location #define M_MOUSEEXIT (M_USER+101) /// \brief GView change notification /// /// a = (GView*) Wnd;\n /// b = (int) Flags; // Specific to each GView #define M_CHANGE (M_USER+102) /// \brief Pass a text message up to the UI to descibe whats happening /// /// a = (GView*) Wnd;\n /// b = (char*) Text; // description from window #define M_DESCRIBE (M_USER+103) // return (bool) #define M_WANT_DIALOG_PROC (M_USER+104) #define M_MENU (M_USER+105) #define M_COMMAND (M_USER+106) #define M_DRAG_DROP (M_USER+107) #define M_TRAY_NOTIFY (M_USER+108) #define M_CUT (M_USER+109) #define M_COPY (M_USER+110) #define M_PASTE (M_USER+111) #define M_PULSE (M_USER+112) #define M_DELETE (M_USER+113) /// GThreadWork object completed /// /// MsgA = (GThreadOwner*) Owner; /// MsgB = (GThreadWork*) WorkUnit; #define M_GTHREADWORK_COMPELTE (M_USER+114) /// Standard ID for an "Ok" button. /// \sa LgiMsg #define IDOK 1 /// Standard ID for a "Cancel" button. /// \sa LgiMsg #define IDCANCEL 2 /// Standard ID for a "Yes" button. /// \sa LgiMsg #define IDYES 3 /// Standard ID for a "No" button. /// \sa LgiMsg #define IDNO 4 /// Standard message box with an Ok button. /// \sa LgiMsg #define MB_OK 5 /// Standard message box with Ok and Cancel buttons. /// \sa LgiMsg #define MB_OKCANCEL 6 /// Standard message box with Yes and No buttons. /// \sa LgiMsg #define MB_YESNO 7 /// Standard message box with Yes, No and Cancel buttons. /// \sa LgiMsg #define MB_YESNOCANCEL 8 #define MB_SYSTEMMODAL 0x1000 /// The CTRL key is pressed /// \sa GKey #define LGI_VKEY_CTRL 0x001 /// The ALT key is pressed /// \sa GKey #define LGI_VKEY_ALT 0x002 /// The SHIFT key is pressed /// \sa GKey #define LGI_VKEY_SHIFT 0x004 /// The left mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_LEFT 0x008 /// The middle mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_MIDDLE 0x010 /// The right mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_RIGHT 0x020 /// The ctrl key is pressed /// \sa GMouse #define LGI_VMOUSE_CTRL 0x040 /// The alt key is pressed /// \sa GMouse #define LGI_VMOUSE_ALT 0x080 /// The shift key is pressed /// \sa GMouse #define LGI_VMOUSE_SHIFT 0x100 /// The mouse event is a down click /// \sa GMouse #define LGI_VMOUSE_DOWN 0x200 /// The mouse event is a double click /// \sa GMouse #define LGI_VMOUSE_DOUBLE 0x400 // Keys #define VK_F1 1 #define VK_F2 2 #define VK_ENTER 3 #define VK_F3 4 #define VK_F4 5 #define VK_F5 6 #define VK_F6 7 #define VK_BACKSPACE 8 #define VK_TAB 9 #define VK_F7 11 #define VK_F8 12 #define VK_RETURN 13 #define VK_F9 14 #define VK_F10 15 #define VK_F11 16 #define VK_F12 17 #define VK_SHIFT 18 #define VK_PAGEUP 19 #define VK_PAGEDOWN 20 #define VK_HOME 21 #define VK_END 22 #define VK_INSERT 23 #define VK_DELETE 24 #define VK_APPS 25 #define VK_ESCAPE 27 #define VK_LEFT 28 #define VK_RIGHT 29 #define VK_UP 30 #define VK_DOWN 31 ///////////////////////////////////////////////////////////////////////////////////// // Externs LgiFunc GView *GWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt); LgiFunc int WinHeightToPoint(int Ht); LgiFunc int stricmp(const char *a, const char *b); LgiFunc char *strlwr(char *a); LgiFunc char *strupr(char *a); LgiFunc char *p2c(unsigned char *s); LgiFunc void c2p255(Str255 &d, char *s); LgiFunc char *CFStringToUtf8(CFStringRef r); LgiFunc CFStringRef Utf8ToCFString(char *s, int len = -1); /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(char *s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); #endif 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; +} + diff --git a/src/mac/carbon/Lgi/GFileSelect.cpp b/src/mac/carbon/Lgi/GFileSelect.cpp --- a/src/mac/carbon/Lgi/GFileSelect.cpp +++ b/src/mac/carbon/Lgi/GFileSelect.cpp @@ -1,509 +1,509 @@ /// \file /// \author Matthew Allen /// \brief Native mac file open dialog. #include #include #include "Lgi.h" #include "GToken.h" ////////////////////////////////////////////////////////////////////////// // This is just a private data container to make it easier to change the // implementation of this class without effecting headers and applications. class GFileSelectPrivate { friend class GFileSelect; friend class GFileSelectDlg; friend class GFolderList; GView *Parent; GFileSelect *Select; char *Title; char *DefExt; bool MultiSelect; List Files; int CurrentType; List Types; List History; bool ShowReadOnly; bool ReadOnly; bool EatClose; public: static char *InitPath; static bool InitShowHiddenFiles; static GRect InitSize; GFileSelectPrivate(GFileSelect *select) { ShowReadOnly = false; ReadOnly = false; EatClose = false; Select = select; Title = 0; DefExt = 0; MultiSelect = false; Parent = 0; CurrentType = -1; } virtual ~GFileSelectPrivate() { DeleteArray(Title); DeleteArray(DefExt); Types.DeleteObjects(); Files.DeleteArrays(); History.DeleteArrays(); } Boolean FilterProc(AEDesc * theItem, NavFilterModes filterMode) { FSRef fs; if (!AEGetDescData(theItem, &fs, sizeof(fs))) { UInt8 path[300]; FSRefMakePath(&fs, path, sizeof(path)); if (DirExists((char*)path)) return true; char *Dir = strrchr((char*)path, '/'); if (Dir) { Dir++; for (GFileType *t = Types.First(); t; t = Types.Next()) { char *e = t->Extension(); if (e) { if (MatchStr(e, Dir)) return true; } } return false; } } return true; } void DoTypes(NavDialogRef Dlg) { if (Types.Length()) { int Len = 0; for (GFileType *t = Types.First(); t; t = Types.Next()) { if (t->Extension()) { if (stricmp(t->Extension(), LGI_ALL_FILES)) Len++; } } if (Len) { CFMutableArrayRef identifiers = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks ); if (identifiers) { for (GFileType *t = Types.First(); t; t = Types.Next()) { char *Ext = t->Extension(); GToken e(Ext, ";"); for (int i=0; iShowReadOnly = b;; + d->ShowReadOnly = b; } bool GFileSelect::ReadOnly() { return d->ReadOnly; } char *GFileSelect::Name() { return d->Files.First(); } bool GFileSelect::Name(const char *n) { d->Files.DeleteArrays(); if (n) { char *ns = NewStr(n); if (ns) { for (char *c = ns; *c; c++) { if (strchr(LGI_IllegalFileNameChars, *c)) *c = '_'; } d->Files.Insert(ns); } } return true; } char *GFileSelect::operator [](int i) { return d->Files.ItemAt(i); } int GFileSelect::Length() { return d->Files.Length(); } int GFileSelect::Types() { return d->Types.Length(); } void GFileSelect::ClearTypes() { d->Types.DeleteObjects(); } GFileType *GFileSelect::TypeAt(int n) { return d->Types.ItemAt(n); } bool GFileSelect::Type(const char *Description, const char *Extension, int Data) { GFileType *Type = new GFileType; if (Type) { Type->Description(Description); Type->Extension(Extension); d->Types.Insert(Type); } return Type != 0; } int GFileSelect::SelectedType() { return d->CurrentType; } GViewI *GFileSelect::Parent() { return d->Parent; } void GFileSelect::Parent(GViewI *Window) { d->Parent = dynamic_cast(Window); } bool GFileSelect::MultiSelect() { return d->MultiSelect; } void GFileSelect::MultiSelect(bool Multi) { d->MultiSelect = Multi; } #define CharPropImpl(Func, Var) \ char *GFileSelect::Func() \ { \ return Var; \ } \ void GFileSelect::Func(const char *i) \ { \ DeleteArray(Var); \ if (i) \ { \ Var = NewStr(i); \ } \ } CharPropImpl(InitialDir, d->InitPath); CharPropImpl(Title, d->Title); CharPropImpl(DefaultExtension, d->DefExt); void Lgi_NavEventProc( NavEventCallbackMessage callBackSelector, NavCBRecPtr callBackParms, void * callBackUD) { GFileSelectPrivate *d = (GFileSelectPrivate*) callBackUD; switch (callBackSelector) { case kNavCBStart: { if (d->InitPath) { OSStatus e = noErr; AEDesc theLocation = {typeNull, NULL}; FSRef Ref; e = FSPathMakeRef((uchar*) d->InitPath, &Ref, 0); if (e) printf("%s:%i - FSPathMakeRef failed.\n", _FL); FSSpec Spec; FSGetCatalogInfo(&Ref, kFSCatInfoNone, 0, 0, &Spec, 0); e = AECreateDesc(typeFSS, &Spec, sizeof(FSSpec), &theLocation); if (e) printf("%s:%i - FSMakeFSSpec failed.\n", _FL); e = NavCustomControl(callBackParms->context, kNavCtlSetLocation, (void*)&theLocation); if (e) printf("%s:%i - FSMakeFSSpec failed.\n", _FL); } break; } } } Boolean Lgi_NavObjectFilterProc(AEDesc * theItem, void *info, void *callBackUD, NavFilterModes filterMode) { GFileSelectPrivate *d = (GFileSelectPrivate*) callBackUD; return d->FilterProc(theItem, filterMode); } bool GFileSelect::Open() { bool Status = false; NavDialogCreationOptions o; ZeroObj(o); OSStatus e = NavGetDefaultDialogCreationOptions(&o); if (e) printf("%s:%i - NavGetDefaultDialogCreationOptions failed with %i\n", _FL, (int)e); else { if (Name()) o.saveFileName = Utf8ToCFString(Name()); o.modality = kWindowModalityAppModal; if (MultiSelect()) o.optionFlags |= kNavAllowMultipleFiles; NavEventUPP EventUPP = NewNavEventUPP(Lgi_NavEventProc); NavObjectFilterUPP ObjectUPP = NewNavObjectFilterUPP(Lgi_NavObjectFilterProc); NavDialogRef Dlg = 0; e = NavCreateGetFileDialog( &o, 0, // NavTypeListHandle inTypeList, EventUPP, 0, // NavPreviewUPP inPreviewProc, 0, // ObjectUPP, d, &Dlg); if (e) printf("%s:%i - NavCreateGetFileDialog failed with %i\n", _FL, (int)e); - else + else if (Dlg) { d->DoTypes(Dlg); e = NavDialogRun(Dlg); if (e) printf("%s:%i - NavDialogRun failed with %i\n", _FL, (int)e); else { Status = d->DoReply(Dlg); } NavDialogDispose(Dlg); } DisposeNavEventUPP(EventUPP); DisposeNavObjectFilterUPP(ObjectUPP); } return Status; } bool GFileSelect::OpenFolder() { bool Status = false; NavDialogCreationOptions o; ZeroObj(o); OSStatus e = NavGetDefaultDialogCreationOptions(&o); if (e) printf("%s:%i - NavGetDefaultDialogCreationOptions failed with %i\n", _FL, (int)e); else { if (Name()) o.saveFileName = Utf8ToCFString(Name()); o.modality = kWindowModalityAppModal; NavEventUPP EventUPP = NewNavEventUPP(Lgi_NavEventProc); NavObjectFilterUPP ObjectUPP = NewNavObjectFilterUPP(Lgi_NavObjectFilterProc); NavDialogRef Dlg = 0; e = NavCreateChooseFolderDialog(&o, EventUPP, 0, // ObjectUPP, d, &Dlg); if (e) printf("%s:%i - NavCreateGetFileDialog failed with %i\n", _FL, (int)e); else { d->DoTypes(Dlg); e = NavDialogRun(Dlg); if (e) printf("%s:%i - NavDialogRun failed with %i\n", _FL, (int)e); else { Status = d->DoReply(Dlg); } NavDialogDispose(Dlg); } DisposeNavEventUPP(EventUPP); DisposeNavObjectFilterUPP(ObjectUPP); } return Status; } bool GFileSelect::Save() { bool Status = false; NavDialogCreationOptions o; ZeroObj(o); OSStatus e = NavGetDefaultDialogCreationOptions(&o); if (e) printf("%s:%i - NavGetDefaultDialogCreationOptions failed with %i\n", _FL, (int)e); else { if (Name()) o.saveFileName = Utf8ToCFString(Name()); o.modality = kWindowModalityAppModal; NavEventUPP EventUPP = NewNavEventUPP(Lgi_NavEventProc); NavObjectFilterUPP ObjectUPP = NewNavObjectFilterUPP(Lgi_NavObjectFilterProc); NavDialogRef Dlg = 0; e = NavCreatePutFileDialog( &o, 'type', 'lgi ', EventUPP, d, &Dlg); if (e) printf("%s:%i - NavCreatePutFileDialog failed with %i\n", _FL, (int)e); - else + else if (Dlg) { d->DoTypes(Dlg); e = NavDialogRun(Dlg); if (e) printf("%s:%i - NavDialogRun failed with %i\n", _FL, (int)e); else { Status = d->DoReply(Dlg, true); } NavDialogDispose(Dlg); } DisposeNavEventUPP(EventUPP); DisposeNavObjectFilterUPP(ObjectUPP); } return Status; }