react-native-email-imap-smtp 0.2.0 → 0.2.1
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.
- package/package.json +1 -1
- package/windows/EmailImapSmtp/EmailImapSmtp.vcxproj +43 -16
- package/windows/EmailImapSmtp/EmailImapSmtpModule.h +400 -45
- package/windows/EmailImapSmtp/ReactPackageProvider.idl +1 -2
- package/windows/EmailImapSmtp/TlsSocket.cpp +138 -15
- package/windows/EmailImapSmtp/TlsSocket.h +3 -3
- package/windows/ExperimentalFeatures.props +3 -1
package/package.json
CHANGED
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
<Project ToolsVersion="Current" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
8
8
|
<Import Project="$(MSBuildThisFileDirectory)..\ExperimentalFeatures.props" Condition="Exists('$(MSBuildThisFileDirectory)..\ExperimentalFeatures.props')" />
|
|
9
9
|
|
|
10
|
+
<ItemGroup Label="ProjectConfigurations">
|
|
11
|
+
<ProjectConfiguration Include="Debug|ARM64">
|
|
12
|
+
<Configuration>Debug</Configuration>
|
|
13
|
+
<Platform>ARM64</Platform>
|
|
14
|
+
</ProjectConfiguration>
|
|
15
|
+
<ProjectConfiguration Include="Debug|Win32">
|
|
16
|
+
<Configuration>Debug</Configuration>
|
|
17
|
+
<Platform>Win32</Platform>
|
|
18
|
+
</ProjectConfiguration>
|
|
19
|
+
<ProjectConfiguration Include="Debug|x64">
|
|
20
|
+
<Configuration>Debug</Configuration>
|
|
21
|
+
<Platform>x64</Platform>
|
|
22
|
+
</ProjectConfiguration>
|
|
23
|
+
<ProjectConfiguration Include="Release|ARM64">
|
|
24
|
+
<Configuration>Release</Configuration>
|
|
25
|
+
<Platform>ARM64</Platform>
|
|
26
|
+
</ProjectConfiguration>
|
|
27
|
+
<ProjectConfiguration Include="Release|Win32">
|
|
28
|
+
<Configuration>Release</Configuration>
|
|
29
|
+
<Platform>Win32</Platform>
|
|
30
|
+
</ProjectConfiguration>
|
|
31
|
+
<ProjectConfiguration Include="Release|x64">
|
|
32
|
+
<Configuration>Release</Configuration>
|
|
33
|
+
<Platform>x64</Platform>
|
|
34
|
+
</ProjectConfiguration>
|
|
35
|
+
</ItemGroup>
|
|
36
|
+
|
|
10
37
|
<PropertyGroup Label="Globals">
|
|
11
38
|
<CppWinRTOptimized>true</CppWinRTOptimized>
|
|
12
39
|
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
|
@@ -21,12 +48,15 @@
|
|
|
21
48
|
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
|
22
49
|
</PropertyGroup>
|
|
23
50
|
|
|
24
|
-
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
25
|
-
|
|
26
51
|
<PropertyGroup Label="ReactNativeWindowsProps">
|
|
27
52
|
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
|
|
28
53
|
</PropertyGroup>
|
|
29
54
|
|
|
55
|
+
<!-- 导入 RNW Windows SDK 默认配置(设置 WindowsTargetPlatformVersion) -->
|
|
56
|
+
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.WindowsSdk.Default.props" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.WindowsSdk.Default.props')" />
|
|
57
|
+
|
|
58
|
+
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
59
|
+
|
|
30
60
|
<PropertyGroup Label="Configuration">
|
|
31
61
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
|
32
62
|
<PlatformToolset>v143</PlatformToolset>
|
|
@@ -51,6 +81,11 @@
|
|
|
51
81
|
<Import Project="PropertySheet.props" />
|
|
52
82
|
</ImportGroup>
|
|
53
83
|
|
|
84
|
+
<!-- 导入 RNW UWP C++ Lib 配置(设置 TargetFrameworkMoniker、AssetTargetFallback、MIDL 引用等) -->
|
|
85
|
+
<ImportGroup Label="ReactNativeWindowsPropertySheets" Condition="'$(ReactNativeWindowsDir)' != ''">
|
|
86
|
+
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.CppLib.props" />
|
|
87
|
+
</ImportGroup>
|
|
88
|
+
|
|
54
89
|
<PropertyGroup Label="UserMacros" />
|
|
55
90
|
|
|
56
91
|
<ItemDefinitionGroup>
|
|
@@ -66,6 +101,7 @@
|
|
|
66
101
|
<AdditionalIncludeDirectories>$(ReactNativeWindowsDir);$(ReactNativeWindowsDir)Microsoft.ReactNative;$(ReactNativeWindowsDir)Microsoft.ReactNative.Cxx;$(ReactNativeWindowsDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
|
67
102
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
|
68
103
|
<PreprocessorDefinitions>NOMINMAX;WIN32_LEAN_AND_MEAN;SECURITY_WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
104
|
+
<AdditionalOptions>%(AdditionalOptions) /GS-</AdditionalOptions>
|
|
69
105
|
</ClCompile>
|
|
70
106
|
<Link>
|
|
71
107
|
<SubSystem>Console</SubSystem>
|
|
@@ -88,6 +124,7 @@
|
|
|
88
124
|
<ClCompile Include="SmtpProtocol.cpp" />
|
|
89
125
|
<ClCompile Include="TlsSocket.cpp" />
|
|
90
126
|
<ClCompile Include="ReactPackageProvider.cpp" />
|
|
127
|
+
<ClCompile Include="Generated Files\module.g.cpp" />
|
|
91
128
|
<ClCompile Include="pch.cpp">
|
|
92
129
|
<PrecompiledHeader>Create</PrecompiledHeader>
|
|
93
130
|
</ClCompile>
|
|
@@ -109,22 +146,12 @@
|
|
|
109
146
|
<Midl Include="ReactPackageProvider.idl" />
|
|
110
147
|
</ItemGroup>
|
|
111
148
|
|
|
112
|
-
<!-- ═══════════════════════════════════════════════════════ -->
|
|
113
|
-
<!-- NuGet 引用 -->
|
|
114
|
-
<!-- ═══════════════════════════════════════════════════════ -->
|
|
115
|
-
|
|
116
|
-
<ItemGroup>
|
|
117
|
-
<PackageReference Include="Microsoft.ReactNative.WindowsSdk">
|
|
118
|
-
<Version>10.0.18362.0</Version>
|
|
119
|
-
</PackageReference>
|
|
120
|
-
<PackageReference Include="Microsoft.Windows.CppWinRT">
|
|
121
|
-
<Version>2.0.240111.5</Version>
|
|
122
|
-
</PackageReference>
|
|
123
|
-
</ItemGroup>
|
|
124
|
-
|
|
125
149
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
126
150
|
|
|
127
|
-
<!-- 导入
|
|
151
|
+
<!-- 导入 RNW UWP C++ Lib targets(自动添加 Microsoft.ReactNative、Microsoft.Windows.CppWinRT 等包引用) -->
|
|
152
|
+
<ImportGroup Label="ReactNativeWindowsTargets" Condition="'$(ReactNativeWindowsDir)' != ''">
|
|
153
|
+
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.CppLib.targets" />
|
|
154
|
+
</ImportGroup>
|
|
128
155
|
<ImportGroup Label="ReactNativeWindowsTargets">
|
|
129
156
|
<Import Project="$(ReactNativeWindowsDir)\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.targets" Condition="Exists('$(ReactNativeWindowsDir)\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.targets')" />
|
|
130
157
|
<Import Project="$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.targets" Condition="Exists('$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.targets')" />
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
#include <unordered_map>
|
|
10
10
|
#include <memory>
|
|
11
11
|
#include <mutex>
|
|
12
|
+
#include <sstream>
|
|
13
|
+
#include <optional>
|
|
14
|
+
#include <thread>
|
|
12
15
|
|
|
13
16
|
#include "winrt/Microsoft.ReactNative.h"
|
|
14
17
|
#include "NativeModules.h"
|
|
@@ -18,6 +21,95 @@
|
|
|
18
21
|
|
|
19
22
|
namespace winrt::EmailImapSmtp {
|
|
20
23
|
|
|
24
|
+
// ─── JSON 序列化辅助 ────────────────────────────────────
|
|
25
|
+
namespace detail {
|
|
26
|
+
|
|
27
|
+
inline std::string escapeJson(const std::string& s) {
|
|
28
|
+
std::string out;
|
|
29
|
+
out.reserve(s.size() + 2);
|
|
30
|
+
for (char c : s) {
|
|
31
|
+
switch (c) {
|
|
32
|
+
case '"': out += "\\\""; break;
|
|
33
|
+
case '\\': out += "\\\\"; break;
|
|
34
|
+
case '\n': out += "\\n"; break;
|
|
35
|
+
case '\r': out += "\\r"; break;
|
|
36
|
+
case '\t': out += "\\t"; break;
|
|
37
|
+
default: out += c;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
inline std::string toJson(const emailimap::MailboxInfo& mb) {
|
|
44
|
+
std::ostringstream os;
|
|
45
|
+
os << "{"
|
|
46
|
+
<< "\"name\":\"" << escapeJson(mb.name) << "\","
|
|
47
|
+
<< "\"path\":\"" << escapeJson(mb.path) << "\","
|
|
48
|
+
<< "\"uidNext\":" << mb.uidNext << ","
|
|
49
|
+
<< "\"uidValidity\":" << mb.uidValidity << ","
|
|
50
|
+
<< "\"messageCount\":" << mb.messageCount << ","
|
|
51
|
+
<< "\"recentCount\":" << mb.recentCount << ","
|
|
52
|
+
<< "\"unseenCount\":" << mb.unseenCount << ","
|
|
53
|
+
<< "\"firstUnseenUid\":" << mb.firstUnseenUid
|
|
54
|
+
<< "}";
|
|
55
|
+
return os.str();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
inline std::string toJson(const emailimap::MailboxListItem& item) {
|
|
59
|
+
std::ostringstream os;
|
|
60
|
+
os << "{"
|
|
61
|
+
<< "\"name\":\"" << escapeJson(item.name) << "\","
|
|
62
|
+
<< "\"path\":\"" << escapeJson(item.path) << "\","
|
|
63
|
+
<< "\"subscribed\":" << (item.subscribed ? "true" : "false")
|
|
64
|
+
<< "}";
|
|
65
|
+
return os.str();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
inline std::string toJson(const emailimap::EmailAddress& addr) {
|
|
69
|
+
std::ostringstream os;
|
|
70
|
+
os << "{"
|
|
71
|
+
<< "\"name\":\"" << escapeJson(addr.name) << "\","
|
|
72
|
+
<< "\"email\":\"" << escapeJson(addr.email) << "\""
|
|
73
|
+
<< "}";
|
|
74
|
+
return os.str();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
inline std::string toJson(const emailimap::AttachmentInfo& att) {
|
|
78
|
+
std::ostringstream os;
|
|
79
|
+
os << "{"
|
|
80
|
+
<< "\"filename\":\"" << escapeJson(att.filename) << "\","
|
|
81
|
+
<< "\"mimeType\":\"" << escapeJson(att.mimeType) << "\","
|
|
82
|
+
<< "\"partId\":\"" << escapeJson(att.partId) << "\","
|
|
83
|
+
<< "\"size\":" << att.size
|
|
84
|
+
<< "}";
|
|
85
|
+
return os.str();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
inline std::string toJson(const emailimap::EmailMessage& msg) {
|
|
89
|
+
std::ostringstream os;
|
|
90
|
+
os << "{"
|
|
91
|
+
<< "\"uid\":" << msg.uid << ","
|
|
92
|
+
<< "\"subject\":\"" << escapeJson(msg.subject) << "\","
|
|
93
|
+
<< "\"from\":" << toJson(msg.from) << ","
|
|
94
|
+
<< "\"date\":\"" << escapeJson(msg.date) << "\","
|
|
95
|
+
<< "\"size\":" << msg.size
|
|
96
|
+
<< "}";
|
|
97
|
+
return os.str();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
template<typename T, typename Fn>
|
|
101
|
+
inline std::string arrayToJson(const std::vector<T>& items, Fn serializer) {
|
|
102
|
+
std::string result = "[";
|
|
103
|
+
for (size_t i = 0; i < items.size(); ++i) {
|
|
104
|
+
if (i > 0) result += ",";
|
|
105
|
+
result += serializer(items[i]);
|
|
106
|
+
}
|
|
107
|
+
result += "]";
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} // namespace detail
|
|
112
|
+
|
|
21
113
|
struct SessionState {
|
|
22
114
|
std::unique_ptr<emailimap::ImapClient> imapClient;
|
|
23
115
|
std::unique_ptr<emailimap::SmtpClient> smtpClient;
|
|
@@ -25,7 +117,7 @@ struct SessionState {
|
|
|
25
117
|
bool smtpConnected = false;
|
|
26
118
|
};
|
|
27
119
|
|
|
28
|
-
REACT_MODULE(EmailImapSmtpModule, "EmailImapSmtp");
|
|
120
|
+
REACT_MODULE(EmailImapSmtpModule, L"EmailImapSmtp");
|
|
29
121
|
struct EmailImapSmtpModule {
|
|
30
122
|
// ─── Session 管理 ─────────────────────────────────────
|
|
31
123
|
std::unordered_map<std::string, std::shared_ptr<SessionState>> m_sessions;
|
|
@@ -54,85 +146,348 @@ struct EmailImapSmtpModule {
|
|
|
54
146
|
m_sessions.erase(id);
|
|
55
147
|
}
|
|
56
148
|
|
|
149
|
+
// ─── 异步辅助 ─────────────────────────────────────────
|
|
150
|
+
// 在后台线程执行网络操作,不阻塞 JS 线程
|
|
151
|
+
template<typename Fn>
|
|
152
|
+
void runAsync(React::ReactPromise<std::string> const& promise, Fn&& fn) noexcept {
|
|
153
|
+
std::thread([promise, fn = std::forward<Fn>(fn)]() {
|
|
154
|
+
try {
|
|
155
|
+
fn();
|
|
156
|
+
} catch (const std::exception& e) {
|
|
157
|
+
std::string err = detail::escapeJson(e.what());
|
|
158
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + err + "\"}");
|
|
159
|
+
} catch (...) {
|
|
160
|
+
promise.Resolve("{\"success\":false,\"error\":\"Unknown error\"}");
|
|
161
|
+
}
|
|
162
|
+
}).detach();
|
|
163
|
+
}
|
|
164
|
+
|
|
57
165
|
// ─── IMAP 连接 ────────────────────────────────────────
|
|
58
166
|
|
|
59
167
|
REACT_METHOD(imapConnect);
|
|
60
|
-
std::string
|
|
61
|
-
|
|
168
|
+
void imapConnect(std::string configJson, React::ReactPromise<std::string> promise) noexcept {
|
|
169
|
+
runAsync(promise, [this, configJson = std::move(configJson), promise]() {
|
|
62
170
|
auto config = emailimap::ConnectionConfig{};
|
|
63
|
-
|
|
64
|
-
|
|
171
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::string {
|
|
172
|
+
std::string search = "\"" + key + "\":\"";
|
|
173
|
+
size_t pos = json.find(search);
|
|
174
|
+
if (pos == std::string::npos) return "";
|
|
175
|
+
pos += search.size();
|
|
176
|
+
size_t end = json.find("\"", pos);
|
|
177
|
+
if (end == std::string::npos) return "";
|
|
178
|
+
return json.substr(pos, end - pos);
|
|
179
|
+
};
|
|
180
|
+
auto extractDbl = [](const std::string& json, const std::string& key, double def) -> double {
|
|
181
|
+
std::string search = "\"" + key + "\":";
|
|
182
|
+
size_t pos = json.find(search);
|
|
183
|
+
if (pos == std::string::npos) return def;
|
|
184
|
+
pos += search.size();
|
|
185
|
+
size_t end = json.find_first_of(",}", pos);
|
|
186
|
+
if (end == std::string::npos) return def;
|
|
187
|
+
try { return std::stod(json.substr(pos, end - pos)); }
|
|
188
|
+
catch (...) { return def; }
|
|
189
|
+
};
|
|
190
|
+
auto extractBool = [](const std::string& json, const std::string& key, bool def) -> bool {
|
|
191
|
+
std::string search = "\"" + key + "\":true";
|
|
192
|
+
if (json.find(search) != std::string::npos) return true;
|
|
193
|
+
search = "\"" + key + "\":false";
|
|
194
|
+
if (json.find(search) != std::string::npos) return false;
|
|
195
|
+
return def;
|
|
196
|
+
};
|
|
197
|
+
config.host = extractStr(configJson, "host");
|
|
198
|
+
config.port = extractDbl(configJson, "port", 993);
|
|
199
|
+
config.username = extractStr(configJson, "username");
|
|
200
|
+
config.password = extractStr(configJson, "password");
|
|
201
|
+
config.tls = extractBool(configJson, "tls", true);
|
|
202
|
+
config.authType = extractStr(configJson, "authType");
|
|
203
|
+
if (config.authType.empty()) config.authType = "password";
|
|
204
|
+
config.timeout = extractDbl(configJson, "timeout", 30000);
|
|
205
|
+
config.checkCertificate = extractBool(configJson, "checkCertificate", true);
|
|
206
|
+
config.debug = extractBool(configJson, "debug", false);
|
|
65
207
|
|
|
66
208
|
auto sessionId = createSession();
|
|
67
209
|
auto session = getSession(sessionId);
|
|
68
210
|
std::string error;
|
|
69
211
|
if (session->imapClient->connect(config, error)) {
|
|
70
212
|
session->imapConnected = true;
|
|
71
|
-
|
|
213
|
+
promise.Resolve("{\"success\":true,\"sessionId\":\"" + sessionId + "\"}");
|
|
214
|
+
return;
|
|
72
215
|
}
|
|
73
216
|
removeSession(sessionId);
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
return "{\"success\":false,\"error\":\"" + escapeJson(e.what()) + "\"}";
|
|
77
|
-
}
|
|
217
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
218
|
+
});
|
|
78
219
|
}
|
|
79
220
|
|
|
80
221
|
REACT_METHOD(imapDisconnect);
|
|
81
|
-
void imapDisconnect(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
session->
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
222
|
+
void imapDisconnect(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
223
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
224
|
+
auto session = getSession(sessionId);
|
|
225
|
+
if (session && session->imapConnected) {
|
|
226
|
+
session->imapClient->disconnect();
|
|
227
|
+
session->imapConnected = false;
|
|
228
|
+
}
|
|
229
|
+
removeSession(sessionId);
|
|
230
|
+
promise.Resolve("{\"success\":true}");
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── IMAP 其他方法 ──────────────────────────────────
|
|
235
|
+
|
|
236
|
+
REACT_METHOD(imapListMailboxes);
|
|
237
|
+
void imapListMailboxes(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
238
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
239
|
+
auto session = getSession(sessionId);
|
|
240
|
+
if (!session || !session->imapConnected) {
|
|
241
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
std::string error;
|
|
245
|
+
auto mailboxes = session->imapClient->listMailboxes(error);
|
|
246
|
+
if (!error.empty()) {
|
|
247
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
promise.Resolve("{\"success\":true,\"mailboxes\":" +
|
|
251
|
+
detail::arrayToJson(mailboxes, [](const emailimap::MailboxListItem& i) { return detail::toJson(i); }) + "}");
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
REACT_METHOD(imapSelectMailbox);
|
|
256
|
+
void imapSelectMailbox(std::string sessionId, std::string mailbox, bool readOnly, React::ReactPromise<std::string> promise) noexcept {
|
|
257
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), mailbox = std::move(mailbox), readOnly, promise]() {
|
|
258
|
+
auto session = getSession(sessionId);
|
|
259
|
+
if (!session || !session->imapConnected) {
|
|
260
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
std::string error;
|
|
264
|
+
auto info = session->imapClient->selectMailbox(mailbox, readOnly, error);
|
|
265
|
+
if (!error.empty()) {
|
|
266
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
promise.Resolve("{\"success\":true,\"mailbox\":" + detail::toJson(info) + "}");
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
REACT_METHOD(imapFetchEmails);
|
|
274
|
+
void imapFetchEmails(std::string sessionId, std::string optionsJson, React::ReactPromise<std::string> promise) noexcept {
|
|
275
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), optionsJson = std::move(optionsJson), promise]() {
|
|
276
|
+
auto session = getSession(sessionId);
|
|
277
|
+
if (!session || !session->imapConnected) {
|
|
278
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
emailimap::FetchEmailsOptions opts;
|
|
282
|
+
auto extractDouble = [](const std::string& json, const std::string& key) -> std::optional<double> {
|
|
283
|
+
std::string search = "\"" + key + "\":";
|
|
284
|
+
size_t pos = json.find(search);
|
|
285
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
286
|
+
pos += search.size();
|
|
287
|
+
size_t end = json.find_first_of(",}", pos);
|
|
288
|
+
if (end == std::string::npos) return std::nullopt;
|
|
289
|
+
std::string val = json.substr(pos, end - pos);
|
|
290
|
+
size_t start = val.find_first_not_of(" \t\r\n\"");
|
|
291
|
+
size_t finish = val.find_last_not_of(" \t\r\n\"");
|
|
292
|
+
if (start == std::string::npos) return std::nullopt;
|
|
293
|
+
val = val.substr(start, finish - start + 1);
|
|
294
|
+
try { return std::stod(val); }
|
|
295
|
+
catch (...) { return std::nullopt; }
|
|
296
|
+
};
|
|
297
|
+
auto extractString = [](const std::string& json, const std::string& key) -> std::optional<std::string> {
|
|
298
|
+
std::string search = "\"" + key + "\":\"";
|
|
299
|
+
size_t pos = json.find(search);
|
|
300
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
301
|
+
pos += search.size();
|
|
302
|
+
size_t end = json.find("\"", pos);
|
|
303
|
+
if (end == std::string::npos) return std::nullopt;
|
|
304
|
+
return json.substr(pos, end - pos);
|
|
305
|
+
};
|
|
306
|
+
auto extractBool = [](const std::string& json, const std::string& key) -> bool {
|
|
307
|
+
std::string search = "\"" + key + "\":true";
|
|
308
|
+
return json.find(search) != std::string::npos;
|
|
309
|
+
};
|
|
310
|
+
auto page = extractDouble(optionsJson, "page");
|
|
311
|
+
auto pageSize = extractDouble(optionsJson, "pageSize");
|
|
312
|
+
auto from = extractDouble(optionsJson, "from");
|
|
313
|
+
auto to = extractDouble(optionsJson, "to");
|
|
314
|
+
if (page.has_value()) opts.page = page.value();
|
|
315
|
+
if (pageSize.has_value()) opts.pageSize = pageSize.value();
|
|
316
|
+
if (from.has_value()) opts.from = from.value();
|
|
317
|
+
if (to.has_value()) opts.to = to.value();
|
|
318
|
+
opts.fetchBody = extractBool(optionsJson, "fetchBody");
|
|
319
|
+
opts.fetchAttachments = extractBool(optionsJson, "fetchAttachments");
|
|
320
|
+
opts.fetchFlags = extractBool(optionsJson, "fetchFlags");
|
|
321
|
+
opts.fetchHeaders = extractBool(optionsJson, "fetchHeaders");
|
|
322
|
+
auto since = extractString(optionsJson, "since");
|
|
323
|
+
auto before = extractString(optionsJson, "before");
|
|
324
|
+
if (since.has_value()) opts.since = since.value();
|
|
325
|
+
if (before.has_value()) opts.before = before.value();
|
|
326
|
+
std::string error;
|
|
327
|
+
auto emails = session->imapClient->fetchEmails(opts, error);
|
|
328
|
+
if (!error.empty()) {
|
|
329
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
promise.Resolve("{\"success\":true,\"emails\":" +
|
|
333
|
+
detail::arrayToJson(emails, [](const emailimap::EmailMessage& e) { return detail::toJson(e); }) + "}");
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
REACT_METHOD(imapFetchEmailByUID);
|
|
338
|
+
void imapFetchEmailByUID(std::string sessionId, double uid, React::ReactPromise<std::string> promise) noexcept {
|
|
339
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), uid, promise]() {
|
|
340
|
+
auto session = getSession(sessionId);
|
|
341
|
+
if (!session || !session->imapConnected) {
|
|
342
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
std::string error;
|
|
346
|
+
auto* msg = session->imapClient->fetchEmailByUID(uid, error);
|
|
347
|
+
if (!msg) {
|
|
348
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
promise.Resolve("{\"success\":true,\"email\":" + detail::toJson(*msg) + "}");
|
|
352
|
+
delete msg;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
REACT_METHOD(imapSearchEmails);
|
|
357
|
+
void imapSearchEmails(std::string sessionId, std::string criteriaJson, React::ReactPromise<std::string> promise) noexcept {
|
|
358
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), criteriaJson = std::move(criteriaJson), promise]() {
|
|
359
|
+
auto session = getSession(sessionId);
|
|
360
|
+
if (!session || !session->imapConnected) {
|
|
361
|
+
promise.Resolve("{\"success\":false,\"error\":\"Not connected\"}");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
emailimap::SearchCriteria criteria;
|
|
365
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::optional<std::string> {
|
|
366
|
+
std::string search = "\"" + key + "\":\"";
|
|
367
|
+
size_t pos = json.find(search);
|
|
368
|
+
if (pos == std::string::npos) return std::nullopt;
|
|
369
|
+
pos += search.size();
|
|
370
|
+
size_t end = json.find("\"", pos);
|
|
371
|
+
if (end == std::string::npos) return std::nullopt;
|
|
372
|
+
return json.substr(pos, end - pos);
|
|
373
|
+
};
|
|
374
|
+
auto extractBool = [](const std::string& json, const std::string& key) -> bool {
|
|
375
|
+
return json.find("\"" + key + "\":true") != std::string::npos;
|
|
376
|
+
};
|
|
377
|
+
auto subject = extractStr(criteriaJson, "subject");
|
|
378
|
+
auto from = extractStr(criteriaJson, "from");
|
|
379
|
+
auto to = extractStr(criteriaJson, "to");
|
|
380
|
+
auto text = extractStr(criteriaJson, "text");
|
|
381
|
+
auto body = extractStr(criteriaJson, "body");
|
|
382
|
+
auto since = extractStr(criteriaJson, "since");
|
|
383
|
+
auto before = extractStr(criteriaJson, "before");
|
|
384
|
+
if (subject.has_value()) criteria.subject = subject.value();
|
|
385
|
+
if (from.has_value()) criteria.from = from.value();
|
|
386
|
+
if (to.has_value()) criteria.to = to.value();
|
|
387
|
+
if (text.has_value()) criteria.text = text.value();
|
|
388
|
+
if (body.has_value()) criteria.body = body.value();
|
|
389
|
+
if (since.has_value()) criteria.since = since.value();
|
|
390
|
+
if (before.has_value()) criteria.before = before.value();
|
|
391
|
+
criteria.flagged = extractBool(criteriaJson, "flagged");
|
|
392
|
+
criteria.seen = extractBool(criteriaJson, "seen");
|
|
393
|
+
std::string error;
|
|
394
|
+
auto emails = session->imapClient->searchEmails(criteria, error);
|
|
395
|
+
if (!error.empty()) {
|
|
396
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
promise.Resolve("{\"success\":true,\"emails\":" +
|
|
400
|
+
detail::arrayToJson(emails, [](const emailimap::EmailMessage& e) { return detail::toJson(e); }) + "}");
|
|
401
|
+
});
|
|
88
402
|
}
|
|
89
403
|
|
|
90
404
|
// ─── SMTP ──────────────────────────────────────────────
|
|
91
405
|
|
|
92
406
|
REACT_METHOD(smtpConnect);
|
|
93
|
-
std::string
|
|
94
|
-
|
|
407
|
+
void smtpConnect(std::string configJson, React::ReactPromise<std::string> promise) noexcept {
|
|
408
|
+
runAsync(promise, [this, configJson = std::move(configJson), promise]() {
|
|
95
409
|
auto config = emailimap::SmtpConnectionConfig{};
|
|
96
|
-
|
|
410
|
+
auto extractStr = [](const std::string& json, const std::string& key) -> std::string {
|
|
411
|
+
std::string search = "\"" + key + "\":\"";
|
|
412
|
+
size_t pos = json.find(search);
|
|
413
|
+
if (pos == std::string::npos) return "";
|
|
414
|
+
pos += search.size();
|
|
415
|
+
size_t end = json.find("\"", pos);
|
|
416
|
+
if (end == std::string::npos) return "";
|
|
417
|
+
return json.substr(pos, end - pos);
|
|
418
|
+
};
|
|
419
|
+
auto extractDbl = [](const std::string& json, const std::string& key, double def) -> double {
|
|
420
|
+
std::string search = "\"" + key + "\":";
|
|
421
|
+
size_t pos = json.find(search);
|
|
422
|
+
if (pos == std::string::npos) return def;
|
|
423
|
+
pos += search.size();
|
|
424
|
+
size_t end = json.find_first_of(",}", pos);
|
|
425
|
+
if (end == std::string::npos) return def;
|
|
426
|
+
try { return std::stod(json.substr(pos, end - pos)); }
|
|
427
|
+
catch (...) { return def; }
|
|
428
|
+
};
|
|
429
|
+
auto extractBool = [](const std::string& json, const std::string& key, bool def) -> bool {
|
|
430
|
+
std::string search = "\"" + key + "\":true";
|
|
431
|
+
if (json.find(search) != std::string::npos) return true;
|
|
432
|
+
search = "\"" + key + "\":false";
|
|
433
|
+
if (json.find(search) != std::string::npos) return false;
|
|
434
|
+
return def;
|
|
435
|
+
};
|
|
436
|
+
config.host = extractStr(configJson, "host");
|
|
437
|
+
config.port = extractDbl(configJson, "port", 465);
|
|
438
|
+
config.username = extractStr(configJson, "username");
|
|
439
|
+
config.password = extractStr(configJson, "password");
|
|
440
|
+
config.tls = extractBool(configJson, "tls", true);
|
|
441
|
+
config.authType = extractStr(configJson, "authType");
|
|
442
|
+
if (config.authType.empty()) config.authType = "password";
|
|
443
|
+
config.timeout = extractDbl(configJson, "timeout", 30000);
|
|
444
|
+
config.checkCertificate = extractBool(configJson, "checkCertificate", true);
|
|
445
|
+
config.debug = extractBool(configJson, "debug", false);
|
|
97
446
|
|
|
98
447
|
auto sessionId = createSession();
|
|
99
448
|
auto session = getSession(sessionId);
|
|
100
449
|
std::string error;
|
|
101
450
|
if (session->smtpClient->connect(config, error)) {
|
|
102
451
|
session->smtpConnected = true;
|
|
103
|
-
|
|
452
|
+
promise.Resolve("{\"success\":true,\"sessionId\":\"" + sessionId + "\"}");
|
|
453
|
+
return;
|
|
104
454
|
}
|
|
105
455
|
removeSession(sessionId);
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
return "{\"success\":false,\"error\":\"" + escapeJson(e.what()) + "\"}";
|
|
109
|
-
}
|
|
456
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
457
|
+
});
|
|
110
458
|
}
|
|
111
459
|
|
|
112
460
|
REACT_METHOD(smtpDisconnect);
|
|
113
|
-
void smtpDisconnect(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
session->
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
461
|
+
void smtpDisconnect(std::string sessionId, React::ReactPromise<std::string> promise) noexcept {
|
|
462
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), promise]() {
|
|
463
|
+
auto session = getSession(sessionId);
|
|
464
|
+
if (session && session->smtpConnected) {
|
|
465
|
+
session->smtpClient->disconnect();
|
|
466
|
+
session->smtpConnected = false;
|
|
467
|
+
}
|
|
468
|
+
removeSession(sessionId);
|
|
469
|
+
promise.Resolve("{\"success\":true}");
|
|
470
|
+
});
|
|
120
471
|
}
|
|
121
472
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
std::
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
case '\n': result += "\\n"; break;
|
|
130
|
-
case '\r': result += "\\r"; break;
|
|
131
|
-
case '\t': result += "\\t"; break;
|
|
132
|
-
default: result += c;
|
|
473
|
+
REACT_METHOD(smtpSendMail);
|
|
474
|
+
void smtpSendMail(std::string sessionId, std::string mailJson, React::ReactPromise<std::string> promise) noexcept {
|
|
475
|
+
runAsync(promise, [this, sessionId = std::move(sessionId), mailJson = std::move(mailJson), promise]() {
|
|
476
|
+
auto session = getSession(sessionId);
|
|
477
|
+
if (!session || !session->smtpConnected) {
|
|
478
|
+
promise.Resolve("{\"success\":false,\"error\":\"SMTP not connected\"}");
|
|
479
|
+
return;
|
|
133
480
|
}
|
|
134
|
-
|
|
135
|
-
|
|
481
|
+
emailimap::SendMailOptions opts;
|
|
482
|
+
emailimap::SendMailResult result;
|
|
483
|
+
// TODO: parse mailJson → opts
|
|
484
|
+
std::string error;
|
|
485
|
+
if (session->smtpClient->sendMail(opts, result, error)) {
|
|
486
|
+
promise.Resolve("{\"success\":true}");
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
promise.Resolve("{\"success\":false,\"error\":\"" + detail::escapeJson(error) + "\"}");
|
|
490
|
+
});
|
|
136
491
|
}
|
|
137
492
|
};
|
|
138
493
|
|
|
@@ -5,6 +5,129 @@
|
|
|
5
5
|
#include "pch.h"
|
|
6
6
|
#include "TlsSocket.h"
|
|
7
7
|
|
|
8
|
+
// Manual SSPI/Schannel declarations for UWP compat (SDK excludes these)
|
|
9
|
+
#pragma comment(lib, "ws2_32.lib")
|
|
10
|
+
#pragma comment(lib, "crypt32.lib")
|
|
11
|
+
#pragma comment(lib, "secur32.lib")
|
|
12
|
+
|
|
13
|
+
#ifndef SCHANNEL_CRED_VERSION
|
|
14
|
+
#define SECURITY_STATUS int
|
|
15
|
+
#define SEC_E_OK 0
|
|
16
|
+
#define SEC_I_CONTINUE_NEEDED 0x00090312L
|
|
17
|
+
#define SEC_E_INCOMPLETE_MESSAGE 0x80090318L
|
|
18
|
+
#define SECPKG_ATTR_STREAM_SIZES 0x00000004
|
|
19
|
+
#define SECPKG_CRED_OUTBOUND 2
|
|
20
|
+
#define SCHANNEL_CRED_VERSION 4
|
|
21
|
+
#define SP_PROT_TLS1_2 0x00000800
|
|
22
|
+
#define SCH_CRED_MANUAL_CRED_VALIDATION 0x00000008
|
|
23
|
+
#define SCH_CRED_NO_SYSTEM_MAPPER 0x00000002
|
|
24
|
+
#define SCH_CRED_NO_SERVERNAME_CHECK 0x00000004
|
|
25
|
+
#define SECBUFFER_VERSION 0
|
|
26
|
+
#define SECBUFFER_EMPTY 0
|
|
27
|
+
#define SECBUFFER_DATA 1
|
|
28
|
+
#define SECBUFFER_TOKEN 2
|
|
29
|
+
#define SECBUFFER_STREAM_HEADER 5
|
|
30
|
+
#define SECBUFFER_STREAM_TRAILER 6
|
|
31
|
+
#define SECBUFFER_EXTRA 9
|
|
32
|
+
#define SECBUFFER_MISSING 11
|
|
33
|
+
#define ISC_REQ_SEQUENCE_DETECT 0x00000008
|
|
34
|
+
#define ISC_REQ_REPLAY_DETECT 0x00000004
|
|
35
|
+
#define ISC_REQ_CONFIDENTIALITY 0x00000010
|
|
36
|
+
#define ISC_REQ_ALLOCATE_MEMORY 0x00000100
|
|
37
|
+
#define ISC_REQ_STREAM 0x00008000
|
|
38
|
+
#define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
|
|
39
|
+
|
|
40
|
+
typedef struct _SecHandle {
|
|
41
|
+
ULONG_PTR dwLower;
|
|
42
|
+
ULONG_PTR dwUpper;
|
|
43
|
+
} SecHandle;
|
|
44
|
+
typedef SecHandle CredHandle;
|
|
45
|
+
typedef SecHandle CtxtHandle;
|
|
46
|
+
|
|
47
|
+
typedef struct _SecBuffer {
|
|
48
|
+
unsigned long cbBuffer;
|
|
49
|
+
unsigned long BufferType;
|
|
50
|
+
void* pvBuffer;
|
|
51
|
+
} SecBuffer;
|
|
52
|
+
|
|
53
|
+
typedef struct _SecBufferDesc {
|
|
54
|
+
unsigned long ulVersion;
|
|
55
|
+
unsigned long cBuffers;
|
|
56
|
+
SecBuffer* pBuffers;
|
|
57
|
+
} SecBufferDesc;
|
|
58
|
+
|
|
59
|
+
typedef struct _SCHANNEL_CRED {
|
|
60
|
+
DWORD dwVersion;
|
|
61
|
+
DWORD cCreds;
|
|
62
|
+
void* paCred;
|
|
63
|
+
void* hRootStore;
|
|
64
|
+
DWORD cMappers;
|
|
65
|
+
void* aphMappers;
|
|
66
|
+
DWORD cSupportedAlgs;
|
|
67
|
+
void* paSupportedAlgs;
|
|
68
|
+
DWORD dwFlags;
|
|
69
|
+
DWORD dwCredFormat;
|
|
70
|
+
DWORD grbitEnabledProtocols;
|
|
71
|
+
DWORD dwMinimumCipherStrength;
|
|
72
|
+
DWORD dwMaximumCipherStrength;
|
|
73
|
+
} SCHANNEL_CRED;
|
|
74
|
+
|
|
75
|
+
typedef struct _SecPkgContext_StreamSizes {
|
|
76
|
+
unsigned long cbHeader;
|
|
77
|
+
unsigned long cbTrailer;
|
|
78
|
+
unsigned long cbMaximumMessage;
|
|
79
|
+
unsigned long cBuffers;
|
|
80
|
+
unsigned long cbBlockSize;
|
|
81
|
+
unsigned long cbMaxTrailer;
|
|
82
|
+
} SecPkgContext_StreamSizes;
|
|
83
|
+
|
|
84
|
+
typedef struct _SecPkgContext_Sizes {
|
|
85
|
+
unsigned long cbMaxToken;
|
|
86
|
+
unsigned long cbMaxSignature;
|
|
87
|
+
unsigned long cbBlockSize;
|
|
88
|
+
unsigned long cbMaxTrailer;
|
|
89
|
+
} SecPkgContext_Sizes;
|
|
90
|
+
|
|
91
|
+
typedef struct _TimeStamp {
|
|
92
|
+
LONG LowPart;
|
|
93
|
+
LONG HighPart;
|
|
94
|
+
} TimeStamp;
|
|
95
|
+
|
|
96
|
+
#ifndef SEC_ENTRY
|
|
97
|
+
#define SEC_ENTRY
|
|
98
|
+
#endif
|
|
99
|
+
extern "C" {
|
|
100
|
+
int SEC_ENTRY AcquireCredentialsHandleW(LPWSTR, LPWSTR, unsigned long, void*, void*, void*, void*, CredHandle*, TimeStamp*);
|
|
101
|
+
int SEC_ENTRY AcquireCredentialsHandleA(LPSTR, LPSTR, unsigned long, void*, void*, void*, void*, CredHandle*, TimeStamp*);
|
|
102
|
+
int SEC_ENTRY InitializeSecurityContextA(CredHandle*, void*, LPSTR, unsigned long, unsigned long, unsigned long, SecBufferDesc*, unsigned long, CtxtHandle*, SecBufferDesc*, unsigned long*, TimeStamp*);
|
|
103
|
+
int SEC_ENTRY InitializeSecurityContextW(CredHandle*, void*, LPWSTR, unsigned long, unsigned long, unsigned long, SecBufferDesc*, unsigned long, CtxtHandle*, SecBufferDesc*, unsigned long*, TimeStamp*);
|
|
104
|
+
int SEC_ENTRY DeleteSecurityContext(CtxtHandle*);
|
|
105
|
+
int SEC_ENTRY FreeCredentialsHandle(CredHandle*);
|
|
106
|
+
int SEC_ENTRY FreeContextBuffer(void*);
|
|
107
|
+
int SEC_ENTRY EncryptMessage(CtxtHandle*, unsigned long, SecBufferDesc*, unsigned long);
|
|
108
|
+
int SEC_ENTRY DecryptMessage(CtxtHandle*, SecBufferDesc*, unsigned long, void*);
|
|
109
|
+
int SEC_ENTRY QueryContextAttributesW(CtxtHandle*, unsigned long, void*);
|
|
110
|
+
int SEC_ENTRY QueryContextAttributesA(CtxtHandle*, unsigned long, void*);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider"
|
|
114
|
+
#define UNISP_NAME UNISP_NAME_A
|
|
115
|
+
#define AcquireCredentialsHandle AcquireCredentialsHandleA
|
|
116
|
+
#define QueryContextAttributes QueryContextAttributesA
|
|
117
|
+
|
|
118
|
+
#endif // SCHANNEL_CRED_VERSION
|
|
119
|
+
|
|
120
|
+
// Cast helpers: uint8_t[] members -> typed references for SSPI API calls
|
|
121
|
+
#ifdef SCHANNEL_CRED_VERSION
|
|
122
|
+
#define credHandle (*reinterpret_cast<CredHandle*>(m_credHandle))
|
|
123
|
+
#define ctxtHandle (*reinterpret_cast<CtxtHandle*>(m_ctxtHandle))
|
|
124
|
+
#define streamSizes (*reinterpret_cast<SecPkgContext_StreamSizes*>(m_streamSizes))
|
|
125
|
+
#else
|
|
126
|
+
#define credHandle m_credHandle
|
|
127
|
+
#define ctxtHandle m_ctxtHandle
|
|
128
|
+
#define streamSizes m_streamSizes
|
|
129
|
+
#endif
|
|
130
|
+
|
|
8
131
|
#include <sstream>
|
|
9
132
|
#include <cstring>
|
|
10
133
|
#include <chrono>
|
|
@@ -250,11 +373,11 @@ bool TlsSocket::connect(const std::string& host, int port, int timeoutMs, bool c
|
|
|
250
373
|
|
|
251
374
|
void TlsSocket::disconnectInternal() {
|
|
252
375
|
if (m_hasCtxtHandle) {
|
|
253
|
-
DeleteSecurityContext(&
|
|
376
|
+
DeleteSecurityContext(&ctxtHandle);
|
|
254
377
|
m_hasCtxtHandle = false;
|
|
255
378
|
}
|
|
256
379
|
if (m_hasCredHandle) {
|
|
257
|
-
FreeCredentialsHandle(&
|
|
380
|
+
FreeCredentialsHandle(&credHandle);
|
|
258
381
|
m_hasCredHandle = false;
|
|
259
382
|
}
|
|
260
383
|
if (m_socket != INVALID_SOCKET) {
|
|
@@ -299,7 +422,7 @@ bool TlsSocket::initializeSchannel() {
|
|
|
299
422
|
&schannelCred,
|
|
300
423
|
nullptr,
|
|
301
424
|
nullptr,
|
|
302
|
-
&
|
|
425
|
+
&credHandle,
|
|
303
426
|
&expiry
|
|
304
427
|
);
|
|
305
428
|
|
|
@@ -338,7 +461,7 @@ bool TlsSocket::performClientHandshake() {
|
|
|
338
461
|
TimeStamp expiry;
|
|
339
462
|
|
|
340
463
|
status = InitializeSecurityContextW(
|
|
341
|
-
&
|
|
464
|
+
&credHandle,
|
|
342
465
|
nullptr,
|
|
343
466
|
const_cast<wchar_t*>(wServerName.c_str()),
|
|
344
467
|
flags,
|
|
@@ -346,7 +469,7 @@ bool TlsSocket::performClientHandshake() {
|
|
|
346
469
|
0,
|
|
347
470
|
nullptr,
|
|
348
471
|
0,
|
|
349
|
-
&
|
|
472
|
+
&ctxtHandle,
|
|
350
473
|
&outDesc,
|
|
351
474
|
&outFlags,
|
|
352
475
|
&expiry
|
|
@@ -405,8 +528,8 @@ bool TlsSocket::performClientHandshake() {
|
|
|
405
528
|
outBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
406
529
|
|
|
407
530
|
status = InitializeSecurityContextW(
|
|
408
|
-
&
|
|
409
|
-
&
|
|
531
|
+
&credHandle,
|
|
532
|
+
&ctxtHandle,
|
|
410
533
|
const_cast<wchar_t*>(wServerName.c_str()),
|
|
411
534
|
flags,
|
|
412
535
|
0,
|
|
@@ -478,7 +601,7 @@ bool TlsSocket::performClientHandshake() {
|
|
|
478
601
|
}
|
|
479
602
|
|
|
480
603
|
if (status == SEC_E_OK) {
|
|
481
|
-
QueryContextAttributes(&
|
|
604
|
+
QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_STREAM_SIZES, &streamSizes);
|
|
482
605
|
m_hasCtxtHandle = true;
|
|
483
606
|
// Save any extra (unconsumed) data to m_receiveBuffer so the caller
|
|
484
607
|
// can read it (e.g. IMAP greeting piggybacked on the handshake).
|
|
@@ -506,22 +629,22 @@ bool TlsSocket::performClientHandshake() {
|
|
|
506
629
|
bool TlsSocket::encryptMessage(const std::string& plaintext, std::vector<char>& ciphertext) {
|
|
507
630
|
if (!m_hasCtxtHandle) return false;
|
|
508
631
|
|
|
509
|
-
int needed =
|
|
632
|
+
int needed = streamSizes.cbHeader + static_cast<int>(plaintext.size()) + streamSizes.cbTrailer;
|
|
510
633
|
std::vector<char> msg(needed);
|
|
511
634
|
|
|
512
635
|
SecBuffer buffers[4];
|
|
513
|
-
buffers[0].cbBuffer =
|
|
636
|
+
buffers[0].cbBuffer = streamSizes.cbHeader;
|
|
514
637
|
buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
515
638
|
buffers[0].pvBuffer = msg.data();
|
|
516
639
|
|
|
517
640
|
buffers[1].cbBuffer = static_cast<unsigned long>(plaintext.size());
|
|
518
641
|
buffers[1].BufferType = SECBUFFER_DATA;
|
|
519
|
-
buffers[1].pvBuffer = msg.data() +
|
|
642
|
+
buffers[1].pvBuffer = msg.data() + streamSizes.cbHeader;
|
|
520
643
|
memcpy(buffers[1].pvBuffer, plaintext.data(), plaintext.size());
|
|
521
644
|
|
|
522
|
-
buffers[2].cbBuffer =
|
|
645
|
+
buffers[2].cbBuffer = streamSizes.cbTrailer;
|
|
523
646
|
buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
524
|
-
buffers[2].pvBuffer = msg.data() +
|
|
647
|
+
buffers[2].pvBuffer = msg.data() + streamSizes.cbHeader + plaintext.size();
|
|
525
648
|
|
|
526
649
|
buffers[3].cbBuffer = 0;
|
|
527
650
|
buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
@@ -531,7 +654,7 @@ bool TlsSocket::encryptMessage(const std::string& plaintext, std::vector<char>&
|
|
|
531
654
|
desc.cBuffers = 4;
|
|
532
655
|
desc.pBuffers = buffers;
|
|
533
656
|
|
|
534
|
-
SECURITY_STATUS status = EncryptMessage(&
|
|
657
|
+
SECURITY_STATUS status = EncryptMessage(&ctxtHandle, 0, &desc, 0);
|
|
535
658
|
if (status != SEC_E_OK) return false;
|
|
536
659
|
|
|
537
660
|
int totalSize = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer;
|
|
@@ -564,7 +687,7 @@ int TlsSocket::decryptMessage(std::vector<char>& buffer, std::vector<char>& plai
|
|
|
564
687
|
desc.cBuffers = 4;
|
|
565
688
|
desc.pBuffers = buffers;
|
|
566
689
|
|
|
567
|
-
SECURITY_STATUS status = DecryptMessage(&
|
|
690
|
+
SECURITY_STATUS status = DecryptMessage(&ctxtHandle, &desc, 0, nullptr);
|
|
568
691
|
if (status == SEC_E_INCOMPLETE_MESSAGE) {
|
|
569
692
|
// need more data
|
|
570
693
|
return 0; // need more data
|
|
@@ -58,13 +58,13 @@ public:
|
|
|
58
58
|
private:
|
|
59
59
|
SOCKET m_socket = INVALID_SOCKET;
|
|
60
60
|
bool m_tls = false;
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
uint8_t m_credHandle[16];
|
|
62
|
+
uint8_t m_ctxtHandle[16];
|
|
63
63
|
bool m_hasCredHandle = false;
|
|
64
64
|
bool m_hasCtxtHandle = false;
|
|
65
65
|
std::string m_serverName;
|
|
66
66
|
bool m_checkCertificate = true;
|
|
67
|
-
|
|
67
|
+
uint8_t m_streamSizes[24];
|
|
68
68
|
|
|
69
69
|
std::mutex m_mutex;
|
|
70
70
|
int m_lastError = 0; // stores WSAGetLastError() on failure
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
<!-- 不使用 WinUI 3 -->
|
|
7
7
|
<UseWinUI3>false</UseWinUI3>
|
|
8
8
|
<!-- 不使用实验性 NuGet -->
|
|
9
|
-
<UseExperimentalNuget>
|
|
9
|
+
<UseExperimentalNuget>true</UseExperimentalNuget>
|
|
10
|
+
<RnwNewArch>true</RnwNewArch>
|
|
11
|
+
<RunCodegenWindows>false</RunCodegenWindows>
|
|
10
12
|
<!-- 不使用 Folly (不再需要) -->
|
|
11
13
|
<UseFolly>false</UseFolly>
|
|
12
14
|
</PropertyGroup>
|