summaryrefslogtreecommitdiff
path: root/protocols/CloudFile
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/CloudFile')
-rw-r--r--protocols/CloudFile/CloudFile.vcxproj40
-rw-r--r--protocols/CloudFile/CloudFile.vcxproj.filters32
-rw-r--r--protocols/CloudFile/res/dropbox.icobin0 -> 5430 bytes
-rw-r--r--protocols/CloudFile/res/gdrive.icobin0 -> 5430 bytes
-rw-r--r--protocols/CloudFile/res/onedrive.icobin0 -> 7886 bytes
-rw-r--r--protocols/CloudFile/res/resource.rc200
-rw-r--r--protocols/CloudFile/res/upload.icobin0 -> 103999 bytes
-rw-r--r--protocols/CloudFile/res/version.rc9
-rw-r--r--protocols/CloudFile/res/yadisk.icobin0 -> 5430 bytes
-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
-rw-r--r--protocols/CloudFile/src/cloud_file.cpp197
-rw-r--r--protocols/CloudFile/src/cloud_file.h61
-rw-r--r--protocols/CloudFile/src/events.cpp48
-rw-r--r--protocols/CloudFile/src/file_transfer.h255
-rw-r--r--protocols/CloudFile/src/http_request.h180
-rw-r--r--protocols/CloudFile/src/icons.cpp39
-rw-r--r--protocols/CloudFile/src/main.cpp42
-rw-r--r--protocols/CloudFile/src/menus.cpp52
-rw-r--r--protocols/CloudFile/src/oauth.cpp33
-rw-r--r--protocols/CloudFile/src/oauth.h23
-rw-r--r--protocols/CloudFile/src/options.cpp124
-rw-r--r--protocols/CloudFile/src/options.h43
-rw-r--r--protocols/CloudFile/src/resource.h38
-rw-r--r--protocols/CloudFile/src/services.cpp106
-rw-r--r--protocols/CloudFile/src/srmm.cpp67
-rw-r--r--protocols/CloudFile/src/stdafx.cxx20
-rw-r--r--protocols/CloudFile/src/stdafx.h126
-rw-r--r--protocols/CloudFile/src/transfers.cpp39
-rw-r--r--protocols/CloudFile/src/utils.cpp113
-rw-r--r--protocols/CloudFile/src/version.h13
41 files changed, 3938 insertions, 0 deletions
diff --git a/protocols/CloudFile/CloudFile.vcxproj b/protocols/CloudFile/CloudFile.vcxproj
new file mode 100644
index 0000000000..88e1946426
--- /dev/null
+++ b/protocols/CloudFile/CloudFile.vcxproj
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>CloudFile</ProjectName>
+ <ProjectGuid>{E876FE63-0701-4CDA-BED5-7C73A379C1D1}</ProjectGuid>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" />
+ </ImportGroup>
+ <ItemGroup>
+ <ClInclude Include="src\Services\*.h" />
+ <ClCompile Include="src\Services\*.cpp">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <None Include="res\*.ico" />
+ </ItemGroup>
+ <ItemDefinitionGroup>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+</Project>
diff --git a/protocols/CloudFile/CloudFile.vcxproj.filters b/protocols/CloudFile/CloudFile.vcxproj.filters
new file mode 100644
index 0000000000..c26cb717b8
--- /dev/null
+++ b/protocols/CloudFile/CloudFile.vcxproj.filters
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Header Files\Services">
+ <UniqueIdentifier>{64D2CE87-E04F-4768-BD94-86282BBD138B}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Services">
+ <UniqueIdentifier>{1D61FE06-83AA-4C8A-A8FC-36EAD0EAAC97}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="src\*.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\Services\*.h">
+ <Filter>Header Files\Services</Filter>
+ </ClInclude>
+ <ClCompile Include="src\*.cpp;src\*.cxx">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\Services\*.cpp">
+ <Filter>Source Files\Services</Filter>
+ </ClCompile>
+ <ResourceCompile Include="res\*.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ <None Include="res\*.ico;res\*.bmp;res\*.cur">
+ <Filter>Resource Files</Filter>
+ </None>
+ </ItemGroup>
+ <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" />
+</Project>
diff --git a/protocols/CloudFile/res/dropbox.ico b/protocols/CloudFile/res/dropbox.ico
new file mode 100644
index 0000000000..8a286d7f32
--- /dev/null
+++ b/protocols/CloudFile/res/dropbox.ico
Binary files differ
diff --git a/protocols/CloudFile/res/gdrive.ico b/protocols/CloudFile/res/gdrive.ico
new file mode 100644
index 0000000000..a34123e95b
--- /dev/null
+++ b/protocols/CloudFile/res/gdrive.ico
Binary files differ
diff --git a/protocols/CloudFile/res/onedrive.ico b/protocols/CloudFile/res/onedrive.ico
new file mode 100644
index 0000000000..502cee9bb7
--- /dev/null
+++ b/protocols/CloudFile/res/onedrive.ico
Binary files differ
diff --git a/protocols/CloudFile/res/resource.rc b/protocols/CloudFile/res/resource.rc
new file mode 100644
index 0000000000..54eb588ffa
--- /dev/null
+++ b/protocols/CloudFile/res/resource.rc
@@ -0,0 +1,200 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "..\src\resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// Russian (Russia) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
+LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
+#pragma code_page(1251)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "..\\src\\resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // Russian (Russia) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_UPLOAD ICON "upload.ico"
+
+IDI_DROPBOX ICON "dropbox.ico"
+
+IDI_GDRIVE ICON "gdrive.ico"
+
+IDI_ONEDRIVE ICON "onedrive.ico"
+
+IDI_YADISK ICON "yadisk.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_OPTIONS_MAIN DIALOGEX 0, 0, 307, 234
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Default service",IDC_STATIC,11,19,85,8
+ COMBOBOX IDC_DEFAULTSERVICE,102,17,194,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "General",IDC_STATIC,5,5,297,33
+ CONTROL "Autosend download link to contact",IDC_URL_AUTOSEND,
+ "Button",BS_AUTORADIOBUTTON | WS_GROUP,11,114,282,10
+ CONTROL "Paste download link into message input area",IDC_URL_COPYTOMIA,
+ "Button",BS_AUTORADIOBUTTON,11,127,282,10
+ CONTROL "Copy download link to clipboard",IDC_URL_COPYTOCB,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,140,282,10
+ GROUPBOX "Download link",IDC_STATIC,5,100,297,57
+ CONTROL "Do nothing",IDC_DONOTHINGONCONFLICT,"Button",BS_AUTORADIOBUTTON | WS_GROUP,11,54,285,10
+ CONTROL "Try to rename",IDC_RENAMEONCONFLICT,"Button",BS_AUTORADIOBUTTON,11,67,285,10
+ CONTROL "Try to replace",IDC_REPLACEONCONFLICT,"Button",BS_AUTORADIOBUTTON,11,80,285,10
+ GROUPBOX "On conflict when upload",IDC_STATIC,5,41,297,56
+END
+
+IDD_OAUTH DIALOGEX 0, 0, 193, 83
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_TOPMOST
+CAPTION "Authorization"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ EDITTEXT IDC_OAUTH_CODE,7,43,179,14,ES_AUTOHSCROLL
+ DEFPUSHBUTTON "OK",IDOK,75,62,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,136,62,50,14
+ LTEXT "Enter authorization code:",IDC_STATIC,7,33,179,8
+ LTEXT "To allow Miranda NG access to %s:",IDC_AUTH_TEXT,7,7,179,8
+ CONTROL "Go to this link",IDC_OAUTH_AUTHORIZE,"Hyperlink",WS_GROUP | WS_TABSTOP | 0x1,7,18,179,8
+END
+
+IDD_ACCMGR DIALOGEX 0, 0, 188, 126
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ PUSHBUTTON "Request access",IDC_REQUESTACCESS,5,19,83,14
+ PUSHBUTTON "Revoke access",IDC_REVOKEACCESS,97,19,83,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_OPTIONS_MAIN AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+IDD_OAUTH AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+IDD_ACCMGR AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_OPTIONS_MAIN, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 302
+ VERTGUIDE, 11
+ VERTGUIDE, 96
+ VERTGUIDE, 102
+ VERTGUIDE, 296
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 229
+ END
+
+ IDD_OAUTH, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 186
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 76
+ END
+
+ IDD_ACCMGR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 181
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 119
+ HORZGUIDE, 33
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/protocols/CloudFile/res/upload.ico b/protocols/CloudFile/res/upload.ico
new file mode 100644
index 0000000000..b51e87ed35
--- /dev/null
+++ b/protocols/CloudFile/res/upload.ico
Binary files differ
diff --git a/protocols/CloudFile/res/version.rc b/protocols/CloudFile/res/version.rc
new file mode 100644
index 0000000000..5a5ddd63ed
--- /dev/null
+++ b/protocols/CloudFile/res/version.rc
@@ -0,0 +1,9 @@
+// Microsoft Visual C++ generated resource script.
+//
+#ifdef APSTUDIO_INVOKED
+#error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+#include "..\src\version.h"
+
+#include "..\..\build\Version.rc"
diff --git a/protocols/CloudFile/res/yadisk.ico b/protocols/CloudFile/res/yadisk.ico
new file mode 100644
index 0000000000..b3a5ac69b4
--- /dev/null
+++ b/protocols/CloudFile/res/yadisk.ico
Binary files differ
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
diff --git a/protocols/CloudFile/src/cloud_file.cpp b/protocols/CloudFile/src/cloud_file.cpp
new file mode 100644
index 0000000000..f6019f8196
--- /dev/null
+++ b/protocols/CloudFile/src/cloud_file.cpp
@@ -0,0 +1,197 @@
+#include "stdafx.h"
+
+CCloudService::CCloudService(const char *protoName, const wchar_t *userName, HPLUGIN pPlugin)
+ : PROTO<CCloudService>(protoName, userName),
+ m_pPlugin(pPlugin)
+{
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = (char*)protoName;
+ nlu.szDescriptiveName.w = (wchar_t*)userName;
+ m_hConnection = Netlib_RegisterUser(&nlu);
+
+ CreateProtoService(PS_CREATEACCMGRUI, &CCloudService::OnAccountManagerInit);
+}
+
+CCloudService::~CCloudService()
+{
+ Netlib_CloseHandle(m_hConnection);
+ m_hConnection = nullptr;
+}
+
+void CCloudService::OnErase()
+{
+ KillModuleMenus(m_pPlugin);
+}
+
+HPLUGIN CCloudService::GetId() const
+{
+ return m_pPlugin;
+}
+
+const char* CCloudService::GetAccountName() const
+{
+ return m_szModuleName;
+}
+
+const wchar_t* CCloudService::GetUserName() const
+{
+ return m_tszUserName;
+}
+
+INT_PTR CCloudService::GetCaps(int type, MCONTACT)
+{
+ switch (type) {
+ case PFLAGNUM_1:
+ return PF1_FILESEND;
+ case PFLAGNUM_2:
+ case PFLAGNUM_5:
+ return PF2_NONE;
+ default:
+ return 0;
+ }
+}
+
+int CCloudService::FileCancel(MCONTACT, HANDLE hTransfer)
+{
+ FileTransferParam *ftp = Transfers.find((FileTransferParam*)&hTransfer);
+ if (ftp)
+ ftp->Terminate();
+
+ return 0;
+}
+
+HANDLE CCloudService::SendFile(MCONTACT hContact, const wchar_t *description, wchar_t **paths)
+{
+ FileTransferParam *ftp = new FileTransferParam(hContact);
+ ftp->SetDescription(description);
+ ftp->SetWorkingDirectory(paths[0]);
+ for (int i = 0; paths[i]; i++) {
+ if (PathIsDirectory(paths[i]))
+ continue;
+ ftp->AddFile(paths[i]);
+ }
+ Transfers.insert(ftp);
+ mir_forkthreadowner(UploadAndReportProgressThread, this, ftp);
+ return (HANDLE)ftp->GetId();
+}
+
+void CCloudService::OpenUploadDialog(MCONTACT hContact)
+{
+ char *proto = GetContactProto(hContact);
+ if (!mir_strcmpi(proto, META_PROTO))
+ hContact = db_mc_getMostOnline(hContact);
+
+ auto it = InterceptedContacts.find(hContact);
+ if (it == InterceptedContacts.end()) {
+ HWND hwnd = (HWND)CallService(MS_FILE_SENDFILE, hContact, 0);
+ InterceptedContacts[hContact] = hwnd;
+ }
+ else
+ SetActiveWindow(it->second);
+}
+
+INT_PTR CCloudService::OnAccountManagerInit(WPARAM, LPARAM lParam)
+{
+ CAccountManagerDlg *page = new CAccountManagerDlg(this);
+ page->SetParent((HWND)lParam);
+ page->Show();
+ return (INT_PTR)page->GetHwnd();
+}
+
+std::string CCloudService::PreparePath(const std::string &path) const
+{
+ std::string newPath = path;
+ if (newPath[0] != '/')
+ newPath.insert(0, "/");
+ std::replace(newPath.begin(), newPath.end(), '\\', '/');
+ size_t pos = newPath.find("//");
+ while (pos != std::string::npos) {
+ newPath.replace(pos, 2, "/");
+ pos = newPath.find("//", pos + 1);
+ }
+ return newPath;
+}
+
+char* CCloudService::HttpStatusToError(int status)
+{
+ switch (status) {
+ case HTTP_CODE_OK:
+ return "OK";
+ case HTTP_CODE_BAD_REQUEST:
+ return "Bad input parameter. Error message should indicate which one and why";
+ case HTTP_CODE_UNAUTHORIZED:
+ return "Bad or expired token. This can happen if the user or Dropbox revoked or expired an access token. To fix, you should re-authenticate the user";
+ case HTTP_CODE_FORBIDDEN:
+ return "Bad OAuth request (wrong consumer key, bad nonce, expired timestamp...). Unfortunately, re-authenticating the user won't help here";
+ case HTTP_CODE_NOT_FOUND:
+ return "File or folder not found at the specified path";
+ case HTTP_CODE_METHOD_NOT_ALLOWED:
+ return "Request method not expected (generally should be GET or POST)";
+ case HTTP_CODE_TOO_MANY_REQUESTS:
+ return "Your app is making too many requests and is being rate limited. 429s can trigger on a per-app or per-user basis";
+ case HTTP_CODE_SERVICE_UNAVAILABLE:
+ return "If the response includes the Retry-After header, this means your OAuth 1.0 app is being rate limited. Otherwise, this indicates a transient server error, and your app should retry its request.";
+ }
+
+ return "Unknown error";
+}
+
+void CCloudService::HttpResponseToError(NETLIBHTTPREQUEST *response)
+{
+ if (response == nullptr)
+ throw Exception(HttpStatusToError());
+ if (response->dataLength)
+ throw Exception(response->pData);
+ throw Exception(HttpStatusToError(response->resultCode));
+}
+
+void CCloudService::HandleHttpError(NETLIBHTTPREQUEST *response)
+{
+ if (response == nullptr)
+ throw Exception(HttpStatusToError());
+
+ if (HTTP_CODE_SUCCESS(response->resultCode))
+ return;
+
+ if (response->resultCode == HTTP_CODE_UNAUTHORIZED)
+ delSetting("TokenSecret");
+
+ HttpResponseToError(response);
+}
+
+JSONNode CCloudService::GetJsonResponse(NETLIBHTTPREQUEST *response)
+{
+ HandleHttpError(response);
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.isnull())
+ throw Exception(HttpStatusToError());
+
+ HandleJsonError(root);
+
+ return root;
+}
+
+UINT CCloudService::Upload(CCloudService *service, FileTransferParam *ftp)
+{
+ try {
+ if (!service->IsLoggedIn())
+ service->Login();
+
+ if (!service->IsLoggedIn()) {
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ service->Upload(ftp);
+ }
+ catch (Exception &ex) {
+ service->debugLogA("%s: %s", service->GetModuleName(), ex.what());
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ ftp->SetStatus(ACKRESULT_SUCCESS);
+ return ACKRESULT_SUCCESS;
+} \ No newline at end of file
diff --git a/protocols/CloudFile/src/cloud_file.h b/protocols/CloudFile/src/cloud_file.h
new file mode 100644
index 0000000000..1e70671054
--- /dev/null
+++ b/protocols/CloudFile/src/cloud_file.h
@@ -0,0 +1,61 @@
+#ifndef _CLOUD_SERVICE_H_
+#define _CLOUD_SERVICE_H_
+
+enum OnConflict
+{
+ NONE,
+ RENAME,
+ REPLACE,
+};
+
+class CCloudService : public PROTO<CCloudService>
+{
+protected:
+ HPLUGIN m_pPlugin;
+ HNETLIBUSER m_hConnection;
+
+ INT_PTR __cdecl OnAccountManagerInit(WPARAM, LPARAM);
+
+ // utils
+ std::string PreparePath(const std::string &path) const;
+
+ virtual char* HttpStatusToError(int status = 0);
+ virtual void HttpResponseToError(NETLIBHTTPREQUEST *response);
+ virtual void HandleHttpError(NETLIBHTTPREQUEST *response);
+ virtual void HandleJsonError(JSONNode &node) = 0;
+
+ void OnErase() override;
+ void OnModulesLoaded() override;
+
+ JSONNode GetJsonResponse(NETLIBHTTPREQUEST *response);
+
+ virtual void Upload(FileTransferParam *ftp) = 0;
+
+public:
+ std::map<MCONTACT, HWND> InterceptedContacts;
+
+ CCloudService(const char *protoName, const wchar_t *userName, HPLUGIN);
+ virtual ~CCloudService();
+
+ INT_PTR GetCaps(int type, MCONTACT) override;
+
+ int FileCancel(MCONTACT hContact, HANDLE hTransfer) override;
+ HANDLE SendFile(MCONTACT hContact, const wchar_t *msg, wchar_t **ppszFiles) override;
+
+ HPLUGIN GetId() const;
+ virtual const char* GetModuleName() const = 0;
+ const char* GetAccountName() const;
+ const wchar_t* GetUserName() const;
+
+ virtual int GetIconId() const = 0;
+
+ virtual bool IsLoggedIn() = 0;
+ virtual void Login(HWND owner = nullptr) = 0;
+ virtual void Logout() = 0;
+
+ void OpenUploadDialog(MCONTACT hContact);
+
+ static UINT Upload(CCloudService *service, FileTransferParam *ftp);
+};
+
+#endif //_CLOUD_SERVICE_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/events.cpp b/protocols/CloudFile/src/events.cpp
new file mode 100644
index 0000000000..010729dddb
--- /dev/null
+++ b/protocols/CloudFile/src/events.cpp
@@ -0,0 +1,48 @@
+#include "stdafx.h"
+
+static int OnProtoAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+
+ if (ack->type != ACKTYPE_STATUS)
+ return 0;
+
+ for (auto &hContact : Contacts(ack->szModule)) {
+ MessageWindowData msgw;
+ if (Srmm_GetWindowData(hContact, msgw) || !(msgw.uState & MSG_WINDOW_STATE_EXISTS))
+ continue;
+
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULENAME;
+ bbd.dwButtonID = BBB_ID_FILE_SEND;
+ bbd.bbbFlags = CanSendToContact(hContact)
+ ? BBSF_RELEASED
+ : BBSF_DISABLED;
+ Srmm_SetButtonState(hContact, &bbd);
+ }
+
+ return 0;
+}
+
+static int OnFileDialogCanceled(WPARAM hContact, LPARAM)
+{
+ for (auto &service : Services) {
+ auto it = service->InterceptedContacts.find(hContact);
+ if (it != service->InterceptedContacts.end())
+ service->InterceptedContacts.erase(it);
+ }
+ return 0;
+}
+
+int OnModulesLoaded(WPARAM, LPARAM)
+{
+ InitializeMenus();
+
+ HookEvent(ME_PROTO_ACK, OnProtoAck);
+
+ // srfile
+ HookEvent(ME_FILEDLG_CANCELED, OnFileDialogCanceled);
+
+ HookTemporaryEvent(ME_MSG_TOOLBARLOADED, OnSrmmToolbarLoaded);
+ return 0;
+} \ No newline at end of file
diff --git a/protocols/CloudFile/src/file_transfer.h b/protocols/CloudFile/src/file_transfer.h
new file mode 100644
index 0000000000..692e717f2e
--- /dev/null
+++ b/protocols/CloudFile/src/file_transfer.h
@@ -0,0 +1,255 @@
+#ifndef _FILE_TRANSFER_H_
+#define _FILE_TRANSFER_H_
+
+class FileTransferParam
+{
+private:
+ static ULONG hFileProcess;
+
+ ULONG id;
+ FILE *hFile;
+ PROTOFILETRANSFERSTATUS pfts;
+
+ bool isTerminated;
+
+ CMStringW m_serverDirectory;
+ int m_relativePathStart;
+
+ LIST<char> m_links;
+ CMStringW m_description;
+
+public:
+ FileTransferParam(MCONTACT hContact)
+ : m_links(1)
+ {
+ hFile = NULL;
+ id = InterlockedIncrement(&hFileProcess);
+
+ isTerminated = false;
+
+ m_relativePathStart = 0;
+
+ pfts.flags = PFTS_UNICODE | PFTS_SENDING;
+ pfts.hContact = hContact;
+ pfts.currentFileNumber = -1;
+ pfts.currentFileProgress = 0;
+ pfts.currentFileSize = 0;
+ pfts.currentFileTime = 0;
+ pfts.totalBytes = 0;
+ pfts.totalFiles = 0;
+ pfts.totalProgress = 0;
+ pfts.pszFiles.w = (wchar_t**)mir_alloc(sizeof(wchar_t*) * (pfts.totalFiles + 1));
+ pfts.pszFiles.w[pfts.totalFiles] = NULL;
+ pfts.szWorkingDir.w = NULL;
+ pfts.szCurrentFile.w = NULL;
+
+ ProtoBroadcastAck(MODULENAME, pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, (HANDLE)id, 0);
+ }
+
+ ~FileTransferParam()
+ {
+ CloseCurrentFile();
+
+ if (pfts.szWorkingDir.w)
+ mir_free(pfts.szWorkingDir.w);
+
+ if (pfts.pszFiles.a) {
+ for (int i = 0; pfts.pszFiles.a[i]; i++)
+ mir_free(pfts.pszFiles.a[i]);
+ mir_free(pfts.pszFiles.a);
+ }
+
+ for (auto &link : m_links)
+ mir_free(link);
+ m_links.destroy();
+ }
+
+ ULONG GetId() const
+ {
+ return id;
+ }
+
+ MCONTACT GetContact() const
+ {
+ return pfts.hContact;
+ }
+
+ const wchar_t* GetDescription() const
+ {
+ return m_description.GetString();
+ }
+
+ const char** GetSharedLinks(size_t &count) const
+ {
+ count = m_links.getCount();
+ return (const char**)m_links.getArray();
+ }
+
+ void Terminate()
+ {
+ isTerminated = true;
+ }
+
+ void SetDescription(const wchar_t *description)
+ {
+ m_description = description;
+ }
+
+ void SetWorkingDirectory(const wchar_t *path)
+ {
+ m_relativePathStart = wcsrchr(path, '\\') - path + 1;
+ pfts.szWorkingDir.w = (wchar_t*)mir_calloc(sizeof(wchar_t) * m_relativePathStart);
+ mir_wstrncpy(pfts.szWorkingDir.w, path, m_relativePathStart);
+ if (PathIsDirectory(path))
+ m_serverDirectory = wcsrchr(path, '\\') + 1;
+ }
+
+ void SetServerDirectory(const wchar_t *name)
+ {
+ if (name)
+ m_serverDirectory = name;
+ }
+
+ const wchar_t* GetServerDirectory() const
+ {
+ if (m_serverDirectory.IsEmpty())
+ return nullptr;
+ return m_serverDirectory.GetString();
+ }
+
+ void AddFile(const wchar_t *path)
+ {
+ pfts.pszFiles.w = (wchar_t**)mir_realloc(pfts.pszFiles.w, sizeof(wchar_t*) * (pfts.totalFiles + 2));
+ pfts.pszFiles.w[pfts.totalFiles++] = mir_wstrdup(path);
+ pfts.pszFiles.w[pfts.totalFiles] = NULL;
+
+ FILE *file = _wfopen(path, L"rb");
+ if (file != NULL) {
+ _fseeki64(file, 0, SEEK_END);
+ pfts.totalBytes += _ftelli64(file);
+ fclose(file);
+ }
+ }
+
+ void AddSharedLink(const char *url)
+ {
+ m_links.insert(mir_strdup(url));
+ }
+
+ const bool IsCurrentFileInSubDirectory() const
+ {
+ const wchar_t *backslash = wcschr(GetCurrentRelativeFilePath(), L'\\');
+ return backslash != nullptr;
+ }
+
+ const wchar_t* GetCurrentFilePath() const
+ {
+ return pfts.pszFiles.w[pfts.currentFileNumber];
+ }
+
+ const wchar_t* GetCurrentRelativeFilePath() const
+ {
+ return &GetCurrentFilePath()[m_relativePathStart];
+ }
+
+ const wchar_t* GetCurrentFileName() const
+ {
+ return wcsrchr(GetCurrentFilePath(), '\\') + 1;
+ }
+
+ void OpenCurrentFile()
+ {
+ hFile = _wfopen(GetCurrentFilePath(), L"rb");
+ if (!hFile)
+ throw Exception("Unable to open file");
+ _fseeki64(hFile, 0, SEEK_END);
+ pfts.currentFileSize = _ftelli64(hFile);
+ rewind(hFile);
+ }
+
+ size_t ReadCurrentFile(void *buffer, size_t count)
+ {
+ return fread(buffer, sizeof(char), count, hFile);
+ }
+
+ void CheckCurrentFile()
+ {
+ if (ferror(hFile))
+ throw Exception("Error while file sending");
+
+ if (isTerminated)
+ throw Exception("Transfer was terminated");
+ }
+
+ void CloseCurrentFile()
+ {
+ if (hFile != NULL)
+ {
+ fclose(hFile);
+ hFile = NULL;
+ }
+ }
+
+ const uint64_t GetCurrentFileSize() const
+ {
+ return pfts.currentFileSize;
+ }
+
+ const size_t GetCurrentFileChunkSize() const
+ {
+ size_t chunkSize = 1024 * 1024;
+ if (pfts.currentFileSize < chunkSize)
+ chunkSize = min(pfts.currentFileSize, chunkSize / 4);
+ else if (pfts.currentFileSize > 20 * chunkSize)
+ chunkSize = chunkSize * 4;
+ return chunkSize;
+ }
+
+ void Progress(size_t count)
+ {
+ pfts.currentFileProgress += count;
+ pfts.totalProgress += count;
+ if (pfts.hContact)
+ ProtoBroadcastAck(MODULENAME, pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&pfts);
+ }
+
+ void FirstFile()
+ {
+ CloseCurrentFile();
+
+ pfts.currentFileNumber = 0;
+ pfts.currentFileProgress = 0;
+ pfts.szCurrentFile.w = wcsrchr(pfts.pszFiles.w[pfts.currentFileNumber], '\\') + 1;
+ if (pfts.hContact)
+ ProtoBroadcastAck(MODULENAME, pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&pfts);
+
+ OpenCurrentFile();
+ CheckCurrentFile();
+ }
+
+ bool NextFile()
+ {
+ CloseCurrentFile();
+
+ if (++pfts.currentFileNumber == pfts.totalFiles)
+ return false;
+
+ pfts.currentFileProgress = 0;
+ pfts.szCurrentFile.w = wcsrchr(pfts.pszFiles.w[pfts.currentFileNumber], '\\') + 1;
+ if (pfts.hContact)
+ ProtoBroadcastAck(MODULENAME, pfts.hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, (HANDLE)id, 0);
+
+ OpenCurrentFile();
+ CheckCurrentFile();
+
+ return true;
+ }
+
+ void SetStatus(int status, LPARAM param = 0)
+ {
+ if (pfts.hContact)
+ ProtoBroadcastAck(MODULENAME, pfts.hContact, ACKTYPE_FILE, status, (HANDLE)id, param);
+ }
+};
+
+#endif //_FILE_TRANSFER_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/http_request.h b/protocols/CloudFile/src/http_request.h
new file mode 100644
index 0000000000..c74b2ae40c
--- /dev/null
+++ b/protocols/CloudFile/src/http_request.h
@@ -0,0 +1,180 @@
+#ifndef _HTTP_REQUEST_H_
+#define _HTTP_REQUEST_H_
+
+class HttpRequestException
+{
+ CMStringA message;
+
+public:
+ HttpRequestException(const char *message) :
+ message(message)
+ {
+ }
+
+ const char* what() const throw()
+ {
+ return message.c_str();
+ }
+};
+
+class HttpRequest : protected NETLIBHTTPREQUEST
+{
+private:
+ CMStringA m_szUrl;
+
+ void Init(int type)
+ {
+ cbSize = sizeof(NETLIBHTTPREQUEST);
+ requestType = type;
+ flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_NODUMP;
+ szUrl = NULL;
+ headers = NULL;
+ headersCount = 0;
+ pData = NULL;
+ dataLength = 0;
+ resultCode = 0;
+ szResultDescr = NULL;
+ nlc = NULL;
+ timeout = 0;
+ }
+
+protected:
+ enum HttpRequestUrlFormat { FORMAT };
+
+ void AddHeader(LPCSTR szName, LPCSTR szValue)
+ {
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER) * (headersCount + 1));
+ headers[headersCount].szName = mir_strdup(szName);
+ headers[headersCount].szValue = mir_strdup(szValue);
+ headersCount++;
+ }
+
+ void AddBasicAuthHeader(LPCSTR szLogin, LPCSTR szPassword)
+ {
+ size_t length = mir_strlen(szLogin) + mir_strlen(szPassword) + 1;
+ ptrA cPair((char*)mir_calloc(length + 1));
+ mir_snprintf(
+ cPair,
+ length,
+ "%s:%s",
+ szLogin,
+ szPassword);
+
+ ptrA ePair(mir_base64_encode(cPair, length));
+
+ length = mir_strlen(ePair) + 7;
+ char *value = (char*)mir_calloc(length + 1);
+ mir_snprintf(
+ value,
+ length,
+ "Basic %s",
+ ePair);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = value;
+ headersCount++;
+ }
+
+ void AddBearerAuthHeader(LPCSTR szValue)
+ {
+ size_t length = mir_strlen(szValue) + 8;
+ char *value = (char*)mir_calloc(length + 1);
+ mir_snprintf(
+ value,
+ length,
+ "Bearer %s",
+ szValue);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = value;
+ headersCount++;
+ }
+
+ void AddOAuthHeader(LPCSTR szValue)
+ {
+ size_t length = mir_strlen(szValue) + 7;
+ char *value = (char*)mir_calloc(length + 1);
+ mir_snprintf(
+ value,
+ length,
+ "OAuth %s",
+ szValue);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = value;
+ headersCount++;
+ }
+
+ void AddUrlParameter(const char *urlFormat, ...)
+ {
+ va_list urlArgs;
+ va_start(urlArgs, urlFormat);
+ m_szUrl += m_szUrl.Find('?') == -1 ? '?' : '&';
+ m_szUrl.AppendFormatV(urlFormat, urlArgs);
+ va_end(urlArgs);
+ }
+
+ void AddUrlParameterWithEncode(const char *name, const char *valueFormat, ...)
+ {
+ va_list valueArgs;
+ va_start(valueArgs, valueFormat);
+ m_szUrl += m_szUrl.Find('?') == -1 ? '?' : '&';
+ m_szUrl.AppendFormat("%s=", name);
+ CMStringA value;
+ value.AppendFormatV(valueFormat, valueArgs);
+ m_szUrl += mir_urlEncode(value);
+ va_end(valueArgs);
+ }
+
+ void SetData(const char *data, size_t size)
+ {
+ if (pData != NULL)
+ mir_free(pData);
+
+ dataLength = (int)size;
+ pData = (char*)mir_alloc(size);
+ memcpy(pData, data, size);
+ }
+
+public:
+ HttpRequest(int type, LPCSTR url)
+ {
+ Init(type);
+
+ m_szUrl = url;
+ }
+
+ HttpRequest(int type, HttpRequestUrlFormat, LPCSTR urlFormat, ...)
+ {
+ Init(type);
+
+ va_list formatArgs;
+ va_start(formatArgs, urlFormat);
+ m_szUrl.AppendFormatV(urlFormat, formatArgs);
+ va_end(formatArgs);
+ }
+
+ ~HttpRequest()
+ {
+ for (int i = 0; i < headersCount; i++)
+ {
+ mir_free(headers[i].szName);
+ mir_free(headers[i].szValue);
+ }
+ mir_free(headers);
+ if (pData)
+ mir_free(pData);
+ }
+
+ NETLIBHTTPREQUEST* Send(HNETLIBUSER hConnection)
+ {
+ m_szUrl.Replace('\\', '/');
+ szUrl = m_szUrl.GetBuffer();
+ return Netlib_HttpTransaction(hConnection, this);
+ }
+};
+
+#endif //_HTTP_REQUEST_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/icons.cpp b/protocols/CloudFile/src/icons.cpp
new file mode 100644
index 0000000000..001df50240
--- /dev/null
+++ b/protocols/CloudFile/src/icons.cpp
@@ -0,0 +1,39 @@
+#include "stdafx.h"
+
+static IconItem iconList[] =
+{
+ { LPGEN("Upload file(s)"), "upload", IDI_UPLOAD },
+ { LPGEN("Dropbox"), "dropbox", IDI_DROPBOX },
+ { LPGEN("Google Drive"), "gdrive", IDI_GDRIVE },
+ { LPGEN("OneDrive"), "onedrive", IDI_ONEDRIVE },
+ { LPGEN("Yandex.Disk"), "yadisk", IDI_YADISK }
+};
+
+void InitializeIcons()
+{
+ g_plugin.registerIcon("Protocols/" MODULENAME, iconList, MODULENAME);
+}
+
+HANDLE GetIconHandle(int iconId)
+{
+ for (auto &it : iconList)
+ if (it.defIconID == iconId)
+ return it.hIcolib;
+ return nullptr;
+}
+
+HANDLE GetIconHandle(const char *name)
+{
+ for (auto &it : iconList)
+ if (mir_strcmpi(it.szName, name) == 0)
+ return it.hIcolib;
+ return nullptr;
+}
+
+HICON LoadIconEx(int iconId, bool big)
+{
+ for (auto &it : iconList)
+ if (it.defIconID == iconId)
+ return IcoLib_GetIconByHandle(it.hIcolib, big);
+ return nullptr;
+} \ No newline at end of file
diff --git a/protocols/CloudFile/src/main.cpp b/protocols/CloudFile/src/main.cpp
new file mode 100644
index 0000000000..2b9ffd6c2d
--- /dev/null
+++ b/protocols/CloudFile/src/main.cpp
@@ -0,0 +1,42 @@
+#include "stdafx.h"
+
+CMPlugin g_plugin;
+
+PLUGININFOEX pluginInfoEx =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {E876FE63-0701-4CDA-BED5-7C73A379C1D1}
+ { 0xe876fe63, 0x701, 0x4cda, { 0xbe, 0xd5, 0x7c, 0x73, 0xa3, 0x79, 0xc1, 0xd1 }}
+};
+
+CMPlugin::CMPlugin() :
+ PLUGIN<CMPlugin>(MODULENAME, pluginInfoEx)
+{}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Interface information
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Load()
+{
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, OnPrebuildContactMenu);
+ HookEvent(ME_MSG_WINDOWEVENT, OnSrmmWindowOpened);
+ HookEvent(ME_MSG_BUTTONPRESSED, OnSrmmButtonPressed);
+ HookEvent(ME_OPT_INITIALISE, OnOptionsInitialized);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+
+ InitializeIcons();
+ InitializeServices();
+
+ return 0;
+}
diff --git a/protocols/CloudFile/src/menus.cpp b/protocols/CloudFile/src/menus.cpp
new file mode 100644
index 0000000000..3ca1b085b9
--- /dev/null
+++ b/protocols/CloudFile/src/menus.cpp
@@ -0,0 +1,52 @@
+#include "stdafx.h"
+
+HGENMENU hContactMenu;
+
+static INT_PTR UploadMenuCommand(void *obj, WPARAM hContact, LPARAM)
+{
+ CCloudService *service = (CCloudService*)obj;
+ service->OpenUploadDialog(hContact);
+ return 0;
+}
+
+void InitializeMenus()
+{
+ CMenuItem mi(&g_plugin);
+ SET_UID(mi, 0x93d4495b, 0x259b, 0x4fba, 0xbc, 0x14, 0xf9, 0x46, 0x2c, 0xda, 0xfc, 0x6d);
+ mi.name.a = LPGEN("Upload to...");
+
+ ptrA defaultService(g_plugin.getStringA("DefaultService"));
+ if (defaultService) {
+ CCloudService *service = FindService(defaultService);
+ if (service) {
+ mi.name.a = LPGEN("Upload");
+ mi.pszService = MODULENAME "/Default/Upload";
+ CreateServiceFunctionObj(mi.pszService, UploadMenuCommand, service);
+ }
+ }
+
+ mi.position = -2000019999;
+ mi.hIcon = LoadIconEx(IDI_UPLOAD);
+ hContactMenu = Menu_AddContactMenuItem(&mi);
+}
+
+void CCloudService::OnModulesLoaded()
+{
+ CMenuItem mi(GetId());
+ mi.root = hContactMenu;
+ CMStringA serviceName(FORMAT, "/%s/Upload", GetAccountName());
+ mi.pszService = serviceName.GetBuffer();
+ mi.flags = CMIF_SYSTEM | CMIF_UNICODE;
+ mi.name.w = (wchar_t*)GetUserName();
+ mi.position = Services.getCount();
+ mi.hIcolibItem = GetIconHandle(GetIconId());
+ Menu_AddContactMenuItem(&mi);
+
+ CreateServiceFunctionObj(mi.pszService, UploadMenuCommand, this);
+}
+
+int OnPrebuildContactMenu(WPARAM hContact, LPARAM)
+{
+ Menu_ShowItem(hContactMenu, CanSendToContact(hContact));
+ return 0;
+}
diff --git a/protocols/CloudFile/src/oauth.cpp b/protocols/CloudFile/src/oauth.cpp
new file mode 100644
index 0000000000..e62629fcd8
--- /dev/null
+++ b/protocols/CloudFile/src/oauth.cpp
@@ -0,0 +1,33 @@
+#include "stdafx.h"
+
+COAuthDlg::COAuthDlg(CCloudService *service, const char *authUrl, CCloudService::MyThreadFunc requestAccessTokenThread)
+ : CDlgBase(g_plugin, IDD_OAUTH), m_service(service),
+ m_requestAccessTokenThread(requestAccessTokenThread),
+ m_authorize(this, IDC_OAUTH_AUTHORIZE, authUrl),
+ m_code(this, IDC_OAUTH_CODE), m_ok(this, IDOK)
+{
+ m_autoClose = CLOSE_ON_CANCEL;
+ m_code.OnChange = Callback(this, &COAuthDlg::Code_OnChange);
+ m_ok.OnClick = Callback(this, &COAuthDlg::Ok_OnClick);
+}
+
+bool COAuthDlg::OnInitDialog()
+{
+ CCtrlLabel &ctrl = *(CCtrlLabel*)FindControl(IDC_AUTH_TEXT);
+ ptrW format(ctrl.GetText());
+ wchar_t text[MAX_PATH];
+ mir_snwprintf(text, (const wchar_t*)format, m_service->GetUserName());
+ ctrl.SetText(text);
+ return true;
+}
+
+void COAuthDlg::Code_OnChange(CCtrlBase*)
+{
+ ptrA requestToken(m_code.GetTextA());
+ m_ok.Enable(mir_strlen(requestToken) != 0);
+}
+
+void COAuthDlg::Ok_OnClick(CCtrlButton*)
+{
+ m_service->ForkThread(m_requestAccessTokenThread, m_hwnd);
+} \ No newline at end of file
diff --git a/protocols/CloudFile/src/oauth.h b/protocols/CloudFile/src/oauth.h
new file mode 100644
index 0000000000..2b32ecbd9b
--- /dev/null
+++ b/protocols/CloudFile/src/oauth.h
@@ -0,0 +1,23 @@
+#ifndef _OAUTH_H_
+#define _OAUTH_H_
+
+class COAuthDlg : public CDlgBase
+{
+ CCloudService *m_service;
+ CCloudService::MyThreadFunc m_requestAccessTokenThread;
+
+ CCtrlHyperlink m_authorize;
+ CCtrlEdit m_code;
+ CCtrlButton m_ok;
+
+protected:
+ bool OnInitDialog() override;
+
+ void Code_OnChange(CCtrlBase*);
+ void Ok_OnClick(CCtrlButton*);
+
+public:
+ COAuthDlg(CCloudService *service, const char *authUrl, CCloudService::MyThreadFunc requestAccessTokenThread);
+};
+
+#endif //_OAUTH_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/options.cpp b/protocols/CloudFile/src/options.cpp
new file mode 100644
index 0000000000..0ac7c120a0
--- /dev/null
+++ b/protocols/CloudFile/src/options.cpp
@@ -0,0 +1,124 @@
+#include "stdafx.h"
+
+COptionsMainDlg::COptionsMainDlg()
+ : CDlgBase(g_plugin, IDD_OPTIONS_MAIN),
+ m_defaultService(this, IDC_DEFAULTSERVICE),
+ m_doNothingOnConflict(this, IDC_DONOTHINGONCONFLICT),
+ m_renameOnConflict(this, IDC_RENAMEONCONFLICT),
+ m_repalceOnConflict(this, IDC_REPLACEONCONFLICT),
+ m_urlAutoSend(this, IDC_URL_AUTOSEND),
+ m_urlPasteToMessageInputArea(this, IDC_URL_COPYTOMIA),
+ m_urlCopyToClipboard(this, IDC_URL_COPYTOCB)
+{
+ CreateLink(m_defaultService, "DefaultService", L"");
+
+ CreateLink(m_urlAutoSend, "UrlAutoSend", DBVT_BYTE, 1);
+ CreateLink(m_urlPasteToMessageInputArea, "UrlPasteToMessageInputArea", DBVT_BYTE, 0);
+ CreateLink(m_urlCopyToClipboard, "UrlCopyToClipboard", DBVT_BYTE, 0);
+}
+
+bool COptionsMainDlg::OnInitDialog()
+{
+ CDlgBase::OnInitDialog();
+
+ ptrA defaultService(g_plugin.getStringA("DefaultService"));
+ int iItem = m_defaultService.AddString(TranslateT("None"));
+ m_defaultService.SetCurSel(iItem);
+
+ for (auto &service : Services) {
+ iItem = m_defaultService.AddString(mir_wstrdup(service->GetUserName()), (LPARAM)service);
+ if (!mir_strcmpi(service->GetAccountName(), defaultService))
+ m_defaultService.SetCurSel(iItem);
+ }
+
+ BYTE strategy = g_plugin.getByte("ConflictStrategy", OnConflict::REPLACE);
+ switch (strategy)
+ {
+ case OnConflict::RENAME:
+ m_renameOnConflict.SetState(TRUE);
+ m_repalceOnConflict.SetState(FALSE);
+ m_doNothingOnConflict.SetState(FALSE);
+ break;
+ case OnConflict::REPLACE:
+ m_renameOnConflict.SetState(FALSE);
+ m_repalceOnConflict.SetState(TRUE);
+ m_doNothingOnConflict.SetState(FALSE);
+ break;
+ default:
+ m_renameOnConflict.SetState(FALSE);
+ m_repalceOnConflict.SetState(FALSE);
+ m_doNothingOnConflict.SetState(TRUE);
+ break;
+ }
+ return true;
+}
+
+bool COptionsMainDlg::OnApply()
+{
+ int iItem = m_defaultService.GetCurSel();
+ CCloudService *service = (CCloudService*)m_defaultService.GetItemData(iItem);
+ if (service)
+ g_plugin.setString("DefaultService", service->GetAccountName());
+ else
+ g_plugin.delSetting("DefaultService");
+
+ if (m_renameOnConflict.GetState())
+ g_plugin.setByte("ConflictStrategy", OnConflict::RENAME);
+ else if (m_repalceOnConflict.GetState())
+ g_plugin.setByte("ConflictStrategy", OnConflict::REPLACE);
+ else
+ g_plugin.delSetting("ConflictStrategy");
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+int OnOptionsInitialized(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.szTitle.w = _A2W(MODULENAME);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_UNICODE | ODPF_DONTTRANSLATE;
+ odp.szGroup.w = LPGENW("Services");
+
+ //odp.szTab.w = LPGENW("General");
+ odp.pDialog = new COptionsMainDlg();
+ g_plugin.addOptions(wParam, &odp);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+CAccountManagerDlg::CAccountManagerDlg(CCloudService *service)
+ : CProtoDlgBase(service, IDD_ACCMGR),
+ m_requestAccess(this, IDC_REQUESTACCESS),
+ m_revokeAccess(this, IDC_REVOKEACCESS)
+{
+ m_requestAccess.OnClick = Callback(this, &CAccountManagerDlg::RequestAccess_OnClick);
+ m_revokeAccess.OnClick = Callback(this, &CAccountManagerDlg::RevokeAccess_OnClick);
+}
+
+bool CAccountManagerDlg::OnInitDialog()
+{
+ ptrA token(m_proto->getStringA("TokenSecret"));
+ m_requestAccess.Enable(!token);
+ m_revokeAccess.Enable(token);
+ return true;
+}
+
+void CAccountManagerDlg::RequestAccess_OnClick(CCtrlButton*)
+{
+ m_proto->Login(m_hwnd);
+ ptrA token(m_proto->getStringA("TokenSecret"));
+ m_requestAccess.Enable(!token);
+ m_revokeAccess.Enable(token);
+}
+
+void CAccountManagerDlg::RevokeAccess_OnClick(CCtrlButton*)
+{
+ m_proto->Logout();
+ m_requestAccess.Enable();
+ m_revokeAccess.Disable();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
diff --git a/protocols/CloudFile/src/options.h b/protocols/CloudFile/src/options.h
new file mode 100644
index 0000000000..aaf1440def
--- /dev/null
+++ b/protocols/CloudFile/src/options.h
@@ -0,0 +1,43 @@
+#ifndef _OPTIONS_H_
+#define _OPTIONS_H_
+
+class COptionsMainDlg : public CDlgBase
+{
+private:
+ CCtrlCombo m_defaultService;
+
+ CCtrlCheck m_doNothingOnConflict;
+ CCtrlCheck m_renameOnConflict;
+ CCtrlCheck m_repalceOnConflict;
+
+ CCtrlCheck m_urlAutoSend;
+ CCtrlCheck m_urlPasteToMessageInputArea;
+ CCtrlCheck m_urlCopyToClipboard;
+
+protected:
+ bool OnInitDialog() override;
+ bool OnApply() override;
+
+public:
+ COptionsMainDlg();
+};
+
+/////////////////////////////////////////////////////////////////////////////////
+
+class CAccountManagerDlg : public CProtoDlgBase<CCloudService>
+{
+private:
+ CCtrlButton m_requestAccess;
+ CCtrlButton m_revokeAccess;
+
+protected:
+ bool OnInitDialog() override;
+
+ void RequestAccess_OnClick(CCtrlButton*);
+ void RevokeAccess_OnClick(CCtrlButton*);
+
+public:
+ CAccountManagerDlg(CCloudService *service);
+};
+
+#endif //_OPTIONS_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/resource.h b/protocols/CloudFile/src/resource.h
new file mode 100644
index 0000000000..d839f15da4
--- /dev/null
+++ b/protocols/CloudFile/src/resource.h
@@ -0,0 +1,38 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by D:\Projects\miranda-ng\miranda-ng\plugins\CloudFile\res\resource.rc
+//
+#define IDOK 1
+#define IDCANCEL 2
+#define IDD_ACCMGR 9
+#define IDI_UPLOAD 101
+#define IDI_DROPBOX 102
+#define IDI_GDRIVE 103
+#define IDI_ONEDRIVE 104
+#define IDI_YADISK 105
+#define IDD_OAUTH 120
+#define IDC_OAUTH_CODE 121
+#define IDC_OAUTH_AUTHORIZE 122
+#define IDD_OPTIONS_MAIN 1000
+#define IDC_DEFAULTSERVICE 1001
+#define IDC_DONOTHINGONCONFLICT 1010
+#define IDC_RENAMEONCONFLICT 1011
+#define IDC_REPLACEONCONFLICT 1012
+#define IDC_URL_ISTEMPORARY 1021
+#define IDC_URL_COPYTOCB 1022
+#define IDC_URL_COPYTOMIA 1023
+#define IDC_URL_AUTOSEND 1024
+#define IDC_AUTH_TEXT 1031
+#define IDC_REQUESTACCESS 1033
+#define IDC_REVOKEACCESS 1034
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 133
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1034
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/CloudFile/src/services.cpp b/protocols/CloudFile/src/services.cpp
new file mode 100644
index 0000000000..e0a01fbfba
--- /dev/null
+++ b/protocols/CloudFile/src/services.cpp
@@ -0,0 +1,106 @@
+#include "stdafx.h"
+
+static int CompareServices(const CCloudService *p1, const CCloudService *p2)
+{
+ return mir_strcmp(p1->GetAccountName(), p2->GetAccountName());
+}
+
+LIST<CCloudService> Services(10, CompareServices);
+
+CCloudService* FindService(const char *szProto)
+{
+ for (auto &it : Services)
+ if (!mir_strcmp(it->GetAccountName(), szProto))
+ return it;
+
+ return nullptr;
+}
+
+static INT_PTR GetService(WPARAM wParam, LPARAM lParam)
+{
+ CFSERVICEINFO *info = (CFSERVICEINFO*)lParam;
+ if (info == nullptr)
+ return 1;
+
+ ptrA accountName(mir_strdup((char*)wParam));
+ if (!accountName || !mir_strlen(accountName))
+ accountName = g_plugin.getStringA("DefaultService");
+ if (accountName == nullptr)
+ return 2;
+
+ CCloudService *service = FindService(accountName);
+ if (service == nullptr)
+ return 3;
+
+ info->accountName = service->GetAccountName();
+ info->userName = service->GetUserName();
+
+ return 0;
+}
+
+static INT_PTR EnumServices(WPARAM wParam, LPARAM lParam)
+{
+ CFSERVICEINFO info = {};
+ enumCFServiceFunc enumFunc = (enumCFServiceFunc)wParam;
+ void *param = (void*)lParam;
+
+ for (auto &service : Services) {
+ info.accountName = service->GetAccountName();
+ info.userName = service->GetUserName();
+ int res = enumFunc(&info, param);
+ if (res != 0)
+ return res;
+ }
+
+ return 0;
+}
+
+INT_PTR Upload(WPARAM wParam, LPARAM lParam)
+{
+ CFUPLOADDATA *uploadData = (CFUPLOADDATA*)wParam;
+ if (uploadData == nullptr)
+ return 1;
+
+ ptrA accountName(mir_strdup(uploadData->accountName));
+ if (!mir_strlen(accountName))
+ accountName = g_plugin.getStringA("DefaultService");
+ if (accountName == nullptr)
+ return 2;
+
+ CCloudService *service = FindService(uploadData->accountName);
+ if (service == nullptr)
+ return 3;
+
+ if (PathIsDirectory(uploadData->localPath)) {
+ // temporary unsupported
+ return 4;
+ }
+
+ FileTransferParam ftp(0);
+ ftp.SetWorkingDirectory(uploadData->localPath);
+ ftp.SetServerDirectory(uploadData->serverFolder);
+ ftp.AddFile(uploadData->localPath);
+
+ int res = CCloudService::Upload(service, &ftp);
+ if (res == ACKRESULT_SUCCESS && lParam) {
+ size_t linkCount = 0;
+ const char **links = ftp.GetSharedLinks(linkCount);
+ if (linkCount > 0) {
+ CFUPLOADRESULT *result = (CFUPLOADRESULT*)lParam;
+ result->link = mir_strdup(links[linkCount - 1]);
+ }
+ }
+
+ return res;
+}
+
+void InitializeServices()
+{
+ Proto_RegisterModule(PROTOTYPE_FILTER, MODULENAME);
+
+ CreateServiceFunction(MODULENAME PSS_FILE, SendFileInterceptor);
+
+ CreateServiceFunction(MS_CLOUDFILE_GETSERVICE, GetService);
+ CreateServiceFunction(MS_CLOUDFILE_ENUMSERVICES, EnumServices);
+ CreateServiceFunction(MS_CLOUDFILE_UPLOAD, Upload);
+}
diff --git a/protocols/CloudFile/src/srmm.cpp b/protocols/CloudFile/src/srmm.cpp
new file mode 100644
index 0000000000..b4d44c9fdd
--- /dev/null
+++ b/protocols/CloudFile/src/srmm.cpp
@@ -0,0 +1,67 @@
+#include "stdafx.h"
+
+int OnSrmmToolbarLoaded(WPARAM, LPARAM)
+{
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULENAME;
+ bbd.bbbFlags = BBBF_ISIMBUTTON | BBBF_ISCHATBUTTON | BBBF_ISRSIDEBUTTON | BBBF_ISARROWBUTTON;
+
+ CMStringW tooltip(FORMAT, TranslateT("Upload files to..."));
+ bbd.pwszTooltip = tooltip;
+ bbd.hIcon = GetIconHandle(IDI_UPLOAD);
+ bbd.dwButtonID = BBB_ID_FILE_SEND;
+ bbd.dwDefPos = 100 + bbd.dwButtonID;
+ Srmm_AddButton(&bbd, &g_plugin);
+ return 0;
+}
+
+int OnSrmmWindowOpened(WPARAM, LPARAM lParam)
+{
+ MessageWindowEventData *ev = (MessageWindowEventData*)lParam;
+ if (ev->uType == MSG_WINDOW_EVT_OPENING && ev->hContact) {
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULENAME;
+ bbd.dwButtonID = BBB_ID_FILE_SEND;
+ bbd.bbbFlags = CanSendToContact(ev->hContact)
+ ? BBSF_RELEASED
+ : BBSF_DISABLED;
+ Srmm_SetButtonState(ev->hContact, &bbd);
+ }
+
+ return 0;
+}
+
+int OnSrmmButtonPressed(WPARAM, LPARAM lParam)
+{
+ CustomButtonClickData *cbc = (CustomButtonClickData*)lParam;
+
+ if (mir_strcmp(cbc->pszModule, MODULENAME))
+ return 0;
+
+ if (cbc->dwButtonId != BBB_ID_FILE_SEND)
+ return 0;
+
+ if (cbc->flags != BBCF_ARROWCLICKED) {
+ ptrA defaultService(g_plugin.getStringA("DefaultService"));
+ if (defaultService) {
+ CCloudService *service = FindService(defaultService);
+ if (service)
+ service->OpenUploadDialog(cbc->hContact);
+ return 0;
+ }
+ }
+
+ HMENU hMenu = CreatePopupMenu();
+ for (auto &it : Services)
+ AppendMenu(hMenu, MF_STRING, Services.indexOf(&it) + 1, TranslateW(it->GetUserName()));
+
+ int pos = TrackPopupMenu(hMenu, TPM_RETURNCMD, cbc->pt.x, cbc->pt.y, 0, cbc->hwndFrom, nullptr);
+ DestroyMenu(hMenu);
+
+ if (pos > 0) {
+ CCloudService *service = Services[pos - 1];
+ service->OpenUploadDialog(cbc->hContact);
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/protocols/CloudFile/src/stdafx.cxx b/protocols/CloudFile/src/stdafx.cxx
new file mode 100644
index 0000000000..708e6f1c91
--- /dev/null
+++ b/protocols/CloudFile/src/stdafx.cxx
@@ -0,0 +1,20 @@
+/*
+Copyright (C) 2012-19 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+ULONG FileTransferParam::hFileProcess = 1; \ No newline at end of file
diff --git a/protocols/CloudFile/src/stdafx.h b/protocols/CloudFile/src/stdafx.h
new file mode 100644
index 0000000000..58801c4a34
--- /dev/null
+++ b/protocols/CloudFile/src/stdafx.h
@@ -0,0 +1,126 @@
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <windows.h>
+#include <shlwapi.h>
+#include <commctrl.h>
+
+#include <malloc.h>
+#include <time.h>
+
+#include <map>
+#include <algorithm>
+
+#include <newpluginapi.h>
+
+#include <m_options.h>
+#include <m_database.h>
+#include <m_netlib.h>
+#include <m_clist.h>
+#include <m_icolib.h>
+#include <m_popup.h>
+#include <m_file.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_gui.h>
+#include <m_chat.h>
+#include <m_http.h>
+#include <m_json.h>
+#include <m_metacontacts.h>
+#include <m_protoint.h>
+#include <m_protosvc.h>
+#include <m_contacts.h>
+
+#include <m_cloudfile.h>
+
+#include "version.h"
+#include "resource.h"
+
+class CCloudService;
+
+#include "options.h"
+
+extern HNETLIBUSER hNetlibConnection;
+extern PLUGININFOEX pluginInfoEx;
+
+class Exception
+{
+ CMStringA message;
+
+public:
+ Exception(const char *message) :
+ message(message)
+ {
+ }
+
+ const char* what() const throw()
+ {
+ return message.c_str();
+ }
+};
+
+#define MODULENAME "CloudFile"
+
+#define FILE_CHUNK_SIZE 1024 * 1024 //1 MB
+
+#include "http_request.h"
+#include "file_transfer.h"
+
+// services
+#include "cloud_file.h"
+#include "oauth.h"
+#include "Services\dropbox_service.h"
+#include "Services\google_service.h"
+#include "Services\microsoft_service.h"
+#include "Services\yandex_service.h"
+extern LIST<CCloudService> Services;
+void InitializeServices();
+
+// events
+int OnModulesLoaded(WPARAM, LPARAM);
+
+// icons
+void InitializeIcons();
+HANDLE GetIconHandle(int iconId);
+HANDLE GetIconHandle(const char *name);
+HICON LoadIconEx(int iconId, bool big = false);
+
+// menus
+extern HGENMENU hContactMenu;
+void InitializeMenus();
+int OnPrebuildContactMenu(WPARAM, LPARAM);
+
+// srmm
+#define BBB_ID_FILE_SEND 10001
+int OnSrmmToolbarLoaded(WPARAM, LPARAM);
+int OnSrmmWindowOpened(WPARAM, LPARAM);
+int OnSrmmButtonPressed(WPARAM, LPARAM);
+
+// options
+int OnOptionsInitialized(WPARAM wParam, LPARAM);
+
+// transfers
+extern LIST<FileTransferParam> Transfers;
+
+INT_PTR SendFileInterceptor(WPARAM wParam, LPARAM lParam);
+UINT UploadAndReportProgressThread(void *owner, void *arg);
+
+// utils
+void ShowNotification(const wchar_t *caption, const wchar_t *message, int flags, MCONTACT hContact = NULL);
+void ShowNotification(const wchar_t *message, int flags, MCONTACT hContact = NULL);
+bool CanSendToContact(MCONTACT hContact);
+void SendToContact(MCONTACT hContact, const wchar_t *data);
+void PasteToInputArea(MCONTACT hContact, const wchar_t *data);
+void PasteToClipboard(const wchar_t *data);
+void Report(MCONTACT hContact, const wchar_t *data);
+
+CCloudService* FindService(const char *szProto);
+
+struct CMPlugin : public PLUGIN<CMPlugin>
+{
+ CMPlugin();
+
+ int Load() override;
+};
+
+#endif //_COMMON_H_ \ No newline at end of file
diff --git a/protocols/CloudFile/src/transfers.cpp b/protocols/CloudFile/src/transfers.cpp
new file mode 100644
index 0000000000..5236e0c9f0
--- /dev/null
+++ b/protocols/CloudFile/src/transfers.cpp
@@ -0,0 +1,39 @@
+#include "stdafx.h"
+
+LIST<FileTransferParam> Transfers(1, HandleKeySortT);
+
+INT_PTR SendFileInterceptor(WPARAM, LPARAM lParam)
+{
+ CCSDATA *pccsd = (CCSDATA*)lParam;
+ for (auto &service : Services) {
+ auto it = service->InterceptedContacts.find(pccsd->hContact);
+ if (it == service->InterceptedContacts.end())
+ continue;
+ service->InterceptedContacts.erase(it);
+ return (INT_PTR)service->SendFile(pccsd->hContact, (wchar_t*)pccsd->wParam, (wchar_t**)pccsd->lParam);
+ }
+ return CALLSERVICE_NOTFOUND;
+}
+
+UINT UploadAndReportProgressThread(void *owner, void *arg)
+{
+ CCloudService *service = (CCloudService*)owner;
+ FileTransferParam *ftp = (FileTransferParam*)arg;
+
+ int res = CCloudService::Upload(service, ftp);
+ if (res == ACKRESULT_SUCCESS) {
+ CMStringW data = ftp->GetDescription();
+ size_t linkCount;
+ auto links = ftp->GetSharedLinks(linkCount);
+ for (size_t i = 0; i < linkCount; i++) {
+ data.Append(ptrW(mir_utf8decodeW(links[i])));
+ data.AppendChar(0x0A);
+ }
+ Report(ftp->GetContact(), data);
+ }
+
+ Transfers.remove(ftp);
+ delete ftp;
+
+ return res;
+}
diff --git a/protocols/CloudFile/src/utils.cpp b/protocols/CloudFile/src/utils.cpp
new file mode 100644
index 0000000000..79b743f5c2
--- /dev/null
+++ b/protocols/CloudFile/src/utils.cpp
@@ -0,0 +1,113 @@
+#include "stdafx.h"
+
+void ShowNotification(const wchar_t *caption, const wchar_t *message, int flags, MCONTACT hContact)
+{
+ if (Miranda_IsTerminated())
+ return;
+
+ if (ServiceExists(MS_POPUP_ADDPOPUPW) && db_get_b(0, "Popup", "ModuleIsEnabled", 1)) {
+ POPUPDATAW ppd = { 0 };
+ ppd.lchContact = hContact;
+ wcsncpy(ppd.lpwzContactName, caption, MAX_CONTACTNAME);
+ wcsncpy(ppd.lpwzText, message, MAX_SECONDLINE);
+ ppd.lchIcon = IcoLib_GetIcon("Slack_main");
+
+ if (!PUAddPopupW(&ppd))
+ return;
+ }
+
+ MessageBox(nullptr, message, caption, MB_OK | flags);
+}
+
+void ShowNotification(const wchar_t *message, int flags, MCONTACT hContact)
+{
+ ShowNotification(_A2W(MODULENAME), message, flags, hContact);
+}
+
+MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD flags, DWORD cbBlob, PBYTE pBlob)
+{
+ DBEVENTINFO dbei = {};
+ dbei.szModule = MODULENAME;
+ dbei.timestamp = time(0);
+ dbei.eventType = type;
+ dbei.cbBlob = cbBlob;
+ dbei.pBlob = pBlob;
+ dbei.flags = flags;
+ return db_event_add(hContact, &dbei);
+}
+
+bool CanSendToContact(MCONTACT hContact)
+{
+ if (!hContact)
+ return false;
+
+ const char *proto = GetContactProto(hContact);
+ if (!proto)
+ return false;
+
+ bool isCtrlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
+ if (isCtrlPressed)
+ return true;
+
+ bool canSend = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND) != 0;
+ if (!canSend)
+ return false;
+
+ bool isProtoOnline = Proto_GetStatus(proto) > ID_STATUS_OFFLINE;
+ if (!isProtoOnline)
+ return false;
+
+ bool isContactOnline = Contact_GetStatus(hContact) > ID_STATUS_OFFLINE;
+ if (isContactOnline)
+ return true;
+
+ return CallProtoService(proto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE;
+}
+
+void SendToContact(MCONTACT hContact, const wchar_t *data)
+{
+ const char *szProto = GetContactProto(hContact);
+ if (db_get_b(hContact, szProto, "ChatRoom", 0) == TRUE) {
+ ptrW tszChatRoom(db_get_wsa(hContact, szProto, "ChatRoomID"));
+ Chat_SendUserMessage(szProto, tszChatRoom, data);
+ return;
+ }
+
+ char *message = mir_utf8encodeW(data);
+ if (ProtoChainSend(hContact, PSS_MESSAGE, 0, (LPARAM)message) != ACKRESULT_FAILED)
+ AddEventToDb(hContact, EVENTTYPE_MESSAGE, DBEF_UTF | DBEF_SENT, (DWORD)mir_strlen(message), (PBYTE)message);
+}
+
+void PasteToInputArea(MCONTACT hContact, const wchar_t *data)
+{
+ CallService(MS_MSG_SENDMESSAGEW, hContact, (LPARAM)data);
+}
+
+void PasteToClipboard(const wchar_t *data)
+{
+ if (OpenClipboard(nullptr)) {
+ EmptyClipboard();
+
+ size_t size = sizeof(wchar_t) * (mir_wstrlen(data) + 1);
+ HGLOBAL hClipboardData = GlobalAlloc(NULL, size);
+ if (hClipboardData) {
+ wchar_t *pchData = (wchar_t*)GlobalLock(hClipboardData);
+ mir_wstrcpy(pchData, data);
+ GlobalUnlock(hClipboardData);
+ SetClipboardData(CF_UNICODETEXT, hClipboardData);
+ }
+ CloseClipboard();
+ }
+}
+
+void Report(MCONTACT hContact, const wchar_t *data)
+{
+ if (g_plugin.getByte("UrlAutoSend", 1))
+ SendToContact(hContact, data);
+
+ if (g_plugin.getByte("UrlPasteToMessageInputArea", 0))
+ PasteToInputArea(hContact, data);
+
+ if (g_plugin.getByte("UrlCopyToClipboard", 0))
+ PasteToClipboard(data);
+}
diff --git a/protocols/CloudFile/src/version.h b/protocols/CloudFile/src/version.h
new file mode 100644
index 0000000000..fbc4ce1219
--- /dev/null
+++ b/protocols/CloudFile/src/version.h
@@ -0,0 +1,13 @@
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 11
+#define __RELEASE_NUM 0
+#define __BUILD_NUM 6
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "CloudFile"
+#define __FILENAME "CloudFile.dll"
+#define __DESCRIPTION "Allows you to transfer files via cloud services."
+#define __AUTHOR "Miranda NG team"
+#define __AUTHORWEB "https://miranda-ng.org/p/CloudFile/"
+#define __COPYRIGHT "© 2017-19 Miranda NG team"