diff options
author | George Hazan <ghazan@miranda.im> | 2019-03-02 12:32:44 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2019-03-02 12:32:55 +0300 |
commit | 931a7dc1ac0dbc7e6c1083583ced915e572f5b47 (patch) | |
tree | 9fe9a6448d44030e26aa7107ce16044ed413e0d0 /protocols/CloudFile/src/Services | |
parent | dd7d9954042254e66e3bbbec7195c6be8b1a0663 (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.h | 221 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/dropbox_service.cpp | 302 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/dropbox_service.h | 36 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/google_api.h | 200 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/google_service.cpp | 297 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/google_service.h | 35 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/microsoft_api.h | 188 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/microsoft_service.cpp | 269 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/microsoft_service.h | 34 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/yandex_api.h | 131 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/yandex_service.cpp | 290 | ||||
-rw-r--r-- | protocols/CloudFile/src/Services/yandex_service.h | 35 |
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 |