| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825 |
- #include "napi/native_api.h"
- #include <algorithm>
- #include <atomic>
- #include <cctype>
- #include <cstdint>
- #include <cstdio>
- #include <cstring>
- #include <filesystem>
- #include <fcntl.h>
- #include <system_error>
- #include <memory>
- #include <mutex>
- #include <stdexcept>
- #include <string>
- #include <unordered_map>
- #include <vector>
- #include "hilog/log.h"
- extern "C" {
- #include "smb2/smb2.h"
- #include "smb2/libsmb2.h"
- }
- namespace {
- constexpr unsigned int SMB_LOG_DOMAIN = 0xD001780;
- constexpr const char *SMB_LOG_TAG = "libsmb2";
- #define SMB_LOGI(fmt, ...) OH_LOG_Print(LOG_APP, LOG_INFO, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__)
- #define SMB_LOGE(fmt, ...) OH_LOG_Print(LOG_APP, LOG_ERROR, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__)
- inline void NapiCheck(napi_status status, const char *message)
- {
- if (status != napi_ok) {
- throw std::runtime_error(message == nullptr ? "NAPI call failed" : message);
- }
- }
- struct SambaClient {
- int64_t id = 0;
- std::string host;
- smb2_context *ctx = nullptr;
- bool closed = false;
- };
- struct SambaSession {
- int64_t id = 0;
- std::weak_ptr<SambaClient> client;
- std::string username;
- std::string password;
- std::string domain;
- bool closed = false;
- int64_t activeTreeId = 0;
- };
- struct SambaTree {
- int64_t id = 0;
- std::weak_ptr<SambaSession> session;
- std::string share;
- bool connected = false;
- };
- std::atomic<int64_t> g_nextHandle{1};
- std::mutex g_mutex;
- std::unordered_map<int64_t, std::shared_ptr<SambaClient>> g_clients;
- std::unordered_map<int64_t, std::shared_ptr<SambaSession>> g_sessions;
- std::unordered_map<int64_t, std::shared_ptr<SambaTree>> g_trees;
- int64_t GenerateHandle()
- {
- return g_nextHandle.fetch_add(1);
- }
- void Ensure(bool condition, const std::string &message)
- {
- if (!condition) {
- throw std::runtime_error(message);
- }
- }
- std::string Trim(const std::string &value)
- {
- if (value.empty()) {
- return value;
- }
- size_t start = 0;
- size_t end = value.size();
- while (start < end && std::isspace(static_cast<unsigned char>(value[start])) != 0) {
- start++;
- }
- if (start == end) {
- return "";
- }
- while (end > start && std::isspace(static_cast<unsigned char>(value[end - 1])) != 0) {
- end--;
- }
- return value.substr(start, end - start);
- }
- std::string NormalizeRemotePath(const std::string &raw)
- {
- std::string trimmed = Trim(raw);
- if (trimmed.empty() || trimmed == "/" || trimmed == "\\") {
- return "";
- }
- size_t index = 0;
- while (index < trimmed.size() && (trimmed[index] == '/' || trimmed[index] == '\\')) {
- index++;
- }
- std::string normalized = trimmed.substr(index);
- if (normalized.empty()) {
- return "";
- }
- std::replace(normalized.begin(), normalized.end(), '\\', '/');
- return normalized;
- }
- std::string BuildSmbErrorMessage(smb2_context *ctx, const std::string &fallback)
- {
- const char *error = ctx != nullptr ? smb2_get_error(ctx) : nullptr;
- if (error == nullptr || std::strlen(error) == 0) {
- return fallback;
- }
- return fallback + ": " + error;
- }
- std::string ReadString(napi_env env, napi_value value, const char *name, bool required)
- {
- napi_valuetype type = napi_undefined;
- NapiCheck(napi_typeof(env, value, &type), "Failed to read argument type");
- if (type == napi_null || type == napi_undefined) {
- if (required) {
- throw std::runtime_error(std::string(name) + " is required");
- }
- return "";
- }
- if (type != napi_string) {
- throw std::runtime_error(std::string(name) + " must be a string");
- }
- size_t length = 0;
- NapiCheck(napi_get_value_string_utf8(env, value, nullptr, 0, &length), "Failed to measure string length");
- std::string result(length, '\0');
- size_t written = 0;
- NapiCheck(napi_get_value_string_utf8(env, value, result.data(), length + 1, &written), "Failed to read string value");
- result.resize(written);
- return result;
- }
- int64_t ReadInt64(napi_env env, napi_value value, const char *name)
- {
- napi_valuetype type = napi_undefined;
- NapiCheck(napi_typeof(env, value, &type), "Failed to read numeric argument type");
- if (type != napi_number) {
- throw std::runtime_error(std::string(name) + " must be a number");
- }
- int64_t result = 0;
- NapiCheck(napi_get_value_int64(env, value, &result), "Failed to read numeric argument");
- return result;
- }
- napi_value CreateInt64Value(napi_env env, int64_t value)
- {
- napi_value result = nullptr;
- NapiCheck(napi_create_int64(env, value, &result), "Failed to create int value");
- return result;
- }
- napi_value CreateUndefined(napi_env env)
- {
- napi_value result = nullptr;
- NapiCheck(napi_get_undefined(env, &result), "Failed to create undefined value");
- return result;
- }
- std::shared_ptr<SambaClient> RequireClient(int64_t clientId)
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_clients.find(clientId);
- if (it == g_clients.end() || it->second == nullptr || it->second->closed) {
- throw std::runtime_error("Invalid SMB client handle");
- }
- return it->second;
- }
- std::shared_ptr<SambaSession> RequireSession(int64_t sessionId)
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_sessions.find(sessionId);
- if (it == g_sessions.end() || it->second == nullptr || it->second->closed) {
- throw std::runtime_error("Invalid SMB session handle");
- }
- return it->second;
- }
- std::shared_ptr<SambaTree> RequireTree(int64_t treeId)
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_trees.find(treeId);
- if (it == g_trees.end() || it->second == nullptr) {
- throw std::runtime_error("Invalid SMB tree handle");
- }
- return it->second;
- }
- void RemoveTreeEntry(int64_t treeId)
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- g_trees.erase(treeId);
- }
- void ResetSessionTree(const std::shared_ptr<SambaSession> &session, int64_t treeId)
- {
- if (session == nullptr) {
- return;
- }
- std::lock_guard<std::mutex> lock(g_mutex);
- if (session->activeTreeId == treeId) {
- session->activeTreeId = 0;
- }
- }
- void CloseTreeInternal(const std::shared_ptr<SambaTree> &tree)
- {
- if (!tree) {
- return;
- }
- auto session = tree->session.lock();
- auto client = session ? session->client.lock() : nullptr;
- if (client && client->ctx && tree->connected) {
- smb2_disconnect_share(client->ctx);
- }
- tree->connected = false;
- ResetSessionTree(session, tree->id);
- }
- void RemoveTreesForSession(int64_t sessionId)
- {
- std::vector<std::shared_ptr<SambaTree>> ownedTrees;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- for (auto it = g_trees.begin(); it != g_trees.end(); ) {
- auto session = it->second ? it->second->session.lock() : nullptr;
- if (!session || session->id == sessionId) {
- ownedTrees.push_back(it->second);
- it = g_trees.erase(it);
- continue;
- }
- ++it;
- }
- }
- for (const auto &tree : ownedTrees) {
- CloseTreeInternal(tree);
- }
- }
- void RemoveSessionsForClient(int64_t clientId)
- {
- std::vector<std::shared_ptr<SambaSession>> sessions;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- for (auto it = g_sessions.begin(); it != g_sessions.end(); ) {
- auto client = it->second ? it->second->client.lock() : nullptr;
- if (!client || client->id == clientId) {
- it->second->closed = true;
- sessions.push_back(it->second);
- it = g_sessions.erase(it);
- continue;
- }
- ++it;
- }
- }
- for (const auto &session : sessions) {
- RemoveTreesForSession(session->id);
- }
- }
- void CloseClientContext(const std::shared_ptr<SambaClient> &client)
- {
- if (!client || client->ctx == nullptr) {
- return;
- }
- smb2_disconnect_share(client->ctx);
- smb2_destroy_context(client->ctx);
- client->ctx = nullptr;
- client->closed = true;
- }
- napi_value CreateClient(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 1;
- napi_value args[1] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for createClient");
- if (argc < 1) {
- throw std::runtime_error("host argument is required");
- }
- std::string host = Trim(ReadString(env, args[0], "host", true));
- Ensure(!host.empty(), "host must not be empty");
- smb2_context *ctx = smb2_init_context();
- if (ctx == nullptr) {
- throw std::runtime_error("Unable to initialize libsmb2 context");
- }
- smb2_set_timeout(ctx, 15);
- smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
- auto client = std::make_shared<SambaClient>();
- client->id = GenerateHandle();
- client->host = host;
- client->ctx = ctx;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- g_clients[client->id] = client;
- }
- return CreateInt64Value(env, client->id);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value DestroyClient(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 1;
- napi_value args[1] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for destroyClient");
- if (argc < 1) {
- return CreateUndefined(env);
- }
- int64_t clientId = ReadInt64(env, args[0], "clientId");
- std::shared_ptr<SambaClient> client;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_clients.find(clientId);
- if (it != g_clients.end()) {
- client = it->second;
- g_clients.erase(it);
- }
- }
- if (client) {
- RemoveSessionsForClient(client->id);
- CloseClientContext(client);
- }
- return CreateUndefined(env);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value Authenticate(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 4;
- napi_value args[4] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for authenticate");
- if (argc < 3) {
- throw std::runtime_error("authenticate requires clientId, username and password");
- }
- int64_t clientId = ReadInt64(env, args[0], "clientId");
- std::string username = Trim(ReadString(env, args[1], "username", true));
- std::string password = ReadString(env, args[2], "password", true);
- std::string domain;
- if (argc >= 4) {
- domain = Trim(ReadString(env, args[3], "domain", false));
- }
- Ensure(!username.empty(), "username must not be empty");
- auto client = RequireClient(clientId);
- auto session = std::make_shared<SambaSession>();
- session->id = GenerateHandle();
- session->client = client;
- session->username = username;
- session->password = password;
- session->domain = domain;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- g_sessions[session->id] = session;
- }
- return CreateInt64Value(env, session->id);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value DisconnectSession(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 1;
- napi_value args[1] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectSession");
- if (argc < 1) {
- return CreateUndefined(env);
- }
- int64_t sessionId = ReadInt64(env, args[0], "sessionId");
- std::shared_ptr<SambaSession> session;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_sessions.find(sessionId);
- if (it != g_sessions.end()) {
- session = it->second;
- g_sessions.erase(it);
- }
- }
- if (session) {
- session->closed = true;
- RemoveTreesForSession(session->id);
- }
- return CreateUndefined(env);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value ConnectTree(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 2;
- napi_value args[2] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for connectTree");
- if (argc < 2) {
- throw std::runtime_error("connectTree requires sessionId and share name");
- }
- int64_t sessionId = ReadInt64(env, args[0], "sessionId");
- std::string share = Trim(ReadString(env, args[1], "share", true));
- Ensure(!share.empty(), "share must not be empty");
- auto session = RequireSession(sessionId);
- auto client = session->client.lock();
- if (!client || client->closed) {
- throw std::runtime_error("SMB client is not available");
- }
- Ensure(session->activeTreeId == 0, "Session already has an active tree");
- smb2_context *ctx = client->ctx;
- if (ctx == nullptr) {
- throw std::runtime_error("SMB context is not initialized");
- }
- smb2_set_user(ctx, session->username.c_str());
- smb2_set_password(ctx, session->password.c_str());
- smb2_set_domain(ctx, session->domain.empty() ? nullptr : session->domain.c_str());
- int rc = smb2_connect_share(ctx, client->host.c_str(), share.c_str(), session->username.c_str());
- if (rc != 0) {
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
- }
- auto tree = std::make_shared<SambaTree>();
- tree->id = GenerateHandle();
- tree->session = session;
- tree->share = share;
- tree->connected = true;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- session->activeTreeId = tree->id;
- g_trees[tree->id] = tree;
- }
- return CreateInt64Value(env, tree->id);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value DisconnectTree(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 1;
- napi_value args[1] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectTree");
- if (argc < 1) {
- return CreateUndefined(env);
- }
- int64_t treeId = ReadInt64(env, args[0], "treeId");
- std::shared_ptr<SambaTree> tree;
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- auto it = g_trees.find(treeId);
- if (it != g_trees.end()) {
- tree = it->second;
- g_trees.erase(it);
- }
- }
- CloseTreeInternal(tree);
- return CreateUndefined(env);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value ReadDirectory(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 2;
- napi_value args[2] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readDirectory");
- if (argc < 2) {
- throw std::runtime_error("readDirectory requires treeId and remote path");
- }
- int64_t treeId = ReadInt64(env, args[0], "treeId");
- std::string path = ReadString(env, args[1], "path", false);
- auto tree = RequireTree(treeId);
- auto session = tree->session.lock();
- auto client = session ? session->client.lock() : nullptr;
- if (!client || client->ctx == nullptr) {
- throw std::runtime_error("SMB client context is not available");
- }
- Ensure(tree->connected, "SMB tree is not connected");
- std::string normalized = NormalizeRemotePath(path);
- SMB_LOGI("readDirectory invoked tree=%{public}lld path=%{public}s", static_cast<long long>(treeId), normalized.c_str());
- smb2_context *ctx = client->ctx;
- smb2dir *dir = smb2_opendir(ctx, normalized.c_str());
- if (dir == nullptr) {
- const char *err = ctx != nullptr ? smb2_get_error(ctx) : "";
- SMB_LOGE("smb2_opendir failed tree=%{public}lld path=%{public}s err=%{public}s", static_cast<long long>(treeId), normalized.c_str(), err ? err : "");
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote directory"));
- }
- std::vector<napi_value> entries;
- struct smb2dirent *dirent = nullptr;
- while ((dirent = smb2_readdir(ctx, dir)) != nullptr) {
- if (!dirent->name) {
- continue;
- }
- if (std::strcmp(dirent->name, ".") == 0 || std::strcmp(dirent->name, "..") == 0) {
- continue;
- }
- napi_value entry = nullptr;
- NapiCheck(napi_create_object(env, &entry), "Failed to create directory entry object");
- napi_value nameValue = nullptr;
- NapiCheck(napi_create_string_utf8(env, dirent->name, NAPI_AUTO_LENGTH, &nameValue), "Failed to create directory name");
- NapiCheck(napi_set_named_property(env, entry, "name", nameValue), "Failed to set name property");
- NapiCheck(napi_set_named_property(env, entry, "fileName", nameValue), "Failed to set fileName property");
- bool isDirectory = dirent->st.smb2_type == SMB2_TYPE_DIRECTORY;
- bool isFile = dirent->st.smb2_type == SMB2_TYPE_FILE;
- napi_value dirValue = nullptr;
- NapiCheck(napi_get_boolean(env, isDirectory, &dirValue), "Failed to create directory flag");
- NapiCheck(napi_set_named_property(env, entry, "isDirectory", dirValue), "Failed to set directory flag");
- napi_value fileValue = nullptr;
- NapiCheck(napi_get_boolean(env, isFile, &fileValue), "Failed to create file flag");
- NapiCheck(napi_set_named_property(env, entry, "isFile", fileValue), "Failed to set file flag");
- napi_value sizeValue = nullptr;
- NapiCheck(napi_create_double(env, static_cast<double>(dirent->st.smb2_size), &sizeValue), "Failed to create size value");
- NapiCheck(napi_set_named_property(env, entry, "size", sizeValue), "Failed to set size property");
- entries.push_back(entry);
- }
- smb2_closedir(ctx, dir);
- napi_value result = nullptr;
- NapiCheck(napi_create_array_with_length(env, entries.size(), &result), "Failed to create directory result array");
- for (size_t i = 0; i < entries.size(); i++) {
- NapiCheck(napi_set_element(env, result, i, entries[i]), "Failed to append directory entry");
- }
- return result;
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value DownloadSmbFile(napi_env env, napi_callback_info info)
- {
- try {
- size_t argc = 7;
- napi_value args[7] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for downloadSmbFile");
- if (argc < 7) {
- throw std::runtime_error("downloadSmbFile requires host, share, username, password, domain, remotePath and localPath");
- }
- std::string host = ReadString(env, args[0], "host", true);
- std::string share = ReadString(env, args[1], "share", true);
- std::string username = ReadString(env, args[2], "username", true);
- std::string password = ReadString(env, args[3], "password", true);
- std::string domain = ReadString(env, args[4], "domain", false);
- std::string remotePath = ReadString(env, args[5], "remotePath", true);
- std::string localPath = ReadString(env, args[6], "localPath", true);
- if (share.empty()) {
- throw std::runtime_error("share must not be empty");
- }
- if (localPath.empty()) {
- throw std::runtime_error("localPath must not be empty");
- }
- std::string normalizedRemotePath = NormalizeRemotePath(remotePath);
- if (normalizedRemotePath.empty()) {
- throw std::runtime_error("remotePath must not be empty");
- }
- std::error_code fsError;
- std::filesystem::path targetPath(localPath);
- auto parent = targetPath.parent_path();
- if (!parent.empty()) {
- std::filesystem::create_directories(parent, fsError);
- if (fsError) {
- throw std::runtime_error("Failed to prepare cache directory: " + fsError.message());
- }
- }
- FILE *output = std::fopen(localPath.c_str(), "wb");
- if (output == nullptr) {
- throw std::runtime_error("Unable to open local file for writing");
- }
- smb2_context *ctx = smb2_init_context();
- if (ctx == nullptr) {
- std::fclose(output);
- throw std::runtime_error("Unable to initialize libsmb2 context");
- }
- auto cleanupContext = [&ctx]() {
- if (ctx != nullptr) {
- smb2_disconnect_share(ctx);
- smb2_destroy_context(ctx);
- ctx = nullptr;
- }
- };
- auto cleanupFile = [&output]() {
- if (output != nullptr) {
- std::fclose(output);
- output = nullptr;
- }
- };
- auto removeCacheFile = [&localPath]() {
- std::remove(localPath.c_str());
- };
- smb2_set_timeout(ctx, 30);
- smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
- smb2_set_user(ctx, username.c_str());
- smb2_set_password(ctx, password.c_str());
- smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str());
- int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str());
- if (rc != 0) {
- cleanupFile();
- cleanupContext();
- removeCacheFile();
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
- }
- smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY);
- if (fileHandle == nullptr) {
- cleanupFile();
- cleanupContext();
- removeCacheFile();
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file"));
- }
- const size_t BUFFER_SIZE = 64 * 1024;
- std::vector<uint8_t> buffer(BUFFER_SIZE);
- int bytesRead = 0;
- while ((bytesRead = smb2_read(ctx, fileHandle, buffer.data(), static_cast<uint32_t>(buffer.size()))) > 0) {
- size_t written = std::fwrite(buffer.data(), 1, static_cast<size_t>(bytesRead), output);
- if (written != static_cast<size_t>(bytesRead)) {
- smb2_close(ctx, fileHandle);
- cleanupFile();
- cleanupContext();
- removeCacheFile();
- throw std::runtime_error("Failed to write to local file");
- }
- }
- if (bytesRead < 0) {
- std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote file");
- smb2_close(ctx, fileHandle);
- cleanupFile();
- cleanupContext();
- removeCacheFile();
- throw std::runtime_error(message);
- }
- smb2_close(ctx, fileHandle);
- cleanupFile();
- cleanupContext();
- return CreateUndefined(env);
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- napi_value ReadSmbFileRange(napi_env env, napi_callback_info info)
- {
- constexpr size_t MAX_RANGE_SIZE = 2 * 1024 * 1024;
- constexpr size_t BUFFER_SIZE = 64 * 1024;
- try {
- size_t argc = 8;
- napi_value args[8] = {nullptr};
- NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readSmbFileRange");
- if (argc < 8) {
- throw std::runtime_error("readSmbFileRange requires host, share, username, password, domain, remotePath, offset and length");
- }
- std::string host = ReadString(env, args[0], "host", true);
- std::string share = ReadString(env, args[1], "share", true);
- std::string username = ReadString(env, args[2], "username", true);
- std::string password = ReadString(env, args[3], "password", true);
- std::string domain = ReadString(env, args[4], "domain", false);
- std::string remotePath = ReadString(env, args[5], "remotePath", true);
- int64_t offsetValue = ReadInt64(env, args[6], "offset");
- int64_t lengthValue = ReadInt64(env, args[7], "length");
- if (offsetValue < 0) {
- offsetValue = 0;
- }
- if (lengthValue <= 0) {
- napi_value emptyResult = nullptr;
- void *tmp = nullptr;
- NapiCheck(napi_create_arraybuffer(env, 0, &tmp, &emptyResult), "Failed to create empty buffer");
- return emptyResult;
- }
- size_t requestedLength = static_cast<size_t>(lengthValue);
- if (requestedLength > MAX_RANGE_SIZE) {
- requestedLength = MAX_RANGE_SIZE;
- }
- smb2_context *ctx = smb2_init_context();
- if (ctx == nullptr) {
- throw std::runtime_error("Unable to initialize libsmb2 context");
- }
- auto cleanupContext = [&ctx]() {
- if (ctx != nullptr) {
- smb2_disconnect_share(ctx);
- smb2_destroy_context(ctx);
- ctx = nullptr;
- }
- };
- smb2_set_timeout(ctx, 30);
- smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP);
- smb2_set_user(ctx, username.c_str());
- smb2_set_password(ctx, password.c_str());
- smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str());
- int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str());
- if (rc != 0) {
- cleanupContext();
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share"));
- }
- std::string normalizedRemotePath = NormalizeRemotePath(remotePath);
- smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY);
- if (fileHandle == nullptr) {
- cleanupContext();
- throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file"));
- }
- std::vector<uint8_t> buffer(requestedLength);
- size_t totalRead = 0;
- while (totalRead < requestedLength) {
- const size_t remaining = requestedLength - totalRead;
- const uint32_t toRead = static_cast<uint32_t>(std::min<size_t>(BUFFER_SIZE, remaining));
- int bytesRead = smb2_pread(ctx, fileHandle, buffer.data() + totalRead, toRead,
- static_cast<uint64_t>(offsetValue) + totalRead);
- if (bytesRead < 0) {
- std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote range");
- smb2_close(ctx, fileHandle);
- cleanupContext();
- throw std::runtime_error(message);
- }
- if (bytesRead == 0) {
- break;
- }
- totalRead += static_cast<size_t>(bytesRead);
- if (static_cast<uint32_t>(bytesRead) < toRead) {
- break;
- }
- }
- smb2_close(ctx, fileHandle);
- cleanupContext();
- napi_value bufferValue = nullptr;
- void *outData = nullptr;
- NapiCheck(napi_create_arraybuffer(env, totalRead, &outData, &bufferValue), "Failed to create range buffer");
- if (totalRead > 0) {
- std::memcpy(outData, buffer.data(), totalRead);
- }
- return bufferValue;
- } catch (const std::exception &error) {
- napi_throw_error(env, nullptr, error.what());
- return nullptr;
- }
- }
- }
- EXTERN_C_START
- static napi_value Init(napi_env env, napi_value exports)
- {
- napi_property_descriptor descriptors[] = {
- {"createClient", nullptr, CreateClient, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"destroyClient", nullptr, DestroyClient, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"authenticate", nullptr, Authenticate, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"disconnectSession", nullptr, DisconnectSession, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"connectTree", nullptr, ConnectTree, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"disconnectTree", nullptr, DisconnectTree, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"readDirectory", nullptr, ReadDirectory, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"downloadSmbFile", nullptr, DownloadSmbFile, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"readSmbFileRange", nullptr, ReadSmbFileRange, nullptr, nullptr, nullptr, napi_default, nullptr}
- };
- NapiCheck(napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors), "Failed to define native exports");
- return exports;
- }
- EXTERN_C_END
- static napi_module g_module = {
- .nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = Init,
- .nm_modname = "entry",
- .nm_priv = nullptr,
- .reserved = {0},
- };
- extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
- {
- napi_module_register(&g_module);
- }
|