react-native-email-imap-smtp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/EmailImapSmtp.podspec +29 -0
  2. package/LICENSE +20 -0
  3. package/README.md +121 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +117 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  8. package/android/src/main/java/com/margelo/nitro/emailimapsmtp/EmailImapSmtp.kt +1533 -0
  9. package/android/src/main/java/com/margelo/nitro/emailimapsmtp/EmailImapSmtpPackage.kt +22 -0
  10. package/ios/EmailImapSmtp.swift +5 -0
  11. package/lib/module/EmailImapSmtp.nitro.js +4 -0
  12. package/lib/module/EmailImapSmtp.nitro.js.map +1 -0
  13. package/lib/module/NativeEmailImapSmtp.js +14 -0
  14. package/lib/module/NativeEmailImapSmtp.js.map +1 -0
  15. package/lib/module/index.js +64 -0
  16. package/lib/module/index.js.map +1 -0
  17. package/lib/module/native.js +227 -0
  18. package/lib/module/native.js.map +1 -0
  19. package/lib/module/package.json +1 -0
  20. package/lib/module/types.js +2 -0
  21. package/lib/module/types.js.map +1 -0
  22. package/lib/module/web.js +107 -0
  23. package/lib/module/web.js.map +1 -0
  24. package/lib/module/windows.js +156 -0
  25. package/lib/module/windows.js.map +1 -0
  26. package/lib/typescript/package.json +1 -0
  27. package/lib/typescript/src/EmailImapSmtp.nitro.d.ts +268 -0
  28. package/lib/typescript/src/EmailImapSmtp.nitro.d.ts.map +1 -0
  29. package/lib/typescript/src/NativeEmailImapSmtp.d.ts +36 -0
  30. package/lib/typescript/src/NativeEmailImapSmtp.d.ts.map +1 -0
  31. package/lib/typescript/src/index.d.ts +33 -0
  32. package/lib/typescript/src/index.d.ts.map +1 -0
  33. package/lib/typescript/src/native.d.ts +32 -0
  34. package/lib/typescript/src/native.d.ts.map +1 -0
  35. package/lib/typescript/src/types.d.ts +261 -0
  36. package/lib/typescript/src/types.d.ts.map +1 -0
  37. package/lib/typescript/src/web.d.ts +32 -0
  38. package/lib/typescript/src/web.d.ts.map +1 -0
  39. package/lib/typescript/src/windows.d.ts +32 -0
  40. package/lib/typescript/src/windows.d.ts.map +1 -0
  41. package/nitro.json +23 -0
  42. package/nitrogen/generated/android/c++/JAppendResult.hpp +65 -0
  43. package/nitrogen/generated/android/c++/JAttachmentInfo.hpp +96 -0
  44. package/nitrogen/generated/android/c++/JConnectionConfig.hpp +94 -0
  45. package/nitrogen/generated/android/c++/JConnectionResult.hpp +73 -0
  46. package/nitrogen/generated/android/c++/JDeleteResult.hpp +87 -0
  47. package/nitrogen/generated/android/c++/JEmailAddress.hpp +61 -0
  48. package/nitrogen/generated/android/c++/JEmailBody.hpp +84 -0
  49. package/nitrogen/generated/android/c++/JEmailMessage.hpp +273 -0
  50. package/nitrogen/generated/android/c++/JFetchEmailsOptions.hpp +110 -0
  51. package/nitrogen/generated/android/c++/JFolderInfo.hpp +126 -0
  52. package/nitrogen/generated/android/c++/JFunc_void_IdleNotification.hpp +79 -0
  53. package/nitrogen/generated/android/c++/JHybridEmailImapSmtpSpec.cpp +697 -0
  54. package/nitrogen/generated/android/c++/JHybridEmailImapSmtpSpec.hpp +92 -0
  55. package/nitrogen/generated/android/c++/JIdleNotification.hpp +78 -0
  56. package/nitrogen/generated/android/c++/JImapServerInfo.hpp +84 -0
  57. package/nitrogen/generated/android/c++/JMailboxInfo.hpp +134 -0
  58. package/nitrogen/generated/android/c++/JMailboxListItem.hpp +92 -0
  59. package/nitrogen/generated/android/c++/JQuotaInfo.hpp +78 -0
  60. package/nitrogen/generated/android/c++/JSearchCriteria.hpp +187 -0
  61. package/nitrogen/generated/android/c++/JSearchHeader.hpp +61 -0
  62. package/nitrogen/generated/android/c++/JSendMailOptions.hpp +229 -0
  63. package/nitrogen/generated/android/c++/JSendMailResult.hpp +88 -0
  64. package/nitrogen/generated/android/c++/JSmtpAttachment.hpp +80 -0
  65. package/nitrogen/generated/android/c++/JSmtpConnectionConfig.hpp +98 -0
  66. package/nitrogen/generated/android/c++/JSmtpServerInfo.hpp +102 -0
  67. package/nitrogen/generated/android/c++/JVariant_ImapServerInfo_SmtpServerInfo.cpp +26 -0
  68. package/nitrogen/generated/android/c++/JVariant_ImapServerInfo_SmtpServerInfo.hpp +73 -0
  69. package/nitrogen/generated/android/c++/JVariant_NullType_AttachmentInfo.cpp +26 -0
  70. package/nitrogen/generated/android/c++/JVariant_NullType_AttachmentInfo.hpp +75 -0
  71. package/nitrogen/generated/android/c++/JVariant_NullType_EmailMessage.cpp +26 -0
  72. package/nitrogen/generated/android/c++/JVariant_NullType_EmailMessage.hpp +83 -0
  73. package/nitrogen/generated/android/emailimapsmtp+autolinking.cmake +84 -0
  74. package/nitrogen/generated/android/emailimapsmtp+autolinking.gradle +27 -0
  75. package/nitrogen/generated/android/emailimapsmtpOnLoad.cpp +56 -0
  76. package/nitrogen/generated/android/emailimapsmtpOnLoad.hpp +34 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/AppendResult.kt +61 -0
  78. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/AttachmentInfo.kt +96 -0
  79. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/ConnectionConfig.kt +96 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/ConnectionResult.kt +61 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/DeleteResult.kt +61 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/EmailAddress.kt +56 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/EmailBody.kt +61 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/EmailMessage.kt +156 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/FetchEmailsOptions.kt +116 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/FolderInfo.kt +91 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/Func_void_IdleNotification.kt +80 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/HybridEmailImapSmtpSpec.kt +178 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/IdleNotification.kt +76 -0
  90. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/ImapServerInfo.kt +61 -0
  91. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/MailboxInfo.kt +101 -0
  92. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/MailboxListItem.kt +71 -0
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/QuotaInfo.kt +76 -0
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SearchCriteria.kt +196 -0
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SearchHeader.kt +56 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SendMailOptions.kt +126 -0
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SendMailResult.kt +86 -0
  98. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SmtpAttachment.kt +76 -0
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SmtpConnectionConfig.kt +101 -0
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/SmtpServerInfo.kt +61 -0
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/Variant_ImapServerInfo_SmtpServerInfo.kt +62 -0
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/Variant_NullType_AttachmentInfo.kt +62 -0
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/Variant_NullType_EmailMessage.kt +62 -0
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/emailimapsmtp/emailimapsmtpOnLoad.kt +35 -0
  105. package/nitrogen/generated/ios/EmailImapSmtp+autolinking.rb +62 -0
  106. package/nitrogen/generated/ios/EmailImapSmtp-Swift-Cxx-Bridge.cpp +161 -0
  107. package/nitrogen/generated/ios/EmailImapSmtp-Swift-Cxx-Bridge.hpp +1132 -0
  108. package/nitrogen/generated/ios/EmailImapSmtp-Swift-Cxx-Umbrella.hpp +118 -0
  109. package/nitrogen/generated/ios/EmailImapSmtpAutolinking.mm +33 -0
  110. package/nitrogen/generated/ios/EmailImapSmtpAutolinking.swift +26 -0
  111. package/nitrogen/generated/ios/c++/HybridEmailImapSmtpSpecSwift.cpp +11 -0
  112. package/nitrogen/generated/ios/c++/HybridEmailImapSmtpSpecSwift.hpp +390 -0
  113. package/nitrogen/generated/ios/swift/AppendResult.swift +39 -0
  114. package/nitrogen/generated/ios/swift/AttachmentInfo.swift +113 -0
  115. package/nitrogen/generated/ios/swift/ConnectionConfig.swift +87 -0
  116. package/nitrogen/generated/ios/swift/ConnectionResult.swift +84 -0
  117. package/nitrogen/generated/ios/swift/DeleteResult.swift +64 -0
  118. package/nitrogen/generated/ios/swift/EmailAddress.swift +34 -0
  119. package/nitrogen/generated/ios/swift/EmailBody.swift +45 -0
  120. package/nitrogen/generated/ios/swift/EmailMessage.swift +184 -0
  121. package/nitrogen/generated/ios/swift/FetchEmailsOptions.swift +198 -0
  122. package/nitrogen/generated/ios/swift/FolderInfo.swift +81 -0
  123. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  124. package/nitrogen/generated/ios/swift/Func_void_AppendResult.swift +46 -0
  125. package/nitrogen/generated/ios/swift/Func_void_ConnectionResult.swift +46 -0
  126. package/nitrogen/generated/ios/swift/Func_void_DeleteResult.swift +46 -0
  127. package/nitrogen/generated/ios/swift/Func_void_IdleNotification.swift +46 -0
  128. package/nitrogen/generated/ios/swift/Func_void_MailboxInfo.swift +46 -0
  129. package/nitrogen/generated/ios/swift/Func_void_QuotaInfo.swift +46 -0
  130. package/nitrogen/generated/ios/swift/Func_void_SendMailResult.swift +46 -0
  131. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  132. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  133. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__AttachmentInfo_.swift +58 -0
  134. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__EmailMessage_.swift +58 -0
  135. package/nitrogen/generated/ios/swift/Func_void_std__vector_AttachmentInfo_.swift +46 -0
  136. package/nitrogen/generated/ios/swift/Func_void_std__vector_EmailMessage_.swift +46 -0
  137. package/nitrogen/generated/ios/swift/Func_void_std__vector_FolderInfo_.swift +46 -0
  138. package/nitrogen/generated/ios/swift/Func_void_std__vector_MailboxListItem_.swift +46 -0
  139. package/nitrogen/generated/ios/swift/HybridEmailImapSmtpSpec.swift +84 -0
  140. package/nitrogen/generated/ios/swift/HybridEmailImapSmtpSpec_cxx.swift +759 -0
  141. package/nitrogen/generated/ios/swift/IdleNotification.swift +93 -0
  142. package/nitrogen/generated/ios/swift/ImapServerInfo.swift +45 -0
  143. package/nitrogen/generated/ios/swift/MailboxInfo.swift +91 -0
  144. package/nitrogen/generated/ios/swift/MailboxListItem.swift +55 -0
  145. package/nitrogen/generated/ios/swift/QuotaInfo.swift +80 -0
  146. package/nitrogen/generated/ios/swift/SearchCriteria.swift +485 -0
  147. package/nitrogen/generated/ios/swift/SearchHeader.swift +34 -0
  148. package/nitrogen/generated/ios/swift/SendMailOptions.swift +310 -0
  149. package/nitrogen/generated/ios/swift/SendMailResult.swift +155 -0
  150. package/nitrogen/generated/ios/swift/SmtpAttachment.swift +80 -0
  151. package/nitrogen/generated/ios/swift/SmtpConnectionConfig.swift +105 -0
  152. package/nitrogen/generated/ios/swift/SmtpServerInfo.swift +51 -0
  153. package/nitrogen/generated/ios/swift/Variant_ImapServerInfo_SmtpServerInfo.swift +30 -0
  154. package/nitrogen/generated/ios/swift/Variant_NullType_AttachmentInfo.swift +30 -0
  155. package/nitrogen/generated/ios/swift/Variant_NullType_EmailMessage.swift +30 -0
  156. package/nitrogen/generated/shared/c++/AppendResult.hpp +91 -0
  157. package/nitrogen/generated/shared/c++/AttachmentInfo.hpp +121 -0
  158. package/nitrogen/generated/shared/c++/ConnectionConfig.hpp +120 -0
  159. package/nitrogen/generated/shared/c++/ConnectionResult.hpp +98 -0
  160. package/nitrogen/generated/shared/c++/DeleteResult.hpp +93 -0
  161. package/nitrogen/generated/shared/c++/EmailAddress.hpp +87 -0
  162. package/nitrogen/generated/shared/c++/EmailBody.hpp +92 -0
  163. package/nitrogen/generated/shared/c++/EmailMessage.hpp +177 -0
  164. package/nitrogen/generated/shared/c++/FetchEmailsOptions.hpp +136 -0
  165. package/nitrogen/generated/shared/c++/FolderInfo.hpp +116 -0
  166. package/nitrogen/generated/shared/c++/HybridEmailImapSmtpSpec.cpp +50 -0
  167. package/nitrogen/generated/shared/c++/HybridEmailImapSmtpSpec.hpp +145 -0
  168. package/nitrogen/generated/shared/c++/IdleNotification.hpp +104 -0
  169. package/nitrogen/generated/shared/c++/ImapServerInfo.hpp +92 -0
  170. package/nitrogen/generated/shared/c++/MailboxInfo.hpp +124 -0
  171. package/nitrogen/generated/shared/c++/MailboxListItem.hpp +100 -0
  172. package/nitrogen/generated/shared/c++/QuotaInfo.hpp +104 -0
  173. package/nitrogen/generated/shared/c++/SearchCriteria.hpp +203 -0
  174. package/nitrogen/generated/shared/c++/SearchHeader.hpp +87 -0
  175. package/nitrogen/generated/shared/c++/SendMailOptions.hpp +151 -0
  176. package/nitrogen/generated/shared/c++/SendMailResult.hpp +113 -0
  177. package/nitrogen/generated/shared/c++/SmtpAttachment.hpp +105 -0
  178. package/nitrogen/generated/shared/c++/SmtpConnectionConfig.hpp +124 -0
  179. package/nitrogen/generated/shared/c++/SmtpServerInfo.hpp +92 -0
  180. package/package.json +196 -0
  181. package/react-native.config.js +27 -0
  182. package/src/EmailImapSmtp.nitro.ts +333 -0
  183. package/src/NativeEmailImapSmtp.ts +100 -0
  184. package/src/index.tsx +166 -0
  185. package/src/native.ts +305 -0
  186. package/src/types.ts +328 -0
  187. package/src/web.ts +196 -0
  188. package/src/windows.ts +304 -0
  189. package/windows/EmailImapSmtp.cpp +735 -0
  190. package/windows/EmailImapSmtp.h +137 -0
  191. package/windows/EmailImapSmtpModule.cpp +10 -0
  192. package/windows/EmailImapSmtpModule.h +139 -0
  193. package/windows/EmailImapSmtpPackage.cpp +15 -0
  194. package/windows/EmailImapSmtpPackage.h +13 -0
  195. package/windows/ImapProtocol.cpp +1382 -0
  196. package/windows/ImapProtocol.h +97 -0
  197. package/windows/ProtocolDefs.h +300 -0
  198. package/windows/SmtpProtocol.cpp +334 -0
  199. package/windows/SmtpProtocol.h +36 -0
  200. package/windows/TlsSocket.cpp +818 -0
  201. package/windows/TlsSocket.h +87 -0
@@ -0,0 +1,818 @@
1
+ // ============================================================
2
+ // TlsSocket.cpp — WinSock2 TCP + Schannel TLS implementation
3
+ // ============================================================
4
+
5
+ #include "TlsSocket.h"
6
+
7
+ #include <sstream>
8
+ #include <cstring>
9
+ #include <chrono>
10
+ #include <algorithm>
11
+ #include <cctype>
12
+ #include <iomanip>
13
+
14
+ #ifndef SCH_CRED_NO_SYSTEM_MAPPER
15
+ #define SCH_CRED_NO_SYSTEM_MAPPER 0x00000002
16
+ #endif
17
+ #ifndef SCH_CRED_NO_SERVERNAME_CHECK
18
+ #define SCH_CRED_NO_SERVERNAME_CHECK 0x00000004
19
+ #endif
20
+
21
+ namespace emailimap {
22
+
23
+ // ═══════════════════════════════════════════════════════════
24
+ // Utility functions
25
+ // ═══════════════════════════════════════════════════════════
26
+
27
+ std::string trim(const std::string& str) {
28
+ size_t first = str.find_first_not_of(" \t\r\n");
29
+ if (first == std::string::npos) return "";
30
+ size_t last = str.find_last_not_of(" \t\r\n");
31
+ return str.substr(first, last - first + 1);
32
+ }
33
+
34
+ std::string toUpper(const std::string& str) {
35
+ std::string result = str;
36
+ std::transform(result.begin(), result.end(), result.begin(), ::toupper);
37
+ return result;
38
+ }
39
+
40
+ std::vector<std::string> split(const std::string& str, char delimiter) {
41
+ std::vector<std::string> tokens;
42
+ std::stringstream ss(str);
43
+ std::string token;
44
+ while (std::getline(ss, token, delimiter)) {
45
+ tokens.push_back(token);
46
+ }
47
+ return tokens;
48
+ }
49
+
50
+ std::string currentTimestamp() {
51
+ auto now = std::chrono::system_clock::now();
52
+ auto tt = std::chrono::system_clock::to_time_t(now);
53
+ std::tm tm;
54
+ gmtime_s(&tm, &tt);
55
+ std::stringstream ss;
56
+ ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S+0000");
57
+ return ss.str();
58
+ }
59
+
60
+ std::string formatDate(std::time_t t) {
61
+ std::tm tm;
62
+ gmtime_s(&tm, &t);
63
+ std::stringstream ss;
64
+ ss << std::put_time(&tm, "%d-%b-%Y %H:%M:%S +0000");
65
+ return ss.str();
66
+ }
67
+
68
+ // ═══════════════════════════════════════════════════════════
69
+ // Base64
70
+ // ═══════════════════════════════════════════════════════════
71
+
72
+ static const char BASE64_CHARS[] =
73
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
74
+
75
+ std::string encodeBase64(const std::vector<char>& data) {
76
+ std::string result;
77
+ int i = 0;
78
+ int j = 0;
79
+ unsigned char array3[3];
80
+ unsigned char array4[4];
81
+
82
+ for (unsigned char c : data) {
83
+ array3[i++] = c;
84
+ if (i == 3) {
85
+ array4[0] = (array3[0] & 0xfc) >> 2;
86
+ array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
87
+ array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
88
+ array4[3] = array3[2] & 0x3f;
89
+ for (i = 0; i < 4; i++)
90
+ result += BASE64_CHARS[array4[i]];
91
+ i = 0;
92
+ }
93
+ }
94
+
95
+ if (i) {
96
+ for (j = i; j < 3; j++)
97
+ array3[j] = '\0';
98
+ array4[0] = (array3[0] & 0xfc) >> 2;
99
+ array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
100
+ array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
101
+ array4[3] = array3[2] & 0x3f;
102
+ for (j = 0; j < i + 1; j++)
103
+ result += BASE64_CHARS[array4[j]];
104
+ while ((i++ < 3))
105
+ result += '=';
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ std::vector<char> decodeBase64(const std::string& data) {
112
+ std::vector<char> result;
113
+ std::string tmp;
114
+ for (char c : data) {
115
+ if (c == '=') break;
116
+ if (std::isalnum(c) || c == '+' || c == '/')
117
+ tmp += c;
118
+ }
119
+ if (tmp.empty()) return result;
120
+
121
+ int i = 0;
122
+ unsigned char array4[4], array3[3];
123
+ for (char c : tmp) {
124
+ char* p = strchr(const_cast<char*>(BASE64_CHARS), c);
125
+ if (!p) continue;
126
+ array4[i++] = static_cast<unsigned char>(p - BASE64_CHARS);
127
+ if (i == 4) {
128
+ array3[0] = (array4[0] << 2) + ((array4[1] & 0x30) >> 4);
129
+ array3[1] = ((array4[1] & 0xf) << 4) + ((array4[2] & 0x3c) >> 2);
130
+ array3[2] = ((array4[2] & 0x3) << 6) + array4[3];
131
+ for (i = 0; i < 3; i++)
132
+ result.push_back(array3[i]);
133
+ i = 0;
134
+ }
135
+ }
136
+ return result;
137
+ }
138
+
139
+ std::string encodeBase64Text(const std::string& text) {
140
+ std::vector<char> data(text.begin(), text.end());
141
+ return encodeBase64(data);
142
+ }
143
+
144
+ // ═══════════════════════════════════════════════════════════
145
+ // TlsSocket
146
+ // ═══════════════════════════════════════════════════════════
147
+
148
+ TlsSocket::TlsSocket()
149
+ : m_socket(INVALID_SOCKET)
150
+ , m_tls(false)
151
+ , m_hasCredHandle(false)
152
+ , m_hasCtxtHandle(false)
153
+ , m_checkCertificate(true)
154
+ {
155
+ WSADATA wsaData;
156
+ WSAStartup(MAKEWORD(2, 2), &wsaData);
157
+ }
158
+
159
+ TlsSocket::~TlsSocket() {
160
+ disconnect();
161
+ WSACleanup();
162
+ }
163
+
164
+ bool TlsSocket::connect(const std::string& host, int port, int timeoutMs, bool checkCertificate) {
165
+ std::lock_guard<std::mutex> lock(m_mutex);
166
+ m_lastError = 0;
167
+
168
+ m_serverName = host;
169
+ m_checkCertificate = checkCertificate;
170
+
171
+ struct addrinfo hints = {}, *addrList = nullptr;
172
+ hints.ai_family = AF_UNSPEC;
173
+ hints.ai_socktype = SOCK_STREAM;
174
+ hints.ai_protocol = IPPROTO_TCP;
175
+
176
+ std::string portStr = std::to_string(port);
177
+ if (getaddrinfo(host.c_str(), portStr.c_str(), &hints, &addrList) != 0 || !addrList) {
178
+ m_lastError = WSAGetLastError();
179
+ return false;
180
+ }
181
+
182
+ for (struct addrinfo* addr = addrList; addr; addr = addr->ai_next) {
183
+ m_socket = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
184
+ if (m_socket == INVALID_SOCKET) {
185
+ m_lastError = WSAGetLastError();
186
+ continue;
187
+ }
188
+
189
+ u_long mode = 1;
190
+ ioctlsocket(m_socket, FIONBIO, &mode);
191
+
192
+ ::connect(m_socket, addr->ai_addr, static_cast<int>(addr->ai_addrlen));
193
+
194
+ fd_set fd;
195
+ FD_ZERO(&fd);
196
+ FD_SET(m_socket, &fd);
197
+ timeval tv = { timeoutMs / 1000, (timeoutMs % 1000) * 1000 };
198
+ int sel = select(static_cast<int>(m_socket) + 1, nullptr, &fd, nullptr, &tv);
199
+ if (sel <= 0) {
200
+ m_lastError = WSAGetLastError();
201
+ closesocket(m_socket);
202
+ m_socket = INVALID_SOCKET;
203
+ continue;
204
+ }
205
+
206
+ int error = 0;
207
+ socklen_t len = sizeof(error);
208
+ getsockopt(m_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &len);
209
+ if (error != 0) {
210
+ m_lastError = error;
211
+ closesocket(m_socket);
212
+ m_socket = INVALID_SOCKET;
213
+ continue;
214
+ }
215
+
216
+ mode = 0;
217
+ ioctlsocket(m_socket, FIONBIO, &mode);
218
+
219
+ DWORD tv_ms = timeoutMs;
220
+ setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&tv_ms), sizeof(tv_ms));
221
+ setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&tv_ms), sizeof(tv_ms));
222
+
223
+ break;
224
+ }
225
+
226
+ freeaddrinfo(addrList);
227
+
228
+ if (m_socket == INVALID_SOCKET) {
229
+ return false;
230
+ }
231
+
232
+ m_tls = (port == 993 || port == 465);
233
+ if (m_tls) {
234
+ if (!initializeSchannel()) {
235
+ // acquireCredentialsHandle failed; SECURITY_STATUS stored in m_lastError
236
+ closesocket(m_socket);
237
+ m_socket = INVALID_SOCKET;
238
+ return false;
239
+ }
240
+ if (!performClientHandshake()) {
241
+ // InitializeSecurityContext failed; SECURITY_STATUS stored in m_lastError
242
+ disconnectInternal();
243
+ return false;
244
+ }
245
+ }
246
+
247
+ return true;
248
+ }
249
+
250
+ void TlsSocket::disconnectInternal() {
251
+ if (m_hasCtxtHandle) {
252
+ DeleteSecurityContext(&m_ctxtHandle);
253
+ m_hasCtxtHandle = false;
254
+ }
255
+ if (m_hasCredHandle) {
256
+ FreeCredentialsHandle(&m_credHandle);
257
+ m_hasCredHandle = false;
258
+ }
259
+ if (m_socket != INVALID_SOCKET) {
260
+ ::shutdown(m_socket, SD_BOTH);
261
+ closesocket(m_socket);
262
+ m_socket = INVALID_SOCKET;
263
+ }
264
+ m_receiveBuffer.clear();
265
+ m_pendingTlsData.clear();
266
+ }
267
+
268
+ void TlsSocket::disconnect() {
269
+ std::lock_guard<std::mutex> lock(m_mutex);
270
+ disconnectInternal();
271
+ }
272
+
273
+ bool TlsSocket::isConnected() const {
274
+ return m_socket != INVALID_SOCKET;
275
+ }
276
+
277
+ // ─── TLS: Initialize Schannel ───────────────────────────────
278
+
279
+ bool TlsSocket::initializeSchannel() {
280
+ SCHANNEL_CRED schannelCred = {};
281
+ schannelCred.dwVersion = SCHANNEL_CRED_VERSION;
282
+
283
+ // Explicitly enable TLS 1.2 only to avoid compatibility issues
284
+ schannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2;
285
+
286
+ if (!m_checkCertificate) {
287
+ schannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
288
+ schannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER;
289
+ schannelCred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
290
+ }
291
+
292
+ TimeStamp expiry;
293
+ SECURITY_STATUS status = AcquireCredentialsHandle(
294
+ nullptr,
295
+ const_cast<char*>(UNISP_NAME),
296
+ SECPKG_CRED_OUTBOUND,
297
+ nullptr,
298
+ &schannelCred,
299
+ nullptr,
300
+ nullptr,
301
+ &m_credHandle,
302
+ &expiry
303
+ );
304
+
305
+ if (status != SEC_E_OK) {
306
+ fprintf(stderr, "[TLS] AcquireCredentialsHandle failed: 0x%08x\n", (unsigned int)status);
307
+ m_lastError = static_cast<int>(status);
308
+ return false;
309
+ }
310
+ m_hasCredHandle = true;
311
+ return true;
312
+ }
313
+
314
+ // ─── TLS: Perform Handshake ─────────────────────────────────
315
+
316
+ bool TlsSocket::performClientHandshake() {
317
+ fprintf(stderr, "[TLS] Starting handshake with %s\n", m_serverName.c_str());
318
+ SecBuffer outBuffers[1] = {};
319
+ SecBufferDesc outDesc = {};
320
+ outDesc.ulVersion = SECBUFFER_VERSION;
321
+ outDesc.cBuffers = 1;
322
+ outDesc.pBuffers = outBuffers;
323
+ outBuffers[0].BufferType = SECBUFFER_TOKEN;
324
+
325
+ DWORD flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
326
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
327
+ ISC_REQ_STREAM;
328
+
329
+ if (!m_checkCertificate) {
330
+ flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
331
+ }
332
+
333
+ std::wstring wServerName(m_serverName.begin(), m_serverName.end());
334
+
335
+ SECURITY_STATUS status;
336
+ DWORD outFlags;
337
+ TimeStamp expiry;
338
+
339
+ status = InitializeSecurityContextW(
340
+ &m_credHandle,
341
+ nullptr,
342
+ const_cast<wchar_t*>(wServerName.c_str()),
343
+ flags,
344
+ 0,
345
+ 0,
346
+ nullptr,
347
+ 0,
348
+ &m_ctxtHandle,
349
+ &outDesc,
350
+ &outFlags,
351
+ &expiry
352
+ );
353
+
354
+ if (status != SEC_I_CONTINUE_NEEDED) {
355
+ fprintf(stderr, "[TLS] InitSecCtx(first) failed: status=0x%08x\n", (unsigned int)status);
356
+ m_lastError = static_cast<int>(status);
357
+ return false;
358
+ }
359
+
360
+ fprintf(stderr, "[TLS] ClientHello generated: %d bytes\n", (int)outBuffers[0].cbBuffer);
361
+ if (outBuffers[0].cbBuffer > 0) {
362
+ int sent = ::send(m_socket, reinterpret_cast<const char*>(outBuffers[0].pvBuffer),
363
+ outBuffers[0].cbBuffer, 0);
364
+ fprintf(stderr, "[TLS] Sent ClientHello: %d bytes\n", sent);
365
+ FreeContextBuffer(outBuffers[0].pvBuffer);
366
+ }
367
+
368
+ std::vector<char> extraBuf;
369
+ int loopCount = 0;
370
+ while (status == SEC_I_CONTINUE_NEEDED ||
371
+ status == SEC_E_INCOMPLETE_MESSAGE) {
372
+ loopCount++;
373
+ fprintf(stderr, "[TLS] Loop #%d status=0x%08x extraBuf=%zu\n",
374
+ loopCount, (unsigned int)status, extraBuf.size());
375
+ fflush(stderr);
376
+ // If we don't have saved extra data, recv from socket
377
+ if (extraBuf.empty()) {
378
+ char buf[4096];
379
+ int bytes = ::recv(m_socket, buf, sizeof(buf), 0);
380
+ fprintf(stderr, "[TLS] recv: %d bytes", bytes);
381
+ if (bytes <= 0) {
382
+ fprintf(stderr, " (WSAError=%d)\n", WSAGetLastError());
383
+ m_lastError = WSAGetLastError();
384
+ return false;
385
+ }
386
+ fprintf(stderr, " (first byte=0x%02x)\n", (unsigned char)buf[0]);
387
+ extraBuf.assign(buf, buf + bytes);
388
+ }
389
+
390
+ // Provide 4 input buffers so Schannel can report SECBUFFER_EXTRA
391
+ SecBuffer inBuffers[4] = {};
392
+ inBuffers[0].cbBuffer = static_cast<unsigned long>(extraBuf.size());
393
+ inBuffers[0].BufferType = SECBUFFER_TOKEN;
394
+ inBuffers[0].pvBuffer = extraBuf.data();
395
+ // remaining 3 buffers are SECBUFFER_EMPTY (zero-initialized)
396
+
397
+ SecBufferDesc inDesc = {};
398
+ inDesc.ulVersion = SECBUFFER_VERSION;
399
+ inDesc.cBuffers = 4;
400
+ inDesc.pBuffers = inBuffers;
401
+
402
+ outBuffers[0].pvBuffer = nullptr;
403
+ outBuffers[0].cbBuffer = 0;
404
+ outBuffers[0].BufferType = SECBUFFER_TOKEN;
405
+
406
+ status = InitializeSecurityContextW(
407
+ &m_credHandle,
408
+ &m_ctxtHandle,
409
+ const_cast<wchar_t*>(wServerName.c_str()),
410
+ flags,
411
+ 0,
412
+ 0,
413
+ &inDesc,
414
+ 0,
415
+ nullptr,
416
+ &outDesc,
417
+ &outFlags,
418
+ &expiry
419
+ );
420
+
421
+ fprintf(stderr, "[TLS] InitSecCtx(loop) -> status=0x%08x, outBuf=%d bytes\n",
422
+ (unsigned int)status, (int)outBuffers[0].cbBuffer);
423
+
424
+ // Send output token (e.g. ClientKeyExchange) if generated
425
+ if (outBuffers[0].cbBuffer > 0) {
426
+ int sent = (int)::send(m_socket, reinterpret_cast<const char*>(outBuffers[0].pvBuffer),
427
+ outBuffers[0].cbBuffer, 0);
428
+ fprintf(stderr, "[TLS] Sent response: %d bytes\n", sent);
429
+ FreeContextBuffer(outBuffers[0].pvBuffer);
430
+ }
431
+
432
+ // Handle extra (unconsumed) data or incomplete messages
433
+ // Schannel may report:
434
+ // SECBUFFER_EXTRA — unconsumed data remaining in our buffer
435
+ // SECBUFFER_MISSING — number of additional bytes needed for a complete message
436
+ {
437
+ size_t extraBytes = 0;
438
+ size_t missingBytes = 0;
439
+ for (int i = 0; i < 4; i++) {
440
+ if (inBuffers[i].BufferType == SECBUFFER_EXTRA && inBuffers[i].cbBuffer > 0) {
441
+ extraBytes = inBuffers[i].cbBuffer;
442
+ }
443
+ if (inBuffers[i].BufferType == SECBUFFER_MISSING && inBuffers[i].cbBuffer > 0) {
444
+ missingBytes = inBuffers[i].cbBuffer;
445
+ }
446
+ }
447
+
448
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
449
+ // Schannel needs more data — keep existing extraBuf and recv more
450
+ char buf[4096];
451
+ int bytes = ::recv(m_socket, buf, sizeof(buf), 0);
452
+ fprintf(stderr, "[TLS] recv (incomplete): %d bytes", bytes);
453
+ if (bytes > 0) {
454
+ fprintf(stderr, " (first byte=0x%02x, missing=%zu)\n",
455
+ (unsigned char)buf[0], missingBytes);
456
+ extraBuf.insert(extraBuf.end(), buf, buf + bytes);
457
+ } else {
458
+ fprintf(stderr, " (WSAError=%d)\n", WSAGetLastError());
459
+ m_lastError = WSAGetLastError();
460
+ return false;
461
+ }
462
+ } else if (extraBytes > 0 && extraBytes < extraBuf.size()) {
463
+ // Extra data is at the tail of our input buffer
464
+ size_t consumed = extraBuf.size() - extraBytes;
465
+ std::vector<char> tmp(extraBuf.begin() + static_cast<ptrdiff_t>(consumed),
466
+ extraBuf.end());
467
+ extraBuf = std::move(tmp);
468
+ fprintf(stderr, "[TLS] Extra data: %zu bytes (consumed=%zu)\n", extraBuf.size(), consumed);
469
+ } else if (extraBytes >= extraBuf.size()) {
470
+ // All data was extra (nothing consumed) - shouldn't happen
471
+ fprintf(stderr, "[TLS] Warning: all %zu bytes were extra\n", extraBuf.size());
472
+ extraBuf.clear();
473
+ } else {
474
+ // No extra data - need to recv more
475
+ extraBuf.clear();
476
+ }
477
+ }
478
+
479
+ if (status == SEC_E_OK) {
480
+ QueryContextAttributes(&m_ctxtHandle, SECPKG_ATTR_STREAM_SIZES, &m_streamSizes);
481
+ m_hasCtxtHandle = true;
482
+ // Save any extra (unconsumed) data to m_receiveBuffer so the caller
483
+ // can read it (e.g. IMAP greeting piggybacked on the handshake).
484
+ if (!extraBuf.empty()) {
485
+ fprintf(stderr, "[TLS] Handshake extra data: %zu bytes saved to buffer\n", extraBuf.size());
486
+ m_receiveBuffer.append(extraBuf.data(), extraBuf.size());
487
+ }
488
+ fprintf(stderr, "[TLS] Handshake successful!\n");
489
+ return true;
490
+ }
491
+
492
+ if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_INCOMPLETE_MESSAGE) {
493
+ fprintf(stderr, "[TLS] Unexpected status: 0x%08x\n", (unsigned int)status);
494
+ m_lastError = static_cast<int>(status);
495
+ }
496
+ }
497
+
498
+ fprintf(stderr, "[TLS] Handshake failed, final status=0x%08x\n", (unsigned int)status);
499
+ m_lastError = static_cast<int>(status);
500
+ return false;
501
+ }
502
+
503
+ // ─── TLS: Encrypt ───────────────────────────────────────────
504
+
505
+ bool TlsSocket::encryptMessage(const std::string& plaintext, std::vector<char>& ciphertext) {
506
+ if (!m_hasCtxtHandle) return false;
507
+
508
+ int needed = m_streamSizes.cbHeader + static_cast<int>(plaintext.size()) + m_streamSizes.cbTrailer;
509
+ std::vector<char> msg(needed);
510
+
511
+ SecBuffer buffers[4];
512
+ buffers[0].cbBuffer = m_streamSizes.cbHeader;
513
+ buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
514
+ buffers[0].pvBuffer = msg.data();
515
+
516
+ buffers[1].cbBuffer = static_cast<unsigned long>(plaintext.size());
517
+ buffers[1].BufferType = SECBUFFER_DATA;
518
+ buffers[1].pvBuffer = msg.data() + m_streamSizes.cbHeader;
519
+ memcpy(buffers[1].pvBuffer, plaintext.data(), plaintext.size());
520
+
521
+ buffers[2].cbBuffer = m_streamSizes.cbTrailer;
522
+ buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
523
+ buffers[2].pvBuffer = msg.data() + m_streamSizes.cbHeader + plaintext.size();
524
+
525
+ buffers[3].cbBuffer = 0;
526
+ buffers[3].BufferType = SECBUFFER_EMPTY;
527
+
528
+ SecBufferDesc desc;
529
+ desc.ulVersion = SECBUFFER_VERSION;
530
+ desc.cBuffers = 4;
531
+ desc.pBuffers = buffers;
532
+
533
+ SECURITY_STATUS status = EncryptMessage(&m_ctxtHandle, 0, &desc, 0);
534
+ if (status != SEC_E_OK) return false;
535
+
536
+ int totalSize = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer;
537
+ ciphertext.resize(totalSize);
538
+ memcpy(ciphertext.data(), msg.data(), totalSize);
539
+ return true;
540
+ }
541
+
542
+ // ─── TLS: Decrypt ───────────────────────────────────────────
543
+ // Returns: 1=success, 0=incomplete (need more data), -1=fatal error
544
+ int TlsSocket::decryptMessage(std::vector<char>& buffer, std::vector<char>& plaintext) {
545
+ if (!m_hasCtxtHandle) return -1;
546
+
547
+ // Save original data so we can locate SECBUFFER_EXTRA after DecryptMessage
548
+ std::vector<char> originalBuf = buffer;
549
+
550
+ SecBuffer buffers[4];
551
+ buffers[0].cbBuffer = static_cast<unsigned long>(buffer.size());
552
+ buffers[0].BufferType = SECBUFFER_DATA;
553
+ buffers[0].pvBuffer = buffer.data();
554
+ buffers[1].cbBuffer = 0;
555
+ buffers[1].BufferType = SECBUFFER_EMPTY;
556
+ buffers[2].cbBuffer = 0;
557
+ buffers[2].BufferType = SECBUFFER_EMPTY;
558
+ buffers[3].cbBuffer = 0;
559
+ buffers[3].BufferType = SECBUFFER_EMPTY;
560
+
561
+ SecBufferDesc desc;
562
+ desc.ulVersion = SECBUFFER_VERSION;
563
+ desc.cBuffers = 4;
564
+ desc.pBuffers = buffers;
565
+
566
+ SECURITY_STATUS status = DecryptMessage(&m_ctxtHandle, &desc, 0, nullptr);
567
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
568
+ // need more data
569
+ return 0; // need more data
570
+ }
571
+ if (status != SEC_E_OK) {
572
+ // decrypt failed
573
+ return -1; // fatal
574
+ }
575
+
576
+ // Find decrypted plaintext and any extra (unconsumed) encrypted data
577
+ const char* decPtr = nullptr;
578
+ unsigned long decLen = 0;
579
+ size_t extraOffset = 0; // offset within the ORIGINAL buffer
580
+ unsigned long extraLen = 0;
581
+
582
+ for (int i = 0; i < 4; i++) {
583
+ if (buffers[i].BufferType == SECBUFFER_DATA && buffers[i].cbBuffer > 0) {
584
+ decPtr = reinterpret_cast<const char*>(buffers[i].pvBuffer);
585
+ decLen = buffers[i].cbBuffer;
586
+ }
587
+ if (buffers[i].BufferType == SECBUFFER_EXTRA && buffers[i].cbBuffer > 0) {
588
+ // extraPtr points into buffer.data(); compute offset from there
589
+ const char* extraPtr = reinterpret_cast<const char*>(buffers[i].pvBuffer);
590
+ extraOffset = static_cast<size_t>(extraPtr - buffer.data());
591
+ extraLen = buffers[i].cbBuffer;
592
+ }
593
+ }
594
+
595
+ if (!decPtr || decLen == 0) return -1;
596
+
597
+ // Output: plaintext = decrypted data
598
+ plaintext.assign(decPtr, decPtr + decLen);
599
+
600
+ // Output: buffer = remaining encrypted data (from the saved original copy)
601
+ if (extraLen > 0 && extraOffset + extraLen <= originalBuf.size()) {
602
+ buffer.assign(originalBuf.data() + extraOffset,
603
+ originalBuf.data() + extraOffset + extraLen);
604
+ } else {
605
+ buffer.clear();
606
+ }
607
+
608
+ return 1;
609
+ }
610
+
611
+ bool TlsSocket::sendEncrypted(const std::vector<char>& data) {
612
+ int totalSent = 0;
613
+ while (totalSent < static_cast<int>(data.size())) {
614
+ int n = ::send(m_socket, data.data() + totalSent,
615
+ static_cast<int>(data.size()) - totalSent, 0);
616
+ if (n <= 0) return false;
617
+ totalSent += n;
618
+ }
619
+ return true;
620
+ }
621
+
622
+ std::vector<char> TlsSocket::receiveEncrypted() {
623
+ std::vector<char> result;
624
+ char buf[16384];
625
+ int n = ::recv(m_socket, buf, sizeof(buf), 0);
626
+ if (n <= 0) return result;
627
+ result.assign(buf, buf + n);
628
+ // Drain any additional available data without blocking
629
+ u_long available = 0;
630
+ while (ioctlsocket(m_socket, FIONREAD, &available) == 0 && available > 0) {
631
+ size_t currentSize = (std::min)((size_t)available, sizeof(buf));
632
+ int more = ::recv(m_socket, buf, (int)currentSize, 0);
633
+ if (more <= 0) break;
634
+ result.insert(result.end(), buf, buf + more);
635
+ }
636
+ return result;
637
+ }
638
+
639
+ // ─── TLS: Send / Receive ───────────────────────────────────
640
+
641
+ bool TlsSocket::send(const std::string& data) {
642
+ std::lock_guard<std::mutex> lock(m_mutex);
643
+
644
+ if (m_tls) {
645
+ std::vector<char> encrypted;
646
+ if (!encryptMessage(data, encrypted)) return false;
647
+ return sendEncrypted(encrypted);
648
+ } else {
649
+ int totalSent = 0;
650
+ while (totalSent < static_cast<int>(data.size())) {
651
+ int n = ::send(m_socket, data.data() + totalSent,
652
+ static_cast<int>(data.size()) - totalSent, 0);
653
+ if (n <= 0) return false;
654
+ totalSent += n;
655
+ }
656
+ return true;
657
+ }
658
+ }
659
+
660
+ std::string TlsSocket::receiveLine(int timeoutMs) {
661
+ std::lock_guard<std::mutex> lock(m_mutex);
662
+
663
+ auto deadline = std::chrono::steady_clock::now() +
664
+ std::chrono::milliseconds(timeoutMs);
665
+
666
+ while (std::chrono::steady_clock::now() < deadline) {
667
+ size_t pos = m_receiveBuffer.find('\n');
668
+ if (pos != std::string::npos) {
669
+ std::string line = m_receiveBuffer.substr(0, pos);
670
+ m_receiveBuffer.erase(0, pos + 1);
671
+ if (!line.empty() && line.back() == '\r') {
672
+ line.pop_back();
673
+ }
674
+ return line;
675
+ }
676
+
677
+ if (m_tls) {
678
+ // Use pending data, or recv fresh (FIONREAD inside drains all available)
679
+ std::vector<char> recvBuf;
680
+ if (!m_pendingTlsData.empty()) {
681
+ recvBuf.swap(m_pendingTlsData);
682
+ } else {
683
+ recvBuf = receiveEncrypted();
684
+ if (recvBuf.empty()) { break; }
685
+ }
686
+
687
+ // Decrypt one record at a time until all data is consumed
688
+ while (!recvBuf.empty()) {
689
+ std::vector<char> plaintext;
690
+ int ret = decryptMessage(recvBuf, plaintext);
691
+ if (ret == 1) {
692
+ m_receiveBuffer.append(plaintext.data(), plaintext.size());
693
+ // recvBuf now contains remaining encrypted data (or empty)
694
+ continue;
695
+ } else if (ret == 0) {
696
+ // Incomplete record - save for next call
697
+ if (!recvBuf.empty()) m_pendingTlsData.swap(recvBuf);
698
+ break;
699
+ } else {
700
+ // Fatal error - but we may have decrypted some data already
701
+ m_pendingTlsData.clear();
702
+ if (m_receiveBuffer.empty()) break;
703
+ break;
704
+ }
705
+ }
706
+ // If we got any decrypted data, continue the outer loop to find \n
707
+ if (!m_receiveBuffer.empty()) continue;
708
+ break;
709
+ } else {
710
+ char buf[4096];
711
+ int n = ::recv(m_socket, buf, sizeof(buf), 0);
712
+ if (n <= 0) break;
713
+ m_receiveBuffer.append(buf, n);
714
+ }
715
+ }
716
+
717
+ if (!m_receiveBuffer.empty()) {
718
+ size_t pos = m_receiveBuffer.find('\n');
719
+ if (pos != std::string::npos) {
720
+ std::string line = m_receiveBuffer.substr(0, pos);
721
+ m_receiveBuffer.erase(0, pos + 1);
722
+ if (!line.empty() && line.back() == '\r') line.pop_back();
723
+ return line;
724
+ }
725
+ }
726
+
727
+ return "";
728
+ }
729
+
730
+ std::string TlsSocket::receiveRaw(int timeoutMs, size_t maxBytes) {
731
+ std::lock_guard<std::mutex> lock(m_mutex);
732
+
733
+ auto deadline = std::chrono::steady_clock::now() +
734
+ std::chrono::milliseconds(timeoutMs);
735
+
736
+ std::string result;
737
+
738
+ while (std::chrono::steady_clock::now() < deadline) {
739
+ if (!m_receiveBuffer.empty()) {
740
+ size_t toTake = maxBytes > 0 ? std::min(maxBytes, m_receiveBuffer.size()) : m_receiveBuffer.size();
741
+ result.append(m_receiveBuffer, 0, toTake);
742
+ m_receiveBuffer.erase(0, toTake);
743
+ if (!maxBytes || result.size() < maxBytes) continue;
744
+ break;
745
+ }
746
+
747
+ if (m_tls) {
748
+ std::vector<char> recvBuf;
749
+ if (!m_pendingTlsData.empty()) recvBuf.swap(m_pendingTlsData);
750
+ else {
751
+ recvBuf = receiveEncrypted();
752
+ if (recvBuf.empty()) break;
753
+ }
754
+ std::vector<char> plaintext;
755
+ int ret = decryptMessage(recvBuf, plaintext);
756
+ if (ret == 1) {
757
+ m_receiveBuffer.append(plaintext.data(), plaintext.size());
758
+ if (!recvBuf.empty()) m_pendingTlsData.swap(recvBuf);
759
+ } else if (ret == 0) {
760
+ if (!recvBuf.empty()) m_pendingTlsData.swap(recvBuf);
761
+ if (m_receiveBuffer.empty()) break;
762
+ } else {
763
+ if (m_receiveBuffer.empty()) break;
764
+ }
765
+ } else {
766
+ char buf[16384];
767
+ int n = ::recv(m_socket, buf, sizeof(buf), 0);
768
+ if (n <= 0) break;
769
+ m_receiveBuffer.append(buf, n);
770
+ }
771
+ }
772
+
773
+ return result;
774
+ }
775
+
776
+ int TlsSocket::receiveRawBuffer(char* buf, int bufSize, int timeoutMs) {
777
+ std::lock_guard<std::mutex> lock(m_mutex);
778
+
779
+ if (!m_receiveBuffer.empty()) {
780
+ int toCopy = std::min(bufSize, static_cast<int>(m_receiveBuffer.size()));
781
+ memcpy(buf, m_receiveBuffer.data(), toCopy);
782
+ m_receiveBuffer.erase(0, toCopy);
783
+ return toCopy;
784
+ }
785
+
786
+ if (m_tls) {
787
+ std::vector<char> recvBuf;
788
+ if (!m_pendingTlsData.empty()) recvBuf.swap(m_pendingTlsData);
789
+ else {
790
+ recvBuf = receiveEncrypted();
791
+ if (recvBuf.empty()) return -1;
792
+ }
793
+ std::vector<char> allPlaintext;
794
+ while (true) {
795
+ std::vector<char> plaintext;
796
+ int ret = decryptMessage(recvBuf, plaintext);
797
+ if (ret == 1) {
798
+ allPlaintext.insert(allPlaintext.end(), plaintext.begin(), plaintext.end());
799
+ if (!recvBuf.empty()) continue;
800
+ break;
801
+ } else if (ret == 0) {
802
+ if (!recvBuf.empty()) m_pendingTlsData.swap(recvBuf);
803
+ break;
804
+ } else { return -1; }
805
+ }
806
+ int toCopy = std::min(bufSize, static_cast<int>(allPlaintext.size()));
807
+ memcpy(buf, allPlaintext.data(), toCopy);
808
+ if (toCopy < static_cast<int>(allPlaintext.size())) {
809
+ m_receiveBuffer.append(allPlaintext.data() + toCopy, allPlaintext.size() - toCopy);
810
+ }
811
+ return toCopy;
812
+ } else {
813
+ int n = ::recv(m_socket, buf, bufSize, 0);
814
+ return n;
815
+ }
816
+ }
817
+
818
+ } // namespace emailimap