diff --git a/include/lgi/common/Http.h b/include/lgi/common/Http.h --- a/include/lgi/common/Http.h +++ b/include/lgi/common/Http.h @@ -1,115 +1,116 @@ #ifndef __IHTTP_H #define __IHTTP_H #include "lgi/common/Net.h" +#include "lgi/common/Uri.h" class LHttp { char *Proxy; int ProxyPort; int BufferLen; char *Buffer; LAutoPtr Socket; // commands size_t ResumeFrom; LAutoString FileLocation; char *Headers; bool NoCache; LString AuthUser, AuthPassword; LString ErrorMsg; public: enum ContentEncoding { EncodeNone, EncodeRaw, EncodeGZip, }; Progress *Meter; LHttp(); virtual ~LHttp(); void SetResume(size_t i) { ResumeFrom = i; } void SetProxy(char *p, int Port); void SetNoCache(bool i) { NoCache = i; } void SetAuth(char *User = 0, char *Pass = 0); // Data LSocketI *Handle() { return Socket; } char *AlternateLocation() { return FileLocation; } char *GetHeaders() { return Headers; } // Connection bool Open(LAutoPtr S, const char *RemoteHost, int Port = 0); bool Close(); bool IsOpen(); LSocketI *GetSocket() { return Socket; } LString GetErrorString() { return ErrorMsg; } // General bool Request( const char *Type, const char *Uri, int *ProtocolStatus, const char *InHeaders, LStreamI *InBody, LStreamI *Out, LStreamI *OutHeaders, ContentEncoding *OutEncoding); bool Get( const char *Uri, const char *InHeaders, int *ProtocolStatus, LStreamI *Out, ContentEncoding *OutEncoding, LStreamI *OutHeaders = 0) { return Request("GET", Uri, ProtocolStatus, InHeaders, NULL, // InBody Out, OutHeaders, OutEncoding); } bool Post( char *Uri, LStreamI *In, int *ProtocolStatus, LStreamI *Out, LStreamI *OutHeaders = 0, const char *InHeaders = 0) { return Request("POST", Uri, ProtocolStatus, InHeaders, In, Out, OutHeaders, NULL); } }; /// This method will download a URI. bool LgiGetUri ( /// Method for cancelling the call... LCancel *Cancel, /// The output stream to put the data LStreamI *Out, /// Any error message LString *OutError, /// The input URI to retreive const char *InUri, /// [Optional] Extra headers to use const char *InHeaders = NULL, /// [Optional] The proxy to use LUri *InProxy = NULL ); #endif diff --git a/include/lgi/common/LgiDefs.h b/include/lgi/common/LgiDefs.h --- a/include/lgi/common/LgiDefs.h +++ b/include/lgi/common/LgiDefs.h @@ -1,582 +1,581 @@ /** \file \author Matthew Allen \date 24/9/1999 \brief Defines and types Copyright (C) 1999-2004, Matthew Allen */ #ifndef _LGIDEFS_H_ #define _LGIDEFS_H_ #ifdef HAIKU #include #endif #include "LgiInc.h" #if defined(WIN32) && defined(__GNUC__) #define PLATFORM_MINGW #endif #include // Unsafe typedefs, for backward compatibility typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; // Length safe typedesf, use these in new code #ifdef _MSC_VER /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed __int64 int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned __int64 uint64; #ifndef _WCHAR_T_DEFINED #include #endif #pragma warning(error:4263) #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef int64_t int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef uint64_t uint64; #elif !defined(HAIKU) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed long long int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned long long uint64; #endif #ifndef __cplusplus #include #if defined(_MSC_VER) && _MSC_VER<1800 typedef unsigned char bool; #define true 1 #define false 0 #else #include #endif #endif /// \brief Wide unicode char /// /// This is 16 bits on Win32 and Mac, but 32 bits on unix platforms. There are a number /// of wide character string function available for manipulating wide char strings. /// /// Firstly to convert to and from utf-8 there is: ///
    ///
  • Utf8ToWide() ///
  • WideToUtf8() ///
/// /// Wide versions of standard library functions are available: ///
    ///
  • StrchrW() ///
  • StrrchrW() ///
  • StrnchrW() ///
  • StrstrW() ///
  • StristrW() ///
  • StrnstrW() ///
  • StrnistrW() ///
  • StrcmpW() ///
  • StricmpW() ///
  • StrncmpW() ///
  • StrnicmpW() ///
  • StrcpyW() ///
  • StrncpyW() ///
  • StrlenW() ///
  • StrcatW() ///
  • HtoiW() ///
  • NewStrW() ///
  • TrimStrW() ///
  • ValidStrW() ///
  • MatchStrW() ///
#include typedef wchar_t char16; #if !WINNATIVE #ifdef UNICODE typedef char16 TCHAR; #ifndef _T #define _T(arg) L##arg #endif #else typedef char TCHAR; #ifndef _T #define _T(arg) arg #endif #endif #endif #if defined(_MSC_VER) #if _MSC_VER >= 1400 #ifdef _WIN64 typedef __int64 NativeInt; typedef unsigned __int64 UNativeInt; #else typedef _W64 int NativeInt; typedef _W64 unsigned int UNativeInt; #endif #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #else #if __LP64__ typedef int64 NativeInt; typedef uint64 UNativeInt; #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #endif /// Generic pointer to any base type. Used when addressing continuous data of /// different types. typedef union { int8_t *s8; uint8_t *u8; int16_t *s16; uint16_t *u16; int32_t *s32; uint32_t *u32; int64 *s64; uint64 *u64; NativeInt *ni; UNativeInt *uni; char *c; char16 *w; float *f; double *d; #ifdef __cplusplus bool *b; #else unsigned char *b; #endif void **vp; int i; } LPointer; // Basic macros #define limit(i,l,u) (((i)<(l)) ? (l) : (((i)>(u)) ? (u) : (i))) -#define makelong(a, b) ((a)<<16 | (b&0xFFFF)) -#define loword(a) (a&0xFFFF) -#define hiword(a) (a>>16) +// #define makelong(a, b) ((a)<<16 | (b&0xFFFF)) +// #define loword(a) (a&0xFFFF) +// #define hiword(a) (a>>16) #undef ABS #ifdef __cplusplus template inline T ABS(T v) { if (v < 0) return -v; return v; } #else #define ABS(v) ((v) < 0 ? -(v) : (v)) #endif /// Returns true if 'c' is an ascii character #define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) /// Returns true if 'c' is a digit (number) #define IsDigit(c) ((c) >= '0' && (c) <= '9') /// Returns true if 'c' is a hexadecimal digit #define IsHexDigit(c) ( \ ((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F') \ ) // Byte swapping #define LgiSwap16(a) ( (((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8) ) #define LgiSwap32(a) ( (((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24) ) #ifdef __GNUC__ #define LgiSwap64(a) ( (((a) & 0xff00000000000000LLU) >> 56) | \ (((a) & 0x00ff000000000000LLU) >> 40) | \ (((a) & 0x0000ff0000000000LLU) >> 24) | \ (((a) & 0x000000ff00000000LLU) >> 8) | \ (((a) & 0x00000000ff000000LLU) << 8) | \ (((a) & 0x0000000000ff0000LLU) << 24) | \ (((a) & 0x000000000000ff00LLU) << 40) | \ (((a) & 0x00000000000000ffLLU) << 56) ) #else #define LgiSwap64(a) ( (((a) & 0xff00000000000000) >> 56) | \ (((a) & 0x00ff000000000000) >> 40) | \ (((a) & 0x0000ff0000000000) >> 24) | \ (((a) & 0x000000ff00000000) >> 8) | \ (((a) & 0x00000000ff000000) << 8) | \ (((a) & 0x0000000000ff0000) << 24) | \ (((a) & 0x000000000000ff00) << 40) | \ (((a) & 0x00000000000000ff) << 56) ) #endif // Asserts LgiFunc void _lgi_assert(bool b, const char *test, const char *file, int line); #ifdef _DEBUG -#define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) + #define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) #else -#define LAssert(b) ((void)0) + #define LAssert(b) ((void)0) #endif // Good ol NULLy #ifndef NULL #define NULL 0 #endif // Slashes and quotes #define IsSlash(c) (((c)=='/')||((c)=='\\')) #define IsQuote(c) (((c)=='\"')||((c)=='\'')) // Some objectish ones #define ZeroObj(obj) memset(&obj, 0, sizeof(obj)) #ifndef CountOf #define CountOf(array) (sizeof(array)/sizeof(array[0])) #endif #ifdef __cplusplus template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } #endif #ifndef MEMORY_DEBUG #define DeleteObj(obj) if (obj) { delete obj; obj = 0; } #define DeleteArray(obj) if (obj) { delete [] obj; obj = 0; } #endif // Flags #define SetFlag(i, f) (i) |= (f) #define ClearFlag(i, f) (i) &= ~(f) #define TestFlag(i, f) (((i) & (f)) != 0) // Defines /// Enum of all the operating systems we might be running on. enum LgiOs { /// \brief Unknown OS /// \sa LGetOs LGI_OS_UNKNOWN = 0, /// \brief Windows 95, 98[se] or ME. (Not supported) /// \sa LGetOs LGI_OS_WIN9X, - /// \brief 32bit NT, 2k, XP, Vista, 7, 8 or later. (XP and later supported) + /// \brief Windows 10+ 32bit. (Not supported) /// \sa LGetOs LGI_OS_WIN32, - /// \brief 64bit NT, 2k, XP, Vista, 7, 8 or later. (XP and later supported) + /// \brief Windows 10+ 64bit. (Supported) /// \sa LGetOs LGI_OS_WIN64, /// \brief BeOS/Haiku. (Somewhat supported) /// \sa LGetOs LGI_OS_HAIKU, /// \brief Linux. (Kernels v2.4 and up supported) /// \sa LGetOs LGI_OS_LINUX, - /// \brief There was an Atheos port at one point. (Not supported) + /// \brief Mac OS X 10.15 or later. (Supported) /// \sa LGetOs LGI_OS_MAC_OS_X, /// One higher than the maximum OS define LGI_OS_MAX, }; // Edge types enum LEdge { EdgeNone, EdgeXpSunken, EdgeXpRaised, EdgeXpChisel, EdgeXpFlat, EdgeWin7FocusSunken, EdgeWin7Sunken, }; #define DefaultSunkenEdge EdgeWin7Sunken #define DefaultRaisedEdge EdgeXpRaised // Cursors enum LCursor { /// Blank/invisible cursor LCUR_Blank, /// Normal arrow LCUR_Normal, /// Upwards arrow LCUR_UpArrow, /// Downwards arrow LCUR_DownArrow, /// Left arrow LCUR_LeftArrow, /// Right arrow LCUR_RightArrow, /// Crosshair LCUR_Cross, /// Hourglass/watch LCUR_Wait, /// Ibeam/text entry LCUR_Ibeam, /// Vertical resize (|) LCUR_SizeVer, /// Horizontal resize (-) LCUR_SizeHor, /// Diagonal resize (/) LCUR_SizeBDiag, /// Diagonal resize (\) LCUR_SizeFDiag, /// All directions resize LCUR_SizeAll, /// Vertical splitting LCUR_SplitV, /// Horziontal splitting LCUR_SplitH, /// A pointing hand LCUR_PointingHand, /// A slashed circle LCUR_Forbidden, /// Copy Drop LCUR_DropCopy, /// Copy Move LCUR_DropMove, }; // General Event Flags #define LGI_EF_LCTRL (1 << 0) #define LGI_EF_RCTRL (1 << 1) #define LGI_EF_CTRL (LGI_EF_LCTRL | LGI_EF_RCTRL) #define LGI_EF_LALT (1 << 2) #define LGI_EF_RALT (1 << 3) #define LGI_EF_ALT (LGI_EF_LALT | LGI_EF_RALT) #define LGI_EF_LSHIFT (1 << 4) #define LGI_EF_RSHIFT (1 << 5) #define LGI_EF_SHIFT (LGI_EF_LSHIFT | LGI_EF_RSHIFT) #define LGI_EF_DOWN (1 << 6) #define LGI_EF_DOUBLE (1 << 7) #define LGI_EF_CAPS_LOCK (1 << 8) #define LGI_EF_IS_CHAR (1 << 9) #define LGI_EF_IS_NOT_CHAR (1 << 10) #define LGI_EF_SYSTEM (1 << 11) // Windows key/Apple key etc // Mouse Event Flags #define LGI_EF_LEFT (1 << 16) #define LGI_EF_MIDDLE (1 << 17) #define LGI_EF_RIGHT (1 << 18) #define LGI_EF_MOVE (1 << 19) #define LGI_EF_XBTN1 (1 << 20) #define LGI_EF_XBTN2 (1 << 21) // Emit compiler warnings #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning: " // To use just do #pragma message(__LOC__"My warning message") // Simple definition of breakable unicode characters #define LGI_BreakableChar(c) ( \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' || \ ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) // Os metrics - enum LSystemMetric { /// Get the standard window horizontal border size /// \sa LApp::GetMetric() LGI_MET_DECOR_X = 1, /// Get the standard window vertical border size including caption bar. /// \sa LApp::GetMetric() LGI_MET_DECOR_Y, /// Get the standard window caption bar height only. /// \sa LApp::GetMetric() LGI_MET_DECOR_CAPTION, /// Get the height of a single line menu bar /// \sa LApp::GetMetric() LGI_MET_MENU, /// This is non-zero if the system is theme aware LGI_MET_THEME_AWARE, /// Size of a window's shadow LGI_MET_WINDOW_SHADOW, }; /// \brief Types of system paths available for querying /// \sa LgiGetSystemPath enum LSystemPath { LSP_ROOT, /// The location of the operating system folder /// [Win32] = e.g. C:\Windows /// [Mac] = /System /// [Linux] /boot LSP_OS, /// The system library folder /// [Win32] = e.g. C:\Windows\System32 /// [Mac] = /Library /// [Linux] = /usr/lib LSP_OS_LIB, /// A folder for storing temporary files. These files are usually /// deleted automatically later by the system. /// [Win32] = ~\Local Settings\Temp /// [Mac] = ~/Library/Caches/TemporaryItems /// [Linux] = /tmp LSP_TEMP, /// System wide application data /// [Win32] = ~\..\All Users\Application Data /// [Mac] = /System/Library /// [Linux] = /usr LSP_COMMON_APP_DATA, /// User specific application data /// [Win32] = ~\Application Data /// [Mac] = ~/Library /// [Linux] = /usr /// [Haiku] = ~/config (???) LSP_USER_APP_DATA, /// Machine + user specific application data (probably should not use) /// [Win32] = ~\Local Settings\Application Data /// [Mac] = ~/Library /// [Linux] = /usr/local LSP_LOCAL_APP_DATA, /// Desktop dir /// i.e. ~/Desktop LSP_DESKTOP, /// Home dir /// i.e. ~ LSP_HOME, /// Application install folder: /// [Win] c:\Program Files /// [Mac] /Applications /// [Linux] /usr/bin LSP_USER_APPS, /// The running application's path. /// [Mac] This doesn't include the "Contents/MacOS" part of the path inside the bundle. LSP_EXE, /// The system trash folder. LSP_TRASH, /// The app's install folder. /// [Win32] = $ExePath (sans '\Release' or '\Debug') /// [Mac/Linux] = $ExePath /// Where $ExePath is the folder the application is running from LSP_APP_INSTALL, /// The app's root folder (Where config and data should be stored) /// LOptionsFile uses LSP_APP_ROOT as the default location. /// [Win32] = ~\Application Data\Roaming\$AppName /// [Mac] = ~/Library/$AppName /// [Linux] = ~/.$AppName /// Where $AppName = LApp::GetName. /// If the given folder doesn't exist it will be created. LSP_APP_ROOT, /// This is the user's documents folder /// [Win32] ~\My Documents /// [Mac] ~\Documents LSP_USER_DOCUMENTS, /// This is the user's music folder /// [Win32] ~\My Music /// [Mac] ~\Music LSP_USER_MUSIC, /// This is the user's video folder /// [Win32] ~\My Videos /// [Mac] ~\Movies LSP_USER_VIDEO, /// This is the user's download folder /// ~\Downloads LSP_USER_DOWNLOADS, /// This is the user's links folder /// [Win32] = ~\Links /// [Mac] = ??? /// [Linux] = ??? LSP_USER_LINKS, /// User's pictures/photos folder LSP_USER_PICTURES, /// [Win32] = C:\Users\%HOME%\Pictures /// [Mac] = ??? /// [Linux] = ~\Pictures LSP_MOUNT_POINT, }; #ifdef _DEBUG #define DeclDebugArgs , const char *_file, int _line #define PassDebugArgs , __FILE__, __LINE__ #else #define DeclDebugArgs #define PassDebugArgs #endif -#define _FL __FILE__, __LINE__ +#define _FL __FILE__, __LINE__ -#define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) +#define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) #include "lgi/common/AutoPtr.h" #endif diff --git a/include/lgi/common/LgiNetInc.h b/include/lgi/common/LgiNetInc.h deleted file mode 100644 --- a/include/lgi/common/LgiNetInc.h +++ /dev/null @@ -1,47 +0,0 @@ - - - -#ifndef __LGI_NET_INC_H -#define __LGI_NET_INC_H - -#ifdef LGI_STATIC - - // static linking - #define LgiNetFunc extern - #define LgiNetClass - #define LgiNetExtern extern - -#else - - // dynamically linked - - #if defined(WIN32) || defined(_WIN64) - - #ifdef LGI_LIBRARY - #define LgiNetFunc extern "C" __declspec(dllexport) - #define LgiNetClass __declspec(dllexport) - #define LgiNetExtern extern __declspec(dllexport) - #else - #define LgiNetFunc extern "C" __declspec(dllimport) - #define LgiNetClass __declspec(dllimport) - #define LgiNetExtern extern __declspec(dllimport) - #endif - - #else // Unix like OS - - #ifdef LGI_LIBRARY - #define LgiNetFunc extern "C" - #define LgiNetClass - #define LgiNetExtern extern - #else - #define LgiNetFunc extern "C" - #define LgiNetClass - #define LgiNetExtern extern - #endif - - - #endif - -#endif - -#endif diff --git a/include/lgi/common/Mime.h b/include/lgi/common/Mime.h --- a/include/lgi/common/Mime.h +++ b/include/lgi/common/Mime.h @@ -1,182 +1,178 @@ -#ifndef _GMIME_H_ -#define _GMIME_H_ +#pragma once -#include "LgiNetInc.h" #include "lgi/common/Stream.h" -#include "lgi/common/NetTools.h" extern void CreateMimeBoundary(char *Buf, int BufLen); // MIME content types enum LMimeEncodings { CONTENT_NONE, CONTENT_BASE64, CONTENT_QUOTED_PRINTABLE, CONTENT_OCTET_STREAM }; class LMime; class LMimeAction { friend class LMime; protected: // Parent ptr LMime *Mime = NULL; public: virtual void Empty() {} // reset to initial state }; class LMimeBuf : public LStringPipe { ssize_t Total = 0; LStreamI *Src = NULL; LStreamEnd *End = NULL; public: constexpr static int BlockSz = 4 << 10; LMimeBuf(LStreamI *src, LStreamEnd *end); // Read some data from 'src' // \returns true if some data was received. bool ReadSrc(); /// Reads 'Len' bytes into a LString LString ReadStr(ssize_t Len = -1) { if (Len < 0) Len = GetSize(); LString s; if (!s.Length(Len)) return s; auto rd = Read(s.Get(), s.Length()); if (rd < 0) s.Empty(); else if (rd < (ssize_t)s.Length()) s.Length(rd); return s; } ssize_t Pop(LArray &Buf) override; ssize_t Pop(char *Str, ssize_t BufSize) override; }; class LMime { // Header info LString Headers; // Data info ssize_t DataPos; ssize_t DataSize; LMutex *DataLock; LStreamI *DataStore; bool OwnDataStore; // Other info char *TmpPath; LMime *Parent; LArray Children; // Private methods bool Lock(); void Unlock(); bool CreateTempData(); char *NewValue(char *&s, bool Alloc = true); char *StartOfField(char *s, const char *Feild); char *NextField(char *s); char *GetTmpPath(); public: static const char *DefaultCharset; LMime(const char *TmpFileRoot = 0); virtual ~LMime(); // Methods bool Insert(LMime *m, int pos = -1); void Remove(); ssize_t Length() { return Children.Length(); } LMime *operator[](uint32_t i); LMime *NewChild(); void DeleteChildren() { Children.DeleteObjects(); } void Empty(); bool SetHeaders(const char *h); const char *GetHeaders() { return Headers; } ssize_t GetLength() { return DataSize; } LStreamI *GetData(bool Detach = false); bool SetData(bool OwnStream, LStreamI *Input, int RdPos = 0, int RdSize = -1, LMutex *Lock = 0); bool SetData(char *Str, int Len); // Simple Header Management char *Get(const char *Field, bool Short = true, const char *Default = 0); // 'Short'=true returns the value with out subfields bool Set(const char *Field, const char *Value); // 'Value' has to include any subfields. char *GetSub(const char *Field, const char *Sub); bool SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue = 0); // Header Shortcuts (uses Get[Sub]/Set[Sub]) char *GetMimeType() { return Get("Content-Type", true, "text/plain"); } bool SetMimeType(const char *s) { return Set("Content-Type", s); } char *GetEncoding() { return Get("Content-Transfer-Encoding"); } bool SetEncoding(const char *s) { return Set("Content-Transfer-Encoding", s); } char *GetCharset() { return GetSub("Content-Type", "Charset"); } bool SetCharset(const char *s) { return SetSub("Content-Type", "Charset", s, DefaultCharset); } char *GetBoundary() { return GetSub("Content-Type", "Boundary"); } bool SetBoundary(const char *s) { return SetSub("Content-Type", "Boundary", s, DefaultCharset); } char *GetFileName(); bool SetFileName(const char *s) { return SetSub("Content-Type", "Name", s, DefaultCharset); } // Streaming class LMimeText { public: class LMimeDecode : public LPullStreamer, public LMimeAction { public: ssize_t Pull(LStreamI *Source, LStreamEnd *End = NULL); ssize_t Parse(LMimeBuf *Source, class ParentState *State = NULL); void Empty(); } Decode; class LMimeEncode : public LPushStreamer, public LMimeAction { public: ssize_t Push(LStreamI *Dest, LStreamEnd *End = NULL); void Empty(); } Encode; } Text; friend class LMime::LMimeText::LMimeDecode; friend class LMime::LMimeText::LMimeEncode; class LMimeBinary { public: class LMimeRead : public LPullStreamer, public LMimeAction { public: ssize_t Pull(LStreamI *Source, LStreamEnd *End = 0); void Empty(); } Read; class LMimeWrite : public LPushStreamer, public LMimeAction { public: int64 GetSize(); ssize_t Push(LStreamI *Dest, LStreamEnd *End = 0); void Empty(); } Write; } Binary; friend class LMime::LMimeBinary::LMimeRead; friend class LMime::LMimeBinary::LMimeWrite; }; -#endif diff --git a/include/lgi/common/Net.h b/include/lgi/common/Net.h --- a/include/lgi/common/Net.h +++ b/include/lgi/common/Net.h @@ -1,554 +1,497 @@ /** \file \author Matthew Allen \date 28/5/1998 \brief Network sockets classes Copyright (C) 1998, Matthew Allen */ #ifndef __INET_H #define __INET_H -#include "lgi/common/LgiNetInc.h" #include "lgi/common/LgiInterfaces.h" #include "lgi/common/Mem.h" #include "lgi/common/Containers.h" #include "lgi/common/Stream.h" #include "lgi/common/LgiString.h" #include "lgi/common/Cancel.h" #include "lgi/common/HashTable.h" #if defined WIN32 #include "ws2ipdef.h" #elif defined POSIX #include #elif defined BEOS #include #include #else typedef int SOCKET; #endif #define DEFAULT_TIMEOUT 30 // Standard ports #define FTP_PORT 21 #define SSH_PORT 22 #define SMTP_PORT 25 #define HTTP_PORT 80 #define HTTPS_PORT 443 #define SMTP_SSL_PORT 465 #define POP3_PORT 110 #define POP3_SSL_PORT 995 #define IMAP_PORT 143 #define IMAP_SSL_PORT 993 // Parameters for passing to LSocket::SetVariant /// Turn on/off logging. Used with LSocket::SetParameter. #define LSocket_Log "Log" /// Set the progress object. Used with LSocket::SetParameter. Value = (LProgressView*)Prog #define LSocket_Progress "Progress" /// Set the size of the transfer. Used with LSocket::SetParameter. Value = (int)Size #define LSocket_TransferSize "TransferSize" /// Set the type of protocol. Used with LSocket::SetParameter. Value = (char*)Protocol #define LSocket_Protocol "Protocol" #define LSocket_SetDelete "SetDelete" // Functions -LgiNetFunc bool HaveNetConnection(); -LgiNetFunc bool WhatsMyIp(LAutoString &Ip); +LgiFunc bool HaveNetConnection(); +LgiFunc bool WhatsMyIp(LAutoString &Ip); LgiExtern LString LIpToStr(uint32_t ip); LgiExtern uint32_t LIpToInt(LString str); // Convert IP as string to host order int LgiExtern uint32_t LHostnameToIp(const char *HostName); // Hostname lookup (DNS), returns IP in host order or 0 on error +/// Get a specific value from a list of headers (as a dynamic string) +LgiFunc char *InetGetHeaderField(const char *Headers, const char *Field, ssize_t Len = -1); +LgiExtern LString LGetHeaderField(LString Headers, const char *Field); + +/// Gets a sub field of a header value +LgiFunc char *InetGetSubField(const char *s, const char *Field); +LgiExtern LString LGetSubField(LString HeaderValue, const char *Field); + /// Make md5 hash -LgiNetFunc void MDStringToDigest +LgiFunc void MDStringToDigest ( /// Buffer to receive md5 hash unsigned char digest[16], /// Input string char *Str, /// Length of input string or -1 for null terminated int Len = -1 ); /// Implementation of a network socket -class LgiNetClass LSocket : +class LgiClass LSocket : public LSocketI, public LStream { protected: class LSocketImplPrivate *d; // Methods void Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len); bool CreateUdpSocket(); public: ssize_t BytesRead, BytesWritten; /// Creates the class LSocket(LStreamI *logger = 0, void *unused_param = 0); /// Destroys the class ~LSocket(); /// Gets the active cancellation object LCancel *GetCancel() override; /// Sets the active cancellation object void SetCancel(LCancel *c) override; /// Returns the operating system handle to the socket. OsSocket Handle(OsSocket Set = INVALID_SOCKET) override; OsSocket ReleaseHandle(); /// Returns true if the internal state of the class is ok bool IsOK(); /// Returns the IP address at this end of the socket. bool GetLocalIp(char *IpAddr) override; /// Returns the port at this end of the socket. int GetLocalPort() override; /// Gets the IP address at the remote end of the socket. bool GetRemoteIp(uint32_t *IpAddr); bool GetRemoteIp(char *IpAddr) override; /// Gets the IP address at the remote end of the socket. int GetRemotePort() override; /// Gets the current timeout for operations in ms int GetTimeout() override; /// Sets the current timeout for operations in ms void SetTimeout(int ms) override; /// Returns whether there is data available for reading. bool IsReadable(int TimeoutMs = 0) override; /// Returns whether there is data available for reading. bool IsWritable(int TimeoutMs = 0) override; /// Returns if the socket is ready to accept a connection bool CanAccept(int TimeoutMs = 0) override; /// Returns if the socket is set to block bool IsBlocking() override; /// Set the socket to block void IsBlocking(bool block) override; /// Get the send delay setting bool IsDelayed() override; /// Set the send delay setting void IsDelayed(bool Delay) override; /// Opens a connection. int Open ( /// The name of the remote host. const char *HostAddr, /// The port on the remote host. int Port ) override; /// Returns true if the socket is connected. bool IsOpen() override; /// Closes the connection to the remote host. int Close() override; /// Binds on a given port. bool Bind(int Port, bool reuseAddr = true); /// Binds and listens on a given port for an incomming connection. bool Listen(int Port = 0) override; /// Accepts an incomming connection and connects the socket you pass in to the remote host. bool Accept ( /// The socket to handle the connection. LSocketI *c ) override; /// \brief Sends data to the remote host. /// \return the number of bytes written or <= 0 on error. ssize_t Write ( /// Pointer to the data to write const void *Data, /// Numbers of bytes to write ssize_t Len, /// Flags to pass to send int Flags = 0 ) override; inline ssize_t Write(const LString s) { return LStreamI::Write(s); } /// \brief Reads data from the remote host. /// \return the number of bytes read or <= 0 on error. /// /// Generally the number of bytes returned is less than the buffer size. Depending on how much data /// you are expecting you will need to keep reading until you get and end of field marker or the number /// of bytes your looking for. ssize_t Read ( /// Pointer to the buffer to write output to void *Data, /// The length of the receive buffer. ssize_t Len, /// The flags to pass to recv int Flags = 0 ) override; /// Returns the last error or 0. int Error(void *Param = 0) override; const char *GetErrorString() override; /// Not supported int64 GetSize() override { return -1; } /// Not supported int64 SetSize(int64 Size) override { return -1; } /// Not supported int64 GetPos() override { return -1; } /// Not supported int64 SetPos(int64 Pos) override { return -1; } /// Gets called when the connection is disconnected void OnDisconnect() override; /// Gets called when data is received. void OnRead(char *Data, ssize_t Len) override {} /// Gets called when data is sent. void OnWrite(const char *Data, ssize_t Len) override {} /// Gets called when an error occurs. void OnError(int ErrorCode, const char *ErrorDescription) override; /// Gets called when some information is available. void OnInformation(const char *Str) override {} /// Parameter change handler. int SetParameter ( /// e.g. #LSocket_Log int Param, int Value ) { return false; } /// Get UPD mode bool GetUdp() override; /// Set UPD mode void SetUdp(bool isUdp = true) override; /// Makes the socket able to broadcast void SetBroadcast(bool isBroadcast = true); /// Read UPD packet int ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip = 0, uint16_t *Port = 0) override; /// Write UPD packet int WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) override; bool AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface); bool SetMulticastInterface(uint32_t Interface); // Impl LStreamI *Clone() override { LSocket *s = new LSocket; if (s) s->SetCancel(GetCancel()); return s; } // Statics /// Enumerates the current interfaces struct Interface { LString Name; uint32_t Ip4; // Host order... uint32_t Netmask4; bool IsLoopBack() { return Ip4 == 0x7f000001; } bool IsPrivate() { uint8_t h1 = (Ip4 >> 24) & 0xff; if (h1 == 192) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 == 168; } else if (h1 == 10) { return true; } else if (h1 == 172) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 >= 16 && h2 <= 31; } return false; } bool IsLinkLocal() { uint8_t h1 = (Ip4 >> 24) & 0xff; if (h1 == 169) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 == 254; } return false; } }; static bool EnumInterfaces(LArray &Out); }; -class LgiNetClass LSocks5Socket : public LSocket +class LgiClass LSocks5Socket : public LSocket { LAutoString Proxy; int Port; LAutoString UserName; LAutoString Password; protected: bool Socks5Connected; public: LSocks5Socket(); LSocks5Socket &operator=(const LSocks5Socket &s) { Proxy.Reset(NewStr(s.Proxy)); UserName.Reset(NewStr(s.UserName)); Password.Reset(NewStr(s.Password)); Port = s.Port; return *this; } // Connection void SetProxy(char *proxy, int port, char *username, char *password); void SetProxy(const LSocks5Socket *s); int Open(const char *HostAddr, int port); // Server bool Listen(int Port) { return false; } }; -/// Uri parser -class LgiNetClass LUri -{ -public: - LString sProtocol; - LString sUser; - LString sPass; - LString sHost; - int Port; - LString sPath; - LString sAnchor; - - /// Parser for URI's. - LUri - ( - /// Optional URI to start parsing - const char *uri = 0 - ); - ~LUri(); - - bool IsProtocol(const char *p) { return sProtocol.Equals(p); } - bool IsHttp() { return sProtocol.Equals("http") || sProtocol.Equals("https"); } - bool IsFile() { return sProtocol.Equals("file"); } - void SetFile(LString Path) { Empty(); sProtocol = "file"; sPath = Path; } - const char *LocalPath(); - operator bool(); - - /// Parse a URI into it's sub fields... - bool Set(const char *uri); - - /// Re-constructs the URI - LString ToString(); - - /// Empty this object... - void Empty(); - - /// URL encode - LString EncodeStr - ( - /// The string to encode - const char *s, - /// [Optional] Any extra characters you want encoded - const char *ExtraCharsToEncode = 0 - ); - - /// URL decode - LString DecodeStr(const char *s); - - /// Separate args into map - typedef LHashTbl,LString> StrMap; - StrMap Params(); - - LUri &operator =(const LUri &u); - LUri &operator =(const char *s) { Set(s); return *this; } - LUri &operator +=(const char *s); -}; - -/// Proxy settings lookup -class LgiNetClass LProxyUri : public LUri -{ -public: - LProxyUri(); -}; - #define MAX_UDP_SIZE 512 class LUdpListener : public LSocket { LStream *Log; LString Context; public: bool Status = false; /* If this isn't working on Linux, most likely it's a firewall issue. For instance if you are running 'ufw' as your firewall you could allow packets through with: sudo ufw allow ${port}/udp */ LUdpListener(LArray interface_ips, uint32_t mc_ip, uint16_t port, LStream *log = NULL) : Log(log) { SetUdp(true); struct sockaddr_in addr; ZeroObj(addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); #ifdef WINDOWS addr.sin_addr.S_un.S_addr = INADDR_ANY; #elif defined(MAC) addr.sin_addr.s_addr = htonl(mc_ip); #else addr.sin_addr.s_addr = INADDR_ANY; #endif Context = "LUdpListener.bind"; Status = bind(Handle(), (struct sockaddr*)&addr, sizeof(addr)) == 0; if (!Status) { #ifdef WIN32 int err = WSAGetLastError(); #else int err = errno; #endif OnError(err, LErrorCodeToString(err)); LgiTrace("Error: Bind on %s:%i\n", LIpToStr(ntohl(addr.sin_addr.s_addr)).Get(), port); } if (mc_ip) { Context = "LUdpListener.AddMulticastMember"; for (auto ip: interface_ips) AddMulticastMember(mc_ip, ip); } } bool ReadPacket(LString &d, uint32_t &Ip, uint16_t &Port) { if (!IsReadable(10)) return false; char Data[MAX_UDP_SIZE]; int Rd = ReadUdp(Data, sizeof(Data), 0, &Ip, &Port); if (Rd <= 0) return false; d.Set(Data, Rd); return true; } void OnError(int ErrorCode, const char *ErrorDescription) { LString s; if (Context) s.Printf("Error: %s - %i, %s\n", Context.Get(), ErrorCode, ErrorDescription); else s.Printf("Error: %i, %s\n", ErrorCode, ErrorDescription); if (Log) Log->Print("%s", s.Get()); else LgiTrace("%s", s.Get()); } }; class LUdpBroadcast : public LSocket { // LArray Intf; uint32_t SelectIf; public: LUdpBroadcast(uint32_t selectIf) : SelectIf(selectIf) { SetBroadcast(); SetUdp(true); // EnumInterfaces(Intf); } bool BroadcastPacket(LString Data, uint32_t Ip, uint16_t Port) { return BroadcastPacket(Data.Get(), Data.Length(), Ip, Port); } bool BroadcastPacket(void *Ptr, size_t Size, uint32_t Ip, uint16_t Port) { if (Size > MAX_UDP_SIZE) return false; if (SelectIf) { struct in_addr addr; addr.s_addr = htonl(SelectIf); auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (char*)&addr, sizeof(addr)); if (r) LgiTrace("%s:%i - set IP_MULTICAST_IF for '%s' failed: %i\n", _FL, LIpToStr(SelectIf).Get(), r); SelectIf = 0; } uint32_t BroadcastIp = Ip; #if 0 LgiTrace("Broadcast %i.%i.%i.%i\n", (BroadcastIp >> 24) & 0xff, (BroadcastIp >> 16) & 0xff, (BroadcastIp >> 8) & 0xff, (BroadcastIp) & 0xff); #endif int wr = WriteUdp(Ptr, (int)Size, 0, BroadcastIp, Port); return wr == Size; } }; #endif diff --git a/include/lgi/common/NetTools.h b/include/lgi/common/NetTools.h deleted file mode 100644 --- a/include/lgi/common/NetTools.h +++ /dev/null @@ -1,16 +0,0 @@ -/// \file -/// \author Matthew Allen -/// \brief General socket / mail-news related classes and functions - -#pragma once - -#include "lgi/common/LgiNetInc.h" -#include "lgi/common/StringClass.h" - -/// Get a specific value from a list of headers (as a dynamic string) -LgiNetFunc char *InetGetHeaderField(const char *Headers, const char *Field, ssize_t Len = -1); -LgiNetFunc LString LGetHeaderField(LString Headers, const char *Field); - -/// Gets a sub field of a header value -LgiNetFunc char *InetGetSubField(const char *s, const char *Field); -LgiNetFunc LString LGetSubField(LString HeaderValue, const char *Field); diff --git a/include/lgi/common/OAuth2.h b/include/lgi/common/OAuth2.h --- a/include/lgi/common/OAuth2.h +++ b/include/lgi/common/OAuth2.h @@ -1,59 +1,60 @@ #ifndef _LOAUTH2_H_ #define _LOAUTH2_H_ ////////////////////////////////////////////////////////////////// #include "lgi/common/Net.h" +#include "lgi/common/Uri.h" /* Do this somewhere? LAutoString ErrorMsg; StartSSL(ErrorMsg, NULL); */ class LOAuth2 { struct LOAuth2Priv *d; public: struct Params { enum ServiceProvider { None, OAuthGoogle, OAuthMicrosoft, } Provider; LString ClientID; LString ClientSecret; LString RedirURIs; LString AuthUri; LString ApiUri; // LString RevokeUri; LString Scope; LUri Proxy; Params() { Provider = None; } bool IsValid() { return Provider != None && ClientID && ClientSecret && RedirURIs && AuthUri && Scope && ApiUri; } }; LOAuth2(Params ¶ms, const char *account, LDom *store, LCancel *cancel, LStream *log = NULL); virtual ~LOAuth2(); LString GetAccessToken(); bool Refresh(); }; #endif diff --git a/include/lgi/common/Uri.h b/include/lgi/common/Uri.h new file mode 100644 --- /dev/null +++ b/include/lgi/common/Uri.h @@ -0,0 +1,67 @@ +#pragma once + +#include "lgi/common/StringClass.h" + +/// Uri parser +class LgiClass LUri +{ +public: + LString sProtocol; + LString sUser; + LString sPass; + LString sHost; + int Port; + LString sPath; + LString sAnchor; + + /// Parser for URI's. + LUri + ( + /// Optional URI to start parsing + const char *uri = 0 + ); + ~LUri(); + + bool IsProtocol(const char *p) { return sProtocol.Equals(p); } + bool IsHttp() { return sProtocol.Equals("http") || sProtocol.Equals("https"); } + bool IsFile() { return sProtocol.Equals("file"); } + void SetFile(LString Path) { Empty(); sProtocol = "file"; sPath = Path; } + const char *LocalPath(); + operator bool(); + + /// Parse a URI into it's sub fields... + bool Set(const char *uri); + + /// Re-constructs the URI + LString ToString(); + + /// Empty this object... + void Empty(); + + /// URL encode + LString EncodeStr + ( + /// The string to encode + const char *s, + /// [Optional] Any extra characters you want encoded + const char *ExtraCharsToEncode = 0 + ); + + /// URL decode + LString DecodeStr(const char *s); + + /// Separate args into map + typedef LHashTbl,LString> StrMap; + StrMap Params(); + + LUri &operator =(const LUri &u); + LUri &operator =(const char *s) { Set(s); return *this; } + LUri &operator +=(const char *s); +}; + +/// Proxy settings lookup +class LgiClass LProxyUri : public LUri +{ +public: + LProxyUri(); +}; \ No newline at end of file diff --git a/src/common/Net/Http.cpp b/src/common/Net/Http.cpp --- a/src/common/Net/Http.cpp +++ b/src/common/Net/Http.cpp @@ -1,936 +1,935 @@ #include #include #include #include "lgi/common/Gdc2.h" #include "lgi/common/Http.h" #include "lgi/common/LgiCommon.h" -#include "lgi/common/NetTools.h" #include "lgi/common/LgiString.h" #include "lgi/common/Base64.h" #include "lgi/common/Variant.h" #define DEBUG_LOGGING 0 /////////////////////////////////////////////////////////////////// class ILogProxy : public LSocketI { LSocketI *Dest; public: ILogProxy(LSocketI *dest) { Dest = dest; } void OnError(int ErrorCode, const char *ErrorDescription) { if (Dest && ErrorDescription) { char Str[256]; sprintf_s(Str, sizeof(Str), "[Data] %s", ErrorDescription); Dest->OnInformation(Str); } } void OnInformation(const char *s) { if (Dest && s) { char Str[256]; sprintf_s(Str, sizeof(Str), "[Data] %s", s); Dest->OnInformation(Str); } } void OnDisconnect() { if (Dest) { Dest->OnInformation("[Data] Disconnect"); } } }; /////////////////////////////////////////////////////////////////// LHttp::LHttp() { Meter = 0; ResumeFrom = 0; Proxy = 0; ProxyPort = 0; Headers = 0; NoCache = false; BufferLen = 16 << 10; Buffer = new char[BufferLen]; } LHttp::~LHttp() { Close(); DeleteArray(Proxy); DeleteArray(Headers); DeleteArray(Buffer); } void LHttp::SetProxy(char *p, int Port) { DeleteArray(Proxy); Proxy = NewStr(p); ProxyPort = Port; } void LHttp::SetAuth(char *User, char *Pass) { AuthUser = User; AuthPassword = Pass; } bool LHttp::Open(LAutoPtr S, const char *RemoteHost, int Port) { Close(); Socket = S; if (Proxy) { RemoteHost = Proxy; Port = ProxyPort; } if (RemoteHost) { LUri u; if (stristr(RemoteHost, "://") || strchr(RemoteHost, '/')) u.Set(RemoteHost); else u.sHost = RemoteHost; if (!u.Port && Port) u.Port = Port; if (!Socket) ErrorMsg = "No socket object."; else if (Socket->Open(u.sHost, u.Port > 0 ? u.Port : HTTP_PORT)) return true; else ErrorMsg = Socket->GetErrorString(); } else ErrorMsg = "No remote host."; if (ErrorMsg) LgiTrace("%s:%i - %s.\n", _FL, ErrorMsg.Get()); return false; } bool LHttp::Close() { if (Socket) Socket->Close(); return 0; } bool LHttp::IsOpen() { return Socket != NULL; } enum HttpRequestType { HttpNone, HttpGet, HttpPost, HttpOther, }; bool LHttp::Request ( const char *Type, const char *Uri, int *ProtocolStatus, const char *InHeaders, LStreamI *InBody, LStreamI *Out, LStreamI *OutHeaders, ContentEncoding *OutEncoding ) { // Input validation if (!Socket || !Uri || !Out || !Type) return false; #if DEBUG_LOGGING LStringPipe Log; #endif HttpRequestType ReqType = HttpNone; if (!_stricmp(Type, "GET")) ReqType = HttpGet; else if (!_stricmp(Type, "POST")) ReqType = HttpPost; else ReqType = HttpOther; // Generate the request string LStringPipe Cmd; LUri u(Uri); bool IsHTTPS = u.sProtocol && !_stricmp(u.sProtocol, "https"); LString EncPath = u.EncodeStr(u.sPath.Get() ? u.sPath.Get() : (char*)"/"), Mem; char s[1024]; LHtmlLinePrefix EndHeaders("\r\n"); LStringPipe Headers; if (IsHTTPS && Proxy) { Cmd.Print( "CONNECT %s:%i HTTP/1.1\r\n" "Host: %s\r\n" "\r\n", u.sHost.Get(), u.Port ? u.Port : HTTPS_PORT, u.sHost.Get()); LAutoString c(Cmd.NewStr()); size_t cLen = strlen(c); ssize_t r = Socket->Write(c, cLen); if (r == cLen) { ssize_t Length = 0; while (Out) { ssize_t r = Socket ? Socket->Read(s, sizeof(s)) : -1; if (r > 0) { ssize_t e = EndHeaders.IsEnd(s, r); if (e < 0) { Headers.Write(s, r); } else { e -= Length; Headers.Write(s, e); break; } Length += r; } else break; } LAutoString Hdr(Headers.NewStr()); LVariant v; if (Socket) Socket->SetValue(LSocket_Protocol, v = "SSL"); EndHeaders.Reset(); } else return false; } Cmd.Print("%s %s HTTP/1.1\r\n", Type, (Proxy && !IsHTTPS) ? Uri : EncPath.Get()); Cmd.Print("Host: %s\r\n", u.sHost.Get()); if (InHeaders) Cmd.Write(InHeaders, strlen(InHeaders)); if (AuthUser && AuthPassword) { if (1) { // Basic authentication char Raw[128]; sprintf_s(Raw, sizeof(Raw), "%s:%s", AuthUser.Get(), AuthPassword.Get()); char Base64[128]; ZeroObj(Base64); ConvertBinaryToBase64(Base64, sizeof(Base64)-1, (uchar*)Raw, strlen(Raw)); Cmd.Print("Authorization: Basic %s\r\n", Base64); } else { // Digest authentication // Not implemented yet... LAssert(!"Not impl."); } } Cmd.Push("\r\n"); auto c = Cmd.NewLStr(); #if DEBUG_LOGGING Log.Print("HTTP req.hdrs=%s\n-------------------------------------\nHTTP req.body=", c.Get()); #endif bool Status = false; if (Socket && c) { // Write the headers... bool WriteOk = Socket->Write(c, c.Length()) == c.Length(); if (WriteOk) { // Write any body... if (InBody) { ssize_t r; while ((r = InBody->Read(s, sizeof(s))) > 0) { ssize_t w = Socket->Write(s, r); if (w < r) { return false; } #if DEBUG_LOGGING Log.Print("%.*s", (int)w, s); #endif } } // Read the response ssize_t Total = 0; ssize_t Used = 0; while (Out) { ssize_t r = Socket ? Socket->Read(s, sizeof(s)) : -1; if (r > 0) { ssize_t e = EndHeaders.IsEnd(s, r); if (e < 0) { Headers.Write(s, r); } else { e -= Total; Headers.Write(s, e); if (r > e) { // Move the tailing data down to the start of the buffer memmove(s, s + e, r - e); Used = r - e; } break; } Total += r; } else break; } // Process output LAutoString h(Headers.NewStr()); if (h) { #if DEBUG_LOGGING Log.Print("HTTP res.hdrs=%s\n-------------------------------------\nHTTP res.body=", h); #endif LAutoString sContentLen(InetGetHeaderField(h, "Content-Length")); int64 ContentLen = sContentLen ? atoi64(sContentLen) : -1; bool IsChunked = false; if (ContentLen > 0) Out->SetSize(ContentLen); else { LAutoString sTransferEncoding(InetGetHeaderField(h, "Transfer-Encoding")); IsChunked = sTransferEncoding && !_stricmp(sTransferEncoding, "chunked"); Out->SetSize(0); } LAutoString sContentEncoding(InetGetHeaderField(h, "Content-Encoding")); ContentEncoding Encoding = EncodeRaw; if (sContentEncoding && !_stricmp(sContentEncoding, "gzip")) Encoding = EncodeGZip; if (OutEncoding) *OutEncoding = Encoding; int HttpStatus = 0; if (_strnicmp(h, "HTTP/", 5) == 0) { HttpStatus = atoi(h + 9); if (ProtocolStatus) *ProtocolStatus = HttpStatus; } if (HttpStatus / 100 == 3) { FileLocation.Reset(InetGetHeaderField(h, "Location")); } if (OutHeaders) { OutHeaders->Write(h, strlen(h)); } if (IsChunked) { #ifdef _DEBUG LStringPipe Log; #endif while (true) { // Try and get chunk header char *End = Strnstr(s, "\r\n", Used); if (!End) { ssize_t r = Socket->Read(s + Used, sizeof(s) - Used); if (r < 0) break; Used += r; End = Strnstr(s, "\r\n", Used); if (!End) { LAssert(!"No chunk header"); break; } } // Process the chunk header End += 2; ssize_t HdrLen = End - s; int ChunkSize = htoi(s); if (ChunkSize <= 0) { // End of stream. Status = true; break; } ssize_t ChunkDone = 0; memmove(s, End, Used - HdrLen); Used -= HdrLen; // Loop over the body of the chunk while (Socket && ChunkDone < ChunkSize) { ssize_t Remaining = ChunkSize - ChunkDone; ssize_t Common = MIN(Used, Remaining); #ifdef _DEBUG Log.Print("%s:%i - remaining:%i, common=%i\n", _FL, (int)Remaining, (int)Common); #endif if (Common > 0) { ssize_t w = Out->Write(s, Common); #ifdef _DEBUG Log.Print("%s:%i - w:%i\n", _FL, (int)w); #endif if (w != Common) { LAssert(!"Write failed."); break; } if (Used > Common) { #ifdef _DEBUG Log.Print("%s:%i - common:%i, used=%i\n", _FL, (int)Common, (int)Used); #endif memmove(s, s + Common, Used - Common); } ChunkDone += Common; Used -= Common; if (ChunkDone >= ChunkSize) { #ifdef _DEBUG Log.Print("%s:%i - chunkdone\n", _FL); #endif break; } } ssize_t r = Socket->Read(s + Used, sizeof(s) - Used); #ifdef _DEBUG Log.Print("%s:%i - r:%i\n", _FL, (int)r); #endif if (r < 0) break; Used += r; } // Loop over the CRLF postfix if (Socket && Used < 2) { ssize_t r = Socket->Read(s + Used, sizeof(s) - Used); #ifdef _DEBUG Log.Print("%s:%i - r:%i\n", _FL, (int)r); #endif if (r < 0) break; Used += r; } if (Used < 2 || s[0] != '\r' || s[1] != '\n') { #ifdef _DEBUG LgiTrace("Log: %s\n", Log.NewLStr().Get()); #endif LAssert(!"Post fix missing."); break; } if (Used > 2) memmove(s, s + 2, Used - 2); Used -= 2; } } else { // Non chunked connection. int64 Written = 0; if (Used > 0) { // auto Pos = Out->GetPos(); ssize_t w = Out->Write(s, Used); /* LgiTrace("%s:%i - Write @ " LPrintfInt64 " of " LPrintfSSizeT " = " LPrintfSSizeT " (%x,%x,%x,%x)\n", _FL, Pos, Used, w, (uint8_t)s[0], (uint8_t)s[1], (uint8_t)s[2], (uint8_t)s[3]); */ #if DEBUG_LOGGING Log.Write(s, w); #endif if (w == Used) { Written += w; Used = 0; } else { LAssert(0); Written = -1; } } while ( Socket && Written >= 0 && Written < ContentLen && Socket->IsOpen()) { ssize_t r = Socket->Read(s, sizeof(s)); if (r <= 0) { LgiTrace("%s:%i - Socket read failed.\n", _FL); break; } // auto Pos = Out->GetPos(); ssize_t w = Out->Write(s, r); /* auto NewPos = Out->GetPos(); LgiTrace("%s:%i - Write @ " LPrintfInt64 " of " LPrintfSSizeT " = " LPrintfSSizeT " (%x,%x,%x,%x) " LPrintfSSizeT "\n", _FL, Pos, r, w, (uint8_t)s[0], (uint8_t)s[1], (uint8_t)s[2], (uint8_t)s[3], NewPos); */ #if DEBUG_LOGGING Log.Print("%.*s", (int)w, s); #endif if (w != r) { LgiTrace("%s:%i - File write failed.\n", _FL); break; } Written += w; } Status = Written == ContentLen; if (Written != ContentLen) { LgiTrace("%s:%i - HTTP length not reached.\n", _FL); } #if DEBUG_LOGGING Log.Print("\n---------------------------------------------\n"); #endif } } } } #if DEBUG_LOGGING // if (ProtocolStatus && *ProtocolStatus >= 300) { auto LogData = Log.NewLStr(); if (LogData) { LgiTrace("%.*s\n", (int)LogData.Length(), LogData.Get()); int asd=0; } } #endif return Status; } #if 0 #define HTTP_POST_LOG 1 bool LHttp::Post ( char *File, const char *ContentType, LStreamI *In, int *ProtocolStatus, LStreamI *Out, LStreamI *OutHeaders, char *InHeaders ) { bool Status = false; if (Socket && File && Out) { // Read in the data part of the PUT command, because we need the // Content-Length to put in the headers. LStringPipe Content; while (In) { char s[1024]; int r = In->Read(s, sizeof(s)); if (r > 0) { Content.Write(s, r); } else break; } // Generate the request string LStringPipe Cmd; Cmd.Print("POST %s HTTP/1.0\r\n", File); if (InHeaders) Cmd.Print("%s", InHeaders); if (ContentType) Cmd.Print("Content-Type: %s\r\n", ContentType); Cmd.Print("Content-Length: %i\r\n\r\n", Content.GetSize()); char *s = Cmd.NewStr(); if (s) { #if HTTP_POST_LOG LgiTrace("IHTTP::Post write headers:\n%s\n\n", s); #endif // Write the headers... int SLen = strlen(s); bool WriteOk = Socket->Write(s, SLen) == SLen; DeleteArray(s); #if HTTP_POST_LOG LgiTrace("IHTTP::Post WriteOk=%i\n", WriteOk); #endif if (WriteOk) { // Copy all the 'Content' to the socket char Buf[1024]; while (true) { int r = Content.Read(Buf, sizeof(Buf)); #if HTTP_POST_LOG LgiTrace("IHTTP::Post Content.Read=%i\n", r); #endif if (r > 0) { int w = Socket->Write(Buf, r); #if HTTP_POST_LOG LgiTrace("IHTTP::Post Socket.Write=%i\n", w); #endif if (w <= 0) { break; } } else break; } #if HTTP_POST_LOG LgiTrace("IHTTP::Post Starting on response\n"); #endif // Read the response LHtmlLinePrefix EndHeaders("\r\n"); int Total = 0; LStringPipe Headers; while (Out) { int r = Socket->Read(Buf, sizeof(Buf)); #if HTTP_POST_LOG LgiTrace("IHTTP::Post Read=%i\n", r); #endif if (r > 0) { int e = EndHeaders.IsEnd(Buf, r); #if HTTP_POST_LOG LgiTrace("IHTTP::Post EndHeaders=%i\n", e); #endif if (e < 0) { Headers.Write(Buf, r); } else { e -= Total; Headers.Write(Buf, e); Out->Write(Buf + e, r - e); break; } Total += r; } else break; } // Process output char *h = Headers.NewStr(); #if HTTP_POST_LOG LgiTrace("IHTTP::Post Headers=\n%s\n\n", h); #endif if (h) { if (ProtocolStatus) { char *Eol = strchr(h, '\r'); if (Eol) { LToken Resp(h, " ", true, Eol-h); if (Resp.Length() > 1) *ProtocolStatus = atoi(Resp[1]); } } if (OutHeaders) { OutHeaders->Write(h, strlen(h)); } DeleteArray(h); while (Socket->IsOpen()) { int r = Socket->Read(Buf, sizeof(Buf)); #if HTTP_POST_LOG LgiTrace("IHTTP::Post ResponseBody.Read=%i\n", r); #endif if (r > 0) { Out->Write(Buf, r); } else break; } Status = true; } } } } return Status; } #endif /////////////////////////////////////////////////////////////////////////// #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/Net.h" #define COMP_FUNCTIONS 1 #include "lgi/common/ZlibWrapper.h" void ZLibFree(voidpf opaque, voidpf address) { // Do nothing... the memory is owned by an autoptr } bool LgiGetUri(LCancel *Cancel, LStreamI *Out, LString *OutError, const char *InUri, const char *InHeaders, LUri *InProxy) { if (!InUri || !Out) { if (OutError) OutError->Printf("Parameter missing error (%p,%p)", InUri, Out); return false; } LHttp Http; int RedirectLimit = 10; LAutoString Location; for (int i=0; iIsCancelled()); i++) { LUri u(InUri); bool IsHTTPS = u.sProtocol && !_stricmp(u.sProtocol, "https"); int DefaultPort = IsHTTPS ? HTTPS_PORT : HTTP_PORT; if (InProxy) Http.SetProxy(InProxy->sHost, InProxy->Port ? InProxy->Port : DefaultPort); LAutoPtr s; if (IsHTTPS) { SslSocket *ssl; s.Reset(ssl = new SslSocket()); ssl->SetSslOnConnect(true); } else { s.Reset(new LSocket); } if (!s) { if (OutError) OutError->Printf("Alloc Failed"); return false; } if (Cancel) s->SetCancel(Cancel); s->SetTimeout(10 * 1000); if (!Http.Open(s, InUri, DefaultPort)) { if (OutError) OutError->Printf("Http open failed for '%s:%i'", InUri, DefaultPort); return false; } const char DefaultHeaders[] = "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/39.0\r\n" "Accept: text/html,application/xhtml+xml,application/xml,image/png,image/*;q=0.9,*/*;q=0.8\r\n" "Accept-Language: en-US,en;q=0.5\r\n" "Accept-Encoding: gzip, deflate\r\n" "Connection: keep-alive\r\n"; LString InputHeaders; if (InHeaders) { auto Hdrs = LString(InHeaders).SplitDelimit("\r\n"); for (auto h: Hdrs) { LString s; s.Printf("%s\r\n", h.Get()); InputHeaders += s; } } else { InputHeaders = DefaultHeaders; } int Status = 0; LHttp::ContentEncoding Enc; LStringPipe OutHeaders; LStringPipe TmpFile(4 << 10); Http.Get(InUri, InHeaders ? InputHeaders : DefaultHeaders, &Status, &TmpFile, &Enc, &OutHeaders); int StatusCatagory = Status / 100; if (StatusCatagory == 3) { LString Headers = OutHeaders.NewLStr(); LgiTrace("Header=%s\n", Headers.Get()); LAutoString Loc(InetGetHeaderField(Headers, "Location", -1)); if (!Loc) { if (OutError) *OutError = "HTTP redirect doesn't have a location header."; return false; } if (!_stricmp(Loc, InUri)) { if (OutError) *OutError = "HTTP redirect to same URI."; return false; } LgiTrace("HTTP redir(%i) from '%s' to '%s'\n", i, InUri, Loc.Get()); LUri u(Loc); if (!u.sHost) { LUri in(InUri); in.sPath = u.sPath; Location.Reset(NewStr(in.ToString())); } else { Location = Loc; } InUri = Location; continue; } else if (StatusCatagory != 2) { // auto Hdr = OutHeaders.NewLStr(); Enc = LHttp::EncodeRaw; if (OutError) OutError->Printf("Got %i for '%.200s'\n", Status, InUri); return false; } Http.Close(); if (Enc == LHttp::EncodeRaw) { // Copy TmpFile to Out LCopyStreamer Cp; if (!Cp.Copy(&TmpFile, Out)) { if (OutError) *OutError = "Stream copy failed."; return false; } } else if (Enc == LHttp::EncodeGZip) { int64 Len = TmpFile.GetSize(); if (Len <= 0) { if (OutError) *OutError = "No data to ungzip."; return false; } LAutoPtr Data(new uchar[(size_t)Len]); if (!Data) { if (OutError) *OutError = "Alloc Failed"; return false; } TmpFile.Read(Data, (int)Len); LAutoPtr z; if (!z) z.Reset(new Zlib); else if (!z->IsLoaded()) z->Reload(); if (z && z->IsLoaded()) { z_stream Stream; ZeroObj(Stream); Stream.next_in = Data; Stream.avail_in = (uInt) Len; Stream.zfree = ZLibFree; int r = z->inflateInit2_(&Stream, 16+MAX_WBITS, ZLIB_VERSION, sizeof(z_stream)); uchar Buf[4 << 10]; Stream.next_out = Buf; Stream.avail_out = sizeof(Buf); while ( Stream.avail_in > 0 && (r = z->inflate(&Stream, Z_NO_FLUSH)) >= 0) { Out->Write(Buf, sizeof(Buf) - Stream.avail_out); Stream.next_out = Buf; Stream.avail_out = sizeof(Buf); } r = z->inflateEnd(&Stream); } else { GdcD->NeedsCapability("zlib"); if (OutError) OutError->Printf("Gzip decompression not available (needs %s)", z ? z->Name() : "zlib library"); return false; } } break; } return true; } diff --git a/src/common/Net/HttpTools.cpp b/src/common/Net/HttpTools.cpp --- a/src/common/Net/HttpTools.cpp +++ b/src/common/Net/HttpTools.cpp @@ -1,1146 +1,1145 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/HttpTools.h" #include "lgi/common/Net.h" -#include "lgi/common/NetTools.h" #include "lgi/common/Http.h" #ifdef _DEBUG // #define PROXY_OVERRIDE "10.10.0.50:8080" #endif LXmlTag *GetFormField(LXmlTag *Form, char *Field) { if (Form && Field) { char *Ast = strchr(Field, '*'); LArray Matches; for (auto x: Form->Children) { char *Name = x->GetAttr("Name"); if (Name) { if (Ast) { if (MatchStr(Field, Name)) { Matches.Add(x); } } else if (_stricmp(Name, Field) == 0) { Matches.Add(x); } } } if (Matches.Length() >= 1) { return Matches[0]; } /* else if (Matches.Length() > 1) { for (int i=0; iGetAttr("Name")); } LAssert(0); } */ } return 0; } void StrFormEncode(LStream &p, char *s, bool InValue) { for (char *c = s; *c; c++) { if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') { p.Write(c, 1); } else if (*c == ' ') { p.Write((char*)"+", 1); } else { p.Print("%%%02.2X", *c); } } } // This just extracts the forms in the HTML and makes an XML tree of what it finds. LXmlTag *ExtractForms(char *Html, LStream *Log) { LXmlTag *f = 0; if (Html) { WebPage Page(Html, Log); if (Page.Html) { LXmlTag *x = Page.GetRoot(Log); if (x) { for (auto It = x->Children.begin(); It != x->Children.end(); It++) { auto c = *It; if (c->IsTag("form")) { if (!f) { f = new LXmlTag("Forms"); } if (f) { LXmlTag *Form = new LXmlTag("Form"); if (Form) { f->InsertTag(Form); char *s = c->GetAttr("action"); if (s) { LStringPipe p; for (char *c = s; *c; c++) { if (*c == ' ') { char h[6]; sprintf_s(h, sizeof(h), "%%%2.2X", (uint8_t)*c); p.Push(h); } else p.Push(c, 1); } char *e = p.NewStr(); Form->SetAttr("action", e); DeleteArray(e); } s = c->GetAttr("method"); if (s) Form->SetAttr("method", s); while (c) { #define CopyAttr(name) \ { char *s = c->GetAttr(name); \ if (s) i->SetAttr(name, s); } if (c->IsTag("input")) { LXmlTag *i = new LXmlTag("Input"); if (i) { Form->InsertTag(i); CopyAttr("type"); CopyAttr("name"); CopyAttr("value"); } } if (c->IsTag("select")) { LXmlTag *i = new LXmlTag("Select"); if (i) { Form->InsertTag(i); CopyAttr("name"); while ((c && !c->GetTag()) || _stricmp(c->GetTag(), "/select") != 0) { if (c->IsTag("option")) { LXmlTag *o = new LXmlTag("Option"); if (o) { char *s = c->GetAttr("Value"); if (s) o->SetAttr("Value", s); o->SetContent(c->GetContent()); i->InsertTag(o); } } c = *(++It); } } } if (c->IsTag("/form")) { break; } } } } } } } } else if (Log) Log->Print("%s:%i - No html after parsing out scripts\n", __FILE__, __LINE__); } else if (Log) Log->Print("%s:%i - No html.\n", __FILE__, __LINE__); return f; } void XmlToStream(LStream *s, LXmlTag *x, char *Style) { if (s && x) { LStringPipe p(1024); LXmlTree t; if (Style) t.SetStyleFile(Style, "text/xsl"); t.Write(x, &p); int Len = (int)p.GetSize(); char *Xml = p.NewStr(); if (Xml) { s->Write(Xml, Len); DeleteArray(Xml); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// WebPage::WebPage(char *Page, LStream *Log) { Html = 0; Script = 0; Parsed = 0; Charset = 0; if (Page) { // Parse out the scripts... LStringPipe h, scr; for (char *s = Page; s && *s; ) { char *e = stristr(s, "'); if (e) { e++; h.Push(s, (int) (e - s)); s = e; e = stristr(s, ""); if (e) { scr.Push(s, (int) (e - s)); s = e; } else { if (Log) Log->Print("%s:%i - No end of script tag????\n", __FILE__, __LINE__); scr.Push(s); break; } } else break; } else { h.Push(s); break; } } Html = h.NewStr(); Script = scr.NewStr(); } } WebPage::~WebPage() { DeleteArray(Html); DeleteArray(Script); DeleteArray(Charset); DeleteObj(Parsed); } char *WebPage::GetCharSet() { if (!Charset) { LXmlTag *t = GetRoot(); if (t) { auto it = t->Children.begin(); for (LXmlTag *c = *it; !Charset && c; c = *++it) { if (c->IsTag("meta")) { char *http_equiv = c->GetAttr("http-equiv"); if (http_equiv && _stricmp(http_equiv, "Content-Type") == 0) { char *Value = c->GetAttr("Content"); if (Value) { char *s = stristr(Value, "charset="); if (s) { s += 8; char *e = s; while (*e && (isalpha(*e) || isdigit(*e) || *e == '-' || *e == '_')) e++; Charset = NewStr(s, e - s); } } } } } } } return Charset; } LXmlTag *WebPage::GetRoot(LStream *Log) { if (!Parsed && Html) { LXmlTree t(GXT_NO_DOM); if ((Parsed = new LXmlTag)) { LStringPipe p; p.Push(Html); t.GetEntityTable()->Add("nbsp", ' '); if (!t.Read(Parsed, &p, 0) && Log) { Log->Print("%s:%i - Html parse failed: %s\n", _FL, t.GetErrorMsg()); DeleteObj(Parsed); } } } return Parsed; } char *WebPage::GetFormValue(char *field) { LXmlTag *x = GetRoot(); if (x) { for (auto t: x->Children) { if (t->IsTag("input")) { char *Name = t->GetAttr("name"); if (Name && _stricmp(Name, field) == 0) { return t->GetAttr("Value"); } } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// FormPost::FormPost(LXmlTag *f) { Form = f; } LXmlTag *FormPost::GetField(char *n) { if (n && Form) { char *a = strchr(n, '*'); for (auto t: Form->Children) { char *Name = t->GetAttr("name"); if (Name) { if (a) { if (MatchStr(n, Name)) return t; } else { if (_stricmp(Name, n) == 0) return t; } } } } return 0; } char *FormPost::GetActionUri() { return Form->GetAttr("action"); } char *FormPost::EncodeFields(LStream *Debug, char *RealFields, bool EncodePlus) { LStringPipe p; LHashTbl,bool> Done; LHashTbl,char*> Real; if (RealFields) { auto t = LString(RealFields).SplitDelimit("\n"); for (unsigned i=0; iValue && v->Field) { #if 0 if (Debug && stricmp(v->Field, "__VIEWSTATE") == 0) { Debug->Print("OurField '%s' is %i long\n", v->Field, strlen(v->Value)); } #endif if (!Done.Find(v->Field)) { Done.Add(v->Field, true); if (p.GetSize()) p.Push("&"); char *a; while ((a = strchr(v->Field, ' '))) *a = '+'; while ((a = strchr(v->Value, ' '))) *a = '+'; StrFormEncode(p, v->Field, false); p.Write((char*)"=", 1); StrFormEncode(p, v->Value, EncodePlus); if (Debug && RealFields) { char *Value = Real.Find(v->Field); if (Value) { if (_stricmp(Value, v->Value) != 0) { Debug->Print("\tValues for '%s' different: '%s' = '%s'\n", v->Field.Get(), Value, v->Value.Get()); } } else { Debug->Print("\tExtra field in our list: %s = %s\n", v->Field.Get(), v->Value.Get()); } Real.Delete(v->Field); } } } } if (Debug && RealFields) { // char *k; // for (char *p = Real.First(&k); p; p = Real.Next(&k)) for (auto k : Real) { Debug->Print("\tMissing field: %s = %s\n", k.key, k.value); } } #if 0 // if (Form) { for (LXmlTag *t = Form->Children.First(); t; t = Form->Children.Next()) { char *Type = t->GetAttr("type"); if (Type && _stricmp(Type, "image") == 0) continue; char *Name = t->GetAttr("name"); if (Name) { if (!Done.Find(Name)) { Done.Add(Name, true); if (p.GetSize()) p.Push("&"); StrFormEncode(p, Name, false); p.Write((char*)"=", 1); } } } } #endif return p.NewStr(); } bool Match(char *a, char *b) { if (a && b) { bool Fuzzy = strchr(b, '*') != 0; if (Fuzzy) { return MatchStr(b, a); } else { return _stricmp(a, b) == 0; } } return false; } bool FormPost::Set(char *field, char *value, LStream *Log, bool AllowCreate) { bool Status = false; if (field && value && Form) { LXmlTag *f = GetFormField(Form, field); if (f) { if (f->GetTag()) { if (f->IsTag("Input")) { char *Nm = f->GetAttr("Name"); FormValue *v = Nm ? Get(Nm) : 0; if (v) { v->Value.Reset(NewStr(value)); Status = true; } } else if (f->IsTag("Select")) { char *Nm = f->GetAttr("Name"); if (Nm && Match(Nm, field)) { for (auto o: f->Children) { char *Value = o->GetAttr("Value"); char *Content = o->GetContent(); if (Value || Content) { bool Mat = !ValidStr(value); if (!Mat) { Mat = Match(Value, value); } if (!Mat) { Mat = Match(Content, value); } if (Mat) { FormValue *v = Nm ? Get(Nm) : 0; if (v) { if (ValidStr(value)) { char *n = o->GetAttr("Value"); if (n) { v->Value.Reset(NewStr(n)); Status = true; // if (Log) Log->Print("'%s'='%s'\n", v->Field, v->Value); break; } } else { v->Value.Reset(NewStr(value)); Status = true; break; } } } } } if (!Status && Log) { Log->Print("Error: No option for field '%s' had the value '%s'\n", field, value); } } } else if (Log) Log->Print("%s:%i - Unrecognised field '%s'\n", _FL, f->GetTag()); } else if (Log) Log->Print("%s:%i - Field has no tag\n", _FL); } else { FormValue *v = Get(field, AllowCreate); if (v) { v->Value.Reset(NewStr(value)); // if (Log) Log->Print("'%s'='%s'\n", v->Field, v->Value); Status = true; } else if (Log) { Log->Print("%s:%i - Invalid field name '%s'\n", _FL, field); } } } else { if (Log) Log->Print("%s:%i - Invalid arguments trying to set '%s' = '%s'\n", _FL, field, value); } return Status; } FormValue *FormPost::Get(char *Field, bool Create) { char *Ast = strchr(Field, '*'); LArray Matches; for (unsigned i=0; iField.Reset(NewStr(Field)); return v; } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////////// HttpTools::HttpTools() { Wnd = 0; } HttpTools::~HttpTools() { } void HttpTools::DumpView(LViewI *v, char *p) { if (v && p) { uint8_t *c = (uint8_t*) p; while (*c) { if (*c & 0x80) { *c = ' '; } c++; } v->Name(p); } } char *HttpTools::Fetch(char *uri, LStream *Log, LViewI *Dump, CookieJar *Cookies) { char *Page = 0; const char *DefHeaders = "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.9) Gecko/20061206 Firefox/1.5.0.9\r\n" "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n" "Accept-Language: en-us,en;q=0.5\r\n" "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"; if (ValidStr(uri)) { LHttp h; LProxyUri Proxy; if (Proxy.sHost) h.SetProxy(Proxy.sHost, Proxy.Port); LUri u(uri); if (!u.Port) u.Port = HTTP_PORT; LAutoPtr Sock(new LSocket); if (h.Open(Sock, u.sHost)) { int ProtocolStatus = 0; LStringPipe p, hdr; auto Enc = u.ToString(); LHttp::ContentEncoding type; if (h.Get(Enc, DefHeaders, &ProtocolStatus, &p, &type, &hdr)) { if (Cookies) { char *Headers = hdr.NewStr(); if (Headers) { Cookies->Set(Headers); DeleteArray(Headers); } } if (ProtocolStatus == 200) { Page = p.NewStr(); if (Page) { char *Err = stristr(Page, ""); if (Err) { Err[5] = ' '; } DumpView(Dump, Page); } else { Log->Print("Error: No page data for '%s'\n", uri); } } /* else if (ProtocolStatus == 302) { char *Headers = hdr.NewStr(); if (Headers) { char *Loc = InetGetHeaderField(Headers, "Location", -1); if (Loc) { Log->Print("Redirects to '%s'\n", Loc); DeleteArray(Loc); } DeleteArray(Headers); } } */ else if (Log) { if (Dump) { char *pg = p.NewStr(); if (pg) Dump->Name(pg); DeleteArray(pg); } Log->Print("Error: Error getting '%s' with HTTP error %i\n", Enc.Get(), ProtocolStatus); } } else if (Log) { Log->Print("Error: Failed to GET '%s' (errorcode: %i)\n", uri, ProtocolStatus); } } else if (Log) { Log->Print("Error: Couldn't open connection to '%s' [:%s]\n", u.sHost.Get(), u.Port); } } return Page; } char *HttpTools::Post(char *uri, char *headers, char *body, LStream *Log, LViewI *Dump) { if (uri && headers && body) { LHttp h; LUri u(uri); LSocket s; bool Open; LProxyUri Proxy; if (Proxy.sHost) { Open = s.Open(Proxy.sHost, Proxy.Port) != 0; } else { Open = s.Open(u.sHost, u.Port ? u.Port : 80) != 0; } if (Open) { char *e = headers + strlen(headers) - 1; while (e > headers && strchr("\r\n", *e)) { *e-- = 0; } size_t ContentLen = strlen(body); LStringPipe p; auto EncPath = u.EncodeStr(u.sPath); if (Proxy.sHost) { p.Print("POST http://%s%s HTTP/1.1\r\n" "Host: %s\r\n", u.sHost.Get(), EncPath.Get(), u.sHost.Get()); } else { p.Print("POST %s HTTP/1.1\r\n" "Host: %s\r\n", EncPath.Get(), u.sHost.Get()); } p.Print("Content-Length: %i\r\n" "%s\r\n" "\r\n", ContentLen, headers); int64 Len = p.GetSize(); char *h = p.NewStr(); if (h) { ssize_t w = s.Write(h, (int)Len, 0); if (w == Len) { w = s.Write(body, ContentLen, 0); if (w == ContentLen) { char Buf[1024]; ssize_t r; while ((r = s.Read(Buf, sizeof(Buf), 0)) > 0) { p.Push(Buf, r); } if (Log && p.GetSize() == 0) { Log->Print("HTTP Response: Failed with %i\n", r); } if (p.GetSize()) { char *Page = p.NewStr(); if (Page) { DumpView(Dump, Page); return Page; } } } else { Log->Print("HTTP Request: Error, wrote %i of %i bytes\n", w, ContentLen); } } else { Log->Print("HTTP Request: Error, wrote %i of %i bytes\n", w, Len); } DeleteArray(h); } } else if (Log) { Log->Print("Error: Failed to open socket.\n"); } } return 0; } ////////////////////////////////////////////////////////////////////// void CookieJar::Empty() { // for (char *p = (char*)First(); p; p = (char*)Next()) for (auto p : *this) { DeleteArray(p.value); } } char *EndCookie(char *s) { while (*s) { if (*s == '\n' || *s == '\r' || *s == ';') return s; s++; } return 0; } void CookieJar::Set(char *Cookie, char *Value) { char *s; if ((s = (char*)Find(Cookie))) { DeleteArray(s); Delete(Cookie); } Add(Cookie, NewStr(Value)); } void CookieJar::Set(char *Headers) { Empty(); if (Headers) { const char *Match = "Set-Cookie"; size_t MatchLen = strlen(Match); char Ws[] = " \t\r\n"; for (char *s = stristr(Headers, "\r\n"); s && *s; ) { while (*s && strchr(Ws, *s)) s++; char *Hdr = s; while (*s && *s != ':' && !strchr(Ws, *s)) s++; if (*s == ':') { ssize_t Len = s - Hdr; bool IsCookie = Len == MatchLen && _strnicmp(Hdr, Match, Len) == 0; s++; while (*s && strchr(Ws, *s)) s++; if (IsCookie) { char *e; while ((e = EndCookie(s))) { char *Cookie = NewStr(s, e - s); if (Cookie) { char *Eq = strchr(Cookie, '='); if (Eq) { *Eq++ = 0; Add(Cookie, NewStr(Eq)); } } s = e + 1; while (*s && strchr(" \t", *s)) s++; if (*s == '\r' || *s == '\n' || *s == 0) break; } } while (s && *s) { while (*s && *s != '\n') s++; if (*s) { if (*s == '\n' && s[1] && strchr(Ws, s[1])) s += 2; else break; } } } else break; } } } char *CookieJar::Get() { LStringPipe p; // char *k; // for (char *s = (char*)First(&k); s; s = (char*)Next(&k)) for (auto k : *this) { if (p.GetSize()) p.Print("; "); p.Print("%s=%s", k.key, k.value); } return p.NewStr(); } /////////////////////////////////////////////////////////////////////////////////// char *HtmlTidy(char *Html) { LStringPipe p(256); int Depth = 0; bool LastWasEnd = false; for (char *s = Html; *s; ) { char *c = s; while (*c) { if (*c == '<') { // start tag if (c > s) { Depth++; for (int i=0; i') c++; bool IsEnd = s[1] == '/'; if (IsEnd) { // end tag Depth--; } else { // start tag if (!LastWasEnd) Depth++; } for (int i=0; i Sock(new LSocket); if (Http.Open(Sock, u.sHost)) { LStringPipe Data; int Code = 0; LHttp::ContentEncoding enc; if (Http.Get(Uri, 0, &Code, &Data, &enc)) { if (Code == 200) { char n[MAX_PATH_LEN], r[32]; do { sprintf_s(r, sizeof(r), "_%x", LRand()); LMakePath(n, sizeof(n), LGetSystemPath(LSP_TEMP), r); } while (LFileExists(n)); LFile f; if (f.Open(n, O_WRITE)) { LCopyStreamer c; c.Copy(&Data, &f); f.Close(); if ((Img = GdcD->Load(n))) { // LgiTrace("Read uri '%s'\n", __FILE__, __LINE__, Uri); } else LgiTrace("%s:%i - Failed to read image '%s'\n", _FL, n); FileDev->Delete(n, false); } else LgiTrace("%s:%i - Failed to open '%s'\n", _FL, n); } else LgiTrace("%s:%i - HTTP code %i\n", _FL, Code); } else LgiTrace("%s:%i - failed to download to '%s'\n", _FL, Uri); } else LgiTrace("%s:%i - failed to connect to '%s:%i'\n", _FL, u.sHost.Get(), u.Port); } return Img; } 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,2709 +1,2708 @@ /*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, [&Name, &Addr](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, [&Name, &Addr](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.NewLStr(); 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.NewLStr(); } ////////////////////////////////////////////////////////////////////////////////////////////////// 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(LString &s) { 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 { auto s = Socket->Read(); if (!s) break; a += s; if (!a || a[0] != '+') return NULL; } 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/Net/MailImap.cpp b/src/common/Net/MailImap.cpp --- a/src/common/Net/MailImap.cpp +++ b/src/common/Net/MailImap.cpp @@ -1,3331 +1,3330 @@ #include #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/Mail.h" #include "lgi/common/Base64.h" -#include "lgi/common/NetTools.h" #include "lgi/common/DocView.h" #include "lgi/common/Http.h" #include "lgi/common/HttpTools.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/Json.h" #define DEBUG_OAUTH2 1 #ifdef _DEBUG #define DEBUG_FETCH 0 #else #define DEBUG_FETCH 0 #endif #define OPT_ImapOAuth2AccessToken "OAuth2AccessTok" #undef _FL #define _FL LGetLeaf(__FILE__), __LINE__ //////////////////////////////////////////////////////////////////////////// #if GPL_COMPATIBLE #include "AuthNtlm/Ntlm.h" #else #include "../src/common/Net/libntlm-0.4.2/ntlm.h" #endif #if HAS_LIBGSASL #include "gsasl.h" #endif static const char *sRfc822Header = "RFC822.HEADER"; static const char *sRfc822Size = "RFC822.SIZE"; struct TraceLog : public LStream { ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { LgiTrace("%.*s", (int)Size, Buffer); return Size; } }; /* #define SkipWhiteSpace(s) while (*s && IsWhiteSpace(*s)) s++; bool JsonDecode(LXmlTag &t, const char *s) { if (*s != '{') return false; s++; while (*s) { SkipWhiteSpace(s); if (*s != '\"') break; LAutoString Variable(LTokStr(s)); SkipWhiteSpace(s); if (*s != ':') return false; s++; SkipWhiteSpace(s); LAutoString Value(LTokStr(s)); SkipWhiteSpace(s); t.SetAttr(Variable, Value); if (*s != ',') break; s++; } if (*s != '}') return false; s++; return true; } */ #define SkipWhite(s) while (*s && strchr(WhiteSpace, *s)) s++ #define SkipSpaces(s) while (*s && strchr(" \t", *s)) s++ #define SkipNonWhite(s) while (*s && !strchr(WhiteSpace, *s)) s++; #define ExpectChar(ch) if (*s != ch) return 0; s++ ssize_t MailIMap::ParseImapResponse(char *Buffer, ssize_t BufferLen, LArray &Ranges, int Names) { Ranges.Length(0); if (!*Buffer || *Buffer != '*') return 0; #ifdef _DEBUG char *End = Buffer + BufferLen; #endif char *s = Buffer + 1; char *Start; for (int n=0; nOpen(u.sHost, u.Port?u.Port:443)) return false; ssize_t w = S->Write(Req, ReqLen); if (w != ReqLen) return false; char Buf[256]; LArray Res; ssize_t r; ssize_t ContentLen = 0; ssize_t HdrLen = 0; while ((r = S->Read(Buf, sizeof(Buf))) > 0) { ssize_t Old = Res.Length(); Res.Length(Old + r); memcpy(&Res[Old], Buf, r); if (ContentLen) { if ((ssize_t)Res.Length() >= HdrLen + ContentLen) break; } else { auto Eoh = Strnstr(&Res[0], "\r\n\r\n", Res.Length()); if (Eoh) { HdrLen = Eoh - &Res[0]; LAutoString c(InetGetHeaderField(&Res[0], "Content-Length", HdrLen)); if (c) { ContentLen = atoi(c); } } } } char *Rp = &Res[0]; auto Eoh = Strnstr(Rp, "\r\n\r\n", Res.Length()); if (Eoh) { if (OutHeaders) OutHeaders->Reset(NewStr(Rp, Eoh-Rp)); if (OutBody) OutBody->Reset(NewStr(Eoh + 4, Res.Length() - (Eoh-Rp) - 4)); if (StatusCode) { *StatusCode = 0; char *Eol = strchr(Rp, '\n'); if (Eol) { auto t = LString(Rp, Eol-Rp).SplitDelimit(" \t\r\n"); if (t.Length() > 2) *StatusCode = (int)t[1].Int(); } } } else return false; #ifndef _DEBUG LFile f; if (f.Open("c:\\temp\\http.html", O_WRITE)) { f.SetSize(0); f.Write(&Res[0], Res.Length()); f.Close(); } #endif return true; } LAutoString ImapBasicTokenize(char *&s) { if (s) { while (*s && strchr(WhiteSpace, *s)) s++; char start = 0, end = 0; if (*s == '\'' || *s == '\"') start = end = *s; else if (*s == '[') { start = '['; end = ']'; } else if (*s == '(') { start = '('; end = ')'; } else if (*s == '{') { start = '{'; end = '}'; } if (start && end) { s++; char *e = strchr(s, end); if (e) { char *n = NewStr(s, e - s); s = e + 1; return LAutoString(n); } } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; if (e > s) { char *n = NewStr(s, e - s); s = e + (*e != 0); return LAutoString(n); } } } s += strlen(s); return LAutoString(); } char *Tok(char *&s) { char *Ret = 0; while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '=' || *s == ',') { Ret = NewStr(s++, 1); } else if (*s == '\'' || *s == '\"') { char d = *s++; char *e = strchr(s, d); if (e) { Ret = NewStr(s, e - s); s = e + 1; } } else if (*s) { char *e; for (e=s; *e && (IsDigit(*e) || IsAlpha(*e) || *e == '-'); e++); Ret = NewStr(s, e - s); s = e; } return Ret; } char *DecodeImapString(const char *s) { LStringPipe p; while (s && *s) { if (*s == '&') { char Escape = *s++; const char *e = s; while (*e && *e != '-') { e++; } ssize_t Len = e - s; if (Len) { char *Base64 = new char[Len + 4]; if (Base64) { memcpy(Base64, s, Len); char *n = Base64 + Len; for (ssize_t i=Len; i%4; i++) *n++ = '='; *n++ = 0; Len = strlen(Base64); ssize_t BinLen = BufferLen_64ToBin(Len); uint16 *Bin = new uint16[(BinLen/2)+1]; if (Bin) { BinLen = ConvertBase64ToBinary((uchar*)Bin, BinLen, Base64, Len); if (BinLen) { ssize_t Chars = BinLen / 2; Bin[Chars] = 0; for (int i=0; i>8) | ((Bin[i]&0xff)<<8); } char *c8 = WideToUtf8((char16*)Bin, BinLen); if (c8) { p.Push(c8); DeleteArray(c8); } } DeleteArray(Bin); } DeleteArray(Base64); } } else { p.Push(&Escape, 1); } s = e + 1; } else { p.Push(s, 1); s++; } } return p.NewStr(); } char *EncodeImapString(const char *s) { LStringPipe p; ssize_t Len = s ? strlen(s) : 0; while (s && *s) { int c = LgiUtf8To32((uint8*&)s, Len); DoNextChar: if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { // Literal char ch = c; p.Push(&ch, 1); } else { // Encoded LArray Str; Str[0] = c; while ((c = LgiUtf8To32((uint8*&)s, Len))) { if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { break; } else { Str[Str.Length()] = c; } } for (uint32 i=0; i>8) | ((Str[i]&0xff)<<8); } ssize_t BinLen = Str.Length() << 1; ssize_t BaseLen = BufferLen_BinTo64(BinLen); char *Base64 = new char[BaseLen+1]; if (Base64) { ssize_t Bytes = ConvertBinaryToBase64(Base64, BaseLen, (uchar*)&Str[0], BinLen); while (Bytes > 0 && Base64[Bytes-1] == '=') { Base64[Bytes-1] = 0; Bytes--; } Base64[Bytes] = 0; p.Print("&%s-", Base64); DeleteArray(Base64); } goto DoNextChar; } } return p.NewStr(); } void ChopNewLine(char *Str) { char *End = Str+strlen(Str)-1; if (*End == '\n') { *End-- = 0; } if (*End == '\r') { *End-- = 0; } } MailImapFolder::MailImapFolder() { Sep = '/'; Path = 0; NoSelect = false; NoInferiors = false; Marked = false; Exists = -1; Recent = -1; // UnseenIndex = -1; Deleted = 0; } MailImapFolder::~MailImapFolder() { DeleteArray(Path); } void MailImapFolder::operator =(LHashTbl,int> &v) { int o = v.Find("exists"); if (o >= 0) Exists = o; o = v.Find("recent"); if (o >= 0) Recent = o; } char *MailImapFolder::GetPath() { return Path; } void MailImapFolder::SetPath(const char *s) { char *NewPath = DecodeImapString(s); DeleteArray(Path); Path = NewPath; } char *MailImapFolder::GetName() { if (Path) { char *s = strrchr(Path, Sep); if (s) { return s + 1; } else { return Path; } } return 0; } void MailImapFolder::SetName(const char *s) { if (s) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), Path?Path:(char*)""); DeleteArray(Path); char *Last = strrchr(Buf, Sep); if (Last) { Last++; strcpy_s(Last, sizeof(Buf)-(Last-Buf), s); Path = NewStr(Buf); } else { Path = NewStr(s); } } } ///////////////////////////////////////////// class MailIMapPrivate : public LMutex { public: int NextCmd, IdleCmd; bool Logging; bool ExpungeOnExit; bool ReadDebug; char FolderSep; char *Current; char *Flags; LHashTbl,bool> Capability; LString WebLoginUri; LViewI *ParentWnd; LCancel *Cancel; OsThread InCommand; LString LastWrite; MailIMapPrivate() : LMutex("MailImapSem") { ParentWnd = NULL; FolderSep = '/'; NextCmd = 1; IdleCmd = -1; Logging = true; ExpungeOnExit = true; Current = 0; Flags = 0; InCommand = 0; Cancel = NULL; ReadDebug = false; } ~MailIMapPrivate() { DeleteArray(Current); DeleteArray(Flags); } }; MailIMap::MailIMap() { d = new MailIMapPrivate; Buffer[0] = 0; } MailIMap::~MailIMap() { if (Lock(_FL)) { ClearDialog(); ClearUid(); DeleteObj(d); } } bool MailIMap::Lock(const char *file, int line) { if (!d->Lock(file, line)) return false; return true; } bool MailIMap::LockWithTimeout(int Timeout, const char *file, int line) { if (!d->LockWithTimeout(Timeout, file, line)) return false; return true; } void MailIMap::Unlock() { d->Unlock(); d->InCommand = 0; } void MailIMap::SetCancel(LCancel *Cancel) { d->Cancel = Cancel; } void MailIMap::SetParentWindow(LViewI *wnd) { d->ParentWnd = wnd; } const char *MailIMap::GetWebLoginUri() { return d->WebLoginUri; } bool MailIMap::IsOnline() { return Socket ? Socket->IsOpen() : false; } char MailIMap::GetFolderSep() { return d->FolderSep; } char *MailIMap::GetCurrentPath() { return d->Current; } bool MailIMap::GetExpungeOnExit() { return d->ExpungeOnExit; } void MailIMap::SetExpungeOnExit(bool b) { d->ExpungeOnExit = b; } void MailIMap::ClearUid() { if (Lock(_FL)) { Uid.DeleteArrays(); Unlock(); } } void MailIMap::ClearDialog() { if (Lock(_FL)) { Dialog.DeleteArrays(); Unlock(); } } bool MailIMap::WriteBuf(bool ObsurePass, const char *Buffer, bool Continuation) { if (Socket) { if (!Buffer) Buffer = Buf; ssize_t Len = strlen(Buffer); d->LastWrite = Buffer; if (!Continuation && d->InCommand) { LString Msg; Msg.Printf("%s:%i - WriteBuf failed(%s)\n", LGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get()); Socket->OnInformation(Msg); LAssert(!"Can't be issuing new commands while others are still running."); return false; } /* else { LString Msg; Msg.Printf("%s:%i - WriteBuf ok(%s)\n", LGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get()); Socket->OnInformation(Msg); } */ if (Socket->Write((void*)Buffer, Len, 0) == Len) { if (ObsurePass) { char *Sp = (char*)strrchr(Buffer, ' '); if (Sp) { Sp++; LString s; s.Printf("%.*s********\r\n", Sp - Buffer, Buffer); Log(s.Get(), LSocketI::SocketMsgSend); } } else Log(Buffer, LSocketI::SocketMsgSend); d->InCommand = LGetCurrentThread(); return true; } // else Log("Failed to write data to socket.", LSocketI::SocketMsgError); } else Log("Not connected.", LSocketI::SocketMsgError); return false; } bool MailIMap::Read(LStreamI *Out, int Timeout) { int Lines = 0; while (!Lines && Socket) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer)); if (Timeout > 0 && Socket->IsOpen() && r <= 0) { auto St = LCurrentTime(); auto Rd = Socket->IsReadable(Timeout); auto End = LCurrentTime(); if (Rd) { r = Socket->Read(Buffer, sizeof(Buffer)); if (r < 0) { Socket->Close(); LgiTrace("%s:%i - Wut? IsReadable/Read mismatch.\n", _FL); return false; } #if 0 else if (d->ReadDebug) { LgiTrace("%s:%i - Idle Read '%.*s'\n", _FL, (int)r, Buffer); } #endif } else { if (End - St < Timeout - 20) LgiTrace("%s:%i - IsReadable broken (again)\n", _FL); return false; } } if (r > 0) { ReadBuf.Push(Buffer, r); while (ReadBuf.Pop(Buffer, sizeof(Buffer))) { // Trim trailing whitespace char *e = Buffer + strlen(Buffer) - 1; while (e > Buffer && strchr(WhiteSpace, *e)) *e-- = 0; Lines++; if (Out) { Out->Write(Buffer, strlen(Buffer)); Out->Write((char*)"\r\n", 2); } else { Dialog.Add(NewStr(Buffer)); } } } else break; } return Lines > 0; } bool MailIMap::IsResponse(const char *Buf, int Cmd, bool &Ok) { char Num[8]; int Ch = sprintf_s(Num, sizeof(Num), "A%4.4i ", Cmd); if (!Buf || _strnicmp(Buf, Num, Ch) != 0) return false; Ok = _strnicmp(Buf+Ch, "OK", 2) == 0; if (!Ok) SetError(L_ERROR_GENERIC, "Error: %s", Buf+Ch); return true; } bool MailIMap::ReadResponse(int Cmd, bool Plus) { bool Done = false; bool Status = false; if (Socket) { ssize_t Pos = Dialog.Length(); while (!Done) { if (Read(NULL)) { for (auto It = Dialog.begin(Pos); !Done && It != Dialog.end(); It++) { auto Dlg = *It; Pos++; if (Cmd < 0 || (Plus && *Dlg == '+')) { Status = Done = true; } if (IsResponse(Dlg, Cmd, Status)) Done = true; if (d->Logging) { bool Good = strchr("*+", *Dlg) != NULL || Status; Log(Dlg, Good ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError); } } } else { // LgiTrace("%s:%i - 'Read' failed.\n", _FL); break; } } } return Status; } void Hex(char *Out, int OutLen, uchar *In, ssize_t InLen = -1) { if (Out && In) { if (InLen < 0) InLen = strlen((char*)In); for (int i=0; i 0) { Out += ch; OutLen -= ch; } else break; } } } void _unpack(void *ptr, int ptrsize, char *b64) { ConvertBase64ToBinary((uchar*) ptr, ptrsize, b64, strlen(b64)); } bool MailIMap::ReadLine() { ssize_t Len = 0; Buf[0] = 0; do { ssize_t r = Socket->Read(Buf+Len, sizeof(Buf)-Len); if (r < 1) return false; Len += r; } while (!stristr(Buf, "\r\n")); Log(Buf, LSocketI::SocketMsgReceive); return true; } #if HAS_LIBGSASL int GsaslCallback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { return 0; } #endif class OAuthWebServer : public LThread, public LMutex { bool Loop; int Port; LSocket Listen; LAutoString Req; LString Resp; bool Finished; public: OAuthWebServer(int DesiredPort = 0) : LThread("OAuthWebServerThread"), LMutex("OAuthWebServerMutex") { Loop = false; if (Listen.Listen(DesiredPort)) { Port = Listen.GetLocalPort(); Run(); } else Port = 0; Finished = false; } ~OAuthWebServer() { if (Loop) { Loop = false; while (!IsExited()) LSleep(10); } } int GetPort() { return Port; } LString GetRequest(LCancel *Loop, uint64 TimeoutMs = 0) { LString r; uint64 Start = LCurrentTime(); while (!r && (!Loop || !Loop->IsCancelled())) { if (Lock(_FL)) { if (Req) r = Req; Unlock(); } if (TimeoutMs) { uint64 Now = LCurrentTime(); if (Now - Start >= TimeoutMs) break; } if (!r) LSleep(50); } return r; } void SetResponse(const char *r) { if (Lock(_FL)) { Resp = r; Unlock(); } } bool IsFinished() { return Finished; } int Main() { LAutoPtr s; Loop = true; while (Loop) { if (Listen.CanAccept(100)) { s.Reset(new LSocket); if (!Listen.Accept(s)) s.Reset(); else { LArray Mem; ssize_t r; char buf[512]; do { r = s->Read(buf, sizeof(buf)); if (r > 0) { Mem.Add(buf, r); bool End = Strnstr(&Mem[0], "\r\n\r\n", Mem.Length()) != NULL; if (End) break; } } while (r > 0); if (Lock(_FL)) { Mem.Add(0); Req.Reset(Mem.Release()); Unlock(); } // Wait for the response... LString Response; do { if (Lock(_FL)) { if (Resp) Response = Resp; Unlock(); } if (!Response) LSleep(10); } while (Loop && !Response); if (Response) s->Write(Response, Response.Length()); Loop = false; } } else LSleep(10); } Finished = true; return 0; } }; static void AddIfMissing(LArray &Auths, const char *a, LString *DefaultAuthType = NULL) { for (unsigned i=0; iLastWrite.Strip().Get()); Socket->OnInformation(Msg); */ d->InCommand = 0; d->LastWrite.Empty(); } bool MailIMap::Open(LSocketI *s, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *settingStore, int Flags) { bool Status = false; if (settingStore) SettingStore = settingStore; if (SocketLock.Lock(_FL)) { Socket.Reset(s); SocketLock.Unlock(); } if (Socket && ValidStr(RemoteHost) && ValidStr(User) && ( ValidStr(Password) || OAuth2.IsValid() ) && Lock(_FL)) { // prepare address if (Port < 1) { if (Flags & MAIL_SSL) Port = IMAP_SSL_PORT; else Port = IMAP_PORT; } char Remote[256]; strcpy_s(Remote, sizeof(Remote), RemoteHost); char *Colon = strchr(Remote, ':'); if (Colon) { *Colon++ = 0; Port = atoi(Colon); } // Set SSL mode LVariant v; if (Flags == MAIL_SSL) v = "SSL"; Socket->SetValue(LSocket_Protocol, v); // connect if (Socket->Open(Remote, Port)) { bool IMAP4Server = false; LArray Auths; // check capability int CapCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CAPABILITY\r\n", CapCmd); if (WriteBuf()) { bool Rd = ReadResponse(CapCmd); CommandFinished(); if (Rd) { for (auto r: Dialog) { auto T = LString(r).SplitDelimit(" "); if (T.Length() > 1 && _stricmp(T[1], "CAPABILITY") == 0) { for (unsigned i=2; i 0) { Auths.DeleteAt(n); Auths.AddAt(0, DefaultAuthType); break; } } // SSL bool TlsError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { int CapCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STARTTLS\r\n", CapCmd); if (WriteBuf()) { bool Rd = ReadResponse(CapCmd); CommandFinished(); if (Rd) { LVariant v; TlsError = !Socket->SetValue(LSocket_Protocol, v="SSL"); } else { TlsError = true; } } else LAssert(0); if (TlsError) { Log("STARTTLS failed", LSocketI::SocketMsgError); } } // login bool LoggedIn = false; char AuthTypeStr[256] = ""; for (unsigned i=0; i 0) { strconcat(AuthTypeStr, ", "); } strconcat(AuthTypeStr, AuthType); } // Do auth #if HAS_LIBGSASL if (!_stricmp(AuthType, "GSSAPI")) { int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE GSSAPI\r\n", AuthCmd); if (WriteBuf() && ReadLine() && Buf[0] == '+') { // Start GSSAPI Gsasl *ctx = NULL; Gsasl_session *sess = NULL; int rc = gsasl_init(&ctx); if (rc == GSASL_OK) { char *mechs; rc = gsasl_client_mechlist(ctx, &mechs); gsasl_callback_set(ctx, GsaslCallback); rc = gsasl_client_start(ctx, AuthType, &sess); if (rc != GSASL_OK) { Log("gsasl_client_start failed", LSocketI::SocketMsgError); } // gsasl_step(ctx, gsasl_done(ctx); } else Log("gsasl_init failed", LSocketI::SocketMsgError); } else Log("AUTHENTICATE GSSAPI failed", LSocketI::SocketMsgError); } else #endif if (_stricmp(AuthType, "LOGIN") == 0 || _stricmp(AuthType, "OTP") == 0) { // clear text authentication int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGIN %s %s\r\n", AuthCmd, User, Password); if (WriteBuf(true)) { LoggedIn = ReadResponse(AuthCmd); CommandFinished(); } } else if (_stricmp(AuthType, "PLAIN") == 0) { // plain auth type char s[256]; char *e = s; *e++ = 0; strcpy_s(e, sizeof(s)-(e-s), User); e += strlen(e); e++; strcpy_s(e, sizeof(s)-(e-s), Password); e += strlen(e); *e++ = '\r'; *e++ = '\n'; ssize_t Len = e - s - 2; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE PLAIN\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { ssize_t b = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)s, Len); strcpy_s(Buf+b, sizeof(Buf)-b, "\r\n"); if (WriteBuf(false, NULL, true)) { bool Rd = ReadResponse(AuthCmd); CommandFinished(); if (Rd) { LoggedIn = true; } else { // Look for WEBALERT from Google for (auto s: Dialog) { char *start = strchr(s, '['); char *end = start ? strrchr(start, ']') : NULL; if (start && end) { start++; if (_strnicmp(start, "WEBALERT", 8) == 0) { start += 8; while (*start && strchr(WhiteSpace, *start)) start++; d->WebLoginUri.Set(start, end - start); } } } } } } } } #if (GPL_COMPATIBLE || defined(_LIBNTLM_H)) && defined(WINNATIVE) else if (_stricmp(AuthType, "NTLM") == 0) { // NT Lan Man authentication OSVERSIONINFO ver; ZeroObj(ver); ver.dwOSVersionInfoSize = sizeof(ver); if (!GetVersionEx(&ver)) { DWORD err = GetLastError(); Log("Couldn't get OS version", LSocketI::SocketMsgError); } else { // Username is in the format: User[@Domain] char UserDom[256]; strcpy_s(UserDom, sizeof(UserDom), User); char *Domain = strchr(UserDom, '@'); if (Domain) *Domain++ = 0; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE NTLM\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { tSmbNtlmAuthNegotiate negotiate; tSmbNtlmAuthChallenge challenge; tSmbNtlmAuthResponse response; buildSmbNtlmAuthNegotiate(&negotiate, 0, 0); if (NTLM_VER(&negotiate) == 2) { negotiate.v2.version.major = (uint8) ver.dwMajorVersion; negotiate.v2.version.minor = (uint8) ver.dwMinorVersion; negotiate.v2.version.buildNumber = (uint16) ver.dwBuildNumber; negotiate.v2.version.ntlmRevisionCurrent = 0x0f; } ZeroObj(Buf); auto negotiateLen = SmbLength(&negotiate); auto c = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)&negotiate, negotiateLen); strcpy_s(Buf+c, sizeof(Buf)-c, "\r\n"); WriteBuf(false, NULL, true); /* read challange data from server, convert from base64 */ Buf[0] = 0; ClearDialog(); if (ReadResponse()) { /* buffer should contain the string "+ [base 64 data]" */ #if 1 ZeroObj(challenge); char *Line = Dialog[0]; LAssert(Line != NULL); ChopNewLine(Line); auto LineLen = strlen(Line); auto challengeLen = sizeof(challenge); c = ConvertBase64ToBinary((uchar*) &challenge, sizeof(challenge), Line+2, LineLen-2); if (NTLM_VER(&challenge) == 2) challenge.v2.bufIndex = (uint32)(c - (challenge.v2.buffer-(uint8*)&challenge)); else challenge.v1.bufIndex = (uint32)(c - (challenge.v1.buffer-(uint8*)&challenge)); #endif /* prepare response, convert to base64, send to server */ ZeroObj(response); FILETIME time = {0, 0}; SYSTEMTIME stNow; GetSystemTime(&stNow); SystemTimeToFileTime(&stNow, &time); char HostName[256] = ""; gethostname(HostName, sizeof(HostName)); buildSmbNtlmAuthResponse(&challenge, &response, UserDom, HostName, Domain, Password, (uint8*)&time); if (NTLM_VER(&response) == 2) { response.v2.version.major = (uint8) ver.dwMajorVersion; response.v2.version.minor = (uint8) ver.dwMinorVersion; response.v2.version.buildNumber = (uint16) ver.dwBuildNumber; response.v2.version.ntlmRevisionCurrent = 0x0f; } #if 0 { uint8 *r1 = (uint8*)&response; uint8 *r2 = (uint8*)&response_good; for (int i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE DIGEST-MD5\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd)) { char *TestCnonce = 0; #if 0 // Test case strcpy(Buf, "+ cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hhcnNldD11dGYtOA=="); RemoteHost = "elwood.innosoft.com"; User = "chris"; Password = "secret"; TestCnonce = "OA6MHXh6VqTrRk"; #endif char *In = (char*)Buf; if (In[0] == '+' && In[1] == ' ') { In += 2; uchar Out[2048]; ssize_t b = ConvertBase64ToBinary(Out, sizeof(Out), In, strlen(In)); Out[b] = 0; LHashTbl, char*> Map; char *s = (char*)Out; while (s && *s) { char *Var = Tok(s); char *Eq = Tok(s); char *Val = Tok(s); char *Comma = Tok(s); if (Var && Eq && Val && strcmp(Eq, "=") == 0) { Map.Add(Var, Val); Val = 0; } DeleteArray(Var); DeleteArray(Eq); DeleteArray(Val); DeleteArray(Comma); } int32 CnonceI[2] = { (int32)LRand(), (int32)LRand() }; char Cnonce[32]; if (TestCnonce) strcpy_s(Cnonce, sizeof(Cnonce), TestCnonce); else Cnonce[ConvertBinaryToBase64(Cnonce, sizeof(Cnonce), (uchar*)&CnonceI, sizeof(CnonceI))] = 0; s = strchr(Cnonce, '='); if (s) *s = 0; int Nc = 1; char *Realm = Map.Find("realm"); char DigestUri[256]; sprintf_s(DigestUri, sizeof(DigestUri), "imap/%s", Realm ? Realm : RemoteHost); LStringPipe p; p.Print("username=\"%s\"", User); p.Print(",nc=%08.8i", Nc); p.Print(",digest-uri=\"%s\"", DigestUri); p.Print(",cnonce=\"%s\"", Cnonce); char *Nonce = Map.Find("nonce"); if (Nonce) { p.Print(",nonce=\"%s\"", Nonce); } if (Realm) { p.Print(",realm=\"%s\"", Realm); } char *Charset = Map.Find("charset"); if (Charset) { p.Print(",charset=%s", Charset); } char *Qop = Map.Find("qop"); if (Qop) { p.Print(",qop=%s", Qop); } // Calculate A1 char a1[256]; uchar md5[16]; sprintf_s(Buf, sizeof(Buf), "%s:%s:%s", User, Realm ? Realm : (char*)"", Password); MDStringToDigest((uchar*)a1, Buf); char *Authzid = Map.Find("authzid"); int ch = 16; if (Authzid) ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s:%s", Nonce, Cnonce, Authzid); else ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s", Nonce, Cnonce); MDStringToDigest(md5, a1, ch); char a1hex[256]; Hex(a1hex, sizeof(a1hex), (uchar*)md5, sizeof(md5)); // Calculate char a2[256]; if (Qop && (_stricmp(Qop, "auth-int") == 0 || _stricmp(Qop, "auth-conf") == 0)) sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s:00000000000000000000000000000000", DigestUri); else sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s", DigestUri); MDStringToDigest(md5, a2); char a2hex[256]; Hex(a2hex, sizeof(a2hex), (uchar*)md5, sizeof(md5)); // Calculate the final response sprintf_s(Buf, sizeof(Buf), "%s:%s:%8.8i:%s:%s:%s", a1hex, Nonce, Nc, Cnonce, Qop, a2hex); MDStringToDigest(md5, Buf); Hex(Buf, sizeof(Buf), (uchar*)md5, sizeof(md5)); p.Print(",response=%s", Buf); if ((s = p.NewStr())) { ssize_t Chars = ConvertBinaryToBase64(Buf, sizeof(Buf) - 4, (uchar*)s, strlen(s)); LAssert(Chars < sizeof(Buf)); strcpy_s(Buf+Chars, sizeof(Buf)-Chars, "\r\n"); if (WriteBuf(false, NULL, true) && Read()) { for (auto Dlg: Dialog) { if (Dlg[0] == '+' && Dlg[1] == ' ') { Log(Dlg, LSocketI::SocketMsgReceive); strcpy_s(Buf, sizeof(Buf), "\r\n"); if (WriteBuf(false, NULL, true)) { LoggedIn = ReadResponse(AuthCmd); } } else { Log(Dlg, LSocketI::SocketMsgError); break; } } } DeleteArray(s); } } } CommandFinished(); } } else if (!_stricmp(AuthType, "XOAUTH2")) { if (stristr(RemoteHost, "office365.com")) { Log("office365.com doesn't support OAUTH2:", LSocketI::SocketMsgInfo); Log("\thttps://stackoverflow.com/questions/29747477/imap-auth-in-office-365-using-oauth2", LSocketI::SocketMsgInfo); Log("\tSo why does it report support in the CAPABILITY response? Don't ask me - fret", LSocketI::SocketMsgInfo); continue; } else if (!OAuth2.IsValid()) { sprintf_s(Buf, sizeof(Buf), "Error: Unknown OAUTH2 server '%s' (ask fret@memecode.com to add)", RemoteHost); Log(Buf, LSocketI::SocketMsgError); continue; } TraceLog TLog; LOAuth2 Auth(OAuth2, User, SettingStore, Socket->GetCancel(), &TLog); auto AccessToken = Auth.GetAccessToken(); if (!AccessToken) { sprintf_s(Buf, sizeof(Buf), "Warning: No OAUTH2 Access Token."); #if DEBUG_OAUTH2 LgiTrace("%s:%i - %s.\n", _FL, Buf); #endif Log(Buf, LSocketI::SocketMsgWarning); break; } // Construct the XOAUTH2 parameter LString s; s.Printf("user=%s\001auth=Bearer %s\001\001", User, AccessToken.Get()); #if DEBUG_OAUTH2 LgiTrace("%s:%i - s=%s.\n", _FL, s.Replace("\001", "%01").Get()); #endif Base64Str(s); // Issue the IMAP command int AuthCmd = d->NextCmd++; LString AuthStr; AuthStr.Printf("A%4.4i AUTHENTICATE XOAUTH2 %s\r\n", AuthCmd, s.Get()); if (WriteBuf(false, AuthStr)) { Dialog.DeleteArrays(); if (Read(NULL)) { for (auto l: Dialog) { if (*l == '+') { l++; while (*l && strchr(WhiteSpace, *l)) l++; s = l; UnBase64Str(s); Log(s.Strip(), LSocketI::SocketMsgError); LJson t(s); auto StatusCode = t.Get("status").Int(); LgiTrace("%s:%i - HTTP status: %" PRIi64 "\n%s\n", _FL, StatusCode, s.Get()); sprintf_s(Buf, sizeof(Buf), "\r\n"); WriteBuf(false, NULL, true); if (StatusCode == 400) { // Refresh the token...? if (Auth.Refresh()) { CommandFinished(); // We need to restart the connection to use the refreshed token // Seems we can't just re-try the authentication command. return false; } } } else if (*l == '*') { Log(l, LSocketI::SocketMsgReceive); } else { if (IsResponse(l, AuthCmd, LoggedIn) && LoggedIn) { Log(l, LSocketI::SocketMsgReceive); if (SettingStore) { // Login successful, so persist the AuthCode for next time LVariant v = AccessToken.Get(); bool b = SettingStore->SetValue(OPT_ImapOAuth2AccessToken, v); if (!b) { Log("Couldn't store access token.", LSocketI::SocketMsgWarning); } } break; } else { Log(l, LSocketI::SocketMsgError); } } } } CommandFinished(); } } else { char s[256]; sprintf_s(s, sizeof(s), "Warning: Unsupported authentication type '%s'", AuthType); Log(s, LSocketI::SocketMsgWarning); } } if (LoggedIn) { Status = true; // Ask server for it's heirarchy (folder) separator. int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { for (auto Dlg: Dialog) { LArray t; char *s = Dlg; while (*s) { LAutoString a = ImapBasicTokenize(s); if (a) t.New() = a; else break; } if (t.Length() >= 5 && strcmp(t[0], "*") == 0 && _stricmp(t[1], "list") == 0) { for (unsigned i=2; iFolderSep = *s; break; } } break; } } } CommandFinished(); } } else { SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", ValidStr(AuthTypeStr) ? AuthTypeStr : "(none)"); } } } Unlock(); } return Status; } bool MailIMap::Close() { bool Status = false; if (Socket && Lock(_FL)) { if (d->ExpungeOnExit) { ExpungeFolder(); } int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGOUT\r\n", Cmd); if (WriteBuf()) Status = true; CommandFinished(); Socket->Close(); Unlock(); } return Status; } bool MailIMap::GetCapabilities(LArray &s) { // char *k = 0; // for (bool p=d->Capability.First(&k); p; p=d->Capability.Next(&k)) for (auto i : d->Capability) { s.Add(i.key); } return s.Length() > 0; } bool MailIMap::ServerOption(char *Opt) { return d->Capability.Find(Opt) != 0; } char *MailIMap::GetSelectedFolder() { return d->Current; } bool MailIMap::SelectFolder(const char *Path, StrMap *Values) { bool Status = false; if (!Path) { LAssert(!"Path is missing."); return false; } if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i SELECT \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { DeleteArray(d->Current); ClearDialog(); if (ReadResponse(Cmd)) { Uid.DeleteArrays(); if (Values) { for (LString Dlg: Dialog) { LString::Array t = Dlg.SplitDelimit(" []"); if (t.Length() > 0 && t[0].Equals("*")) { for (unsigned i=1; iAdd(t[i], t[i-1]); } else if (t[i].Equals("unseen")) { //char *val = t[i+1]; if (t[i+1].IsNumeric()) Values->Add(t[i], t[i+1]); } else if (t[i].Equals("flags")) { ssize_t s = Dlg.Find("("); ssize_t e = Dlg.Find(")", s + 1); if (e >= 0) { LString Val = Dlg(s+1, e); Values->Add(t[i], Val); } } } } } } Status = true; d->Current = NewStr(Path); ClearDialog(); } CommandFinished(); } Unlock(); } return Status; } int MailIMap::GetMessages(const char *Path) { int Status = 0; if (!Path) { LAssert(!"No path."); return 0; } if (Socket && Lock(_FL)) { StrMap f; if (SelectFolder(Path, &f)) { LString Exists = f.Find("exists"); if (Exists && Exists.Int() >= 0) Status = (int)Exists.Int(); else LgiTrace("%s:%i - Failed to get 'exists' value.\n", _FL); } Unlock(); } return Status; } ssize_t MailIMap::GetMessages() { return GetMessages("INBOX"); } char *MailIMap::SequenceToString(LArray *Seq) { if (!Seq) return NewStr("1:*"); LStringPipe p; // int Last = 0; for (unsigned s=0; sLength(); ) { unsigned e = s; while (eLength()-1 && (*Seq)[e] == (*Seq)[e+1]-1) e++; if (s) p.Print(","); if (e == s) p.Print("%i", (*Seq)[s]); else p.Print("%i:%i", (*Seq)[s], (*Seq)[e]); s = e + 1; } return p.NewStr(); } static void RemoveBytes(LArray &a, ssize_t &Used, ssize_t Bytes) { if (Used >= Bytes) { ssize_t Remain = Used - Bytes; if (Remain > 0) memmove(&a[0], &a[Bytes], Remain); Used -= Bytes; } else LAssert(0); } static bool PopLine(LArray &a, ssize_t &Used, LAutoString &Line) { for (ssize_t i=0; iSet(EINVAL); Error->AddNote(_FL, "Invalid arg: %p,%p,%p.", Parts, Callback, Seq); return false; } if (!Lock(_FL)) { LgiTrace("%s:%i - Failed to get lock.\n", _FL); Error->Set(ENOLCK); Error->AddNote(_FL, "Failed to get lock."); return false; } int Status = 0; int Cmd = d->NextCmd++; LStringPipe p(256); p.Print("A%4.4i %sFETCH ", Cmd, ByUid ? "UID " : ""); p.Write(Seq, strlen(Seq)); p.Print(" (%s)\r\n", Parts); LAutoString WrBuf(p.NewStr()); if (!WriteBuf(false, WrBuf)) { Error->Set(EIO); Error->AddNote(_FL, "Write failed."); } else { ClearDialog(); LArray Buf; Buf.Length(1024 + (SizeHint>0?(uint32)SizeHint:0)); ssize_t Used = 0; ssize_t MsgSize; // int64 Start = LCurrentTime(); int64 Bytes = 0; bool Done = false; // uint64 TotalTs = 0; bool Blocking = Socket->IsBlocking(); Socket->IsBlocking(false); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Starting loop\n", _FL); #endif uint64 LastActivity = LCurrentTime(); bool Debug = false; while (!Done) { ssize_t r; if (!Socket->IsOpen()) { Error->Set(ENOTSOCK); Error->AddNote(_FL, "Socket closed."); break; } // We don't wait for 'readable' with select here because // a) The socket is in non-blocking mode and // b) For OpenSSL connections 'readable' on the socket != can get bytes from 'read'. // Just try the read first and see if it gives you bytes, if not then 'select' on the socket. while (true) { // Extend the buffer if getting used up if (Buf.Length()-Used <= 256) { Buf.Length(Buf.Length() + (64<<10)); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Ext buf: %i\n", _FL, Buf.Length()); #endif } // Try and read bytes from server. r = Socket->Read(Buf.AddressOf(Used), Buf.Length()-Used-1); // -1 for NULL terminator #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: r=%i, used=%i, buf=%i\n", _FL, r, Used, Buf.Length()); #endif if (r > 0) { if (RawCopy) RawCopy->Write(&Buf[Used], r); Used += r; Bytes += r; LastActivity = LCurrentTime(); } else { LSleep(1); // Don't eat the whole CPU... break; } if (Debug) LgiTrace("%s:%i - Recv=%i\n", _FL, r); } // See if we can parse out a single response LArray Ranges; LAssert(Used < (ssize_t)Buf.Length()); Buf[Used] = 0; // NULL terminate before we parse while (true) { MsgSize = ParseImapResponse(&Buf[0], Used, Ranges, 2); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: MsgSize=%i\n", _FL, MsgSize); #endif if (Debug) LgiTrace("%s:%i - ParseImapResponse=%i\n", _FL, MsgSize); if (!MsgSize) break; if (!Debug) LastActivity = LCurrentTime(); char *b = &Buf[0]; if (MsgSize > Used) { // This is an error... ParseImapResponse should always return <= Used. // If this triggers, ParseImapResponse is skipping a NULL that it shouldn't. #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Wrong size %i, %i\n", _FL, MsgSize, Used); #endif Ranges.Length(0); LAssert(0); #if _DEBUG ParseImapResponse(&Buf[0], Used, Ranges, 2); #endif Done = true; Error->Set(ENODATA); Error->AddNote(_FL, "Wrong data size."); break; } LAssert(Ranges.Length() >= 2); // Setup strings for callback char *Param = b + Ranges[0].Start; Param[Ranges[0].Len()] = 0; char *Name = b + Ranges[1].Start; Name[Ranges[1].Len()] = 0; if (Stricmp(Name, "FETCH")) { // Not the response we're looking for. #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Wrong response: %s\n", _FL, Name); #endif } else if (Stricmp(Param, "BYE")) { // Process ranges into a hash table StrMap Parts; for (unsigned i=2; iSet(ECANCELED); Error->AddNote(_FL, "Callback failed."); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Callback return FALSE?\n", _FL); #endif } } // Remove this msg from buffer RemoveBytes(Buf, Used, MsgSize); Buf[Used] = 0; // 'Used' changed... so NULL terminate before we parse } // Look for the end marker #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: End, Used=%i, Buf=%.12s\n", _FL, Used, Buf.AddressOf()); #endif if (Used > 0 && Buf[0] != '*') { LAutoString Line; while (PopLine(Buf, Used, Line)) { #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Line='%s'\n", _FL, Line.Get()); #endif auto t = LString(Line).SplitDelimit(" \r\n"); if (t.Length() >= 2) { char *r = t[0]; if (*r == 'A') { bool IsOk = !_stricmp(t[1], "Ok"); int Response = atoi(r + 1); Log(Line, IsOk ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError); if (Response == Cmd) { Done = true; break; } } else { Error->Set(EPROTO); Error->AddNote(_FL, "ImapErr: %s", &Buf[0]); Log(&Buf[0], LSocketI::SocketMsgError); } } else { // This is normal behaviour... just don't have the end marker yet. Done = true; break; } } } } Socket->IsBlocking(Blocking); CommandFinished(); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch finished, status=%i\n", _FL, Status); #endif } Unlock(); if (!Status && !Error->GetCode()) { Error->Set(ENODATA); Error->AddNote(_FL, "No records received."); } return Status; } bool IMapHeadersCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData) { char *s = Parts.Find(sRfc822Header); if (s) { Parts.Delete(sRfc822Header); LString *Hdrs = (LString*)UserData; *Hdrs = s; } return true; } LString MailIMap::GetHeaders(int Message) { LString Text; if (!Lock(_FL)) { char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", Message + 1); Fetch( false, Seq, sRfc822Header, IMapHeadersCallback, &Text, NULL); Unlock(); } return Text; } struct ReceiveCallbackState { MailTransaction *Trans; MailCallbacks *Callbacks; }; static bool IMapReceiveCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData) { ReceiveCallbackState *State = (ReceiveCallbackState*) UserData; char *Flags = Parts.Find("FLAGS"); if (Flags) { State->Trans->Imap.Set(Flags); } char *Hdrs = Parts.Find(sRfc822Header); if (Hdrs) { ssize_t Len = strlen(Hdrs); State->Trans->Stream->Write(Hdrs, Len); } char *Body = Parts.Find("BODY[TEXT]"); if (Body) { ssize_t Len = strlen(Body); State->Trans->Stream->Write(Body, Len); } State->Trans->Status = Hdrs != NULL || Body != NULL; if (Imap->Items) Imap->Items->Value++; Parts.Empty(); if (State->Callbacks) { bool Ret = State->Callbacks->OnReceive(State->Trans, State->Callbacks->CallbackData); if (!Ret) return false; } return true; } bool MailIMap::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Lock(_FL)) { int Errors = 0; ReceiveCallbackState State; State.Callbacks = Callbacks; for (unsigned i=0; iStatus = false; char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", State.Trans->Index + 1); Fetch ( false, Seq, "FLAGS RFC822.HEADER BODY[TEXT]", IMapReceiveCallback, &State, NULL ); if (State.Trans->Status) { Status = true; } else if (Errors++ > 5) { // Yeah... not feelin' it Status = false; break; } } Unlock(); } return Status; } bool MailIMap::Append(const char *Folder, ImapMailFlags *Flags, const char *Msg, LString &NewUid) { bool Status = false; if (Folder && Msg && Lock(_FL)) { LString Flag; if (Flags) Flag = Flags->Get(); LAutoString Path(EncodePath(Folder)); int Cmd = d->NextCmd++; int Len = 0; for (const char *m = Msg; *m; m++) { if (*m == '\n') { Len += 2; } else if (*m != '\r') { Len++; } } // Append on the end of the mailbox int c = sprintf_s(Buf, sizeof(Buf), "A%4.4i APPEND \"%s\"", Cmd, Path.Get()); if (Flag) c += sprintf_s(Buf+c, sizeof(Buf)-c, " (%s)", Flag.Get()); c += sprintf_s(Buf+c, sizeof(Buf)-c, " {%i}\r\n", Len); if (WriteBuf()) { if (Read()) { bool GotPlus = false; for (auto Dlg: Dialog) { if (Dlg[0] == '+') { Dialog.Delete(Dlg); DeleteArray(Dlg); GotPlus = true; break; } } if (GotPlus) { ssize_t Wrote = 0; for (const char *m = Msg; *m; ) { while (*m == '\r' || *m == '\n') { if (*m == '\n') { Wrote += Socket->Write((char*)"\r\n", 2); } m++; } const char *e = m; while (*e && *e != '\r' && *e != '\n') e++; if (e > m) { Wrote += Socket->Write(m, e-m); m = e; } else break; } LAssert(Wrote == Len); Wrote += Socket->Write((char*)"\r\n", 2); // Read response.. ClearDialog(); if ((Status = ReadResponse(Cmd))) { char Tmp[16]; sprintf_s(Tmp, sizeof(Tmp), "A%4.4i", Cmd); for (auto Line: Dialog) { LAutoString c = ImapBasicTokenize(Line); if (!c) break; if (!strcmp(Tmp, c)) { LAutoString a; while ((a = ImapBasicTokenize(Line)).Get()) { auto t = LString(a).SplitDelimit(" "); if (t.Length() > 2 && !_stricmp(t[0], "APPENDUID")) { NewUid = t[2]; break; } } } } } } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Delete(int Message) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STORE %i FLAGS (\\deleted)\r\n", Cmd, Message+1); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Delete(bool ByUid, const char *Seq) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSTORE %s FLAGS (\\deleted)\r\n", Cmd, ByUid?"UID ":"", Seq); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } int MailIMap::Sizeof(int Message) { int Status = 0; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i FETCH %i (%s)\r\n", Cmd, Message+1, sRfc822Size); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char *d = Dialog[0]; if (d) { char *t = strstr(d, sRfc822Size); if (t) { t += strlen(sRfc822Size) + 1; Status = atoi(t); } } } CommandFinished(); } Unlock(); } return Status; } bool ImapSizeCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData) { LArray *Sizes = (LArray*) UserData; char *Sz = Parts.Find(sRfc822Size); if (!Sz) return false; (*Sizes)[Msg - 1] = atoi(Sz); return true; } bool MailIMap::GetSizes(LArray &Sizes) { return Fetch(false, "1:*", sRfc822Size, ImapSizeCallback, &Sizes) != 0; } bool MailIMap::GetUid(int Message, char *Id, int IdLen) { bool Status = false; if (Lock(_FL)) { if (FillUidList()) { char *s = Uid.ItemAt(Message); if (s && Id) { strcpy_s(Id, IdLen, s); Status = true; } } Unlock(); } return Status; } bool MailIMap::FillUidList() { bool Status = false; if (Socket && Lock(_FL)) { if (Uid.Length() == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i UID SEARCH ALL\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (auto d: Dialog) { auto T = LString(d).SplitDelimit(" "); if (T[1] && strcmp(T[1], "SEARCH") == 0) { for (unsigned i=2; i &Folders) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"*\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char Sep[] = { GetFolderSep(), 0 }; for (auto d: Dialog) { LArray t; char *s; while ((s = LTokStr((const char*&)d))) { t[t.Length()].Reset(s); } if (t.Length() >= 5) { if (strcmp(t[0], "*") == 0 && _stricmp(t[1], "LIST") == 0) { char *Folder = t[t.Length()-1]; MailImapFolder *f = new MailImapFolder(); if (f) { Folders.Add(f); f->Sep = Sep[0]; // Check flags f->NoSelect = stristr(t[2], "NoSelect") != 0; f->NoInferiors = stristr(t[2], "NoInferiors") != 0; // LgiTrace("Imap folder '%s' %s\n", Folder, t[2].Get()); // Alloc name if (Folder[0] == '\"') { char *p = TrimStr(Folder, "\""); f->Path = DecodeImapString(p); DeleteArray(p); } else { f->Path = DecodeImapString(Folder); } } } } } Status = true; ClearDialog(); } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::CreateFolder(MailImapFolder *f) { bool Status = false; // char Dir[2] = { d->FolderSep, 0 }; if (f && f->GetPath() && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(f->GetPath()); sprintf_s(Buf, sizeof(Buf), "A%4.4i CREATE \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); if (Status) { char *End = f->Path + strlen(f->Path) - 1; if (*End == GetFolderSep()) { f->NoSelect = true; *End = 0; } else { f->NoInferiors = true; } } CommandFinished(); } Unlock(); } return Status; } char *MailIMap::EncodePath(const char *Path) { if (!Path) return 0; char Sep = GetFolderSep(); char Native[MAX_PATH_LEN], *o = Native, *e = Native + sizeof(Native) - 1; for (const char *i = Path[0] == '/' && Path[1] ? Path + 1 : Path; *i && o < e; i++) { if (*i == '/') *o++ = Sep; else *o++ = *i; } *o++ = 0; return EncodeImapString(Native); } bool MailIMap::DeleteFolder(const char *Path) { bool Status = false; if (Path && Lock(_FL)) { // Close the current folder if required. if (d->Current && _stricmp(Path, d->Current) == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CLOSE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); ReadResponse(Cmd); DeleteArray(d->Current); CommandFinished(); } } // Delete the folder int Cmd = d->NextCmd++; char *NativePath = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i DELETE \"%s\"\r\n", Cmd, NativePath); DeleteArray(NativePath); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::RenameFolder(const char *From, const char *To) { bool Status = false; if (From && To && Lock(_FL)) { int Cmd = d->NextCmd++; LAutoString f(EncodePath(From)); LAutoString t(EncodePath(To)); sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd, f.Get(), t.Get()); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::SetFolderFlags(MailImapFolder *f) { bool Status = false; if (f && Lock(_FL)) { /* int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } */ Unlock(); } return Status; } bool MailIMap::SetFlagsByUid(LArray &Uids, const char *Flags) { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; LStringPipe p; p.Print("A%04.4i UID STORE ", Cmd); if (Uids.Length()) { for (unsigned i=0; i &InUids, const char *DestFolder) { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; LAutoString Dest(EncodePath(DestFolder)); LStringPipe p(1024); p.Print("A%04.4i UID COPY ", Cmd); for (unsigned i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i EXPUNGE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Search(bool Uids, LArray &SeqNumbers, const char *Filter) { bool Status = false; if (ValidStr(Filter) && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSEARCH %s\r\n", Cmd, Uids?"UID ":"", Filter); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (auto d: Dialog) { if (*d != '*') continue; d++; LAutoString s(Tok(d)); if (!s || _stricmp(s, "Search")) continue; while (s.Reset(Tok(d))) { SeqNumbers.New() = s.Get(); Status = true; } } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Status(char *Path, int *Recent) { bool Status = false; if (Path && Recent && Lock(_FL)) { LAutoString Dest(EncodePath(Path)); int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STATUS %s (RECENT)\r\n", Cmd, Dest.Get()); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (auto d: Dialog) { if (*d != '*') continue; d++; LAutoString Cmd = ImapBasicTokenize(d); LAutoString Folder = ImapBasicTokenize(d); LAutoString Fields = ImapBasicTokenize(d); if (Cmd && Folder && Fields && !_stricmp(Cmd, "status") && !_stricmp(Folder, Dest)) { char *f = Fields; LAutoString Field = ImapBasicTokenize(f); LAutoString Value = ImapBasicTokenize(f); if (Field && Value && !_stricmp(Field, "recent")) { *Recent = atoi(Value); Status = true; break; } } } } else LgiTrace("%s:%i - STATUS cmd failed.\n", _FL); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Poll(int *Recent, LArray *New) { bool Status = true; if (Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i NOOP\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if ((Status = ReadResponse(Cmd))) { int LocalRecent; if (!Recent) Recent = &LocalRecent; *Recent = 0; for (auto Dlg: Dialog) { if (Recent && stristr(Dlg, " RECENT")) { *Recent = atoi(Dlg + 2); } } if (*Recent && New) { Search(false, *New, "new"); } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::StartIdle() { bool Status = false; if (Lock(_FL)) { d->IdleCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i IDLE\r\n", d->IdleCmd); Status = WriteBuf(); CommandFinished(); Unlock(); } return Status; } bool MailIMap::OnIdle(int Timeout, LString::Array &Resp) { bool Status = false; if (Lock(_FL)) { auto Blk = Socket->IsBlocking(); Socket->IsBlocking(false); d->ReadDebug = true; Read(NULL, Timeout); d->ReadDebug = false; Socket->IsBlocking(Blk); Resp.SetFixedLength(false); char *Dlg; while ((Dlg = Dialog[0])) { Dialog.Delete(Dlg); Log(Dlg, LSocketI::SocketMsgReceive); if (Dlg[0] == '*' && Dlg[1] == ' ') { Resp.New() = Dlg; Status = true; } DeleteArray(Dlg); } Unlock(); } return Status; } bool MailIMap::FinishIdle() { bool Status = false; if (Lock(_FL)) { if (WriteBuf(false, "DONE\r\n")) { Status = ReadResponse(d->IdleCmd); CommandFinished(); d->IdleCmd = -1; } Unlock(); } return Status; } diff --git a/src/common/Net/Net.cpp b/src/common/Net/Net.cpp --- a/src/common/Net/Net.cpp +++ b/src/common/Net/Net.cpp @@ -1,1856 +1,2041 @@ /*hdr ** FILE: INet.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Internet sockets ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #if defined(LINUX) #include #include #include #include #elif defined(MAC) #include #include #include #include #include #endif #include #include "lgi/common/File.h" #include "lgi/common/Net.h" #include "lgi/common/LgiString.h" #include "lgi/common/LgiCommon.h" #include "LgiOsClasses.h" #include "lgi/common/RegKey.h" #define USE_BSD_SOCKETS 1 #define DEBUG_CONNECT 0 #define ETIMEOUT 400 #define PROTO_UDP 0x100 #define PROTO_BROADCAST 0x200 #if defined WIN32 #include #include #include #include typedef HOSTENT HostEnt; typedef int socklen_t; typedef unsigned long in_addr_t; typedef BOOL option_t; #define LPrintSock "%I64x" #define MSG_NOSIGNAL 0 #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif #ifndef EISCONN #define EISCONN WSAEISCONN #endif #define OsAddr S_un.S_addr #elif defined POSIX #include #include #include #include #include #include #include #include #include #include #define SOCKET_ERROR -1 #define LPrintSock "%x" typedef hostent HostEnt; typedef int option_t; #define OsAddr s_addr #endif -#include "lgi/common/LgiNetInc.h" - /////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 static bool SocketsOpen = false; #endif bool StartNetworkStack() { #ifdef WIN32 #ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif if (!SocketsOpen) { // Start sockets WSADATA WsaData; int Result = WSAStartup(WINSOCK_VERSION, &WsaData); if (!Result) { if (WsaData.wVersion == WINSOCK_VERSION) { SocketsOpen = Result == 0; } else { WSACleanup(); return false; } } } #endif return true; } void StopNetworkStack() { #ifdef WIN32 if (SocketsOpen) { WSACleanup(); SocketsOpen = false; } #endif } #ifdef WIN32 #include "..\..\Win\INet\MibAccess.h" #include #pragma comment(lib, "iphlpapi.lib") #endif bool LSocket::EnumInterfaces(LArray &Out) { bool Status = false; StartNetworkStack(); #ifdef WIN32 #if 0 PMIB_IF_TABLE2 Tbl; SecureZeroMemory(&Tbl, sizeof(Tbl)); auto r = GetIfTable2(&Tbl); for (size_t i=0; iNumEntries; i++) { auto &e = Tbl->Table[i]; WCHAR w[256] = {0}; r = ConvertInterfaceLuidToNameW(&e.InterfaceLuid, w, 256); MIB_UNICASTIPADDRESS_ROW Addr; InitializeUnicastIpAddressEntry(&Addr); Addr.Address.si_family = AF_INET; Addr.InterfaceLuid = e.InterfaceLuid; Addr.InterfaceIndex = e.InterfaceIndex; r = GetUnicastIpAddressEntry(&Addr); if (r == NO_ERROR) { int asd=0; } } FreeMibTable(Tbl); #else MibII m; m.Init(); MibInterface Intf[16] = {0}; int Count = m.GetInterfaces(Intf, CountOf(Intf)); if (Count) { for (int i=0; iifa_next) { if (a->ifa_addr && a->ifa_addr->sa_family == AF_INET) { sockaddr_in *in = (sockaddr_in*)a->ifa_addr; sockaddr_in *mask = (sockaddr_in*)a->ifa_netmask; auto &Intf = Out.New(); Intf.Ip4 = ntohl(in->sin_addr.s_addr); Intf.Netmask4 = ntohl(mask->sin_addr.s_addr); Intf.Name = a->ifa_name; Status = true; } } freeifaddrs(addrs); } #endif return Status; } //////////////////////////////////////////////////////////////////////////////////////////////// class LSocketImplPrivate : public LCancel { public: // Data int Blocking : 1; int NoDelay : 1; int Udp : 1; int Broadcast : 1; int LogType = NET_LOG_NONE; LString LogFile; int Timeout = -1; OsSocket Socket = INVALID_SOCKET; int LastError = 0; LCancel *Cancel = NULL; LString ErrStr; LSocketImplPrivate() { Cancel = this; Blocking = true; NoDelay = false; Udp = false; Broadcast = false; } ~LSocketImplPrivate() { } bool Select(int TimeoutMs, bool Read) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Socket; if (ValidSocket(s) && !Cancel->IsCancelled()) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set set; FD_ZERO(&set); FD_SET(s, &set); int ret = select( (int)s+1, Read ? &set : NULL, !Read ? &set : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (ret > 0 && FD_ISSET(s, &set)) { return true; } } return false; } // This is the timing granularity of the SelectWithCancel loop. Ie the // number of milliseconds between checking the Cancel object. constexpr static int CancelCheckMs = 50; bool SelectWithCancel(int TimeoutMs, bool Read) { if (!Cancel) // Regular select where we just wait the whole timeout... return Select(TimeoutMs, false); // Because select can't check out 'Cancel' value during the waiting we run the select // call in a much smaller timeout and a loop so that we can respond to Cancel being set // in a timely manner. auto Now = LCurrentTime(); auto End = Now + TimeoutMs; do { // Do the cancel check... if (Cancel->IsCancelled()) break; // How many ms to wait? Now = LCurrentTime(); auto Remain = MIN(CancelCheckMs, (int)(End - Now)); if (Remain <= 0) break; if (Select(Remain, Read)) return true; } while (Now < End); return false; } }; LSocket::LSocket(LStreamI *logger, void *unused_param) { StartNetworkStack(); BytesWritten = 0; BytesRead = 0; d = new LSocketImplPrivate; } LSocket::~LSocket() { Close(); DeleteObj(d); } bool LSocket::IsOK() { return #ifndef __llvm__ this != 0 && #endif d != 0; } LCancel *LSocket::GetCancel() { return d->Cancel; } void LSocket::SetCancel(LCancel *c) { d->Cancel = c; } void LSocket::OnDisconnect() { } OsSocket LSocket::ReleaseHandle() { auto h = d->Socket; d->Socket = INVALID_SOCKET; return h; } OsSocket LSocket::Handle(OsSocket Set) { if (Set != INVALID_SOCKET) { d->Socket = Set; } return d->Socket; } bool LSocket::IsOpen() { if (ValidSocket(d->Socket) && !d->Cancel->IsCancelled()) { return true; } return false; } int LSocket::GetTimeout() { return d->Timeout; } void LSocket::SetTimeout(int ms) { d->Timeout = ms; } bool LSocket::IsReadable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = d->Socket; if (ValidSocket(s) && !d->Cancel->IsCancelled()) { #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? struct pollfd fds; fds.fd = s; fds.events = POLLIN | POLLRDHUP | POLLERR; fds.revents = 0; int r = poll(&fds, 1, TimeoutMs); if (r > 0) { return fds.revents != 0; } else if (r < 0) { Error(); } #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { return true; } else if (v < 0) { Error(); } #endif } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } bool LSocket::IsWritable(int TimeoutMs) { return d->SelectWithCancel(TimeoutMs, false); } bool LSocket::CanAccept(int TimeoutMs) { return IsReadable(TimeoutMs); } bool LSocket::IsBlocking() { return d->Blocking != 0; } void LSocket::IsBlocking(bool block) { if (d->Blocking ^ block) { d->Blocking = block; #if defined WIN32 ulong NonBlocking = !block; ioctlsocket(d->Socket, FIONBIO, &NonBlocking); #elif defined POSIX fcntl(d->Socket, F_SETFL, d->Blocking ? 0 : O_NONBLOCK); #else #error Impl me. #endif } } bool LSocket::IsDelayed() { return !d->NoDelay; } void LSocket::IsDelayed(bool Delay) { bool NoDelay = !Delay; if (d->NoDelay ^ NoDelay) { d->NoDelay = NoDelay; option_t i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&i, sizeof(i)); } } int LSocket::GetLocalPort() { struct sockaddr_in addr; socklen_t size; ZeroObj(addr); size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) >= 0) { return ntohs(addr.sin_port); } return 0; } bool LSocket::GetLocalIp(char *IpAddr) { if (IpAddr) { struct sockaddr_in addr; socklen_t size; size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) < 0) return false; if (addr.sin_addr.s_addr == INADDR_ANY) return false; uchar *a = (uchar*)&addr.sin_addr.s_addr; sprintf_s( IpAddr, 16, "%i.%i.%i.%i", a[0], a[1], a[2], a[3]); return true; } return false; } bool LSocket::GetRemoteIp(uint32_t *IpAddr) { if (IpAddr) { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { *IpAddr = ntohl(a.sin_addr.s_addr); return true; } } return false; } bool LSocket::GetRemoteIp(char *IpAddr) { if (!IpAddr) return false; uint32_t Ip = 0; if (!GetRemoteIp(&Ip)) return false; sprintf_s( IpAddr, 16, "%u.%u.%u.%u", (Ip >> 24) & 0xff, (Ip >> 16) & 0xff, (Ip >> 8) & 0xff, (Ip) & 0xff); return false; } int LSocket::GetRemotePort() { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { return a.sin_port; } return 0; } int LSocket::Open(const char *HostAddr, int Port) { int Status = -1; Close(); if (HostAddr) { BytesWritten = 0; BytesRead = 0; sockaddr_in RemoteAddr; HostEnt *Host = 0; in_addr_t IpAddress = 0; ZeroObj(RemoteAddr); #ifdef WIN32 d->Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); #else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #endif if (ValidSocket(d->Socket)) { LArray Buf(512); #if !defined(MAC) option_t i; socklen_t sz = sizeof(i); int r = getsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char*)&i, &sz); if (d->NoDelay ^ i) { i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i)); } #endif if (IsDigit(*HostAddr) && strchr(HostAddr, '.')) { // Ip address // IpAddress = inet_addr(HostAddr); if (!inet_pton(AF_INET, HostAddr, &IpAddress)) { Error(); return 0; } /* This seems complete unnecessary? -fret Dec 2018 #if defined(WIN32) Host = c((const char*) &IpAddress, 4, AF_INET); if (!Host) Error(); #else Host = gethostbyaddr ( #ifdef MAC HostAddr, #else &IpAddress, #endif 4, AF_INET ); #endif */ } else { // Name address #ifdef LINUX Host = new HostEnt; if (Host) { memset(Host, 0, sizeof(*Host)); HostEnt *Result = 0; int Err = 0; int Ret; while ( ( !GetCancel() || !GetCancel()->IsCancelled() ) && ( Ret = gethostbyname_r(HostAddr, Host, &Buf[0], Buf.Length(), &Result, &Err) ) == ERANGE ) { Buf.Length(Buf.Length() << 1); } if (Ret) { auto ErrStr = LErrorCodeToString(Err); printf("%s:%i - gethostbyname_r('%s') returned %i, %i, %s\n", _FL, HostAddr, Ret, Err, ErrStr.Get()); DeleteObj(Host); } } #if DEBUG_CONNECT printf("%s:%i - Host=%p\n", __FILE__, __LINE__, Host); #endif #else Host = gethostbyname(HostAddr); #endif if (!Host) { Error(); Close(); return false; } } if (1) { RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons(Port); if (Host) { if (Host->h_addr_list && Host->h_addr_list[0]) { memcpy(&RemoteAddr.sin_addr, Host->h_addr_list[0], sizeof(in_addr) ); } else return false; } else { memcpy(&RemoteAddr.sin_addr, &IpAddress, sizeof(IpAddress) ); } #ifdef WIN32 if (d->Timeout < 0) { // Do blocking connect Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); } else #endif { #define CONNECT_LOGGING 0 // Setup the connect bool Block = IsBlocking(); if (Block) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Setting non blocking\n", d->Socket); #endif IsBlocking(false); } // Do initial connect to kick things off.. #if CONNECT_LOGGING LgiTrace(LPrintSock " - Doing initial connect to %s:%i\n", d->Socket, HostAddr, Port); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Initial connect=%i Block=%i\n", d->Socket, Status, Block); #endif // Wait for the connect to finish? if (Status && Block) { Error(Host); #ifdef WIN32 // yeah I know... wtf? (http://itamarst.org/writings/win32sockets.html) #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == WSAEINVAL || d->LastError == WSAEWOULDBLOCK) #else #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == EINPROGRESS) #endif #if CONNECT_LOGGING LgiTrace(LPrintSock " - IsWouldBlock()=%i d->LastError=%i\n", d->Socket, IsWouldBlock(), d->LastError); #endif int64 End = LCurrentTime() + (d->Timeout > 0 ? d->Timeout : 30000); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket) && IsWouldBlock()) { int64 Remaining = End - LCurrentTime(); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Remaining " LPrintfInt64 "\n", d->Socket, Remaining); #endif if (Remaining < 0) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Leaving loop\n", d->Socket); #endif break; } if (IsWritable((int)MIN(Remaining, 1000))) { // Should be ready to connect now... #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect...\n", d->Socket); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect=%i\n", d->Socket, Status); #endif if (Status != 0) { Error(Host); if (d->LastError == EISCONN #ifdef WIN32 || d->LastError == WSAEISCONN // OMG windows, really? #endif ) { Status = 0; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connect=%i Err=%i\n", d->Socket, Status, d->LastError); #endif if (IsWouldBlock()) continue; } break; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connected...\n", d->Socket); #endif break; } } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Timout...\n", d->Socket); #endif } } } if (Block) IsBlocking(true); } if (!Status) { char Info[256]; sprintf_s(Info, sizeof(Info), "[INET] Socket Connect: %s [%i.%i.%i.%i], port: %i", HostAddr, (RemoteAddr.sin_addr.s_addr) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 8) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 16) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 24) & 0xFF, Port); OnInformation(Info); } } if (Status) { #ifdef WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; } } else { Error(); } } return Status == 0; } bool LSocket::Bind(int Port, bool reuseAddr) { if (!ValidSocket(d->Socket)) { OnError(0, "Attempt to use invalid socket to bind."); return false; } if (reuseAddr) { int so_reuseaddr = 1; if (setsockopt(Handle(), SOL_SOCKET, SO_REUSEADDR, (const char *)&so_reuseaddr, sizeof so_reuseaddr)) OnError(0, "Attempt to set SO_REUSEADDR failed."); // This might not be fatal... so continue on. } sockaddr_in add; add.sin_family = AF_INET; add.sin_addr.s_addr = htonl(INADDR_ANY); add.sin_port = htons(Port); int ret = bind(Handle(), (sockaddr*)&add, sizeof(add)); if (ret) { Error(); } return ret == 0; } bool LSocket::Listen(int Port) { Close(); d->Socket = socket(AF_INET, SOCK_STREAM, 0); if (d->Socket >= 0) { BytesWritten = 0; BytesRead = 0; sockaddr Addr; sockaddr_in *a = (sockaddr_in*) &Addr; ZeroObj(Addr); a->sin_family = AF_INET; a->sin_port = htons(Port); a->sin_addr.OsAddr = INADDR_ANY; if (bind(d->Socket, &Addr, sizeof(Addr)) >= 0) { if (listen(d->Socket, SOMAXCONN) != SOCKET_ERROR) { return true; } else { Error(); } } else { Error(); } } else { Error(); } return false; } bool LSocket::Accept(LSocketI *c) { if (!c) { LAssert(0); return false; } OsSocket NewSocket = INVALID_SOCKET; sockaddr Address; /* int Length = sizeof(Address); NewSocket = accept(d->Socket, &Address, &Length); */ // int Loop = 0; socklen_t Length = sizeof(Address); uint64 Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket)) { if (IsReadable(100)) { NewSocket = accept(d->Socket, &Address, &Length); break; } else if (d->Timeout > 0) { uint64 Now = LCurrentTime(); if (Now - Start >= d->Timeout) { LString s; s.Printf("Accept timeout after %.1f seconds.", ((double)(Now-Start)) / 1000.0); OnInformation(s); return false; } } } if (!ValidSocket(NewSocket)) return false; return ValidSocket(c->Handle(NewSocket)); } int LSocket::Close() { if (ValidSocket(d->Socket)) { #if defined WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; OnDisconnect(); } return true; } void LSocket::Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len) { if (d->LogFile) { LFile f; if (f.Open(d->LogFile, O_WRITE)) { f.Seek(f.GetSize(), SEEK_SET); switch (d->LogType) { case NET_LOG_HEX_DUMP: { char s[256]; f.Write(s, sprintf_s(s, sizeof(s), "%s = %i\r\n", Msg, (int)Ret)); for (int i=0; iSocket) || !Data || d->Cancel->IsCancelled()) return -1; int Status = 0; if (d->Timeout < 0 || IsWritable(d->Timeout)) { Status = (int)send ( d->Socket, (char*)Data, (int) Len, Flags #ifndef MAC | MSG_NOSIGNAL #endif ); } if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesWritten += Status; OnWrite((char*)Data, Status); } return Status; } ssize_t LSocket::Read(void *Data, ssize_t Len, int Flags) { if (!ValidSocket(d->Socket) || !Data || d->Cancel->IsCancelled()) return -1; ssize_t Status = -1; if (d->Timeout < 0 || IsReadable(d->Timeout)) { Status = recv(d->Socket, (char*)Data, (int) Len, Flags #ifdef MSG_NOSIGNAL | MSG_NOSIGNAL #endif ); } Log("Read", (int)Status, (char*)Data, Status>0 ? Status : 0); if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesRead += Status; OnRead((char*)Data, (int)Status); } return (int)Status; } void LSocket::OnError(int ErrorCode, const char *ErrorDescription) { d->ErrStr.Printf("Error(%i): %s", ErrorCode, ErrorDescription); } const char *LSocket::GetErrorString() { return d->ErrStr; } int LSocket::Error(void *Param) { // Get the most recent error. if (!(d->LastError = #ifdef WIN32 WSAGetLastError() #else errno #endif )) return 0; // These are not really errors... if (d->LastError == EWOULDBLOCK || d->LastError == EISCONN) return 0; static class ErrorMsg { public: int Code; const char *Msg; } ErrorCodes[] = { {0, "Socket disconnected."}, #if defined WIN32 {WSAEACCES, "Permission denied."}, {WSAEADDRINUSE, "Address already in use."}, {WSAEADDRNOTAVAIL, "Cannot assign requested address."}, {WSAEAFNOSUPPORT, "Address family not supported by protocol family."}, {WSAEALREADY, "Operation already in progress."}, {WSAECONNABORTED, "Software caused connection abort."}, {WSAECONNREFUSED, "Connection refused."}, {WSAECONNRESET, "Connection reset by peer."}, {WSAEDESTADDRREQ, "Destination address required."}, {WSAEFAULT, "Bad address."}, {WSAEHOSTDOWN, "Host is down."}, {WSAEHOSTUNREACH, "No route to host."}, {WSAEINPROGRESS, "Operation now in progress."}, {WSAEINTR, "Interrupted function call."}, {WSAEINVAL, "Invalid argument."}, {WSAEISCONN, "Socket is already connected."}, {WSAEMFILE, "Too many open files."}, {WSAEMSGSIZE, "Message too long."}, {WSAENETDOWN, "Network is down."}, {WSAENETRESET, "Network dropped connection on reset."}, {WSAENETUNREACH, "Network is unreachable."}, {WSAENOBUFS, "No buffer space available."}, {WSAENOPROTOOPT, "Bad protocol option."}, {WSAENOTCONN, "Socket is not connected."}, {WSAENOTSOCK, "Socket operation on non-socket."}, {WSAEOPNOTSUPP, "Operation not supported."}, {WSAEPFNOSUPPORT, "Protocol family not supported."}, {WSAEPROCLIM, "Too many processes."}, {WSAEPROTONOSUPPORT,"Protocol not supported."}, {WSAEPROTOTYPE, "Protocol wrong type for socket."}, {WSAESHUTDOWN, "Cannot send after socket shutdown."}, {WSAESOCKTNOSUPPORT,"Socket type not supported."}, {WSAETIMEDOUT, "Connection timed out."}, {WSAEWOULDBLOCK, "Operation would block."}, {WSAHOST_NOT_FOUND, "Host not found."}, {WSANOTINITIALISED, "Successful WSAStartup not yet performed."}, {WSANO_DATA, "Valid name, no data record of requested type."}, {WSANO_RECOVERY, "This is a non-recoverable error."}, {WSASYSNOTREADY, "Network subsystem is unavailable."}, {WSATRY_AGAIN, "Non-authoritative host not found."}, {WSAVERNOTSUPPORTED,"WINSOCK.DLL version out of range."}, {WSAEDISCON, "Graceful shutdown in progress."}, #else {EACCES, "Permission denied."}, {EADDRINUSE, "Address already in use."}, {EADDRNOTAVAIL, "Cannot assign requested address."}, {EAFNOSUPPORT, "Address family not supported by protocol family."}, {EALREADY, "Operation already in progress."}, {ECONNABORTED, "Software caused connection abort."}, {ECONNREFUSED, "Connection refused."}, {ECONNRESET, "Connection reset by peer."}, {EFAULT, "Bad address."}, {EHOSTUNREACH, "No route to host."}, {EINPROGRESS, "Operation now in progress."}, {EINTR, "Interrupted function call."}, {EINVAL, "Invalid argument."}, {EISCONN, "Socket is already connected."}, {EMFILE, "Too many open files."}, {EMSGSIZE, "Message too long."}, {ENETDOWN, "Network is down."}, {ENETRESET, "Network dropped connection on reset."}, {ENETUNREACH, "Network is unreachable."}, {ENOBUFS, "No buffer space available."}, {ENOPROTOOPT, "Bad protocol option."}, {ENOTCONN, "Socket is not connected."}, {ENOTSOCK, "Socket operation on non-socket."}, {EOPNOTSUPP, "Operation not supported."}, {EPFNOSUPPORT, "Protocol family not supported."}, {EPROTONOSUPPORT, "Protocol not supported."}, {EPROTOTYPE, "Protocol wrong type for socket."}, {ESHUTDOWN, "Cannot send after socket shutdown."}, {ETIMEDOUT, "Connection timed out."}, {EWOULDBLOCK, "Resource temporarily unavailable."}, {HOST_NOT_FOUND, "Host not found."}, {NO_DATA, "Valid name, no data record of requested type."}, {NO_RECOVERY, "This is a non-recoverable error."}, {TRY_AGAIN, "Non-authoritative host not found."}, {ETIMEOUT, "Operation timed out."}, {EDESTADDRREQ, "Destination address required."}, {EHOSTDOWN, "Host is down."}, #ifndef HAIKU {ESOCKTNOSUPPORT, "Socket type not supported."}, #endif #endif {-1, 0} }; ErrorMsg *Error = ErrorCodes; while (Error->Code >= 0 && Error->Code != d->LastError) { Error++; } if (d->LastError == 10060 && Param) { HostEnt *He = (HostEnt*)Param; char s[256]; sprintf_s(s, sizeof(s), "%s (gethostbyname returned '%s')", Error->Msg, He->h_name); OnError(d->LastError, s); } else #if defined(MAC) if (d->LastError != 36) #endif { OnError(d->LastError, (Error->Code >= 0) ? Error->Msg : ""); } switch (d->LastError) { case 0: #ifdef WIN32 case 183: // I think this is a XP 'disconnect' ??? case WSAECONNABORTED: case WSAECONNRESET: case WSAENETRESET: #else case ECONNABORTED: case ECONNRESET: case ENETRESET: #endif { Close(); break; } } return d->LastError; } bool LSocket::GetUdp() { return d->Udp != 0; } void LSocket::SetUdp(bool isUdp) { if (d->Udp ^ isUdp) { d->Udp = isUdp; if (!ValidSocket(d->Socket)) { if (d->Udp) d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (d->Broadcast) { option_t enabled = d->Broadcast != 0; setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); } } } void LSocket::SetBroadcast(bool isBroadcast) { d->Broadcast = isBroadcast; } bool LSocket::AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface) { if (!MulticastIp) return false; struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = htonl(MulticastIp); // your multicast address mreq.imr_interface.s_addr = htonl(LocalInterface); // your incoming interface IP int r = setsockopt(Handle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*) &mreq, sizeof(mreq)); if (!r) { LgiTrace("AddMulticastMember(%s, %s)\n", LIpToStr(MulticastIp).Get(), LIpToStr(LocalInterface).Get()); return true; } Error(); return false; } bool LSocket::SetMulticastInterface(uint32_t Interface) { if (!Interface) return false; struct sockaddr_in addr; addr.sin_addr.s_addr = Interface; auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (const char*) &addr, sizeof(addr)); if (!r) return true; Error(); return false; } bool LSocket::CreateUdpSocket() { if (!ValidSocket(d->Socket)) { d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (ValidSocket(d->Socket)) { if (d->Broadcast) { option_t enabled = d->Broadcast != 0; auto r = setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); if (r) Error(); } } } return ValidSocket(d->Socket); } int LSocket::ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip, uint16_t *Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; socklen_t AddrSize = sizeof(a); ZeroObj(a); a.sin_family = AF_INET; if (Port) a.sin_port = htons(*Port); #if defined(WINDOWS) a.sin_addr.S_un.S_addr = INADDR_ANY; #else a.sin_addr.s_addr = INADDR_ANY; #endif auto b = recvfrom(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, &AddrSize); if (b > 0) { OnRead((char*)Buffer, (int)b); if (Ip) *Ip = ntohl(a.sin_addr.OsAddr); if (Port) *Port = ntohs(a.sin_port); } return (int)b; } int LSocket::WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; ZeroObj(a); a.sin_family = AF_INET; a.sin_port = htons(Port); a.sin_addr.OsAddr = htonl(Ip); ssize_t b = sendto(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, sizeof(a)); if (b > 0) { OnWrite((char*)Buffer, (int)b); } else { printf("%s:%i - sendto failed with %i.\n", _FL, errno); } return (int)b; } ////////////////////////////////////////////////////////////////////////////// bool HaveNetConnection() { bool Status = false; // Check for dial up connection #if defined WIN32 typedef DWORD (__stdcall *RasEnumConnections_Proc)(LPRASCONN lprasconn, LPDWORD lpcb, LPDWORD lpcConnections); typedef DWORD (__stdcall *RasGetConnectStatus_Proc)(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus); HMODULE hRas = (HMODULE) LoadLibraryA("rasapi32.dll"); if (hRas) { RASCONN Con[10]; DWORD Connections = 0; DWORD Bytes = sizeof(Con); ZeroObj(Con); Con[0].dwSize = sizeof(Con[0]); RasEnumConnections_Proc pRasEnumConnections = (RasEnumConnections_Proc) GetProcAddress(hRas, "RasEnumConnectionsA"); RasGetConnectStatus_Proc pRasGetConnectStatus = (RasGetConnectStatus_Proc) GetProcAddress(hRas, "RasGetConnectStatusA"); if (pRasEnumConnections && pRasGetConnectStatus) { pRasEnumConnections(Con, &Bytes, &Connections); for (unsigned i=0; i s && strchr(" \r\t\n", e[-1])) + e--; + + // Calc the length + size_t Size = e - s; + char *Str = new char[Size+1]; + if (Str) + { + const char *In = s; + char *Out = Str; + + while (In < e) + { + if (*In == '\r') + { + } + else if (*In == 9) + { + *Out++ = ' '; + } + else + { + *Out++ = *In; + } + + In++; + } + + *Out++ = 0; + } + + return Str; +} + +const char *SeekNextLine(const char *s, const char *End) +{ + if (s) + { + for (; *s && *s != '\n' && (!End || s < End); s++); + if (*s == '\n' && (!End || s < End)) s++; + } + + return s; +} + +// Search through headers for a field +char *InetGetHeaderField( // Returns an allocated string or NULL on failure + const char *Headers, // Pointer to all the headers + const char *Field, // The field your after + ssize_t Len) // Maximum len to run, or -1 for NULL terminated +{ + if (Headers && Field) + { + // for all lines + const char *End = Len < 0 ? 0 : Headers + Len; + size_t FldLen = strlen(Field); + + for (const char *s = Headers; + *s && (!End || s < End); + s = SeekNextLine(s, End)) + { + if (*s != 9 && + _strnicmp(s, Field, FldLen) == 0) + { + // found a match + s += FldLen; + if (*s == ':') + { + s++; + while (*s) + { + if (strchr(" \t\r", *s)) + { + s++; + } + else if (*s == '\n') + { + if (strchr(" \r\n\t", s[1])) + s += 2; + else + break; + } + else break; + } + + return InetGetField(s); + } + } + } + } + + return 0; +} + +char *InetGetSubField(const char *s, const char *Field) +{ + char *Status = 0; + + if (s && Field) + { + s = strchr(s, ';'); + if (s) + { + s++; + + size_t FieldLen = strlen(Field); + char White[] = " \t\r\n"; + while (*s) + { + // Skip leading whitespace + while (*s && (strchr(White, *s) || *s == ';')) s++; + + // Parse field name + if (IsAlpha((uint8_t)*s)) + { + const char *f = s; + while (*s && (IsAlpha(*s) || *s == '-')) s++; + bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0); + while (*s && strchr(White, *s)) s++; + if (*s == '=') + { + s++; + while (*s && strchr(White, *s)) s++; + if (*s && strchr("\'\"", *s)) + { + // Quote Delimited Field + char d = *s++; + char *e = strchr((char*)s, d); + if (e) + { + if (HasField) + { + Status = NewStr(s, e-s); + break; + } + + s = e + 1; + } + else break; + } + else + { + // Space Delimited Field + const char *e = s; + while (*e && !strchr(White, *e) && *e != ';') e++; + + if (HasField) + { + Status = NewStr(s, e-s); + break; + } + + s = e; + } + } + else break; + } + else break; + } + } + } + + return Status; +} + LString LIpToStr(uint32_t ip) { LString s; s.Printf("%i.%i.%i.%i", (ip>>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, (ip)&0xff); return s; } uint32_t LIpToInt(LString str) { auto p = str.Split("."); if (p.Length() != 4) return 0; uint32_t ip = 0; for (auto &s : p) { ip <<= 8; auto n = s.Int(); if (n > 255) { LAssert(0); return 0; } ip |= (uint8_t)s.Int(); } return ip; } uint32_t LHostnameToIp(const char *Host) { if (!Host) return 0; // struct addrinfo hints = {}; struct addrinfo *res = NULL; getaddrinfo(Host, NULL, NULL, &res); if (!res) return 0; uint32_t ip = 0; if (res->ai_addr) { auto fam = res->ai_addr->sa_family; if (fam == AF_INET) { auto a = (sockaddr_in*)res->ai_addr; ip = ntohl(a->sin_addr.s_addr); } } freeaddrinfo(res); return ip; } bool WhatsMyIp(LAutoString &Ip) { bool Status = false; StartNetworkStack(); #if defined WIN32 char hostname[256]; HostEnt *e = NULL; if (gethostname(hostname, sizeof(hostname)) != SOCKET_ERROR) { e = gethostbyname(hostname); } if (e) { int Which = 0; for (; e->h_addr_list[Which]; Which++); Which--; char IpAddr[32]; sprintf_s(IpAddr, sizeof(IpAddr), "%i.%i.%i.%i", (uchar)e->h_addr_list[Which][0], (uchar)e->h_addr_list[Which][1], (uchar)e->h_addr_list[Which][2], (uchar)e->h_addr_list[Which][3]); Status = Ip.Reset(NewStr(IpAddr)); } #endif return Status; } ////////////////////////////////////////////////////////////////////// #define SOCKS5_VER 5 #define SOCKS5_CMD_CONNECT 1 #define SOCKS5_CMD_BIND 2 #define SOCKS5_CMD_ASSOCIATE 3 #define SOCKS5_ADDR_IPV4 1 #define SOCKS5_ADDR_DOMAIN 3 #define SOCKS5_ADDR_IPV6 4 #define SOCKS5_AUTH_NONE 0 #define SOCKS5_AUTH_GSSAPI 1 #define SOCKS5_AUTH_USER_PASS 2 LSocks5Socket::LSocks5Socket() { Socks5Connected = false; } void LSocks5Socket::SetProxy(const LSocks5Socket *s) { Proxy.Reset(s ? NewStr(s->Proxy) : 0); Port = s ? s->Port : 0; UserName.Reset(s ? NewStr(s->UserName) : 0); Password.Reset(s ? NewStr(s->Password) : 0); } void LSocks5Socket::SetProxy(char *proxy, int port, char *username, char *password) { Proxy.Reset(NewStr(proxy)); Port = port; UserName.Reset(NewStr(username)); Password.Reset(NewStr(password)); } int LSocks5Socket::Open(const char *HostAddr, int port) { bool Status = false; if (HostAddr) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), "[SOCKS5] Connecting to proxy server '%s'", HostAddr); OnInformation(Msg); Status = LSocket::Open(Proxy, Port) != 0; if (Status) { char Buf[1024]; Buf[0] = SOCKS5_VER; Buf[1] = 2; // methods Buf[2] = SOCKS5_AUTH_NONE; Buf[3] = SOCKS5_AUTH_USER_PASS; // No idea how to implement this. // AuthReq[3] = SOCKS5_AUTH_GSSAPI; OnInformation("[SOCKS5] Connected, Requesting authentication type."); LSocket::Write(Buf, 4, 0); if (LSocket::Read(Buf, 2, 0) == 2) { if (Buf[0] == SOCKS5_VER) { bool Authenticated = false; switch (Buf[1]) { case SOCKS5_AUTH_NONE: { Authenticated = true; OnInformation("[SOCKS5] No authentication needed."); break; } case SOCKS5_AUTH_USER_PASS: { OnInformation("[SOCKS5] User/Pass authentication needed."); if (UserName && Password) { char *b = Buf; *b++ = 1; // ver of sub-negotiation ?? size_t NameLen = strlen(UserName); LAssert(NameLen < 0x80); *b++ = (char)NameLen; b += sprintf_s(b, NameLen+1, "%s", UserName.Get()); size_t PassLen = strlen(Password); LAssert(PassLen < 0x80); *b++ = (char)PassLen; b += sprintf_s(b, PassLen+1, "%s", Password.Get()); LSocket::Write(Buf, (int)(3 + NameLen + PassLen)); if (LSocket::Read(Buf, 2, 0) == 2) { Authenticated = (Buf[0] == 1 && Buf[1] == 0); } if (!Authenticated) { OnInformation("[SOCKS5] User/Pass authentication failed."); } } break; } } if (Authenticated) { OnInformation("[SOCKS5] Authentication successful."); int HostPort = htons(port); // Header char *b = Buf; *b++ = SOCKS5_VER; *b++ = SOCKS5_CMD_CONNECT; *b++ = 0; // reserved long IpAddr = inet_addr(HostAddr); if (IpAddr != -1) { // Ip *b++ = SOCKS5_ADDR_IPV4; memcpy(b, &IpAddr, 4); b += 4; } else { // Domain Name *b++ = SOCKS5_ADDR_DOMAIN; size_t Len = strlen(HostAddr); LAssert(Len < 0x80); *b++ = (char)Len; strcpy_s(b, Buf+sizeof(Buf)-b, HostAddr); b += Len; } // Port memcpy(b, &HostPort, 2); b += 2; LSocket::Write(Buf, (int)(b - Buf), 0); if (LSocket::Read(Buf, 10, 0) == 10) { if (Buf[0] == SOCKS5_VER) { switch (Buf[1]) { case 0: Socks5Connected = true; OnInformation("[SOCKS5] Connected!"); break; case 1: OnInformation("[SOCKS5] General SOCKS server failure"); break; case 2: OnInformation("[SOCKS5] Connection not allowed by ruleset"); break; case 3: OnInformation("[SOCKS5] Network unreachable"); break; case 4: OnInformation("[SOCKS5] Host unreachable"); break; case 5: OnInformation("[SOCKS5] Connection refused"); break; case 6: OnInformation("[SOCKS5] TTL expired"); break; case 7: OnInformation("[SOCKS5] Command not supported"); break; case 8: OnInformation("[SOCKS5] Address type not supported"); break; default: OnInformation("[SOCKS5] Unknown SOCKS server failure"); break; } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Connection request read failed."); } } else { OnInformation("[SOCKS5] Not authenticated."); } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Authentication type read failed."); } Status = Socks5Connected; if (!Status) { LSocket::Close(); OnInformation("[SOCKS5] Failure: Disconnecting."); } } } return Status; } diff --git a/src/common/Net/NetTools.cpp b/src/common/Net/NetTools.cpp deleted file mode 100644 --- a/src/common/Net/NetTools.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include -#include - -#include "lgi/common/Mem.h" -#include "LgiOsDefs.h" -#include "lgi/common/NetTools.h" -#include "lgi/common/LgiString.h" - -#define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) - -// Gets a field value -char *InetGetField(const char *s) -{ - const char *e = s; - static const char *WhiteSpace = " \r\t\n"; - - // Look for the end of the string - while (*e) - { - if (*e == '\n') - { - if (!strchr(" \t", e[1])) - { - break; - } - } - - e++; - } - - // Trim whitespace off each end of the string - while (s < e && strchr(WhiteSpace, *s)) - s++; - while (e > s && strchr(" \r\t\n", e[-1])) - e--; - - // Calc the length - size_t Size = e - s; - char *Str = new char[Size+1]; - if (Str) - { - const char *In = s; - char *Out = Str; - - while (In < e) - { - if (*In == '\r') - { - } - else if (*In == 9) - { - *Out++ = ' '; - } - else - { - *Out++ = *In; - } - - In++; - } - - *Out++ = 0; - } - - return Str; -} - -const char *SeekNextLine(const char *s, const char *End) -{ - if (s) - { - for (; *s && *s != '\n' && (!End || s < End); s++); - if (*s == '\n' && (!End || s < End)) s++; - } - - return s; -} - -// Search through headers for a field -char *InetGetHeaderField( // Returns an allocated string or NULL on failure - const char *Headers, // Pointer to all the headers - const char *Field, // The field your after - ssize_t Len) // Maximum len to run, or -1 for NULL terminated -{ - if (Headers && Field) - { - // for all lines - const char *End = Len < 0 ? 0 : Headers + Len; - size_t FldLen = strlen(Field); - - for (const char *s = Headers; - *s && (!End || s < End); - s = SeekNextLine(s, End)) - { - if (*s != 9 && - _strnicmp(s, Field, FldLen) == 0) - { - // found a match - s += FldLen; - if (*s == ':') - { - s++; - while (*s) - { - if (strchr(" \t\r", *s)) - { - s++; - } - else if (*s == '\n') - { - if (strchr(" \r\n\t", s[1])) - s += 2; - else - break; - } - else break; - } - - return InetGetField(s); - } - } - } - } - - return 0; -} - -char *InetGetSubField(const char *s, const char *Field) -{ - char *Status = 0; - - if (s && Field) - { - s = strchr(s, ';'); - if (s) - { - s++; - - size_t FieldLen = strlen(Field); - char White[] = " \t\r\n"; - while (*s) - { - // Skip leading whitespace - while (*s && (strchr(White, *s) || *s == ';')) s++; - - // Parse field name - if (IsAlpha((uint8_t)*s)) - { - const char *f = s; - while (*s && (IsAlpha(*s) || *s == '-')) s++; - bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0); - while (*s && strchr(White, *s)) s++; - if (*s == '=') - { - s++; - while (*s && strchr(White, *s)) s++; - if (*s && strchr("\'\"", *s)) - { - // Quote Delimited Field - char d = *s++; - char *e = strchr((char*)s, d); - if (e) - { - if (HasField) - { - Status = NewStr(s, e-s); - break; - } - - s = e + 1; - } - else break; - } - else - { - // Space Delimited Field - const char *e = s; - while (*e && !strchr(White, *e) && *e != ';') e++; - - if (HasField) - { - Status = NewStr(s, e-s); - break; - } - - s = e; - } - } - else break; - } - else break; - } - } - } - - return Status; -} - diff --git a/src/common/Net/OAuth2.cpp b/src/common/Net/OAuth2.cpp --- a/src/common/Net/OAuth2.cpp +++ b/src/common/Net/OAuth2.cpp @@ -1,507 +1,506 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextLog.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/Base64.h" -#include "lgi/common/NetTools.h" #include "lgi/common/OAuth2.h" #include "lgi/common/Json.h" ////////////////////////////////////////////////////////////////// #define LOCALHOST_PORT 54900 #define OPT_AccessToken "AccessToken" #define OPT_RefreshToken "RefreshToken" static LString GetHeaders(LSocketI *s) { char Buf[256]; ssize_t Rd; LString p; while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) { p += LString(Buf, Rd); if (p.Find("\r\n\r\n") >= 0) return p; } s->Close(); return NULL; } ssize_t ChunkSize(ssize_t &Pos, LString &Buf, LString &Body) { static LString Eol("\r\n"); auto End = Buf.Find(Eol, Pos); if (End > Pos) { auto Sz = Buf(Pos, End).Int(16); if (Sz >= 0) { End += Eol.Length(); auto Bytes = End + Sz + Eol.Length(); if (Buf.Length() >= Bytes) { Body += Buf(End, End + Sz); Pos = End + Sz + Eol.Length(); return Sz; } } } return -1; } static bool GetHttp(LSocketI *s, LString &Hdrs, LString &Body, bool IsResponse) { LString Resp = GetHeaders(s); char Buf[512]; ssize_t Rd; auto BodyPos = Resp.Find("\r\n\r\n"); LAutoString Len(InetGetHeaderField(Resp, "Content-Length", BodyPos)); if (Len) { int Bytes = atoi(Len); size_t Total = BodyPos + 4 + Bytes; while (Resp.Length() < Total) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) { Resp += LString(Buf, Rd); } } } else if (s->IsOpen() && IsResponse) { LAutoString Te(InetGetHeaderField(Resp, "Transfer-Encoding", BodyPos)); bool Chunked = Te && !_stricmp(Te, "chunked"); if (Chunked) { ssize_t Pos = 0; Hdrs = Resp(0, BodyPos); LString Raw = Resp(BodyPos + 4, -1); Body.Empty(); while (s->IsOpen()) { auto Sz = ChunkSize(Pos, Raw, Body); if (Sz == 0) break; if (Sz < 0) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) Raw += LString(Buf, Rd); else break; } } return true; } else { while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) Resp += LString(Buf, Rd); } } Hdrs = Resp(0, BodyPos); Body = Resp(BodyPos + 4, -1); return true; } static LString UrlFromHeaders(LString Hdrs) { auto Lines = Hdrs.Split("\r\n", 1); auto p = Lines[0].SplitDelimit(); if (p.Length() < 3) { return NULL; } return p[1]; } static bool Write(LSocketI *s, LString b) { for (size_t i = 0; i < b.Length(); ) { auto Wr = s->Write(b.Get() + i, b.Length() - i); if (Wr <= 0) return false; i += Wr; } return true; } static LString FormEncode(const char *s, bool InValue = true) { LStringPipe p; for (auto c = s; *c; c++) { if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') { p.Write(c, 1); } else if (*c == ' ') { p.Write((char*)"+", 1); } else { p.Print("%%%02.2X", *c); } } return p.NewLStr(); } struct LOAuth2Priv { LOAuth2::Params Params; LString Id; LStream *Log; LString Token; LString CodeVerifier; LStringPipe LocalLog; LDom *Store; LCancel *Cancel; LString AccessToken, RefreshToken; int64 ExpiresIn; struct Server : public LSocket { LSocket Listen; LOAuth2Priv *d; LSocket s; public: LHashTbl,LString> Params; LString Body; Server(LOAuth2Priv *cd) : d(cd) { auto Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && !Listen.Listen(LOCALHOST_PORT) && (LCurrentTime() - Start) < 60000) { d->Log->Print("Error: Can't listen on %i... (%s)\n", LOCALHOST_PORT, Listen.GetErrorString()); LSleep(1000); } } bool GetReq() { while (!d->Cancel->IsCancelled()) { if (Listen.IsReadable(100) && Listen.Accept(&s)) { // Read access code out of response LString Hdrs; if (GetHttp(&s, Hdrs, Body, false)) { auto Url = UrlFromHeaders(Hdrs); auto Vars = Url.Split("?", 1); if (Vars.Length() != 2) { return false; } Vars = Vars[1].Split("&"); for (auto v : Vars) { auto p = v.Split("=", 1); if (p.Length() != 2) continue; Params.Add(p[0], p[1]); } return true; } } } return false; } bool Response(const char *Txt) { LString Msg; Msg.Printf("HTTP/1.0 200 OK\r\n" "\r\n" "\n" "%s\n" "", Txt); return ::Write(&s, Msg); } }; LString Base64(LString s) { LString b; b.Length(BufferLen_BinTo64(s.Length())); ConvertBinaryToBase64(b.Get(), b.Length(), (uchar*)s.Get(), s.Length()); b.Get()[b.Length()] = 0; return b; } LString ToText(LString Bin) { LArray t; for (char i='0'; i<='9'; i++) t.Add(i); for (char i='a'; i<='z'; i++) t.Add(i); for (char i='A'; i<='Z'; i++) t.Add(i); t.Add('-'); t.Add('.'); t.Add('_'); t.Add('~'); LString Txt; Txt.Length(Bin.Length()); int Pos = 0; for (int i=0; iPrint("%s:%i - Uri: %s\n", _FL, Uri.Get()); LExecute(Uri); // Open browser for user to auth if (Svr.GetReq()) { Token = Svr.Params.Find("code"); Svr.Response(Token ? "Ok: Got token. You can close this window/tab now." : "Error: No token."); } return Token != NULL; } bool GetAccess() { if (!AccessToken) { LStringPipe p(1024); LUri u(Params.ApiUri); SslSocket sock(NULL, NULL, true); if (!sock.Open(u.sHost, HTTPS_PORT)) { Log->Print("Error: Can't connect to '%s:%i'\n", u.sHost.Get(), HTTPS_PORT); return NULL; } LString Body, Http; Body.Printf("code=%s&" "client_id=%s&" "redirect_uri=http://localhost:%i&" "code_verifier=%s&" "grant_type=authorization_code", FormEncode(Token).Get(), Params.ClientID.Get(), LOCALHOST_PORT, FormEncode(CodeVerifier).Get()); if (Params.ClientSecret) { Body += "&client_secret="; Body += Params.ClientSecret; } LUri Api(Params.ApiUri); Http.Printf("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-length: " LPrintfSizeT "\r\n" "\r\n" "%s", Api.sPath.Get(), Api.sHost.Get(), Body.Length(), Body.Get()); if (!Write(&sock, Http)) { Log->Print("%s:%i - Error writing to socket.\n", _FL); return false; } LString Hdrs; if (!GetHttp(&sock, Hdrs, Body, true)) { return false; } // Log->Print("Body=%s\n", Body.Get()); LJson j(Body); AccessToken = j.Get("access_token"); RefreshToken = j.Get("refresh_token"); ExpiresIn = j.Get("expires_in").Int(); if (!AccessToken) Log->Print("Failed to get AccessToken: %s\n", Body.Get()); } return AccessToken.Get() != NULL; } bool Refresh() { if (!RefreshToken) return false; LStringPipe p(1024); LUri u(Params.Scope); SslSocket sock(NULL, NULL, true); if (!sock.Open(u.sHost, HTTPS_PORT)) { Log->Print("Error: Can't connect to '%s:%i'\n", u.sHost.Get(), HTTPS_PORT); return NULL; } LString Body, Http; Body.Printf("refresh_token=%s&" "client_id=%s&" "client_secret=%s&" "grant_type=refresh_token", FormEncode(RefreshToken).Get(), Params.ClientID.Get(), Params.ClientSecret.Get()); LUri Api(Params.ApiUri); Http.Printf("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-length: " LPrintfSizeT "\r\n" "\r\n" "%s", Api.sPath.Get(), Api.sHost.Get(), Body.Length(), Body.Get()); if (!Write(&sock, Http)) { Log->Print("%s:%i - Error writing to socket.\n", _FL); return false; } LString Hdrs; if (!GetHttp(&sock, Hdrs, Body, true)) { return false; } // Log->Print("Body=%s\n%s\n", Params.ApiUri.Get(), Body.Get()); LJson j(Body); AccessToken = j.Get("access_token"); ExpiresIn = j.Get("expires_in").Int(); return AccessToken.Get() != NULL; } LOAuth2Priv(LOAuth2::Params ¶ms, const char *account, LDom *store, LStream *log, LCancel *cancel) { Params = params; Id = account; Store = store; Cancel = cancel; Log = log ? log : &LocalLog; } bool Serialize(bool Write) { if (!Store) return false; LVariant v; LString Key, kAccTok, kRefreshTok; Key.Printf("%s.%s", Params.Scope.Get(), Id.Get()); auto KeyB64 = Base64(Key); kAccTok.Printf("OAuth2-%s-%s", OPT_AccessToken, KeyB64.Get()); kAccTok = kAccTok.RStrip("="); kRefreshTok.Printf("OAuth2-%s-%s", OPT_RefreshToken, KeyB64.Get()); kRefreshTok= kRefreshTok.RStrip("="); if (Write) { Store->SetValue(kAccTok, v = AccessToken.Get()); Store->SetValue(kRefreshTok, v = RefreshToken.Get()); } else { if (Store->GetValue(kAccTok, v)) AccessToken = v.Str(); else return false; if (Store->GetValue(kRefreshTok, v)) RefreshToken = v.Str(); else return false; } return true; } }; LOAuth2::LOAuth2(LOAuth2::Params ¶ms, const char *account, LDom *store, LCancel *cancel, LStream *log) { d = new LOAuth2Priv(params, account, store, log, cancel); d->Serialize(false); } LOAuth2::~LOAuth2() { d->Serialize(true); delete d; } bool LOAuth2::Refresh() { d->AccessToken.Empty(); d->Serialize(true); return d->Refresh(); } LString LOAuth2::GetAccessToken() { if (d->AccessToken) return d->AccessToken; if (d->GetToken()) { d->Log->Print("Got token.\n"); if (d->GetAccess()) return d->AccessToken; else d->Log->Print("No access.\n"); } else d->Log->Print("No token.\n"); return LString(); } diff --git a/src/common/Net/Uri.cpp b/src/common/Net/Uri.cpp --- a/src/common/Net/Uri.cpp +++ b/src/common/Net/Uri.cpp @@ -1,379 +1,380 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/RegKey.h" +#include "lgi/common/Uri.h" ///////////////////////////////////////////////////////////////////////////////// static const char *Ws = " \t\r\n"; #define SkipWs(s) while (*s && strchr(Ws, *s)) s++; LUri::LUri(const char *uri) { Port = 0; if (uri) Set(uri); } LUri::~LUri() { Empty(); } LUri &LUri::operator +=(const char *s) { // Add segment to path if (!sPath.Length() || sPath(-1) != '/') sPath += IsFile() ? DIR_STR : "/"; auto len = sPath.Length(); sPath += s; if (IsFile()) { char *c = sPath.Get(); #if DIR_CHAR == '/' char from = '\\'; #else char from = '/'; #endif for (size_t i=len; c && i 1 && s[1] == '/' && s[2] == '/') { sProtocol.Set(p, s - p); s += 3; } else { // No protocol, so assume it's a host name or path s = p; } // Check for path bool HasPath = false; if ( (s[0] && s[1] == ':') || (s[0] == '/') || (s[0] == '\\') ) { HasPath = true; } else { // Scan over the host name p = s; while ( *s && *s > ' ' && *s < 127 && *s != '/' && *s != '\\') { s++; } sHost.Set(p, s - p); if (sHost) { char *At = strchr(sHost, '@'); if (At) { *At++ = 0; char *Col = strchr(sHost, ':'); if (Col) { *Col++ = 0; sPass = DecodeStr(Col); } sUser = DecodeStr(sHost); sHost = At; } char *Col = strchr(sHost, ':'); if (Col) { Port = atoi(Col+1); sHost.Length(Col-sHost.Get()); } } HasPath = *s == '/'; } if (HasPath) { const char *Start = s; while (*s && *s != '#') s++; if (*s) { sPath.Set(Start, s - Start); sAnchor = s + 1; } else { sPath = Start; } #if 0 // This decodes the path from %## encoding to raw characters. // However sometimes we need the encoded form. So instead of // doing the conversion here the caller has to do it now. char *i = Path, *o = Path; while (*i) { if (*i == '%' && i[1] && i[2]) { char h[3] = {i[1], i[2], 0}; *o++ = htoi(h); i+=2; } else { *o++ = *i; } i++; } *o = 0; #endif } return sHost || sPath; } LString LUri::EncodeStr(const char *s, const char *ExtraCharsToEncode) { LStringPipe p(256); if (s) { while (*s) { if (*s == ' ' || (ExtraCharsToEncode && strchr(ExtraCharsToEncode, *s))) { char h[4]; sprintf_s(h, sizeof(h), "%%%2.2X", (uint32_t)(uchar)*s++); p.Write(h, 3); } else { p.Write(s++, 1); } } } return p.NewLStr(); } LUri::StrMap LUri::Params() { StrMap m; if (sPath) { const char *q = strchr(sPath, '?'); if (q++) { auto Parts = LString(q).SplitDelimit("&"); for (auto p : Parts) { auto Var = p.Split("=", 1); if (Var.Length() == 2) m.Add(Var[0], Var[1]); } } } return m; } LString LUri::DecodeStr(const char *s) { LStringPipe p(256); if (s) { while (*s) { if (s[0] == '%' && s[1] && s[2]) { char h[3] = { s[1], s[2], 0 }; char c = htoi(h); p.Write(&c, 1); s += 3; } else { p.Write(s++, 1); } } } return p.NewLStr(); } #if defined LGI_CARBON int CFNumberRefToInt(CFNumberRef r, int Default = 0) { int i = Default; if (r && CFGetTypeID(r) == CFNumberGetTypeID()) { CFNumberGetValue(r, kCFNumberIntType, &r); } return i; } #endif LProxyUri::LProxyUri() { #if defined(WIN32) LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); if (k.IsOk()) { uint32_t Enabled = 0; if (k.GetInt("ProxyEnable", Enabled) && Enabled) { char *p = k.GetStr("ProxyServer"); if (p) { Set(p); } } } #elif defined LINUX char *HttpProxy = getenv("http_proxy"); if (HttpProxy) { Set(HttpProxy); } #elif defined MAC // CFDictionaryRef Proxies = SCDynamicStoreCopyProxies(0); // if (!Proxies) // LgiTrace("%s:%i - SCDynamicStoreCopyProxies failed.\n", _FL); // else // { // int enable = CFNumberRefToInt((CFNumberRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPEnable)); // if (enable) // { // #ifdef LGI_COCOA // LAssert(!"Fixme"); // #else // Host = CFStringToUtf8((CFStringRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPProxy)); // #endif // Port = CFNumberRefToInt((CFNumberRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPPort)); // } // // CFRelease(Proxies); // } #else #warning "Impl getting OS proxy here." #endif } diff --git a/src/common/Text/DocView.cpp b/src/common/Text/DocView.cpp --- a/src/common/Text/DocView.cpp +++ b/src/common/Text/DocView.cpp @@ -1,224 +1,225 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" +#include "lgi/common/Uri.h" #define SubtractPtr(a, b) ((a)-(b)) const char *LDocView::WhiteSpace = " \t\r\n"; const char *LDocView::Delimiters = "!@#$%^&*()'\":;,.<>/?[]{}-=+\\|`~"; const char *LDocView::UrlDelim = "!~/:%+-?@&$#._=,;*()|"; LDocumentEnv::LDocumentEnv(LDocView *v) { if (v) { Viewers.Add(v); } } LDocumentEnv::~LDocumentEnv() { for (uint32_t i=0; iEnvironment = 0; } } int LDocumentEnv::NextUid() { if (!Lock(_FL)) return -1; int Uid = 0; for (auto v : Viewers) Uid = MAX(Uid, v->GetDocumentUid() + 1); Unlock(); return Uid; } void LDocumentEnv::OnDone(LAutoPtr j) { LoadJob *ld = dynamic_cast(j.Get()); if (ld) { if (Lock(_FL)) { LDocView *View = NULL; for (unsigned i=0; iGetDocumentUid(); if (Uid == ld->UserUid) { View = Viewers[i]; break; } } if (View) { View->OnContent(ld); j.Release(); } Unlock(); } } else LAssert(!"RTTI failed."); } ////////////////////////////////////////////////////////////////////////////////// char16 *ConvertToCrLf(char16 *Text) { if (Text) { // add '\r's int Lfs = 0; int Len = 0; char16 *s=Text; for (; *s; s++) { if (*s == '\n') Lfs++; Len++; } char16 *Temp = new char16[Len+Lfs+1]; if (Temp) { char16 *d=Temp; s = Text; for (; *s; s++) { if (*s == '\n') { *d++ = 0x0d; *d++ = 0x0a; } else if (*s == '\r') { // ignore } else { *d++ = *s; } } *d++ = 0; DeleteObj(Text); return Temp; } } return Text; } ////////////////////////////////////////////////////////////////////////////////// #include "lgi/common/Net.h" LDocumentEnv::LoadType LDefaultDocumentEnv::GetContent(LoadJob *&j) { if (!j || !ValidStr(j->Uri)) return LoadError; char p[MAX_PATH_LEN]; char *FullPath = NULL; char *FileName = NULL; LUri u(j->Uri); if (u.sProtocol && !_stricmp(u.sProtocol, "file")) FileName = u.sPath; else FileName = j->Uri; if (FileName) { LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LMakePath(p, sizeof(p), p, FileName); if (LFileExists(p)) { FullPath = p; } else { auto f = LFindFile(FileName); if (f) strcpy_s(FullPath = p, sizeof(p), f); } } if (LFileExists(FullPath)) { LString Mt = LGetFileMimeType(FullPath); if (Mt.Find("image/") == 0) { j->pDC.Reset(GdcD->Load(p)); return LoadImmediate; } j->Filename = FullPath; return LoadImmediate; } return LoadError; } bool LDefaultDocumentEnv::OnNavigate(LDocView *Parent, const char *Uri) { if (Uri) { if ( _strnicmp(Uri, "mailto:", 7) == 0 || ( strchr(Uri, '@') != 0 && strchr(Uri, '/') == 0 ) ) { // email LArray Apps; if (LGetAppsForMimeType("application/email", Apps)) { LAppInfo *First = &Apps[0]; LStringPipe a; char *Arg = First->Params ? strstr(First->Params, "%1") : 0; if (Arg) { // change '%1' into the email address in question a.Write(First->Params, (int) (Arg-First->Params)); a.Print("%s%s", Uri, Arg + 2); } else { // no argument place holder... just pass as a normal arg a.Print(" %s", Uri); } LAutoString Exe(TrimStr(First->Path, "\"\'")); LAutoString Args(a.NewStr()); LString ErrorMsg; if (LExecute(Exe, Args, ".", &ErrorMsg)) return true; LgiMsg(Parent, "Failed to open '%s':\n%s", LAppInst->LBase::Name(), MB_OK, Exe.Get(), ErrorMsg.Get()); } else { LgiMsg(Parent, "Couldn't get app to handle email.", LAppInst->LBase::Name(), MB_OK); } } else { // webpage LString ErrorMsg; if (LExecute(Uri, NULL, NULL, &ErrorMsg)) return true; LgiMsg(Parent, "Failed to open '%s':\n%s", LAppInst->LBase::Name(), MB_OK, Uri, ErrorMsg.Get()); } } return false; } diff --git a/test/HtmlEditor/CMakeLists.txt b/test/HtmlEditor/CMakeLists.txt --- a/test/HtmlEditor/CMakeLists.txt +++ b/test/HtmlEditor/CMakeLists.txt @@ -1,62 +1,62 @@ set(APP_MANIFEST) if (WIN32) set(APP_TYPE WIN32) - set(APP_ICON Rich.rc) + set(APP_ICON resources/Rich.rc) set(APP_MANIFEST ${LGI_DIR}/src/win/General/Lgi.manifest) set(SPELL_CHECK ${LGI_DIR}/src/win/Lgi/SpellCheckWindows.cpp) elseif (APPLE) set(APP_TYPE MACOSX_BUNDLE) set(SPELL_CHECK ${LGI_DIR}/src/mac/cocoa/SpellCheckMac.mm) elseif (LINUX) set(APP_ICON) set(SPELL_CHECK ${LGI_DIR}/src/common/Text/SpellCheckAspell.cpp) endif() add_executable(HtmlEdit ${APP_TYPE} - Rich.cpp + src/Rich.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Jpeg.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Png.cpp ${LGI_DIR}/src/common/Gdc2/Tools/GdcTools.cpp ${LGI_DIR}/src/common/Net/Http.cpp ${LGI_DIR}/src/common/Net/OpenSSLSocket.cpp ${LGI_DIR}/src/common/Lgi/LgiMain.cpp ${LGI_DIR}/src/common/Text/Emoji/EmojiMap.cpp ${LGI_DIR}/src/common/Text/Emoji/EmojiTools.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/Homoglyphs.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/HomoglyphsTable.cpp ${LGI_DIR}/src/common/Text/Html.cpp ${LGI_DIR}/src/common/Text/HtmlCommon.cpp ${LGI_DIR}/src/common/Text/HtmlParser.cpp ${LGI_DIR}/src/common/Widgets/Editor/BlockCursor.cpp ${LGI_DIR}/src/common/Widgets/Editor/RichTextEdit.cpp ${LGI_DIR}/src/common/Widgets/Editor/RichTextEditPriv.cpp ${LGI_DIR}/src/common/Widgets/Editor/HorzRuleBlock.cpp ${LGI_DIR}/src/common/Widgets/Editor/ImageBlock.cpp ${LGI_DIR}/src/common/Widgets/Editor/TextBlock.cpp ${SPELL_CHECK} - resource.h + resources/resource.h ${APP_ICON} ${APP_MANIFEST}) target_include_directories(HtmlEdit PRIVATE Code Resources ${OPENSSL_INCLUDE_DIR} ../../private/common) target_link_libraries(HtmlEdit PRIVATE lgi libpng16 libjpeg9a) if (APPLE) macBundlePostBuild(HtmlEdit) endif() set_target_properties(HtmlEdit PROPERTIES EXCLUDE_FROM_ALL TRUE) diff --git a/win/Lgi_vs2019.vcxproj b/win/Lgi_vs2019.vcxproj --- a/win/Lgi_vs2019.vcxproj +++ b/win/Lgi_vs2019.vcxproj @@ -1,626 +1,625 @@  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 Lgi19x64 ..\Lib\ $(Platform)$(Configuration)19\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64d ..\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include 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 true 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 false true _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 false true 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/win/Lgi_vs2019.vcxproj.filters b/win/Lgi_vs2019.vcxproj.filters --- a/win/Lgi_vs2019.vcxproj.filters +++ b/win/Lgi_vs2019.vcxproj.filters @@ -1,1232 +1,1229 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} {814a5d81-3fd5-461b-a4a3-cda593ea404b} {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl Source Files\Core Source Files\Core Source Files\Dialogs Source Files\General Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Graphics Source Files\Widgets Source Files\Text Source Files\General\Clipboard Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Widgets\Css Source Files\Widgets\Css Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Core\Drag and Drop Source Files\Core\Drag and Drop Source Files\Core Source Files\Core\Drag and Drop Source Files\Widgets Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Dialogs Source Files\Graphics\Filters Source Files\Dialogs Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\Core\Skin Source Files\General Source Files\General\Growl Source Files\Graphics Source Files\Dialogs Source Files\Core Source Files\Core Source Files\General Source Files\General Source Files\Core\Resources Source Files\Core\Libraries Source Files\Widgets Source Files\Dialogs Source Files\General\Hash Source Files\General\Hash Source Files\Core\Memory Subsystem Source Files\Graphics\Surfaces Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\Network Source Files\Core Source Files\Core\Threads Source Files\Network - - Source Files\Network - Source Files\General Source Files\General Source Files\Widgets Source Files\General Source Files\Graphics Source Files\Widgets Source Files\Graphics\Surfaces Source Files\Core Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Widgets Source Files\Widgets\Native Windows Source Files\General Source Files\Graphics Source Files\Core\Resources Source Files\Graphics\Surfaces Source Files\Widgets Source Files\General\Hash Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Widgets Source Files\Core\Memory Subsystem Source Files\Text Source Files\Graphics\Font Source Files\Core\Process Source Files\Graphics\Surfaces Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Threads Source Files\Text Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text Source Files\General Source Files\Core Source Files\Core Source Files\Core Source Files\Core Source Files\Text Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files \ No newline at end of file