diff --git a/Lgi_vs2019.vcxproj b/Lgi_vs2019.vcxproj
--- a/Lgi_vs2019.vcxproj
+++ b/Lgi_vs2019.vcxproj
@@ -1,471 +1,471 @@
Debug
x64
ReleaseNoOptimize
x64
Release
x64
Lgi
{95DF9CA4-6D37-4A85-A648-80C2712E0DA1}
10.0
DynamicLibrary
v142
false
Unicode
DynamicLibrary
v142
false
Unicode
DynamicLibrary
v142
false
Unicode
<_ProjectFileVersion>12.0.30501.0
.\Lib\
$(Platform)$(Configuration)19\
false
$(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include
- LgiHaiku19x64
+ Lgi19x64
.\Lib\
$(Platform)$(Configuration)19\
true
$(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include
- LgiHaiku19x64d
+ Lgi19x64d
.\Lib\
$(Platform)$(Configuration)19\
false
$(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include
- LgiHaiku19x64nop
+ Lgi19x64nop
NDEBUG;%(PreprocessorDefinitions)
true
true
X64
.\Release/Lgi.tlb
MinSpace
OnlyExplicitInline
include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories)
WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions)
true
MultiThreadedDLL
true
true
$(IntDir)$(TargetName).pch
$(IntDir)
$(IntDir)
$(IntDir)
Level2
true
ProgramDatabase
Default
NDEBUG;%(PreprocessorDefinitions)
0x0c09
ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetFileName)
true
true
$(OutDir)$(TargetName).pdb
Windows
true
false
$(OutDir)$(TargetName).lib
MachineX64
_DEBUG;%(PreprocessorDefinitions)
true
true
X64
.\Debug/Lgi.tlb
Disabled
include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories)
WIN64;LGI_LIBRARY;_DEBUG;WINDOWS;LGI_RES;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebugDLL
true
$(IntDir)$(TargetName).pch
$(IntDir)
$(IntDir)
$(IntDir)
Level3
true
ProgramDatabase
Default
_DEBUG;%(PreprocessorDefinitions)
0x0c09
ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies)
NotSet
$(OutDir)$(TargetFileName)
true
true
$(OutDir)$(TargetName).pdb
Windows
false
$(OutDir)$(TargetName).lib
MachineX64
NDEBUG;%(PreprocessorDefinitions)
true
true
X64
.\Release/Lgi.tlb
MinSpace
OnlyExplicitInline
include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories)
WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions)
true
MultiThreadedDLL
true
true
$(IntDir)$(TargetName).pch
$(IntDir)
$(IntDir)
$(IntDir)
Level2
true
ProgramDatabase
Default
NDEBUG;%(PreprocessorDefinitions)
0x0c09
ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetFileName)
true
true
$(OutDir)$(TargetName).pdb
Windows
true
false
$(OutDir)$(TargetName).lib
MachineX64
false
true
true
false
false
false
true
true
true
true
true
true
false
false
false
true
true
true
true
true
true
\ No newline at end of file
diff --git a/include/lgi/common/LgiInterfaces.h b/include/lgi/common/LgiInterfaces.h
--- a/include/lgi/common/LgiInterfaces.h
+++ b/include/lgi/common/LgiInterfaces.h
@@ -1,567 +1,569 @@
// \file
/// \author Matthew Allen
#ifndef _LGI_INTERFACES_H_
#define _LGI_INTERFACES_H_
// Includes
#include "lgi/common/Mem.h"
#include "lgi/common/Array.h"
#include "lgi/common/Colour.h"
#include "lgi/common/Cancel.h"
#include "lgi/common/StringClass.h"
#include "lgi/common/LgiUiBase.h"
#include "lgi/common/Notifications.h"
// Fwd defs
class LXmlTag;
class LMouseHook;
class LFont;
class LRect;
class LPoint;
class LRegion;
class LSurface;
class LMouse;
class LKey;
class LWindow;
class LVariant;
class LCss;
class LViewI;
class LView;
#ifdef Yield
#undef Yield
#endif
// Classes
class LDomI
{
public:
virtual ~LDomI() {}
virtual bool GetValue(const char *Var, LVariant &Value) { return false; }
virtual bool SetValue(const char *Var, LVariant &Value) { return false; }
virtual bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { return false; }
};
/// Stream interface class
///
/// Defines the API
/// for all the streaming data classes. Allows applications to plug
/// different types of date streams into functions that take a LStream.
/// Typically this means being able to swap files with sockets or data
/// buffers etc.
///
class LgiClass LStreamI : virtual public LDomI
{
public:
/// Open a connection
/// \returns > zero on success
virtual int Open
(
/// A string connection parameter
const char *Str = 0,
/// An integer connection parameter
int Int = 0
)
{ return false; }
/// Returns true is the stream is still open
virtual bool IsOpen() { return false; }
/// Closes the connection
/// \returns > zero on success
virtual int Close() { return 0; }
/// \brief Gets the size of the stream
/// \return The size or -1 on error (e.g. the information is not available)
virtual int64 GetSize() { return -1; }
/// \brief Sets the size of the stream
/// \return The new size or -1 on error (e.g. the size is not set-able)
virtual int64 SetSize(int64 Size) { return -1; }
/// \brief Gets the current position of the stream
/// \return Current position or -1 on error (e.g. the position is not known)
virtual int64 GetPos() { return -1; }
/// \brief Sets the current position of the stream
/// \return The new current position or -1 on error (e.g. the position can't be set)
virtual int64 SetPos(int64 Pos) { return -1; }
/// \brief Read bytes out of the stream
/// \return > 0 on succes, which indicates the number of bytes read
virtual ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) = 0;
/// \brief Write bytes to the stream
/// \return > 0 on succes, which indicates the number of bytes written
virtual ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) = 0;
/// \brief Creates a dynamically allocated copy of the same type of stream.
/// This new stream is not connected to anything.
/// \return The new stream or NULL on error.
virtual LStreamI *Clone() { return 0; }
virtual void ChangeThread() {}
/// Utility: Read as LString.
LString Read(size_t bufLen = 256)
{
LString s;
s.Length(bufLen);
auto rd = Read(s.Get(), s.Length());
- if (rd < (ssize_t) s.Length())
+ if (rd < 0)
+ s.Empty();
+ else if (rd < (ssize_t) s.Length())
s.Length(rd);
return s;
}
/// Utility: Write a LString
size_t Write(const LString s)
{
return Write(s.Get(), s.Length());
}
};
/// Socket logging types..
enum LSocketLogTypes
{
/// Do no logging
NET_LOG_NONE = 0,
/// Log a hex dump of everything
NET_LOG_HEX_DUMP = 1,
/// Log just the bytes
NET_LOG_ALL_BYTES = 2
};
/// Virtual base class for a socket. See the documentation for LSocket for a more
/// through treatment of this object's API.
class LSocketI :
virtual public LStreamI
{
public:
enum SocketMsgType
{
SocketMsgNone,
SocketMsgInfo,
SocketMsgSend,
SocketMsgReceive,
SocketMsgWarning,
SocketMsgError,
};
virtual ~LSocketI() {}
/// Returns the actual socket (as defined by the OS)
virtual OsSocket Handle(OsSocket Set = INVALID_SOCKET) { return INVALID_SOCKET; }
// Cancel
virtual LCancel *GetCancel() { return NULL; }
virtual void SetCancel(LCancel *c) { }
// Logging and utility
virtual class LStreamI *GetLog() { return NULL; }
// Host/Port meta data
/// Returns the IP at this end of the socket
virtual bool GetLocalIp
(
/// Ptr to a buffer of at least 16 bytes
char *IpAddr
) { return false; }
/// Return the port at this end of the connection
virtual int GetLocalPort() { return 0; }
/// Gets the remote IP
virtual bool GetRemoteIp(char *IpAddr) { return false; }
/// Return the port at this end of the connection
virtual int GetRemotePort() { return 0; }
// Timeout
/// Gets the current timeout for operations in ms
virtual int GetTimeout() { return -1; }
/// Sets the current timeout for operations in ms
virtual void SetTimeout(int ms) {}
/// Sets the continue token
// virtual void SetContinue(bool *Token) {}
// State
/// True if there is data available to read.
virtual bool IsReadable(int TimeoutMs = 0) { return false; }
/// True if the socket can be written to.
virtual bool IsWritable(int TimeoutMs = 0) { return false; }
/// True if the socket can be accept.
virtual bool CanAccept(int TimeoutMs = 0) { return false; }
/// Returns whether the socket is set to blocking or not
virtual bool IsBlocking() { return true; }
/// Set whether the socket should block or not
virtual void IsBlocking(bool block) {}
/// Get the send delay setting
virtual bool IsDelayed() { return true; }
/// Set the send delay setting
virtual void IsDelayed(bool Delay) {}
// UDP
/// Get UPD mode
virtual bool GetUdp() { return false; }
/// Set UPD mode
virtual void SetUdp(bool b) {}
/// Read UPD packet
virtual int ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip = 0, uint16_t *Port = 0) { return 0; }
/// Write UPD packet
virtual int WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { return 0; }
// Server
/// Listens on a given port for an incoming connection.
virtual bool Listen(int Port = 0) { return false; }
/// Accepts an incoming connection and connects the socket you pass in to the remote host.
virtual bool Accept(LSocketI *c) { return false; }
// Event call backs
/// Called when the connection is dropped
virtual void OnDisconnect() {}
/// Called when data is read
virtual void OnRead(char *Data, ssize_t Len) {}
/// Called when data is written
virtual void OnWrite(const char *Data, ssize_t Len) {}
/// Called when an error occurs
virtual void OnError(int ErrorCode, const char *ErrorDescription) {}
/// Called when some events happens
virtual void OnInformation(const char *Str) {}
/// Process an error
virtual int Error(void *Param) { return 0; }
virtual const char *GetErrorString() { return NULL; }
LString LocalIp()
{
char Ip[32];
return GetLocalIp(Ip) ? Ip : NULL;
}
};
class LAppI
{
public:
/// The idle function should return false to wait for more
/// messages, otherwise it will be called continuously when no
/// messages are available.
typedef bool (*OnIdleProc)(void *Param);
/// Destroys the object
virtual ~LAppI() {}
virtual bool IsOk() = 0;
/// Returns this processes ID
virtual OsProcessId GetProcessId() = 0;
/// Returns the thread currently running the active message loop
virtual OsThreadId GetGuiThreadId() = 0;
virtual bool InThread() = 0;
/// Resets the arguments
virtual void SetAppArgs(OsAppArguments &AppArgs) = 0;
/// Returns the arguemnts
virtual OsAppArguments *GetAppArgs() = 0;
/// Returns the n'th argument as a heap string. Free with DeleteArray(...).
virtual const char *GetArgumentAt(int n) = 0;
/// Enters the message loop.
virtual bool Run
(
/// [Optional] Idle callback
OnIdleProc IdleCallback = 0,
/// [Optional] User param for IdleCallback
void *IdleParam = 0
) = 0;
/// Processed queued events and then return
virtual bool Yield() = 0;
/// Event called to process the command line
virtual void OnCommandLine() = 0;
/// Event called to process files dropped on the application
virtual void OnReceiveFiles(LArray &Files) = 0;
/// Event called to process URLs given to the application
virtual void OnUrl(const char *Url) = 0;
/// Exits the event loop with the code specified
virtual void Exit
(
/// The application exit code.
int Code = 0
) = 0;
/// \brief Parses the command line for a switch
/// \return true if the option exists.
virtual bool GetOption
(
/// The option to look for.
const char *Option,
/// String to receive the value (if any) of the option
LString &Value
) = 0;
/// \brief Parses the command line for a switch
/// \return true if the option exists.
virtual bool GetOption
(
/// The option to look for.
const char *Option,
/// The buffer to receive the value of the command line parameter or NULL if you don't care.
char *Dst = 0,
/// The buffer size in bytes
int DstSize = 0
) = 0;
/// Gets the application conf stored in lgi.conf
virtual LString GetConfig(const char *Tag) = 0;
/// Sets a single tag in the config. (Not written to disk)
virtual void SetConfig(const char *Var, const char *Val) = 0;
/// Gets the control with the keyboard focus
virtual LViewI *GetFocus() = 0;
/// Gets the MIME type of a file
virtual LString GetFileMimeType
(
/// The file to identify
const char *File
) = 0;
/// Get a system metric
virtual int32 GetMetric
(
/// One of #LGI_MET_DECOR_X, #LGI_MET_DECOR_Y
LSystemMetric Metric
) = 0;
/// Get the mouse hook instance
virtual LMouseHook *GetMouseHook() = 0;
/// Returns the number of cpu cores or -1 if unknown.
virtual int GetCpuCount() { return -1; }
/// Gets the font cache
virtual class LFontCache *GetFontCache() = 0;
};
class LEventSinkI
{
public:
virtual ~LEventSinkI() {}
virtual bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) = 0;
};
class LEventTargetI
{
public:
virtual ~LEventTargetI() {}
virtual LMessage::Result OnEvent(LMessage *Msg) = 0;
};
class LEventsI : public LEventTargetI
{
public:
virtual ~LEventsI() {}
// Events
virtual void OnMouseClick(LMouse &m) = 0;
virtual void OnMouseEnter(LMouse &m) = 0;
virtual void OnMouseExit(LMouse &m) = 0;
virtual void OnMouseMove(LMouse &m) = 0;
virtual bool OnMouseWheel(double Lines) = 0;
virtual bool OnKey(LKey &k) = 0;
virtual void OnAttach() = 0;
virtual void OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual void OnFocus(bool f) = 0;
virtual void OnPulse() = 0;
virtual void OnPosChange() = 0;
virtual bool OnRequestClose(bool OsShuttingDown) = 0;
virtual int OnHitTest(int x, int y) = 0;
virtual void OnChildrenChanged(LViewI *Wnd, bool Attaching) = 0;
virtual void OnPaint(LSurface *pDC) = 0;
virtual int OnCommand(int Cmd, int Event, OsView Wnd) = 0;
virtual int OnNotify(LViewI *Ctrl, LNotification Data) = 0;
};
class LViewLayoutInfo
{
public:
struct Range
{
// 0 if unknown, -1 for "all available"
int32 Min, Max;
Range()
{
Min = Max = 0;
}
};
Range Width;
Range Height;
};
class LgiClass LViewI :
public LEventsI,
public LEventSinkI,
public virtual LDomI
{
friend class LView;
public:
// Handles
#if LGI_VIEW_HANDLE
virtual OsView Handle() const = 0;
#endif
virtual int AddDispatch() = 0;
virtual OsWindow WindowHandle() { printf("LViewI::WindowHandle()\n"); return NULL; }
virtual LView *GetGView() { return NULL; }
// Heirarchy
virtual bool Attach(LViewI *p) = 0;
virtual bool AttachChildren() = 0;
virtual bool Detach() = 0;
virtual bool IsAttached() = 0;
virtual LWindow *GetWindow() = 0;
virtual LViewI *GetParent() = 0;
virtual void SetParent(LViewI *p) = 0;
virtual void Quit(bool DontDelete = false) = 0;
virtual bool AddView(LViewI *v, int Where = -1) = 0;
virtual bool DelView(LViewI *v) = 0;
virtual bool HasView(LViewI *v) = 0;
virtual LArray IterateViews() = 0;
// Threading
virtual bool Lock(const char *file, int line, int TimeOut = -1) = 0;
virtual void Unlock() = 0;
virtual bool InThread() = 0;
// Properties
virtual bool Enabled() = 0;
virtual void Enabled(bool e) = 0;
virtual bool Visible() = 0;
virtual void Visible(bool v) = 0;
virtual bool Focus() = 0;
virtual void Focus(bool f) = 0;
virtual class LDragDropSource *DropSource(LDragDropSource *Set = NULL) = 0;
virtual class LDragDropTarget *DropTarget(LDragDropTarget *Set = NULL) = 0;
virtual bool DropTarget(bool t) = 0;
virtual bool Sunken() = 0;
virtual void Sunken(bool i) = 0;
virtual bool Flat() = 0;
virtual void Flat(bool i) = 0;
virtual bool Raised() = 0;
virtual void Raised(bool i) = 0;
virtual bool GetTabStop() = 0;
virtual void SetTabStop(bool b) = 0;
// Style
virtual LCss *GetCss(bool Create = false) = 0;
virtual void SetCss(LCss *css) = 0;
virtual bool SetColour(LColour &c, bool Fore) = 0;
virtual LString CssStyles(const char *Set = NULL) { return LString(); }
virtual LString::Array *CssClasses() { return NULL; }
virtual LFont *GetFont() = 0;
virtual void SetFont(LFont *Fnt, bool OwnIt = false) = 0;
// Name and value
virtual bool Name(const char *n) = 0;
virtual bool NameW(const char16 *n) = 0;
virtual const char *Name() = 0;
virtual const char16 *NameW() = 0;
virtual int64 Value() = 0;
virtual void Value(int64 i) = 0;
virtual const char *GetClass() { return "LViewI"; } // mainly for debugging
// Size and position
virtual LRect &GetPos() = 0;
virtual LRect &GetClient(bool InClientSpace = true) = 0;
virtual bool SetPos(LRect &p, bool Repaint = false) = 0;
virtual int X() = 0;
virtual int Y() = 0;
virtual LPoint GetMinimumSize() = 0;
virtual void SetMinimumSize(LPoint Size) = 0;
// Id
virtual int GetId() = 0;
virtual void SetId(int i) = 0;
// Events and notification
virtual void SendNotify(LNotification note) = 0;
virtual LViewI *GetNotify() = 0;
virtual void SetNotify(LViewI *n) = 0;
// Mouse
virtual LCursor GetCursor(int x, int y) = 0;
virtual bool Capture(bool c) = 0;
virtual bool IsCapturing() = 0;
virtual bool GetMouse(LMouse &m, bool ScreenCoords = false) = 0;
// Helper
#if LGI_VIEW_HANDLE
virtual LViewI *FindControl(OsView hnd) = 0;
#endif
virtual LViewI *FindControl(int Id) = 0;
virtual int64 GetCtrlValue(int Id) = 0;
virtual void SetCtrlValue(int Id, int64 i) = 0;
virtual const char *GetCtrlName(int Id) = 0;
virtual void SetCtrlName(int Id, const char *s) = 0;
virtual bool GetCtrlEnabled(int Id) = 0;
virtual void SetCtrlEnabled(int Id, bool Enabled) = 0;
virtual bool GetCtrlVisible(int Id) = 0;
virtual void SetCtrlVisible(int Id, bool Visible) = 0;
virtual bool Pour(LRegion &r) = 0;
template
bool GetViewById(int Id, T *&Ptr)
{
LViewI *Ctrl = FindControl(Id);
Ptr = dynamic_cast(Ctrl);
#ifdef _DEBUG
if (Ctrl != NULL && Ptr == NULL)
LgiTrace("%s:%i - Can't cast '%s' to target type.\n", _FL, Ctrl->GetClass());
#endif
return Ptr != NULL;
}
// Points
virtual bool PointToScreen(LPoint &p) = 0;
virtual bool PointToView(LPoint &p) = 0;
virtual bool WindowVirtualOffset(LPoint *Offset) = 0;
virtual LViewI *WindowFromPoint(int x, int y, int DebugDepth = 0) = 0;
virtual LPoint &GetWindowBorderSize() = 0;
virtual bool IsOver(LMouse &m) = 0;
// Misc
virtual bool Invalidate(LRect *r = 0, bool Repaint = false, bool NonClient = false) = 0;
virtual bool Invalidate(LRegion *r, bool Repaint = false, bool NonClient = false) = 0;
virtual void SetPulse(int Ms = -1) = 0;
virtual bool OnLayout(LViewLayoutInfo &Inf) = 0;
protected:
virtual bool OnViewMouse(LView *v, LMouse &m) = 0;
virtual bool OnViewKey(LView *v, LKey &k) = 0;
};
class LMemoryPoolI
{
public:
virtual ~LMemoryPoolI() {}
virtual void *Alloc(size_t Size) = 0;
virtual void Free(void *Ptr) = 0;
virtual void Empty() = 0;
};
#endif
diff --git a/include/lgi/common/Mail.h b/include/lgi/common/Mail.h
--- a/include/lgi/common/Mail.h
+++ b/include/lgi/common/Mail.h
@@ -1,835 +1,835 @@
/** \file
\author Matthew Allen
*/
#ifndef __MAIL_H
#define __MAIL_H
#include
#include "lgi/common/Net.h"
#include "lgi/common/Base64.h"
#include "lgi/common/Progress.h"
#include "lgi/common/Variant.h"
#include "lgi/common/OAuth2.h"
#include "lgi/common/Store3Defs.h"
#ifndef GPL_COMPATIBLE
#define GPL_COMPATIBLE 0
#endif
// Defines
#define MAX_LINE_SIZE 1024
#define MAX_NAME_SIZE 64
#define EMAIL_LINE_SIZE 76
// #define IsDigit(c) ((c) >= '0' AND (c) <= '9')
// Mail logging defines
#define MAIL_SEND_COLOUR Rgb24(0, 0, 0xff)
#define MAIL_RECEIVE_COLOUR Rgb24(0, 0x8f, 0)
#define MAIL_ERROR_COLOUR Rgb24(0xff, 0, 0)
#define MAIL_WARNING_COLOUR Rgb24(0xff, 0x7f, 0)
#define MAIL_INFO_COLOUR Rgb24(0, 0, 0)
// Helper functions
extern void TokeniseStrList(char *Str, List &Output, const char *Delim);
extern char ConvHexToBin(char c);
#define ConvBinToHex(i) (((i)<10)?'0'+(i):'A'+(i)-10)
extern void DecodeAddrName(const char *Start, std::function cb, const char *DefaultDomain);
extern void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain);
extern void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain);
extern int MaxLineLen(char *Text);
extern char *EncodeImapString(const char *s);
extern char *DecodeImapString(const char *s);
extern bool UnBase64Str(LString &s);
extern bool Base64Str(LString &s);
extern const char *sTextPlain;
extern const char *sTextHtml;
extern const char *sTextXml;
extern const char *sApplicationInternetExplorer;
extern const char sMultipartMixed[];
extern const char sMultipartEncrypted[];
extern const char sMultipartSigned[];
extern const char sMultipartAlternative[];
extern const char sMultipartRelated[];
extern const char sAppOctetStream[];
// Classes
class MailProtocol;
struct MailProtocolError
{
int Code;
LString ErrMsg;
MailProtocolError()
{
Code = 0;
}
};
class MailProtocolProgress
{
public:
uint64 Start;
ssize_t Value;
ssize_t Range;
MailProtocolProgress()
{
Empty();
}
void Empty()
{
Start = 0;
Value = 0;
Range = 0;
}
void StartTransfer(ssize_t Size)
{
Start = LCurrentTime();
Value = 0;
Range = Size;
}
};
class LogEntry
{
LColour c;
public:
LArray Txt;
LogEntry(LColour col);
LColour GetColour() { return c; }
bool Add(const char *t, ssize_t len = -1);
};
/// Attachment descriptor
class FileDescriptor : public LBase
{
protected:
// Global
int64 Size;
char *MimeType;
char *ContentId;
// Read from file
LFile File;
LStreamI *Embeded;
bool OwnEmbeded;
int64 Offset;
LMutex *Lock;
// Write to memory
uchar *Data;
LAutoPtr DataStream;
public:
FileDescriptor(LStreamI *embed, int64 Offset, int64 Size, char *Name);
FileDescriptor(char *name);
FileDescriptor(char *data, int64 len);
FileDescriptor();
~FileDescriptor();
void SetLock(LMutex *l);
LMutex *GetLock();
void SetOwnEmbeded(bool i);
// Access functions
LStreamI *GotoObject(); // Get data to read
uchar *GetData(); // Get data from write
int Sizeof();
char *GetMimeType() { return MimeType; }
void SetMimeType(char *s) { DeleteArray(MimeType); MimeType = NewStr(s); }
char *GetContentId() { return ContentId; }
void SetContentId(char *s) { DeleteArray(ContentId); ContentId = NewStr(s); }
// Decode MIME data to memory
bool Decode(char *ContentType,
char *ContentTransferEncoding,
char *MimeData,
int MimeDataLength);
};
/// Address descriptor
class AddressDescriptor
{
public:
uint8_t Status = false;
EmailAddressType CC = MAIL_ADDR_TO;
LString sName;
LString sAddr;
AddressDescriptor(const AddressDescriptor *Copy = NULL);
virtual ~AddressDescriptor();
void _Delete();
LString Print();
};
/// Base class for mail protocol implementations
class MailProtocol
{
protected:
char Buffer[4<<10];
LMutex SocketLock;
LAutoPtr Socket;
LOAuth2::Params OAuth2;
LDom *SettingStore;
bool Error(const char *file, int line, const char *msg, ...);
bool Read();
bool Write(const char *Buf = NULL, bool Log = false);
virtual void OnUserMessage(char *Str) {}
public:
// Logging
LStreamI *Logger;
void Log(const char *Str, LSocketI::SocketMsgType type);
// Task Progress
MailProtocolProgress *Items;
MailProtocolProgress *Transfer;
// Settings
int ErrMsgId; /// \sa #L_ERROR_ESMTP_NO_AUTHS, #L_ERROR_ESMTP_UNSUPPORTED_AUTHS
LString ErrMsgFmt; /// The format for the printf
LString ErrMsgParam; /// The arguments for the printf
LString ProgramName;
LString ExtraOutgoingHeaders;
List CharsetPrefs;
// Object
MailProtocol();
virtual ~MailProtocol();
// Methods
void SetOAuthParams(LOAuth2::Params &p) { OAuth2 = p; }
void SetSettingStore(LDom *store) { SettingStore = store; }
/// Thread safe hard close (quit now)
bool CloseSocket()
{
LMutex::Auto l(&SocketLock, _FL);
if (Socket != NULL)
return Socket->Close() != 0;
return false;
}
void SetError(int ResourceId, const char *Fmt, const char *Param = NULL)
{
ErrMsgId = ResourceId;
ErrMsgFmt = Fmt;
ErrMsgParam = Param;
}
};
/////////////////////////////////////////////////////////////////////
// Mail IO parent classes
/// Enable STARTTLS support (requires an SSL capable socket)
#define MAIL_USE_STARTTLS 0x01
/// Use authentication
#define MAIL_USE_AUTH 0x02
/// Force the use of PLAIN type authentication
#define MAIL_USE_PLAIN 0x04
/// Force the use of LOGIN type authentication
#define MAIL_USE_LOGIN 0x08
/// Force the use of NTLM type authentication
#define MAIL_USE_NTLM 0x10
/// Secure auth
#define MAIL_SECURE_AUTH 0x20
/// Use SSL
#define MAIL_SSL 0x40
/// OAUTH2
#define MAIL_USE_OAUTH2 0x80
/// CRAM-MD5
#define MAIL_USE_CRAM_MD5 0x100
/// Mail sending protocol
class MailSink : public MailProtocol
{
public:
/// Connection setup/shutdown
virtual bool Open
(
/// The transport layer to use
LSocketI *S,
/// The host to connect to
const char *RemoteHost,
/// The local domain
const char *LocalDomain,
/// The sink username (or NULL)
const char *UserName,
/// The sink password (or NULL)
const char *Password,
/// The port to connect with or 0 for default.
int Port,
/// Options: Use any of #MAIL_SSL, #MAIL_USE_STARTTLS, #MAIL_SECURE_AUTH, #MAIL_USE_PLAIN, #MAIL_USE_LOGIN etc or'd together.
int Flags
) = 0;
/// Close the connection
virtual bool Close() = 0;
// Commands available while connected
/// Write the email's contents into the LStringPipe returned from
/// SendStart and then call SendEnd to finish the transaction
virtual LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0) = 0;
/// Finishes the mail send
virtual bool SendEnd(LStringPipe *Sink) = 0;
};
struct ImapMailFlags
{
union
{
struct
{
uint8_t ImapAnswered : 1;
uint8_t ImapDeleted : 1;
uint8_t ImapDraft : 1;
uint8_t ImapFlagged : 1;
uint8_t ImapRecent : 1;
uint8_t ImapSeen : 1;
uint8_t ImapExpunged :1;
};
uint16 All = 0;
};
ImapMailFlags(char *init = 0)
{
if (init)
Set(init);
}
LString Get()
{
char s[256] = "";
int ch = 0;
if (ImapAnswered) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\answered ");
if (ImapDeleted) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\deleted ");
if (ImapDraft) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\draft ");
if (ImapFlagged) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\flagged ");
if (ImapRecent) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\recent ");
if (ImapSeen) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\seen ");
if (ch == 0)
return NULL;
LAssert(ch < sizeof(s));
s[--ch] = 0;
return s;
}
void Set(const char *s)
{
All = 0;
if (!s) s = "";
while (*s)
{
if (*s == '/' || *s == '\\')
{
while (*s == '/' || *s == '\\')
s++;
const char *e = s;
while (*e && isalpha(*e))
e++;
if (!_strnicmp(s, "answered", e-s))
ImapAnswered = true;
else if (!_strnicmp(s, "deleted", e-s))
ImapDeleted = true;
else if (!_strnicmp(s, "draft", e-s))
ImapDraft = true;
else if (!_strnicmp(s, "flagged", e-s))
ImapFlagged = true;
else if (!_strnicmp(s, "recent", e-s))
ImapRecent = true;
else if (!_strnicmp(s, "seen", e-s))
ImapSeen = true;
s = e;
}
else s++;
}
}
ImapMailFlags &operator =(const ImapMailFlags &f)
{
All = f.All;
return *this;
}
bool operator ==(ImapMailFlags &f) const
{
return All == f.All;
}
bool operator !=(ImapMailFlags &f) const
{
return All != f.All;
}
};
/// A bulk mail handling class
class MailTransaction
{
public:
/// The index of the mail in the folder
int Index;
/// \sa #MAIL_POSTED_TO_GUI, #MAIL_EXPLICIT
int Flags;
// bool Delete;
bool Status;
bool Oversize;
/// The mail protocol handler writes the email to this stream
LStreamI *Stream;
/// Flags used on the IMAP protocolf
ImapMailFlags Imap;
/// The user app can use this for whatever
void *UserData;
MailTransaction();
~MailTransaction();
};
/// Return code from MailSrcCallback
enum MailSrcStatus
{
/// Download the whole email
DownloadAll,
/// Download just the top part
DownloadTop,
/// Skip this email
DownloadNone,
/// About the whole receive
DownloadAbort
};
/// The callback function used by MailSource::Receive
typedef MailSrcStatus (*MailSrcCallback)
(
/// The currently executing transaction
MailTransaction *Trans,
/// The size of the email about to be downloaded
uint64 Size,
/// If DownloadTop is returned, you can set the number of lines to retreive here
int *LinesToDownload,
/// The data cookie passed into MailSource::Receive
void *Data
);
/// The callback function used by MailSource::Receive
typedef bool (*MailReceivedCallback)
(
/// The currently executing transaction
MailTransaction *Trans,
/// The data cookie passed into MailSource::Receive
void *Data
);
/// Collection of callbacks called during mail receive. You should zero this
/// entire object before using it. Because if someone adds new callbacks after
/// you write the calling code you wouldn't want to leave some callbacks un-
/// initialized. A NULL callback is ignored.
struct MailCallbacks
{
/// The callback data
void *CallbackData;
/// Called before receiving mail
MailSrcCallback OnSrc;
/// Called after mail received
MailReceivedCallback OnReceive;
};
/// A generic mail source object
class MailSource : public MailProtocol
{
public:
/// Opens a connection to the server
virtual bool Open
(
/// The transport socket
LSocketI *S,
/// The hostname or IP of the server
const char *RemoteHost,
/// The port on the host to connect to
int Port,
/// The username for authentication
const char *User,
/// The password for authentication
const char *Password,
/// [Optional] Persistant storage of settings
LDom *SettingStore,
/// [Optional] Flags: #MAIL_SOURCE_STARTTLS, #MAIL_SOURCE_AUTH, #MAIL_SOURCE_USE_PLAIN, #MAIL_SOURCE_USE_LOGIN
int Flags = 0) = 0;
/// Closes the connection
virtual bool Close() = 0;
/// Returns the number of messages available on the server
virtual ssize_t GetMessages() = 0;
/// Receives a list of messages from the server.
virtual bool Receive
(
/// An array of messages to receive. The MailTransaction objects contains the index of the message to receive
/// and various status values returned after the operation.
LArray &Trans,
/// An optional set of callback functions.
MailCallbacks *Callbacks = 0
) = 0;
/// Deletes a message on the server
virtual bool Delete(int Message) = 0;
/// Gets the size of the message on the server
virtual int Sizeof(int Message) = 0;
/// Gets the size of all the messages on the server
virtual bool GetSizes(LArray &Sizes) { return false; }
/// Gets the unique identifier of the message
virtual bool GetUid(int Message, char *Id, int IdLen) = 0;
/// Gets the unique identifiers of a list of messages
virtual bool GetUidList(LString::Array &Id) = 0;
/// Gets the headers associated with a given message
virtual LString GetHeaders(int Message) = 0;
/// Sets the proxy server. e.g. HTTP mail.
virtual void SetProxy(char *Server, int Port) {}
};
/////////////////////////////////////////////////////////////////////
// Mail IO implementations
/// SMTP implementation
class MailSmtp : public MailSink
{
protected:
bool ReadReply(const char *Str, LStringPipe *Pipe = 0, MailProtocolError *Err = 0);
bool WriteText(const char *Str);
public:
MailSmtp();
~MailSmtp();
bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0);
bool Close();
bool SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err = 0);
LStringPipe *SendData(MailProtocolError *Err = 0);
LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0);
bool SendEnd(LStringPipe *Sink);
// bool Send(MailMessage *Msg, bool Mime = false);
};
class MailSendFolder : public MailSink
{
class MailPostFolderPrivate *d;
public:
MailSendFolder(char *Path);
~MailSendFolder();
bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0);
bool Close();
LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0);
bool SendEnd(LStringPipe *Sink);
};
class MailPop3 : public MailSource
{
protected:
bool ReadReply();
LString ReadMultiLineReply();
int GetInt();
- bool MailIsEnd(char *Ptr, ssize_t Len);
+ bool MailIsEnd(LString &s);
bool ListCmd(const char *Cmd, LHashTbl, bool> &Results);
const char *End;
const char *Marker;
int Messages;
public:
MailPop3();
~MailPop3();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
};
class MailReceiveFolder : public MailSource
{
protected:
class MailReceiveFolderPrivate *d;
public:
MailReceiveFolder(char *Path);
~MailReceiveFolder();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
};
class MailPhp : public MailSource
{
protected:
class MailPhpPrivate *d;
bool Get(LSocketI *S, char *Uri, LStream &Out, bool ChopDot);
public:
MailPhp();
~MailPhp();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
void SetProxy(char *Server, int Port) override;
};
class MailImapFolder
{
friend class MailIMap;
friend struct ImapThreadPrivate;
char Sep;
char *Path;
public:
bool NoSelect;
bool NoInferiors;
bool Marked;
int Exists;
int Recent;
int Deleted;
// int UnseenIndex;
MailImapFolder();
virtual ~MailImapFolder();
char *GetPath();
void SetPath(const char *s);
char *GetName();
void SetName(const char *s);
char GetSep() { return Sep; }
void operator =(LHashTbl,int> &v);
};
class MailIMap : public MailSource
{
protected:
class MailIMapPrivate *d;
char Buf[2048];
List Uid;
LStringPipe ReadBuf;
List Dialog;
void ClearDialog();
void ClearUid();
bool FillUidList();
bool WriteBuf(bool ObsurePass = false, const char *Buffer = 0, bool Continuation = false);
bool ReadResponse(int Cmd = -1, bool Plus = false);
bool Read(LStreamI *Out = 0, int Timeout = -1);
bool ReadLine();
bool IsResponse(const char *Buf, int Cmd, bool &Ok);
void CommandFinished();
public:
typedef LHashTbl,LString> StrMap;
struct StrRange
{
ssize_t Start, End;
void Set(ssize_t s, ssize_t e)
{
Start = s;
End = e;
}
ssize_t Len() { return End - Start; }
};
// Typedefs
struct Untagged
{
LString Cmd;
LString Param;
int Id;
};
/// This callback is used to notify the application using this object of IMAP fetch responses.
/// \returns true if the application wants to continue reading and has taken ownership of the strings in "Parts".
typedef bool (*FetchCallback)
(
/// The IMAP object
class MailIMap *Imap,
/// The message sequence number
uint32_t Msg,
/// The fetch parts (which the callee needs to own if returning true)
StrMap &Parts,
/// The user data passed to the Fetch function
void *UserData
);
// Object
MailIMap();
~MailIMap();
// Mutex
bool Lock(const char *file, int line);
bool LockWithTimeout(int Timeout, const char *file, int line);
void Unlock();
// General
char GetFolderSep();
char *EncodePath(const char *Path);
char *GetCurrentPath();
bool GetExpungeOnExit();
void SetExpungeOnExit(bool b);
bool ServerOption(char *Opt);
bool IsOnline();
const char *GetWebLoginUri();
void SetParentWindow(LViewI *wnd);
void SetCancel(LCancel *Cancel);
ssize_t ParseImapResponse(char *Buffer, ssize_t BufferLen, LArray &Ranges, int Names);
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override; // Non-threadsafe soft close (normal operation)
bool GetCapabilities(LArray &s);
// Commands available while connected
bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override;
ssize_t GetMessages() override;
bool Delete(int Message) override;
bool Delete(bool ByUid, const char *Seq);
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
char *SequenceToString(LArray *Seq);
// Imap specific commands
/// This method wraps the imap FETCH command
/// \returns the number of records accepted by the callback fn
int Fetch
(
/// True if 'Seq' is a UID, otherwise it's a sequence
bool ByUid,
/// The sequence number or UID
const char *Seq,
/// The parts to retrieve
const char *Parts,
/// Data is returned to the caller via this callback function
FetchCallback Callback,
/// A user defined param to pass back to the 'Callback' function.
void *UserData,
/// [Optional] The raw data received will be written to this stream if provided, else NULL.
LStreamI *RawCopy = NULL,
/// [Optional] The rough size of the fetch... used to pre-allocate a buffer to receive data.
int64 SizeHint = -1,
/// [Optional] An error object
LError *Error = NULL
);
/// Appends a message to the specified folder
bool Append
(
/// The folder to write to
const char *Folder,
/// [Optional] Flags for the message
ImapMailFlags *Flags,
/// The rfc822 body of the message
const char *Msg,
/// [Out] The UID of the message appended (if known, can be empty if not known)
LString &NewUid
);
bool GetFolders(LArray &Folders);
bool SelectFolder(const char *Path, StrMap *Values = 0);
char *GetSelectedFolder();
int GetMessages(const char *Path);
bool CreateFolder(MailImapFolder *f);
bool DeleteFolder(const char *Path);
bool RenameFolder(const char *From, const char *To);
bool SetFolderFlags(MailImapFolder *f);
/// Expunges (final delete) any deleted messages the current folder.
bool ExpungeFolder();
// Uid methods
bool CopyByUid(LArray &InUids, const char *DestFolder);
bool SetFlagsByUid(LArray &Uids, const char *Flags);
/// Idle processing...
/// \returns true if something happened
bool StartIdle();
// bool OnIdle(int Timeout, LArray &Resp);
bool OnIdle(int Timeout, LString::Array &Resp);
bool FinishIdle();
bool Poll(int *Recent = 0, LArray *New = 0);
bool Status(char *Path, int *Recent);
bool Search(bool Uids, LArray &SeqNumbers, const char *Filter);
// Utility
static bool Http(LSocketI *S,
LAutoString *OutHeaders,
LAutoString *OutBody,
int *StatusCode,
const char *InMethod,
const char *InUri,
const char *InHeaders,
const char *InBody);
};
#endif
diff --git a/include/lgi/common/StringClass.h b/include/lgi/common/StringClass.h
--- a/include/lgi/common/StringClass.h
+++ b/include/lgi/common/StringClass.h
@@ -1,1235 +1,1240 @@
/*
* A mis-guided attempt to make a string class look and feel like a python string.
*
* Author: Matthew Allen
* Email: fret@memecode.com
* Created: 16 Sept 2014
*/
#pragma once
#include
#include
#include
#if defined(_MSC_VER) || defined(__GTK_H__)
// This fixes compile errors in VS2008/Gtk
#undef _SIGN_DEFINED
#undef abs
#endif
#include
#if defined(_MSC_VER) && _MSC_VER < 1800/*_MSC_VER_VS2013*/
#include
#define PRId64 "I64i"
#else
#define __STDC_FORMAT_MACROS 1
#include
#include
#ifndef PRId64
#warning "PRId64 not defined."
#define PRId64 "Ld"
#endif
#endif
#include "LgiOsDefs.h"
#include "lgi/common/Unicode.h"
#include "lgi/common/Array.h"
#ifndef IsDigit
#define IsDigit(ch) ((ch) >= '0' && (ch) <= '9')
#endif
LgiExtern int LPrintf(class LString &Str, const char *Format, va_list &Arg);
/// A pythonic string class.
class LString
{
protected:
/// This structure holds the string's data itself and is shared
/// between one or more LString instances.
struct RefStr
{
/// A reference count
int32 Refs;
/// The bytes in 'Str' not including the NULL terminator
size_t Len;
/// The first byte of the string. Further bytes are allocated
/// off the end of the structure using malloc. This must always
/// be the last element in the struct.
char Str[1];
} *Str;
inline void _strip(LString &ret, const char *set, bool left, bool right)
{
if (!Str)
return;
char *s = Str->Str;
char *e = s + Str->Len;
if (!set) set = " \t\r\n";
if (left)
{
while (s < e && strchr(set, *s))
s++;
}
if (right)
{
while (e > s && strchr(set, e[-1]))
e--;
}
if (e > s)
ret.Set(s, e - s);
}
public:
#ifdef LGI_UNIT_TESTS
static int32 RefStrCount;
#endif
/// A copyable array of strings
class Array : public LArray
{
public:
Array(size_t PreAlloc = 0) : LArray(PreAlloc) {}
Array(const Array &a)
{
*this = (Array&)a;
}
Array &operator =(const Array &a)
{
SetFixedLength(false);
*((LArray*)this) = a;
SetFixedLength(true);
return *this;
}
Array &operator +=(const Array &a)
{
SetFixedLength(false);
Add(a);
SetFixedLength(true);
return *this;
}
};
/// Empty constructor
LString()
{
Str = NULL;
}
// This odd looking constructor allows the object to be used as the value type
// in a GHashTable, where the initialiser is '0', an integer.
LString(int i)
{
Str = NULL;
}
#ifndef _MSC_VER
// This odd looking constructor allows the object to be used as the value type
// in a GHashTable, where the initialiser is '0', an integer.
LString(long int i)
{
Str = NULL;
}
#endif
/// String constructor
LString(const char *str, ptrdiff_t bytes)
{
Str = NULL;
Set(str, bytes);
}
/// const char* constructor
LString(const char *str)
{
Str = NULL;
Set(str);
}
/// const char16* constructor
LString(const wchar_t *str, ptrdiff_t wchars = -1)
{
Str = NULL;
SetW(str, wchars);
}
#if defined(_WIN32) || defined(MAC)
/// const uint32* constructor
LString(const uint32_t *str, ptrdiff_t chars = -1)
{
Str = NULL;
if (chars < 0)
chars = Strlen(str);
ptrdiff_t utf_len = 0;
const uint32_t *end = str + chars;
const uint32_t *c = str;
while (c < end)
{
uint8_t utf[6], *u = utf;
ssize_t len = sizeof(utf);
if (!LgiUtf32To8(*c++, u, len))
break;
utf_len += u - utf;
}
if (Length((uint32_t)utf_len))
{
c = str;
uint8_t *u = (uint8_t*)Str->Str;
ssize_t len = Str->Len;
while (c < end)
{
if (!LgiUtf32To8(*c++, u, len))
break;
}
*u++ = 0;
}
}
#endif
/// LString constructor
LString(const LString &s)
{
Str = s.Str;
if (Str)
Str->Refs++;
}
~LString()
{
Empty();
}
/// Removes a reference to the string and deletes if needed
void Empty()
{
if (!Str) return;
Str->Refs--;
if (Str->Refs < 0)
{
assert(!"Invalid refs");
}
if (Str->Refs == 0)
{
free(Str);
#ifdef LGI_UNIT_TESTS
RefStrCount--;
#endif
}
Str = NULL;
}
/// Returns the pointer to the string data
char *Get() const
{
return Str ? Str->Str : NULL;
}
/// Sets the string to a new value
bool Set
(
/// Can be a pointer to string data or NULL to create an empty buffer (requires valid length)
const char *str,
/// Byte length of input string or -1 to copy till the NULL terminator.
ptrdiff_t bytes = -1
)
{
Empty();
if (bytes < 0)
{
if (str)
bytes = strlen(str);
else
return false;
}
Str = (RefStr*)malloc(sizeof(RefStr) + bytes);
if (!Str)
return false;
Str->Refs = 1;
Str->Len = (uint32_t)bytes;
#ifdef LGI_UNIT_TESTS
RefStrCount++;
#endif
if (str)
memcpy(Str->Str, str, bytes);
Str->Str[bytes] = 0;
return true;
}
/// Sets the string to a new value
bool SetW
(
/// Can be a pointer to string data or NULL to create an empty buffer (requires valid length)
const wchar_t *str,
/// Number of 'char16' values in the input string or -1 to copy till the NULL terminator.
ptrdiff_t wchars = -1
)
{
size_t Sz = WideToUtf8Len(str, wchars);
if (Length(Sz))
{
#ifdef _MSC_VER
const uint16 *i = (const uint16*) str;
ssize_t InLen = wchars >= 0 ? wchars << 1 : 0x7fffffff;
assert(sizeof(*i) == sizeof(*str));
uint8_t *o = (uint8_t*)Str->Str;
ssize_t OutLen = Str->Len;
for (uint32_t ch; ch = LgiUtf16To32(i, InLen); )
{
if (!LgiUtf32To8(ch, o, OutLen))
{
*o = 0;
break;
}
}
#else
uint8_t *o = (uint8_t*)Str->Str;
ssize_t OutLen = Str->Len;
if (wchars >= 0)
{
const wchar_t *end = str + wchars;
for (const wchar_t *ch = str; ch < end; ch++)
{
if (!LgiUtf32To8(*ch, o, OutLen))
{
*o = 0;
break;
}
}
}
else
{
for (const wchar_t *ch = str; *ch; ch++)
{
if (!LgiUtf32To8(*ch, o, OutLen))
{
*o = 0;
break;
}
}
}
#endif
*o = 0;
}
return true;
}
/// Equality operator (case sensitive)
bool operator ==(const LString &s)
{
const char *a = Get();
const char *b = s.Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !strcmp(a, b);
}
bool operator !=(const LString &s)
{
return !(*this == s);
}
// Equality function (default: case insensitive, as the operator== is case sensitive)
bool Equals(const char *b, bool CaseInsensitive = true) const
{
const char *a = Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b));
}
// Equality function (default: case insensitive, as the operator== is case sensitive)
bool Equals(const LString &s, bool CaseInsensitive = true) const
{
const char *a = Get();
const char *b = s.Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b));
}
/// Assignment operator to copy one string to another
LString &operator =(const LString &s)
{
if (this != &s)
{
Empty();
Str = s.Str;
if (Str)
Str->Refs++;
}
return *this;
}
/// Equality with a C string (case sensitive)
bool operator ==(const char *b)
{
const char *a = Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !strcmp(a, b);
}
bool operator !=(const char *b)
{
return !(*this == b);
}
/// Assignment operators
LString &operator =(const char *s)
{
if (Str == NULL ||
s < Str->Str ||
s > Str->Str + Str->Len)
{
Empty();
Set(s);
}
else if (s != Str->Str)
{
// Special case for setting it to part of itself
// If you try and set a string to the start, it's a NOP
ptrdiff_t Off = s - Str->Str;
memmove(Str->Str, s, Str->Len - Off + 1);
Str->Len -= (uint32_t)Off;
}
return *this;
}
LString &operator =(const wchar_t *s)
{
SetW(s);
return *this;
}
LString &operator =(int val)
{
char n[32];
sprintf_s(n, sizeof(n), "%i", val);
Set(n);
return *this;
}
LString &operator =(int64 val)
{
char n[32];
sprintf_s(n, sizeof(n), "%" PRId64, (int64_t)val);
Set(n);
return *this;
}
/// Cast to C string operator
operator char *() const
{
return Str && Str->Len > 0 ? Str->Str : NULL;
}
int operator -(const LString &s) const
{
return Stricmp(Get(), s.Get());
}
/// Concatenation operator
LString operator +(const LString &s)
{
LString Ret;
size_t Len = Length() + s.Length();
if (Ret.Set(NULL, Len))
{
char *p = Ret.Get();
if (p)
{
if (Str)
{
memcpy(p, Str->Str, Str->Len);
p += Str->Len;
}
if (s.Str)
{
memcpy(p, s.Str->Str, s.Str->Len);
p += s.Str->Len;
}
*p++ = 0;
}
}
return Ret;
}
/// Concatenation / assignment operator
LString &operator +=(const LString &s)
{
ssize_t Len = Length() + s.Length();
ssize_t Alloc = sizeof(RefStr) + Len;
RefStr *rs = (RefStr*)malloc(Alloc);
if (rs)
{
rs->Refs = 1;
rs->Len = Len;
#ifdef LGI_UNIT_TESTS
RefStrCount++;
#endif
char *p = rs->Str;
if (Str)
{
memcpy(p, Str->Str, Str->Len);
p += Str->Len;
}
if (s.Str)
{
memcpy(p, s.Str->Str, s.Str->Len);
p += s.Str->Len;
}
*p++ = 0;
assert(p - (char*)rs <= Alloc);
Empty();
Str = rs;
}
return *this;
}
LString operator *(ssize_t mul)
{
LString s;
if (Str)
{
s.Length(Str->Len * mul);
char *out = s.Get();
for (ssize_t i=0; iLen)
memcpy(out, Str->Str, Str->Len);
*out = 0;
}
return s;
}
/// Gets the length in bytes
size_t Length() const
{
return Str ? Str->Len : 0;
}
- size_t Length(size_t NewLen)
+ size_t Length(ssize_t NewLen)
{
- if (Str)
+ if (NewLen < 0)
{
- if (NewLen <= Str->Len)
+ LAssert(!"No negative string len.");
+ Empty();
+ }
+ else if (Str)
+ {
+ if (NewLen <= (ssize_t)Str->Len)
{
Str->Len = NewLen;
Str->Str[NewLen] = 0;
}
else
{
RefStr *n = (RefStr*)malloc(sizeof(RefStr) + NewLen);
if (n)
{
n->Len = NewLen;
n->Refs = 1;
memcpy(n->Str, Str->Str, Str->Len);
n->Str[Str->Len] = 0; // NULL terminate...
Empty(); // Deref the old string...
Str = n;
}
else return 0;
}
}
else
{
Str = (RefStr*)malloc(sizeof(RefStr) + NewLen);
if (Str)
{
Str->Len = NewLen;
Str->Refs = 1;
Str->Str[0] = 0; // NULL terminate...
}
else return 0;
}
- return Str->Len;
+ return Str ? Str->Len : 0;
}
/// Splits the string into parts using a separator
Array Split(const char *Sep, int Count = -1, bool CaseSen = false)
{
Array a;
if (Str && Sep)
{
const char *s = Str->Str, *Prev = s;
const char *end = s + Str->Len;
size_t SepLen = strlen(Sep);
if (s[Str->Len] == 0)
{
while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep)))
{
if (s > Prev)
a.New().Set(Prev, s - Prev);
s += SepLen;
Prev = s;
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
if (Prev < end)
a.New().Set(Prev, end - Prev);
a.SetFixedLength();
}
else assert(!"String not NULL terminated.");
}
return a;
}
/// Splits the string into parts using a separator
Array RSplit(const char *Sep, int Count = -1, bool CaseSen = false)
{
Array a;
if (Str && Sep)
{
const char *s = Get();
size_t SepLen = strlen(Sep);
LArray seps;
while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep)))
{
seps.Add(s);
s += SepLen;
}
ssize_t i, Last = seps.Length() - 1;
LString p;
for (i=Last; i>=0; i--)
{
const char *part = seps[i] + SepLen;
if (i == Last)
p.Set(part);
else
p.Set(part, seps[i+1]-part);
a.AddAt(0, p);
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
const char *End = seps[i > 0 ? i : 0];
p.Set(Get(), End - Get());
a.AddAt(0, p);
}
a.SetFixedLength();
return a;
}
/// Splits the string into parts using delimiter chars
Array SplitDelimit(const char *Delimiters = NULL, int Count = -1, bool GroupDelimiters = true) const
{
Array a;
if (Str)
{
const char *delim = Delimiters ? Delimiters : " \t\r\n";
const char *s = Get(), *end = s + Length();
while (s < end)
{
// Skip over non-delimiters
const char *e = s;
while (e < end && !strchr(delim, *e))
e++;
if (e > s || !GroupDelimiters)
a.New().Set(s, e - s);
s = e;
if (*s) s++;
if (GroupDelimiters)
{
// Skip any delimiters
while (s < end && strchr(delim, *s))
s++;
}
// Create the string
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
if
(
s < end ||
(
!GroupDelimiters &&
s > Get() &&
strchr(delim, s[-1])
)
)
a.New().Set(s);
}
a.SetFixedLength();
return a;
}
/// Joins an array of strings using a separator
LString Join(const LArray &a)
{
LString ret;
if (a.Length() == 0)
return ret;
char *Sep = Get();
size_t SepLen = Sep ? strlen(Sep) : 0;
size_t Bytes = SepLen * (a.Length() - 1);
LArray ALen;
for (unsigned i=0; iStr;
LArray Matches;
while ((Match = (CaseSen ? strstr(Match, Old) : Stristr(Match, Old))))
{
Matches.Add(Match);
if (Count >= 0 && (int)Matches.Length() >= Count)
break;
Match += OldLen;
}
size_t NewSize = Str->Len + (Matches.Length() * (NewLen - OldLen));
s.Length((uint32_t)NewSize);
char *Out = s.Get();
char *In = Str->Str;
// For each match...
for (unsigned i=0; iStr + Str->Len;
if (In < End)
{
ptrdiff_t Bytes = End - In;
memcpy(Out, In, Bytes);
Out += Bytes;
}
assert(Out - s.Get() == NewSize); // Check we got the size right...
*Out = 0; // Null terminate
}
else
{
s = *this;
}
return s;
}
/// Convert string to double
double Float()
{
return Str ? atof(Str->Str) : NAN;
}
/// Convert to integer
int64 Int(int Base = 10)
{
if (!Str)
return -1;
if
(
Str->Len > 2 &&
Str->Str[0] == '0' &&
(
Str->Str[1] == 'x' ||
Str->Str[1] == 'X'
)
)
{
return Atoi(Str->Str+2, 16);
}
return Atoi(Str->Str, Base);
}
/// Checks if the string is a number
bool IsNumeric()
{
if (!Str)
return false;
for (char *s = Str->Str; *s; s++)
{
if (!IsDigit(*s) && !strchr("e-+.", *s))
return false;
}
return true;
}
/// Check for non whitespace
bool IsEmpty()
{
if (!Str)
return true;
for (char *s = Str->Str; *s; s++)
{
if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
return false;
}
return true;
}
/// Reverses all the characters in the string
LString Reverse()
{
LString s;
if (Length() > 0)
{
s = Str->Str;
for (auto *a = s.Get(), *b = s.Get() + s.Length() - 1; a < b; a++, b--)
{
char t = *a;
*a = *b;
*b = t;
}
}
return s;
}
/// Find a sub-string
ssize_t Find(const char *needle, ssize_t start = 0, ssize_t end = -1)
{
if (!needle) return -1;
char *c = Get();
if (!c) return -1;
char *pos = c + start;
while (c < pos)
{
if (!*c)
return -1;
c++;
}
char *found = (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle);
return (found) ? found - Get() : -1;
}
/// Reverse find a string (starting from the end)
ssize_t RFind(const char *needle, int start = 0, ssize_t end = -1)
{
if (!needle) return -1;
char *c = Get();
if (!c) return -1;
char *pos = c + start;
while (c < pos)
{
if (!*c)
return -1;
c++;
}
char *found, *prev = NULL;
size_t str_len = strlen(needle);
while
((
found =
(
(end > 0) ?
Strnstr(c, needle, end - start) :
strstr(c, needle)
)
))
{
prev = found;
c = found + str_len;
}
return (prev) ? prev - Get() : -1;
}
/// Returns a copy of the string with all the characters converted to lower case
LString Lower()
{
LString s;
if (Str && s.Set(Str->Str, Str->Len))
Strlwr(s.Get());
return s;
}
/// Returns a copy of the string with all the characters converted to upper case
LString Upper()
{
LString s;
if (Str && s.Set(Str->Str, Str->Len))
Strupr(s.Get());
return s;
}
void Swap(LString &s)
{
LSwap(Str, s.Str);
}
/// Gets the character at 'index'
int operator() (ssize_t index) const
{
if (!Str)
return 0;
char *c = Str->Str;
if (index < 0)
{
size_t idx = Str->Len + index;
return c[idx];
}
else if (index < (int)Str->Len)
{
return c[index];
}
return 0;
}
/// Gets the string between at 'start' and 'end' (not including the end'th character)
LString operator() (ptrdiff_t start, ptrdiff_t end)
{
LString s;
if (Str)
{
ptrdiff_t start_idx = start < 0 ? Str->Len + start + 1 : start;
if (start_idx >= 0 && (uint32_t)start_idx < Str->Len)
{
ptrdiff_t end_idx = end < 0 ? Str->Len + end + 1 : end;
if (end_idx >= start_idx && (uint32_t)end_idx <= Str->Len)
s.Set(Str->Str + start_idx, end_idx - start_idx);
}
}
return s;
}
/// Strip off any leading and trailing characters from 'set' (or whitespace if NULL)
LString Strip(const char *set = NULL)
{
LString ret;
_strip(ret, set, true, true);
return ret;
}
/// Strip off any leading characters from 'set' (or whitespace if NULL)
LString LStrip(const char *set = NULL)
{
LString ret;
_strip(ret, set, true, false);
return ret;
}
/// Strip off any trailing characters from 'set' (or whitespace if NULL)
LString RStrip(const char *set = NULL)
{
LString ret;
_strip(ret, set, false, true);
return ret;
}
/// Prints a formatted string to this object
int Printf(const char *Fmt, ...)
{
Empty();
va_list Arg;
va_start(Arg, Fmt);
int Bytes = Printf(Arg, Fmt);
va_end(Arg);
return Bytes;
}
/// Prints a varargs string
int Printf(va_list &Arg, const char *Fmt)
{
Empty();
return LPrintf(*this, Fmt, Arg);
}
static LString Escape(const char *In, ssize_t Len = -1, const char *Chars = "\r\n\b\\\'\"")
{
LString 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++] = '\\';
switch (*In)
{
#undef EscChar
#define EscChar(from, to) \
case from: Buf[Ch++] = to; break
EscChar('\n', 'n');
EscChar('\r', 'r');
EscChar('\\', '\\');
EscChar('\b', 'b');
EscChar('\a', 'a');
EscChar('\t', 't');
EscChar('\v', 'v');
EscChar('\'', '\'');
EscChar('\"', '\"');
EscChar('&', '&');
EscChar('?', '?');
#undef EscChar
default: Ch += sprintf_s(Buf+Ch, sizeof(Buf)-Ch, "x%02x", *In); break;
}
}
else Buf[Ch++] = *In;
In++;
}
if (Ch > 0)
{
Buf[Ch] = 0;
s += Buf;
}
}
return s;
}
template
static LString UnEscape(const T *In, ssize_t Len = -1)
{
if (!In)
return LString();
LString s;
if (Len < 0)
// As memory allocation/copying around data is far slower then
// just scanning the string for size... don't try and chunk the
// processing.
Len = Strlen(In);
if (!s.Length(Len))
return LString();
auto *Out = s.Get();
auto *End = In + Len;
while (In < End)
{
if (*In == '\\')
{
In++;
switch (*In)
{
case 'n':
case 'N':
*Out++ = '\n';
break;
case 'r':
case 'R':
*Out++ = '\r';
break;
case 'b':
case 'B':
*Out++ = '\b';
break;
case 't':
case 'T':
*Out++ = '\t';
break;
default:
*Out++ = *In;
break;
case 0:
break;
}
if (*In)
In++;
else
break;
}
else
*Out++ = *In++;
}
// Trim excess size off string
s.Length(Out - s.Get());
return s;
}
static LString UnEscape(LString s) { return UnEscape(s.Get(), s.Length()); }
#if defined(__GTK_H__)
#elif defined(MAC) // && __COREFOUNDATION_CFBASE__
LString(const CFStringRef r)
{
Str = NULL;
*this = r;
}
LString &operator =(CFStringRef r)
{
if (r)
{
CFIndex length = CFStringGetLength(r);
CFRange range = CFRangeMake(0, length);
CFIndex usedBufLen = 0;
CFIndex slen = CFStringGetBytes(r,
range,
kCFStringEncodingUTF8,
'?',
false,
NULL,
0,
&usedBufLen);
if (Set(NULL, usedBufLen))
{
slen = CFStringGetBytes( r,
range,
kCFStringEncodingUTF8,
'?',
false,
(UInt8*)Str->Str,
Str->Len,
&usedBufLen);
Str->Str[usedBufLen] = 0; // NULL terminate
}
}
return *this;
}
CFStringRef CreateStringRef()
{
char *s = Get();
if (!s)
return NULL;
return CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8);
}
#ifdef __OBJC__
NSString *NsStr()
{
if (Str)
return [[NSString alloc] initWithBytes:Str->Str length:Str->Len encoding:NSUTF8StringEncoding];
return nil;
}
bool operator=(NSString *const s)
{
*this = [s UTF8String];
return !IsEmpty();
}
LString(NSString *const s)
{
Str = NULL;
*this = [s UTF8String];
}
#endif
#endif
};
diff --git a/src/common/Net/Mail.cpp b/src/common/Net/Mail.cpp
--- a/src/common/Net/Mail.cpp
+++ b/src/common/Net/Mail.cpp
@@ -1,2708 +1,2709 @@
/*hdr
** FILE: Mail.cpp
** AUTHOR: Matthew Allen
** DATE: 28/5/98
** DESCRIPTION: Mail app
**
** Copyright (C) 1998, Matthew Allen
** fret@memecode.com
*/
#include
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Mail.h"
#include "lgi/common/Base64.h"
#include "lgi/common/NetTools.h"
#include "lgi/common/DateTime.h"
#include "lgi/common/DocView.h"
#include "lgi/common/Store3Defs.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/TextConvert.h"
#include "lgi/common/Mime.h"
#include "../Hash/md5/md5.h"
const char *sTextPlain = "text/plain";
const char *sTextHtml = "text/html";
const char *sTextXml = "text/xml";
const char *sApplicationInternetExplorer = "application/internet-explorer";
const char sMultipartMixed[] = "multipart/mixed";
const char sMultipartEncrypted[] = "multipart/encrypted";
const char sMultipartSigned[] = "multipart/signed";
const char sMultipartAlternative[] = "multipart/alternative";
const char sMultipartRelated[] = "multipart/related";
const char sAppOctetStream[] = "application/octet-stream";
//////////////////////////////////////////////////////////////////////////////////////////////////
LogEntry::LogEntry(LColour col)
{
c = col;
}
bool LogEntry::Add(const char *t, ssize_t len)
{
if (!t)
return false;
if (len < 0)
len = strlen(t);
/*
// Strip off any whitespace on the end of the line.
while (len > 0 && strchr(" \t\r\n", t[len-1]))
len--;
*/
LAutoWString w(Utf8ToWide(t, len));
if (!w)
return false;
size_t ch = StrlenW(w);
return Txt.Add(w, ch);
}
bool Base64Str(LString &s)
{
LString b64;
ssize_t Base64Len = BufferLen_BinTo64(s.Length());
if (!b64.Set(NULL, Base64Len))
return false;
#ifdef _DEBUG
ssize_t Ch =
#endif
ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length());
LAssert(Ch == b64.Length());
s = b64;
return true;
}
bool UnBase64Str(LString &s)
{
LString Bin;
ssize_t BinLen = BufferLen_64ToBin(s.Length());
if (!Bin.Set(NULL, BinLen))
return false;
ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length());
LAssert(Ch <= (int)Bin.Length());
s = Bin;
s.Get()[Ch] = 0;
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// returns the maximum length of the lines contained in the string
int MaxLineLen(char *Text)
{
if (!Text)
return false;
int Max = 0;
int i = 0;
for (char *c = Text; *c; c++)
{
if (*c == '\r')
{
// return
}
else if (*c == '\n')
{
// eol
Max = MAX(i, Max);
i = 0;
}
else
{
// normal char
i++;
}
}
return Max;
}
bool IsDotLined(char *Text)
{
if (Text)
{
for (char *l = Text; l && *l; )
{
if (l[0] == '.')
{
if (l[1] == '\n' || l[1] == 0)
{
return true;
}
}
l = strchr(l, '\n');
if (l) l++;
}
}
return false;
}
// Is s a valid non-whitespace string?
bool ValidNonWSStr(const char *s)
{
if (s && *s)
{
while (*s && strchr(" \r\t\n", *s))
{
s++;
}
if (*s)
{
return true;
}
}
return false;
}
void TokeniseStrList(char *Str, List &Output, const char *Delim)
{
if (Str && Delim)
{
char *s = Str;
while (*s)
{
while (*s && strchr(WhiteSpace, *s))
s++;
char *e = s;
for (; *e; e++)
{
if (strchr("\'\"", *e))
{
// handle string constant
char delim = *e++;
e = strchr(e, delim);
}
else if (*e == '<')
{
e = strchr(e, '>');
}
else
{
while (*e && *e != '<' && !IsWhiteSpace(*e) && !strchr(Delim, *e))
e++;
}
if (!e || !*e || strchr(Delim, *e))
{
break;
}
}
ssize_t Len = e ? e - s : strlen(s);
if (Len > 0)
{
char *Temp = new char[Len+1];
if (Temp)
{
memcpy(Temp, s, Len);
Temp[Len] = 0;
Output.Insert(Temp);
}
}
if (e)
{
s = e;
for (; *s && strchr(Delim, *s); s++);
}
else break;
}
}
}
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DeNullText(char *in, ssize_t &len)
{
char *out = in;
char *end = in + len;
while (in < end)
{
if (*in)
{
*out++ = *in;
}
else
{
len--;
}
in++;
}
}
//////////////////////////////////////////////////////////////////////////////
typedef char CharPair[2];
static CharPair Pairs[] =
{
{'<', '>'},
{'(', ')'},
{'\'', '\''},
{'\"', '\"'},
{0, 0},
};
struct MailAddrPart
{
LAutoString Part;
bool Brackets;
bool ValidEmail;
LAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs)
{
char *s = Str;
if (Len < 0)
Len = strlen(s);
while (*s && strchr(WhiteSpace, *s))
{
s++;
Len--;
}
if (!*s)
return LAutoString();
// Get the end of the string...
char *e = s;
if (Len < 0)
e += strlen(s);
else
e += Len;
// Seek back over any trailing whitespace
while (e > s && strchr(WhiteSpace, e[-1]))
e--;
for (CharPair *p = Pairs; (*p)[0]; p++)
{
if ((*p)[0] == *s && (*p)[1] == e[-1])
{
s++;
e--;
if (s < e)
{
// reset search
p = Pairs - 1;
}
else break;
}
}
Len = e - s;
if (Len < 0)
return LAutoString();
return LAutoString(NewStr(s, Len));
}
MailAddrPart(char *s, ssize_t len)
{
ValidEmail = false;
Brackets = false;
if (s)
{
if (len < 0)
len = strlen(s);
while (strchr(WhiteSpace, *s) && len > 0)
{
s++;
len--;
}
Brackets = *s == '<';
Part = RemovePairs(s, len, Pairs);
// ValidEmail = IsValidEmail(Part);
}
}
int Score()
{
if (!Part)
return 0;
return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0);
}
};
int PartCmp(LAutoPtr *a, LAutoPtr *b)
{
return (*b)->Score() - (*a)->Score();
}
bool IsAngleBrackets(LString &s)
{
if (s(0) == '<' && s(-1) == '>')
return true;
return false;
}
void DecodeAddrName(const char *Str, std::function Cb, const char *DefaultDomain)
{
if (!Str)
return;
LString s = Str;
LString non;
LString email;
LString::Array a;
auto startBracket = s.Find("<");
auto endBracket = s.Find(">", startBracket);
if (startBracket >= 0 && endBracket >= 0)
{
// Keep the angle brackets for the time being...
a.New() = s(0, startBracket) + s(++endBracket, -1);
a.New() = s(startBracket, endBracket);
}
else a.New() = s;
for (unsigned i=0; i");
}
else
{
non += a[i];
}
}
if (!email)
{
a = s.SplitDelimit("()");
non.Empty();
for (unsigned i=0; i 0)
{
const char *ChSet = " \t\r\n\'\"<>";
do
{
non = non.Strip(ChSet);
}
while (non.Length() > 0 && strchr(ChSet, non(0)));
}
Cb(non, email.Strip());
}
void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain)
{
DecodeAddrName(Start, [&](LString n, LString a){
Name.Reset(NewStr(n));
Addr.Reset(NewStr(a));
}, DefaultDomain);
}
void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain)
{
DecodeAddrName(Start, [&](LString n, LString a){
Name = n;
Addr = a;
}, DefaultDomain);
}
#if 0
struct LDecodeAddrNameTest
{
LDecodeAddrNameTest()
{
// Testing code
char *Input[] =
{
"\"Sound&Secure@speedytechnical.com\" ",
"\"@MM-Social Mailman List\" ",
"'Matthew Allen (fret)' ",
"Matthew Allen (fret) ",
"\"'Matthew Allen'\" ",
"Matthew Allen",
"fret@memecode.com",
"\"\" ",
" (fret@memecode.com)",
"Matthew Allen ",
"\"Matthew, Allen\" (fret@memecode.com)",
"Matt'hew Allen ",
"john.omalley ",
"Bankers' Association (ABA)",
"'Amy's Mum' ",
"\"Philip Doggett (JIRA)\" ",
"\"group name\" ",
NULL
};
LAutoString Name, Addr;
for (char **i = Input; *i; i++)
{
Name.Reset();
Addr.Reset();
DecodeAddrName(*i, Name, Addr, "name.com");
LgiTrace("N=%-#32s A=%-32s\n", Name, Addr);
}
int asd=0;
}
} DecodeAddrNameTest;
#endif
void StrCopyToEOL(char *d, char *s)
{
if (d && s)
{
while (*s && *s != '\r' && *s != '\n')
{
*d++ = *s++;
}
*d = 0;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
MailTransaction::MailTransaction()
{
Index = -1;
Flags = 0;
Status = false;
Oversize = false;
Stream = 0;
UserData = 0;
}
MailTransaction::~MailTransaction()
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////
FileDescriptor::FileDescriptor()
{
Embeded = 0;
Offset = 0;
Size = 0;
Data = 0;
MimeType = 0;
ContentId = 0;
Lock = 0;
OwnEmbeded = false;
}
FileDescriptor::FileDescriptor(LStreamI *embed, int64 offset, int64 size, char *name)
{
Embeded = embed;
Offset = offset;
Size = size;
Data = 0;
MimeType = 0;
Lock = 0;
ContentId = 0;
OwnEmbeded = false;
if (name)
{
Name(name);
}
}
FileDescriptor::FileDescriptor(char *name)
{
Embeded = 0;
Offset = 0;
Size = 0;
Data = 0;
MimeType = 0;
Lock = 0;
ContentId = 0;
OwnEmbeded = false;
if (name)
{
Name(name);
if (File.Open(name, O_READ))
{
Size = File.GetSize();
File.Close();
}
}
}
FileDescriptor::FileDescriptor(char *data, int64 len)
{
Embeded = 0;
Offset = 0;
MimeType = 0;
Lock = 0;
ContentId = 0;
Size = len;
OwnEmbeded = false;
Data = data ? new uchar[(size_t)Size] : 0;
if (Data)
{
memcpy(Data, data, (size_t)Size);
}
}
FileDescriptor::~FileDescriptor()
{
if (OwnEmbeded)
{
DeleteObj(Embeded);
}
DeleteArray(MimeType);
DeleteArray(ContentId);
DeleteArray(Data);
}
void FileDescriptor::SetOwnEmbeded(bool i)
{
OwnEmbeded = i;
}
void FileDescriptor::SetLock(LMutex *l)
{
Lock = l;
}
LMutex *FileDescriptor::GetLock()
{
return Lock;
}
LStreamI *FileDescriptor::GotoObject()
{
if (Embeded)
{
Embeded->SetPos(Offset);
return Embeded;
}
else if (Name() && File.Open(Name(), O_READ))
{
return &File;
}
else if (Data && Size > 0)
{
DataStream.Reset(new LMemStream(Data, Size, false));
return DataStream;
}
return 0;
}
int FileDescriptor::Sizeof()
{
return (int)Size;
}
uchar *FileDescriptor::GetData()
{
return Data;
}
bool FileDescriptor::Decode(char *ContentType,
char *ContentTransferEncoding,
char *MimeData,
int MimeDataLen)
{
bool Status = false;
int Content = CONTENT_NONE;
if (ContentType && ContentTransferEncoding)
{
// Content-Type: application/octet-stream; name="Scribe.opt"
Content = CONTENT_OCTET_STREAM;
if (strnistr(ContentTransferEncoding, "base64", 1000))
{
Content = CONTENT_BASE64;
}
if (strnistr(ContentTransferEncoding, "quoted-printable", 1000))
{
Content = CONTENT_QUOTED_PRINTABLE;
}
if (Content != CONTENT_NONE)
{
const char *NameKey = "name";
char *n = strnistr(ContentType, NameKey, 1000);
if (n)
{
char *Equal = strchr(n, '=');
if (Equal)
{
Equal++;
while (*Equal && *Equal == '\"')
{
Equal++;
}
char *End = strchr(Equal, '\"');
if (End)
{
*End = 0;
}
Name(Equal);
Status = true;
}
}
}
}
if (Status && MimeData && MimeDataLen > 0 && Content != CONTENT_NONE)
{
Status = false;
char *Base64 = new char[MimeDataLen];
switch (Content)
{
case CONTENT_OCTET_STREAM:
{
Size = 0;
DeleteObj(Data);
Data = new uchar[MimeDataLen];
if (Data)
{
Size = MimeDataLen;
memcpy(Data, MimeData, (size_t)Size);
Status = true;
}
break;
}
case CONTENT_QUOTED_PRINTABLE:
{
Size = 0;
DeleteObj(Data);
Data = new uchar[MimeDataLen+1];
if (Data)
{
char *Out = (char*) Data;
for (int i=0; i= Size - 3;
if (Status)
{
Size = Converted;
}
else
{
DeleteArray(Data);
Size = 0;
}
}
break;
}
}
}
return Status;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
AddressDescriptor::AddressDescriptor(const AddressDescriptor *Copy)
{
if (Copy)
{
Status = Copy->Status;
CC = Copy->CC;
sAddr = Copy->sAddr;
sName = Copy->sName;
}
}
AddressDescriptor::~AddressDescriptor()
{
_Delete();
}
void AddressDescriptor::_Delete()
{
Status = false;
CC = MAIL_ADDR_CC;
sName.Empty();
sAddr.Empty();
}
LString AddressDescriptor::Print()
{
LString s;
char delim = '\'';
if (sName)
{
bool hasSingle = sName.Find("\'") >= 0;
bool hasDouble = sName.Find("\"") >= 0;
if (hasSingle && !hasDouble)
delim = '\"';
}
if (sAddr && sName)
s.Printf("%c%s%c <%s>", delim, sAddr.Get(), delim, sName.Get());
else if (sAddr)
s.Printf("<%s>", sAddr.Get());
else if (sName)
s.Printf("%c%s%c", delim, sName.Get(), delim);
return s;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
MailProtocol::MailProtocol() :
SocketLock("MailProtocol")
{
Buffer[0] = 0;
Logger = 0;
ErrMsgId = 0;
SettingStore = NULL;
Items = 0;
Transfer = 0;
}
MailProtocol::~MailProtocol()
{
CharsetPrefs.DeleteArrays();
}
void MailProtocol::Log(const char *Str, LSocketI::SocketMsgType type)
{
if (Logger && Str)
{
char s[1024];
char *e = s + sizeof(s) - 2;
const char *i = Str;
char *o = s;
while (*i && o < e)
{
*o++ = *i++;
}
while (o > s && (o[-1] == '\r' || o[-1] == '\n'))
o--;
*o++ = '\n';
*o = 0;
Logger->Write(s, o - s, type);
}
}
bool MailProtocol::Error(const char *file, int line, const char *msg, ...)
{
char s[1024];
va_list a;
va_start(a, msg);
vsprintf_s(s, sizeof(s), msg, a);
va_end(a);
Log(s, LSocketI::SocketMsgError);
LgiTrace("%s:%i - Error: %s", file, line, s);
return false;
}
bool MailProtocol::Read()
{
bool Status = false;
if (Socket)
{
Status = Socket->Read(Buffer, sizeof(Buffer), 0) > 0;
}
return Status;
}
bool MailProtocol::Write(const char *Buf, bool LogWrite)
{
bool Status = false;
if (Socket)
{
const char *p = Buf ? Buf : Buffer;
Status = Socket->Write(p, strlen(p), 0) > 0;
if (LogWrite)
{
Log(p, LSocketI::SocketMsgSend);
}
}
return Status;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
#define VERIFY_RET_VAL(Arg) \
{ \
if (!Arg) \
{ \
LMutex::Auto Lck(&SocketLock, _FL); \
Socket.Reset(0); \
return NULL; \
} \
}
#define VERIFY_ONERR(Arg) \
{ \
if (!Arg) \
{ \
LMutex::Auto Lck(&SocketLock, _FL); \
Socket.Reset(0); \
goto CleanUp; \
} \
}
void Reorder(LArray &a, const char *s)
{
for (unsigned i=0; i 0)
{
a.DeleteAt(i, true);
a.AddAt(0, s);
break;
}
}
}
MailSmtp::MailSmtp()
{
}
MailSmtp::~MailSmtp()
{
}
bool MailSmtp::Open(LSocketI *S,
const char *RemoteHost,
const char *LocalDomain,
const char *UserName,
const char *Password,
int Port,
int Flags)
{
char Str[256] = "";
bool Status = false;
if (!RemoteHost)
Error(_FL, "No remote SMTP host.\n");
else
{
strcpy_s(Str, sizeof(Str), RemoteHost);
char *Colon = strchr(Str, ':');
if (Colon)
{
*Colon = 0;
Colon++;
Port = atoi(Colon);
}
if (Port == 0)
{
if (Flags & MAIL_SSL)
Port = SMTP_SSL_PORT;
else
Port = SMTP_PORT;
}
LAutoString Server(TrimStr(Str));
if (Server)
{
if (SocketLock.Lock(_FL))
{
Socket.Reset(S);
SocketLock.Unlock();
}
Socket->SetTimeout(30 * 1000);
char Msg[256];
sprintf_s(Msg, sizeof(Msg), "Connecting to %s:%i...", Server.Get(), Port);
Log(Msg, LSocketI::SocketMsgInfo);
if (!Socket->Open(Server, Port))
Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port);
else
{
LStringPipe Str;
// receive signon message
VERIFY_RET_VAL(ReadReply("220"));
// Rfc 2554 ESMTP authentication
SmtpHello:
sprintf_s(Buffer, sizeof(Buffer), "EHLO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default");
VERIFY_RET_VAL(Write(0, true));
/*bool HasSmtpExtensions =*/ ReadReply("250", &Str);
bool Authed = false;
bool NoAuthTypes = false;
bool SupportsStartTLS = false;
LArray AuthTypes;
// Look through the response for the auth line
LString Response = Str.NewGStr();
if (Response)
{
auto Lines = Response.SplitDelimit("\n");
for (auto &l: Lines)
{
char *AuthStr = stristr(l, "AUTH");
if (AuthStr)
{
// walk through AUTH types
auto Types = LString(AuthStr + 4).SplitDelimit(" ,;");
for (auto &t: Types)
AuthTypes.Add(t);
}
if (stristr(l, "STARTTLS"))
SupportsStartTLS = true;
}
}
if (SupportsStartTLS && TestFlag(Flags, MAIL_USE_STARTTLS))
{
strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n");
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply("220", &Str));
LVariant v;
if (Socket->SetValue(LSocket_Protocol, v="SSL"))
{
Flags &= ~MAIL_USE_STARTTLS;
goto SmtpHello;
}
else
{
// SSL init failed... what to do here?
return false;
}
}
if (TestFlag(Flags, MAIL_USE_AUTH))
{
if (!ValidStr(UserName))
{
// We need a user name in all authentication types.
SetError(L_ERROR_ESMTP_NO_USERNAME, "No username for authentication.");
return false;
}
if (AuthTypes.Length() == 0)
{
// No auth types? huh?
if (TestFlag(Flags, MAIL_USE_PLAIN))
// Force plain type
AuthTypes.Add("PLAIN");
else if (TestFlag(Flags, MAIL_USE_LOGIN))
// Force login type
AuthTypes.Add("LOGIN");
else if (TestFlag(Flags, MAIL_USE_CRAM_MD5))
// Force CRAM MD5 type
AuthTypes.Add("CRAM-MD5");
else if (TestFlag(Flags, MAIL_USE_OAUTH2))
// Force OAUTH2 type
AuthTypes.Add("XOAUTH2");
else
{
// Try all
AuthTypes.Add("PLAIN");
AuthTypes.Add("LOGIN");
AuthTypes.Add("CRAM-MD5");
AuthTypes.Add("XOAUTH2");
}
}
else
{
// Force user preference
if (TestFlag(Flags, MAIL_USE_PLAIN))
Reorder(AuthTypes, "PLAIN");
else if (TestFlag(Flags, MAIL_USE_LOGIN))
Reorder(AuthTypes, "LOGIN");
else if (TestFlag(Flags, MAIL_USE_CRAM_MD5))
Reorder(AuthTypes, "CRAM-MD5");
else if (TestFlag(Flags, MAIL_USE_OAUTH2))
Reorder(AuthTypes, "XOAUTH2");
}
for (auto Auth : AuthTypes)
{
// Try all their auth types against our internally support types
if (Auth.Equals("LOGIN"))
{
VERIFY_RET_VAL(Write("AUTH LOGIN\r\n", true));
VERIFY_RET_VAL(ReadReply("334"));
ZeroObj(Buffer);
ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)UserName, strlen(UserName));
strcat(Buffer, "\r\n");
VERIFY_RET_VAL(Write(0, true));
if (ReadReply("334") && Password)
{
ZeroObj(Buffer);
ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)Password, strlen(Password));
strcat(Buffer, "\r\n");
VERIFY_RET_VAL(Write(0, true));
if (ReadReply("235"))
Authed = true;
}
}
else if (Auth.Equals("PLAIN"))
{
char Ascii[512];
int ch = sprintf_s(Ascii, sizeof(Ascii), "%c%s%c%s", 0, UserName, 0, Password);
char Base64[512] = {0};
ConvertBinaryToBase64(Base64, sizeof(Base64), (uint8_t*)Ascii, ch);
sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", Base64);
VERIFY_RET_VAL(Write(0, true));
if (ReadReply("235"))
{
Authed = true;
}
}
else if (Auth.Equals("CRAM-MD5"))
{
sprintf_s(Buffer, sizeof(Buffer), "AUTH CRAM-MD5\r\n");
VERIFY_RET_VAL(Write(0, true));
if (ReadReply("334"))
{
auto Sp = strchr(Buffer, ' ');
if (Sp)
{
Sp++;
// Decode the server response:
uint8_t Txt[128];
auto InLen = strlen(Sp);
ssize_t TxtLen = ConvertBase64ToBinary(Txt, sizeof(Txt), Sp, InLen);
// Calc the hash:
// https://tools.ietf.org/html/rfc2104
char Key[64] = {0};
memcpy(Key, Password, MIN(strlen(Password), sizeof(Key)));
uint8_t iKey[256];
char oKey[256];
for (unsigned i=0; i<64; i++)
{
iKey[i] = Key[i] ^ 0x36;
oKey[i] = Key[i] ^ 0x5c;
}
memcpy(iKey+64, Txt, TxtLen);
md5_state_t md5;
md5_init(&md5);
md5_append(&md5, iKey, 64 + TxtLen);
md5_finish(&md5, oKey + 64);
md5_init(&md5);
md5_append(&md5, (uint8_t*)oKey, 64 + 16);
char digest[16];
md5_finish(&md5, digest);
char r[256];
int ch = sprintf_s(r, sizeof(r), "%s ", UserName);
for (unsigned i=0; i<16; i++)
ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8_t)digest[i]);
// Base64 encode
ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8_t*)r, ch);
Buffer[Len++] = '\r';
Buffer[Len++] = '\n';
Buffer[Len++] = 0;
VERIFY_RET_VAL(Write(0, true));
if (ReadReply("235"))
Authed = true;
}
}
}
else if (Auth.Equals("XOAUTH2"))
{
auto Log = dynamic_cast(Socket->GetLog());
LOAuth2 Authenticator(OAuth2, UserName, SettingStore, Socket->GetCancel(), Log);
auto Tok = Authenticator.GetAccessToken();
if (Tok)
{
LString s;
s.Printf("user=%s\001auth=Bearer %s\001\001\0", UserName, Tok.Get());
Base64Str(s);
sprintf_s(Buffer, sizeof(Buffer), "AUTH %s %s\r\n", Auth.Get(), s.Get());
VERIFY_RET_VAL(Write(0, true));
Authed = ReadReply("235");
if (!Authed)
{
Authenticator.Refresh();
}
}
}
else
{
LgiTrace("%s:%i - Unsupported auth type '%s'\n", _FL, Auth.Get());
}
if (Authed)
break;
}
if (!Authed)
{
if (NoAuthTypes)
SetError(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports.");
else
{
LString p;
for (auto i : AuthTypes)
{
if (p.Get())
p += ", ";
p += i;
}
SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", p);
}
}
Status = Authed;
}
else
{
Status = true;
}
}
}
}
return Status;
}
bool MailSmtp::WriteText(const char *Str)
{
// we have to convert all strings to CRLF in here
bool Status = false;
if (Str)
{
LMemQueue Temp;
const char *Start = Str;
while (*Str)
{
if (*Str == '\n')
{
// send a string
ssize_t Size = Str-Start;
if (Str[-1] == '\r') Size--;
Temp.Write((uchar*) Start, Size);
Temp.Write((uchar*) "\r\n", 2);
Start = Str + 1;
}
Str++;
}
// send the final string
ssize_t Size = Str-Start;
if (Str[-1] == '\r') Size--;
Temp.Write((uchar*) Start, (int)Size);
Size = (int)Temp.GetSize();
char *Data = new char[(size_t)Size];
if (Data)
{
Temp.Read((uchar*) Data, Size);
Status = Socket->Write(Data, (int)Size, 0) == Size;
DeleteArray(Data);
}
}
return Status;
}
void StripChars(LString &s)
{
s = s.Strip("\r\n");
}
char *CreateAddressTag(List &l, int Type, List *CharsetPrefs)
{
char *Result = 0;
List Addr;
for (auto a: l)
{
if (a->CC == Type)
{
Addr.Insert(a);
}
}
if (Addr.Length() > 0)
{
LStringPipe StrBuf;
StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: ");
for (auto It = Addr.begin(); It != Addr.end(); )
{
auto a = *It;
AddressDescriptor *NextA = *(++It);
char Buffer[256] = "";
StripChars(a->sName);
StripChars(a->sAddr);
if (a->sAddr && strchr(a->sAddr, ','))
{
// Multiple address format
auto t = a->sAddr.SplitDelimit(",");
for (uint32_t i=0; i", t[i].Get());
if (i < t.Length()-1) strcat(Buffer, ",\r\n\t");
StrBuf.Push(Buffer);
Buffer[0] = 0;
}
}
else if (a->sName)
{
// Name and addr
char *Mem = 0;
char *Name = a->sName.Get();
if (Is8Bit(Name))
{
Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs);
}
if (strchr(Name, '\"'))
sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->sAddr.Get());
else
sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->sAddr.Get());
DeleteArray(Mem);
}
else if (a->sAddr)
{
// Just addr
sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->sAddr.Get());
}
if (NextA) strcat(Buffer, ",\r\n\t");
StrBuf.Push(Buffer);
a = NextA;
}
StrBuf.Push("\r\n");
Result = StrBuf.NewStr();
}
return Result;
}
// This class implements a pipe that writes to a socket
class SocketPipe : public LStringPipe
{
LSocketI *s;
MailProtocolProgress *p;
public:
bool Status;
SocketPipe(LSocketI *socket, MailProtocolProgress *progress)
{
s = socket;
p = progress;
Status = true;
}
ssize_t Read(void *Ptr, ssize_t Size, int Flags)
{
return false;
}
int64 SetSize(int64 Size)
{
if (p)
{
p->Start = LCurrentTime();
p->Range = (int)Size;
return Size;
}
return -1;
}
ssize_t Write(const void *InPtr, ssize_t Size, int Flags)
{
char *Ptr = (char*)InPtr;
char *e = Ptr + Size;
while (Ptr < e)
{
ssize_t w = s->Write(Ptr, e - Ptr, 0);
if (w > 0)
{
Ptr += w;
if (p && p->Range && w > 0)
p->Value += w;
}
else break;
}
return Ptr - (char*)InPtr;
}
};
bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err)
{
bool AddrOk = false;
if (To.Length() == 0)
{
ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT;
ErrMsgFmt = "No recipients to send to.";
ErrMsgParam.Empty();
LgiTrace("%s:%i - No recipients.\n", _FL);
return false;
}
// send MAIL message
if (From && ValidStr(From->sAddr))
{
sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->sAddr.Get());
}
else
{
ErrMsgId = L_ERROR_ESMTP_NO_FROM;
ErrMsgFmt = "No 'from' address in email.";
ErrMsgParam.Empty();
LgiTrace("%s:%i - Invalid from '%s'.\n", _FL, From->sAddr.Get());
return false;
}
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply("250", 0, Err));
// send RCPT message
AddrOk = true;
List::I Recip = To.begin();
for (AddressDescriptor *a = *Recip; a; a = *++Recip)
{
LString Addr = ValidStr(a->sAddr) ? a->sAddr : a->sName;
if (ValidStr(Addr))
{
auto Parts = Addr.SplitDelimit(",");
for (auto p: Parts)
{
sprintf_s(Buffer, sizeof(Buffer), "RCPT TO: <%s>\r\n", p.Get());
VERIFY_RET_VAL(Write(0, true));
a->Status = ReadReply("25", 0, Err);
AddrOk |= a->Status != 0; // at least one address is ok
}
}
else if (Err)
{
ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT;
ErrMsgFmt = "Invalid recipient '%s'.";
ErrMsgParam = Addr;
}
}
return AddrOk;
}
LStringPipe *MailSmtp::SendData(MailProtocolError *Err)
{
// send DATA message
sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n");
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply("354", 0, Err));
return new SocketPipe(Socket, Transfer);
}
LStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err)
{
return SendToFrom(To, From, Err) ? SendData(Err) : NULL;
}
bool MailSmtp::SendEnd(LStringPipe *m)
{
bool Status = false;
SocketPipe *Msg = dynamic_cast(m);
if (Msg)
{
// send message terminator and receive reply
if (Msg->Status &&
Msg->Write((void*)"\r\n.\r\n", 5, 0))
{
Status = ReadReply("250");
}
// else
// just close the connection on them
// so nothing gets sent
}
DeleteObj(m);
return Status;
}
/*
bool MailSmtp::Send(MailMessage *Msg, bool Mime)
{
bool Status = false;
if (Socket && Msg)
{
LStringPipe *Sink = SendStart(Msg->To, Msg->From);
if (Sink)
{
// setup a gui progress meter to send the email,
// the length is just a guesstimate as we won't know the exact
// size until we encode it all, and I don't want it hanging around
// in memory at once, so we encode and send on the fly.
int Length = 1024 +
(Msg->GetBody() ? strlen(Msg->GetBody()) : 0);
for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next())
{
Length += f->Sizeof() * 4 / 3;
}
// encode and send message for transport
Msg->Encode(*Sink, 0, this);
Status = SendEnd(Sink);
}
}
return Status;
}
*/
bool MailSmtp::Close()
{
if (Socket)
{
// send QUIT message
sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n");
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply("221"));
LMutex::Auto Lock(&SocketLock, _FL);
Socket.Reset(0);
return true;
}
return false;
}
bool MailSmtp::ReadReply(const char *Str, LStringPipe *Pipe, MailProtocolError *Err)
{
bool Status = false;
if (Socket && Str)
{
ssize_t Pos = 0;
char *Start = Buffer;
ZeroObj(Buffer);
while (Pos < sizeof(Buffer))
{
ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0);
if (Len > 0)
{
char *Eol = strstr(Start, "\r\n");
while (Eol)
{
// wipe EOL chars
*Eol++ = 0;
*Eol++ = 0;
// process
if (Pipe)
{
if (Pipe->GetSize()) Pipe->Push("\n");
Pipe->Push(Start);
}
if (Start[3] == ' ')
{
// end of response
if (!strncmp(Start, Str, strlen(Str)))
{
Status = true;
}
if (Err)
{
Err->Code = atoi(Start);
char *Sp = strchr(Start, ' ');
Err->ErrMsg = Sp ? Sp + 1 : Start;
}
// Log
Log(Start, atoi(Start) >= 400 ? LSocketI::SocketMsgError : LSocketI::SocketMsgReceive);
// exit loop
Pos = sizeof(Buffer);
break;
}
else
{
Log(Start, LSocketI::SocketMsgReceive);
// more lines follow
Start = Eol;
Eol = strstr(Start, "\r\n");
}
}
Pos += Len;
}
else break;
}
if (!Status)
{
SetError(L_ERROR_GENERIC, "Error: %s", Buffer);
}
}
return Status;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
class Mail2Folder : public LStringPipe
{
char File[256];
LFile F;
public:
Mail2Folder(char *Path, List &To)
{
do
{
char n[32];
sprintf_s(n, sizeof(n), "%u.mail", LRand());
LMakePath(File, sizeof(File), Path, n);
}
while (LFileExists(File));
if (F.Open(File, O_WRITE))
{
F.Print("Forward-Path: ");
int i = 0;
for (auto a: To)
{
a->Status = true;
auto Addrs = a->sAddr.SplitDelimit(",");
for (unsigned n=0; n", Addrs[n].Get());
}
}
F.Print("\r\n");
}
}
~Mail2Folder()
{
F.Close();
}
ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0)
{
return F.Read(Buffer, Size, Flags);
}
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
{
return F.Write(Buffer, Size, Flags);
}
};
class MailPostFolderPrivate
{
public:
char *Path;
MailPostFolderPrivate()
{
Path = 0;
}
~MailPostFolderPrivate()
{
DeleteArray(Path);
}
};
MailSendFolder::MailSendFolder(char *Path)
{
d = new MailPostFolderPrivate;
d->Path = NewStr(Path);
}
MailSendFolder::~MailSendFolder()
{
DeleteObj(d);
}
bool MailSendFolder::Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags)
{
return LDirExists(d->Path);
}
bool MailSendFolder::Close()
{
return true;
}
LStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err)
{
return new Mail2Folder(d->Path, To);
}
bool MailSendFolder::SendEnd(LStringPipe *Sink)
{
DeleteObj(Sink);
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
class MailItem
{
public:
char *File;
bool Delete;
MailItem(char *f)
{
File = NewStr(f);
Delete = false;
}
~MailItem()
{
DeleteArray(File);
}
};
class MailReceiveFolderPrivate
{
public:
char *Path;
List Mail;
MailReceiveFolderPrivate()
{
Path = 0;
}
~MailReceiveFolderPrivate()
{
DeleteArray(Path);
Mail.DeleteObjects();
}
void Empty()
{
for (auto m: Mail)
{
if (m->Delete)
{
FileDev->Delete(m->File, false);
}
}
Mail.DeleteObjects();
}
};
MailReceiveFolder::MailReceiveFolder(char *Path)
{
d = new MailReceiveFolderPrivate;
d->Path = NewStr(Path);
}
MailReceiveFolder::~MailReceiveFolder()
{
DeleteObj(d);
}
bool MailReceiveFolder::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags)
{
// We don't use the socket so just free it here...
DeleteObj(S);
// Argument check
if (!LDirExists(d->Path))
return false;
LDirectory Dir;
// Loop through files, looking for email
for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next())
{
if (!Dir.IsDir())
{
if (MatchStr("*.eml", Dir.GetName()) ||
MatchStr("*.mail", Dir.GetName()))
{
char p[300];
Dir.Path(p, sizeof(p));
d->Mail.Insert(new MailItem(p));
}
}
}
return true;
}
bool MailReceiveFolder::Close()
{
d->Empty();
return true;
}
ssize_t MailReceiveFolder::GetMessages()
{
return d->Mail.Length();
}
bool MailReceiveFolder::Receive(LArray &Trans, MailCallbacks *Callbacks)
{
bool Status = false;
for (unsigned i=0; iStream)
{
t->Status = false;
MailItem *m = d->Mail[t->Index];
if (m)
{
LFile i;
if (i.Open(m->File, O_READ))
{
LCopyStreamer c;
if (c.Copy(&i, t->Stream))
{
Status = t->Status = true;
if (Callbacks && Callbacks->OnReceive)
{
Callbacks->OnReceive(t, Callbacks->CallbackData);
}
}
}
}
}
}
return Status;
}
bool MailReceiveFolder::Delete(int Message)
{
MailItem *m = d->Mail[Message];
if (m)
{
m->Delete = true;
return false;
}
return false;
}
int MailReceiveFolder::Sizeof(int Message)
{
MailItem *m = d->Mail[Message];
if (m)
{
return (int)LFileSize(m->File);
}
return 0;
}
bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen)
{
if (Id)
{
MailItem *m = d->Mail[Message];
if (m)
{
char *s = strrchr(m->File, DIR_CHAR);
if (s++)
{
char *e = strchr(s, '.');
if (!e) e = s + strlen(s);
ssize_t Len = e - s;
memcpy(Id, s, Len);
Id[Len] = 0;
return true;
}
}
}
return false;
}
bool MailReceiveFolder::GetUidList(LString::Array &Id)
{
bool Status = false;
for (int i=0; iMail.Length(); i++)
{
char Uid[256];
if (GetUid(i, Uid, sizeof(Uid)))
{
Status = true;
Id.New() = Uid;
}
else
{
Status = false;
break;
}
}
return Status;
}
LString MailReceiveFolder::GetHeaders(int Message)
{
MailItem *m = d->Mail[Message];
if (!m)
return NULL;
LFile i;
if (!i.Open(m->File, O_READ))
return NULL;
LStringPipe o;
LCopyStreamer c;
LHtmlLinePrefix e("", false);
if (!c.Copy(&i, &o, &e))
return NULL;
return o.NewGStr();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
MailPop3::MailPop3()
{
End = "\r\n.\r\n";
Marker = End;
Messages = -1;
}
MailPop3::~MailPop3()
{
}
ssize_t MailPop3::GetMessages()
{
if (Messages < 0)
{
if (Socket && Socket->IsOpen())
{
// see how many messages there are
VERIFY_ONERR(Write("STAT\r\n", true));
VERIFY_ONERR(ReadReply());
Messages = GetInt();
}
else LAssert(!"No socket to get message count.");
}
CleanUp:
return Messages;
}
int MailPop3::GetInt()
{
char Buf[32];
char *Start = strchr(Buffer, ' ');
if (Start)
{
Start++;
char *End = strchr(Start, ' ');
if (End)
{
int Len = (int) (End - Start);
memcpy(Buf, Start, Len);
Buf[Len] = 0;
return atoi(Buf);
}
}
return 0;
}
bool MailPop3::ReadReply()
{
bool Status = false;
if (Socket)
{
ssize_t Pos = 0;
ZeroObj(Buffer);
do
{
ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0);
if (Result <= 0) // an error?
{
// Leave the loop...
break;
}
Pos += Result;
}
while ( !strstr(Buffer, "\r\n") &&
sizeof(Buffer)-Pos > 0);
Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n");
char *Cr = strchr(Buffer, '\r');
if (Cr) *Cr = 0;
if (ValidStr(Buffer))
Log(Buffer, (Status) ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError);
if (Cr) *Cr = '\r';
if (!Status)
{
SetError(L_ERROR_GENERIC, "Error: %s", Buffer);
}
}
return Status;
}
bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results)
{
sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd);
if (!Write(0, true))
return false;
char *b = Buffer;
ssize_t r;
while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0)
{
b += r;
if (Strnstr(Buffer, "\r\n.\r\n", b-Buffer))
break;
}
if (r <= 0)
return false;
auto Lines = LString(Buffer).SplitDelimit("\r\n");
for (unsigned i=1; iGetValue("IsSSL", IsSsl) &&
IsSsl.CastInt32())
Port = POP3_SSL_PORT;
else
Port = POP3_PORT;
}
strcpy_s(Str, sizeof(Str), RemoteHost);
char *Colon = strchr(Str, ':');
if (Colon)
{
*Colon = 0;
Colon++;
Port = atoi(Colon);
}
if (S &&
User &&
Password &&
(Server = TrimStr(Str)))
{
S->SetTimeout(30 * 1000);
ReStartConnection:
if (SocketLock.Lock(_FL))
{
Socket.Reset(S);
SocketLock.Unlock();
}
if (Socket &&
Socket->Open(Server, Port) &&
ReadReply())
{
LVariant NoAPOP = false;
if (SettingStore)
SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP);
if (!NoAPOP.CastInt32())
{
char *s = strchr(Buffer + 3, '<');
if (s)
{
char *e = strchr(s + 1, '>');
if (e)
{
Apop = NewStr(s, e - s + 1);
}
}
}
// login
bool Authed = false;
char *user = (char*) LNewConvertCp("iso-8859-1", User, "utf-8");
char *pass = (char*) LNewConvertCp("iso-8859-1", Password, "utf-8");
if (user && (pass || SecureAuth))
{
bool SecurityError = false;
if (TestFlag(Flags, MAIL_USE_STARTTLS))
{
strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n");
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply());
LVariant v;
if (Socket->SetValue(LSocket_Protocol, v="SSL"))
{
Flags &= ~MAIL_USE_STARTTLS;
}
else
{
SecurityError = true;
}
}
if (!SecurityError && Apop) // GotKey, not implemented
{
// using encrypted password
unsigned char Digest[16];
char HexDigest[33];
// append password
char Key[256];
sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass);
ZeroObj(Digest);
MDStringToDigest(Digest, Key);
for (int i = 0; i < 16; i++)
sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]);
HexDigest[32] = 0;
sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest);
VERIFY_ONERR(Write(0, true));
Authed = ReadReply();
if (!Authed)
{
DeleteArray(Apop);
LVariant NoAPOP = true;
if (SettingStore)
SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP);
S->Close();
goto ReStartConnection;
}
}
if (!SecurityError && SecureAuth)
{
LHashTbl, bool> AuthTypes, Capabilities;
if (ListCmd("AUTH", AuthTypes) &&
ListCmd("CAPA", Capabilities))
{
if (AuthTypes.Find("GSSAPI"))
{
sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n");
VERIFY_ONERR(Write(0, true));
VERIFY_ONERR(ReadReply());
// http://www.faqs.org/rfcs/rfc2743.html
}
}
}
else if (!SecurityError && !Authed)
{
// have to use non-key method
sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user);
VERIFY_ONERR(Write(0, true));
VERIFY_ONERR(ReadReply());
sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass);
VERIFY_ONERR(Write(0, false));
Log("PASS *******", LSocketI::SocketMsgSend);
Authed = ReadReply();
}
DeleteArray(user);
DeleteArray(pass);
}
if (Authed)
{
Status = true;
}
else
{
if (SocketLock.Lock(_FL))
{
Socket.Reset(0);
SocketLock.Unlock();
}
LgiTrace("%s:%i - Failed auth.\n", _FL);
}
}
else
Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port);
}
else Error(_FL, "No user/pass.\n");
}
CleanUp:
DeleteArray(Apop);
DeleteArray(Server);
return Status;
}
-bool MailPop3::MailIsEnd(char *Ptr, ssize_t Len)
+bool MailPop3::MailIsEnd(LString &s)
{
- for (char *c = Ptr; Len-- > 0; c++)
+ ssize_t Len = s.Length();
+ for (auto c = s.Get(); c && Len-- > 0; c++)
{
if (*c != *Marker)
{
Marker = End;
}
if (*c == *Marker)
{
Marker++;
if (!*Marker)
{
return true;
}
}
}
return false;
}
bool MailPop3::Receive(LArray &Trans, MailCallbacks *Callbacks)
{
bool Status = false;
if (Trans.Length() > 0 &&
Socket)
{
for (unsigned n = 0; nIndex;
LStreamI *Msg = Trans[n]->Stream;
if (Msg)
{
int Size = 0;
// Transfer is not null when the caller wants info on the bytes comming in
if (Transfer || Callbacks)
{
// get message size
sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1);
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply());
char *s = strchr(Buffer, ' ');
if (s)
{
s = strchr(s+1, ' ');
if (s)
{
Size = atoi(s);
}
}
}
MailSrcStatus Action = DownloadAll;
int TopLines = 100;
if (Callbacks && Callbacks->OnSrc)
{
Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData);
}
if (Action == DownloadAbort)
{
break;
}
if (Action == DownloadAll ||
Action == DownloadTop)
{
if (Action == DownloadAll)
{
sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1);
}
else
{
sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines);
}
VERIFY_RET_VAL(Write(0, true));
LHtmlLinePrefix End(".\r\n");
if (Transfer)
{
Transfer->Value = 0;
Transfer->Range = Size;
Transfer->Start = LCurrentTime();
}
// Read status line
ZeroObj(Buffer);
ssize_t Used = 0;
bool Ok = false;
bool Finished = false;
int64 DataPos = 0;
while (Socket->IsOpen())
{
ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0);
if (r > 0)
{
DeNullText(Buffer + Used, r);
if (Transfer)
{
Transfer->Value += r;
}
char *Eol = strchr(Buffer, '\n');
if (Eol)
{
Eol++;
Ok = Buffer[0] == '+';
if (Ok)
{
// Log(Buffer, LSocketI::SocketMsgReceive);
// The Buffer was zero'd at the beginning garrenteeing
// NULL termination
size_t Len = strlen(Eol);
ssize_t EndPos = End.IsEnd(Eol, Len);
if (EndPos >= 0)
{
Msg->Write(Eol, EndPos - 3);
Status = Trans[n]->Status = true;
Finished = true;
}
else
{
Msg->Write(Eol, Len);
DataPos += Len;
}
}
else
{
Log(Buffer, LSocketI::SocketMsgError);
Finished = true;
}
break;
}
Used += r;
}
else break;
}
if (!Finished)
{
if (Ok)
{
// Read rest of message
while (Socket->IsOpen())
{
ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0);
if (r > 0)
{
DeNullText(Buffer, r);
if (Transfer)
{
Transfer->Value += r;
}
ssize_t EndPos = End.IsEnd(Buffer, r);
if (EndPos >= 0)
{
ssize_t Actual = EndPos - DataPos - 3;
if (Actual > 0)
{
#ifdef _DEBUG
ssize_t w =
#endif
Msg->Write(Buffer, Actual);
LAssert(w == Actual);
}
// else the end point was in the last buffer
Status = Trans[n]->Status = true;
break;
}
else
{
#ifdef _DEBUG
ssize_t w =
#endif
Msg->Write(Buffer, r);
LAssert(w == r);
DataPos += r;
}
}
else
{
break;
}
}
if (!Status)
{
LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL);
}
}
else
{
LgiTrace("%s:%i - Didn't get Ok.\n", _FL);
break;
}
}
if (Callbacks && Callbacks->OnReceive)
{
Callbacks->OnReceive(Trans[n], Callbacks->CallbackData);
}
if (Transfer)
{
Transfer->Empty();
}
}
else
{
Trans[n]->Oversize = Status = true;
}
if (Items)
{
Items->Value++;
}
}
else
{
LgiTrace("%s:%i - No stream.\n", _FL);
}
}
}
else
{
LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get());
}
return Status;
}
bool MailPop3::GetSizes(LArray &Sizes)
{
if (!Socket)
return false;
strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n");
VERIFY_RET_VAL(Write(0, true));
auto s = ReadMultiLineReply();
if (!s)
return false;
for (auto ln: s.SplitDelimit("\r\n"))
{
auto p = ln.SplitDelimit();
if (p.Length() > 1)
Sizes.Add((int)p.Last().Int());
}
return Sizes.Length() > 0;
}
int MailPop3::Sizeof(int Message)
{
int Size = 0;
if (Socket)
{
sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1);
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply());
char *s = strchr(Buffer, ' ');
if (s)
{
s = strchr(s+1, ' ');
if (s)
{
Size = atoi(s);
}
}
}
return Size;
}
bool MailPop3::Delete(int Message)
{
if (Socket)
{
sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1);
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply());
return true;
}
return false;
}
bool MailPop3::GetUid(int Index, char *Id, int IdLen)
{
if (Socket && Id)
{
sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1);
VERIFY_RET_VAL(Write(0, true));
VERIFY_RET_VAL(ReadReply());
char *Space = strchr(Buffer, ' ');
if (Space)
{
Space = strchr(Space+1, ' ');
if (Space)
{
for (char *s = Space+1; *s; s++)
{
if (*s == '\r' || *s == '\n')
{
*s = 0;
break;
}
}
strcpy_s(Id, IdLen, Space+1);
return true;
}
}
}
return false;
}
bool MailPop3::GetUidList(LString::Array &Id)
{
if (!Socket)
return false;
sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n");
VERIFY_RET_VAL(Write(0, true));
auto Str = ReadMultiLineReply();
if (!Str)
return false;
auto lines = Str.SplitDelimit("\r\n");
for (auto s: lines)
{
if (s(0) != '.')
{
char *Space = strchr(s, ' ');
if (Space++)
Id.New() = Space;
}
}
return true;
}
LString MailPop3::GetHeaders(int Message)
{
if (!Socket)
return NULL;
sprintf_s(Buffer, sizeof(Buffer), "TOP %i 0\r\n", Message + 1);
if (!Write(NULL, true))
return NULL;
return ReadMultiLineReply();
}
LString MailPop3::ReadMultiLineReply()
{
if (!Socket)
{
LAssert(!"No socket.");
return false;
}
LString a;
do
{
- LString s = Socket->Read();
+ auto s = Socket->Read();
if (!s)
break;
a += s;
- if (a[0] != '+')
+ if (!a || a[0] != '+')
return NULL;
}
- while (!MailIsEnd(a.Get(), a.Length()));
+ while (!MailIsEnd(a));
// Strip off the first line...
auto FirstNewLen = a.Find("\n");
return FirstNewLen >= 0 ? a(FirstNewLen, -1) : NULL;
}
bool MailPop3::Close()
{
if (Socket)
{
// logout
VERIFY_RET_VAL(Write("QUIT\r\n", true));
// 2 sec timeout, we don't really care about the server's response
Socket->SetTimeout(2000);
ReadReply();
if (SocketLock.Lock(_FL))
{
Socket.Reset(0);
SocketLock.Unlock();
}
Messages = 0;
return true;
}
return false;
}
diff --git a/src/common/Widgets/TabView.cpp b/src/common/Widgets/TabView.cpp
--- a/src/common/Widgets/TabView.cpp
+++ b/src/common/Widgets/TabView.cpp
@@ -1,1524 +1,1526 @@
/*hdr
** FILE: LTabView.cpp
** AUTHOR: Matthew Allen
** DATE: 20/10/2000
** DESCRIPTION: Lgi self-drawn tab control
**
** Copyright (C) 2000 Matthew Allen
** fret@memecode.com
*/
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/TabView.h"
#include "lgi/common/SkinEngine.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/TableLayout.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/CssTools.h"
#include "lgi/common/Path.h"
enum TabViewStyle
{
TvWinXp, // Skin
TvWin7,
TvMac,
};
#define MAC_STYLE_RADIUS 7
#define MAC_DBL_BUF 1
#if defined(__GTK_H__)
#define TAB_TXT_PAD 2
#else
#define TAB_TXT_PAD 3
#endif
#if defined(MAC) && !LGI_COCOA && !defined(LGI_SDL)
#define MAC_PAINT 1
#else
#define MAC_PAINT 0
#endif
#define TAB_MARGIN_X 10 // Px each side of the text label on the tab
#define CLOSE_BTN_SIZE 8
#define CLOSE_BTN_GAP 8
#define cFocusFore LColour(L_FOCUS_SEL_FORE)
#define cFocusBack LColour(L_FOCUS_SEL_BACK)
////////////////////////////////////////////////////////////////////////////////////////////
class LTabViewPrivate
{
public:
// General
int Current;
LRect TabClient;
bool PourChildren;
// Painting
LRect Inset, Tabs;
int TabsHeight;
double TabsBaseline;
int Depth;
TabViewStyle Style;
enum ResType
{
ResWorkspace,
ResSelectedUnfocused,
ResSelectedFocused,
ResMax
};
LAutoPtr Corners[ResMax];
LColour cBack, cBorder, cFill, cSelUnfoc, cTopEdge, cBottomEdge;
// Scrolling
int Scroll; // number of buttons scrolled off the left of the control
LRect LeftBtn; // scroll left button
LRect RightBtn; // scroll right button
LTabViewPrivate()
{
Depth = 0;
TabsHeight = 0;
TabsBaseline = 0.0;
PourChildren = true;
Current = 0;
TabClient.ZOff(-1, -1);
Scroll = 0;
LeftBtn.ZOff(-1, -1);
RightBtn.ZOff(-1, -1);
Style = TvMac;
}
uint8_t Clamp(int i)
{
if (i < 0) return 0;
if (i > 255) return 255;
return (uint8_t)i;
}
LColour Tint(double amt)
{
auto Bk = cBack.GetGray();
double Ratio = Bk < 100 ? 1.0 / amt : amt;
LColour c( Clamp((int)(Ratio * cBack.r())),
Clamp((int)(Ratio * cBack.g())),
Clamp((int)(Ratio * cBack.b())));
return c;
}
bool DrawCircle(LAutoPtr &Dc, LColour c)
{
if (Dc)
return true;
double r = 7.0;
int x = (int)(r * 2.0);
if (!Dc.Reset(new LMemDC(x, x, System32BitColourSpace)))
return false;
Dc->Colour(0, 32);
Dc->Rectangle();
LPath p;
p.Circle(r, r, r-0.7);
p.SetFillRule(FILLRULE_ODDEVEN);
LSolidBrush s(c);
p.Fill(Dc, s);
p.Empty();
p.Circle(r, r, r);
p.Circle(r, r, r - 1.0);
p.SetFillRule(FILLRULE_ODDEVEN);
LBlendStop Stops[2] = {
{0.0, cTopEdge.c32()},
{1.0, cBottomEdge.c32()}
};
LPointF a(4, 4), b(9, 9);
LLinearBlendBrush s2(a, b, CountOf(Stops), Stops);
p.Fill(Dc, s2);
+ #ifndef WINDOWS
if (Dc->IsPreMultipliedAlpha())
Dc->ConvertPreMulAlpha(true);
+ #endif
return true;
}
void CreateCorners()
{
LAutoPtr &White = Corners[ResWorkspace];
LAutoPtr &Unfoc = Corners[ResSelectedUnfocused];
LAutoPtr &Sel = Corners[ResSelectedFocused];
DrawCircle(White, LColour(L_WORKSPACE));
DrawCircle(Unfoc, cSelUnfoc);
DrawCircle(Sel, cFocusBack);
}
};
struct LTabPagePriv
{
LTabPage *Tab;
bool NonDefaultFont;
LAutoPtr Ds;
LTabPagePriv(LTabPage *t) : Tab(t)
{
NonDefaultFont = false;
}
LDisplayString *GetDs()
{
auto Text = Tab->Name();
if (Text && !Ds)
{
LFont *f = NULL;
auto s = Tab->GetCss();
NonDefaultFont = s ? s->HasFontStyle() : false;
if (NonDefaultFont)
{
if ((f = new LFont))
{
*f = *LSysFont;
if (f->CreateFromCss(s))
Tab->SetFont(f, true);
else
DeleteObj(f);
}
}
else
{
f = Tab->GetFont();
}
if (f)
Ds.Reset(new LDisplayString(f, Text));
else
LAssert(!"no font.");
}
return Ds;
}
};
class TabIterator : public LArray
{
public:
TabIterator(List &l)
{
for (auto c : l)
{
auto p = dynamic_cast(c);
if (p) Add(p);
}
fixed = true;
}
};
////////////////////////////////////////////////////////////////////////////////////////////
// Tab Control
LTabView::LTabView(int id, int x, int y, int cx, int cy, const char *name, int Init) :
ResObject(Res_TabView)
{
d = new LTabViewPrivate;
d->Current = Init;
SetId(id);
LRect r(x, y, x+cx, y+cy);
SetPos(r);
Name(name);
_BorderSize = 0;
SetTabStop(true);
SetPourLargest(true);
#if WINNATIVE
SetDlgCode(DLGC_WANTARROWS);
#endif
}
LTabView::~LTabView()
{
DeleteObj(d);
}
bool LTabView::GetPourChildren()
{
return d->PourChildren;
}
void LTabView::SetPourChildren(bool b)
{
d->PourChildren = b;
}
LTabPage *LTabView::TabAt(int Idx)
{
TabIterator i(Children);
return i[Idx];
}
size_t LTabView::GetTabs()
{
return Children.Length();
}
LTabPage *LTabView::GetCurrent()
{
TabIterator i(Children);
return i[d->Current];
}
int LTabView::TabY()
{
return d->TabsHeight + (TAB_TXT_PAD << 1);
}
void LTabView::OnChildrenChanged(LViewI *Wnd, bool Attaching)
{
if (!Attaching)
{
TabIterator c(Children);
if (d->Current >= c.Length())
d->Current = (int)c.Length() - 1;
#if LGI_VIEW_HANDLE
if (Handle())
#endif
Invalidate();
}
}
#if defined(WINNATIVE)
LViewI *LTabView::FindControl(HWND hCtrl)
{
LViewI *Ctrl = 0;
if (hCtrl == Handle())
{
return this;
}
TabIterator it(Children);
for (int i=0; iFindControl(hCtrl))
return Ctrl;
}
return 0;
}
#endif
LViewI *LTabView::FindControl(int Id)
{
if (GetId() == Id)
{
return this;
}
LViewI *Ctrl;
TabIterator it(Children);
for (int i=0; iFindControl(Id)))
return Ctrl;
}
return 0;
}
bool LTabView::Attach(LViewI *parent)
{
bool Status = LView::Attach(parent);
if (Status)
{
TabIterator it(Children);
LTabPage *p = d->Current < it.Length() ? it[d->Current] : 0;
if (p)
{
OnPosChange();
p->Attach(this);
}
for (int i=0; i_Window = _Window;
}
}
return Status;
}
int64 LTabView::Value()
{
return d->Current;
}
void LTabView::OnCreate()
{
LResources::StyleElement(this);
d->Depth = 0;
LViewI *p = this;
while ((p = p->GetParent()))
{
if (p == (LViewI*)GetWindow())
break;
LTabView *tv = dynamic_cast(p);
if (tv)
d->Depth++;
}
OnStyleChange();
TabIterator it(Children);
LTabPage *page = d->Current < it.Length() ? it[d->Current] : 0;
if (page)
{
page->Attach(this);
page->Visible(true);
}
}
void LTabView::Value(int64 i)
{
if (Children.Length() > 0 &&
i != d->Current)
{
// change tab
TabIterator it(Children);
LTabPage *Old = it[d->Current];
if (Old)
{
// printf("%s:%i - old[%i] hide.\n", _FL, d->Current);
Old->Visible(false);
}
else printf("%s:%i - no old.\n", _FL);
d->Current = (int)MIN(i, (ssize_t)it.Length()-1);
OnPosChange();
LTabPage *p = it[d->Current];
if (p)
{
if (!p->IsAttached())
{
// printf("%s:%i - new[%i] attach %p.\n", _FL, d->Current, p->Handle());
p->Attach(this);
}
// printf("%s:%i - new[%i] visible %p.\n", _FL, d->Current, p->Handle());
p->Visible(true);
}
Invalidate();
SendNotify(LNotifyValueChanged);
// GetWindow()->_Dump();
}
}
LMessage::Result LTabView::OnEvent(LMessage *Msg)
{
return LView::OnEvent(Msg);
}
int LTabView::OnNotify(LViewI *Ctrl, LNotification n)
{
if (GetParent())
{
return GetParent()->OnNotify(Ctrl, n);
}
return 0;
}
bool LTabView::Append(LTabPage *Page, int Where)
{
if (Page)
{
Page->TabCtrl = this;
Page->_Window = _Window;
if (IsAttached() && Children.Length() == 1)
{
Page->Attach(this);
OnPosChange();
}
else
{
Page->Visible(Children.Length() == d->Current);
AddView(Page);
}
Invalidate();
return true;
}
return false;
}
LTabPage *LTabView::Append(const char *name, int Where)
{
LTabPage *Page = new LTabPage(name);
if (Page)
{
Page->TabCtrl = this;
Page->_Window = _Window;
Page->SetParent(this);
if (IsAttached() && Children.Length() == 0)
{
Page->Attach(this);
OnPosChange();
}
else
{
Page->Visible(Children.Length() == d->Current);
AddView(Page);
}
Invalidate();
}
return Page;
}
bool LTabView::Delete(LTabPage *Page)
{
bool Status = false;
TabIterator it(Children);
ssize_t Index = it.IndexOf(Page);
if (Index >= 0)
{
if (Index == d->Current)
{
Status = Page->Detach();
LAssert(Status);
}
else
{
Status = DelView(Page);
LAssert(Status);
}
delete Page;
Value(Index);
}
return Status;
}
LRect <abView::GetTabClient()
{
if (d->Style == TvMac)
{
d->TabClient = CalcInset();
d->TabClient.Inset(2, 2); // The inset border
d->TabClient.y1 = d->Tabs.y2 + 1; // The tab strip
LTabPage *p = Children.Length() ? GetCurrent() : NULL;
if (p && p->GetCss())
{
// Inset by any padding
LCss::Len l;
d->TabClient.x1 += (l = p->GetCss()->PaddingLeft()).IsValid() ? l.ToPx(d->TabClient.X(), GetFont()) : 0;
d->TabClient.y1 += (l = p->GetCss()->PaddingTop()).IsValid() ? l.ToPx(d->TabClient.Y(), GetFont()) : 0;
d->TabClient.x2 -= (l = p->GetCss()->PaddingRight()).IsValid() ? l.ToPx(d->TabClient.X(), GetFont()) : 0;
d->TabClient.y2 -= (l = p->GetCss()->PaddingBottom()).IsValid() ? l.ToPx(d->TabClient.Y(), GetFont()) : 0;
}
}
else
{
d->TabClient = LView::GetClient();
d->TabClient.Offset(-d->TabClient.x1, -d->TabClient.y1);
d->TabClient.Inset(2, 2);
d->TabClient.y1 += TabY();
}
return d->TabClient;
}
int LTabView::HitTest(LMouse &m)
{
if (d->LeftBtn.Overlap(m.x, m.y))
{
return LeftScrollBtn;
}
else if (d->RightBtn.Overlap(m.x, m.y))
{
return RightScrollBtn;
}
else
{
// int Hit = -1;
TabIterator it(Children);
for (int i=0; iTabPos.Overlap(m.x, m.y))
return i;
}
}
return NoBtn;
}
void LTabView::OnMouseClick(LMouse &m)
{
bool DownLeft = m.Down() || m.Left();
int Result = HitTest(m);
if (m.Down())
Focus(true);
if (Result == LeftScrollBtn)
{
if (DownLeft)
{
d->Scroll++;
Invalidate();
}
}
else if (Result == RightScrollBtn)
{
if (DownLeft)
{
d->Scroll = MAX(0, d->Scroll-1);
Invalidate();
}
}
else if (Result >= 0)
{
TabIterator it(Children);
LTabPage *p = it[Result];
if (p)
{
if (p->HasButton() &&
p->BtnPos.Overlap(m))
{
if (DownLeft)
{
p->OnButtonClick(m);
// The tab can delete itself after this event
return;
}
}
else
{
// We set this before firing the event, otherwise the
// code seeing the notication gets the old value.
if (DownLeft)
Value(Result);
p->OnTabClick(m);
}
}
}
if (DownLeft)
Focus(true);
}
bool LTabView::OnKey(LKey &k)
{
if (k.Down())
{
switch (k.vkey)
{
case LK_LEFT:
{
if (k.Alt())
break;
if (d->Current > 0)
{
TabIterator it(Children);
LTabPage *p = it[d->Current - 1];
if (p && !p->TabPos.Valid())
{
if (d->Scroll) d->Scroll--;
}
Value(d->Current - 1);
}
return true;
break;
}
case LK_RIGHT:
{
if (k.Alt())
break;
TabIterator it(Children);
if (d->Current < it.Length() - 1)
{
LTabPage *p = it[d->Current + 1];
if (p && !p->TabPos.Valid())
{
d->Scroll++;
}
Value(d->Current + 1);
}
return true;
break;
}
}
}
return false;
}
void LTabView::OnFocus(bool f)
{
if (!Children.Length())
return;
TabIterator it(Children);
LTabPage *p = it[d->Current];
if (p)
{
LRect r = p->TabPos;
r.y2 += 2;
Invalidate(&r);
}
}
void LTabView::OnAttach()
{
}
LRect <abView::CalcInset()
{
LRect Padding(0, 0, 0, 0);
d->Inset = GetClient();
auto f = GetFont();
if (GetCss())
{
LCss::Len l;
if ((l = GetCss()->PaddingLeft()).IsValid()) Padding.x1 = l.ToPx(d->Inset.X(), f);
if ((l = GetCss()->PaddingTop()).IsValid()) Padding.y1 = l.ToPx(d->Inset.Y(), f);
if ((l = GetCss()->PaddingRight()).IsValid()) Padding.x2 = l.ToPx(d->Inset.X(), f);
if ((l = GetCss()->PaddingBottom()).IsValid()) Padding.y2 = l.ToPx(d->Inset.Y(), f);
}
int TabTextY = 0;
d->TabsBaseline = 0;
TabIterator Tabs(Children);
for (auto t : Tabs)
{
auto Ds = t->d->GetDs();
if (Ds)
{
TabTextY = MAX(TabTextY, Ds->Y());
auto Fnt = Ds->GetFont();
d->TabsBaseline = MAX(d->TabsBaseline, Fnt->Ascent());
}
}
if (!TabTextY)
TabTextY = f->GetHeight();
d->TabsHeight = TabTextY;
d->Inset.x1 += Padding.x1;
d->Inset.x2 -= Padding.x2;
d->Inset.y1 += Padding.y1;
d->Inset.y2 -= Padding.y2;
d->Tabs.ZOff(d->Inset.X() - 20, TabY() - 1);
d->Tabs.Offset(d->Inset.x1 + 10, d->Inset.y1);
d->Inset.y1 = d->Tabs.y1 + (d->Tabs.Y() / 2);
return d->Inset;
}
void LTabView::OnStyleChange()
{
if (!d->cBack.IsValid())
{
LCssTools Tools(this);
d->cBack = Tools.GetBack();
d->cTopEdge = d->Tint(0.845);
d->cBottomEdge = d->Tint(0.708);
d->cSelUnfoc = LColour(L_NON_FOCUS_SEL_BACK);
auto mul = pow(0.909f, 1+d->Depth); // 240->218
d->cBorder = d->Tint(mul);
mul = pow(0.959f, 1+d->Depth); // 240->230
d->cFill = d->Tint(mul);
auto *Css = GetCss(true);
if (Css)
{
if (!Css->BackgroundColor().IsValid())
Css->BackgroundColor(LCss::ColorDef(d->cFill));
}
d->CreateCorners();
}
TabIterator Tabs(Children);
for (auto t : Tabs)
t->OnStyleChange();
Invalidate();
}
void LTabView::OnPaint(LSurface *pDC)
{
if (!d->cBack.IsValid())
OnStyleChange();
LCssTools Tools(this);
TabIterator it(Children);
if (d->Current >= it.Length())
Value(it.Length() - 1);
if (d->Style == TvMac)
{
CalcInset();
LView *Pv = GetParent() ? GetParent()->GetGView() : NULL;
LColour NoPaint = (Pv ? Pv : this)->StyleColour(LCss::PropBackgroundColor, LColour(L_MED));
if (!NoPaint.IsTransparent())
{
pDC->Colour(NoPaint);
pDC->Rectangle();
}
#ifdef LGI_CARBON
HIRect Bounds = d->Inset;
HIThemeTabPaneDrawInfo Info;
Info.version = 1;
Info.state = Enabled() ? kThemeStateActive : kThemeStateInactive;
Info.direction = kThemeTabNorth;
Info.size = kHIThemeTabSizeNormal;
Info.kind = kHIThemeTabKindNormal;
Info.adornment = kHIThemeTabPaneAdornmentNormal;
OSStatus e = HIThemeDrawTabPane(&Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal);
#else
// Draw the inset area at 'd->Inset'
LRect Bounds = d->Inset;
pDC->Colour(d->cBorder);
pDC->Box(&Bounds);
Bounds.Inset(1, 1);
pDC->Box(&Bounds);
Bounds.Inset(1, 1);
pDC->Colour(d->cFill);
pDC->Rectangle(&Bounds);
#endif
int x = d->Tabs.x1, y = d->Tabs.y1;
#ifndef LGI_CARBON
LSurface *pScreen = pDC;
#endif
for (unsigned i = 0; i < it.Length(); i++)
{
auto Tab = it[i];
auto Foc = Focus();
LDisplayString *ds = Tab->d->GetDs();
bool First = i == 0;
bool Last = i == it.Length() - 1;
bool IsCurrent = d->Current == i;
LRect r(0, 0, Tab->GetTabPx() - 1, d->Tabs.Y() - 1);
r.Offset(x, y);
#ifdef LGI_CARBON
HIRect TabRc = r;
HIThemeTabDrawInfo TabInfo;
HIRect Label;
TabInfo.version = 1;
TabInfo.style = IsCurrent ? (Foc ? kThemeTabFront : kThemeTabNonFrontPressed) : kThemeTabNonFront;
TabInfo.direction = Info.direction;
TabInfo.size = Info.size;
TabInfo.adornment = Info.adornment;
TabInfo.kind = Info.kind;
if (it.Length() == 1)
TabInfo.position = kHIThemeTabPositionOnly;
else if (First)
TabInfo.position = kHIThemeTabPositionFirst;
else if (Last)
TabInfo.position = kHIThemeTabPositionLast;
else
TabInfo.position = kHIThemeTabPositionMiddle;
e = HIThemeDrawTab(&TabRc, &TabInfo, pDC->Handle(), kHIThemeOrientationNormal, &Label);
r = Label;
#else
LColour cTabFill = IsCurrent ? (Foc ? cFocusBack : d->cSelUnfoc) : LColour(L_WORKSPACE);
LColour cInterTabBorder = d->Tint(0.9625);
LRect b = r;
#if MAC_DBL_BUF
LMemDC Mem;
if (First || Last)
{
if (Mem.Create(r.X(), r.Y(), System32BitColourSpace))
{
pDC = &Mem;
b.Offset(-b.x1, -b.y1);
}
Mem.Colour(LColour::Red);
Mem.Rectangle();
}
#endif
pDC->Colour(d->cTopEdge);
pDC->Line(b.x1, b.y1, b.x2, b.y1); // top edge
if (i == 0)
{
pDC->Line(b.x1, b.y1, b.x1, b.y2); // left edge
}
else
{
pDC->Colour(cInterTabBorder);
pDC->Line(b.x1, b.y1+1, b.x1, b.y2+1); // left edge
}
pDC->Colour(d->cBottomEdge);
pDC->Line(b.x2, b.y2, b.x1, b.y2); // bottom edge
if (Last)
{
pDC->Line(b.x2, b.y2, b.x2, b.y1); // right edge
}
else
{
pDC->Colour(cInterTabBorder);
pDC->Line(b.x2, b.y2-1, b.x2, b.y1+1); // right edge between tabs
}
b.Inset(1, 1);
pDC->Colour(cTabFill);
pDC->Rectangle(&b);
LRect Clip00(0, 0, MAC_STYLE_RADIUS-1, MAC_STYLE_RADIUS-1);
auto Res = IsCurrent ? (Foc ? LTabViewPrivate::ResSelectedFocused : LTabViewPrivate::ResSelectedUnfocused) : LTabViewPrivate::ResWorkspace;
if (First)
{
LRect Clip01 = Clip00.Move(0, r.Y() - Clip00.Y());
#if MAC_DBL_BUF
// Erase the areas we will paint over
pDC->Op(GDC_SET);
pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : NoPaint);
pDC->Rectangle(&Clip00);
pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : d->cFill);
pDC->Rectangle(&Clip01);
#endif
// Draw in the rounded corners
pDC->Op(GDC_ALPHA);
pDC->Blt(Clip00.x1, Clip00.y1, d->Corners[Res], Clip00);
pDC->Blt(Clip01.x1, Clip01.y1, d->Corners[Res], Clip00.Move(0, MAC_STYLE_RADIUS));
}
if (Last)
{
LRect Clip10 = Clip00.Move(r.X() - Clip00.X(), 0);
LRect Clip11 = Clip00.Move(Clip10.x1, r.Y() - Clip00.Y());
#if MAC_DBL_BUF
// Erase the areas we will paint over
pDC->Op(GDC_SET);
pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : NoPaint);
pDC->Rectangle(&Clip10);
pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : d->cFill);
pDC->Rectangle(&Clip11);
#endif
// Draw in the rounded corners
pDC->Op(GDC_ALPHA);
pDC->Blt(Clip10.x1, Clip10.y1, d->Corners[Res], Clip00.Move(MAC_STYLE_RADIUS, 0));
pDC->Blt(Clip11.x1, Clip11.y1, d->Corners[Res], Clip00.Move(MAC_STYLE_RADIUS, MAC_STYLE_RADIUS));
}
#if MAC_DBL_BUF
if (First || Last)
{
if (pScreen->SupportsAlphaCompositing())
pScreen->Op(GDC_ALPHA);
pScreen->Blt(r.x1, r.y1, &Mem);
}
#endif
pDC = pScreen;
#endif
LFont *tf = ds->GetFont();
int BaselineOff = (int) (d->TabsBaseline - tf->Ascent());
tf->Transparent(true);
LCss::ColorDef Fore;
if (Tab->GetCss())
Fore = Tab->GetCss()->Color();
tf->Fore(Fore.IsValid() ? (LColour)Fore :
IsCurrent && Foc ? cFocusFore : Tools.GetFore());
int DsX = r.x1 + TAB_MARGIN_X;
int DsY = r.y1 + TAB_TXT_PAD + BaselineOff;
ds->Draw(pDC, DsX, DsY, &r);
if (Tab->HasButton())
{
Tab->BtnPos.ZOff(CLOSE_BTN_SIZE-1, CLOSE_BTN_SIZE-1);
Tab->BtnPos.Offset(DsX + ds->X() + CLOSE_BTN_GAP, r.y1 + ((r.Y()-Tab->BtnPos.Y()) >> 1));
Tab->OnButtonPaint(pDC);
}
else Tab->BtnPos.ZOff(-1, -1);
it[i]->TabPos = r;
x += r.X()
#ifdef LGI_CARBON
+ (i ? -1 : 2) // Fudge factor to make it look nice, wtf apple?
#endif
;
}
#if 0
pDC->Colour(LColour::Green);
pDC->Line(0, 0, pDC->X()-1, pDC->Y()-1);
#endif
}
else if (d->Style == TvWinXp)
{
if (LApp::SkinEngine &&
TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_TABVIEW))
{
LSkinState State;
State.pScreen = pDC;
State.MouseOver = false;
LApp::SkinEngine->OnPaint_LTabView(this, &State);
}
else
{
LRect r = GetTabClient();
r.Inset(-2, -2);
LWideBorder(pDC, r, DefaultRaisedEdge);
pDC->Colour(L_MED);
pDC->Rectangle(0, 0, X()-1, d->TabClient.y1-3);
LTabPage *Sel = 0;
int x = r.x1;
if (d->Scroll)
{
d->RightBtn.ZOff(12, TabY() - 2);
x = d->RightBtn.x2 + 4;
}
else
{
d->RightBtn.ZOff(-1, -1);
}
d->LeftBtn.ZOff(-1, -1);
for (int n=0; nScroll)
{
p->TabPos.ZOff(-1, -1);
}
else
{
int Wid = p->GetTabPx();
p->TabPos.ZOff(Wid, TabY()-3);
p->TabPos.Offset(x, 2);
if (p->TabPos.x2 > r.x2 - 16)
{
d->LeftBtn.x2 = X()-1;
d->LeftBtn.x1 = d->LeftBtn.x2 - 12;
d->LeftBtn.y1 = 0;
d->LeftBtn.y2 = TabY() - 2;
p->TabPos.ZOff(-1, -1);
break;
}
if (d->Current != n)
{
p->PaintTab(pDC, false);
}
else
{
Sel = p;
}
x += Wid+1;
}
}
if (!it.Length())
{
pDC->Colour(L_MED);
pDC->Rectangle(&r);
}
if (Sel)
{
Sel->PaintTab(pDC, true);
}
if (d->LeftBtn.Valid())
{
r = d->LeftBtn;
LWideBorder(pDC, r, DefaultRaisedEdge);
int x = r.x1 + (r.X() >> 1) + 1;
int y = r.y1 + (r.Y() >> 1) - 1;
pDC->Colour(L_TEXT);
for (int i=0; i<4; i++)
{
pDC->Line(x-i, y-i, x-i, y+i);
}
}
if (d->RightBtn.Valid())
{
r = d->RightBtn;
LWideBorder(pDC, r, DefaultRaisedEdge);
int x = r.x1 + (r.X() >> 1) - 2;
int y = r.y1 + (r.Y() >> 1) - 1;
pDC->Colour(L_TEXT);
for (int i=0; i<4; i++)
{
pDC->Line(x+i, y-i, x+i, y+i);
}
}
}
}
else LAssert(!"Not impl.");
}
void LTabView::OnPosChange()
{
GetTabClient();
if (Children.Length())
{
TabIterator it(Children);
LTabPage *p = it[d->Current];
if (p)
{
p->SetPos(d->TabClient, true);
if (d->PourChildren)
{
LRect r = d->TabClient;
r.Offset(-r.x1, -r.y1);
LRegion Rgn(r);
for (LViewI *c: p->IterateViews())
c->Pour(Rgn);
}
else
{
auto It = p->IterateViews();
if (It.Length() == 1)
{
LTableLayout *tl = dynamic_cast(It[0]);
if (tl)
{
LRect r = p->GetClient();
r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing);
tl->SetPos(r);
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////
char *_lgi_gview_cmp(LView *a, LView *b)
{
static char Str[256];
if (a && b)
{
#if !LGI_VIEW_HANDLE
sprintf_s(Str, sizeof(Str),
"LView: %p,%p",
dynamic_cast(a),
dynamic_cast(b));
#else
sprintf_s(Str, sizeof(Str),
"LView: %p,%p Hnd: %p,%p",
dynamic_cast(a),
dynamic_cast(b),
(void*)a->Handle(),
(void*)b->Handle());
#endif
}
else
{
Str[0] = 0;
}
return Str;
}
LTabPage::LTabPage(const char *name) : ResObject(Res_Tab)
{
d = new LTabPagePriv(this);
LRect r(0, 0, 1000, 1000);
SetPos(r);
Name(name);
Button = false;
TabCtrl = 0;
TabPos.ZOff(-1, -1);
BtnPos.ZOff(-1, -1);
GetCss(true)->Padding("4px");
#if WINNATIVE
SetStyle(GetStyle() | WS_CLIPCHILDREN);
CreateClassW32(GetClass(), 0, CS_HREDRAW | CS_VREDRAW);
#elif defined(HAIKU)
Visible(false);
#endif
LResources::StyleElement(this);
}
LTabPage::~LTabPage()
{
delete d;
}
int LTabPage::GetTabPx()
{
LDisplayString *Ds = d->GetDs();
int Px = TAB_MARGIN_X << 1;
if (Ds)
{
Px += Ds->X();
if (Button)
Px += CLOSE_BTN_GAP + CLOSE_BTN_SIZE;
}
return Px;
}
bool LTabPage::HasButton()
{
return Button;
}
void LTabPage::HasButton(bool b)
{
Button = b;
if (GetParent())
GetParent()->Invalidate();
}
void LTabPage::OnButtonClick(LMouse &m)
{
if (GetId() > 0)
SendNotify(LNotifyTabPageButtonClick);
}
void LTabPage::OnTabClick(LMouse &m)
{
LViewI *v = GetId() > 0 ? this : GetParent();
v->SendNotify(LNotifyItemClick);
}
void LTabPage::OnButtonPaint(LSurface *pDC)
{
#if MAC_PAINT
#else
// The default is a close button
LColour Low(L_LOW);
LColour Mid(L_MED);
Mid = Mid.Mix(Low);
pDC->Colour(Mid);
pDC->Line(BtnPos.x1, BtnPos.y1+1, BtnPos.x2-1, BtnPos.y2);
pDC->Line(BtnPos.x1+1, BtnPos.y1, BtnPos.x2, BtnPos.y2-1);
pDC->Line(BtnPos.x1, BtnPos.y2-1, BtnPos.x2-1, BtnPos.y1);
pDC->Line(BtnPos.x1+1, BtnPos.y2, BtnPos.x2, BtnPos.y1+1);
pDC->Colour(Low);
pDC->Line(BtnPos.x1+1, BtnPos.y1+1, BtnPos.x2-1, BtnPos.y2-1);
pDC->Line(BtnPos.x2-1, BtnPos.y1+1, BtnPos.x1+1, BtnPos.y2-1);
#endif
}
int64 LTabPage::Value()
{
if (!TabCtrl)
return false;
ssize_t Idx = TabCtrl->IterateViews().IndexOf(this);
return TabCtrl->Value() == Idx;
}
void LTabPage::Value(int64 v)
{
if (v)
Select();
}
const char *LTabPage::Name()
{
return LBase::Name();
}
bool LTabPage::Name(const char *name)
{
bool Status = LView::Name(name);
d->Ds.Reset();
if (GetParent())
GetParent()->Invalidate();
return Status;
}
void LTabPage::PaintTab(LSurface *pDC, bool Selected)
{
#if MAC_PAINT
#else
LRect r = TabPos;
if (Selected)
{
r.Inset(-2, -2);
}
pDC->Colour(L_LIGHT);
bool First = false;
if (TabCtrl)
{
TabIterator it(TabCtrl->Children);
First = it[0] == this;
}
if (First)
pDC->Line(r.x1, r.y1+1, r.x1, r.y2);
else
pDC->Line(r.x1, r.y1+1, r.x1, r.y2-1);
pDC->Line(r.x1+1, r.y1, r.x2-1, r.y1);
pDC->Colour(L_HIGH);
pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2);
pDC->Line(r.x1+1, r.y1+1, r.x2-1, r.y1+1);
pDC->Colour(L_LOW);
pDC->Line(r.x2-1, r.y1+1, r.x2-1, r.y2);
pDC->Colour(L_SHADOW);
pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1);
r.x2 -= 2;
r.x1 += 2;
r.y1 += 2;
pDC->Colour(L_MED);
pDC->Rectangle(&r);
pDC->Set(r.x1, r.y1);
pDC->Set(r.x2, r.y1);
int Cx = r.x1 + TAB_MARGIN_X;
auto t = Name();
if (t)
{
LFont *f = GetFont();
LDisplayString ds(f, t);
f->Colour(L_TEXT, L_MED);
f->Transparent(true);
int y = r.y1 + ((r.Y()-ds.Y())/2);
ds.Draw(pDC, Cx, y);
if (TabCtrl->Focus() && Selected)
{
r.Set(Cx, y, Cx+ds.X(), y+ds.Y());
r.Inset(-2, -1);
r.y1++;
pDC->Colour(L_LOW);
pDC->Box(&r);
}
Cx += ds.X() + CLOSE_BTN_GAP;
}
if (Button)
{
BtnPos.ZOff(CLOSE_BTN_SIZE-1, CLOSE_BTN_SIZE-1);
BtnPos.Offset(Cx, r.y1 + ((r.Y()-BtnPos.Y()) / 2));
OnButtonPaint(pDC);
}
else BtnPos.ZOff(-1, -1);
#endif
}
bool LTabPage::Attach(LViewI *parent)
{
bool Status = false;
if (TabCtrl)
{
if (!IsAttached())
{
Status = LView::Attach(parent);
}
else
{
Status = true;
}
for (auto w: Children)
{
if (!w->IsAttached())
{
w->Attach(this);
w->SetNotify(TabCtrl->GetParent());
}
}
}
return Status;
}
LMessage::Result LTabPage::OnEvent(LMessage *Msg)
{
return LView::OnEvent(Msg);
}
void LTabPage::Append(LViewI *Wnd)
{
if (Wnd)
{
Wnd->SetNotify(TabCtrl);
if (IsAttached() && TabCtrl)
{
Wnd->Attach(this);
LTabView *v = dynamic_cast(GetParent());
if (v && v->GetPourChildren())
{
v->OnPosChange();
}
}
else if (!HasView(Wnd))
{
AddView(Wnd);
}
else LAssert(0);
}
}
bool LTabPage::Remove(LViewI *Wnd)
{
if (Wnd)
{
LAssert(HasView(Wnd));
Wnd->Detach();
return true;
}
return false;
}
LColour LTabPage::GetBackground()
{
if (TabCtrl && TabCtrl->d->Style == TvMac)
return LColour(226, 226, 226); // 207?
else
return LColour(L_MED);
}
void LTabPage::OnStyleChange()
{
SetFont(LSysFont, false);
GetParent()->Invalidate();
}
void LTabPage::SetFont(LFont *Font, bool OwnIt)
{
d->Ds.Reset();
Invalidate();
return LView::SetFont(Font, OwnIt);
}
void LTabPage::OnPaint(LSurface *pDC)
{
LRect r(0, 0, X()-1, Y()-1);
LColour Bk = StyleColour(LCss::PropBackgroundColor, TabCtrl ? TabCtrl->d->cFill : LColour(L_MED), 1);
pDC->Colour(Bk);
pDC->Rectangle(&r);
#if 0
pDC->Colour(LColour::Red);
pDC->Line(0, 0, pDC->X()-1, pDC->Y()-1);
#endif
}
void LTabPage::OnFocus(bool b)
{
}
bool LTabPage::OnKey(LKey &k)
{
return false;
}
bool LTabPage::LoadFromResource(int Res)
{
LAutoString n;
auto ch = IterateViews();
LViewI *v;
while ((v = ch[0]))
{
v->Detach();
DelView(v);
ch.Delete(v, true);
}
bool Status = LResourceLoad::LoadFromResource(Res, this, 0, &n);
if (ValidStr(n))
Name(n);
/* This isn't needed if the controls properly inherit colours.
if (TabCtrl && TabCtrl->d->Style == TvMac)
// Sigh
for (auto c : Children)
c->GetCss(true)->BackgroundColor(LCss::ColorDef(GetBackground()));
*/
if (IsAttached())
AttachChildren();
return Status;
}
void LTabPage::Select()
{
if (GetParent())
{
ssize_t Idx = GetParent()->IterateViews().IndexOf(this);
if (Idx >= 0)
GetParent()->Value(Idx);
}
}