summaryrefslogtreecommitdiff
path: root/protocols/CloudFile/src/Services
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:44 +0300
committerGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:55 +0300
commit931a7dc1ac0dbc7e6c1083583ced915e572f5b47 (patch)
tree9fe9a6448d44030e26aa7107ce16044ed413e0d0 /protocols/CloudFile/src/Services
parentdd7d9954042254e66e3bbbec7195c6be8b1a0663 (diff)
all protocols (even virtual ones) moved to the Protocols folder
Diffstat (limited to 'protocols/CloudFile/src/Services')
-rw-r--r--protocols/CloudFile/src/Services/dropbox_api.h221
-rw-r--r--protocols/CloudFile/src/Services/dropbox_service.cpp302
-rw-r--r--protocols/CloudFile/src/Services/dropbox_service.h36
-rw-r--r--protocols/CloudFile/src/Services/google_api.h200
-rw-r--r--protocols/CloudFile/src/Services/google_service.cpp297
-rw-r--r--protocols/CloudFile/src/Services/google_service.h35
-rw-r--r--protocols/CloudFile/src/Services/microsoft_api.h188
-rw-r--r--protocols/CloudFile/src/Services/microsoft_service.cpp269
-rw-r--r--protocols/CloudFile/src/Services/microsoft_service.h34
-rw-r--r--protocols/CloudFile/src/Services/yandex_api.h131
-rw-r--r--protocols/CloudFile/src/Services/yandex_service.cpp290
-rw-r--r--protocols/CloudFile/src/Services/yandex_service.h35
12 files changed, 2038 insertions, 0 deletions
diff --git a/protocols/CloudFile/src/Services/dropbox_api.h b/protocols/CloudFile/src/Services/dropbox_api.h
new file mode 100644
index 0000000000..e7e77600c0
--- /dev/null
+++ b/protocols/CloudFile/src/Services/dropbox_api.h
@@ -0,0 +1,221 @@
+#ifndef _DROPBOXSERVICE_API_H_
+#define _DROPBOXSERVICE_API_H_
+
+// https://www.dropbox.com/developers/documentation/http/documentation
+namespace DropboxAPI
+{
+#define DROPBOX_API_VER "/2"
+#define DROPBOX_API "https://api.dropboxapi.com"
+#define DROPBOX_API_OAUTH DROPBOX_API "/oauth2"
+#define DROPBOX_API_RPC DROPBOX_API DROPBOX_API_VER
+#define DROPBOX_CONTENT "https://content.dropboxapi.com"
+#define DROPBOX_API_CU DROPBOX_CONTENT DROPBOX_API_VER
+
+#define DROPBOX_APP_KEY "fa8du7gkf2q8xzg"
+#include "../../../miranda-private-keys/Dropbox/secret_key.h"
+
+#define DROPBOX_API_AUTH "https://www.dropbox.com/oauth2/authorize?response_type=code&redirect_uri=https%3A%2F%2Foauth.miranda-ng.org%2Fverification&client_id=" DROPBOX_APP_KEY
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *code) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data = "redirect_uri=https://oauth.miranda-ng.org/verification";
+ data.AppendFormat("&client_id=%s&client_secret=%s", DROPBOX_APP_KEY, DROPBOX_API_SECRET);
+ data.AppendFormat("&grant_type=authorization_code&code=%s", code);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RevokeAccessTokenRequest : public HttpRequest
+ {
+ public:
+ RevokeAccessTokenRequest(const char *token) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_OAUTH "/token/revoke")
+ {
+ AddBearerAuthHeader(token);
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *path, const char *data, size_t size, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("path", path);
+ if (strategy == OnConflict::RENAME) {
+ params
+ << JSONNode("mode", "add")
+ << JSONNode("autorename", true);
+ }
+ else if (strategy == OnConflict::REPLACE) {
+ params
+ << JSONNode("mode", "overwrite")
+ << JSONNode("autorename", false);
+ }
+
+ AddHeader("Dropbox-API-Arg", params.write().c_str());
+
+ SetData(data, size);
+ }
+ };
+
+ class CreateUploadSessionRequest : public HttpRequest
+ {
+ public:
+ CreateUploadSessionRequest(const char *token, const char *chunk, size_t chunkSize) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/start")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class UploadFileChunkRequest : public HttpRequest
+ {
+ public:
+ UploadFileChunkRequest(const char *token, const char *sessionId, size_t offset, const char *chunk, size_t chunkSize) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/append_v2")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+
+ JSONNode cursor;
+ cursor.set_name("cursor");
+ cursor
+ << JSONNode("session_id", sessionId)
+ << JSONNode("offset", (unsigned long)offset);
+
+ JSONNode param;
+ param << cursor;
+
+ AddHeader("Dropbox-API-Arg", param.write().c_str());
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class CommitUploadSessionRequest : public HttpRequest
+ {
+ public:
+ CommitUploadSessionRequest(const char *token, const char *sessionId, size_t offset, const char *path, const char *chunk, size_t chunkSize, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/finish")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+ JSONNode cursor(JSON_NODE);
+ cursor.set_name("cursor");
+ cursor
+ << JSONNode("session_id", sessionId)
+ << JSONNode("offset", (unsigned long)offset);
+
+ JSONNode commit(JSON_NODE);
+ commit.set_name("commit");
+ commit
+ << JSONNode("path", path);
+ if (strategy == OnConflict::RENAME) {
+ commit
+ << JSONNode("mode", "add")
+ << JSONNode("autorename", true);
+ }
+ else if (strategy == OnConflict::REPLACE) {
+ commit
+ << JSONNode("mode", "overwrite")
+ << JSONNode("autorename", false);
+ }
+
+ JSONNode params(JSON_NODE);
+ params
+ << cursor
+ << commit;
+
+ AddHeader("Dropbox-API-Arg", params.write().c_str());
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/files/create_folder_v2")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode root(JSON_NODE);
+ root << JSONNode("path", path);
+
+ json_string data = root.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class GetTemporaryLinkRequest : public HttpRequest
+ {
+ public:
+ GetTemporaryLinkRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/files/get_temporary_link")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode root(JSON_NODE);
+ root << JSONNode("path", path);
+
+ json_string data = root.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class CreateSharedLinkRequest : public HttpRequest
+ {
+ public:
+ CreateSharedLinkRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/sharing/create_shared_link_with_settings")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode root(JSON_NODE);
+ root << JSONNode("path", path);
+
+ json_string data = root.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class GetSharedLinkRequest : public HttpRequest
+ {
+ public:
+ GetSharedLinkRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/sharing/list_shared_links")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode root(JSON_NODE);
+ root << JSONNode("path", path);
+
+ json_string data = root.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+};
+
+#endif //_DROPBOXSERVICE_API_H_
diff --git a/protocols/CloudFile/src/Services/dropbox_service.cpp b/protocols/CloudFile/src/Services/dropbox_service.cpp
new file mode 100644
index 0000000000..a2cfa5a2e5
--- /dev/null
+++ b/protocols/CloudFile/src/Services/dropbox_service.cpp
@@ -0,0 +1,302 @@
+#include "..\stdafx.h"
+#include "dropbox_api.h"
+
+struct CMPluginDropbox : public PLUGIN<CMPluginDropbox>
+{
+ CMPluginDropbox() :
+ PLUGIN<CMPluginDropbox>(MODULENAME "/Dropbox", pluginInfoEx)
+ {
+ m_hInst = g_plugin.getInst();
+
+ RegisterProtocol(PROTOTYPE_PROTOWITHACCS, (pfnInitProto)CDropboxService::Init, (pfnUninitProto)CDropboxService::UnInit);
+ }
+}
+g_pluginDropbox;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CDropboxService::CDropboxService(const char *protoName, const wchar_t *userName) :
+ CCloudService(protoName, userName, &g_pluginDropbox)
+{
+ m_hProtoIcon = GetIconHandle(IDI_DROPBOX);
+}
+
+CDropboxService* CDropboxService::Init(const char *moduleName, const wchar_t *userName)
+{
+ CDropboxService *proto = new CDropboxService(moduleName, userName);
+ Services.insert(proto);
+ return proto;
+}
+
+int CDropboxService::UnInit(CDropboxService *proto)
+{
+ Services.remove(proto);
+ delete proto;
+ return 0;
+}
+
+const char* CDropboxService::GetModuleName() const
+{
+ return "Dropbox";
+}
+
+int CDropboxService::GetIconId() const
+{
+ return IDI_DROPBOX;
+}
+
+bool CDropboxService::IsLoggedIn()
+{
+ ptrA token(getStringA("TokenSecret"));
+ if (!token || token[0] == 0)
+ return false;
+ return true;
+}
+
+void CDropboxService::Login(HWND owner)
+{
+ COAuthDlg dlg(this, DROPBOX_API_AUTH, (MyThreadFunc)&CDropboxService::RequestAccessTokenThread);
+ dlg.SetParent(owner);
+ dlg.DoModal();
+}
+
+void CDropboxService::Logout()
+{
+ ForkThread((MyThreadFunc)&CDropboxService::RevokeAccessTokenThread);
+}
+
+void CDropboxService::RequestAccessTokenThread(void *param)
+{
+ HWND hwndDlg = (HWND)param;
+
+ if (IsLoggedIn())
+ Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ DropboxAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response == nullptr || response->resultCode != HTTP_CODE_OK) {
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError());
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ CMStringW error_description = node.as_mstring();
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(error_description, MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ node = root.at("access_token");
+ db_set_s(0, GetAccountName(), "TokenSecret", node.as_string().c_str());
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+}
+
+void CDropboxService::RevokeAccessTokenThread(void *)
+{
+ ptrA token(getStringA("TokenSecret"));
+ DropboxAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ delSetting("ExpiresIn");
+ delSetting("TokenSecret");
+ delSetting("RefreshToken");
+}
+
+void CDropboxService::HandleJsonError(JSONNode &node)
+{
+ JSONNode error = node.at("error");
+ if (!error.isnull()) {
+ json_string tag = error.at(".tag").as_string();
+ throw Exception(tag.c_str());
+ }
+}
+
+auto CDropboxService::UploadFile(const char *data, size_t size, const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ DropboxAPI::UploadFileRequest request(token, path.c_str(), data, size, (OnConflict)strategy);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["path_lower"].as_string();
+}
+
+auto CDropboxService::CreateUploadSession(const char *chunk, size_t chunkSize)
+{
+ ptrA token(getStringA("TokenSecret"));
+ DropboxAPI::CreateUploadSessionRequest request(token, chunk, chunkSize);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["session_id"].as_string();
+}
+
+void CDropboxService::UploadFileChunk(const std::string &sessionId, const char *chunk, size_t chunkSize, size_t offset)
+{
+ ptrA token(getStringA("TokenSecret"));
+ DropboxAPI::UploadFileChunkRequest request(token, sessionId.c_str(), offset, chunk, chunkSize);
+ NLHR_PTR response(request.Send(m_hConnection));
+ HandleHttpError(response);
+}
+
+auto CDropboxService::CommitUploadSession(const std::string &sessionId, const char *data, size_t size, size_t offset, const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ DropboxAPI::CommitUploadSessionRequest request(token, sessionId.c_str(), offset, path.c_str(), data, size, (OnConflict)strategy);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["path_lower"].as_string();
+}
+
+void CDropboxService::CreateFolder(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ DropboxAPI::CreateFolderRequest request(token, path.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response == nullptr)
+ throw Exception(HttpStatusToError());
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ GetJsonResponse(response);
+ return;
+ }
+
+ // forder exists on server
+ if (response->resultCode == HTTP_CODE_CONFLICT) {
+ return;
+ }
+
+ HttpResponseToError(response);
+}
+
+auto CDropboxService::CreateSharedLink(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ DropboxAPI::CreateSharedLinkRequest shareRequest(token, path.c_str());
+ NLHR_PTR response(shareRequest.Send(m_hConnection));
+
+ if (response && HTTP_CODE_SUCCESS(response->resultCode)) {
+ JSONNode root = GetJsonResponse(response);
+ return root["url"].as_string();
+ }
+
+ if (!response || response->resultCode != HTTP_CODE_CONFLICT)
+ HttpResponseToError(response);
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.isnull())
+ throw Exception(HttpStatusToError());
+
+ JSONNode error = root.at("error");
+ if (error.isnull()) {
+ JSONNode link = root.at("url");
+ return link.as_string();
+ }
+
+ json_string tag = error.at(".tag").as_string();
+ if (tag != "shared_link_already_exists")
+ throw Exception(tag.c_str());
+
+ DropboxAPI::GetSharedLinkRequest getRequest(token, path.c_str());
+ response = getRequest.Send(m_hConnection);
+
+ root = GetJsonResponse(response);
+
+ JSONNode links = root.at("links").as_array();
+ JSONNode link = links[(size_t)0].at("url");
+ return link.as_string();
+}
+
+void CDropboxService::Upload(FileTransferParam *ftp)
+{
+ auto serverDictionary = ftp->GetServerDirectory();
+ std::string serverFolder = serverDictionary ? T2Utf(serverDictionary) : "";
+ if (!serverFolder.empty()) {
+ auto path = PreparePath(serverFolder);
+ CreateFolder(path);
+ auto link = CreateSharedLink(path);
+ ftp->AddSharedLink(link.c_str());
+ }
+
+ ftp->FirstFile();
+ do
+ {
+ std::string fileName = T2Utf(ftp->GetCurrentRelativeFilePath());
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ size_t chunkSize = ftp->GetCurrentFileChunkSize();
+ mir_ptr<char> chunk((char*)mir_calloc(chunkSize));
+
+ std::string path;
+ if (!serverFolder.empty())
+ path = "/" + serverFolder + "/" + fileName;
+ else
+ path = PreparePath(fileName);
+
+ if (chunkSize == fileSize) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+
+ path = UploadFile(chunk, size, path);
+
+ ftp->Progress(size);
+ }
+ else {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+
+ auto sessionId = CreateUploadSession(chunk, size);
+
+ ftp->Progress(size);
+
+ size_t offset = size;
+ double chunkCount = ceil(double(fileSize) / chunkSize) - 2;
+ for (size_t i = 0; i < chunkCount; i++) {
+ ftp->CheckCurrentFile();
+
+ size = ftp->ReadCurrentFile(chunk, chunkSize);
+ UploadFileChunk(sessionId, chunk, size, offset);
+
+ offset += size;
+ ftp->Progress(size);
+ }
+
+ ftp->CheckCurrentFile();
+ size = offset < fileSize
+ ? ftp->ReadCurrentFile(chunk, fileSize - offset)
+ : 0;
+
+ path = CommitUploadSession(sessionId, chunk, size, offset, path);
+
+ ftp->Progress(size);
+ }
+
+ if (!ftp->IsCurrentFileInSubDirectory()) {
+ auto link = CreateSharedLink(path);
+ ftp->AddSharedLink(link.c_str());
+ }
+ } while (ftp->NextFile());
+}
diff --git a/protocols/CloudFile/src/Services/dropbox_service.h b/protocols/CloudFile/src/Services/dropbox_service.h
new file mode 100644
index 0000000000..b6c5a7dfcd
--- /dev/null
+++ b/protocols/CloudFile/src/Services/dropbox_service.h
@@ -0,0 +1,36 @@
+#ifndef _CLOUDSERVICE_DROPBOX_H_
+#define _CLOUDSERVICE_DROPBOX_H_
+
+class CDropboxService : public CCloudService
+{
+private:
+ void __cdecl RequestAccessTokenThread(void *);
+ void __cdecl RevokeAccessTokenThread(void *);
+
+ void HandleJsonError(JSONNode &node) override;
+
+ auto UploadFile(const char *data, size_t size, const std::string &path);
+ auto CreateUploadSession(const char *chunk, size_t chunkSize);
+ void UploadFileChunk(const std::string &sessionId, const char *chunk, size_t chunkSize, size_t offset);
+ auto CommitUploadSession(const std::string &sessionId, const char *chunk, size_t chunkSize, size_t offset, const std::string &path);
+ void CreateFolder(const std::string &path);
+ auto CreateSharedLink(const std::string &path);
+
+ void Upload(FileTransferParam *ftp) override;
+
+public:
+ CDropboxService(const char *protoName, const wchar_t *userName);
+
+ static CDropboxService* Init(const char *szModuleName, const wchar_t *szUserName);
+ static int UnInit(CDropboxService*);
+
+ const char* GetModuleName() const override;
+
+ int GetIconId() const override;
+
+ bool IsLoggedIn() override;
+ void Login(HWND owner = nullptr) override;
+ void Logout() override;
+};
+
+#endif //_CLOUDSERVICE_DROPBOX_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/Services/google_api.h b/protocols/CloudFile/src/Services/google_api.h
new file mode 100644
index 0000000000..90cfdb4d03
--- /dev/null
+++ b/protocols/CloudFile/src/Services/google_api.h
@@ -0,0 +1,200 @@
+#ifndef _GDRIVESERVICE_API_H_
+#define _GDRIVESERVICE_API_H_
+
+// https://developers.google.com/drive/v3/reference/
+namespace GDriveAPI
+{
+#define GOOGLE_OAUTH "https://accounts.google.com/o/oauth2/v2"
+#define GOOGLE_API "https://www.googleapis.com"
+#define GDRIVE_API_OAUTH GOOGLE_API "/oauth2/v4"
+#define GDRIVE_API_VER "/v3"
+#define GDRIVE_API GOOGLE_API "/drive" GDRIVE_API_VER "/files"
+#define GDRIVE_UPLOAD GOOGLE_API "/upload/drive" GDRIVE_API_VER "/files"
+#define GDRIVE_SHARE "https://drive.google.com/open?id="
+
+#define GOOGLE_APP_ID "528761318515-h1etlccvk5vjsbjuuj8i73cud8do4adi.apps.googleusercontent.com"
+#include "../../../miranda-private-keys/Google/client_secret.h"
+
+#define GOOGLE_AUTH GOOGLE_OAUTH "/auth?response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&access_type=offline&prompt=consent&redirect_uri=https%3A%2F%2Foauth.miranda-ng.org%2Fverification&client_id=" GOOGLE_APP_ID
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *code) :
+ HttpRequest(REQUEST_POST, GDRIVE_API_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data = "redirect_uri=https://oauth.miranda-ng.org/verification";
+ data.AppendFormat("&client_id=%s&client_secret=%s", GOOGLE_APP_ID, GOOGLE_CLIENT_SECRET);
+ data.AppendFormat("&grant_type=authorization_code&code=%s", code);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RefreshTokenRequest : public HttpRequest
+ {
+ public:
+ RefreshTokenRequest(const char *refreshToken) :
+ HttpRequest(REQUEST_POST, GDRIVE_API_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data(CMStringDataFormat::FORMAT,
+ "client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s",
+ GOOGLE_APP_ID, GOOGLE_CLIENT_SECRET, refreshToken);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RevokeAccessTokenRequest : public HttpRequest
+ {
+ public:
+ RevokeAccessTokenRequest(const char *token) :
+ HttpRequest(REQUEST_POST, GOOGLE_OAUTH "/revoke")
+ {
+ AddUrlParameter("token=%s", token);
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *parentId, const char *name, const char *data, size_t size) :
+ HttpRequest(REQUEST_POST, GDRIVE_UPLOAD)
+ {
+ AddUrlParameter("fields=id");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "multipart/related; boundary=upload");
+
+ CMStringA body = "--upload";
+ body.AppendChar(0x0A);
+ body.Append("Content-Type: application/json");
+ body.AppendChar(0x0A);
+ body.AppendChar(0x0A);
+ body.Append("{");
+ body.AppendFormat("\"name\": \"%s\"", name);
+ if (mir_strlen(parentId))
+ body.AppendFormat(", \"parents\": [\"%s\"]", parentId);
+ body.Append("}");
+ body.AppendChar(0x0A);
+ body.AppendChar(0x0A);
+ body.Append("--upload");
+ body.AppendChar(0x0A);
+ body.Append("Content-Type: application/octet-stream");
+ body.AppendChar(0x0A);
+ body.Append("Content-Transfer-Encoding: base64");
+ body.AppendChar(0x0A);
+ body.AppendChar(0x0A);
+ body.Append(ptrA(mir_base64_encode(data, size)));
+ body.AppendChar(0x0A);
+ body.Append("--upload--");
+
+ SetData(body.GetBuffer(), body.GetLength());
+ }
+ };
+
+ class CreateUploadSessionRequest : public HttpRequest
+ {
+ public:
+ CreateUploadSessionRequest(const char *token, const char *parentId, const char *name) :
+ HttpRequest(REQUEST_POST, GDRIVE_UPLOAD)
+ {
+ AddUrlParameter("uploadType=resumable");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode parents(JSON_ARRAY);
+ parents << JSONNode("", parentId);
+ parents.set_name("parents");
+
+ JSONNode params(JSON_NODE);
+ params << JSONNode("name", name);
+ params << parents;
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class UploadFileChunkRequest : public HttpRequest
+ {
+ public:
+ UploadFileChunkRequest(const char *uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize):
+ HttpRequest(REQUEST_PUT, uploadUri)
+ {
+ AddUrlParameter("fields=id");
+
+ uint64_t rangeMin = offset;
+ uint64_t rangeMax = offset + chunkSize - 1;
+ CMStringA range(CMStringDataFormat::FORMAT, "bytes %I64u-%I64u/%I64u", rangeMin, rangeMax, fileSize);
+ AddHeader("Content-Range", range);
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class GetFolderRequest : public HttpRequest
+ {
+ public:
+ GetFolderRequest(const char *token, const char *parentId, const char *name) :
+ HttpRequest(REQUEST_GET, GDRIVE_API)
+ {
+ AddUrlParameterWithEncode("q", "mimeType = 'application/vnd.google-apps.folder' and trashed = false and '%s' in parents and name = '%s'", mir_strlen(parentId) ? parentId : "root", name);
+ AddUrlParameterWithEncode("fields", "files(id)");
+
+ AddBearerAuthHeader(token);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *parentId, const char *name) :
+ HttpRequest(REQUEST_POST, GDRIVE_API)
+ {
+ AddUrlParameter("fields=id");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode parents(JSON_ARRAY);
+ parents.set_name("parents");
+ parents.push_back(JSONNode("", parentId));
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("name", name)
+ << JSONNode("mimeType", "application/vnd.google-apps.folder")
+ << parents;
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class GrantPermissionsRequest : public HttpRequest
+ {
+ public:
+ GrantPermissionsRequest(const char *token, const char *fileId) :
+ HttpRequest(REQUEST_POST, FORMAT, GDRIVE_API "/%s/permissions", fileId)
+ {
+ AddUrlParameter("fields=id");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("role", "reader")
+ << JSONNode("type", "anyone");
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+};
+
+#endif //_GDRIVESERVICE_API_H_
diff --git a/protocols/CloudFile/src/Services/google_service.cpp b/protocols/CloudFile/src/Services/google_service.cpp
new file mode 100644
index 0000000000..536d2ff65e
--- /dev/null
+++ b/protocols/CloudFile/src/Services/google_service.cpp
@@ -0,0 +1,297 @@
+#include "..\stdafx.h"
+#include "google_api.h"
+
+
+struct CMPluginGoogle : public CMPluginBase
+{
+ CMPluginGoogle() :
+ CMPluginBase(MODULENAME "/GDrive", pluginInfoEx)
+ {
+ m_hInst = g_plugin.getInst();
+
+ RegisterProtocol(PROTOTYPE_PROTOWITHACCS, (pfnInitProto)CGDriveService::Init, (pfnUninitProto)CGDriveService::UnInit);
+ }
+}
+g_pluginGoogle;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CGDriveService::CGDriveService(const char *protoName, const wchar_t *userName) :
+ CCloudService(protoName, userName, &g_pluginGoogle)
+{
+ m_hProtoIcon = GetIconHandle(IDI_GDRIVE);
+}
+
+CGDriveService* CGDriveService::Init(const char *moduleName, const wchar_t *userName)
+{
+ CGDriveService *proto = new CGDriveService(moduleName, userName);
+ Services.insert(proto);
+ return proto;
+}
+
+int CGDriveService::UnInit(CGDriveService *proto)
+{
+ Services.remove(proto);
+ delete proto;
+ return 0;
+}
+
+const char* CGDriveService::GetModuleName() const
+{
+ return "/Google";
+}
+
+int CGDriveService::GetIconId() const
+{
+ return IDI_GDRIVE;
+}
+
+bool CGDriveService::IsLoggedIn()
+{
+ ptrA token(getStringA("TokenSecret"));
+ if (!token || token[0] == 0)
+ return false;
+ time_t now = time(0);
+ time_t expiresIn = getDword("ExpiresIn");
+ return now < expiresIn;
+}
+
+void CGDriveService::Login(HWND owner)
+{
+ ptrA token(getStringA("TokenSecret"));
+ ptrA refreshToken(getStringA("RefreshToken"));
+ if (token && refreshToken && refreshToken[0]) {
+ GDriveAPI::RefreshTokenRequest request(refreshToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+
+ JSONNode node = root.at("access_token");
+ setString("TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ setDword("ExpiresIn", expiresIn);
+
+ return;
+ }
+
+ COAuthDlg dlg(this, GOOGLE_AUTH, (MyThreadFunc)&CGDriveService::RequestAccessTokenThread);
+ dlg.SetParent(owner);
+ dlg.DoModal();
+}
+
+void CGDriveService::Logout()
+{
+ ForkThread((MyThreadFunc)&CGDriveService::RevokeAccessTokenThread);
+}
+
+void CGDriveService::RequestAccessTokenThread(void *param)
+{
+ HWND hwndDlg = (HWND)param;
+
+ if (IsLoggedIn())
+ Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ GDriveAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response == nullptr || response->resultCode != HTTP_CODE_OK) {
+ const char *error = response && response->dataLength
+ ? response->pData
+ : HttpStatusToError(response ? response->resultCode : 0);
+
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), error);
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ CMStringW error_description = node.as_mstring();
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(error_description, MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ node = root.at("access_token");
+ db_set_s(0, GetAccountName(), "TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ db_set_dw(0, GetAccountName(), "ExpiresIn", expiresIn);
+
+ node = root.at("refresh_token");
+ db_set_s(0, GetAccountName(), "RefreshToken", node.as_string().c_str());
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+}
+
+void CGDriveService::RevokeAccessTokenThread(void*)
+{
+ ptrA token(db_get_sa(0, GetAccountName(), "TokenSecret"));
+ GDriveAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ delSetting("ExpiresIn");
+ delSetting("TokenSecret");
+ delSetting("RefreshToken");
+}
+
+void CGDriveService::HandleJsonError(JSONNode &node)
+{
+ JSONNode error = node.at("error");
+ if (!error.isnull()) {
+ json_string tag = error.at(".tag").as_string();
+ throw Exception(tag.c_str());
+ }
+}
+
+auto CGDriveService::UploadFile(const std::string &parentId, const std::string &fileName, const char *data, size_t size)
+{
+ ptrA token(getStringA("TokenSecret"));
+ GDriveAPI::UploadFileRequest request(token, parentId.c_str(), fileName.c_str(), data, size);
+ NLHR_PTR response(request.Send(m_hConnection));
+ JSONNode root = GetJsonResponse(response);
+ return root["id"].as_string();
+}
+
+auto CGDriveService::CreateUploadSession(const std::string &parentId, const std::string &fileName)
+{
+ ptrA token(getStringA("TokenSecret"));
+ GDriveAPI::CreateUploadSessionRequest request(token, parentId.c_str(), fileName.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ HandleHttpError(response);
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ for (int i = 0; i < response->headersCount; i++) {
+ if (mir_strcmpi(response->headers[i].szName, "Location"))
+ continue;
+ return std::string(response->headers[i].szValue);
+ }
+ }
+
+ HttpResponseToError(response);
+
+ return std::string();
+}
+
+auto CGDriveService::UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize)
+{
+ GDriveAPI::UploadFileChunkRequest request(uploadUri.c_str(), chunk, chunkSize, offset, fileSize);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response->resultCode == HTTP_CODE_PERMANENT_REDIRECT)
+ return std::string();
+
+ HandleHttpError(response);
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ JSONNode root = GetJsonResponse(response);
+ return root["id"].as_string();
+ }
+
+ HttpResponseToError(response);
+
+ return std::string();
+}
+
+auto CGDriveService::CreateFolder(const std::string &parentId, const std::string &name)
+{
+ ptrA token(getStringA("TokenSecret"));
+ GDriveAPI::GetFolderRequest getFolderRequest(token, parentId.c_str(), name.c_str());
+ NLHR_PTR response(getFolderRequest.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode files = root["files"].as_array();
+ if (files.size() > 0)
+ return files[(size_t)0]["id"].as_string();
+
+ GDriveAPI::CreateFolderRequest createFolderRequest(token, parentId.c_str(), name.c_str());
+ response = createFolderRequest.Send(m_hConnection);
+
+ root = GetJsonResponse(response);
+ return root["id"].as_string();
+}
+
+auto CGDriveService::CreateSharedLink(const std::string &itemId)
+{
+ ptrA token(getStringA("TokenSecret"));
+ GDriveAPI::GrantPermissionsRequest request(token, itemId.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ HandleHttpError(response);
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ std::string url = GDRIVE_SHARE;
+ url += itemId;
+ return url;
+ }
+
+ HttpResponseToError(response);
+
+ return std::string();
+}
+
+void CGDriveService::Upload(FileTransferParam *ftp)
+{
+ std::string folderId = "root";
+ auto serverDictionary = ftp->GetServerDirectory();
+ std::string serverFolder = serverDictionary ? T2Utf(serverDictionary) : "";
+ if (!serverFolder.empty()) {
+ folderId = CreateFolder(folderId, serverFolder);
+ auto link = CreateSharedLink(folderId);
+ ftp->AddSharedLink(link.c_str());
+ }
+
+ ftp->FirstFile();
+ do {
+ std::string fileName = T2Utf(ftp->GetCurrentRelativeFilePath());
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ size_t chunkSize = ftp->GetCurrentFileChunkSize();
+ mir_ptr<char> chunk((char*)mir_calloc(chunkSize));
+
+ std::string fileId;
+ if (chunkSize == fileSize) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+ fileId = UploadFile(folderId, fileName, chunk, size);
+ ftp->Progress(size);
+ }
+ else {
+ auto uploadUri = CreateUploadSession(folderId, fileName);
+
+ uint64_t offset = 0;
+ double chunkCount = ceil(double(fileSize) / chunkSize);
+ for (size_t i = 0; i < chunkCount; i++) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+ fileId = UploadFileChunk(uploadUri, chunk, size, offset, fileSize);
+ offset += size;
+ ftp->Progress(size);
+ }
+ }
+
+ if (!ftp->IsCurrentFileInSubDirectory()) {
+ auto link = CreateSharedLink(fileId);
+ ftp->AddSharedLink(link.c_str());
+ }
+ } while (ftp->NextFile());
+}
diff --git a/protocols/CloudFile/src/Services/google_service.h b/protocols/CloudFile/src/Services/google_service.h
new file mode 100644
index 0000000000..919babb86f
--- /dev/null
+++ b/protocols/CloudFile/src/Services/google_service.h
@@ -0,0 +1,35 @@
+#ifndef _CLOUDFILE_GDRIVE_H_
+#define _CLOUDFILE_GDRIVE_H_
+
+class CGDriveService : public CCloudService
+{
+private:
+ void __cdecl RequestAccessTokenThread(void *param);
+ void __cdecl RevokeAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node) override;
+
+ auto UploadFile(const std::string &parentId, const std::string &fileName, const char *data, size_t size);
+ auto CreateUploadSession(const std::string &parentId, const std::string &fileName);
+ auto UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize);
+ auto CreateFolder(const std::string &parentId, const std::string &name);
+ auto CreateSharedLink(const std::string &itemId);
+
+ void Upload(FileTransferParam *ftp) override;
+
+public:
+ CGDriveService(const char *protoName, const wchar_t *userName);
+
+ static CGDriveService* Init(const char *szModuleName, const wchar_t *szUserName);
+ static int UnInit(CGDriveService*);
+
+ const char* GetModuleName() const override;
+
+ int GetIconId() const override;
+
+ bool IsLoggedIn() override;
+ void Login(HWND owner = nullptr) override;
+ void Logout() override;
+};
+
+#endif //_CLOUDFILE_GDRIVE_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/Services/microsoft_api.h b/protocols/CloudFile/src/Services/microsoft_api.h
new file mode 100644
index 0000000000..6c22ac512f
--- /dev/null
+++ b/protocols/CloudFile/src/Services/microsoft_api.h
@@ -0,0 +1,188 @@
+#ifndef _ONEDRIVESERVICE_API_H_
+#define _ONEDRIVESERVICE_API_H_
+
+// https://docs.microsoft.com/onedrive/developer/rest-api/
+namespace OneDriveAPI
+{
+#define MICROSOFT_OAUTH "https://login.microsoftonline.com/common/oauth2/v2.0"
+#define ONEDRIVE_API "https://graph.microsoft.com/v1.0/drive"
+
+#define MS_APP_ID "72b87ac7-42eb-4a97-a620-91a7f8d8b5ae"
+#include "../../../miranda-private-keys/Microsoft/client_secret.h"
+
+#define MICROSOFT_AUTH MICROSOFT_OAUTH "/authorize?response_type=code&scope=offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Ffiles.readWrite&redirect_uri=https%3A%2F%2Foauth.miranda-ng.org%2Fverification&client_id=" MS_APP_ID
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *code) :
+ HttpRequest(REQUEST_POST, MICROSOFT_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data = "redirect_uri=https://oauth.miranda-ng.org/verification";
+ data.Append("&scope=offline_access https://graph.microsoft.com/files.readWrite");
+ data.AppendFormat("&client_id=%s&client_secret=%s", MS_APP_ID, MS_CLIENT_SECRET);
+ data.AppendFormat("&grant_type=authorization_code&code=%s", code);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RefreshTokenRequest : public HttpRequest
+ {
+ public:
+ RefreshTokenRequest(const char *refreshToken) :
+ HttpRequest(REQUEST_POST, MICROSOFT_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data = "redirect_uri=https://oauth.miranda-ng.org/verification";
+ data.Append("&scope=offline_access https://graph.microsoft.com/files.readWrite");
+ data.AppendFormat("&client_id=%s&client_secret=%s", MS_APP_ID, MS_CLIENT_SECRET);
+ data.AppendFormat("&grant_type=refresh_token&refresh_token=%s", refreshToken);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *name, const char *data, size_t size, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_PUT, FORMAT, ONEDRIVE_API "/special/approot:/%s:/content", mir_urlEncode(name).c_str())
+ {
+ AddUrlParameter("select=id");
+
+ if (strategy == OnConflict::RENAME)
+ AddUrlParameter("@microsoft.graph.conflictBehavior=rename");
+ else if (strategy == OnConflict::REPLACE)
+ AddUrlParameter("@microsoft.graph.conflictBehavior=replace");
+
+ AddBearerAuthHeader(token);
+
+ SetData(data, size);
+ }
+
+ UploadFileRequest(const char *token, const char *parentId, const char *name, const char *data, size_t size, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_PUT, FORMAT, ONEDRIVE_API "/items/%s:/%s:/content", parentId, mir_urlEncode(name).c_str())
+ {
+ AddUrlParameter("select=id");
+
+ if (strategy == OnConflict::RENAME)
+ AddUrlParameter("@microsoft.graph.conflictBehavior=rename");
+ else if (strategy == OnConflict::REPLACE)
+ AddUrlParameter("@microsoft.graph.conflictBehavior=replace");
+
+ AddBearerAuthHeader(token);
+
+ SetData(data, size);
+ }
+ };
+
+ class CreateUploadSessionRequest : public HttpRequest
+ {
+ public:
+ CreateUploadSessionRequest(const char *token, const char *name, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_POST, FORMAT, ONEDRIVE_API "/special/approot:/%s:/createUploadSession", mir_urlEncode(name).c_str())
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode item(JSON_NODE);
+ item.set_name("item");
+ if (strategy == OnConflict::RENAME)
+ item << JSONNode("@microsoft.graph.conflictBehavior", "rename");
+ if (strategy == OnConflict::REPLACE)
+ item << JSONNode("@microsoft.graph.conflictBehavior", "replace");
+
+ JSONNode params(JSON_NODE);
+ params << item;
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+
+ CreateUploadSessionRequest(const char *token, const char *parentId, const char *name, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_POST, FORMAT, ONEDRIVE_API "/items/%s:/%s:/createUploadSession", parentId, mir_urlEncode(name).c_str())
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode item(JSON_NODE);
+ item.set_name("item");
+ if (strategy == OnConflict::RENAME)
+ item << JSONNode("@microsoft.graph.conflictBehavior", "rename");
+ if (strategy == OnConflict::REPLACE)
+ item << JSONNode("@microsoft.graph.conflictBehavior", "replace");
+
+ JSONNode params(JSON_NODE);
+ params << item;
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class UploadFileChunkRequest : public HttpRequest
+ {
+ public:
+ UploadFileChunkRequest(const char *uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize) :
+ HttpRequest(REQUEST_PUT, uploadUri)
+ {
+ AddUrlParameter("select=id");
+
+ uint64_t rangeMin = offset;
+ uint64_t rangeMax = offset + chunkSize - 1;
+ CMStringA range(CMStringDataFormat::FORMAT, "bytes %I64u-%I64u/%I64u", rangeMin, rangeMax, fileSize);
+ AddHeader("Content-Range", range);
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, ONEDRIVE_API "/special/approot/children")
+ {
+ AddUrlParameter("select=id");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode folder(JSON_NODE);
+ folder.set_name("folder");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("name", path)
+ << folder;
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class CreateSharedLinkRequest : public HttpRequest
+ {
+ public:
+ CreateSharedLinkRequest(const char *token, const char *itemId) :
+ HttpRequest(REQUEST_POST, FORMAT, ONEDRIVE_API "/items/%s/createLink", itemId)
+ {
+ AddUrlParameter("select=link");
+
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("type", "view")
+ << JSONNode("scope", "anonymous");
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+};
+
+#endif //_ONEDRIVESERVICE_API_H_
diff --git a/protocols/CloudFile/src/Services/microsoft_service.cpp b/protocols/CloudFile/src/Services/microsoft_service.cpp
new file mode 100644
index 0000000000..992312e2a8
--- /dev/null
+++ b/protocols/CloudFile/src/Services/microsoft_service.cpp
@@ -0,0 +1,269 @@
+#include "..\stdafx.h"
+#include "microsoft_api.h"
+
+struct CMPluginOnedrive : public CMPluginBase
+{
+ CMPluginOnedrive() :
+ CMPluginBase(MODULENAME "/OneDrive", pluginInfoEx)
+ {
+ m_hInst = g_plugin.getInst();
+
+ RegisterProtocol(PROTOTYPE_PROTOWITHACCS, (pfnInitProto)COneDriveService::Init, (pfnUninitProto)COneDriveService::UnInit);
+ }
+}
+g_pluginOnedrive;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+COneDriveService::COneDriveService(const char *protoName, const wchar_t *userName) :
+ CCloudService(protoName, userName, &g_pluginOnedrive)
+{
+ m_hProtoIcon = GetIconHandle(IDI_ONEDRIVE);
+}
+
+COneDriveService* COneDriveService::Init(const char *moduleName, const wchar_t *userName)
+{
+ COneDriveService *proto = new COneDriveService(moduleName, userName);
+ Services.insert(proto);
+ return proto;
+}
+
+int COneDriveService::UnInit(COneDriveService *proto)
+{
+ Services.remove(proto);
+ delete proto;
+ return 0;
+}
+
+const char* COneDriveService::GetModuleName() const
+{
+ return "/OneDrive";
+}
+
+int COneDriveService::GetIconId() const
+{
+ return IDI_ONEDRIVE;
+}
+
+bool COneDriveService::IsLoggedIn()
+{
+ ptrA token(getStringA("TokenSecret"));
+ if (!token || token[0] == 0)
+ return false;
+ time_t now = time(0);
+ time_t expiresIn = getDword("ExpiresIn");
+ return now < expiresIn;
+}
+
+void COneDriveService::Login(HWND owner)
+{
+ ptrA refreshToken(getStringA("RefreshToken"));
+ if (refreshToken && refreshToken[0]) {
+ OneDriveAPI::RefreshTokenRequest request(refreshToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+
+ JSONNode node = root.at("access_token");
+ db_set_s(0, GetAccountName(), "TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ setDword("ExpiresIn", expiresIn);
+
+ return;
+ }
+
+ COAuthDlg dlg(this, MICROSOFT_AUTH, (MyThreadFunc)&COneDriveService::RequestAccessTokenThread);
+ dlg.SetParent(owner);
+ dlg.DoModal();
+}
+
+void COneDriveService::Logout()
+{
+ delSetting("ExpiresIn");
+ delSetting("TokenSecret");
+ delSetting("RefreshToken");
+}
+
+void COneDriveService::RequestAccessTokenThread(void *param)
+{
+ HWND hwndDlg = (HWND)param;
+
+ if (IsLoggedIn())
+ Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ OneDriveAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response == nullptr || response->resultCode != HTTP_CODE_OK) {
+ const char *error = response->dataLength
+ ? response->pData
+ : HttpStatusToError(response->resultCode);
+
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), error);
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ CMStringW error_description = node.as_mstring();
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(error_description, MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ node = root.at("access_token");
+ setString("TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ setDword("ExpiresIn", expiresIn);
+
+ node = root.at("refresh_token");
+ setString("RefreshToken", node.as_string().c_str());
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+}
+
+void COneDriveService::HandleJsonError(JSONNode &node)
+{
+ JSONNode error = node.at("error");
+ if (!error.isnull()) {
+ json_string tag = error.at("message").as_string();
+ throw Exception(tag.c_str());
+ }
+}
+
+auto COneDriveService::UploadFile(const std::string &parentId, const std::string &fileName, const char *data, size_t size)
+{
+ ptrA token(getStringA("TokenSecret"));
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ OneDriveAPI::UploadFileRequest *request = !parentId.empty()
+ ? new OneDriveAPI::UploadFileRequest(token, parentId.c_str(), fileName.c_str(), data, size, (OnConflict)strategy)
+ : new OneDriveAPI::UploadFileRequest(token, fileName.c_str(), data, size, (OnConflict)strategy);
+ NLHR_PTR response(request->Send(m_hConnection));
+ delete request;
+
+ JSONNode root = GetJsonResponse(response);
+ return root["id"].as_string();
+}
+
+auto COneDriveService::CreateUploadSession(const std::string &parentId, const std::string &fileName)
+{
+ ptrA token(getStringA("TokenSecret"));
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ OneDriveAPI::CreateUploadSessionRequest *request = !parentId.empty()
+ ? new OneDriveAPI::CreateUploadSessionRequest(token, parentId.c_str(), fileName.c_str(), (OnConflict)strategy)
+ : new OneDriveAPI::CreateUploadSessionRequest(token, fileName.c_str(), (OnConflict)strategy);
+ NLHR_PTR response(request->Send(m_hConnection));
+ delete request;
+
+ JSONNode root = GetJsonResponse(response);
+ return root["uploadUrl"].as_string();
+}
+
+auto COneDriveService::UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize)
+{
+ OneDriveAPI::UploadFileChunkRequest request(uploadUri.c_str(), chunk, chunkSize, offset, fileSize);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ HandleHttpError(response);
+
+ if (response->resultCode == HTTP_CODE_ACCEPTED)
+ return std::string();
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ JSONNode root = GetJsonResponse(response);
+ return root["id"].as_string();
+ }
+
+ HttpResponseToError(response);
+
+ return std::string();
+}
+
+auto COneDriveService::CreateFolder(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ OneDriveAPI::CreateFolderRequest request(token, path.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["id"].as_string();
+}
+
+auto COneDriveService::CreateSharedLink(const std::string &itemId)
+{
+ ptrA token(getStringA("TokenSecret"));
+ OneDriveAPI::CreateSharedLinkRequest request(token, itemId.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["link"]["webUrl"].as_string();
+}
+
+void COneDriveService::Upload(FileTransferParam *ftp)
+{
+ std::string folderId;
+ auto serverDictionary = ftp->GetServerDirectory();
+ std::string serverFolder = serverDictionary ? T2Utf(serverDictionary) : "";
+ if (!serverFolder.empty()) {
+ folderId = CreateFolder(serverFolder);
+ auto link = CreateSharedLink(folderId);
+ ftp->AddSharedLink(link.c_str());
+ }
+
+ ftp->FirstFile();
+ do {
+ std::string fileName = T2Utf(ftp->GetCurrentRelativeFilePath());
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ size_t chunkSize = ftp->GetCurrentFileChunkSize();
+ mir_ptr<char> chunk((char*)mir_calloc(chunkSize));
+
+ std::string fileId;
+ if (chunkSize == fileSize) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+
+ fileId = UploadFile(folderId, fileName, chunk, size);
+
+ ftp->Progress(size);
+ }
+ else {
+ auto uploadUri = CreateUploadSession(folderId, fileName);
+
+ uint64_t offset = 0;
+ double chunkCount = ceil(double(fileSize) / chunkSize);
+ for (size_t i = 0; i < chunkCount; i++) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+ fileId = UploadFileChunk(uploadUri, chunk, size, offset, fileSize);
+ offset += size;
+ ftp->Progress(size);
+ }
+ }
+
+ if (!ftp->IsCurrentFileInSubDirectory()) {
+ auto link = CreateSharedLink(fileId);
+ ftp->AddSharedLink(link.c_str());
+ }
+ } while (ftp->NextFile());
+}
diff --git a/protocols/CloudFile/src/Services/microsoft_service.h b/protocols/CloudFile/src/Services/microsoft_service.h
new file mode 100644
index 0000000000..d993410003
--- /dev/null
+++ b/protocols/CloudFile/src/Services/microsoft_service.h
@@ -0,0 +1,34 @@
+#ifndef _CLOUDFILE_ONEDRIVE_H_
+#define _CLOUDFILE_ONEDRIVE_H_
+
+class COneDriveService : public CCloudService
+{
+private:
+ void __cdecl RequestAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node) override;
+
+ auto UploadFile(const std::string &parentId, const std::string &fileName, const char *data, size_t size);
+ auto CreateUploadSession(const std::string &parentId, const std::string &fileName);
+ auto UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize);
+ auto CreateFolder(const std::string &name);
+ auto CreateSharedLink(const std::string &itemId);
+
+ void Upload(FileTransferParam *ftp) override;
+
+public:
+ COneDriveService(const char *protoName, const wchar_t *userName);
+
+ static COneDriveService* Init(const char *szModuleName, const wchar_t *szUserName);
+ static int UnInit(COneDriveService*);
+
+ const char* GetModuleName() const override;
+
+ int GetIconId() const override;
+
+ bool IsLoggedIn() override;
+ void Login(HWND owner = nullptr) override;
+ void Logout() override;
+};
+
+#endif //_CLOUDFILE_ONEDRIVE_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/Services/yandex_api.h b/protocols/CloudFile/src/Services/yandex_api.h
new file mode 100644
index 0000000000..c7c3dfe19d
--- /dev/null
+++ b/protocols/CloudFile/src/Services/yandex_api.h
@@ -0,0 +1,131 @@
+#ifndef _YANDEXSERVICE_API_H_
+#define _YANDEXSERVICE_API_H_
+
+// https://tech.yandex.ru/disk/api/concepts/about-docpage/
+namespace YandexAPI
+{
+#define YANDEX_OAUTH "https://oauth.yandex.ru"
+#define YADISK_API_VER "/v1"
+#define YADISK_API "https://cloud-api.yandex.net" YADISK_API_VER "/disk/resources"
+
+#define YANDEX_APP_ID "c311a5967cae4efa88d1af97d01ea0e8"
+#include "../../../miranda-private-keys/Yandex/client_secret.h"
+
+#define YANDEX_AUTH YANDEX_OAUTH "/authorize?response_type=code&client_id=" YANDEX_APP_ID
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *code) :
+ HttpRequest(REQUEST_POST, YANDEX_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data(CMStringDataFormat::FORMAT,
+ "client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s",
+ YANDEX_APP_ID, YADISK_CLIENT_SECRET, code);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RefreshTokenRequest : public HttpRequest
+ {
+ public:
+ RefreshTokenRequest(const char *refreshToken) :
+ HttpRequest(REQUEST_POST, YANDEX_OAUTH "/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data(CMStringDataFormat::FORMAT,
+ "client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s",
+ YANDEX_APP_ID, YADISK_CLIENT_SECRET, refreshToken);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RevokeAccessTokenRequest : public HttpRequest
+ {
+ public:
+ RevokeAccessTokenRequest(const char *token) :
+ HttpRequest(REQUEST_POST, YANDEX_OAUTH "/token/revoke")
+ {
+ AddOAuthHeader(token);
+ }
+ };
+
+ class GetUploadUrlRequest : public HttpRequest
+ {
+ public:
+ GetUploadUrlRequest(const char *token, const char *path, OnConflict strategy = NONE) :
+ HttpRequest(REQUEST_GET, YADISK_API "/upload")
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=app:%s", mir_urlEncode(path).c_str());
+ AddUrlParameter("fields=href");
+ if (strategy == OnConflict::REPLACE)
+ AddUrlParameter("overwrite=true");
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *url, const char *data, size_t size) :
+ HttpRequest(REQUEST_PUT, url)
+ {
+ SetData(data, size);
+ }
+ };
+
+ class UploadFileChunkRequest : public HttpRequest
+ {
+ public:
+ UploadFileChunkRequest(const char *url, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize) :
+ HttpRequest(REQUEST_PUT, url)
+ {
+ uint64_t rangeMin = offset;
+ uint64_t rangeMax = offset + chunkSize - 1;
+ CMStringA range(CMStringDataFormat::FORMAT, "bytes %I64u-%I64u/%I64u", rangeMin, rangeMax, fileSize);
+ AddHeader("Content-Range", range);
+
+ SetData(chunk, chunkSize);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_PUT, YADISK_API)
+ {
+ AddOAuthHeader(token);
+ AddUrlParameterWithEncode("path", "app:%s", path);
+ AddUrlParameter("fields=href");
+ }
+ };
+
+ class PublishRequest : public HttpRequest
+ {
+ public:
+ PublishRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_PUT, YADISK_API "/publish")
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=app:%s", mir_urlEncode(path).c_str());
+ }
+ };
+
+ class GetResourcesRequest : public HttpRequest
+ {
+ public:
+ GetResourcesRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_GET, YADISK_API)
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=app:%s", mir_urlEncode(path).c_str());
+ AddUrlParameter("fields=public_url");
+ }
+ };
+};
+
+#endif //_YANDEXSERVICE_API_H_
diff --git a/protocols/CloudFile/src/Services/yandex_service.cpp b/protocols/CloudFile/src/Services/yandex_service.cpp
new file mode 100644
index 0000000000..4bd0210ca1
--- /dev/null
+++ b/protocols/CloudFile/src/Services/yandex_service.cpp
@@ -0,0 +1,290 @@
+#include "..\stdafx.h"
+#include "yandex_api.h"
+
+struct CMPluginYandex : public CMPluginBase
+{
+ CMPluginYandex() :
+ CMPluginBase(MODULENAME "/YandexDisk", pluginInfoEx)
+ {
+ m_hInst = g_plugin.getInst();
+
+ RegisterProtocol(PROTOTYPE_PROTOWITHACCS, (pfnInitProto)CYandexService::Init, (pfnUninitProto)CYandexService::UnInit);
+ }
+}
+g_pluginYandex;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CYandexService::CYandexService(const char *protoName, const wchar_t *userName) :
+ CCloudService(protoName, userName, &g_pluginYandex)
+{
+ m_hProtoIcon = GetIconHandle(IDI_YADISK);
+}
+
+CYandexService* CYandexService::Init(const char *moduleName, const wchar_t *userName)
+{
+ CYandexService *proto = new CYandexService(moduleName, userName);
+ Services.insert(proto);
+ return proto;
+}
+
+int CYandexService::UnInit(CYandexService *proto)
+{
+ Services.remove(proto);
+ delete proto;
+ return 0;
+}
+
+const char* CYandexService::GetModuleName() const
+{
+ return "Yandex.Disk";
+}
+
+int CYandexService::GetIconId() const
+{
+ return IDI_YADISK;
+}
+
+bool CYandexService::IsLoggedIn()
+{
+ ptrA token(getStringA("TokenSecret"));
+ if (!token || token[0] == 0)
+ return false;
+ time_t now = time(0);
+ time_t expiresIn = getDword("ExpiresIn");
+ return now < expiresIn;
+}
+
+void CYandexService::Login(HWND owner)
+{
+ ptrA token(getStringA("TokenSecret"));
+ ptrA refreshToken(getStringA("RefreshToken"));
+ if (token && refreshToken && refreshToken[0]) {
+ YandexAPI::RefreshTokenRequest request(refreshToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+
+ JSONNode node = root.at("access_token");
+ setString("TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ setDword("ExpiresIn", expiresIn);
+
+ node = root.at("refresh_token");
+ setString("RefreshToken", node.as_string().c_str());
+
+ return;
+ }
+
+ COAuthDlg dlg(this, YANDEX_AUTH, (MyThreadFunc)&CYandexService::RequestAccessTokenThread);
+ dlg.SetParent(owner);
+ dlg.DoModal();
+}
+
+void CYandexService::Logout()
+{
+ ForkThread((MyThreadFunc)&CYandexService::RevokeAccessTokenThread);
+}
+
+void CYandexService::RequestAccessTokenThread(void *param)
+{
+ HWND hwndDlg = (HWND)param;
+
+ if (IsLoggedIn())
+ Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ YandexAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (response == nullptr || response->resultCode != HTTP_CODE_OK) {
+ const char *error = response->dataLength
+ ? response->pData
+ : HttpStatusToError(response->resultCode);
+
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), error);
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("Server does not respond"), MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ CMStringW error_description = node.as_mstring();
+ Netlib_Logf(m_hConnection, "%s: %s", GetAccountName(), HttpStatusToError(response->resultCode));
+ ShowNotification(error_description, MB_ICONERROR);
+ EndDialog(hwndDlg, 0);
+ return;
+ }
+
+ node = root.at("access_token");
+ setString("TokenSecret", node.as_string().c_str());
+
+ node = root.at("expires_in");
+ time_t expiresIn = time(0) + node.as_int();
+ setDword("ExpiresIn", expiresIn);
+
+ node = root.at("refresh_token");
+ setString("RefreshToken", node.as_string().c_str());
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+}
+
+void CYandexService::RevokeAccessTokenThread(void*)
+{
+ ptrA token(db_get_sa(0, GetAccountName(), "TokenSecret"));
+ YandexAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ delSetting("ExpiresIn");
+ delSetting("TokenSecret");
+ delSetting("RefreshToken");
+}
+
+void CYandexService::HandleJsonError(JSONNode &node)
+{
+ JSONNode error = node.at("error");
+ if (!error.isnull()) {
+ json_string tag = error.at(".tag").as_string();
+ throw Exception(tag.c_str());
+ }
+}
+
+auto CYandexService::CreateUploadSession(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ YandexAPI::GetUploadUrlRequest request(token, path.c_str(), (OnConflict)strategy);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ return root["href"].as_string();
+}
+
+void CYandexService::UploadFile(const std::string &uploadUri, const char *data, size_t size)
+{
+ YandexAPI::UploadFileRequest request(uploadUri.c_str(), data, size);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ HandleHttpError(response);
+
+ if (response->resultCode == HTTP_CODE_CREATED)
+ return;
+
+ HttpResponseToError(response);
+}
+
+void CYandexService::UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize)
+{
+ YandexAPI::UploadFileChunkRequest request(uploadUri.c_str(), chunk, chunkSize, offset, fileSize);
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ HandleHttpError(response);
+
+ if (response->resultCode == HTTP_CODE_ACCEPTED ||
+ response->resultCode == HTTP_CODE_CREATED)
+ return;
+
+ HttpResponseToError(response);
+}
+
+void CYandexService::CreateFolder(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ YandexAPI::CreateFolderRequest request(token, path.c_str());
+ NLHR_PTR response(request.Send(m_hConnection));
+
+ if (HTTP_CODE_SUCCESS(response->resultCode)) {
+ GetJsonResponse(response);
+ return;
+ }
+
+ // forder exists on server
+ if (response->resultCode == HTTP_CODE_CONFLICT) {
+ return;
+ }
+
+ HttpResponseToError(response);
+}
+
+auto CYandexService::CreateSharedLink(const std::string &path)
+{
+ ptrA token(getStringA("TokenSecret"));
+ YandexAPI::PublishRequest publishRequest(token, path.c_str());
+ NLHR_PTR response(publishRequest.Send(m_hConnection));
+
+ GetJsonResponse(response);
+
+ YandexAPI::GetResourcesRequest resourcesRequest(token, path.c_str());
+ response = resourcesRequest.Send(m_hConnection);
+
+ JSONNode root = GetJsonResponse(response);
+ return root["public_url"].as_string();
+}
+
+void CYandexService::Upload(FileTransferParam *ftp)
+{
+ auto serverDictionary = ftp->GetServerDirectory();
+ std::string serverFolder = serverDictionary ? T2Utf(serverDictionary) : "";
+ if (!serverFolder.empty()) {
+ auto path = PreparePath(serverFolder);
+ CreateFolder(path);
+ auto link = CreateSharedLink(path);
+ ftp->AddSharedLink(link.c_str());
+ }
+
+ ftp->FirstFile();
+ do
+ {
+ std::string fileName = T2Utf(ftp->GetCurrentRelativeFilePath());
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ size_t chunkSize = ftp->GetCurrentFileChunkSize();
+ mir_ptr<char> chunk((char*)mir_calloc(chunkSize));
+
+ std::string path;
+ if (!serverFolder.empty())
+ path = "/" + serverFolder + "/" + fileName;
+ else
+ path = PreparePath(fileName);
+
+ auto uploadUri = CreateUploadSession(path);
+
+ if (chunkSize == fileSize) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+ UploadFile(uploadUri, chunk, size);
+ ftp->Progress(size);
+ }
+ else {
+ uint64_t offset = 0;
+ double chunkCount = ceil(double(fileSize) / chunkSize);
+ for (size_t i = 0; i < chunkCount; i++) {
+ ftp->CheckCurrentFile();
+ size_t size = ftp->ReadCurrentFile(chunk, chunkSize);
+ UploadFileChunk(uploadUri, chunk, size, offset, fileSize);
+ offset += size;
+ ftp->Progress(size);
+ }
+ }
+
+ if (!ftp->IsCurrentFileInSubDirectory()) {
+ auto link = CreateSharedLink(path);
+ ftp->AddSharedLink(link.c_str());
+ }
+ } while (ftp->NextFile());
+}
diff --git a/protocols/CloudFile/src/Services/yandex_service.h b/protocols/CloudFile/src/Services/yandex_service.h
new file mode 100644
index 0000000000..0fdcdf679a
--- /dev/null
+++ b/protocols/CloudFile/src/Services/yandex_service.h
@@ -0,0 +1,35 @@
+#ifndef _CLOUDFILE_YANDEX_H_
+#define _CLOUDFILE_YANDEX_H_
+
+class CYandexService : public CCloudService
+{
+private:
+ void __cdecl RequestAccessTokenThread(void *param);
+ void __cdecl RevokeAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node) override;
+
+ auto CreateUploadSession(const std::string &path);
+ void UploadFile(const std::string &uploadUri, const char *data, size_t size);
+ void UploadFileChunk(const std::string &uploadUri, const char *chunk, size_t chunkSize, uint64_t offset, uint64_t fileSize);
+ void CreateFolder(const std::string &path);
+ auto CreateSharedLink(const std::string &path);
+
+ void Upload(FileTransferParam *ftp) override;
+
+public:
+ CYandexService(const char *protoName, const wchar_t *userName);
+
+ static CYandexService* Init(const char *szModuleName, const wchar_t *szUserName);
+ static int UnInit(CYandexService*);
+
+ const char* GetModuleName() const override;
+
+ int GetIconId() const override;
+
+ bool IsLoggedIn() override;
+ void Login(HWND owner = nullptr) override;
+ void Logout() override;
+};
+
+#endif //_CLOUDFILE_YANDEX_H_ \ No newline at end of file