From 0a1420dd96397d0433495a4caea258298e82b33d Mon Sep 17 00:00:00 2001 From: Chee Yee Date: Sat, 20 Jul 2024 02:45:52 -0700 Subject: [PATCH] add support for myrient.erista.me website --- CMakeLists.txt | 1 + source/actions.cpp | 3 + source/clients/archiveorg.cpp | 4 +- source/clients/baseclient.cpp | 21 +++ source/clients/myrient.cpp | 241 ++++++++++++++++++++++++++++++++++ source/clients/myrient.h | 17 +++ source/config.cpp | 2 +- source/config.h | 1 + source/windows.cpp | 4 + 9 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 source/clients/myrient.cpp create mode 100644 source/clients/myrient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d114187..35cbca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(ezremote_client source/clients/archiveorg.cpp source/clients/ftpclient.cpp source/clients/gdrive.cpp + source/clients/myrient.cpp source/clients/iis.cpp source/clients/nginx.cpp source/clients/npxserve.cpp diff --git a/source/actions.cpp b/source/actions.cpp index ede80a6..47b0e17 100644 --- a/source/actions.cpp +++ b/source/actions.cpp @@ -11,6 +11,7 @@ #include "clients/webdav.h" #include "clients/apache.h" #include "clients/archiveorg.h" +#include "clients/myrient.h" #include "clients/nginx.h" #include "clients/npxserve.h" #include "clients/nfsclient.h" @@ -1249,6 +1250,8 @@ namespace Actions remoteclient = new RCloneClient(); else if (strcmp(remote_settings->http_server_type, HTTP_SERVER_ARCHIVEORG) == 0) remoteclient = new ArchiveOrgClient(); + else if (strcmp(remote_settings->http_server_type, HTTP_SERVER_MYRIENT) == 0) + remoteclient = new MyrientClient(); } else if (strncmp(remote_settings->server, "webdavs://", 10) == 0 || strncmp(remote_settings->server, "webdav://", 9) == 0) { diff --git a/source/clients/archiveorg.cpp b/source/clients/archiveorg.cpp index 19ce014..aa0952f 100644 --- a/source/clients/archiveorg.cpp +++ b/source/clients/archiveorg.cpp @@ -197,7 +197,9 @@ std::vector ArchiveOrgClient::ListDir(const std::string &path) entry.isDir = false; entry.selectable = true; uint64_t multiplier = 0; - float fsize = std::stof(tmp_string.substr(0, tmp_string.size()-1)); + std::string size_formatted = tmp_string.substr(0, tmp_string.size()-1); + Util::ReplaceAll(size_formatted, ",", ""); + float fsize = std::stof(size_formatted); switch (tmp_string[tmp_string.size()-1]) { case 'B': multiplier = 1; diff --git a/source/clients/baseclient.cpp b/source/clients/baseclient.cpp index 31c782d..eb5217c 100644 --- a/source/clients/baseclient.cpp +++ b/source/clients/baseclient.cpp @@ -90,6 +90,27 @@ int BaseClient::Size(const std::string &path, int64_t *size) *size = atoll(content_length.c_str()); return 1; } + else // Server doesn't support HEAD request. Try get range with 0 bytes and grab size from the response header + // example: Content-Range: bytes 0-10/4372785 + { + Headers headers = {{"Range", "bytes=0-1"}}; + if (auto range_res = client->Get(GetFullPath(path), headers)) + { + if (HTTP_SUCCESS(range_res->status)) + { + if (range_res->has_header("Content-Range")) + { + std::string range = range_res->get_header_value("Content-Range"); + std::vector range_parts = Util::Split(range, "/"); + if (range_parts.size() == 2) + { + *size = atoll(range_parts[1].c_str()); + return 1; + } + } + } + } + } } else { diff --git a/source/clients/myrient.cpp b/source/clients/myrient.cpp new file mode 100644 index 0000000..cdef14d --- /dev/null +++ b/source/clients/myrient.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include "common.h" +#include "clients/remote_client.h" +#include "clients/myrient.h" +#include "lang.h" +#include "util.h" +#include "system.h" +#include "windows.h" + +using httplib::Client; +using httplib::Headers; +using httplib::Result; + +static std::map month_map = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}}; + +std::vector MyrientClient::ListDir(const std::string &path) +{ + std::vector out; + DirEntry entry; + Util::SetupPreviousFolder(path, &entry); + out.push_back(entry); + + std::string encoded_path = httplib::detail::encode_url(GetFullPath(path) + "/"); + if (auto res = client->Get(encoded_path)) + { + lxb_status_t status; + lxb_dom_attr_t *attr; + lxb_dom_element_t *table_element, *tr_element, *td_element; + lxb_html_document_t *document; + lxb_dom_collection_t *table_collection; + lxb_dom_collection_t *tr_collection; + lxb_dom_collection_t *td_collection; + std::string tmp_string; + const lxb_char_t *value; + size_t value_len; + + document = lxb_html_document_create(); + status = lxb_html_document_parse(document, (lxb_char_t *)res->body.c_str(), res->body.length()); + if (status != LXB_STATUS_OK) + { + lxb_html_document_destroy(document); + goto finish; + } + + table_collection = lxb_dom_collection_make(&document->dom_document, 1); + if (table_collection == NULL) + { + lxb_html_document_destroy(document); + goto finish; + } + + tr_collection = lxb_dom_collection_make(&document->dom_document, 128); + if (tr_collection == NULL) + { + lxb_html_document_destroy(document); + goto finish; + } + + status = lxb_dom_elements_by_tag_name(lxb_dom_interface_element(document->body), + table_collection, (const lxb_char_t *)"table", 5); + if (status != LXB_STATUS_OK) + { + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + + if (lxb_dom_collection_length(table_collection) < 1) + { + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + + for (size_t i = 0; i < lxb_dom_collection_length(table_collection); i++) + { + table_element = lxb_dom_collection_element(table_collection, i); + value = lxb_dom_element_id(table_element, &value_len); + tmp_string = std::string((const char *)value, value_len); + if (tmp_string.compare("list") == 0) + break; + table_element = nullptr; + } + + if (table_element == nullptr) + { + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + + status = lxb_dom_elements_by_tag_name(table_element, + tr_collection, (const lxb_char_t *)"tr", 2); + if (status != LXB_STATUS_OK && lxb_dom_collection_length(tr_collection) < 2) + { + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + + // skip row 0 , since it has the previous folder header + for (size_t i = 2; i < lxb_dom_collection_length(tr_collection); i++) + { + DirEntry entry; + std::string title, aclass; + memset(&entry.modified, 0, sizeof(DateTime)); + + tr_element = lxb_dom_collection_element(tr_collection, i); + + td_collection = lxb_dom_collection_make(&document->dom_document, 5); + status = lxb_dom_elements_by_tag_name(tr_element, + td_collection, (const lxb_char_t *)"td", 2); + if (status != LXB_STATUS_OK || lxb_dom_collection_length(td_collection) < 3) + { + lxb_dom_collection_destroy(td_collection, true); + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + + // td0 contains the tag + td_element = lxb_dom_collection_element(td_collection, 0); + lxb_dom_node_t *a_node = NextChildElement(td_element); + value = lxb_dom_element_local_name(lxb_dom_interface_element(a_node), &value_len); + tmp_string = std::string((const char *)value, value_len); + if (tmp_string.compare("a") != 0) + { + lxb_dom_collection_destroy(td_collection, true); + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + goto finish; + } + value = lxb_dom_element_get_attribute(lxb_dom_interface_element(a_node), (const lxb_char_t *)"href", 4, &value_len); + tmp_string = std::string((const char *)value, value_len); + if (tmp_string[tmp_string.length()-1] == '/') + tmp_string = tmp_string.substr(0, tmp_string.length()-1); + tmp_string = BaseClient::UnEscape(tmp_string); + sprintf(entry.name, "%s", tmp_string.c_str()); + sprintf(entry.directory, "%s", path.c_str()); + if (path.length() > 0 && path[path.length() - 1] == '/') + { + sprintf(entry.path, "%s%s", path.c_str(), entry.name); + } + else + { + sprintf(entry.path, "%s/%s", path.c_str(), entry.name); + } + + // next td contains file size, if fize size is "-", then it's a directory + td_element = lxb_dom_collection_element(td_collection, 1); + value = lxb_dom_node_text_content(NextChildTextNode(td_element), &value_len); + tmp_string = std::string((const char *)value, value_len); + + if (tmp_string.compare("-") == 0) + { + entry.isDir = true; + entry.selectable = true; + entry.file_size = 0; + sprintf(entry.display_size, "%s", lang_strings[STR_FOLDER]); + } + else + { + entry.isDir = false; + entry.selectable = true; + uint64_t multiplier = 1; + std::vector fsize_parts = Util::Split(tmp_string, " "); + + float fsize = std::stof(fsize_parts[0]); + + if (fsize_parts.size() > 1) + { + switch (fsize_parts[1][0]) + { + case 'K': + multiplier = 1024; + break; + case 'M': + multiplier = 1048576; + break; + case 'G': + multiplier = 1073741824; + break; + default: + multiplier = 1; + } + } + entry.file_size = fsize * multiplier; + DirEntry::SetDisplaySize(&entry); + } + + // next td contains the date + td_element = lxb_dom_collection_element(td_collection, 2); + value = lxb_dom_node_text_content(NextChildTextNode(td_element), &value_len); + tmp_string = std::string((const char *)value, value_len); + std::vector date_time = Util::Split(tmp_string, " "); + + if (date_time.size() > 1) + { + std::vector adate = Util::Split(date_time[0], "-"); + if (adate.size() == 3) + { + entry.modified.day = atoi(adate[0].c_str()); + entry.modified.month = month_map[adate[1]]; + entry.modified.year = atoi(adate[2].c_str()); + } + + std::vector atime = Util::Split(date_time[1], ":"); + if (atime.size() == 2) + { + entry.modified.hours = atoi(atime[0].c_str()); + entry.modified.minutes = atoi(atime[1].c_str()); + } + } + + lxb_dom_collection_destroy(td_collection, true); + out.push_back(entry); + } + + lxb_dom_collection_destroy(tr_collection, true); + lxb_dom_collection_destroy(table_collection, true); + lxb_html_document_destroy(document); + } + else + { + sprintf(this->response, "%s", httplib::to_string(res.error()).c_str()); + return out; + } + +finish: + return out; +} \ No newline at end of file diff --git a/source/clients/myrient.h b/source/clients/myrient.h new file mode 100644 index 0000000..2910c24 --- /dev/null +++ b/source/clients/myrient.h @@ -0,0 +1,17 @@ +#ifndef EZ_MYRIENT_H +#define EZ_MYRIENT_H + +#include +#include +#include "http/httplib.h" +#include "clients/remote_client.h" +#include "clients/baseclient.h" +#include "common.h" + +class MyrientClient : public BaseClient +{ +public: + std::vector ListDir(const std::string &path); +}; + +#endif \ No newline at end of file diff --git a/source/config.cpp b/source/config.cpp index 946f47f..9efc6fa 100644 --- a/source/config.cpp +++ b/source/config.cpp @@ -159,7 +159,7 @@ namespace CONFIG "Hungarian", "Indonesian", "Italiano", "Japanese", "Korean", "Polish", "Portuguese_BR", "Russian", "Romanian", "Ryukyuan", "Spanish", "Turkish", "Simplified Chinese", "Traditional Chinese", "Thai", "Ukrainian"}; - http_servers = {HTTP_SERVER_APACHE, HTTP_SERVER_MS_IIS, HTTP_SERVER_NGINX, HTTP_SERVER_NPX_SERVE, HTTP_SERVER_RCLONE, HTTP_SERVER_ARCHIVEORG}; + http_servers = {HTTP_SERVER_APACHE, HTTP_SERVER_MS_IIS, HTTP_SERVER_NGINX, HTTP_SERVER_NPX_SERVE, HTTP_SERVER_RCLONE, HTTP_SERVER_ARCHIVEORG, HTTP_SERVER_MYRIENT}; text_file_extensions = { ".txt", ".ini", ".log", ".json", ".xml", ".html", ".xhtml", ".conf", ".config" }; image_file_extensions = { ".bmp", ".jpg", ".jpeg", ".png", ".webp" }; diff --git a/source/config.h b/source/config.h index c8324f9..60bfae5 100644 --- a/source/config.h +++ b/source/config.h @@ -83,6 +83,7 @@ #define HTTP_SERVER_NPX_SERVE "Serve" #define HTTP_SERVER_RCLONE "RClone" #define HTTP_SERVER_ARCHIVEORG "Archive.org" +#define HTTP_SERVER_MYRIENT "Myrient" #define MAX_EDIT_FILE_SIZE 32768 diff --git a/source/windows.cpp b/source/windows.cpp index b24652c..b47483c 100644 --- a/source/windows.cpp +++ b/source/windows.cpp @@ -2599,6 +2599,10 @@ namespace Windows { sprintf(remote_settings->http_server_type, "%s", HTTP_SERVER_ARCHIVEORG); } + else if (strncasecmp(remote_settings->server, "https://myrient.erista.me/", 26) == 0) + { + sprintf(remote_settings->http_server_type, "%s", HTTP_SERVER_MYRIENT); + } } }