@amazon-devices/expo-file-system 2.0.9000000000 → 3.0.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.
- package/CMakeLists.txt +41 -7
- package/README.kepler.md +1 -0
- package/build/ExponentFileSystem.kepler.d.ts.map +1 -1
- package/build/ExponentFileSystem.kepler.js +28 -45
- package/build/ExponentFileSystem.kepler.js.map +1 -1
- package/build/FileSystem.d.ts.map +1 -1
- package/build/FileSystem.js +0 -2
- package/build/FileSystem.js.map +1 -1
- package/kepler/CurlWrapper.cpp +254 -0
- package/kepler/CurlWrapper.h +56 -0
- package/kepler/ExponentFileSystem.cpp +290 -171
- package/kepler/ExponentFileSystem.h +39 -24
- package/kepler/KeplerInit.cpp +5 -10
- package/lib/aarch64-release/libexpo-file-system-3.so +0 -0
- package/lib/armv7-release/libexpo-file-system-3.so +0 -0
- package/lib/x86_64-release/libexpo-file-system-3.so +0 -0
- package/package.json +1 -1
- package/react-native.config.js +5 -3
- package/src/ExponentFileSystem.kepler.ts +33 -60
- package/src/FileSystem.ts +0 -2
- package/kepler/NetworkingRequestHandler.cpp +0 -47
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* PROPRIETARY/CONFIDENTIAL
|
|
5
|
+
*
|
|
6
|
+
* Use is subject to license terms.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "CurlWrapper.h"
|
|
10
|
+
|
|
11
|
+
#include <Kepler/turbomodule/TMLog.h>
|
|
12
|
+
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <fstream>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <string>
|
|
17
|
+
|
|
18
|
+
static std::once_flag curlInitFlag;
|
|
19
|
+
|
|
20
|
+
CurlWrapper::CurlWrapper() {
|
|
21
|
+
std::call_once(curlInitFlag, []() {
|
|
22
|
+
TMDEBUG("[CurlWrapper] Initializing libcurl (once)");
|
|
23
|
+
curl_global_init(CURL_GLOBAL_ALL);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
CurlWrapper::~CurlWrapper() {
|
|
28
|
+
// curl_global_cleanup() is intentionally not called here.
|
|
29
|
+
// Per libcurl docs, curl_global_init/cleanup are not thread-safe and
|
|
30
|
+
// cleanup should only happen once at application shutdown, not per-instance.
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
size_t CurlWrapper::writeToFileCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
|
34
|
+
auto* file = static_cast<std::ofstream*>(userp);
|
|
35
|
+
size_t totalBytes = size * nmemb;
|
|
36
|
+
file->write(static_cast<const char*>(contents), totalBytes);
|
|
37
|
+
if (file->fail()) {
|
|
38
|
+
TMERROR(std::string("[CurlWrapper] Failed to write ") + std::to_string(totalBytes) + " bytes to file");
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
return totalBytes;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
size_t CurlWrapper::headerCallback(char* buffer, size_t size, size_t nitems, void* userdata) {
|
|
45
|
+
auto* headers = static_cast<std::map<std::string, std::string>*>(userdata);
|
|
46
|
+
size_t totalSize = size * nitems;
|
|
47
|
+
std::string headerLine(buffer, totalSize);
|
|
48
|
+
|
|
49
|
+
auto colonPos = headerLine.find(':');
|
|
50
|
+
if (colonPos != std::string::npos) {
|
|
51
|
+
std::string key = headerLine.substr(0, colonPos);
|
|
52
|
+
std::string value = headerLine.substr(colonPos + 1);
|
|
53
|
+
|
|
54
|
+
auto trimStart = value.find_first_not_of(" \t");
|
|
55
|
+
auto trimEnd = value.find_last_not_of(" \t\r\n");
|
|
56
|
+
if (trimStart != std::string::npos && trimEnd != std::string::npos) {
|
|
57
|
+
value = value.substr(trimStart, trimEnd - trimStart + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
(*headers)[key] = value;
|
|
61
|
+
}
|
|
62
|
+
return totalSize;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
size_t CurlWrapper::readFromFileCallback(void* ptr, size_t size, size_t nmemb, void* userp) {
|
|
66
|
+
auto* file = static_cast<std::ifstream*>(userp);
|
|
67
|
+
file->read(static_cast<char*>(ptr), size * nmemb);
|
|
68
|
+
return file->gcount();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void CurlWrapper::setCommonOptions(
|
|
72
|
+
CURL* curl,
|
|
73
|
+
const std::map<std::string, std::string>& requestHeaders,
|
|
74
|
+
struct curl_slist** headerList) {
|
|
75
|
+
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
76
|
+
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 16L);
|
|
77
|
+
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
|
78
|
+
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
|
|
79
|
+
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
|
80
|
+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
|
81
|
+
// libcurl/OpenSSL are statically linked, so their compiled-in default CA
|
|
82
|
+
// location does not match the device. Point TLS verification at the device
|
|
83
|
+
// CA trust store explicitly.
|
|
84
|
+
curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
|
|
85
|
+
curl_easy_setopt(curl, CURLOPT_CAPATH, "/etc/ssl/certs");
|
|
86
|
+
// Abort if transfer speed drops below 1 byte/sec for 60 seconds
|
|
87
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
|
88
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);
|
|
89
|
+
|
|
90
|
+
for (const auto& [key, value] : requestHeaders) {
|
|
91
|
+
std::string header = key + ": " + value;
|
|
92
|
+
*headerList = curl_slist_append(*headerList, header.c_str());
|
|
93
|
+
}
|
|
94
|
+
if (*headerList) {
|
|
95
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *headerList);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
CurlResponse CurlWrapper::downloadToFile(
|
|
100
|
+
const std::string& url,
|
|
101
|
+
const std::string& filePath,
|
|
102
|
+
const std::map<std::string, std::string>& requestHeaders) {
|
|
103
|
+
CurlResponse response;
|
|
104
|
+
|
|
105
|
+
std::shared_ptr<CURL> curl(curl_easy_init(), curl_easy_cleanup);
|
|
106
|
+
if (!curl) {
|
|
107
|
+
response.errorMessage = "Failed to initialize curl";
|
|
108
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
109
|
+
return response;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
std::ofstream outputFile(filePath, std::ios::binary);
|
|
113
|
+
if (!outputFile.is_open()) {
|
|
114
|
+
response.errorMessage = "Failed to open output file: " + filePath;
|
|
115
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
116
|
+
return response;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
struct curl_slist* headerList = nullptr;
|
|
120
|
+
char errorBuffer[CURL_ERROR_SIZE] = {0};
|
|
121
|
+
|
|
122
|
+
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
|
|
123
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeToFileCallback);
|
|
124
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &outputFile);
|
|
125
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION, headerCallback);
|
|
126
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response.headers);
|
|
127
|
+
curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, errorBuffer);
|
|
128
|
+
setCommonOptions(curl.get(), requestHeaders, &headerList);
|
|
129
|
+
|
|
130
|
+
TMDEBUG(std::string("[CurlWrapper] Starting download: ") + url + " -> " + filePath);
|
|
131
|
+
CURLcode res = curl_easy_perform(curl.get());
|
|
132
|
+
outputFile.close();
|
|
133
|
+
|
|
134
|
+
if (res != CURLE_OK) {
|
|
135
|
+
std::string errorDetail = (errorBuffer[0] != '\0') ? errorBuffer : curl_easy_strerror(res);
|
|
136
|
+
response.errorMessage = "Download failed: " + errorDetail;
|
|
137
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
138
|
+
std::remove(filePath.c_str());
|
|
139
|
+
} else {
|
|
140
|
+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response.statusCode);
|
|
141
|
+
TMDEBUG(std::string("[CurlWrapper] Download complete, HTTP status: ") + std::to_string(response.statusCode));
|
|
142
|
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
143
|
+
response.success = true;
|
|
144
|
+
} else {
|
|
145
|
+
response.errorMessage = "HTTP error: " + std::to_string(response.statusCode);
|
|
146
|
+
std::remove(filePath.c_str());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (headerList) {
|
|
151
|
+
curl_slist_free_all(headerList);
|
|
152
|
+
}
|
|
153
|
+
return response;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
CurlResponse CurlWrapper::uploadFromFile(
|
|
157
|
+
const std::string& url,
|
|
158
|
+
const std::string& filePath,
|
|
159
|
+
const std::string& httpMethod,
|
|
160
|
+
const std::map<std::string, std::string>& requestHeaders) {
|
|
161
|
+
CurlResponse response;
|
|
162
|
+
|
|
163
|
+
std::shared_ptr<CURL> curl(curl_easy_init(), curl_easy_cleanup);
|
|
164
|
+
if (!curl) {
|
|
165
|
+
response.errorMessage = "Failed to initialize curl";
|
|
166
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
167
|
+
return response;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
std::ifstream inputFile(filePath, std::ios::binary | std::ios::ate);
|
|
171
|
+
if (!inputFile.is_open()) {
|
|
172
|
+
response.errorMessage = "Failed to open input file: " + filePath;
|
|
173
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
176
|
+
auto fileSize = inputFile.tellg();
|
|
177
|
+
inputFile.seekg(0, std::ios::beg);
|
|
178
|
+
|
|
179
|
+
// Cap response body at 1MB to prevent OOM when servers echo back uploaded content
|
|
180
|
+
static constexpr size_t MAX_RESPONSE_BODY_SIZE = 1 * 1024 * 1024;
|
|
181
|
+
std::string responseBody;
|
|
182
|
+
bool responseBodyTruncated = false;
|
|
183
|
+
|
|
184
|
+
struct WriteToStringContext {
|
|
185
|
+
std::string* body;
|
|
186
|
+
bool* truncated;
|
|
187
|
+
};
|
|
188
|
+
WriteToStringContext writeCtx{&responseBody, &responseBodyTruncated};
|
|
189
|
+
|
|
190
|
+
auto writeToString = [](void* contents, size_t size, size_t nmemb, void* userp) -> size_t {
|
|
191
|
+
auto* ctx = static_cast<WriteToStringContext*>(userp);
|
|
192
|
+
size_t totalBytes = size * nmemb;
|
|
193
|
+
if (ctx->body->size() + totalBytes > MAX_RESPONSE_BODY_SIZE) {
|
|
194
|
+
*ctx->truncated = true;
|
|
195
|
+
return totalBytes; // Consume but don't store
|
|
196
|
+
}
|
|
197
|
+
ctx->body->append(static_cast<const char*>(contents), totalBytes);
|
|
198
|
+
return totalBytes;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
struct curl_slist* headerList = nullptr;
|
|
202
|
+
char errorBuffer[CURL_ERROR_SIZE] = {0};
|
|
203
|
+
|
|
204
|
+
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
|
|
205
|
+
curl_easy_setopt(curl.get(), CURLOPT_UPLOAD, 1L);
|
|
206
|
+
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, readFromFileCallback);
|
|
207
|
+
curl_easy_setopt(curl.get(), CURLOPT_READDATA, &inputFile);
|
|
208
|
+
curl_easy_setopt(curl.get(), CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(fileSize));
|
|
209
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, +writeToString);
|
|
210
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &writeCtx);
|
|
211
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION, headerCallback);
|
|
212
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response.headers);
|
|
213
|
+
curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, errorBuffer);
|
|
214
|
+
|
|
215
|
+
if (httpMethod == "POST") {
|
|
216
|
+
curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
|
|
217
|
+
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(fileSize));
|
|
218
|
+
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, readFromFileCallback);
|
|
219
|
+
curl_easy_setopt(curl.get(), CURLOPT_READDATA, &inputFile);
|
|
220
|
+
} else if (httpMethod != "PUT") {
|
|
221
|
+
curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST, httpMethod.c_str());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
setCommonOptions(curl.get(), requestHeaders, &headerList);
|
|
225
|
+
|
|
226
|
+
TMDEBUG(std::string("[CurlWrapper] Starting upload: ") + filePath + " -> " + url + " (" + httpMethod + ")");
|
|
227
|
+
CURLcode res = curl_easy_perform(curl.get());
|
|
228
|
+
inputFile.close();
|
|
229
|
+
|
|
230
|
+
if (res != CURLE_OK) {
|
|
231
|
+
std::string errorDetail = (errorBuffer[0] != '\0') ? errorBuffer : curl_easy_strerror(res);
|
|
232
|
+
response.errorMessage = "Upload failed: " + errorDetail;
|
|
233
|
+
TMERROR(std::string("[CurlWrapper] ") + response.errorMessage);
|
|
234
|
+
} else {
|
|
235
|
+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response.statusCode);
|
|
236
|
+
TMDEBUG(std::string("[CurlWrapper] Upload complete, HTTP status: ") + std::to_string(response.statusCode));
|
|
237
|
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
238
|
+
response.success = true;
|
|
239
|
+
} else {
|
|
240
|
+
response.errorMessage = "HTTP error: " + std::to_string(response.statusCode);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (responseBodyTruncated) {
|
|
245
|
+
TMDEBUG(std::string("[CurlWrapper] Response body truncated at ") + std::to_string(MAX_RESPONSE_BODY_SIZE) + "B");
|
|
246
|
+
responseBody += "\n... [truncated]";
|
|
247
|
+
}
|
|
248
|
+
response.headers["__response_body__"] = responseBody;
|
|
249
|
+
|
|
250
|
+
if (headerList) {
|
|
251
|
+
curl_slist_free_all(headerList);
|
|
252
|
+
}
|
|
253
|
+
return response;
|
|
254
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* PROPRIETARY/CONFIDENTIAL
|
|
5
|
+
*
|
|
6
|
+
* Use is subject to license terms.
|
|
7
|
+
*/
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <curl/curl.h>
|
|
11
|
+
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <string>
|
|
14
|
+
|
|
15
|
+
struct CurlResponse {
|
|
16
|
+
long statusCode = 0;
|
|
17
|
+
std::map<std::string, std::string> headers;
|
|
18
|
+
std::string errorMessage;
|
|
19
|
+
bool success = false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* libcurl wrapper for streaming HTTP file transfers directly to/from disk,
|
|
24
|
+
* bypassing the JS heap. Memory footprint is O(buffer_size) not O(file_size).
|
|
25
|
+
*
|
|
26
|
+
* Uses std::call_once for thread-safe one-time curl_global_init().
|
|
27
|
+
* curl_global_cleanup() is intentionally omitted per libcurl docs —
|
|
28
|
+
* it should only be called once at application shutdown.
|
|
29
|
+
*/
|
|
30
|
+
class CurlWrapper {
|
|
31
|
+
public:
|
|
32
|
+
CurlWrapper();
|
|
33
|
+
~CurlWrapper();
|
|
34
|
+
|
|
35
|
+
CurlWrapper(const CurlWrapper&) = delete;
|
|
36
|
+
CurlWrapper& operator=(const CurlWrapper&) = delete;
|
|
37
|
+
|
|
38
|
+
CurlResponse downloadToFile(
|
|
39
|
+
const std::string& url,
|
|
40
|
+
const std::string& filePath,
|
|
41
|
+
const std::map<std::string, std::string>& requestHeaders = {});
|
|
42
|
+
|
|
43
|
+
CurlResponse uploadFromFile(
|
|
44
|
+
const std::string& url,
|
|
45
|
+
const std::string& filePath,
|
|
46
|
+
const std::string& httpMethod,
|
|
47
|
+
const std::map<std::string, std::string>& requestHeaders = {});
|
|
48
|
+
|
|
49
|
+
private:
|
|
50
|
+
static size_t writeToFileCallback(void* contents, size_t size, size_t nmemb, void* userp);
|
|
51
|
+
static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userdata);
|
|
52
|
+
static size_t readFromFileCallback(void* ptr, size_t size, size_t nmemb, void* userp);
|
|
53
|
+
|
|
54
|
+
void setCommonOptions(CURL* curl, const std::map<std::string, std::string>& requestHeaders,
|
|
55
|
+
struct curl_slist** headerList);
|
|
56
|
+
};
|