napi_init.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. #include "napi/native_api.h"
  2. #include <algorithm>
  3. #include <atomic>
  4. #include <cctype>
  5. #include <cstdint>
  6. #include <cstdio>
  7. #include <cstring>
  8. #include <filesystem>
  9. #include <fcntl.h>
  10. #include <system_error>
  11. #include <memory>
  12. #include <mutex>
  13. #include <stdexcept>
  14. #include <string>
  15. #include <unordered_map>
  16. #include <vector>
  17. #include "hilog/log.h"
  18. extern "C" {
  19. #include "smb2/smb2.h"
  20. #include "smb2/libsmb2.h"
  21. }
  22. namespace {
  23. constexpr unsigned int SMB_LOG_DOMAIN = 0xD001780;
  24. constexpr const char *SMB_LOG_TAG = "libsmb2";
  25. #define SMB_LOGI(fmt, ...) OH_LOG_Print(LOG_APP, LOG_INFO, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__)
  26. #define SMB_LOGE(fmt, ...) OH_LOG_Print(LOG_APP, LOG_ERROR, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__)
  27. inline void NapiCheck(napi_status status, const char *message)
  28. {
  29. if (status != napi_ok) {
  30. throw std::runtime_error(message == nullptr ? "NAPI call failed" : message);
  31. }
  32. }
  33. struct SambaClient {
  34. int64_t id = 0;
  35. std::string host;
  36. smb2_context *ctx = nullptr;
  37. bool closed = false;
  38. };
  39. struct SambaSession {
  40. int64_t id = 0;
  41. std::weak_ptr<SambaClient> client;
  42. std::string username;
  43. std::string password;
  44. std::string domain;
  45. bool closed = false;
  46. int64_t activeTreeId = 0;
  47. };
  48. struct SambaTree {
  49. int64_t id = 0;
  50. std::weak_ptr<SambaSession> session;
  51. std::string share;
  52. bool connected = false;
  53. };
  54. std::atomic<int64_t> g_nextHandle{1};
  55. std::mutex g_mutex;
  56. std::unordered_map<int64_t, std::shared_ptr<SambaClient>> g_clients;
  57. std::unordered_map<int64_t, std::shared_ptr<SambaSession>> g_sessions;
  58. std::unordered_map<int64_t, std::shared_ptr<SambaTree>> g_trees;
  59. int64_t GenerateHandle()
  60. {
  61. return g_nextHandle.fetch_add(1);
  62. }
  63. void Ensure(bool condition, const std::string &message)
  64. {
  65. if (!condition) {
  66. throw std::runtime_error(message);
  67. }
  68. }
  69. std::string Trim(const std::string &value)
  70. {
  71. if (value.empty()) {
  72. return value;
  73. }
  74. size_t start = 0;
  75. size_t end = value.size();
  76. while (start < end && std::isspace(static_cast<unsigned char>(value[start])) != 0) {
  77. start++;
  78. }
  79. if (start == end) {
  80. return "";
  81. }
  82. while (end > start && std::isspace(static_cast<unsigned char>(value[end - 1])) != 0) {
  83. end--;
  84. }
  85. return value.substr(start, end - start);
  86. }
  87. std::string NormalizeRemotePath(const std::string &raw)
  88. {
  89. std::string trimmed = Trim(raw);
  90. if (trimmed.empty() || trimmed == "/" || trimmed == "\\") {
  91. return "";
  92. }
  93. size_t index = 0;
  94. while (index < trimmed.size() && (trimmed[index] == '/' || trimmed[index] == '\\')) {
  95. index++;
  96. }
  97. std::string normalized = trimmed.substr(index);
  98. if (normalized.empty()) {
  99. return "";
  100. }
  101. std::replace(normalized.begin(), normalized.end(), '\\', '/');
  102. return normalized;
  103. }
  104. std::string BuildSmbErrorMessage(smb2_context *ctx, const std::string &fallback)
  105. {
  106. const char *error = ctx != nullptr ? smb2_get_error(ctx) : nullptr;
  107. if (error == nullptr || std::strlen(error) == 0) {
  108. return fallback;
  109. }
  110. return fallback + ": " + error;
  111. }
  112. std::string ReadString(napi_env env, napi_value value, const char *name, bool required)
  113. {
  114. napi_valuetype type = napi_undefined;
  115. NapiCheck(napi_typeof(env, value, &type), "Failed to read argument type");
  116. if (type == napi_null || type == napi_undefined) {
  117. if (required) {
  118. throw std::runtime_error(std::string(name) + " is required");
  119. }
  120. return "";
  121. }
  122. if (type != napi_string) {
  123. throw std::runtime_error(std::string(name) + " must be a string");
  124. }
  125. size_t length = 0;
  126. NapiCheck(napi_get_value_string_utf8(env, value, nullptr, 0, &length), "Failed to measure string length");
  127. std::string result(length, '\0');
  128. size_t written = 0;
  129. NapiCheck(napi_get_value_string_utf8(env, value, result.data(), length + 1, &written), "Failed to read string value");
  130. result.resize(written);
  131. return result;
  132. }
  133. int64_t ReadInt64(napi_env env, napi_value value, const char *name)
  134. {
  135. napi_valuetype type = napi_undefined;
  136. NapiCheck(napi_typeof(env, value, &type), "Failed to read numeric argument type");
  137. if (type != napi_number) {
  138. throw std::runtime_error(std::string(name) + " must be a number");
  139. }
  140. int64_t result = 0;
  141. NapiCheck(napi_get_value_int64(env, value, &result), "Failed to read numeric argument");
  142. return result;
  143. }
  144. napi_value CreateInt64Value(napi_env env, int64_t value)
  145. {
  146. napi_value result = nullptr;
  147. NapiCheck(napi_create_int64(env, value, &result), "Failed to create int value");
  148. return result;
  149. }
  150. napi_value CreateUndefined(napi_env env)
  151. {
  152. napi_value result = nullptr;
  153. NapiCheck(napi_get_undefined(env, &result), "Failed to create undefined value");
  154. return result;
  155. }
  156. std::shared_ptr<SambaClient> RequireClient(int64_t clientId)
  157. {
  158. std::lock_guard<std::mutex> lock(g_mutex);
  159. auto it = g_clients.find(clientId);
  160. if (it == g_clients.end() || it->second == nullptr || it->second->closed) {
  161. throw std::runtime_error("Invalid SMB client handle");
  162. }
  163. return it->second;
  164. }
  165. std::shared_ptr<SambaSession> RequireSession(int64_t sessionId)
  166. {
  167. std::lock_guard<std::mutex> lock(g_mutex);
  168. auto it = g_sessions.find(sessionId);
  169. if (it == g_sessions.end() || it->second == nullptr || it->second->closed) {
  170. throw std::runtime_error("Invalid SMB session handle");
  171. }
  172. return it->second;
  173. }
  174. std::shared_ptr<SambaTree> RequireTree(int64_t treeId)
  175. {
  176. std::lock_guard<std::mutex> lock(g_mutex);
  177. auto it = g_trees.find(treeId);
  178. if (it == g_trees.end() || it->second == nullptr) {
  179. throw std::runtime_error("Invalid SMB tree handle");
  180. }
  181. return it->second;
  182. }
  183. void RemoveTreeEntry(int64_t treeId)
  184. {
  185. std::lock_guard<std::mutex> lock(g_mutex);
  186. g_trees.erase(treeId);
  187. }
  188. void ResetSessionTree(const std::shared_ptr<SambaSession> &session, int64_t treeId)
  189. {
  190. if (session == nullptr) {
  191. return;
  192. }
  193. std::lock_guard<std::mutex> lock(g_mutex);
  194. if (session->activeTreeId == treeId) {
  195. session->activeTreeId = 0;
  196. }
  197. }
  198. void CloseTreeInternal(const std::shared_ptr<SambaTree> &tree)
  199. {
  200. if (!tree) {
  201. return;
  202. }
  203. auto session = tree->session.lock();
  204. auto client = session ? session->client.lock() : nullptr;
  205. if (client && client->ctx && tree->connected) {
  206. smb2_disconnect_share(client->ctx);
  207. }
  208. tree->connected = false;
  209. ResetSessionTree(session, tree->id);
  210. }
  211. void RemoveTreesForSession(int64_t sessionId)
  212. {
  213. std::vector<std::shared_ptr<SambaTree>> ownedTrees;
  214. {
  215. std::lock_guard<std::mutex> lock(g_mutex);
  216. for (auto it = g_trees.begin(); it != g_trees.end(); ) {
  217. auto session = it->second ? it->second->session.lock() : nullptr;
  218. if (!session || session->id == sessionId) {
  219. ownedTrees.push_back(it->second);
  220. it = g_trees.erase(it);
  221. continue;
  222. }
  223. ++it;
  224. }
  225. }
  226. for (const auto &tree : ownedTrees) {
  227. CloseTreeInternal(tree);
  228. }
  229. }
  230. void RemoveSessionsForClient(int64_t clientId)
  231. {
  232. std::vector<std::shared_ptr<SambaSession>> sessions;
  233. {
  234. std::lock_guard<std::mutex> lock(g_mutex);
  235. for (auto it = g_sessions.begin(); it != g_sessions.end(); ) {
  236. auto client = it->second ? it->second->client.lock() : nullptr;
  237. if (!client || client->id == clientId) {
  238. it->second->closed = true;
  239. sessions.push_back(it->second);
  240. it = g_sessions.erase(it);
  241. continue;
  242. }
  243. ++it;
  244. }
  245. }
  246. for (const auto &session : sessions) {
  247. RemoveTreesForSession(session->id);
  248. }
  249. }
  250. void CloseClientContext(const std::shared_ptr<SambaClient> &client)
  251. {
  252. if (!client || client->ctx == nullptr) {
  253. return;
  254. }
  255. smb2_disconnect_share(client->ctx);
  256. smb2_destroy_context(client->ctx);
  257. client->ctx = nullptr;
  258. client->closed = true;
  259. }
  260. napi_value CreateClient(napi_env env, napi_callback_info info)
  261. {
  262. try {
  263. size_t argc = 1;
  264. napi_value args[1] = {nullptr};
  265. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for createClient");
  266. if (argc < 1) {
  267. throw std::runtime_error("host argument is required");
  268. }
  269. std::string host = Trim(ReadString(env, args[0], "host", true));
  270. Ensure(!host.empty(), "host must not be empty");
  271. smb2_context *ctx = smb2_init_context();
  272. if (ctx == nullptr) {
  273. throw std::runtime_error("Unable to initialize libsmb2 context");
  274. }
  275. smb2_set_timeout(ctx, 15);
  276. smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
  277. auto client = std::make_shared<SambaClient>();
  278. client->id = GenerateHandle();
  279. client->host = host;
  280. client->ctx = ctx;
  281. {
  282. std::lock_guard<std::mutex> lock(g_mutex);
  283. g_clients[client->id] = client;
  284. }
  285. return CreateInt64Value(env, client->id);
  286. } catch (const std::exception &error) {
  287. napi_throw_error(env, nullptr, error.what());
  288. return nullptr;
  289. }
  290. }
  291. napi_value DestroyClient(napi_env env, napi_callback_info info)
  292. {
  293. try {
  294. size_t argc = 1;
  295. napi_value args[1] = {nullptr};
  296. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for destroyClient");
  297. if (argc < 1) {
  298. return CreateUndefined(env);
  299. }
  300. int64_t clientId = ReadInt64(env, args[0], "clientId");
  301. std::shared_ptr<SambaClient> client;
  302. {
  303. std::lock_guard<std::mutex> lock(g_mutex);
  304. auto it = g_clients.find(clientId);
  305. if (it != g_clients.end()) {
  306. client = it->second;
  307. g_clients.erase(it);
  308. }
  309. }
  310. if (client) {
  311. RemoveSessionsForClient(client->id);
  312. CloseClientContext(client);
  313. }
  314. return CreateUndefined(env);
  315. } catch (const std::exception &error) {
  316. napi_throw_error(env, nullptr, error.what());
  317. return nullptr;
  318. }
  319. }
  320. napi_value Authenticate(napi_env env, napi_callback_info info)
  321. {
  322. try {
  323. size_t argc = 4;
  324. napi_value args[4] = {nullptr};
  325. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for authenticate");
  326. if (argc < 3) {
  327. throw std::runtime_error("authenticate requires clientId, username and password");
  328. }
  329. int64_t clientId = ReadInt64(env, args[0], "clientId");
  330. std::string username = Trim(ReadString(env, args[1], "username", true));
  331. std::string password = ReadString(env, args[2], "password", true);
  332. std::string domain;
  333. if (argc >= 4) {
  334. domain = Trim(ReadString(env, args[3], "domain", false));
  335. }
  336. Ensure(!username.empty(), "username must not be empty");
  337. auto client = RequireClient(clientId);
  338. auto session = std::make_shared<SambaSession>();
  339. session->id = GenerateHandle();
  340. session->client = client;
  341. session->username = username;
  342. session->password = password;
  343. session->domain = domain;
  344. {
  345. std::lock_guard<std::mutex> lock(g_mutex);
  346. g_sessions[session->id] = session;
  347. }
  348. return CreateInt64Value(env, session->id);
  349. } catch (const std::exception &error) {
  350. napi_throw_error(env, nullptr, error.what());
  351. return nullptr;
  352. }
  353. }
  354. napi_value DisconnectSession(napi_env env, napi_callback_info info)
  355. {
  356. try {
  357. size_t argc = 1;
  358. napi_value args[1] = {nullptr};
  359. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectSession");
  360. if (argc < 1) {
  361. return CreateUndefined(env);
  362. }
  363. int64_t sessionId = ReadInt64(env, args[0], "sessionId");
  364. std::shared_ptr<SambaSession> session;
  365. {
  366. std::lock_guard<std::mutex> lock(g_mutex);
  367. auto it = g_sessions.find(sessionId);
  368. if (it != g_sessions.end()) {
  369. session = it->second;
  370. g_sessions.erase(it);
  371. }
  372. }
  373. if (session) {
  374. session->closed = true;
  375. RemoveTreesForSession(session->id);
  376. }
  377. return CreateUndefined(env);
  378. } catch (const std::exception &error) {
  379. napi_throw_error(env, nullptr, error.what());
  380. return nullptr;
  381. }
  382. }
  383. napi_value ConnectTree(napi_env env, napi_callback_info info)
  384. {
  385. try {
  386. size_t argc = 2;
  387. napi_value args[2] = {nullptr};
  388. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for connectTree");
  389. if (argc < 2) {
  390. throw std::runtime_error("connectTree requires sessionId and share name");
  391. }
  392. int64_t sessionId = ReadInt64(env, args[0], "sessionId");
  393. std::string share = Trim(ReadString(env, args[1], "share", true));
  394. Ensure(!share.empty(), "share must not be empty");
  395. auto session = RequireSession(sessionId);
  396. auto client = session->client.lock();
  397. if (!client || client->closed) {
  398. throw std::runtime_error("SMB client is not available");
  399. }
  400. Ensure(session->activeTreeId == 0, "Session already has an active tree");
  401. smb2_context *ctx = client->ctx;
  402. if (ctx == nullptr) {
  403. throw std::runtime_error("SMB context is not initialized");
  404. }
  405. smb2_set_user(ctx, session->username.c_str());
  406. smb2_set_password(ctx, session->password.c_str());
  407. smb2_set_domain(ctx, session->domain.empty() ? nullptr : session->domain.c_str());
  408. int rc = smb2_connect_share(ctx, client->host.c_str(), share.c_str(), session->username.c_str());
  409. if (rc != 0) {
  410. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
  411. }
  412. auto tree = std::make_shared<SambaTree>();
  413. tree->id = GenerateHandle();
  414. tree->session = session;
  415. tree->share = share;
  416. tree->connected = true;
  417. {
  418. std::lock_guard<std::mutex> lock(g_mutex);
  419. session->activeTreeId = tree->id;
  420. g_trees[tree->id] = tree;
  421. }
  422. return CreateInt64Value(env, tree->id);
  423. } catch (const std::exception &error) {
  424. napi_throw_error(env, nullptr, error.what());
  425. return nullptr;
  426. }
  427. }
  428. napi_value DisconnectTree(napi_env env, napi_callback_info info)
  429. {
  430. try {
  431. size_t argc = 1;
  432. napi_value args[1] = {nullptr};
  433. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectTree");
  434. if (argc < 1) {
  435. return CreateUndefined(env);
  436. }
  437. int64_t treeId = ReadInt64(env, args[0], "treeId");
  438. std::shared_ptr<SambaTree> tree;
  439. {
  440. std::lock_guard<std::mutex> lock(g_mutex);
  441. auto it = g_trees.find(treeId);
  442. if (it != g_trees.end()) {
  443. tree = it->second;
  444. g_trees.erase(it);
  445. }
  446. }
  447. CloseTreeInternal(tree);
  448. return CreateUndefined(env);
  449. } catch (const std::exception &error) {
  450. napi_throw_error(env, nullptr, error.what());
  451. return nullptr;
  452. }
  453. }
  454. napi_value ReadDirectory(napi_env env, napi_callback_info info)
  455. {
  456. try {
  457. size_t argc = 2;
  458. napi_value args[2] = {nullptr};
  459. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readDirectory");
  460. if (argc < 2) {
  461. throw std::runtime_error("readDirectory requires treeId and remote path");
  462. }
  463. int64_t treeId = ReadInt64(env, args[0], "treeId");
  464. std::string path = ReadString(env, args[1], "path", false);
  465. auto tree = RequireTree(treeId);
  466. auto session = tree->session.lock();
  467. auto client = session ? session->client.lock() : nullptr;
  468. if (!client || client->ctx == nullptr) {
  469. throw std::runtime_error("SMB client context is not available");
  470. }
  471. Ensure(tree->connected, "SMB tree is not connected");
  472. std::string normalized = NormalizeRemotePath(path);
  473. SMB_LOGI("readDirectory invoked tree=%{public}lld path=%{public}s", static_cast<long long>(treeId), normalized.c_str());
  474. smb2_context *ctx = client->ctx;
  475. smb2dir *dir = smb2_opendir(ctx, normalized.c_str());
  476. if (dir == nullptr) {
  477. const char *err = ctx != nullptr ? smb2_get_error(ctx) : "";
  478. SMB_LOGE("smb2_opendir failed tree=%{public}lld path=%{public}s err=%{public}s", static_cast<long long>(treeId), normalized.c_str(), err ? err : "");
  479. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote directory"));
  480. }
  481. std::vector<napi_value> entries;
  482. struct smb2dirent *dirent = nullptr;
  483. while ((dirent = smb2_readdir(ctx, dir)) != nullptr) {
  484. if (!dirent->name) {
  485. continue;
  486. }
  487. if (std::strcmp(dirent->name, ".") == 0 || std::strcmp(dirent->name, "..") == 0) {
  488. continue;
  489. }
  490. napi_value entry = nullptr;
  491. NapiCheck(napi_create_object(env, &entry), "Failed to create directory entry object");
  492. napi_value nameValue = nullptr;
  493. NapiCheck(napi_create_string_utf8(env, dirent->name, NAPI_AUTO_LENGTH, &nameValue), "Failed to create directory name");
  494. NapiCheck(napi_set_named_property(env, entry, "name", nameValue), "Failed to set name property");
  495. NapiCheck(napi_set_named_property(env, entry, "fileName", nameValue), "Failed to set fileName property");
  496. bool isDirectory = dirent->st.smb2_type == SMB2_TYPE_DIRECTORY;
  497. bool isFile = dirent->st.smb2_type == SMB2_TYPE_FILE;
  498. napi_value dirValue = nullptr;
  499. NapiCheck(napi_get_boolean(env, isDirectory, &dirValue), "Failed to create directory flag");
  500. NapiCheck(napi_set_named_property(env, entry, "isDirectory", dirValue), "Failed to set directory flag");
  501. napi_value fileValue = nullptr;
  502. NapiCheck(napi_get_boolean(env, isFile, &fileValue), "Failed to create file flag");
  503. NapiCheck(napi_set_named_property(env, entry, "isFile", fileValue), "Failed to set file flag");
  504. napi_value sizeValue = nullptr;
  505. NapiCheck(napi_create_double(env, static_cast<double>(dirent->st.smb2_size), &sizeValue), "Failed to create size value");
  506. NapiCheck(napi_set_named_property(env, entry, "size", sizeValue), "Failed to set size property");
  507. entries.push_back(entry);
  508. }
  509. smb2_closedir(ctx, dir);
  510. napi_value result = nullptr;
  511. NapiCheck(napi_create_array_with_length(env, entries.size(), &result), "Failed to create directory result array");
  512. for (size_t i = 0; i < entries.size(); i++) {
  513. NapiCheck(napi_set_element(env, result, i, entries[i]), "Failed to append directory entry");
  514. }
  515. return result;
  516. } catch (const std::exception &error) {
  517. napi_throw_error(env, nullptr, error.what());
  518. return nullptr;
  519. }
  520. }
  521. napi_value DownloadSmbFile(napi_env env, napi_callback_info info)
  522. {
  523. try {
  524. size_t argc = 7;
  525. napi_value args[7] = {nullptr};
  526. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for downloadSmbFile");
  527. if (argc < 7) {
  528. throw std::runtime_error("downloadSmbFile requires host, share, username, password, domain, remotePath and localPath");
  529. }
  530. std::string host = ReadString(env, args[0], "host", true);
  531. std::string share = ReadString(env, args[1], "share", true);
  532. std::string username = ReadString(env, args[2], "username", true);
  533. std::string password = ReadString(env, args[3], "password", true);
  534. std::string domain = ReadString(env, args[4], "domain", false);
  535. std::string remotePath = ReadString(env, args[5], "remotePath", true);
  536. std::string localPath = ReadString(env, args[6], "localPath", true);
  537. if (share.empty()) {
  538. throw std::runtime_error("share must not be empty");
  539. }
  540. if (localPath.empty()) {
  541. throw std::runtime_error("localPath must not be empty");
  542. }
  543. std::string normalizedRemotePath = NormalizeRemotePath(remotePath);
  544. if (normalizedRemotePath.empty()) {
  545. throw std::runtime_error("remotePath must not be empty");
  546. }
  547. std::error_code fsError;
  548. std::filesystem::path targetPath(localPath);
  549. auto parent = targetPath.parent_path();
  550. if (!parent.empty()) {
  551. std::filesystem::create_directories(parent, fsError);
  552. if (fsError) {
  553. throw std::runtime_error("Failed to prepare cache directory: " + fsError.message());
  554. }
  555. }
  556. FILE *output = std::fopen(localPath.c_str(), "wb");
  557. if (output == nullptr) {
  558. throw std::runtime_error("Unable to open local file for writing");
  559. }
  560. smb2_context *ctx = smb2_init_context();
  561. if (ctx == nullptr) {
  562. std::fclose(output);
  563. throw std::runtime_error("Unable to initialize libsmb2 context");
  564. }
  565. auto cleanupContext = [&ctx]() {
  566. if (ctx != nullptr) {
  567. smb2_disconnect_share(ctx);
  568. smb2_destroy_context(ctx);
  569. ctx = nullptr;
  570. }
  571. };
  572. auto cleanupFile = [&output]() {
  573. if (output != nullptr) {
  574. std::fclose(output);
  575. output = nullptr;
  576. }
  577. };
  578. auto removeCacheFile = [&localPath]() {
  579. std::remove(localPath.c_str());
  580. };
  581. smb2_set_timeout(ctx, 30);
  582. smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
  583. smb2_set_user(ctx, username.c_str());
  584. smb2_set_password(ctx, password.c_str());
  585. smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str());
  586. int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str());
  587. if (rc != 0) {
  588. cleanupFile();
  589. cleanupContext();
  590. removeCacheFile();
  591. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
  592. }
  593. smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY);
  594. if (fileHandle == nullptr) {
  595. cleanupFile();
  596. cleanupContext();
  597. removeCacheFile();
  598. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file"));
  599. }
  600. const size_t BUFFER_SIZE = 64 * 1024;
  601. std::vector<uint8_t> buffer(BUFFER_SIZE);
  602. int bytesRead = 0;
  603. while ((bytesRead = smb2_read(ctx, fileHandle, buffer.data(), static_cast<uint32_t>(buffer.size()))) > 0) {
  604. size_t written = std::fwrite(buffer.data(), 1, static_cast<size_t>(bytesRead), output);
  605. if (written != static_cast<size_t>(bytesRead)) {
  606. smb2_close(ctx, fileHandle);
  607. cleanupFile();
  608. cleanupContext();
  609. removeCacheFile();
  610. throw std::runtime_error("Failed to write to local file");
  611. }
  612. }
  613. if (bytesRead < 0) {
  614. std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote file");
  615. smb2_close(ctx, fileHandle);
  616. cleanupFile();
  617. cleanupContext();
  618. removeCacheFile();
  619. throw std::runtime_error(message);
  620. }
  621. smb2_close(ctx, fileHandle);
  622. cleanupFile();
  623. cleanupContext();
  624. return CreateUndefined(env);
  625. } catch (const std::exception &error) {
  626. napi_throw_error(env, nullptr, error.what());
  627. return nullptr;
  628. }
  629. }
  630. napi_value ReadSmbFileRange(napi_env env, napi_callback_info info)
  631. {
  632. constexpr size_t MAX_RANGE_SIZE = 2 * 1024 * 1024;
  633. constexpr size_t BUFFER_SIZE = 64 * 1024;
  634. try {
  635. size_t argc = 8;
  636. napi_value args[8] = {nullptr};
  637. NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readSmbFileRange");
  638. if (argc < 8) {
  639. throw std::runtime_error("readSmbFileRange requires host, share, username, password, domain, remotePath, offset and length");
  640. }
  641. std::string host = ReadString(env, args[0], "host", true);
  642. std::string share = ReadString(env, args[1], "share", true);
  643. std::string username = ReadString(env, args[2], "username", true);
  644. std::string password = ReadString(env, args[3], "password", true);
  645. std::string domain = ReadString(env, args[4], "domain", false);
  646. std::string remotePath = ReadString(env, args[5], "remotePath", true);
  647. int64_t offsetValue = ReadInt64(env, args[6], "offset");
  648. int64_t lengthValue = ReadInt64(env, args[7], "length");
  649. if (offsetValue < 0) {
  650. offsetValue = 0;
  651. }
  652. if (lengthValue <= 0) {
  653. napi_value emptyResult = nullptr;
  654. void *tmp = nullptr;
  655. NapiCheck(napi_create_arraybuffer(env, 0, &tmp, &emptyResult), "Failed to create empty buffer");
  656. return emptyResult;
  657. }
  658. size_t requestedLength = static_cast<size_t>(lengthValue);
  659. if (requestedLength > MAX_RANGE_SIZE) {
  660. requestedLength = MAX_RANGE_SIZE;
  661. }
  662. smb2_context *ctx = smb2_init_context();
  663. if (ctx == nullptr) {
  664. throw std::runtime_error("Unable to initialize libsmb2 context");
  665. }
  666. auto cleanupContext = [&ctx]() {
  667. if (ctx != nullptr) {
  668. smb2_disconnect_share(ctx);
  669. smb2_destroy_context(ctx);
  670. ctx = nullptr;
  671. }
  672. };
  673. smb2_set_timeout(ctx, 30);
  674. smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
  675. smb2_set_user(ctx, username.c_str());
  676. smb2_set_password(ctx, password.c_str());
  677. smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str());
  678. int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str());
  679. if (rc != 0) {
  680. cleanupContext();
  681. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
  682. }
  683. std::string normalizedRemotePath = NormalizeRemotePath(remotePath);
  684. smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY);
  685. if (fileHandle == nullptr) {
  686. cleanupContext();
  687. throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file"));
  688. }
  689. std::vector<uint8_t> buffer(requestedLength);
  690. size_t totalRead = 0;
  691. while (totalRead < requestedLength) {
  692. const size_t remaining = requestedLength - totalRead;
  693. const uint32_t toRead = static_cast<uint32_t>(std::min<size_t>(BUFFER_SIZE, remaining));
  694. int bytesRead = smb2_pread(ctx, fileHandle, buffer.data() + totalRead, toRead,
  695. static_cast<uint64_t>(offsetValue) + totalRead);
  696. if (bytesRead < 0) {
  697. std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote range");
  698. smb2_close(ctx, fileHandle);
  699. cleanupContext();
  700. throw std::runtime_error(message);
  701. }
  702. if (bytesRead == 0) {
  703. break;
  704. }
  705. totalRead += static_cast<size_t>(bytesRead);
  706. if (static_cast<uint32_t>(bytesRead) < toRead) {
  707. break;
  708. }
  709. }
  710. smb2_close(ctx, fileHandle);
  711. cleanupContext();
  712. napi_value bufferValue = nullptr;
  713. void *outData = nullptr;
  714. NapiCheck(napi_create_arraybuffer(env, totalRead, &outData, &bufferValue), "Failed to create range buffer");
  715. if (totalRead > 0) {
  716. std::memcpy(outData, buffer.data(), totalRead);
  717. }
  718. return bufferValue;
  719. } catch (const std::exception &error) {
  720. napi_throw_error(env, nullptr, error.what());
  721. return nullptr;
  722. }
  723. }
  724. }
  725. EXTERN_C_START
  726. static napi_value Init(napi_env env, napi_value exports)
  727. {
  728. napi_property_descriptor descriptors[] = {
  729. {"createClient", nullptr, CreateClient, nullptr, nullptr, nullptr, napi_default, nullptr},
  730. {"destroyClient", nullptr, DestroyClient, nullptr, nullptr, nullptr, napi_default, nullptr},
  731. {"authenticate", nullptr, Authenticate, nullptr, nullptr, nullptr, napi_default, nullptr},
  732. {"disconnectSession", nullptr, DisconnectSession, nullptr, nullptr, nullptr, napi_default, nullptr},
  733. {"connectTree", nullptr, ConnectTree, nullptr, nullptr, nullptr, napi_default, nullptr},
  734. {"disconnectTree", nullptr, DisconnectTree, nullptr, nullptr, nullptr, napi_default, nullptr},
  735. {"readDirectory", nullptr, ReadDirectory, nullptr, nullptr, nullptr, napi_default, nullptr},
  736. {"downloadSmbFile", nullptr, DownloadSmbFile, nullptr, nullptr, nullptr, napi_default, nullptr},
  737. {"readSmbFileRange", nullptr, ReadSmbFileRange, nullptr, nullptr, nullptr, napi_default, nullptr}
  738. };
  739. NapiCheck(napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors), "Failed to define native exports");
  740. return exports;
  741. }
  742. EXTERN_C_END
  743. static napi_module g_module = {
  744. .nm_version = 1,
  745. .nm_flags = 0,
  746. .nm_filename = nullptr,
  747. .nm_register_func = Init,
  748. .nm_modname = "entry",
  749. .nm_priv = nullptr,
  750. .reserved = {0},
  751. };
  752. extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
  753. {
  754. napi_module_register(&g_module);
  755. }