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"
" - This is a list item.\n"
"
- This is a list item.\n"
"
- This is a list item.\n"
"
- This is a list item.\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;
}