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); } }