├── .gitignore ├── LICENSE ├── README.md ├── build.bat ├── mnotify.ini ├── res └── mnotify_logo.png └── src ├── WindowsToast.h ├── imap_client.c ├── imap_client.h ├── imap_parser.c ├── imap_parser.h ├── mnotify.c ├── mnotify.h ├── mnotify.rc ├── mnotify_closed.ico ├── mnotify_closed_warning.ico ├── mnotify_open.ico ├── mnotify_open_warning.ico ├── tls.c └── tokenizer.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MNotify 2 | 3 | Simple email notification application. 4 | 5 | MNotify will sit in the background and listen to incoming emails using the 6 | IMAP protocol and give you notifications when you have unread mail. 7 | 8 | ## Building 9 | 10 | In order to build the application you need to run `build.bat`. 11 | 12 | ## Usage 13 | 14 | Before opening MNotify you have to edit the `mnotify.ini` file within the build directory. 15 | 16 | The ini file contains the following fields: 17 | 18 | | Configuration | Description | Example | 19 | | ------------- | ------------- | ------------- | 20 | | host | The host of your email provider's imap server. | imap.gmail.com | 21 | | port | The port for your email provider's imap server. | 993 | 22 | | accountname | Your account name. | example@gmail.com | 23 | | password | Your password. | RandomGeneratedAppPassword | 24 | | opensite | The link you want to open in your browser when clicking on MNotify in the system tray. | | 25 | | folder | The folder you want to listen for incoming emails in. | inbox | 26 | | pollingtimer | If the IMAP service doesn't support IDLE we need to perform polling. This is to specify how often to poll in seconds. Do note that most mail providers has rate limiting so don't choose a too low value. | 600 | 27 | | retrytime | If the background thread listening for emails exits it will restart after this many seconds. | 60 | 28 | 29 | When `mnotify.ini` contains your desired options then you can simply run `mnotify.exe` located in the build directory. 30 | 31 | In case of errors MNotify will write to a logfile located next to the executable. The icon within the system tray will change to include a warning triangle in the top right corner. In theese cases MNotify will also include an option when right-clicking the application in the system tray to show the error log. 32 | 33 | ### Tested email providers 34 | 35 | The application has been tested against the following providers: 36 | 37 | * Google GMail 38 | * Yahoo 39 | * Outlook 40 | 41 | Note that for most providers they require you to generate an "App Password". 42 | See the following: 43 | 44 | * Google: [https://support.google.com/accounts/answer/185833?hl=en](https://support.google.com/accounts/answer/185833?hl=en) 45 | * Yahoo: [https://help.yahoo.com/kb/SLN15241.html](https://help.yahoo.com/kb/SLN15241.html) 46 | * Outlook: Couldn't find official documentation of how to do this but I managed by first turning on 2FA then navigating to: `My Microsoft Account` -> `Security` -> `Advanced Security Options` -> Scroll down to find "App Passwords". 47 | 48 | If you find any problems or have additional feature requests please submit an issue. 49 | 50 | ## Thanks to 51 | 52 | [Mārtiņš Možeiko (mmozeiko)](https://github.com/mmozeiko) for the tls code which is available [here](https://gist.github.com/mmozeiko/c0dfcc8fec527a90a02145d2cc0bfb6d). And the WindowsToast.h header file to add support for Windows 10 style toasts in C. 53 | 54 | ## License 55 | 56 | This is free and unencumbered software released into the public domain. 57 | 58 | Anyone is free to copy, modify, publish, use, compile, sell, or 59 | distribute this software, either in source code form or as a compiled 60 | binary, for any purpose, commercial or non-commercial, and by any 61 | means. 62 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | where /Q cl.exe || ( 4 | for /f "tokens=*" %%i in ('"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath') do set VisualStudio=%%i 5 | if "%VisualStudio%" equ "" ( 6 | echo ERROR: Can't find visual studio installation 7 | exit /b 1 8 | ) 9 | 10 | call "%VisualStudio%\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 11 | ) 12 | 13 | if "%1" equ "debug" ( 14 | set compile_flags= -nologo /Zi /FC 15 | set link_flags= -opt:ref -incremental:no /Debug:fastlink 16 | ) else ( 17 | set compile_flags= -nologo /GL /O1 18 | set link_flags= /LTCG -opt:ref -opt:icf -incremental:no 19 | ) 20 | 21 | if not exist build mkdir build 22 | pushd build 23 | 24 | start /b /wait "" "rc.exe" /nologo -fo ./mnotify.res ../src/mnotify.rc 25 | start /b /wait "" "cl.exe" %compile_flags% ../src/mnotify.c mnotify.res /link %link_flags% /SUBSYSTEM:WINDOWS /out:mnotify.exe 26 | 27 | if not exist "mnotify.ini" copy "..\\mnotify.ini" 28 | if not exist "mnotify_logo.png" copy "..\\res\\mnotify_logo.png" 29 | popd -------------------------------------------------------------------------------- /mnotify.ini: -------------------------------------------------------------------------------- 1 | [Account] 2 | host=imap.gmail.com 3 | port=993 4 | accountname=examplegmail.com 5 | password=password 6 | opensite=https://mail.google.com/mail/u/0/ 7 | folder=inbox 8 | pollingtimer=600 9 | retrytime=60 -------------------------------------------------------------------------------- /res/mnotify_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/mnotify/72a795270134ac084a117c55fd52f3c56f1fecd1/res/mnotify_logo.png -------------------------------------------------------------------------------- /src/WindowsToast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // interface 7 | 8 | typedef struct WindowsToast WindowsToast; 9 | typedef __x_ABI_CWindows_CUI_CNotifications_CToastDismissalReason WindowsToast_DismissReason; 10 | 11 | typedef void WindowsToast_OnActivated(WindowsToast* Toast, void* Item, LPCWSTR Action); 12 | typedef void WindowsToast_OnDismissed(WindowsToast* Toast, void* Item, WindowsToast_DismissReason Reason); 13 | 14 | struct WindowsToast 15 | { 16 | // public (change these before Show() is called 17 | WindowsToast_OnActivated* OnActivatedCallback; 18 | WindowsToast_OnDismissed* OnDismissedCallback; 19 | 20 | // private 21 | __x_ABI_CWindows_CUI_CNotifications_CINotificationDataFactory* DataFactory; 22 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory* NotificationFactory; 23 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics* ManagerStatics; 24 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier* Notifier; 25 | 26 | __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable OnActivated; 27 | __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs OnDismissed; 28 | }; 29 | 30 | // AppId is in form of "CompanyName.ProductName" 31 | static void WindowsToast_Init(WindowsToast* Toast, LPCWSTR AppName, LPCWSTR AppId); 32 | static void WindowsToast_Done(WindowsToast* Toast); 33 | static void WindowsToast_HideAll(WindowsToast* Toast, LPCWSTR AppId); 34 | 35 | // toast xml editor/visualizer 36 | // https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer 37 | // xml examples & schema 38 | // https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts 39 | // https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema 40 | // https://docs.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root 41 | 42 | // pass -1 for XmlLength if Xml string is 0 terminated 43 | static void WindowsToast_ShowSimple(WindowsToast* Toast, LPCWSTR Xml, int XmlLength); 44 | 45 | static void* WindowsToast_Create(WindowsToast* Toast, LPCWSTR Xml, int XmlLength, LPCWSTR (*Data)[2], UINT32 Count); 46 | static void WindowsToast_Show(WindowsToast* Toast, void* Item); 47 | static void WindowsToast_Hide(WindowsToast* Toast, void* Item); 48 | static BOOL WindowsToast_Update(WindowsToast* Toast, void* Item, LPCWSTR (*Data)[2], UINT32 Count); 49 | static void WindowsToast_Release(WindowsToast* Toast, void* Item); 50 | 51 | // implementation 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #pragma comment (lib, "shlwapi.lib") 60 | #pragma comment (lib, "ole32.lib") 61 | #pragma comment (lib, "runtimeobject.lib") 62 | 63 | #ifndef Assert 64 | #define Assert(Cond) ((void)(Cond)) 65 | #endif 66 | 67 | #ifndef HR 68 | #define HR(hr) ((void)(hr)) 69 | #endif 70 | 71 | typedef struct { 72 | DWORD Flags; 73 | DWORD Length; 74 | DWORD Padding1; 75 | DWORD Padding2; 76 | LPCWSTR Ptr; 77 | } WindowsToastHSTRING; 78 | 79 | #define WindowsToastCSTR(Str) (HSTRING)&(WindowsToastHSTRING){ 1, ARRAYSIZE(Str)-1, 0, 0, L ## Str } 80 | 81 | DEFINE_GUID(IID_IToastNotificationManagerStatics, 0x50ac103f, 0xd235, 0x4598, 0xbb, 0xef, 0x98, 0xfe, 0x4d, 0x1a, 0x3a, 0xd4); 82 | DEFINE_GUID(IID_IToastNotificationManagerStatics2, 0x7ab93c52, 0x0e48, 0x4750, 0xba, 0x9d, 0x1a, 0x41, 0x13, 0x98, 0x18, 0x47); 83 | DEFINE_GUID(IID_IToastNotificationFactory, 0x04124b20, 0x82c6, 0x4229, 0xb1, 0x09, 0xfd, 0x9e, 0xd4, 0x66, 0x2b, 0x53); 84 | DEFINE_GUID(IID_INotificationDataFactory, 0x23c1e33a, 0x1c10, 0x46fb, 0x80, 0x40, 0xde, 0xc3, 0x84, 0x62, 0x1c, 0xf8); 85 | DEFINE_GUID(IID_IToastNotification2, 0x9dfb9fd1, 0x143a, 0x490e, 0x90, 0xbf, 0xb9, 0xfb, 0xa7, 0x13, 0x2d, 0xe7); 86 | DEFINE_GUID(IID_IToastNotification4, 0x15154935, 0x28ea, 0x4727, 0x88, 0xe9, 0xc5, 0x86, 0x80, 0xe2, 0xd1, 0x18); 87 | DEFINE_GUID(IID_IToastNotifier2, 0x354389c6, 0x7c01, 0x4bd5, 0x9c, 0x20, 0x60, 0x43, 0x40, 0xcd, 0x2b, 0x74); 88 | DEFINE_GUID(IID_IToastActivatedEventArgs, 0xe3bf92f3, 0xc197, 0x436f, 0x82, 0x65, 0x06, 0x25, 0x82, 0x4f, 0x8d, 0xac); 89 | DEFINE_GUID(IID_IToastActivatedEventHandler, 0xab54de2d, 0x97d9, 0x5528, 0xb6, 0xad, 0x10, 0x5a, 0xfe, 0x15, 0x65, 0x30); 90 | DEFINE_GUID(IID_IToastDismissedEventHandler, 0x61c2402f, 0x0ed0, 0x5a18, 0xab, 0x69, 0x59, 0xf4, 0xaa, 0x99, 0xa3, 0x68); 91 | DEFINE_GUID(IID_IXmlDocumentIO, 0x6cd0e74e, 0xee65, 0x4489, 0x9e, 0xbf, 0xca, 0x43, 0xe8, 0x7b, 0xa6, 0x37); 92 | 93 | static HRESULT STDMETHODCALLTYPE WindowsToast__OnActivated_QueryInterface(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable* This, REFIID Riid, void** Object) 94 | { 95 | if (Object == NULL) 96 | { 97 | return E_POINTER; 98 | } 99 | if (IsEqualGUID(Riid, &IID_IToastActivatedEventHandler) || 100 | IsEqualGUID(Riid, &IID_IAgileObject) || 101 | IsEqualGUID(Riid, &IID_IUnknown)) 102 | { 103 | *Object = This; 104 | return S_OK; 105 | } 106 | return E_NOINTERFACE; 107 | } 108 | 109 | static ULONG WINAPI STDMETHODCALLTYPE WindowsToast__OnActivated_AddRef(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable* This) 110 | { 111 | return 1; 112 | } 113 | 114 | static ULONG STDMETHODCALLTYPE WindowsToast__OnActivated_Release(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable* This) 115 | { 116 | return 1; 117 | } 118 | 119 | static HRESULT STDMETHODCALLTYPE WindowsToast__OnActivated_Invoke(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable* This, __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Sender, IInspectable* Args) 120 | { 121 | WindowsToast* Toast = CONTAINING_RECORD(This, WindowsToast, OnActivated); 122 | if (Toast->OnActivatedCallback) 123 | { 124 | __x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs* EventArgs; 125 | HR(IInspectable_QueryInterface(Args, &IID_IToastActivatedEventArgs, &EventArgs)); 126 | 127 | HSTRING ArgString; 128 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs_get_Arguments(EventArgs, &ArgString)); 129 | 130 | Toast->OnActivatedCallback(Toast, Sender, WindowsGetStringRawBuffer(ArgString, NULL)); 131 | } 132 | return S_OK; 133 | } 134 | 135 | static __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectableVtbl WindowsToast__OnActivatedVtbl = 136 | { 137 | .QueryInterface = &WindowsToast__OnActivated_QueryInterface, 138 | .AddRef = &WindowsToast__OnActivated_AddRef, 139 | .Release = &WindowsToast__OnActivated_Release, 140 | .Invoke = &WindowsToast__OnActivated_Invoke, 141 | }; 142 | 143 | static HRESULT STDMETHODCALLTYPE WindowsToast__OnDismissed_QueryInterface(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs* This, REFIID Riid, void** Object) 144 | { 145 | if (Object == NULL) 146 | { 147 | return E_POINTER; 148 | } 149 | if (IsEqualGUID(Riid, &IID_IToastDismissedEventHandler) || 150 | IsEqualGUID(Riid, &IID_IAgileObject) || 151 | IsEqualGUID(Riid, &IID_IUnknown)) 152 | { 153 | *Object = This; 154 | return S_OK; 155 | } 156 | return E_NOINTERFACE; 157 | } 158 | 159 | static ULONG STDMETHODCALLTYPE WindowsToast__OnDismissed_AddRef(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs* This) 160 | { 161 | return 1; 162 | } 163 | 164 | static ULONG STDMETHODCALLTYPE WindowsToast__OnDismissed_Release(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs* This) 165 | { 166 | return 1; 167 | } 168 | 169 | static HRESULT STDMETHODCALLTYPE WindowsToast__OnDismissed_Invoke(__FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs* This, __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Sender, __x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs* Args) 170 | { 171 | WindowsToast* Toast = CONTAINING_RECORD(This, WindowsToast, OnDismissed); 172 | if (Toast->OnDismissedCallback) 173 | { 174 | __x_ABI_CWindows_CUI_CNotifications_CToastDismissalReason Reason; 175 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs_get_Reason(Args, &Reason)); 176 | Toast->OnDismissedCallback(Toast, Sender, Reason); 177 | } 178 | return S_OK; 179 | } 180 | 181 | static __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgsVtbl WindowsToast__OnDismissedVtbl = 182 | { 183 | .QueryInterface = &WindowsToast__OnDismissed_QueryInterface, 184 | .AddRef = &WindowsToast__OnDismissed_AddRef, 185 | .Release = &WindowsToast__OnDismissed_Release, 186 | .Invoke = &WindowsToast__OnDismissed_Invoke, 187 | }; 188 | 189 | // NOTE: minimal implementation to support only one active Iterator over Iterable 190 | typedef struct { 191 | __FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING Iterable; 192 | __FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING Iterator; 193 | __FIKeyValuePair_2_HSTRING_HSTRING Pair; 194 | LPCWSTR (*Pairs)[2]; 195 | UINT32 Count; 196 | UINT32 Current; 197 | WindowsToastHSTRING KeyString; 198 | WindowsToastHSTRING ValueString; 199 | } WindowsToast__Data; 200 | 201 | static HRESULT STDMETHODCALLTYPE WindowsToast__IterableQueryInterface(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This, REFIID Riid, void** Object) 202 | { 203 | return E_NOTIMPL; 204 | } 205 | 206 | static ULONG STDMETHODCALLTYPE WindowsToast__IterableAddRef(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This) 207 | { 208 | return 1; 209 | } 210 | 211 | static ULONG STDMETHODCALLTYPE WindowsToast__IterableRelease(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This) 212 | { 213 | return 1; 214 | } 215 | 216 | static HRESULT STDMETHODCALLTYPE WindowsToast__IterableGetIids(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This, ULONG* IidCount, IID** Iids) 217 | { 218 | return E_NOTIMPL; 219 | } 220 | 221 | static HRESULT STDMETHODCALLTYPE WindowsToast__IterableGetRuntimeClassName(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This, HSTRING* ClassName) 222 | { 223 | return E_NOTIMPL; 224 | } 225 | 226 | HRESULT STDMETHODCALLTYPE WindowsToast__IterableGetTrustLevel(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This, TrustLevel* TrustLevel) 227 | { 228 | return E_NOTIMPL; 229 | } 230 | 231 | static HRESULT STDMETHODCALLTYPE WindowsToast__IterableFirst(__FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRING* This, __FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING** Result) 232 | { 233 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Iterable); 234 | Data->Current = 0; 235 | *Result = &Data->Iterator; 236 | return S_OK; 237 | } 238 | 239 | static __FIIterable_1___FIKeyValuePair_2_HSTRING_HSTRINGVtbl WindowsToast__IterableVtbl = 240 | { 241 | .QueryInterface = &WindowsToast__IterableQueryInterface, 242 | .AddRef = &WindowsToast__IterableAddRef, 243 | .Release = &WindowsToast__IterableRelease, 244 | .GetIids = &WindowsToast__IterableGetIids, 245 | .GetRuntimeClassName = &WindowsToast__IterableGetRuntimeClassName, 246 | .GetTrustLevel = &WindowsToast__IterableGetTrustLevel, 247 | .First = &WindowsToast__IterableFirst, 248 | }; 249 | 250 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorQueryInterface(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, REFIID riid, void** Object) 251 | { 252 | return E_NOTIMPL; 253 | } 254 | 255 | static ULONG STDMETHODCALLTYPE WindowsToast_IteratorAddRef(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This) 256 | { 257 | return 1; 258 | } 259 | 260 | static ULONG STDMETHODCALLTYPE WindowsToast_IteratorRelease(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This) 261 | { 262 | return 1; 263 | } 264 | 265 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetIids(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, ULONG* IidCount, IID** Iids) 266 | { 267 | return E_NOTIMPL; 268 | } 269 | 270 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetRuntimeClassName(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, HSTRING* ClassName) 271 | { 272 | return E_NOTIMPL; 273 | } 274 | 275 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetTrustLevel(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, TrustLevel* TrustLevel) 276 | { 277 | return E_NOTIMPL; 278 | } 279 | 280 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetCurrent(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, __FIKeyValuePair_2_HSTRING_HSTRING** Result) 281 | { 282 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Iterator); 283 | *Result = &Data->Pair; 284 | return S_OK; 285 | } 286 | 287 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetHasCurrent(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, boolean* Result) 288 | { 289 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Iterator); 290 | *Result = Data->Current < Data->Count; 291 | return S_OK; 292 | } 293 | 294 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorMoveNext(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, boolean* Result) 295 | { 296 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Iterator); 297 | *Result = ++Data->Current < Data->Count; 298 | return S_OK; 299 | } 300 | 301 | static HRESULT STDMETHODCALLTYPE WindowsToast_IteratorGetMany(__FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRING* This, UINT32 ItemsLength, __FIKeyValuePair_2_HSTRING_HSTRING** Items, UINT32* Result) 302 | { 303 | return E_NOTIMPL; 304 | } 305 | 306 | static __FIIterator_1___FIKeyValuePair_2_HSTRING_HSTRINGVtbl WindowsToast__IteratorVtbl = 307 | { 308 | .QueryInterface = &WindowsToast_IteratorQueryInterface, 309 | .AddRef = &WindowsToast_IteratorAddRef, 310 | .Release = &WindowsToast_IteratorRelease, 311 | .GetIids = &WindowsToast_IteratorGetIids, 312 | .GetRuntimeClassName = &WindowsToast_IteratorGetRuntimeClassName, 313 | .GetTrustLevel = &WindowsToast_IteratorGetTrustLevel, 314 | .get_Current = &WindowsToast_IteratorGetCurrent, 315 | .get_HasCurrent = &WindowsToast_IteratorGetHasCurrent, 316 | .MoveNext = &WindowsToast_IteratorMoveNext, 317 | .GetMany = &WindowsToast_IteratorGetMany, 318 | }; 319 | 320 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairQueryInterface(__FIKeyValuePair_2_HSTRING_HSTRING* This, REFIID Riid, void** Object) 321 | { 322 | return E_NOTIMPL; 323 | } 324 | 325 | static ULONG STDMETHODCALLTYPE WindowsToast__PairAddRef(__FIKeyValuePair_2_HSTRING_HSTRING* This) 326 | { 327 | return 1; 328 | } 329 | 330 | static ULONG STDMETHODCALLTYPE WindowsToast__PairRelease(__FIKeyValuePair_2_HSTRING_HSTRING* This) 331 | { 332 | return 1; 333 | } 334 | 335 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairGetIids(__FIKeyValuePair_2_HSTRING_HSTRING* This, ULONG* IidCount, IID** Iids) 336 | { 337 | return E_NOTIMPL; 338 | } 339 | 340 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairGetRuntimeClassName(__FIKeyValuePair_2_HSTRING_HSTRING* This, HSTRING* ClassName) 341 | { 342 | return E_NOTIMPL; 343 | } 344 | 345 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairGetTrustLevel(__FIKeyValuePair_2_HSTRING_HSTRING* This, TrustLevel* TrustLevel) 346 | { 347 | return E_NOTIMPL; 348 | } 349 | 350 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairGetKey(__FIKeyValuePair_2_HSTRING_HSTRING* This, HSTRING* Result) 351 | { 352 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Pair); 353 | WindowsToastHSTRING* KeyString = &Data->KeyString; 354 | KeyString->Length = lstrlenW(Data->Pairs[Data->Current][0]); 355 | KeyString->Ptr = Data->Pairs[Data->Current][0]; 356 | *Result = (HSTRING)KeyString; 357 | return S_OK; 358 | } 359 | 360 | static HRESULT STDMETHODCALLTYPE WindowsToast__PairGetValue(__FIKeyValuePair_2_HSTRING_HSTRING* This, HSTRING* Result) 361 | { 362 | WindowsToast__Data* Data = CONTAINING_RECORD(This, WindowsToast__Data, Pair); 363 | WindowsToastHSTRING* ValueString = &Data->ValueString; 364 | ValueString->Length = lstrlenW(Data->Pairs[Data->Current][1]); 365 | ValueString->Ptr = Data->Pairs[Data->Current][1]; 366 | *Result = (HSTRING)ValueString; 367 | return S_OK; 368 | } 369 | 370 | static __FIKeyValuePair_2_HSTRING_HSTRINGVtbl WindowsToast__PairVtbl = 371 | { 372 | .QueryInterface = &WindowsToast__PairQueryInterface, 373 | .AddRef = &WindowsToast__PairAddRef, 374 | .Release = &WindowsToast__PairRelease, 375 | .GetIids = &WindowsToast__PairGetIids, 376 | .GetRuntimeClassName = &WindowsToast__PairGetRuntimeClassName, 377 | .GetTrustLevel = &WindowsToast__PairGetTrustLevel, 378 | .get_Key = &WindowsToast__PairGetKey, 379 | .get_Value = &WindowsToast__PairGetValue, 380 | }; 381 | 382 | static __x_ABI_CWindows_CUI_CNotifications_CINotificationData* WindowsToast__CreateData(WindowsToast* Toast, WindowsToast__Data* Data, LPCWSTR(*Pairs)[2], UINT32 Count) 383 | { 384 | Data->Iterable.lpVtbl = &WindowsToast__IterableVtbl; 385 | Data->Iterator.lpVtbl = &WindowsToast__IteratorVtbl; 386 | Data->Pair.lpVtbl = &WindowsToast__PairVtbl; 387 | Data->Pairs = Pairs; 388 | Data->Count = Count; 389 | Data->KeyString.Flags = 1; 390 | Data->ValueString.Flags = 1; 391 | 392 | __x_ABI_CWindows_CUI_CNotifications_CINotificationData* NotificationData; 393 | HR(__x_ABI_CWindows_CUI_CNotifications_CINotificationDataFactory_CreateNotificationDataWithValues(Toast->DataFactory, &Data->Iterable, &NotificationData)); 394 | return NotificationData; 395 | } 396 | 397 | static void WindowsToast_Init(WindowsToast* Toast, LPCWSTR AppName, LPCWSTR AppId) 398 | { 399 | // initialize Windows Runtime 400 | HR(RoInitialize(RO_INIT_MULTITHREADED)); 401 | HR(SetCurrentProcessExplicitAppUserModelID(AppId)); 402 | 403 | // create Toast objects 404 | { 405 | WindowsToastHSTRING AppIdString = { 1, lstrlenW(AppId), 0, 0, AppId }; 406 | 407 | HR(RoGetActivationFactory(WindowsToastCSTR("Windows.UI.Notifications.NotificationData"), &IID_INotificationDataFactory, &Toast->DataFactory)); 408 | HR(RoGetActivationFactory(WindowsToastCSTR("Windows.UI.Notifications.ToastNotification"), &IID_IToastNotificationFactory, &Toast->NotificationFactory)); 409 | HR(SUCCEEDED(RoGetActivationFactory(WindowsToastCSTR("Windows.UI.Notifications.ToastNotificationManager"), &IID_IToastNotificationManagerStatics, &Toast->ManagerStatics))); 410 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics_CreateToastNotifierWithId(Toast->ManagerStatics, (HSTRING)&AppIdString, &Toast->Notifier)); 411 | } 412 | 413 | // setup shortcut with proper properties 414 | // https://docs.microsoft.com/en-us/windows/win32/shell/enable-desktop-toast-with-appusermodelid 415 | { 416 | WCHAR LinkPath[MAX_PATH]; 417 | Assert(0 != GetEnvironmentVariableW(L"APPDATA", LinkPath, ARRAYSIZE(LinkPath))); 418 | PathAppendW(LinkPath, L"Microsoft\\Windows\\Start Menu\\Programs"); 419 | PathAppendW(LinkPath, AppName); 420 | StrCatW(LinkPath, L".lnk"); 421 | 422 | IShellLinkW* ShellLink; 423 | HR(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &ShellLink)); 424 | 425 | IPersistFile* PersistFile; 426 | HR(IShellLinkW_QueryInterface(ShellLink, &IID_IPersistFile, &PersistFile)); 427 | 428 | IPropertyStore* PropertyStore; 429 | HR(IShellLinkW_QueryInterface(ShellLink, &IID_IPropertyStore, &PropertyStore)); 430 | 431 | if (GetFileAttributesW(LinkPath) != INVALID_FILE_ATTRIBUTES) 432 | { 433 | HR(IPersistFile_Load(PersistFile, LinkPath, STGM_READWRITE)); 434 | 435 | PROPVARIANT Var; 436 | if (FAILED(IPropertyStore_GetValue(PropertyStore, &PKEY_AppUserModel_ID, &Var))) 437 | { 438 | Var.vt = VT_LPWSTR; 439 | Var.pwszVal = (LPWSTR)AppId; 440 | 441 | HR(IPropertyStore_SetValue(PropertyStore, &PKEY_AppUserModel_ID, &Var)); 442 | HR(IPropertyStore_Commit(PropertyStore)); 443 | if (IPersistFile_IsDirty(PersistFile) == S_OK) 444 | { 445 | HR(IPersistFile_Save(PersistFile, LinkPath, TRUE)); 446 | } 447 | } 448 | else 449 | { 450 | PropVariantClear(&Var); 451 | } 452 | } 453 | else 454 | { 455 | WCHAR ExePath[MAX_PATH]; 456 | GetModuleFileNameW(NULL, ExePath, ARRAYSIZE(ExePath)); 457 | HR(IShellLinkW_SetPath(ShellLink, ExePath)); 458 | 459 | HR(IShellLinkW_SetArguments(ShellLink, L"")); 460 | 461 | PathRemoveFileSpecW(ExePath); 462 | HR(IShellLinkW_SetWorkingDirectory(ShellLink, ExePath)); 463 | 464 | PROPVARIANT Var = 465 | { 466 | .vt = VT_LPWSTR, 467 | .pwszVal = (LPWSTR)AppId, 468 | }; 469 | 470 | HR(IPropertyStore_SetValue(PropertyStore, &PKEY_AppUserModel_ID, &Var)); 471 | HR(IPropertyStore_Commit(PropertyStore)); 472 | HR(IPersistFile_Save(PersistFile, LinkPath, TRUE)); 473 | } 474 | 475 | IPropertyStore_Release(PropertyStore); 476 | IPersistFile_Release(PersistFile); 477 | IShellLinkW_Release(ShellLink); 478 | } 479 | 480 | Toast->OnActivated.lpVtbl = &WindowsToast__OnActivatedVtbl; 481 | Toast->OnDismissed.lpVtbl = &WindowsToast__OnDismissedVtbl; 482 | } 483 | 484 | static void WindowsToast_HideAll(WindowsToast* Toast, LPCWSTR AppId) 485 | { 486 | WindowsToastHSTRING AppIdString = { 1, lstrlenW(AppId), 0, 0, AppId }; 487 | 488 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics2* ManagerStatics2; 489 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationHistory* NotificationHistory; 490 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics_QueryInterface(Toast->ManagerStatics, &IID_IToastNotificationManagerStatics2, &ManagerStatics2)); 491 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics2_get_History(ManagerStatics2, &NotificationHistory)); 492 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationHistory_ClearWithId(NotificationHistory, (HSTRING)&AppIdString)); 493 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationHistory_Release(NotificationHistory); 494 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics2_Release(ManagerStatics2); 495 | } 496 | 497 | static void WindowsToast_Done(WindowsToast* Toast) 498 | { 499 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier_Release(Toast->Notifier); 500 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics_Release(Toast->ManagerStatics); 501 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory_Release(Toast->NotificationFactory); 502 | __x_ABI_CWindows_CUI_CNotifications_CINotificationDataFactory_Release(Toast->DataFactory); 503 | 504 | RoUninitialize(); 505 | } 506 | 507 | static void WindowsToast_ShowSimple(WindowsToast* Toast, LPCWSTR Xml, int XmlLength) 508 | { 509 | void* Item = WindowsToast_Create(Toast, Xml, XmlLength, NULL, 0); 510 | WindowsToast_Show(Toast, Item); 511 | WindowsToast_Release(Toast, Item); 512 | } 513 | 514 | static void* WindowsToast_Create(WindowsToast* Toast, LPCWSTR Xml, int XmlLength, LPCWSTR (*Data)[2], UINT32 Count) 515 | { 516 | __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument* XmlDocument; 517 | HR(RoActivateInstance(WindowsToastCSTR("Windows.Data.Xml.Dom.XmlDocument"), (IInspectable**)&XmlDocument)); 518 | 519 | __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO* XmlIO; 520 | HR(__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument_QueryInterface(XmlDocument, &IID_IXmlDocumentIO, &XmlIO)); 521 | 522 | WindowsToastHSTRING XmlString = { 1, XmlLength >= 0 ? XmlLength : lstrlenW(Xml), 0, 0, Xml }; 523 | HR(__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO_LoadXml(XmlIO, (HSTRING)&XmlString)); 524 | 525 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Notification; 526 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory_CreateToastNotification(Toast->NotificationFactory, XmlDocument, &Notification)); 527 | 528 | EventRegistrationToken ActivatedToken; 529 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification_add_Activated(Notification, &Toast->OnActivated, &ActivatedToken)); 530 | 531 | EventRegistrationToken DismissedToken; 532 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification_add_Dismissed(Notification, &Toast->OnDismissed, &DismissedToken)); 533 | 534 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification2* Notification2; 535 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification_QueryInterface(Notification, &IID_IToastNotification2, &Notification2)); 536 | 537 | WCHAR Tag[16 + 1]; 538 | wsprintfW(Tag, L"%016I64x", (UINT64)Notification); 539 | WindowsToastHSTRING TagString = { 1, ARRAYSIZE(Tag) - 1, 0, 0, Tag }; 540 | 541 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification2_put_Tag(Notification2, (HSTRING)&TagString)); 542 | 543 | if (Count) 544 | { 545 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification4* Notification4; 546 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification_QueryInterface(Notification, &IID_IToastNotification4, &Notification4)); 547 | 548 | WindowsToast__Data ToastData; 549 | __x_ABI_CWindows_CUI_CNotifications_CINotificationData* NotificationData = WindowsToast__CreateData(Toast, &ToastData, Data, Count); 550 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotification4_put_Data(Notification4, NotificationData)); 551 | 552 | __x_ABI_CWindows_CUI_CNotifications_CINotificationData_Release(NotificationData); 553 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification4_Release(Notification4); 554 | } 555 | 556 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification2_Release(Notification2); 557 | __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO_Release(XmlIO); 558 | __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument_Release(XmlDocument); 559 | 560 | return Notification; 561 | } 562 | static void WindowsToast_Show(WindowsToast* Toast, void* Item) 563 | { 564 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Notification = Item; 565 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier_Show(Toast->Notifier, Notification)); 566 | } 567 | 568 | static void WindowsToast_Hide(WindowsToast* Toast, void* Item) 569 | { 570 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Notification = Item; 571 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier_Hide(Toast->Notifier, Notification)); 572 | } 573 | 574 | static BOOL WindowsToast_Update(WindowsToast* Toast, void* Item, LPCWSTR(*Data)[2], UINT32 Count) 575 | { 576 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier2* Notifier2; 577 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier_QueryInterface(Toast->Notifier, &IID_IToastNotifier2, &Notifier2)); 578 | 579 | WCHAR Tag[16 + 1]; 580 | wsprintfW(Tag, L"%016I64x", (UINT64)Item); 581 | WindowsToastHSTRING TagString = { 1, ARRAYSIZE(Tag) - 1, 0, 0, Tag }; 582 | 583 | WindowsToast__Data ToastData; 584 | __x_ABI_CWindows_CUI_CNotifications_CINotificationData* NotificationData = WindowsToast__CreateData(Toast, &ToastData, Data, Count); 585 | 586 | enum __x_ABI_CWindows_CUI_CNotifications_CNotificationUpdateResult Result; 587 | HR(__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier2_UpdateWithTag(Notifier2, NotificationData, (HSTRING)&TagString, &Result)); 588 | 589 | __x_ABI_CWindows_CUI_CNotifications_CINotificationData_Release(NotificationData); 590 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier2_Release(Notifier2); 591 | 592 | return Result == NotificationUpdateResult_Succeeded; 593 | } 594 | 595 | static void WindowsToast_Release(WindowsToast* Toast, void* Item) 596 | { 597 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* Notification = Item; 598 | __x_ABI_CWindows_CUI_CNotifications_CIToastNotification_Release(Notification); 599 | } -------------------------------------------------------------------------------- /src/imap_client.c: -------------------------------------------------------------------------------- 1 | static void 2 | imap_write_error(imap *Imap, imap_client_error ErrorType, char *Data) 3 | { 4 | int Length = 0; 5 | Length = sprintf(Imap->Error, "%s", ImapClientErrorStrings[ErrorType]); 6 | 7 | if (Data != NULL) 8 | { 9 | Length += sprintf(Imap->Error + Length, ": %s\n", Data); 10 | } 11 | else 12 | { 13 | Length += sprintf(Imap->Error + Length, "\n"); 14 | } 15 | 16 | Imap->ErrorLength = Length; 17 | } 18 | 19 | static void 20 | imap_write_parser_error(imap *Imap, imap_parser *Parser) 21 | { 22 | int Length = 0; 23 | Length = sprintf(Imap->Error, "%s\n", Parser->Error); 24 | 25 | Imap->ErrorLength = Length; 26 | } 27 | 28 | static BOOL 29 | imap_command(imap *Imap, BOOL IncrementCommandNumber, char *Format, ...) 30 | { 31 | char CommandBuffer[32768]; 32 | size_t CommandLength = 0; 33 | 34 | va_list ArgumentPointer; 35 | va_start(ArgumentPointer, Format); 36 | CommandLength = vsnprintf(CommandBuffer, 32768, Format, ArgumentPointer); 37 | va_end(ArgumentPointer); 38 | 39 | if (tls_write(&Imap->Socket, CommandBuffer, CommandLength) != 0) 40 | { 41 | printf("Failed!\n"); 42 | tls_disconnect(&Imap->Socket); 43 | return FALSE; 44 | } 45 | 46 | if (IncrementCommandNumber) 47 | { 48 | ++Imap->CommandNumber; 49 | } 50 | 51 | return TRUE; 52 | } 53 | 54 | static int 55 | imap_read_line(imap *Imap, char *Buffer, int BufferSize, BOOL ClearBuffer) 56 | { 57 | static char LocalBuffer[IMAP_LOCAL_READ_BUFFER_SIZE]; 58 | static int LocalBufferSize = 0; 59 | 60 | if (ClearBuffer) 61 | { 62 | LocalBufferSize = 0; 63 | memset(LocalBuffer, 0, IMAP_LOCAL_READ_BUFFER_SIZE); 64 | } 65 | 66 | char *FoundLine = NULL; 67 | int Received = 0; 68 | 69 | // NOTE(Oskar): If we got spare data from last read we copy it over. 70 | if (LocalBufferSize > 0) 71 | { 72 | // NOTE(Oskar): If localbuffer contains a full line we copy that 73 | char *HasLine = StrStrA(LocalBuffer, IMAP_END_OF_MESSAGE); 74 | if (HasLine != NULL) 75 | { 76 | int Position = HasLine - LocalBuffer; 77 | Position += 2; // \r\n 78 | 79 | int Bytes = Position; 80 | memmove(Buffer, LocalBuffer, Bytes); 81 | Buffer[Bytes] = 0; 82 | 83 | // NOTE(Oskar): Push the contents of the buffer back. 84 | memmove(LocalBuffer, LocalBuffer + Bytes, LocalBufferSize - Bytes); 85 | LocalBufferSize -= Bytes; 86 | 87 | return Bytes; 88 | } 89 | else 90 | { 91 | // NOTE(Oskar): Not a full line but we move whats in there. 92 | memmove(Buffer, LocalBuffer, LocalBufferSize); 93 | Buffer[LocalBufferSize] = 0; 94 | 95 | Received = LocalBufferSize; 96 | LocalBufferSize = 0; 97 | } 98 | } 99 | 100 | do 101 | { 102 | int Result = tls_read(&Imap->Socket, 103 | Buffer + Received, 104 | BufferSize); 105 | if (Result < 0) 106 | { 107 | printf("Error receiving data\n"); 108 | return -2; 109 | } 110 | else if (Result == 0) 111 | { 112 | printf("Socket disconnected\n"); 113 | return -1; 114 | } 115 | else 116 | { 117 | Received += Result; 118 | 119 | // NOTE(Oskar): Check for end of message 120 | FoundLine = StrStrA(Buffer, IMAP_END_OF_MESSAGE); 121 | if (FoundLine != NULL) 122 | { 123 | int Position = FoundLine - Buffer; 124 | Position += 2; // \r\n 125 | 126 | // NOTE(Oskar): If we got spare bytes we write it to our local store. 127 | if(Received > Position) 128 | { 129 | int BytesLeft = Received - Position; 130 | memmove(LocalBuffer, Buffer + Position, BytesLeft); 131 | LocalBufferSize = BytesLeft; 132 | 133 | Buffer[Position] = 0; 134 | } 135 | } 136 | } 137 | } while(FoundLine == NULL); 138 | 139 | return Received; 140 | } 141 | 142 | static imap_client_error 143 | imap_check_read_result(int ReadResult) 144 | { 145 | if (ReadResult > 0) 146 | { 147 | return IMAP_CLIENT_ERROR_SUCCESS; 148 | } 149 | else if (ReadResult == -1) 150 | { 151 | return IMAP_CLIENT_ERROR_SOCKET_DISCONNECTED; 152 | } 153 | else 154 | { 155 | return IMAP_CLIENT_ERROR_SOCKET_RECIEVE; 156 | } 157 | } 158 | 159 | static void 160 | imap_read_capabilities(imap *Imap, imap_response_data *Data, unsigned int DataCount) 161 | { 162 | for (unsigned int Index = 0; Index < DataCount; ++Index) 163 | { 164 | if (strcmp(Data[Index].Value, "IDLE") == 0) 165 | { 166 | Imap->HasIdle = TRUE; 167 | } 168 | } 169 | 170 | Imap->ParsedCapabilities = TRUE; 171 | } 172 | 173 | static imap_client_error 174 | imap_init(imap *Imap, char *HostName, int Port) 175 | { 176 | if (tls_connect(&Imap->Socket, HostName, Port) != 0) 177 | { 178 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_CONNECTION, NULL); 179 | return IMAP_CLIENT_ERROR_SOCKET_CONNECTION; 180 | } 181 | 182 | Imap->ParsedCapabilities = FALSE; 183 | Imap->HasIdle = FALSE; 184 | Imap->HasRecent = FALSE; 185 | Imap->CommandNumber = 1; 186 | 187 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 188 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, TRUE); 189 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 190 | 191 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 192 | { 193 | imap_write_error(Imap, ReadResult, NULL); 194 | return ReadResult; 195 | } 196 | 197 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 198 | imap_response Response = imap_parse_response(&Parser); 199 | 200 | if (Parser.HasError) 201 | { 202 | imap_write_parser_error(Imap, &Parser); 203 | return IMAP_CLIENT_ERROR_PARSE; 204 | } 205 | 206 | if (Response.Type == IMAP_RESPONSE_TYPE_STATUS) 207 | { 208 | if (Response.Status == IMAP_RESPONSE_STATUS_OK) 209 | { 210 | if (Response.Code == IMAP_RESPONSE_CODE_CAPABILITY) 211 | { 212 | imap_read_capabilities(Imap, Response.Data, Response.DataCount); 213 | } 214 | } 215 | else 216 | { 217 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 218 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 219 | } 220 | } 221 | else if (Response.Type == IMAP_RESPONSE_TYPE_DATA) 222 | { 223 | for (int Index = 0; Index < Response.DataCount; ++Index) 224 | { 225 | if (strcmp(Response.Data[Index].Value, "Gimap") == 0) 226 | { 227 | Imap->HasRecent = FALSE; 228 | } 229 | } 230 | } 231 | else 232 | { 233 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 234 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 235 | } 236 | 237 | return IMAP_CLIENT_ERROR_SUCCESS; 238 | } 239 | 240 | static imap_client_error 241 | imap_login(imap *Imap, char *Login, char *Password) 242 | { 243 | int CommandNumber = Imap->CommandNumber; 244 | char CommandTag[10]; 245 | sprintf(CommandTag, "A%03d", CommandNumber); 246 | 247 | if (!imap_command(Imap, TRUE, "%s %s %s %s\r\n", CommandTag, IMAP_COMMAND_LOGIN, Login, Password)) 248 | { 249 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_WRITE, NULL); 250 | return IMAP_CLIENT_ERROR_SOCKET_WRITE; 251 | } 252 | 253 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 254 | BOOL FoundTag = FALSE; 255 | BOOL ClearBuffer = TRUE; 256 | while (!FoundTag) 257 | { 258 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, ClearBuffer); 259 | ClearBuffer = FALSE; 260 | 261 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 262 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 263 | { 264 | imap_write_error(Imap, ReadResult, NULL); 265 | return ReadResult; 266 | } 267 | 268 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 269 | imap_response Response = imap_parse_response(&Parser); 270 | 271 | if (Parser.HasError) 272 | { 273 | imap_write_parser_error(Imap, &Parser); 274 | return IMAP_CLIENT_ERROR_PARSE; 275 | } 276 | 277 | if (Response.Type == IMAP_RESPONSE_TYPE_STATUS) 278 | { 279 | if (Response.Status == IMAP_RESPONSE_STATUS_OK) 280 | { 281 | if (Response.Code == IMAP_RESPONSE_CODE_CAPABILITY) 282 | { 283 | imap_read_capabilities(Imap, Response.Data, Response.DataCount); 284 | } 285 | 286 | if (strcmp(Response.Tag, CommandTag) == 0) 287 | { 288 | FoundTag = TRUE; 289 | } 290 | } 291 | else 292 | { 293 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 294 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 295 | } 296 | } 297 | else if (Response.Type == IMAP_RESPONSE_TYPE_DATA) 298 | { 299 | char *Command = Response.Data[0].Value; 300 | if (strcmp(Command, "CAPABILITY") == 0) 301 | { 302 | imap_read_capabilities(Imap, Response.Data, Response.DataCount); 303 | } 304 | } 305 | } 306 | 307 | return IMAP_CLIENT_ERROR_SUCCESS; 308 | } 309 | 310 | static imap_client_error 311 | imap_examine(imap *Imap, char *Folder) 312 | { 313 | int CommandNumber = Imap->CommandNumber; 314 | 315 | char CommandTag[10]; 316 | sprintf(CommandTag, "A%03d", CommandNumber); 317 | 318 | if (!imap_command(Imap, TRUE, "%s %s %s\r\n", CommandTag, IMAP_COMMAND_EXAMINE, Folder)) 319 | { 320 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_WRITE, NULL); 321 | return IMAP_CLIENT_ERROR_SOCKET_WRITE; 322 | } 323 | 324 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 325 | BOOL FoundTag = FALSE; 326 | BOOL ClearBuffer = TRUE; 327 | while (!FoundTag) 328 | { 329 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, ClearBuffer); 330 | ClearBuffer = FALSE; 331 | 332 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 333 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 334 | { 335 | imap_write_error(Imap, ReadResult, NULL); 336 | return ReadResult; 337 | } 338 | 339 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 340 | imap_response Response = imap_parse_response(&Parser); 341 | 342 | if (Parser.HasError) 343 | { 344 | imap_write_parser_error(Imap, &Parser); 345 | return IMAP_CLIENT_ERROR_PARSE; 346 | } 347 | 348 | // NOTE(Oskar): We are doing this request just to select the 349 | // mailbox. Ignore the data we get back except tagged OK. 350 | if (Response.Type == IMAP_RESPONSE_TYPE_STATUS) 351 | { 352 | if (Response.Status == IMAP_RESPONSE_STATUS_OK) 353 | { 354 | if (strcmp(Response.Tag, CommandTag) == 0) 355 | { 356 | FoundTag = TRUE; 357 | } 358 | } 359 | else 360 | { 361 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 362 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 363 | } 364 | } 365 | } 366 | 367 | return IMAP_CLIENT_ERROR_SUCCESS; 368 | } 369 | 370 | static imap_client_error 371 | imap_idle(imap *Imap) 372 | { 373 | int CommandNumber = Imap->CommandNumber; 374 | char CommandTag[10]; 375 | sprintf(CommandTag, "A%03d", CommandNumber); 376 | 377 | if (!imap_command(Imap, TRUE, "%s %s\r\n", CommandTag, IMAP_COMMAND_IDLE)) 378 | { 379 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_WRITE, NULL); 380 | return IMAP_CLIENT_ERROR_SOCKET_WRITE; 381 | } 382 | 383 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 384 | BOOL FoundTag = FALSE; 385 | BOOL ClearBuffer = TRUE; 386 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, ClearBuffer); 387 | 388 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 389 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 390 | { 391 | imap_write_error(Imap, ReadResult, NULL); 392 | return ReadResult; 393 | } 394 | 395 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 396 | imap_response Response = imap_parse_response(&Parser); 397 | 398 | if (Parser.HasError) 399 | { 400 | imap_write_parser_error(Imap, &Parser); 401 | return IMAP_CLIENT_ERROR_PARSE; 402 | } 403 | 404 | if (Response.Type != IMAP_RESPONSE_TYPE_CONTINUATION) 405 | { 406 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 407 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 408 | } 409 | 410 | return IMAP_CLIENT_ERROR_SUCCESS; 411 | } 412 | 413 | static imap_idle_response 414 | imap_idle_listen(imap *Imap) 415 | { 416 | imap_idle_response Result = {0}; 417 | Result.Error = IMAP_CLIENT_ERROR_SUCCESS; 418 | 419 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 420 | BOOL FoundTag = FALSE; 421 | BOOL ClearBuffer = TRUE; 422 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, ClearBuffer); 423 | 424 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 425 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 426 | { 427 | imap_write_error(Imap, ReadResult, NULL); 428 | Result.Error = ReadResult; 429 | return Result; 430 | } 431 | 432 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 433 | imap_response Response = imap_parse_response(&Parser); 434 | 435 | if (Parser.HasError) 436 | { 437 | imap_write_parser_error(Imap, &Parser); 438 | Result.Error = IMAP_CLIENT_ERROR_PARSE; 439 | return Result; 440 | } 441 | 442 | if (Response.Type == IMAP_RESPONSE_TYPE_DATA && 443 | Response.DataCount >= 2) 444 | { 445 | if (strstr(Response.Data[1].Value, "EXISTS")) 446 | { 447 | Result.Type = IMAP_IDLE_MESSAGE_EXISTS; 448 | } 449 | else if(strstr(Response.Data[1].Value, "EXPUNGE")) 450 | { 451 | Result.Type = IMAP_IDLE_MESSAGE_EXPUNGE; 452 | } 453 | else 454 | { 455 | Result.Type = IMAP_IDLE_MESSAGE_UNKNOWN; 456 | } 457 | } 458 | else 459 | { 460 | Result.Type = IMAP_IDLE_MESSAGE_UNKNOWN; 461 | } 462 | 463 | return Result; 464 | } 465 | 466 | static imap_client_error 467 | imap_done(imap *Imap) 468 | { 469 | if (!imap_command(Imap, FALSE, "%s\r\n", IMAP_COMMAND_DONE)) 470 | { 471 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_WRITE, NULL); 472 | return IMAP_CLIENT_ERROR_SOCKET_WRITE; 473 | } 474 | 475 | int LastCommandNumber = Imap->CommandNumber - 1; 476 | char CommandTag[10]; 477 | sprintf(CommandTag, "A%03d", LastCommandNumber); 478 | 479 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 480 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, TRUE); 481 | 482 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 483 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 484 | { 485 | imap_write_error(Imap, ReadResult, NULL); 486 | return ReadResult; 487 | } 488 | 489 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 490 | imap_response Response = imap_parse_response(&Parser); 491 | 492 | if (Parser.HasError) 493 | { 494 | imap_write_parser_error(Imap, &Parser); 495 | return IMAP_CLIENT_ERROR_PARSE; 496 | } 497 | 498 | if (Response.Type == IMAP_RESPONSE_TYPE_STATUS) 499 | { 500 | if (Response.Status == IMAP_RESPONSE_STATUS_OK) 501 | { 502 | if (strcmp(Response.Tag, CommandTag) == 0) 503 | { 504 | return IMAP_CLIENT_ERROR_SUCCESS; 505 | } 506 | } 507 | else 508 | { 509 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 510 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 511 | } 512 | } 513 | else 514 | { 515 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 516 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 517 | } 518 | 519 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 520 | return IMAP_CLIENT_ERROR_BAD_RESPONSE; 521 | } 522 | 523 | static imap_search_response 524 | imap_search(imap *Imap) 525 | { 526 | imap_search_response Result = {0}; 527 | Result.Error = IMAP_CLIENT_ERROR_SUCCESS; 528 | 529 | char *Keyword = 0; 530 | if (Imap->HasRecent) 531 | { 532 | Keyword = "RECENT"; 533 | } 534 | else 535 | { 536 | Keyword = "UNSEEN"; 537 | } 538 | 539 | int CommandNumber = Imap->CommandNumber; 540 | char CommandTag[10]; 541 | sprintf(CommandTag, "A%03d", CommandNumber); 542 | 543 | if (!imap_command(Imap, TRUE, "%s %s %s\r\n", CommandTag, IMAP_COMMAND_SEARCH, Keyword)) 544 | { 545 | imap_write_error(Imap, IMAP_CLIENT_ERROR_SOCKET_WRITE, NULL); 546 | return Result; 547 | } 548 | 549 | char Line[IMAP_DEFAULT_READ_BUFFER_SIZE] = {0}; 550 | BOOL FoundTag = FALSE; 551 | BOOL ClearBuffer = TRUE; 552 | while (!FoundTag) 553 | { 554 | int ReadLength = imap_read_line(Imap, Line, IMAP_DEFAULT_READ_BUFFER_SIZE, ClearBuffer); 555 | ClearBuffer = FALSE; 556 | 557 | imap_client_error ReadResult = imap_check_read_result(ReadLength); 558 | if (ReadResult != IMAP_CLIENT_ERROR_SUCCESS) 559 | { 560 | imap_write_error(Imap, ReadResult, NULL); 561 | Result.Error = ReadResult; 562 | return Result; 563 | } 564 | 565 | imap_parser Parser = imap_create_parser(Line, strlen(Line)); 566 | imap_response Response = imap_parse_response(&Parser); 567 | 568 | if (Parser.HasError) 569 | { 570 | imap_write_parser_error(Imap, &Parser); 571 | Result.Error = IMAP_CLIENT_ERROR_PARSE; 572 | return Result; 573 | } 574 | 575 | // NOTE(Oskar): We are doing this request just to select the 576 | // mailbox. Ignore the data we get back except tagged OK. 577 | if (Response.Type == IMAP_RESPONSE_TYPE_STATUS) 578 | { 579 | if (Response.Status == IMAP_RESPONSE_STATUS_OK) 580 | { 581 | if (strcmp(Response.Tag, CommandTag) == 0) 582 | { 583 | FoundTag = TRUE; 584 | } 585 | } 586 | else 587 | { 588 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 589 | Result.Error = IMAP_CLIENT_ERROR_BAD_RESPONSE; 590 | return Result; 591 | } 592 | } 593 | else if (Response.Type == IMAP_RESPONSE_TYPE_DATA) 594 | { 595 | if (strcmp(Response.Data[0].Value, "SEARCH") == 0) 596 | { 597 | Result.NumberOfMails = Response.DataCount - 1; 598 | Result.Error = IMAP_CLIENT_ERROR_SUCCESS; 599 | } 600 | else 601 | { 602 | imap_write_error(Imap, IMAP_CLIENT_ERROR_BAD_RESPONSE, Line); 603 | Result.Error = IMAP_CLIENT_ERROR_BAD_RESPONSE; 604 | return Result; 605 | } 606 | } 607 | } 608 | 609 | return Result; 610 | } 611 | 612 | #if 0 613 | static imap_response 614 | imap_fetch(imap *Imap, int *SequenceNumbers, int NumberOfNumbers) 615 | { 616 | // NOTE(Oskar): Build fetch query 617 | char Query[60000]; 618 | int QueryLength = 0; 619 | for (int Index = 0; Index < NumberOfNumbers; ++Index) 620 | { 621 | char *FormatString = "%d,"; 622 | if (Index == NumberOfNumbers -1) 623 | { 624 | FormatString = "%d"; 625 | } 626 | 627 | if (SequenceNumbers[Index] == -1) 628 | { 629 | continue; 630 | } 631 | 632 | QueryLength += sprintf(Query + QueryLength, FormatString, SequenceNumbers[Index]); 633 | } 634 | 635 | int CommandNumber = Imap->CommandNumber; 636 | char CommandBuffer[IMAP_DEFAULT_READ_BUFFER_SIZE]; 637 | int CommandLength = sprintf(CommandBuffer, "A%03d FETCH %s (internaldate flags body[header.fields (date from subject)])\r\n", CommandNumber, Query); 638 | if(_imap_command(Imap, CommandBuffer, CommandLength, 1) != 0) 639 | { 640 | printf("fetch failed, exiting!\n"); 641 | return FailedResponse(); 642 | } 643 | 644 | imap_response Response = {0}; 645 | Response.Type = IMAP_RESPONSE_TYPE_FETCH; 646 | 647 | Response.Emails = malloc(sizeof(imap_email_message) * NumberOfNumbers); 648 | Response.TotalNumberOfEmails = NumberOfNumbers; 649 | Response.ParsedEmails = 0; 650 | Response.ActiveParse = 0; 651 | 652 | for (int Index = 0; Index < NumberOfNumbers; ++Index) 653 | { 654 | imap_email_message *Email = &Response.Emails[Index]; 655 | Email->SequenceNumber = -1; 656 | Email->Error = 0; 657 | Email->Subject = malloc(sizeof(char) * 1024); 658 | Email->From = malloc(sizeof(char) * 1024); 659 | Email->Date = malloc(sizeof(char) * 1024); 660 | } 661 | 662 | imap_parse_fetch(Imap, &Response, CommandNumber); 663 | if (!Response.Success) 664 | { 665 | Response.Success = 0; 666 | return FailedResponse(); 667 | } 668 | 669 | while (Response.Success == IMAP_PARSER_ERR_NOT_DONE) 670 | { 671 | if(!imap_parse_fetch(Imap, &Response, CommandNumber) && Response.Success != IMAP_PARSER_ERR_NOT_DONE) 672 | { 673 | return FailedResponse(); 674 | } 675 | } 676 | 677 | if (!Response.Success) 678 | { 679 | return FailedResponse(); 680 | } 681 | 682 | return Response; 683 | } 684 | #endif 685 | 686 | static void 687 | imap_destroy(imap *Imap) 688 | { 689 | tls_disconnect(&Imap->Socket); 690 | } -------------------------------------------------------------------------------- /src/imap_client.h: -------------------------------------------------------------------------------- 1 | #define IMAP_LOCAL_READ_BUFFER_SIZE 65536 2 | #define IMAP_DEFAULT_ERROR_BUFFER_SIZE 65536 3 | #define IMAP_DEFAULT_READ_BUFFER_SIZE 32768 4 | #define IMAP_END_OF_MESSAGE "\r\n" 5 | 6 | #define IMAP_COMMAND_LOGIN "LOGIN" 7 | #define IMAP_COMMAND_EXAMINE "EXAMINE" 8 | #define IMAP_COMMAND_IDLE "IDLE" 9 | #define IMAP_COMMAND_DONE "DONE" 10 | #define IMAP_COMMAND_SEARCH "SEARCH" 11 | 12 | typedef enum 13 | { 14 | IMAP_CLIENT_ERROR_SUCCESS, // Ok Response 15 | IMAP_CLIENT_ERROR_SOCKET_CONNECTION, 16 | IMAP_CLIENT_ERROR_SOCKET_RECIEVE, 17 | IMAP_CLIENT_ERROR_SOCKET_WRITE, 18 | IMAP_CLIENT_ERROR_SOCKET_DISCONNECTED, 19 | IMAP_CLIENT_ERROR_PARSE, 20 | IMAP_CLIENT_ERROR_BAD_RESPONSE, 21 | 22 | IMAP_CLIENT_ERROR_COUNT, 23 | } imap_client_error; 24 | 25 | static char *ImapClientErrorStrings[IMAP_CLIENT_ERROR_COUNT] = 26 | { 27 | "", 28 | "Error: Failed to establish TLS connection with server.", 29 | "Error: Failed to recieve data from server.", 30 | "Error: Failed to write data to server.", 31 | "Error: Connection lost", 32 | "Error: Bad input when parsing response.", 33 | "Error: Client recieved bad response.", 34 | }; 35 | 36 | typedef enum 37 | { 38 | IMAP_RESPONSE_TYPE_UNKNOWN, 39 | IMAP_RESPONSE_TYPE_STATUS, 40 | IMAP_RESPONSE_TYPE_DATA, 41 | IMAP_RESPONSE_TYPE_CONTINUATION, 42 | } imap_response_type; 43 | 44 | typedef enum 45 | { 46 | IMAP_RESPONSE_STATUS_INVALID, 47 | IMAP_RESPONSE_STATUS_OK, 48 | IMAP_RESPONSE_STATUS_NO, 49 | IMAP_RESPONSE_STATUS_BAD, 50 | IMAP_RESPONSE_STATUS_PREAUTH, 51 | IMAP_RESPONSE_STATUS_BYE, 52 | } imap_response_status; 53 | 54 | typedef enum 55 | { 56 | IMAP_RESPONSE_CODE_INVALID, 57 | IMAP_RESPONSE_CODE_ALERT, 58 | IMAP_RESPONSE_CODE_BADCHARSET, 59 | IMAP_RESPONSE_CODE_CAPABILITY, 60 | IMAP_RESPONSE_CODE_PARSE, 61 | IMAP_RESPONSE_CODE_PERMANENTFLAGS, 62 | IMAP_RESPONSE_CODE_READ_ONLY, 63 | IMAP_RESPONSE_CODE_READ_WRITE, 64 | IMAP_RESPONSE_CODE_TRYCREATE, 65 | IMAP_RESPONSE_CODE_UIDNEXT, 66 | IMAP_RESPONSE_CODE_UIDVALIDITY, 67 | IMAP_RESPONSE_CODE_UNSEEN, 68 | } imap_response_code; 69 | 70 | typedef enum 71 | { 72 | IMAP_IDLE_MESSAGE_UNKNOWN, 73 | IMAP_IDLE_MESSAGE_EXISTS, 74 | IMAP_IDLE_MESSAGE_EXPUNGE, 75 | } imap_idle_response_type; 76 | 77 | typedef struct 78 | { 79 | imap_idle_response_type Type; 80 | imap_client_error Error; 81 | } imap_idle_response; 82 | 83 | typedef struct 84 | { 85 | int NumberOfMails; 86 | imap_client_error Error; 87 | } imap_search_response; 88 | 89 | typedef struct 90 | { 91 | char Value[256]; 92 | } imap_response_data; 93 | 94 | typedef struct 95 | { 96 | imap_response_type Type; 97 | 98 | char Tag[128]; 99 | 100 | imap_response_data Data[128]; 101 | unsigned int DataCount; 102 | 103 | imap_response_status Status; 104 | char *StatusCode; 105 | 106 | imap_response_code Code; 107 | 108 | char Info[256]; 109 | 110 | } imap_response; 111 | 112 | 113 | typedef struct 114 | { 115 | tls_socket Socket; 116 | 117 | BOOL ParsedCapabilities; 118 | BOOL HasIdle; 119 | BOOL HasRecent; 120 | 121 | int CommandNumber; 122 | 123 | int ErrorLength; 124 | char Error[IMAP_DEFAULT_ERROR_BUFFER_SIZE]; 125 | } imap; -------------------------------------------------------------------------------- /src/imap_parser.c: -------------------------------------------------------------------------------- 1 | static BOOL 2 | imap_parse_is_tag(tokenizer *Tokenizer) 3 | { 4 | token Token = PeekToken(Tokenizer); 5 | 6 | if (Token.Type == Token_OpenParen || 7 | Token.Type == Token_CloseParen || 8 | Token.Type == Token_CloseParen || 9 | Token.Type == Token_OpenBracket || 10 | Token.Type == Token_CloseBracket || 11 | Token.Type == Token_Space || 12 | Token.Type == Token_Percent || 13 | Token.Type == Token_OpenBrace || 14 | Token.Type == Token_CloseBrace) 15 | { 16 | return FALSE; 17 | } 18 | 19 | return TRUE; 20 | } 21 | 22 | static imap_response_status 23 | imap_parse_response_status(token Name) 24 | { 25 | if (strncmp(Name.Text, "OK", Name.TextLength) == 0) 26 | { 27 | return IMAP_RESPONSE_STATUS_OK; 28 | } 29 | else if (strncmp(Name.Text, "NO", Name.TextLength) == 0) 30 | { 31 | return IMAP_RESPONSE_STATUS_NO; 32 | } 33 | else if (strncmp(Name.Text, "BAD", Name.TextLength) == 0) 34 | { 35 | return IMAP_RESPONSE_STATUS_BAD; 36 | } 37 | else if (strncmp(Name.Text, "PREAUTH", Name.TextLength) == 0) 38 | { 39 | return IMAP_RESPONSE_STATUS_PREAUTH; 40 | } 41 | else if (strncmp(Name.Text, "BYE", Name.TextLength) == 0) 42 | { 43 | return IMAP_RESPONSE_STATUS_BYE; 44 | } 45 | else 46 | { 47 | return IMAP_RESPONSE_STATUS_INVALID; 48 | } 49 | } 50 | 51 | static BOOL 52 | imap_parse_response_list(tokenizer *Tokenizer, 53 | imap_response_data *Data, unsigned int *DataCount) 54 | { 55 | if (ExpectTokenType(Tokenizer, Token_OpenParen)) 56 | { 57 | // NOTE(Oskar): Missing open paren 58 | return FALSE; 59 | } 60 | 61 | if (!imap_parse_response_data(Tokenizer, Data, DataCount)) 62 | { 63 | // NOTE(Oskar): Invalid list input 64 | return FALSE; 65 | } 66 | 67 | if (ExpectTokenType(Tokenizer, Token_CloseParen)) 68 | { 69 | // NOTE(Oskar): Missing closing paren 70 | return FALSE; 71 | } 72 | 73 | return TRUE; 74 | } 75 | 76 | static BOOL 77 | imap_parse_response_data(tokenizer *Tokenizer, 78 | imap_response_data *Data, unsigned int *DataCount) 79 | { 80 | for (;;) 81 | { 82 | token Token = PeekToken(Tokenizer); 83 | 84 | BOOL Proceeed = TRUE; 85 | char Content[256]; 86 | switch (Token.Type) 87 | { 88 | case Token_OpenBracket: 89 | { 90 | // TODO(Oskar): Parse Literal, for mnotify we can ignore for now since we don't use fetch 91 | while (Token.Type != Token_CloseBracket) 92 | { 93 | if (Token.Type == Token_Unknown || 94 | Token.Type == Token_EndOfLine || 95 | Token.Type == Token_EndOfStream) 96 | { 97 | // Parsing error 98 | return FALSE; 99 | } 100 | Token = GetToken(Tokenizer); 101 | } 102 | } break; 103 | 104 | case Token_String: 105 | { 106 | sprintf(Content, "%.*s", Token.TextLength, Token.Text); 107 | } break; 108 | 109 | case Token_OpenParen: 110 | { 111 | if (!imap_parse_response_list(Tokenizer, Data, DataCount)) 112 | { 113 | // NOTE(Oskar): Failed to parse list. 114 | return FALSE; 115 | } 116 | 117 | Proceeed = FALSE; 118 | } break; 119 | case Token_CloseParen: 120 | { 121 | Proceeed = FALSE; 122 | } break; 123 | case Token_EndOfLine: 124 | { 125 | return TRUE; 126 | } break; 127 | default: 128 | { 129 | // NOTE(Oskar): Just default to reading an atom 130 | sprintf(Content, "%.*s", Token.TextLength, Token.Text); 131 | } break; 132 | } 133 | 134 | if (Proceeed) 135 | { 136 | sprintf(Data[(*DataCount)++].Value, "%s", Content); 137 | GetToken(Tokenizer); 138 | } 139 | 140 | // NOTE(Oskar): Check if next token is an indicator of us ending the fields parse. 141 | Token = PeekToken(Tokenizer); 142 | if (Token.Type == Token_Unknown || 143 | Token.Type == Token_EndOfLine || 144 | Token.Type == Token_EndOfStream || 145 | Token.Type == Token_CloseParen || 146 | Token.Type == Token_CloseBrace) 147 | { 148 | return TRUE; 149 | } 150 | 151 | if (ExpectTokenType(Tokenizer, Token_Space)) 152 | { 153 | // NOTE(Oskar): Missing space 154 | return FALSE; 155 | } 156 | } 157 | 158 | return FALSE; 159 | } 160 | 161 | static imap_response_code 162 | imap_parse_response_code(char *Name) 163 | { 164 | if (strcmp(Name, "ALERT") == 0) 165 | { 166 | return IMAP_RESPONSE_CODE_ALERT; 167 | } 168 | else if (strcmp(Name, "BADCHARSET") == 0) 169 | { 170 | return IMAP_RESPONSE_CODE_BADCHARSET; 171 | } 172 | else if (strcmp(Name, "CAPABILITY") == 0) 173 | { 174 | return IMAP_RESPONSE_CODE_CAPABILITY; 175 | } 176 | else if (strcmp(Name, "PARSE") == 0) 177 | { 178 | return IMAP_RESPONSE_CODE_PARSE; 179 | } 180 | else if (strcmp(Name, "PERMANENTFLAGS") == 0) 181 | { 182 | return IMAP_RESPONSE_CODE_PERMANENTFLAGS; 183 | } 184 | else if (strcmp(Name, "READ-ONLY") == 0) 185 | { 186 | return IMAP_RESPONSE_CODE_READ_ONLY; 187 | } 188 | else if (strcmp(Name, "READ-WRITE") == 0) 189 | { 190 | return IMAP_RESPONSE_CODE_READ_WRITE; 191 | } 192 | else if (strcmp(Name, "TRYCREATE") == 0) 193 | { 194 | return IMAP_RESPONSE_CODE_TRYCREATE; 195 | } 196 | else if (strcmp(Name, "UIDNEXT") == 0) 197 | { 198 | return IMAP_RESPONSE_CODE_UIDNEXT; 199 | } 200 | else if (strcmp(Name, "UIDVALIDITY") == 0) 201 | { 202 | return IMAP_RESPONSE_CODE_UIDVALIDITY; 203 | } 204 | else if (strcmp(Name, "UNSEEN") == 0) 205 | { 206 | return IMAP_RESPONSE_CODE_UNSEEN; 207 | } 208 | else 209 | { 210 | return IMAP_RESPONSE_CODE_INVALID; 211 | } 212 | } 213 | 214 | static BOOL 215 | imap_parse_response_code_and_data(tokenizer *Tokenizer, imap_response *Response) 216 | { 217 | if (ExpectTokenType(Tokenizer, Token_OpenBrace)) 218 | { 219 | // NOTE(Oskar): Missing opening brace 220 | return FALSE; 221 | } 222 | 223 | if (!imap_parse_response_data(Tokenizer, Response->Data, &Response->DataCount)) 224 | { 225 | printf("Error: Failed to parse data!\n"); 226 | return FALSE; 227 | } 228 | 229 | if (Response->DataCount == 0) 230 | { 231 | // Parse Error no data 232 | } 233 | 234 | // NOTE(Oskar): Use First field as Code. 235 | Response->Code = imap_parse_response_code(Response->Data[0].Value); 236 | 237 | // NOTE(Oskar): Pushing all fields back 238 | for (int Index = 0; Index < Response->DataCount - 1; ++Index) 239 | { 240 | Response->Data[Index] = Response->Data[Index + 1]; 241 | } 242 | Response->DataCount--; 243 | 244 | if (ExpectTokenType(Tokenizer, Token_CloseBrace)) 245 | { 246 | printf("Error Missing close brace!\n"); 247 | return FALSE; 248 | } 249 | 250 | return TRUE; 251 | } 252 | 253 | static BOOL 254 | imap_parse_response_info(tokenizer *Tokenizer, char *Out) 255 | { 256 | int OutLength = 0; 257 | token Token = GetToken(Tokenizer); 258 | while (Token.Type != Token_EndOfLine && 259 | Token.Type != Token_EndOfStream) 260 | { 261 | OutLength += sprintf(Out + OutLength, "%.*s", Token.TextLength, Token.Text); 262 | Token = GetToken(Tokenizer); 263 | } 264 | 265 | return TRUE; 266 | } 267 | 268 | static imap_response 269 | imap_parse_response(imap_parser *Parser) 270 | { 271 | imap_response Response = {0}; 272 | Response.Type = IMAP_RESPONSE_TYPE_UNKNOWN; 273 | Response.Tag[0] = 0; 274 | Response.Status = IMAP_RESPONSE_CODE_INVALID; 275 | Response.DataCount = 0; 276 | Response.StatusCode = 0; 277 | Response.Code = IMAP_RESPONSE_CODE_INVALID; 278 | Response.Info[0] = 0; 279 | 280 | for(int Index = 0; Index < ArrayCount(Response.Data); ++Index) 281 | { 282 | Response.Data[Index].Value[0] = 0; 283 | } 284 | 285 | 286 | if (!imap_parse_is_tag(&Parser->Tokenizer)) 287 | { 288 | Parser->HasError = TRUE; 289 | sprintf(Parser->Error, "%s", "Response tag is invalid."); 290 | 291 | return Response; 292 | } 293 | 294 | token Tag = GetToken(&Parser->Tokenizer); 295 | 296 | if (Tag.Type == Token_Plus) // Continuation response 297 | { 298 | Response.Type = IMAP_RESPONSE_TYPE_CONTINUATION; 299 | token Atom = GetToken(&Parser->Tokenizer); 300 | 301 | if (!imap_parse_response_info(&Parser->Tokenizer, Response.Info)) 302 | { 303 | Parser->HasError = TRUE; 304 | sprintf(Parser->Error, "%s", "Response contains invalid ending."); 305 | 306 | return Response; 307 | } 308 | 309 | return Response; 310 | } 311 | 312 | if (ExpectTokenType(&Parser->Tokenizer, Token_Space)) 313 | { 314 | Parser->HasError = TRUE; 315 | sprintf(Parser->Error, "%s", "Response contains invalid format of input."); 316 | 317 | return Response; 318 | } 319 | 320 | // NOTE(Oskar): Can be both data or status type. We first try with status. 321 | tokenizer StatusTokenizer = Parser->Tokenizer; 322 | token Atom = GetToken(&StatusTokenizer); 323 | if (!ExpectTokenType(&StatusTokenizer, Token_Space)) 324 | { 325 | imap_response_status Status = imap_parse_response_status(Atom); 326 | if (Status != IMAP_RESPONSE_STATUS_INVALID) 327 | { 328 | Response.Type = IMAP_RESPONSE_TYPE_STATUS; 329 | sprintf(Response.Tag, "%.*s", Tag.TextLength, Tag.Text); 330 | Response.Status = Status; 331 | 332 | token NextToken = PeekToken(&StatusTokenizer); 333 | if (NextToken.Type == Token_OpenBrace) 334 | { 335 | // NOTE(Oskar): Has code and arguments, Example 336 | // * OK [UNSEEN 12] 337 | if (!imap_parse_response_code_and_data(&StatusTokenizer, &Response)) 338 | { 339 | Parser->HasError = TRUE; 340 | sprintf(Parser->Error, "%s", "Response contains invalid format section input."); 341 | 342 | return Response; 343 | } 344 | } 345 | 346 | if (!imap_parse_response_info(&StatusTokenizer, Response.Info)) 347 | { 348 | Parser->HasError = TRUE; 349 | sprintf(Parser->Error, "%s", "Response contains invalid ending."); 350 | 351 | return Response; 352 | } 353 | 354 | return Response; 355 | } 356 | } 357 | 358 | // NOTE(Oskar): It is not a status response so we parse it as data. 359 | Response.Type = IMAP_RESPONSE_TYPE_DATA; 360 | if (!imap_parse_response_data(&Parser->Tokenizer, Response.Data, &Response.DataCount)) 361 | { 362 | Parser->HasError = TRUE; 363 | sprintf(Parser->Error, "%s", "Error parsing data section."); 364 | 365 | return Response; 366 | } 367 | 368 | return Response; 369 | } 370 | 371 | static imap_parser 372 | imap_create_parser(char *Buffer, unsigned int BufferLength) 373 | { 374 | imap_parser Parser = {0}; 375 | Parser.HasError = FALSE; 376 | Parser.Error[0] = '\0'; 377 | Parser.Tokenizer = Tokenize(Buffer, BufferLength); 378 | return Parser; 379 | } -------------------------------------------------------------------------------- /src/imap_parser.h: -------------------------------------------------------------------------------- 1 | typedef struct 2 | { 3 | BOOL HasError; 4 | char Error[1024]; 5 | tokenizer Tokenizer; 6 | } imap_parser; -------------------------------------------------------------------------------- /src/mnotify.c: -------------------------------------------------------------------------------- 1 | #define INITGUID 2 | #define COBJMACROS 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include 6 | #define SECURITY_WIN32 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #pragma comment (lib, "kernel32.lib") 20 | #pragma comment (lib, "user32.lib") 21 | #pragma comment (lib, "gdi32.lib") 22 | #pragma comment (lib, "shell32.lib") 23 | #pragma comment (lib, "ws2_32.lib") 24 | #pragma comment (lib, "secur32.lib") 25 | #pragma comment (lib, "shlwapi.lib") 26 | #pragma comment (lib, "uxtheme.lib") 27 | #pragma comment (lib, "winmm.lib") 28 | 29 | #include "tls.c" 30 | #include "mnotify.h" 31 | #include "tokenizer.c" 32 | #include "imap_parser.h" 33 | #include "imap_client.h" 34 | 35 | #define WM_MNOTIFY_ALREADY_RUNNING (WM_USER+1) 36 | #define WM_MNOTIFY_EMAIL_MESSAGE (WM_USER+2) 37 | #define WM_MNOTIFY_EMAIL_CLEAR (WM_USER+3) 38 | #define WM_MNOTIFY_COMMAND (WM_USER+4) 39 | #define WM_MNOTIFY_ERROR (WM_USER+5) 40 | 41 | #define MNOTIFY_TIMER_RECOVER 1 42 | 43 | #define CMD_MNOTIFY 1 44 | #define CMD_QUIT 2 45 | #define CMD_LOG 3 46 | 47 | #define MNOTIFY_WINDOW_CLASS_NAME L"mnotify_window_class" 48 | #define MNOTIFY_WINDOW_TITLE L"MNotify" 49 | #define MNOTIFY_CONFIG_FILE "./mnotify.ini" 50 | #define MNOTIFY_CONFIG_FILEW L"./mnotify.ini" 51 | #define MNOTIFY_LOG_FILE "log.txt" 52 | #define MNOTIFY_APPID L"MNotify.MNotify" // CompanyName.ProductName 53 | #define MNOTIFY_LOGO_FILE_NAME L"mnotify_logo.png" 54 | 55 | #define RESTART_IMAP_IDLE_TIMER_ID 1 56 | 57 | #define RESTART_IMAP_IDLE_INTERVAL 25 * 1000 * 60 // 25 Minutes 58 | 59 | #define HR(hr) do { HRESULT _hr = (hr); assert(SUCCEEDED(_hr)); } while (0) 60 | 61 | #include "imap_parser.c" 62 | #include "imap_client.c" 63 | #include "WindowsToast.h" 64 | 65 | // Globals 66 | static UINT WM_TASKBARCREATED; 67 | 68 | static HICON GlobalOpenIcon; 69 | static HICON GlobalClosedIcon; 70 | static HICON GlobalOpenWarningIcon; 71 | static HICON GlobalClosedWarningIcon; 72 | 73 | static HWND GlobalWindow; 74 | 75 | static WindowsToast Toast; 76 | static void* Notification; 77 | 78 | // NOTE(Oskar): IDLE needs to reset every 29 minutes to prevent server from kicking 79 | // us out so we store the currently used Imap Client globally so if the background thread 80 | // is blocked reading messages the main thread can close the connection forcing a reset. 81 | static imap GlobalImapIdleClient; 82 | static BOOL GlobalImapIdleClientWasReset; 83 | 84 | static mnotify_config GlobalConfiguration; 85 | static BOOL GlobalHasErrors; 86 | 87 | static unsigned int EmailCount; 88 | 89 | static void 90 | Win32FatalErrorMessage(char *Message) 91 | { 92 | MessageBox(NULL, Message, "Error", MB_OK); 93 | ExitProcess(-1); 94 | } 95 | 96 | static void 97 | Win32FatalErrorCode(LPSTR Message) 98 | { 99 | LPSTR ErrorMessage; 100 | DWORD LastError = GetLastError(); 101 | 102 | FormatMessageA( 103 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 104 | FORMAT_MESSAGE_FROM_SYSTEM | 105 | FORMAT_MESSAGE_IGNORE_INSERTS, 106 | NULL, 107 | LastError, 108 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 109 | (LPSTR)&ErrorMessage, 110 | 0, NULL ); 111 | 112 | int Characters = (lstrlen(ErrorMessage) + lstrlen(Message) + 40); 113 | LPTSTR MessageBuffer = (LPTSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Characters * sizeof(CHAR)); 114 | StringCchPrintfA(MessageBuffer, Characters, "%s Error failed with error code %d: %s", 115 | Message, LastError, ErrorMessage); 116 | 117 | MessageBox(NULL, MessageBuffer, "Error", MB_OK); 118 | 119 | LocalFree(ErrorMessage); 120 | HeapFree(GetProcessHeap(), 0, MessageBuffer); 121 | ExitProcess(LastError); 122 | } 123 | 124 | 125 | static BOOL 126 | FileExists(char *FilePath) 127 | { 128 | return GetFileAttributesA(FilePath) != INVALID_FILE_ATTRIBUTES; 129 | } 130 | 131 | static void 132 | CreateNewFile(char *FilePath) 133 | { 134 | HANDLE File = {0}; 135 | 136 | SECURITY_ATTRIBUTES SecurityAttributes = 137 | { 138 | .nLength = (DWORD)sizeof(SECURITY_ATTRIBUTES), 139 | .lpSecurityDescriptor = 0, 140 | .bInheritHandle = 0, 141 | }; 142 | 143 | if ((File = CreateFile(FilePath, 144 | GENERIC_READ | GENERIC_WRITE, 145 | 0, 146 | &SecurityAttributes, 147 | CREATE_ALWAYS, 148 | 0, 149 | 0)) != INVALID_HANDLE_VALUE) 150 | { 151 | CloseHandle(File); 152 | } 153 | else 154 | { 155 | Win32FatalErrorCode("Failed to create logfile."); 156 | } 157 | } 158 | 159 | static void 160 | AppendToFile(char *FilePath, void *Data, unsigned int DataLength) 161 | { 162 | HANDLE File = {0}; 163 | 164 | SECURITY_ATTRIBUTES SecurityAttributes = 165 | { 166 | .nLength = (DWORD)sizeof(SECURITY_ATTRIBUTES), 167 | .lpSecurityDescriptor = 0, 168 | .bInheritHandle = 0, 169 | }; 170 | 171 | if ((File = CreateFile(FilePath, 172 | FILE_APPEND_DATA, 173 | 0, 174 | &SecurityAttributes, 175 | OPEN_ALWAYS, 176 | 0, 177 | 0)) 178 | != INVALID_HANDLE_VALUE) 179 | { 180 | void *WriteData = Data; 181 | 182 | SetFilePointer(File, 0, 0, FILE_END); 183 | 184 | DWORD NumberOfBytesWritten = 0; 185 | WriteFile(File, WriteData, (DWORD)DataLength, &NumberOfBytesWritten, 0); 186 | 187 | CloseHandle(File); 188 | } 189 | else 190 | { 191 | Win32FatalErrorCode("Failed to write to logfile."); 192 | } 193 | } 194 | 195 | static void 196 | ShowNotification(LPCWSTR Message, LPCWSTR Title, LPCWSTR OpenLink) 197 | { 198 | static WCHAR ImagePath[MAX_PATH]; 199 | 200 | WCHAR ExeFolder[MAX_PATH]; 201 | GetModuleFileNameW(NULL, ExeFolder, ARRAYSIZE(ExeFolder)); 202 | PathRemoveFileSpecW(ExeFolder); 203 | wsprintfW(ImagePath, L"%s/%s", ExeFolder, MNOTIFY_LOGO_FILE_NAME); 204 | 205 | for (WCHAR* P = ImagePath; *P; P++) 206 | { 207 | if (*P == '\\') *P = '/'; 208 | } 209 | 210 | WCHAR Xml[2048]; 211 | int XmlLength = 0; 212 | 213 | XmlLength = StrCatChainW(Xml, ARRAYSIZE(Xml), XmlLength, 214 | L"" 215 | L"" 219 | L"{title}" 220 | L"{message}" 221 | L""); 222 | 223 | XmlLength = StrCatChainW(Xml, ARRAYSIZE(Xml), XmlLength, L"" 227 | L""); 228 | 229 | LPCWSTR Data[][2] = 230 | { 231 | { L"title", Title }, 232 | { L"message", Message }, 233 | }; 234 | Notification = WindowsToast_Create(&Toast, Xml, XmlLength, Data, ARRAYSIZE(Data)); 235 | WindowsToast_Show(&Toast, Notification); 236 | } 237 | 238 | static void 239 | AddTrayIcon(HWND Window) 240 | { 241 | NOTIFYICONDATAW Data = 242 | { 243 | .cbSize = sizeof(Data), 244 | .hWnd = Window, 245 | .uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP, 246 | .uCallbackMessage = WM_MNOTIFY_COMMAND, 247 | .hIcon = GlobalOpenIcon, 248 | }; 249 | StrCpyNW(Data.szTip, MNOTIFY_WINDOW_TITLE, _countof(Data.szTip)); 250 | Shell_NotifyIconW(NIM_ADD, &Data); 251 | } 252 | 253 | static void 254 | UpdateTrayIcon(HICON Icon) 255 | { 256 | NOTIFYICONDATAW Data = 257 | { 258 | .cbSize = sizeof(Data), 259 | .hWnd = GlobalWindow, 260 | .uFlags = NIF_ICON, 261 | .hIcon = Icon, 262 | }; 263 | Shell_NotifyIconW(NIM_MODIFY, &Data); 264 | } 265 | 266 | static void 267 | RemoveTrayIcon(HWND Window) 268 | { 269 | NOTIFYICONDATAW Data = 270 | { 271 | .cbSize = sizeof(Data), 272 | .hWnd = Window, 273 | }; 274 | Shell_NotifyIconW(NIM_DELETE, &Data); 275 | } 276 | 277 | static void 278 | ReadConfigurationFileString(char *Section, char *Key, char *Out, int OutLength) 279 | { 280 | GetPrivateProfileString(Section, Key, "", Out, OutLength, MNOTIFY_CONFIG_FILE); 281 | if (strcmp(Out, "") == 0) 282 | { 283 | Win32FatalErrorCode("Reading Configuration failed, check your filename and values."); 284 | } 285 | } 286 | 287 | static int 288 | ReadConfigurationFileInt(char *Section, char *Key) 289 | { 290 | int Result = GetPrivateProfileInt(Section, Key, -1, MNOTIFY_CONFIG_FILE); 291 | 292 | if (Result == -1) 293 | { 294 | Win32FatalErrorCode("Reading Configuration failed, check your filename and values."); 295 | } 296 | 297 | return Result; 298 | } 299 | 300 | static mnotify_config 301 | LoadConfiguration() 302 | { 303 | mnotify_config Config = {0}; 304 | 305 | ReadConfigurationFileString("Account", "host", Config.Host, 256); 306 | ReadConfigurationFileString("Account", "accountname", Config.Account, 256); 307 | ReadConfigurationFileString("Account", "password", Config.Password, 256); 308 | ReadConfigurationFileString("Account", "opensite", Config.OpenSite, 256); 309 | ReadConfigurationFileString("Account", "folder", Config.Folder, 256); 310 | Config.Port = ReadConfigurationFileInt("Account", "port"); 311 | Config.PollingTimeSeconds = ReadConfigurationFileInt("Account", "pollingtimer"); 312 | Config.RetryTime = ReadConfigurationFileInt("Account", "retrytime"); 313 | 314 | return Config; 315 | } 316 | 317 | static void 318 | ImapPerformPolling(char *Host, int Port, char *Account, char *Password) 319 | { 320 | for (;;) 321 | { 322 | imap Imap; 323 | if (imap_init(&Imap, Host, Port) != IMAP_CLIENT_ERROR_SUCCESS) 324 | { 325 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 326 | return; 327 | } 328 | 329 | if(imap_login(&Imap, Account, Password) != IMAP_CLIENT_ERROR_SUCCESS) 330 | { 331 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 332 | return; 333 | } 334 | 335 | if(imap_examine(&Imap, GlobalConfiguration.Folder) != IMAP_CLIENT_ERROR_SUCCESS) 336 | { 337 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 338 | goto polling_sleep; 339 | } 340 | 341 | // NOTE(Oskar): We resort to polling 342 | imap_search_response SearchResult = imap_search(&Imap); 343 | if (SearchResult.Error != IMAP_CLIENT_ERROR_SUCCESS) 344 | { 345 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 346 | goto polling_sleep; 347 | } 348 | 349 | // NOTE(Oskar): Find out which emails we want to get 350 | if (SearchResult.NumberOfMails != EmailCount) 351 | { 352 | // NOTE(Oskar): We only trigger a notification if stored number of 353 | // unread emails are lower than what exists in mailbox. 354 | if (EmailCount < SearchResult.NumberOfMails) 355 | { 356 | PostMessageW(GlobalWindow, WM_MNOTIFY_EMAIL_CLEAR, 0, 0); 357 | PostMessageW(GlobalWindow, WM_MNOTIFY_EMAIL_MESSAGE, 0, (LPARAM)SearchResult.NumberOfMails); 358 | } 359 | else 360 | { 361 | EmailCount = SearchResult.NumberOfMails; 362 | } 363 | } 364 | 365 | polling_sleep: ; 366 | Sleep(GlobalConfiguration.PollingTimeSeconds * 1000); 367 | } 368 | } 369 | 370 | void CALLBACK 371 | ImapPerformIdleTimerCallback(HWND Hwnd, UINT uMsg, UINT_PTR TimerId, DWORD DwTime) 372 | { 373 | KillTimer(GlobalWindow, RESTART_IMAP_IDLE_TIMER_ID); 374 | GlobalImapIdleClientWasReset = TRUE; 375 | imap_destroy(&GlobalImapIdleClient); 376 | } 377 | 378 | static void 379 | ImapPerformIdle(char *Host, int Port, char *Account, char *Password) 380 | { 381 | for (;;) 382 | { 383 | GlobalImapIdleClientWasReset = FALSE; 384 | 385 | if (imap_init(&GlobalImapIdleClient, Host, Port) != IMAP_CLIENT_ERROR_SUCCESS) 386 | { 387 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)GlobalImapIdleClient.Error, (LPARAM)GlobalImapIdleClient.ErrorLength); 388 | return; 389 | } 390 | 391 | if(imap_login(&GlobalImapIdleClient, Account, Password) != IMAP_CLIENT_ERROR_SUCCESS) 392 | { 393 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)GlobalImapIdleClient.Error, (LPARAM)GlobalImapIdleClient.ErrorLength); 394 | return; 395 | } 396 | 397 | if(imap_examine(&GlobalImapIdleClient, GlobalConfiguration.Folder) != IMAP_CLIENT_ERROR_SUCCESS) 398 | { 399 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)GlobalImapIdleClient.Error, (LPARAM)GlobalImapIdleClient.ErrorLength); 400 | return; 401 | } 402 | 403 | for (;;) 404 | { 405 | KillTimer(GlobalWindow, RESTART_IMAP_IDLE_TIMER_ID); 406 | SetTimer(GlobalWindow, RESTART_IMAP_IDLE_TIMER_ID, RESTART_IMAP_IDLE_INTERVAL, ImapPerformIdleTimerCallback); 407 | 408 | if(imap_idle(&GlobalImapIdleClient) != IMAP_CLIENT_ERROR_SUCCESS) 409 | { 410 | break; 411 | } 412 | 413 | imap_idle_response_type IdleResponseType = IMAP_IDLE_MESSAGE_UNKNOWN; 414 | while (IdleResponseType != IMAP_IDLE_MESSAGE_EXISTS) 415 | { 416 | imap_idle_response IdleResponse = imap_idle_listen(&GlobalImapIdleClient); 417 | 418 | if (IdleResponse.Error != IMAP_CLIENT_ERROR_SUCCESS) 419 | { 420 | break; 421 | } 422 | 423 | IdleResponseType = IdleResponse.Type; 424 | if (IdleResponseType == IMAP_IDLE_MESSAGE_EXPUNGE) 425 | { 426 | if (EmailCount > 0) 427 | { 428 | --EmailCount; 429 | } 430 | } 431 | } 432 | 433 | if (imap_done(&GlobalImapIdleClient) != IMAP_CLIENT_ERROR_SUCCESS) 434 | { 435 | break; 436 | } 437 | 438 | imap_search_response SearchResult = imap_search(&GlobalImapIdleClient); 439 | if (SearchResult.Error != IMAP_CLIENT_ERROR_SUCCESS) 440 | { 441 | break; 442 | } 443 | 444 | // NOTE(Oskar): Find out which emails we want to get 445 | if (SearchResult.NumberOfMails != EmailCount) 446 | { 447 | PostMessageW(GlobalWindow, WM_MNOTIFY_EMAIL_CLEAR, 0, 0); 448 | 449 | // TODO(Oskar): For later if we want to display the data somehow within the application. 450 | // The parsing is broken through so need to fix that first. It dies on longer requests as we don't 451 | // get the full imap message in one go from gmail and idk how to fix it. 452 | // imap_response FetchResponse = imap_fetch(&Imap, SearchResult.SequenceNumbers, SearchResult.NumberOfNumbers); 453 | // if (!FetchResponse.Success) 454 | // { 455 | // break; 456 | // } 457 | 458 | PostMessageW(GlobalWindow, WM_MNOTIFY_EMAIL_MESSAGE, 0, (LPARAM)SearchResult.NumberOfMails); 459 | } 460 | } 461 | 462 | // NOTE(Oskar): Check if it was us who reset the connection 463 | if (!GlobalImapIdleClientWasReset) 464 | { 465 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)GlobalImapIdleClient.Error, (LPARAM)GlobalImapIdleClient.ErrorLength); 466 | imap_destroy(&GlobalImapIdleClient); 467 | } 468 | } 469 | } 470 | 471 | DWORD WINAPI 472 | ImapBackgroundThread(LPVOID lpParameter) 473 | { 474 | // TODO(Oskar): How do we recover from init and login errors? Do we add 475 | // option for user to restart the thread? 476 | imap Imap; 477 | if (imap_init(&Imap, GlobalConfiguration.Host, GlobalConfiguration.Port) != IMAP_CLIENT_ERROR_SUCCESS) 478 | { 479 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 480 | goto thread_fatal; 481 | } 482 | 483 | if(imap_login(&Imap, GlobalConfiguration.Account, GlobalConfiguration.Password) != IMAP_CLIENT_ERROR_SUCCESS) 484 | { 485 | SendMessage(GlobalWindow, WM_MNOTIFY_ERROR, (WPARAM)Imap.Error, (LPARAM)Imap.ErrorLength); 486 | goto thread_fatal; 487 | } 488 | imap_destroy(&Imap); 489 | 490 | if (Imap.HasIdle) 491 | { 492 | ImapPerformIdle(GlobalConfiguration.Host, GlobalConfiguration.Port, GlobalConfiguration.Account, GlobalConfiguration.Password); 493 | } 494 | else 495 | { 496 | // NOTE(Oskar): No IDLE command support so we proceed with polling. 497 | ImapPerformPolling(GlobalConfiguration.Host, GlobalConfiguration.Port, GlobalConfiguration.Account, GlobalConfiguration.Password); 498 | } 499 | 500 | thread_fatal: ; 501 | SetTimer(GlobalWindow, MNOTIFY_TIMER_RECOVER, GlobalConfiguration.RetryTime * 1000, NULL); 502 | return -1; 503 | } 504 | 505 | static LRESULT CALLBACK 506 | WindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) 507 | { 508 | if (Message == WM_CREATE) 509 | { 510 | HR(BufferedPaintInit()); 511 | AddTrayIcon(Window); 512 | return 0; 513 | } 514 | else if (Message == WM_DESTROY) 515 | { 516 | RemoveTrayIcon(Window); 517 | KillTimer(Window, RESTART_IMAP_IDLE_TIMER_ID); 518 | PostQuitMessage(0); 519 | return 0; 520 | } 521 | else if (Message == WM_TIMER) 522 | { 523 | if (WParam == MNOTIFY_TIMER_RECOVER) 524 | { 525 | KillTimer(Window, MNOTIFY_TIMER_RECOVER); 526 | HANDLE Thread = CreateThread(0, 0, ImapBackgroundThread, 0, 0, NULL); 527 | CloseHandle(Thread); 528 | } 529 | } 530 | else if (Message == WM_MNOTIFY_EMAIL_MESSAGE) 531 | { 532 | int TotalEmails = (int)LParam; 533 | EmailCount = TotalEmails; 534 | 535 | if (EmailCount == 0) 536 | { 537 | GlobalHasErrors ? UpdateTrayIcon(GlobalOpenWarningIcon) : UpdateTrayIcon(GlobalOpenIcon); 538 | } 539 | else 540 | { 541 | GlobalHasErrors ? UpdateTrayIcon(GlobalClosedWarningIcon) : UpdateTrayIcon(GlobalClosedIcon); 542 | 543 | wchar_t Data[512]; 544 | swprintf(Data, 512, L"You have %d unread mail.", EmailCount); 545 | 546 | wchar_t Link[512]; 547 | swprintf(Link, 512, L"%hs", GlobalConfiguration.OpenSite); 548 | 549 | if (Notification != NULL) 550 | { 551 | WindowsToast_Release(&Toast, Notification); 552 | } 553 | 554 | ShowNotification(Data, L"You've got new mails!", Link); 555 | } 556 | 557 | return 0; 558 | } 559 | else if (Message == WM_MNOTIFY_EMAIL_CLEAR) 560 | { 561 | EmailCount = 0; 562 | } 563 | else if (Message == WM_MNOTIFY_COMMAND) 564 | { 565 | if (LOWORD(LParam) == WM_RBUTTONUP) 566 | { 567 | HMENU Menu = CreatePopupMenu(); 568 | assert(Menu); 569 | 570 | AppendMenuW(Menu, MF_STRING, CMD_MNOTIFY, MNOTIFY_WINDOW_TITLE); 571 | AppendMenuW(Menu, MF_SEPARATOR, 0, NULL); 572 | 573 | // TODO(Oskar): Future features? 574 | if (GlobalHasErrors) 575 | { 576 | AppendMenuW(Menu, MF_STRING, CMD_LOG, L"Show Log"); 577 | } 578 | 579 | AppendMenuW(Menu, MF_STRING, CMD_QUIT, L"Exit"); 580 | 581 | POINT Mouse; 582 | GetCursorPos(&Mouse); 583 | 584 | SetForegroundWindow(Window); 585 | int Command = TrackPopupMenu(Menu, TPM_RETURNCMD | TPM_NONOTIFY, Mouse.x, Mouse.y, 0, Window, NULL); 586 | if (Command == CMD_MNOTIFY) 587 | { 588 | ShellExecute(NULL, "open", GlobalConfiguration.OpenSite, NULL, NULL, SW_SHOWNORMAL); 589 | } 590 | else if (Command == CMD_QUIT) 591 | { 592 | DestroyWindow(Window); 593 | } 594 | else if (Command == CMD_LOG) 595 | { 596 | ShellExecute(NULL, "open", MNOTIFY_LOG_FILE, NULL, NULL, SW_SHOWNORMAL); 597 | 598 | // NOTE(Oskar): User has viewed errors so we reset the icon back 599 | GlobalHasErrors = FALSE; 600 | 601 | if (EmailCount == 0) 602 | { 603 | UpdateTrayIcon(GlobalOpenIcon); 604 | } 605 | else 606 | { 607 | UpdateTrayIcon(GlobalClosedIcon); 608 | } 609 | } 610 | 611 | DestroyMenu(Menu); 612 | } 613 | else if (LOWORD(LParam) == WM_LBUTTONDBLCLK) 614 | { 615 | } 616 | 617 | return 0; 618 | } 619 | else if (Message == WM_MNOTIFY_ERROR) 620 | { 621 | GlobalHasErrors = TRUE; 622 | 623 | char *Error = (char *)WParam; 624 | int ErrorLength = (int)LParam; 625 | AppendToFile(MNOTIFY_LOG_FILE, Error, ErrorLength); 626 | 627 | if (EmailCount == 0) 628 | { 629 | UpdateTrayIcon(GlobalOpenWarningIcon); 630 | } 631 | else 632 | { 633 | UpdateTrayIcon(GlobalClosedWarningIcon); 634 | } 635 | 636 | return 0; 637 | } 638 | 639 | return DefWindowProcW(Window, Message, WParam, LParam); 640 | } 641 | 642 | static void MNotifyToastCallback(WindowsToast* Toast, void* Item, LPCWSTR Action) 643 | { 644 | LPCWSTR Url = Action; 645 | ShellExecuteW(NULL, L"open", Url, NULL, NULL, SW_SHOWNORMAL); 646 | } 647 | 648 | int CALLBACK 649 | WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, int ShowCode) 650 | { 651 | WNDCLASSEXW WindowClass = 652 | { 653 | .cbSize = sizeof(WindowClass), 654 | .lpfnWndProc = WindowProc, 655 | .hInstance = GetModuleHandleW(NULL), 656 | .lpszClassName = L"mnotify_window_class", 657 | }; 658 | 659 | // NOTE(Oskar): Check if running 660 | HWND MNotifyWindow = FindWindowW(WindowClass.lpszClassName, NULL); 661 | if (MNotifyWindow) 662 | { 663 | PostMessageW(MNotifyWindow, WM_MNOTIFY_ALREADY_RUNNING, 0, 0); 664 | ExitProcess(0); 665 | } 666 | 667 | WindowsToast_Init(&Toast, MNOTIFY_WINDOW_TITLE, MNOTIFY_APPID); 668 | WindowsToast_HideAll(&Toast, MNOTIFY_APPID); 669 | Toast.OnActivatedCallback = MNotifyToastCallback; 670 | 671 | WM_TASKBARCREATED = RegisterWindowMessageW(L"TaskbarCreated"); 672 | assert(WM_TASKBARCREATED); 673 | 674 | GlobalOpenIcon = LoadIconW(WindowClass.hInstance, MAKEINTRESOURCEW(1)); 675 | GlobalClosedIcon = LoadIconW(WindowClass.hInstance, MAKEINTRESOURCEW(2)); 676 | GlobalOpenWarningIcon = LoadIconW(WindowClass.hInstance, MAKEINTRESOURCEW(3)); 677 | GlobalClosedWarningIcon = LoadIconW(WindowClass.hInstance, MAKEINTRESOURCEW(4)); 678 | assert(GlobalOpenIcon && GlobalClosedIcon && GlobalOpenWarningIcon && GlobalClosedWarningIcon); 679 | 680 | // NOTE(Oskar): Load configuration 681 | GlobalConfiguration = LoadConfiguration(); 682 | 683 | // NOTE(Oskar): Prepare Logfile 684 | if (FileExists(MNOTIFY_LOG_FILE)) 685 | { 686 | DeleteFileA(MNOTIFY_LOG_FILE); 687 | } 688 | CreateNewFile(MNOTIFY_LOG_FILE); 689 | GlobalHasErrors = FALSE; 690 | 691 | 692 | // NOTE(Oskar): Window Creation 693 | ATOM Atom = RegisterClassExW(&WindowClass); 694 | assert(Atom); 695 | 696 | GlobalWindow = CreateWindowExW( 697 | 0, WindowClass.lpszClassName, MNOTIFY_WINDOW_TITLE, WS_POPUP, 698 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 699 | NULL, NULL, WindowClass.hInstance, NULL); 700 | if (!GlobalWindow) 701 | { 702 | ExitProcess(0); 703 | } 704 | 705 | // TODO(Oskar): Later create 1 per email account? 706 | HANDLE Thread = CreateThread(0, 0, ImapBackgroundThread, 0, 0, NULL); 707 | CloseHandle(Thread); 708 | 709 | EmailCount = 0; 710 | for (;;) 711 | { 712 | MSG Message; 713 | BOOL Result = GetMessageW(&Message, NULL, 0, 0); 714 | if (Result == 0) 715 | { 716 | ExitProcess(0); 717 | } 718 | assert(Result > 0); 719 | 720 | TranslateMessage(&Message); 721 | DispatchMessageW(&Message); 722 | } 723 | } -------------------------------------------------------------------------------- /src/mnotify.h: -------------------------------------------------------------------------------- 1 | typedef struct 2 | { 3 | char Host[256]; 4 | char Account[256]; 5 | char Password[256]; 6 | char OpenSite[256]; 7 | char Folder[256]; 8 | int Port; 9 | int PollingTimeSeconds; 10 | int RetryTime; 11 | } mnotify_config; 12 | 13 | 14 | #define ArrayCount(Array) sizeof(Array) / sizeof(Array[0]) -------------------------------------------------------------------------------- /src/mnotify.rc: -------------------------------------------------------------------------------- 1 | 1 ICON "mnotify_open.ico" 2 | 2 ICON "mnotify_closed.ico" 3 | 3 ICON "mnotify_open_warning.ico" 4 | 4 ICON "mnotify_closed_warning.ico" -------------------------------------------------------------------------------- /src/mnotify_closed.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/mnotify/72a795270134ac084a117c55fd52f3c56f1fecd1/src/mnotify_closed.ico -------------------------------------------------------------------------------- /src/mnotify_closed_warning.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/mnotify/72a795270134ac084a117c55fd52f3c56f1fecd1/src/mnotify_closed_warning.ico -------------------------------------------------------------------------------- /src/mnotify_open.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/mnotify/72a795270134ac084a117c55fd52f3c56f1fecd1/src/mnotify_open.ico -------------------------------------------------------------------------------- /src/mnotify_open_warning.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenprogrammer/mnotify/72a795270134ac084a117c55fd52f3c56f1fecd1/src/mnotify_open_warning.ico -------------------------------------------------------------------------------- /src/tls.c: -------------------------------------------------------------------------------- 1 | #define TLS_MAX_PACKET_SIZE (16384+512) // payload + extra over head for header/mac/padding (probably an overestimate) 2 | 3 | typedef struct { 4 | SOCKET sock; 5 | CredHandle handle; 6 | CtxtHandle context; 7 | SecPkgContext_StreamSizes sizes; 8 | int received; // byte count in incoming buffer (ciphertext) 9 | int used; // byte count used from incoming buffer to decrypt current packet 10 | int available; // byte count available for decrypted bytes 11 | char* decrypted; // points to incoming buffer where data is decrypted inplace 12 | char incoming[TLS_MAX_PACKET_SIZE]; 13 | } tls_socket; 14 | 15 | // returns 0 on success or negative value on error 16 | static int tls_connect(tls_socket* s, const char* hostname, unsigned short port) 17 | { 18 | // initialize windows sockets 19 | WSADATA wsadata; 20 | if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) 21 | { 22 | return -1; 23 | } 24 | 25 | // create TCP IPv4 socket 26 | s->sock = socket(AF_INET, SOCK_STREAM, 0); 27 | if (s->sock == INVALID_SOCKET) 28 | { 29 | WSACleanup(); 30 | return -1; 31 | } 32 | 33 | char sport[64]; 34 | wnsprintfA(sport, sizeof(sport), "%u", port); 35 | 36 | // connect to server 37 | if (!WSAConnectByNameA(s->sock, hostname, sport, NULL, NULL, NULL, NULL, NULL, NULL)) 38 | { 39 | closesocket(s->sock); 40 | WSACleanup(); 41 | return -1; 42 | } 43 | 44 | // initialize schannel 45 | { 46 | SCHANNEL_CRED cred = 47 | { 48 | .dwVersion = SCHANNEL_CRED_VERSION, 49 | .dwFlags = SCH_USE_STRONG_CRYPTO // use only strong crypto alogorithms 50 | | SCH_CRED_AUTO_CRED_VALIDATION // automatically validate server certificate 51 | | SCH_CRED_NO_DEFAULT_CREDS, // no client certificate authentication 52 | .grbitEnabledProtocols = SP_PROT_TLS1_2, // allow only TLS v1.2 53 | }; 54 | 55 | if (AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, &s->handle, NULL) != SEC_E_OK) 56 | { 57 | closesocket(s->sock); 58 | WSACleanup(); 59 | return -1; 60 | } 61 | } 62 | 63 | s->received = s->used = s->available = 0; 64 | s->decrypted = NULL; 65 | 66 | // perform tls handshake 67 | // 1) call InitializeSecurityContext to create/update schannel context 68 | // 2) when it returns SEC_E_OK - tls handshake completed 69 | // 3) when it returns SEC_I_INCOMPLETE_CREDENTIALS - server requests client certificate (not supported here) 70 | // 4) when it returns SEC_I_CONTINUE_NEEDED - send token to server and read data 71 | // 5) when it returns SEC_E_INCOMPLETE_MESSAGE - need to read more data from server 72 | // 6) otherwise read data from server and go to step 1 73 | 74 | int wants_client_cert = 0; 75 | 76 | CtxtHandle* context = NULL; 77 | int result = 0; 78 | for (;;) 79 | { 80 | SecBuffer inbuffers[2] = { 0 }; 81 | inbuffers[0].BufferType = SECBUFFER_TOKEN; 82 | inbuffers[0].pvBuffer = s->incoming; 83 | inbuffers[0].cbBuffer = s->received; 84 | inbuffers[1].BufferType = SECBUFFER_EMPTY; 85 | 86 | SecBuffer outbuffers[1] = { 0 }; 87 | outbuffers[0].BufferType = SECBUFFER_TOKEN; 88 | 89 | SecBufferDesc indesc = { SECBUFFER_VERSION, ARRAYSIZE(inbuffers), inbuffers }; 90 | SecBufferDesc outdesc = { SECBUFFER_VERSION, ARRAYSIZE(outbuffers), outbuffers }; 91 | 92 | DWORD flags = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM; 93 | SECURITY_STATUS sec = InitializeSecurityContextA( 94 | &s->handle, 95 | context, 96 | context ? NULL : (SEC_CHAR*)hostname, 97 | flags, 98 | 0, 99 | 0, 100 | context ? &indesc : NULL, 101 | 0, 102 | context ? NULL : &s->context, 103 | &outdesc, 104 | &flags, 105 | NULL); 106 | 107 | // after first call to InitializeSecurityContext context is available and should be reused for next calls 108 | context = &s->context; 109 | 110 | if (inbuffers[1].BufferType == SECBUFFER_EXTRA) 111 | { 112 | MoveMemory(s->incoming, s->incoming + (s->received - inbuffers[1].cbBuffer), inbuffers[1].cbBuffer); 113 | s->received = inbuffers[1].cbBuffer; 114 | } 115 | else 116 | { 117 | s->received = 0; 118 | } 119 | 120 | if (sec == SEC_E_OK) 121 | { 122 | // tls handshake completed 123 | break; 124 | } 125 | else if (sec == SEC_I_INCOMPLETE_CREDENTIALS) 126 | { 127 | // server asked for client certificate, not supported here 128 | if (wants_client_cert == 0) 129 | { 130 | // we retry handshake, it will reply with 0 client certificates 131 | wants_client_cert = 1; 132 | continue; 133 | } 134 | // otherwise server really wants to have have client certificates, cannot continue 135 | result = -1; 136 | break; 137 | } 138 | else if (sec == SEC_I_CONTINUE_NEEDED) 139 | { 140 | // need to send data to server 141 | char* buffer = outbuffers[0].pvBuffer; 142 | int size = outbuffers[0].cbBuffer; 143 | 144 | while (size != 0) 145 | { 146 | int d = send(s->sock, buffer, size, 0); 147 | if (d <= 0) 148 | { 149 | break; 150 | } 151 | size -= d; 152 | buffer += d; 153 | } 154 | FreeContextBuffer(outbuffers[0].pvBuffer); 155 | if (size != 0) 156 | { 157 | // failed to fully send data to server 158 | result = -1; 159 | break; 160 | } 161 | } 162 | else if (sec != SEC_E_INCOMPLETE_MESSAGE) 163 | { 164 | // SEC_E_CERT_EXPIRED - certificate expired or revoked 165 | // SEC_E_WRONG_PRINCIPAL - bad hostname 166 | // SEC_E_UNTRUSTED_ROOT - cannot vertify CA chain 167 | // SEC_E_ILLEGAL_MESSAGE / SEC_E_ALGORITHM_MISMATCH - cannot negotiate crypto algorithms 168 | result = -1; 169 | break; 170 | } 171 | 172 | // read more data from server when possible 173 | if (s->received == sizeof(s->incoming)) 174 | { 175 | // server is sending too much data instead of proper handshake? 176 | result = -1; 177 | break; 178 | } 179 | 180 | int r = recv(s->sock, s->incoming + s->received, sizeof(s->incoming) - s->received, 0); 181 | if (r == 0) 182 | { 183 | // server disconnected socket 184 | return 0; 185 | } 186 | else if (r < 0) 187 | { 188 | // socket error 189 | result = -1; 190 | break; 191 | } 192 | s->received += r; 193 | } 194 | 195 | if (result != 0) 196 | { 197 | DeleteSecurityContext(context); 198 | FreeCredentialsHandle(&s->handle); 199 | closesocket(s->sock); 200 | WSACleanup(); 201 | return result; 202 | } 203 | 204 | QueryContextAttributes(context, SECPKG_ATTR_STREAM_SIZES, &s->sizes); 205 | return 0; 206 | } 207 | 208 | // disconnects socket & releases resources (call this even if tls_write/tls_read function return error) 209 | static void tls_disconnect(tls_socket* s) 210 | { 211 | DWORD type = SCHANNEL_SHUTDOWN; 212 | 213 | SecBuffer inbuffers[1]; 214 | inbuffers[0].BufferType = SECBUFFER_TOKEN; 215 | inbuffers[0].pvBuffer = &type; 216 | inbuffers[0].cbBuffer = sizeof(type); 217 | 218 | SecBufferDesc indesc = { SECBUFFER_VERSION, ARRAYSIZE(inbuffers), inbuffers }; 219 | ApplyControlToken(&s->context, &indesc); 220 | 221 | SecBuffer outbuffers[1]; 222 | outbuffers[0].BufferType = SECBUFFER_TOKEN; 223 | 224 | SecBufferDesc outdesc = { SECBUFFER_VERSION, ARRAYSIZE(outbuffers), outbuffers }; 225 | DWORD flags = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM; 226 | if (InitializeSecurityContextA(&s->handle, &s->context, NULL, flags, 0, 0, &outdesc, 0, NULL, &outdesc, &flags, NULL) == SEC_E_OK) 227 | { 228 | char* buffer = outbuffers[0].pvBuffer; 229 | int size = outbuffers[0].cbBuffer; 230 | while (size != 0) 231 | { 232 | int d = send(s->sock, buffer, size, 0); 233 | if (d <= 0) 234 | { 235 | // ignore any failures socket will be closed anyway 236 | break; 237 | } 238 | buffer += d; 239 | size -= d; 240 | } 241 | FreeContextBuffer(outbuffers[0].pvBuffer); 242 | } 243 | shutdown(s->sock, SD_BOTH); 244 | 245 | DeleteSecurityContext(&s->context); 246 | FreeCredentialsHandle(&s->handle); 247 | closesocket(s->sock); 248 | WSACleanup(); 249 | } 250 | 251 | // returns 0 on success or negative value on error 252 | static int tls_write(tls_socket* s, const void* buffer, int size) 253 | { 254 | while (size != 0) 255 | { 256 | int use = min(size, s->sizes.cbMaximumMessage); 257 | 258 | char wbuffer[TLS_MAX_PACKET_SIZE]; 259 | assert(s->sizes.cbHeader + s->sizes.cbMaximumMessage + s->sizes.cbTrailer <= sizeof(wbuffer)); 260 | 261 | SecBuffer buffers[3]; 262 | buffers[0].BufferType = SECBUFFER_STREAM_HEADER; 263 | buffers[0].pvBuffer = wbuffer; 264 | buffers[0].cbBuffer = s->sizes.cbHeader; 265 | buffers[1].BufferType = SECBUFFER_DATA; 266 | buffers[1].pvBuffer = wbuffer + s->sizes.cbHeader; 267 | buffers[1].cbBuffer = use; 268 | buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; 269 | buffers[2].pvBuffer = wbuffer + s->sizes.cbHeader + use; 270 | buffers[2].cbBuffer = s->sizes.cbTrailer; 271 | 272 | CopyMemory(buffers[1].pvBuffer, buffer, use); 273 | 274 | SecBufferDesc desc = { SECBUFFER_VERSION, ARRAYSIZE(buffers), buffers }; 275 | SECURITY_STATUS sec = EncryptMessage(&s->context, 0, &desc, 0); 276 | if (sec != SEC_E_OK) 277 | { 278 | // this should not happen, but just in case check it 279 | return -1; 280 | } 281 | 282 | int total = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; 283 | int sent = 0; 284 | while (sent != total) 285 | { 286 | int d = send(s->sock, wbuffer + sent, total - sent, 0); 287 | if (d <= 0) 288 | { 289 | // error sending data to socket, or server disconnected 290 | return -1; 291 | } 292 | sent += d; 293 | } 294 | 295 | buffer = (char*)buffer + use; 296 | size -= use; 297 | } 298 | 299 | return 0; 300 | } 301 | 302 | // blocking read, waits & reads up to size bytes, returns amount of bytes received on success (<= size) 303 | // returns 0 on disconnect or negative value on error 304 | static int tls_read(tls_socket* s, void* buffer, int size) 305 | { 306 | int result = 0; 307 | 308 | while (size != 0) 309 | { 310 | if (s->decrypted) 311 | { 312 | // if there is decrypted data available, then use it as much as possible 313 | int use = min(size, s->available); 314 | CopyMemory(buffer, s->decrypted, use); 315 | buffer = (char*)buffer + use; 316 | size -= use; 317 | result += use; 318 | 319 | if (use == s->available) 320 | { 321 | // all decrypted data is used, remove ciphertext from incoming buffer so next time it starts from beginning 322 | MoveMemory(s->incoming, s->incoming + s->used, s->received - s->used); 323 | s->received -= s->used; 324 | s->used = 0; 325 | s->available = 0; 326 | s->decrypted = NULL; 327 | } 328 | else 329 | { 330 | s->available -= use; 331 | s->decrypted += use; 332 | } 333 | } 334 | else 335 | { 336 | // if any ciphertext data available then try to decrypt it 337 | if (s->received != 0) 338 | { 339 | SecBuffer buffers[4]; 340 | assert(s->sizes.cBuffers == ARRAYSIZE(buffers)); 341 | 342 | buffers[0].BufferType = SECBUFFER_DATA; 343 | buffers[0].pvBuffer = s->incoming; 344 | buffers[0].cbBuffer = s->received; 345 | buffers[1].BufferType = SECBUFFER_EMPTY; 346 | buffers[2].BufferType = SECBUFFER_EMPTY; 347 | buffers[3].BufferType = SECBUFFER_EMPTY; 348 | 349 | SecBufferDesc desc = { SECBUFFER_VERSION, ARRAYSIZE(buffers), buffers }; 350 | 351 | SECURITY_STATUS sec = DecryptMessage(&s->context, &desc, 0, NULL); 352 | if (sec == SEC_E_OK) 353 | { 354 | assert(buffers[0].BufferType == SECBUFFER_STREAM_HEADER); 355 | assert(buffers[1].BufferType == SECBUFFER_DATA); 356 | assert(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER); 357 | 358 | s->decrypted = buffers[1].pvBuffer; 359 | s->available = buffers[1].cbBuffer; 360 | s->used = s->received - (buffers[3].BufferType == SECBUFFER_EXTRA ? buffers[3].cbBuffer : 0); 361 | 362 | // data is now decrypted, go back to beginning of loop to copy memory to output buffer 363 | continue; 364 | } 365 | else if (sec == SEC_I_CONTEXT_EXPIRED) 366 | { 367 | // server closed TLS connection (but socket is still open) 368 | s->received = 0; 369 | return result; 370 | } 371 | else if (sec == SEC_I_RENEGOTIATE) 372 | { 373 | // server wants to renegotiate TLS connection, not implemented here 374 | return -1; 375 | } 376 | else if (sec != SEC_E_INCOMPLETE_MESSAGE) 377 | { 378 | // some other schannel or TLS protocol error 379 | return -1; 380 | } 381 | // otherwise sec == SEC_E_INCOMPLETE_MESSAGE which means need to read more data 382 | } 383 | // otherwise not enough data received to decrypt 384 | 385 | if (result != 0) 386 | { 387 | // some data is already copied to output buffer, so return that before blocking with recv 388 | break; 389 | } 390 | 391 | if (s->received == sizeof(s->incoming)) 392 | { 393 | // server is sending too much garbage data instead of proper TLS packet 394 | return -1; 395 | } 396 | 397 | // wait for more ciphertext data from server 398 | int r = recv(s->sock, s->incoming + s->received, sizeof(s->incoming) - s->received, 0); 399 | if (r == 0) 400 | { 401 | // server disconnected socket 402 | return 0; 403 | } 404 | else if (r < 0) 405 | { 406 | // error receiving data from socket 407 | result = -1; 408 | break; 409 | } 410 | s->received += r; 411 | } 412 | } 413 | 414 | return result; 415 | } -------------------------------------------------------------------------------- /src/tokenizer.c: -------------------------------------------------------------------------------- 1 | inline int 2 | RoundReal32ToInt32(float Real32) 3 | { 4 | int Result = _mm_cvtss_si32(_mm_set_ss(Real32)); 5 | return(Result); 6 | } 7 | 8 | static int 9 | IsSpace(char C) 10 | { 11 | int Result = ((C == ' ') || 12 | (C == '\t') || 13 | (C == '\v') || 14 | (C == '\f')); 15 | return (Result); 16 | } 17 | 18 | static int 19 | IsEndOfLine(char C) 20 | { 21 | int Result = ((C == '\n') || 22 | (C == '\r')); 23 | 24 | return(Result); 25 | } 26 | 27 | static int 28 | IsAlpha(char C) 29 | { 30 | int Result = (((C >= 'a') && (C <= 'z')) || 31 | ((C >= 'A') && (C <= 'Z'))); 32 | 33 | return(Result); 34 | } 35 | 36 | static int 37 | IsNumber(char C) 38 | { 39 | int Result = ((C >= '0') && (C <= '9')); 40 | 41 | return(Result); 42 | } 43 | 44 | typedef enum 45 | { 46 | Token_Unknown, 47 | 48 | Token_OpenParen, 49 | Token_CloseParen, 50 | Token_Colon, 51 | Token_Semicolon, 52 | Token_Asterisk, 53 | Token_OpenBracket, 54 | Token_CloseBracket, 55 | Token_OpenBrace, 56 | Token_CloseBrace, 57 | Token_Equals, 58 | Token_Comma, 59 | Token_Or, 60 | Token_Pound, 61 | Token_LessThan, 62 | Token_GreaterThan, 63 | Token_QuestionMark, 64 | // Token_Dot, 65 | Token_At, 66 | Token_Percent, 67 | Token_Dollar, 68 | Token_Quote, 69 | Token_Plus, 70 | 71 | Token_String, 72 | Token_Identifier, 73 | Token_Number, 74 | 75 | Token_Space, 76 | Token_EndOfLine, 77 | Token_Comment, 78 | 79 | Token_EndOfStream, 80 | } token_type; 81 | 82 | typedef struct 83 | { 84 | token_type Type; 85 | 86 | char *Text; 87 | unsigned int TextLength; 88 | 89 | float F32; 90 | int I32; 91 | } token; 92 | 93 | typedef struct 94 | { 95 | char *Input; 96 | unsigned int InputLength; 97 | 98 | int LineNumber; 99 | int ColumnNumber; 100 | 101 | char At[2]; 102 | 103 | int ErrorCode; 104 | } tokenizer; 105 | 106 | static void 107 | Fill(tokenizer *Tokenizer) 108 | { 109 | if (Tokenizer->InputLength == 0) 110 | { 111 | Tokenizer->At[0] = 0; 112 | Tokenizer->At[1] = 0; 113 | } 114 | else if (Tokenizer->InputLength == 1) 115 | { 116 | Tokenizer->At[0] = Tokenizer->Input[0]; 117 | Tokenizer->At[1] = 0; 118 | } 119 | else 120 | { 121 | Tokenizer->At[0] = Tokenizer->Input[0]; 122 | Tokenizer->At[1] = Tokenizer->Input[1]; 123 | } 124 | } 125 | 126 | static char * 127 | MoveBuffer(char **Buffer, unsigned int *BufferLength, unsigned int Length) 128 | { 129 | char *Result = 0; 130 | 131 | if(*BufferLength >= Length) 132 | { 133 | Result = *Buffer; 134 | *Buffer += Length; 135 | *BufferLength -= Length; 136 | } 137 | else 138 | { 139 | *Buffer += *BufferLength; 140 | *BufferLength = 0; 141 | } 142 | 143 | return Result; 144 | } 145 | 146 | static void 147 | MoveForward(tokenizer *Tokenizer, unsigned int Length) 148 | { 149 | Tokenizer->ColumnNumber += Length; 150 | MoveBuffer(&Tokenizer->Input, &Tokenizer->InputLength, Length); 151 | Fill(Tokenizer); 152 | } 153 | 154 | static token 155 | GetToken(tokenizer *Tokenizer) 156 | { 157 | token Token = {0}; 158 | 159 | Token.Text = Tokenizer->Input; 160 | 161 | char C = Tokenizer->At[0]; 162 | MoveForward(Tokenizer, 1); 163 | 164 | switch (C) 165 | { 166 | case '\0': {Token.Type = Token_EndOfStream;} break; 167 | case '(': {Token.Type = Token_OpenParen;} break; 168 | case ')': {Token.Type = Token_CloseParen;} break; 169 | case ':': {Token.Type = Token_Colon;} break; 170 | case ';': {Token.Type = Token_Semicolon;} break; 171 | case '*': {Token.Type = Token_Asterisk;} break; 172 | case '[': {Token.Type = Token_OpenBrace;} break; 173 | case ']': {Token.Type = Token_CloseBrace;} break; 174 | case '{': {Token.Type = Token_OpenBracket;} break; 175 | case '}': {Token.Type = Token_CloseBracket;} break; 176 | case '=': {Token.Type = Token_Equals;} break; 177 | case ',': {Token.Type = Token_Comma;} break; 178 | case '|': {Token.Type = Token_Or;} break; 179 | case '#': {Token.Type = Token_Pound;} break; 180 | case '<': {Token.Type = Token_LessThan;} break; 181 | case '>': {Token.Type = Token_GreaterThan;} break; 182 | case '?': {Token.Type = Token_QuestionMark;} break; 183 | // case '.': {Token.Type = Token_Dot;} break; 184 | case '@': {Token.Type = Token_At;} break; 185 | case '%': {Token.Type = Token_Percent; } break; 186 | // case '$': {Token.Type = Token_Dollar; } break; 187 | case '+': {Token.Type = Token_Plus; } break; 188 | 189 | case '"': 190 | { 191 | Token.Type = Token_String; 192 | 193 | while (Tokenizer->At[0] && 194 | Tokenizer->At[0] != '"') 195 | { 196 | if ((Tokenizer->At[0] == '\\') && Tokenizer->At[1]) 197 | { 198 | MoveForward(Tokenizer, 1); 199 | } 200 | MoveForward(Tokenizer, 1); 201 | } 202 | 203 | if (Tokenizer->At[0] == '"') 204 | { 205 | MoveForward(Tokenizer, 1); 206 | } 207 | } break; 208 | 209 | default: 210 | { 211 | if (IsSpace(C)) 212 | { 213 | Token.Type = Token_Space; 214 | while (IsSpace(Tokenizer->At[0])) 215 | { 216 | MoveForward(Tokenizer, 1); 217 | } 218 | } 219 | else if (IsEndOfLine(C)) 220 | { 221 | Token.Type = Token_EndOfLine; 222 | 223 | if (((C == '\r') && 224 | (Tokenizer->At[0] == '\n')) || 225 | ((C == '\n') && 226 | (Tokenizer->At[0] == '\r'))) 227 | { 228 | MoveForward(Tokenizer, 1); 229 | } 230 | 231 | Tokenizer->ColumnNumber = 1; 232 | ++Tokenizer->LineNumber; 233 | } 234 | else if ((C == '/') && 235 | (Tokenizer->At[0] == '/')) 236 | { 237 | Token.Type = Token_Comment; 238 | 239 | MoveForward(Tokenizer, 2); 240 | while (Tokenizer->At[0] && !IsEndOfLine(Tokenizer->At[0])) 241 | { 242 | MoveForward(Tokenizer, 1); 243 | } 244 | } 245 | else if ((C == '/') && 246 | (Tokenizer->At[0] == '*')) 247 | { 248 | Token.Type = Token_Comment; 249 | 250 | MoveForward(Tokenizer, 2); 251 | while (Tokenizer->At[0] && 252 | !((Tokenizer->At[0] == '*') && 253 | (Tokenizer->At[1] == '/'))) 254 | { 255 | if (((Tokenizer->At[0] == '\r') && 256 | (Tokenizer->At[1] == '\n')) || 257 | ((Tokenizer->At[0] == '\n') && 258 | (Tokenizer->At[1] == '\r'))) 259 | { 260 | MoveForward(Tokenizer, 1); 261 | } 262 | 263 | if (IsEndOfLine(Tokenizer->At[0])) 264 | { 265 | ++Tokenizer->LineNumber; 266 | } 267 | 268 | MoveForward(Tokenizer, 1); 269 | } 270 | 271 | if(Tokenizer->At[0] == '*') 272 | { 273 | MoveForward(Tokenizer, 2); 274 | } 275 | } 276 | else if (IsAlpha(C) || (C == '.') || (C == '/') || (C == '%') || (C == '\\') || (C == '$')) 277 | { 278 | Token.Type = Token_Identifier; 279 | 280 | while (IsAlpha(Tokenizer->At[0]) || 281 | IsNumber(Tokenizer->At[0]) || 282 | (Tokenizer->At[0] == '_') || 283 | (Tokenizer->At[0] == '.') || 284 | (Tokenizer->At[0] == '/') || 285 | (Tokenizer->At[0] == '-') || 286 | (Tokenizer->At[0] == '+') || 287 | (Tokenizer->At[0] == '=')) 288 | { 289 | MoveForward(Tokenizer, 1); 290 | } 291 | } 292 | else if (IsNumber(C)) 293 | { 294 | float Number = (float)(C - '0'); 295 | 296 | while (IsNumber(Tokenizer->At[0])) 297 | { 298 | float Digit = (float)(Tokenizer->At[0] - '0'); 299 | Number = 10.0f*Number + Digit; 300 | MoveForward(Tokenizer, 1); 301 | } 302 | 303 | if (Tokenizer->At[0] == '.') 304 | { 305 | MoveForward(Tokenizer, 1); 306 | float Coefficient = 0.1f; 307 | while (IsNumber(Tokenizer->At[0] - '0')) 308 | { 309 | float Digit = (float)(Tokenizer->At[0] - '0'); 310 | Number += Coefficient*Digit; 311 | Coefficient *= 0.1f; 312 | MoveForward(Tokenizer, 1); 313 | } 314 | } 315 | 316 | Token.Type = Token_Number; 317 | Token.F32 = Number; 318 | Token.I32 = RoundReal32ToInt32(Number); 319 | } 320 | else 321 | { 322 | Token.Type = Token_Unknown; 323 | } 324 | } break; 325 | } 326 | 327 | Token.TextLength = (Tokenizer->Input - Token.Text); 328 | 329 | return Token; 330 | } 331 | 332 | static token 333 | PeekToken(tokenizer *Tokenizer) 334 | { 335 | tokenizer T = *Tokenizer; 336 | token Result = GetToken(&T); 337 | return Result; 338 | } 339 | 340 | static int 341 | ExpectTokenType(tokenizer *Tokenizer, token_type Type) 342 | { 343 | token Token = PeekToken(Tokenizer); 344 | if (Token.Type == Type) 345 | { 346 | GetToken(Tokenizer); 347 | return 0; 348 | } 349 | 350 | return -1; 351 | } 352 | 353 | 354 | static tokenizer 355 | Tokenize(char *Input, unsigned int InputLength) 356 | { 357 | tokenizer Tokenizer = {0}; 358 | 359 | Tokenizer.Input = Input; 360 | Tokenizer.InputLength = InputLength; 361 | Tokenizer.ColumnNumber = 1; 362 | Tokenizer.LineNumber = 1; 363 | 364 | Fill(&Tokenizer); 365 | 366 | return Tokenizer; 367 | } --------------------------------------------------------------------------------