summaryrefslogtreecommitdiff
path: root/plugins/CloudFile
diff options
context:
space:
mode:
authoraunsane <aunsane@gmail.com>2017-04-16 01:32:19 +0300
committeraunsane <aunsane@gmail.com>2017-04-16 01:32:58 +0300
commit0b9fa1d90f8d0aff7118837ceb1211b578a5a9c8 (patch)
tree3b8be7b839a98a3a52a38d713c2d708ada015510 /plugins/CloudFile
parent008fb731e3e3b587f596afba1cfe7446de7f0cac (diff)
CloudFile: initial commit
- Dropbox (worked) - Yandex.Disk (worked) - GDrive (not worked)
Diffstat (limited to 'plugins/CloudFile')
-rw-r--r--plugins/CloudFile/CloudFile.vcxproj47
-rw-r--r--plugins/CloudFile/CloudFile.vcxproj.filters4
-rw-r--r--plugins/CloudFile/res/dropbox.icobin0 -> 5430 bytes
-rw-r--r--plugins/CloudFile/res/gdrive.icobin0 -> 5430 bytes
-rw-r--r--plugins/CloudFile/res/resource.rc163
-rw-r--r--plugins/CloudFile/res/upload.icobin0 -> 103999 bytes
-rw-r--r--plugins/CloudFile/res/version.rc38
-rw-r--r--plugins/CloudFile/src/Services/dropbox_api.h200
-rw-r--r--plugins/CloudFile/src/Services/dropbox_service.cpp277
-rw-r--r--plugins/CloudFile/src/Services/dropbox_service.h33
-rw-r--r--plugins/CloudFile/src/Services/google_api.h101
-rw-r--r--plugins/CloudFile/src/Services/google_service.cpp216
-rw-r--r--plugins/CloudFile/src/Services/google_service.h31
-rw-r--r--plugins/CloudFile/src/Services/yandex_api.h84
-rw-r--r--plugins/CloudFile/src/Services/yandex_service.cpp219
-rw-r--r--plugins/CloudFile/src/Services/yandex_service.h31
-rw-r--r--plugins/CloudFile/src/cloud_service.cpp164
-rw-r--r--plugins/CloudFile/src/cloud_service.h41
-rw-r--r--plugins/CloudFile/src/events.cpp62
-rw-r--r--plugins/CloudFile/src/file_transfer.h245
-rw-r--r--plugins/CloudFile/src/http_request.h166
-rw-r--r--plugins/CloudFile/src/icons.cpp40
-rw-r--r--plugins/CloudFile/src/main.cpp65
-rw-r--r--plugins/CloudFile/src/menus.cpp65
-rw-r--r--plugins/CloudFile/src/oauth.cpp23
-rw-r--r--plugins/CloudFile/src/oauth.h22
-rw-r--r--plugins/CloudFile/src/options.cpp33
-rw-r--r--plugins/CloudFile/src/options.h18
-rw-r--r--plugins/CloudFile/src/proto.cpp32
-rw-r--r--plugins/CloudFile/src/resource.h30
-rw-r--r--plugins/CloudFile/src/srmm.cpp77
-rw-r--r--plugins/CloudFile/src/stdafx.cxx20
-rw-r--r--plugins/CloudFile/src/stdafx.h115
-rw-r--r--plugins/CloudFile/src/transfers.cpp69
-rw-r--r--plugins/CloudFile/src/utils.cpp40
-rw-r--r--plugins/CloudFile/src/version.h14
36 files changed, 2785 insertions, 0 deletions
diff --git a/plugins/CloudFile/CloudFile.vcxproj b/plugins/CloudFile/CloudFile.vcxproj
new file mode 100644
index 0000000000..c16966ec41
--- /dev/null
+++ b/plugins/CloudFile/CloudFile.vcxproj
@@ -0,0 +1,47 @@
+<?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\Dropbox\*.h" />
+ <ClCompile Include="src\Dropbox\*.cpp">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <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>
+ <ClCompile>
+ <ExceptionHandling>Sync</ExceptionHandling>
+ </ClCompile>
+ </ItemDefinitionGroup>
+</Project>
diff --git a/plugins/CloudFile/CloudFile.vcxproj.filters b/plugins/CloudFile/CloudFile.vcxproj.filters
new file mode 100644
index 0000000000..8f90aeb3d5
--- /dev/null
+++ b/plugins/CloudFile/CloudFile.vcxproj.filters
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" />
+</Project>
diff --git a/plugins/CloudFile/res/dropbox.ico b/plugins/CloudFile/res/dropbox.ico
new file mode 100644
index 0000000000..8a286d7f32
--- /dev/null
+++ b/plugins/CloudFile/res/dropbox.ico
Binary files differ
diff --git a/plugins/CloudFile/res/gdrive.ico b/plugins/CloudFile/res/gdrive.ico
new file mode 100644
index 0000000000..a34123e95b
--- /dev/null
+++ b/plugins/CloudFile/res/gdrive.ico
Binary files differ
diff --git a/plugins/CloudFile/res/resource.rc b/plugins/CloudFile/res/resource.rc
new file mode 100644
index 0000000000..bf1cd8940d
--- /dev/null
+++ b/plugins/CloudFile/res/resource.rc
@@ -0,0 +1,163 @@
+// 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"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// 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
+ GROUPBOX "Download link",-1,5,5,297,57
+ CONTROL "Autosend download link to contact",IDC_URL_AUTOSEND,
+ "Button",BS_AUTORADIOBUTTON,15,19,282,10
+ CONTROL "Paste download link into message input area",IDC_URL_COPYTOMIA,
+ "Button",BS_AUTORADIOBUTTON,15,32,282,10
+ CONTROL "Copy download link to clipboard",IDC_URL_COPYTOCB,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,45,282,10
+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,41,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,31,179,8
+ LTEXT "Allow access to team",IDC_STATIC,7,7,68,8
+ CONTROL "Go to this link",IDC_OAUTH_AUTHORIZE,"Hyperlink",WS_GROUP | WS_TABSTOP | 0x1,7,17,179,8
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_OPTIONS_MAIN AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+IDD_OAUTH AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_OPTIONS_MAIN, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 302
+ VERTGUIDE, 15
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 229
+ END
+
+ IDD_OAUTH, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 186
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 76
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/plugins/CloudFile/res/upload.ico b/plugins/CloudFile/res/upload.ico
new file mode 100644
index 0000000000..b51e87ed35
--- /dev/null
+++ b/plugins/CloudFile/res/upload.ico
Binary files differ
diff --git a/plugins/CloudFile/res/version.rc b/plugins/CloudFile/res/version.rc
new file mode 100644
index 0000000000..675d852de7
--- /dev/null
+++ b/plugins/CloudFile/res/version.rc
@@ -0,0 +1,38 @@
+// Microsoft Visual C++ generated resource script.
+//
+#ifdef APSTUDIO_INVOKED
+#error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+#include "afxres.h"
+#include "..\src\version.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION __FILEVERSION_STRING
+ PRODUCTVERSION __FILEVERSION_STRING
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x0L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004b0"
+ BEGIN
+ VALUE "FileDescription", __DESCRIPTION
+ VALUE "InternalName", __PLUGIN_NAME
+ VALUE "LegalCopyright", __COPYRIGHT
+ VALUE "OriginalFilename", __FILENAME
+ VALUE "ProductName", __PLUGIN_NAME
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0, 1200
+ END
+END
diff --git a/plugins/CloudFile/src/Services/dropbox_api.h b/plugins/CloudFile/src/Services/dropbox_api.h
new file mode 100644
index 0000000000..3db69250e8
--- /dev/null
+++ b/plugins/CloudFile/src/Services/dropbox_api.h
@@ -0,0 +1,200 @@
+#ifndef _DROPBOXSERVICE_API_H_
+#define _DROPBOXSERVICE_API_H_
+
+namespace DropboxAPI
+{
+#define DROPBOX_API_VER "/2"
+#define DROPBOX_WWW_URL "https://www.dropbox.com"
+#define DROPBOX_API "https://api.dropboxapi.com"
+#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"
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *requestToken) :
+ HttpRequest(REQUEST_POST, DROPBOX_API "/oauth2/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",
+ DROPBOX_APP_KEY, DROPBOX_API_SECRET, requestToken);
+ SetData(data.GetBuffer(), data.GetLength());
+ }
+ };
+
+ class RevokeAccessTokenRequest : public HttpRequest
+ {
+ public:
+ RevokeAccessTokenRequest(const char *token) :
+ HttpRequest(REQUEST_POST, DROPBOX_API "/oauth2/token/revoke")
+ {
+ AddBearerAuthHeader(token);
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *path, const char *data, size_t size) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("path", path)
+ << JSONNode("mode", "overwrite");
+
+ AddHeader("Dropbox-API-Arg", params.write().c_str());
+
+ SetData(data, size);
+ }
+ };
+
+ class StartUploadSessionRequest : public HttpRequest
+ {
+ public:
+ StartUploadSessionRequest(const char *token, const char *data, size_t size) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/start")
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/octet-stream");
+
+ SetData(data, size);
+ }
+ };
+
+ class AppendToUploadSessionRequest : public HttpRequest
+ {
+ public:
+ AppendToUploadSessionRequest(const char *token, const char *sessionId, size_t offset, const char *data, size_t size) :
+ 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(data, size);
+ }
+ };
+
+ class FinishUploadSessionRequest : public HttpRequest
+ {
+ public:
+ FinishUploadSessionRequest(const char *token, const char *sessionId, size_t offset, const char *path, const char *data, size_t size) :
+ 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)
+ << JSONNode("mode", "overwrite");
+
+ JSONNode params(JSON_NODE);
+ params
+ << cursor
+ << commit;
+
+ AddHeader("Dropbox-API-Arg", params.write().c_str());
+
+ SetData(data, size);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/files/create_folder")
+ {
+ 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/plugins/CloudFile/src/Services/dropbox_service.cpp b/plugins/CloudFile/src/Services/dropbox_service.cpp
new file mode 100644
index 0000000000..26c0ff6af3
--- /dev/null
+++ b/plugins/CloudFile/src/Services/dropbox_service.cpp
@@ -0,0 +1,277 @@
+#include "..\stdafx.h"
+#include "dropbox_api.h"
+
+CDropboxService::CDropboxService(HNETLIBUSER hConnection)
+ : CCloudService(hConnection)
+{
+}
+
+const char* CDropboxService::GetModule() const
+{
+ return "Dropbox";
+}
+
+const wchar_t* CDropboxService::GetText() const
+{
+ return L"Dropbox";
+}
+
+HANDLE CDropboxService::GetIcon() const
+{
+ return GetIconHandle(IDI_DROPBOX);
+}
+
+bool CDropboxService::IsLoggedIn()
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ return token != NULL;
+}
+
+void CDropboxService::Login()
+{
+ COAuthDlg(this, DROPBOX_WWW_URL "/oauth2/authorize?response_type=code&client_id=" DROPBOX_APP_KEY, RequestAccessTokenThread).DoModal();
+}
+
+void CDropboxService::Logout()
+{
+ mir_forkthreadex(RevokeAccessTokenThread, this);
+}
+
+unsigned CDropboxService::RequestAccessTokenThread(void *owner, void *param)
+{
+ HWND hwndDlg = (HWND)param;
+ CDropboxService *service = (CDropboxService*)owner;
+
+ if (service->IsLoggedIn())
+ service->Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ DropboxAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ if (response == NULL || response->resultCode != HTTP_CODE_OK) {
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError());
+ //ShowNotification(TranslateT("server does not respond"), MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ //ShowNotification((wchar_t*)error_description, MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8));
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ //ShowNotification((wchar_t*)error_description, MB_ICONERROR);
+ return 0;
+ }
+
+ node = root.at("access_token");
+ db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str());
+ ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE);
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+
+ return 0;
+}
+
+unsigned CDropboxService::RevokeAccessTokenThread(void *param)
+{
+ CDropboxService *service = (CDropboxService*)param;
+
+ ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret"));
+ DropboxAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ return 0;
+}
+
+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());
+ }
+}
+
+char* CDropboxService::UploadFile(const char *data, size_t size, char *path)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ ptrA encodedPath(mir_utf8encode(path));
+ DropboxAPI::UploadFileRequest request(token, encodedPath, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode node = root.at("path_lower");
+ mir_strcpy(path, node.as_string().c_str());
+
+ return path;
+}
+
+void CDropboxService::StartUploadSession(const char *data, size_t size, char *sessionId)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ DropboxAPI::StartUploadSessionRequest request(token, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode node = root.at("session_id");
+ mir_strcpy(sessionId, node.as_string().c_str());
+}
+
+void CDropboxService::AppendToUploadSession(const char *data, size_t size, const char *sessionId, size_t offset)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ DropboxAPI::AppendToUploadSessionRequest request(token, sessionId, offset, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ GetJsonResponse(response);
+}
+
+char* CDropboxService::FinishUploadSession(const char *data, size_t size, const char *sessionId, size_t offset, char *path)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ DropboxAPI::FinishUploadSessionRequest request(token, sessionId, offset, path, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode node = root.at("path_lower");
+ mir_strcpy(path, node.as_string().c_str());
+
+ return path;
+}
+
+void CDropboxService::CreateFolder(const char *path)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ DropboxAPI::CreateFolderRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ HandleHttpError(response);
+
+ // forder exists on server
+ if (response->resultCode == HTTP_CODE_FORBIDDEN)
+ return;
+
+ GetJsonResponse(response);
+}
+
+void CDropboxService::CreateSharedLink(const char *path, char *url)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ DropboxAPI::CreateSharedLinkRequest shareRequest(token, path);
+ NLHR_PTR response(shareRequest.Send(hConnection));
+
+ HandleHttpError(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("link");
+ mir_strcpy(url, link.as_string().c_str());
+ return;
+ }
+
+ json_string tag = error.at(".tag").as_string();
+ if (tag != "shared_link_already_exists")
+ throw Exception(tag.c_str());
+
+ DropboxAPI::GetSharedLinkRequest getRequest(token, path);
+ response = getRequest.Send(hConnection);
+
+ root = GetJsonResponse(response);
+
+ JSONNode links = root.at("links").as_array();
+ JSONNode link = links[0u].at("url");
+ mir_strcpy(url, link.as_string().c_str());
+}
+
+UINT CDropboxService::Upload(FileTransferParam *ftp)
+{
+ if (!IsLoggedIn())
+ Login();
+
+ try {
+ const wchar_t *folderName = ftp->GetFolderName();
+ if (folderName) {
+ char path[MAX_PATH], url[MAX_PATH];
+ PreparePath(folderName, path);
+ CreateFolder(path);
+ CreateSharedLink(path, url);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url)));
+ }
+
+ ftp->FirstFile();
+ do
+ {
+ const wchar_t *fileName = ftp->GetCurrentRelativeFilePath();
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ int chunkSize = ftp->GetCurrentFileChunkSize();
+ mir_ptr<char>data((char*)mir_calloc(chunkSize));
+ size_t size = ftp->ReadCurrentFile(data, chunkSize);
+
+ size_t offset = 0;
+ char sessionId[64];
+ StartUploadSession(data, size, sessionId);
+
+ offset += size;
+ ftp->Progress(size);
+
+ for (size_t chunk = 0; chunk < (fileSize / chunkSize) - 1; chunk++)
+ {
+ ftp->CheckCurrentFile();
+
+ size = ftp->ReadCurrentFile(data, chunkSize);
+ AppendToUploadSession(data, size, sessionId, offset);
+
+ offset += size;
+ ftp->Progress(size);
+ }
+
+ if (offset < fileSize)
+ size = ftp->ReadCurrentFile(data, fileSize - offset);
+ else
+ size = 0;
+
+ char path[MAX_PATH];
+ const wchar_t *serverFolder = ftp->GetServerFolder();
+ if (serverFolder) {
+ wchar_t serverPath[MAX_PATH] = { 0 };
+ mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName);
+ PreparePath(serverPath, path);
+ }
+ else
+ PreparePath(fileName, path);
+ FinishUploadSession(data, size, sessionId, offset, path);
+
+ ftp->Progress(size);
+
+ if (!wcschr(fileName, L'\\')) {
+ char url[MAX_PATH];
+ CreateSharedLink(path, url);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url)));
+ }
+ } while (ftp->NextFile());
+ }
+ catch (Exception &ex) {
+ Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what());
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ ftp->SetStatus(ACKRESULT_SUCCESS);
+ return ACKRESULT_SUCCESS;
+}
diff --git a/plugins/CloudFile/src/Services/dropbox_service.h b/plugins/CloudFile/src/Services/dropbox_service.h
new file mode 100644
index 0000000000..93b2fb53d3
--- /dev/null
+++ b/plugins/CloudFile/src/Services/dropbox_service.h
@@ -0,0 +1,33 @@
+#ifndef _CLOUDSERVICE_DROPBOX_H_
+#define _CLOUDSERVICE_DROPBOX_H_
+
+class CDropboxService : public CCloudService
+{
+private:
+ static unsigned RequestAccessTokenThread(void *owner, void *param);
+ static unsigned __stdcall RevokeAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node);
+
+ char* UploadFile(const char *data, size_t size, char *path);
+ void StartUploadSession(const char *data, size_t size, char *sessionId);
+ void AppendToUploadSession(const char *data, size_t size, const char *sessionId, size_t offset);
+ char* FinishUploadSession(const char *data, size_t size, const char *sessionId, size_t offset, char *path);
+ void CreateFolder(const char *path);
+ void CreateSharedLink(const char *path, char *url);
+
+public:
+ CDropboxService(HNETLIBUSER hConnection);
+
+ const char* GetModule() const;
+ const wchar_t* GetText() const;
+ HANDLE GetIcon() const;
+
+ bool IsLoggedIn();
+ void Login();
+ void Logout();
+
+ UINT Upload(FileTransferParam *ftp);
+};
+
+#endif //_CLOUDSERVICE_DROPBOX_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/Services/google_api.h b/plugins/CloudFile/src/Services/google_api.h
new file mode 100644
index 0000000000..7df5446065
--- /dev/null
+++ b/plugins/CloudFile/src/Services/google_api.h
@@ -0,0 +1,101 @@
+#ifndef _GDRIVESERVICE_API_H_
+#define _GDRIVESERVICE_API_H_
+
+namespace GDriveAPI
+{
+#define GOOGLE_OAUTH "https://accounts.google.com/o/oauth2/v2"
+#define GDRIVE_API "https://www.googleapis.com/drive/v2/files"
+
+#define GOOGLE_APP_ID "271668553802-3sd3tubkf165ibgrqnrhe3id8mcgnaf7.apps.googleusercontent.com"
+#include "../../../miranda-private-keys/Google/client_secret.h"
+
+ class GetAccessTokenRequest : public HttpRequest
+ {
+ public:
+ GetAccessTokenRequest(const char *code) :
+ HttpRequest(REQUEST_POST, "https://www.googleapis.com/oauth2/v4/token")
+ {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ CMStringA data(CMStringDataFormat::FORMAT,
+ "redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s",
+ GOOGLE_APP_ID, GOOGLE_CLIENT_SECRET, code);
+ 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 StartUploadFileRequest : public HttpRequest
+ {
+ public:
+ StartUploadFileRequest(const char *token) :
+ HttpRequest(REQUEST_POST, GDRIVE_API)
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+ AddUrlParameter("uploadType=resumable");
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *data, size_t size) :
+ HttpRequest(REQUEST_POST, GDRIVE_API)
+ {
+ AddBearerAuthHeader(token);
+ AddUrlParameter("uploadType=resumable");
+
+ SetData(data, size);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_PUT, GDRIVE_API)
+ {
+ AddBearerAuthHeader(token);
+ AddHeader("Content-Type", "application/json");
+
+ JSONNode params(JSON_NODE);
+ params
+ << JSONNode("name", path)
+ << JSONNode("mimeType", "pplication/vnd.google-apps.folder");
+
+ json_string data = params.write();
+ SetData(data.c_str(), data.length());
+ }
+ };
+
+ class ShareRequest : public HttpRequest
+ {
+ public:
+ ShareRequest(const char *token, const char *fileId) :
+ HttpRequest(REQUEST_PUT, FORMAT, GDRIVE_API "/%s/permissions", fileId)
+ {
+ 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/plugins/CloudFile/src/Services/google_service.cpp b/plugins/CloudFile/src/Services/google_service.cpp
new file mode 100644
index 0000000000..f0185bfbb8
--- /dev/null
+++ b/plugins/CloudFile/src/Services/google_service.cpp
@@ -0,0 +1,216 @@
+#include "..\stdafx.h"
+#include "google_api.h"
+
+CGDriveService::CGDriveService(HNETLIBUSER hConnection)
+ : CCloudService(hConnection)
+{
+}
+
+const char* CGDriveService::GetModule() const
+{
+ return "Google";
+}
+
+const wchar_t* CGDriveService::GetText() const
+{
+ return L"GDrive";
+}
+
+HANDLE CGDriveService::GetIcon() const
+{
+ return GetIconHandle(IDI_GDRIVE);
+}
+
+bool CGDriveService::IsLoggedIn()
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ return token != NULL;
+}
+
+void CGDriveService::Login()
+{
+ COAuthDlg(this, GOOGLE_OAUTH "/auth?response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=" GOOGLE_APP_ID, RequestAccessTokenThread).DoModal();
+}
+
+void CGDriveService::Logout()
+{
+ mir_forkthreadex(RevokeAccessTokenThread, this);
+}
+
+unsigned CGDriveService::RequestAccessTokenThread(void *owner, void *param)
+{
+ HWND hwndDlg = (HWND)param;
+ CGDriveService *service = (CGDriveService*)owner;
+
+ if (service->IsLoggedIn())
+ service->Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ GDriveAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ if (response == NULL || response->resultCode != HTTP_CODE_OK) {
+ const char *error = response->dataLength
+ ? response->pData
+ : service->HttpStatusToError(response->resultCode);
+
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), error);
+ ShowNotification(TranslateT("server does not respond"), MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("server does not respond"), MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8));
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ ShowNotification((wchar_t*)error_description, MB_ICONERROR);
+ return 0;
+ }
+
+ node = root.at("access_token");
+ db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str());
+ ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE);
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+
+ return 0;
+}
+
+unsigned CGDriveService::RevokeAccessTokenThread(void *param)
+{
+ CGDriveService *service = (CGDriveService*)param;
+
+ ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret"));
+ GDriveAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ return 0;
+}
+
+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());
+ }
+}
+
+void CGDriveService::StartUploadFile()
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ GDriveAPI::StartUploadFileRequest request(token);
+ NLHR_PTR response(request.Send(hConnection));
+
+ GetJsonResponse(response);
+}
+
+void CGDriveService::UploadFile(const char *url, const char *data, size_t size)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ GDriveAPI::UploadFileRequest request(token, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ if (response == NULL)
+ throw Exception(HttpStatusToError());
+
+ if (response->resultCode >= HTTP_CODE_OK &&
+ response->resultCode <= HTTP_CODE_MULTIPLE_CHOICES) {
+ return;
+ }
+
+ if (response->dataLength)
+ throw Exception(response->pData);
+ throw Exception(HttpStatusToError(response->resultCode));
+}
+
+void CGDriveService::CreateFolder(const char *path)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ GDriveAPI::CreateFolderRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ GetJsonResponse(response);
+}
+
+void CGDriveService::CreateSharedLink(const char *path, char *url)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ GDriveAPI::ShareRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode link = root.at("href");
+ mir_strcpy(url, link.as_string().c_str());
+}
+
+UINT CGDriveService::Upload(FileTransferParam *ftp)
+{
+ if (!IsLoggedIn())
+ Login();
+
+ if (!IsLoggedIn()) {
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ try {
+ const wchar_t *folderName = ftp->GetFolderName();
+ if (folderName) {
+ char path[MAX_PATH], link[MAX_PATH];
+ PreparePath(folderName, path);
+ CreateFolder(path);
+ CreateSharedLink(path, link);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(link)));
+ }
+
+ ftp->FirstFile();
+ do
+ {
+ const wchar_t *fileName = ftp->GetCurrentRelativeFilePath();
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ char path[MAX_PATH];
+ const wchar_t *serverFolder = ftp->GetServerFolder();
+ if (serverFolder) {
+ wchar_t serverPath[MAX_PATH] = { 0 };
+ mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName);
+ PreparePath(serverPath, path);
+ }
+ else
+ PreparePath(fileName, path);
+ StartUploadFile();
+
+ mir_ptr<char>data((char*)mir_calloc(fileSize));
+ size_t size = ftp->ReadCurrentFile(data, fileSize);
+ UploadFile("", data, size);
+
+ ftp->Progress(size);
+
+ if (!wcschr(fileName, L'\\')) {
+ char url[MAX_PATH];
+ CreateSharedLink(path, url);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url)));
+ }
+ } while (ftp->NextFile());
+ }
+ catch (Exception &ex) {
+ Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what());
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ ftp->SetStatus(ACKRESULT_SUCCESS);
+ return ACKRESULT_SUCCESS;
+}
diff --git a/plugins/CloudFile/src/Services/google_service.h b/plugins/CloudFile/src/Services/google_service.h
new file mode 100644
index 0000000000..db72ef9dee
--- /dev/null
+++ b/plugins/CloudFile/src/Services/google_service.h
@@ -0,0 +1,31 @@
+#ifndef _CLOUDFILE_GDRIVE_H_
+#define _CLOUDFILE_GDRIVE_H_
+
+class CGDriveService : public CCloudService
+{
+private:
+ static unsigned RequestAccessTokenThread(void *owner, void *param);
+ static unsigned __stdcall RevokeAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node);
+
+ void StartUploadFile();
+ void UploadFile(const char *url, const char *data, size_t size);
+ void CreateFolder(const char *path);
+ void CreateSharedLink(const char *path, char *url);
+
+public:
+ CGDriveService(HNETLIBUSER hConnection);
+
+ const char* GetModule() const;
+ const wchar_t* GetText() const;
+ HANDLE GetIcon() const;
+
+ bool IsLoggedIn();
+ void Login();
+ void Logout();
+
+ UINT Upload(FileTransferParam *ftp);
+};
+
+#endif //_CLOUDFILE_GDRIVE_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/Services/yandex_api.h b/plugins/CloudFile/src/Services/yandex_api.h
new file mode 100644
index 0000000000..7548f6bd14
--- /dev/null
+++ b/plugins/CloudFile/src/Services/yandex_api.h
@@ -0,0 +1,84 @@
+#ifndef _YANDEXSERVICE_API_H_
+#define _YANDEXSERVICE_API_H_
+
+namespace YandexAPI
+{
+#define YANDEX_OAUTH "https://oauth.yandex.ru"
+#define YADISK_API "https://cloud-api.yandex.net/v1/disk/resources"
+
+#define YANDEX_APP_ID "c311a5967cae4efa88d1af97d01ea0e8"
+#include "../../../miranda-private-keys/Yandex/client_secret.h"
+
+ 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 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) :
+ HttpRequest(REQUEST_GET, YADISK_API "/upload")
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=%s", ptrA(mir_urlEncode(path)));
+ AddUrlParameter("overwrite=true");
+ }
+ };
+
+ class UploadFileRequest : public HttpRequest
+ {
+ public:
+ UploadFileRequest(const char *token, const char *url, const char *data, size_t size) :
+ HttpRequest(REQUEST_PUT, url)
+ {
+ AddOAuthHeader(token);
+
+ SetData(data, size);
+ }
+ };
+
+ class CreateFolderRequest : public HttpRequest
+ {
+ public:
+ CreateFolderRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_PUT, YADISK_API)
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=%s", ptrA(mir_urlEncode(path)));
+ }
+ };
+
+ class PublishRequest : public HttpRequest
+ {
+ public:
+ PublishRequest(const char *token, const char *path) :
+ HttpRequest(REQUEST_PUT, YADISK_API "/publish")
+ {
+ AddOAuthHeader(token);
+ AddUrlParameter("path=%s", ptrA(mir_urlEncode(path)));
+ }
+ };
+};
+
+#endif //_YANDEXSERVICE_API_H_
diff --git a/plugins/CloudFile/src/Services/yandex_service.cpp b/plugins/CloudFile/src/Services/yandex_service.cpp
new file mode 100644
index 0000000000..03f34a432e
--- /dev/null
+++ b/plugins/CloudFile/src/Services/yandex_service.cpp
@@ -0,0 +1,219 @@
+#include "..\stdafx.h"
+#include "yandex_api.h"
+
+CYandexService::CYandexService(HNETLIBUSER hConnection)
+ : CCloudService(hConnection)
+{
+}
+
+const char* CYandexService::GetModule() const
+{
+ return "Yandex";
+}
+
+const wchar_t* CYandexService::GetText() const
+{
+ return L"ßíäåêñ.Äèñê";
+}
+
+HANDLE CYandexService::GetIcon() const
+{
+ return NULL;
+}
+
+bool CYandexService::IsLoggedIn()
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ return token != NULL;
+}
+
+void CYandexService::Login()
+{
+ COAuthDlg(this, YANDEX_OAUTH "/authorize?response_type=code&client_id=" YANDEX_APP_ID, RequestAccessTokenThread).DoModal();
+}
+
+void CYandexService::Logout()
+{
+ mir_forkthreadex(RevokeAccessTokenThread, this);
+}
+
+unsigned CYandexService::RequestAccessTokenThread(void *owner, void *param)
+{
+ HWND hwndDlg = (HWND)param;
+ CYandexService *service = (CYandexService*)owner;
+
+ if (service->IsLoggedIn())
+ service->Logout();
+
+ char requestToken[128];
+ GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken));
+
+ YandexAPI::GetAccessTokenRequest request(requestToken);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ if (response == NULL || response->resultCode != HTTP_CODE_OK) {
+ const char *error = response->dataLength
+ ? response->pData
+ : service->HttpStatusToError(response->resultCode);
+
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), error);
+ ShowNotification(TranslateT("server does not respond"), MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.empty()) {
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ ShowNotification(TranslateT("server does not respond"), MB_ICONERROR);
+ return 0;
+ }
+
+ JSONNode node = root.at("error_description");
+ if (!node.isnull()) {
+ ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8));
+ Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode));
+ ShowNotification((wchar_t*)error_description, MB_ICONERROR);
+ return 0;
+ }
+
+ node = root.at("access_token");
+ db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str());
+ ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE);
+
+ SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, "");
+
+ EndDialog(hwndDlg, 1);
+
+ return 0;
+}
+
+unsigned CYandexService::RevokeAccessTokenThread(void *param)
+{
+ CYandexService *service = (CYandexService*)param;
+
+ ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret"));
+ YandexAPI::RevokeAccessTokenRequest request(token);
+ NLHR_PTR response(request.Send(service->hConnection));
+
+ return 0;
+}
+
+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());
+ }
+}
+
+void CYandexService::GetUploadUrl(char *path, char *url)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ YandexAPI::GetUploadUrlRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode node = root.at("href");
+ mir_strcpy(url, node.as_string().c_str());
+}
+
+void CYandexService::UploadFile(const char *url, const char *data, size_t size)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ YandexAPI::UploadFileRequest request(token, url, data, size);
+ NLHR_PTR response(request.Send(hConnection));
+
+ if (response == NULL)
+ throw Exception(HttpStatusToError());
+
+ if (response->resultCode >= HTTP_CODE_OK &&
+ response->resultCode <= HTTP_CODE_MULTIPLE_CHOICES) {
+ return;
+ }
+
+ if (response->dataLength)
+ throw Exception(response->pData);
+ throw Exception(HttpStatusToError(response->resultCode));
+}
+
+void CYandexService::CreateFolder(const char *path)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ YandexAPI::CreateFolderRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ GetJsonResponse(response);
+}
+
+void CYandexService::CreateSharedLink(const char *path, char *url)
+{
+ ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret"));
+ YandexAPI::PublishRequest request(token, path);
+ NLHR_PTR response(request.Send(hConnection));
+
+ JSONNode root = GetJsonResponse(response);
+ JSONNode link = root.at("href");
+ mir_strcpy(url, link.as_string().c_str());
+}
+
+UINT CYandexService::Upload(FileTransferParam *ftp)
+{
+ if (!IsLoggedIn())
+ Login();
+
+ if (!IsLoggedIn()) {
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ try {
+ const wchar_t *folderName = ftp->GetFolderName();
+ if (folderName) {
+ char path[MAX_PATH], link[MAX_PATH];
+ PreparePath(folderName, path);
+ CreateFolder(path);
+ CreateSharedLink(path, link);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(link)));
+ }
+
+ ftp->FirstFile();
+ do
+ {
+ const wchar_t *fileName = ftp->GetCurrentRelativeFilePath();
+ uint64_t fileSize = ftp->GetCurrentFileSize();
+
+ char path[MAX_PATH];
+ const wchar_t *serverFolder = ftp->GetServerFolder();
+ if (serverFolder) {
+ wchar_t serverPath[MAX_PATH] = { 0 };
+ mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName);
+ PreparePath(serverPath, path);
+ }
+ else
+ PreparePath(fileName, path);
+ char url[MAX_PATH];
+ GetUploadUrl(path, url);
+
+ mir_ptr<char>data((char*)mir_calloc(fileSize));
+ size_t size = ftp->ReadCurrentFile(data, fileSize);
+ UploadFile(url, data, size);
+
+ ftp->Progress(size);
+
+ if (!wcschr(fileName, L'\\')) {
+ char url[MAX_PATH];
+ CreateSharedLink(path, url);
+ ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url)));
+ }
+ } while (ftp->NextFile());
+ }
+ catch (Exception &ex) {
+ Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what());
+ ftp->SetStatus(ACKRESULT_FAILED);
+ return ACKRESULT_FAILED;
+ }
+
+ ftp->SetStatus(ACKRESULT_SUCCESS);
+ return ACKRESULT_SUCCESS;
+}
diff --git a/plugins/CloudFile/src/Services/yandex_service.h b/plugins/CloudFile/src/Services/yandex_service.h
new file mode 100644
index 0000000000..a3a84735c4
--- /dev/null
+++ b/plugins/CloudFile/src/Services/yandex_service.h
@@ -0,0 +1,31 @@
+#ifndef _CLOUDFILE_YANDEX_H_
+#define _CLOUDFILE_YANDEX_H_
+
+class CYandexService : public CCloudService
+{
+private:
+ static unsigned RequestAccessTokenThread(void *owner, void *param);
+ static unsigned __stdcall RevokeAccessTokenThread(void *param);
+
+ void HandleJsonError(JSONNode &node);
+
+ void GetUploadUrl(char *path, char *url);
+ void UploadFile(const char *url, const char *data, size_t size);
+ void CreateFolder(const char *path);
+ void CreateSharedLink(const char *path, char *url);
+
+public:
+ CYandexService(HNETLIBUSER hConnection);
+
+ const char* GetModule() const;
+ const wchar_t* GetText() const;
+ HANDLE GetIcon() const;
+
+ bool IsLoggedIn();
+ void Login();
+ void Logout();
+
+ UINT Upload(FileTransferParam *ftp);
+};
+
+#endif //_CLOUDFILE_YANDEX_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/cloud_service.cpp b/plugins/CloudFile/src/cloud_service.cpp
new file mode 100644
index 0000000000..9e57dc14aa
--- /dev/null
+++ b/plugins/CloudFile/src/cloud_service.cpp
@@ -0,0 +1,164 @@
+#include "stdafx.h"
+
+static int CompareServices(const CCloudService *p1, const CCloudService *p2)
+{
+ return mir_strcmp(p1->GetModule(), p2->GetModule());
+}
+
+LIST<CCloudService> Services(10, CompareServices);
+
+void InitServices()
+{
+ Services.insert(new CDropboxService(hNetlibConnection));
+ //Services.insert(new CGDriveService(hNetlibConnection));
+ Services.insert(new CYandexService(hNetlibConnection));
+
+ PROTOCOLDESCRIPTOR pd = { sizeof(pd) };
+
+ size_t count = Services.getCount();
+ for (size_t i = 0; i < count; i++) {
+ CCloudService *service = Services[i];
+
+ CMStringA moduleName(CMStringDataFormat::FORMAT, "%s/%s", MODULE, service->GetModule());
+ pd.type = PROTOTYPE_VIRTUAL;
+ pd.szName = moduleName.GetBuffer();
+ Proto_RegisterModule(&pd);
+
+ CMStringA serviceName(CMStringDataFormat::FORMAT, "%s%s", moduleName, PSS_FILE);
+ CreateServiceFunctionObj(serviceName, ProtoSendFile, service);
+
+ moduleName = CMStringA(CMStringDataFormat::FORMAT, "%s/%s/Interceptor", MODULE, service->GetModule());
+ pd.szName = moduleName.GetBuffer();
+ pd.type = PROTOTYPE_FILTER;
+ Proto_RegisterModule(&pd);
+
+ serviceName = CMStringA(CMStringDataFormat::FORMAT, "%s%s", moduleName, PSS_FILE);
+ CreateServiceFunctionObj(serviceName, ProtoSendFileInterceptor, service);
+ }
+}
+
+CCloudService::CCloudService(HNETLIBUSER hConnection)
+ : hConnection(hConnection)
+{
+}
+
+void CCloudService::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 CCloudService::PasteToInputArea(MCONTACT hContact, const wchar_t *data)
+{
+ CallService(MS_MSG_SENDMESSAGEW, hContact, (LPARAM)data);
+}
+
+void CCloudService::PasteToClipboard(const wchar_t *data)
+{
+ if (OpenClipboard(NULL)) {
+ 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);
+ if (pchData) {
+ memcpy(pchData, (wchar_t*)data, size);
+ GlobalUnlock(hClipboardData);
+ SetClipboardData(CF_UNICODETEXT, hClipboardData);
+ }
+ }
+ CloseClipboard();
+ }
+}
+
+void CCloudService::Report(MCONTACT hContact, const wchar_t *data)
+{
+ if (db_get_b(NULL, MODULE, "UrlAutoSend", 1))
+ SendToContact(hContact, data);
+
+ if (db_get_b(NULL, MODULE, "UrlPasteToMessageInputArea", 0))
+ PasteToInputArea(hContact, data);
+
+ if (db_get_b(NULL, MODULE, "UrlCopyToClipboard", 0))
+ PasteToClipboard(data);
+}
+
+char* CCloudService::PreparePath(const char *oldPath, char *newPath)
+{
+ if (oldPath == NULL)
+ mir_strcpy(newPath, "");
+ else if (*oldPath != '/')
+ {
+ CMStringA result("/");
+ result.Append(oldPath);
+ result.Replace("\\", "/");
+ mir_strcpy(newPath, result);
+ }
+ else
+ mir_strcpy(newPath, oldPath);
+ return newPath;
+}
+
+char* CCloudService::PreparePath(const wchar_t *oldPath, char *newPath)
+{
+ return PreparePath(ptrA(mir_utf8encodeW(oldPath)), 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::HandleHttpError(NETLIBHTTPREQUEST *response)
+{
+ if (response == NULL)
+ throw Exception(HttpStatusToError());
+
+ if (response->resultCode != HTTP_CODE_OK &&
+ response->resultCode != HTTP_CODE_CONFLICT) {
+ if (response->dataLength)
+ throw Exception(response->pData);
+ throw Exception(HttpStatusToError(response->resultCode));
+ }
+}
+
+JSONNode CCloudService::GetJsonResponse(NETLIBHTTPREQUEST *response)
+{
+ HandleHttpError(response);
+
+ JSONNode root = JSONNode::parse(response->pData);
+ if (root.isnull())
+ throw Exception(HttpStatusToError());
+
+ HandleJsonError(root);
+
+ return root;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/cloud_service.h b/plugins/CloudFile/src/cloud_service.h
new file mode 100644
index 0000000000..8d52e4cea0
--- /dev/null
+++ b/plugins/CloudFile/src/cloud_service.h
@@ -0,0 +1,41 @@
+#ifndef _CLOUD_SERVICE_H_
+#define _CLOUD_SERVICE_H_
+
+class CCloudService
+{
+protected:
+ HNETLIBUSER hConnection;
+
+ // utils
+ char* PreparePath(const char *oldPath, char *newPath);
+ char* PreparePath(const wchar_t *oldPath, char *newPath);
+
+ virtual char* HttpStatusToError(int status = 0);
+ virtual void HandleHttpError(NETLIBHTTPREQUEST *response);
+ virtual void HandleJsonError(JSONNode &node) = 0;
+
+ JSONNode GetJsonResponse(NETLIBHTTPREQUEST *response);
+
+public:
+ std::map<MCONTACT, HWND> InterceptedContacts;
+
+ CCloudService(HNETLIBUSER hConnection);
+
+ virtual const char* GetModule() const = 0;
+ virtual const wchar_t* GetText() const = 0;
+ virtual HANDLE GetIcon() const = 0;
+
+ virtual bool IsLoggedIn() = 0;
+ virtual void Login() = 0;
+ virtual void Logout() = 0;
+
+ virtual UINT Upload(FileTransferParam *ftp) = 0;
+
+ 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);
+};
+
+#endif //_CLOUD_SERVICE_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/events.cpp b/plugins/CloudFile/src/events.cpp
new file mode 100644
index 0000000000..815a5f0b96
--- /dev/null
+++ b/plugins/CloudFile/src/events.cpp
@@ -0,0 +1,62 @@
+#include "stdafx.h"
+
+int OnModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_OPT_INITIALISE, OnOptionsInitialized);
+ // srfile
+ size_t count = Services.getCount();
+ for (size_t i = 0; i < count; i++) {
+ CCloudService *service = Services[i];
+
+ HookEventObj(ME_FILEDLG_CANCELED, OnFileDialogCanceled, service);
+ }
+ // menus
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, OnPrebuildContactMenu);
+ // srmm
+ HookEvent(ME_MSG_TOOLBARLOADED, OnSrmmToolbarLoaded);
+ HookEvent(ME_MSG_WINDOWEVENT, OnSrmmWindowOpened);
+ HookEvent(ME_MSG_BUTTONPRESSED, OnSrmmButtonPressed);
+
+ return 0;
+}
+
+int OnProtoAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+
+ if (!mir_strcmp(ack->szModule, MODULE))
+ return 0; // don't rebroadcast our own acks
+
+ if (ack->type == ACKTYPE_STATUS) {
+ WORD status = ack->lParam;
+ bool canSendOffline = (CallProtoService(ack->szModule, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0;
+
+ for (MCONTACT hContact = db_find_first(ack->szModule); hContact; hContact = db_find_next(hContact, ack->szModule)) {
+ MessageWindowData msgw;
+ if (!Srmm_GetWindowData(hContact, msgw) && msgw.uState & MSG_WINDOW_STATE_EXISTS) {
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULE;
+ bbd.dwButtonID = BBB_ID_FILE_SEND;
+ bbd.bbbFlags = BBSF_RELEASED;
+
+ if (status == ID_STATUS_OFFLINE && !canSendOffline)
+ bbd.bbbFlags = BBSF_DISABLED;
+
+ Srmm_SetButtonState(hContact, &bbd);
+ }
+ }
+ }
+
+ return 0;
+}
+
+int OnFileDialogCanceled(void* obj, WPARAM hContact, LPARAM)
+{
+ CCloudService *service = (CCloudService*)obj;
+
+ auto it = service->InterceptedContacts.find(hContact);
+ if (it != service->InterceptedContacts.end())
+ service->InterceptedContacts.erase(it);
+
+ return 0;
+}
diff --git a/plugins/CloudFile/src/file_transfer.h b/plugins/CloudFile/src/file_transfer.h
new file mode 100644
index 0000000000..16f515f856
--- /dev/null
+++ b/plugins/CloudFile/src/file_transfer.h
@@ -0,0 +1,245 @@
+#ifndef _FILE_TRANSFER_H_
+#define _FILE_TRANSFER_H_
+
+class FileTransferParam
+{
+private:
+ static ULONG hFileProcess;
+
+ ULONG id;
+ FILE *hFile;
+ PROTOFILETRANSFERSTATUS pfts;
+
+ bool isTerminated;
+
+ const wchar_t* folderName;
+ int relativePathStart;
+
+ CMStringW serverFolder;
+
+ CMStringW data;
+
+public:
+ FileTransferParam(MCONTACT hContact)
+ {
+ hFile = NULL;
+ id = InterlockedIncrement(&hFileProcess);
+
+ isTerminated = false;
+
+ folderName = NULL;
+ relativePathStart = 0;
+
+ pfts.cbSize = sizeof(this->pfts);
+ 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.ptszFiles = (wchar_t**)mir_alloc(sizeof(wchar_t*) * (pfts.totalFiles + 1));
+ pfts.ptszFiles[pfts.totalFiles] = NULL;
+ pfts.tszWorkingDir = NULL;
+ pfts.tszCurrentFile = NULL;
+
+ ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, (HANDLE)id, 0);
+ }
+
+ ~FileTransferParam()
+ {
+ CloseCurrentFile();
+
+ if (pfts.tszWorkingDir)
+ mir_free(pfts.tszWorkingDir);
+
+ if (pfts.pszFiles)
+ {
+ for (int i = 0; pfts.pszFiles[i]; i++)
+ {
+ if (pfts.pszFiles[i]) mir_free(pfts.pszFiles[i]);
+ }
+ mir_free(pfts.pszFiles);
+ }
+ }
+
+ ULONG GetId() const
+ {
+ return id;
+ }
+
+ MCONTACT GetHContact() const
+ {
+ return pfts.hContact;
+ }
+
+ const wchar_t* GetData() const
+ {
+ if (data.IsEmpty())
+ return NULL;
+ return data;
+ }
+
+ void Terminate()
+ {
+ isTerminated = true;
+ }
+
+ void SetWorkingDirectory(const wchar_t *path)
+ {
+ relativePathStart = wcsrchr(path, '\\') - path + 1;
+ pfts.tszWorkingDir = (wchar_t*)mir_calloc(sizeof(wchar_t) * relativePathStart);
+ mir_wstrncpy(pfts.tszWorkingDir, path, relativePathStart);
+ if (PathIsDirectory(path))
+ folderName = wcsrchr(path, '\\') + 1;
+ }
+
+ void SetServerFolder(const wchar_t *path)
+ {
+ if (path)
+ serverFolder = path;
+ }
+
+ const wchar_t* GetServerFolder() const
+ {
+ if (serverFolder.IsEmpty())
+ return NULL;
+ return serverFolder;
+ }
+
+ const wchar_t* GetFolderName() const
+ {
+ return folderName;
+ }
+
+ void AddFile(const wchar_t *path)
+ {
+ pfts.ptszFiles = (wchar_t**)mir_realloc(pfts.ptszFiles, sizeof(wchar_t*) * (pfts.totalFiles + 2));
+ pfts.ptszFiles[pfts.totalFiles++] = mir_wstrdup(path);
+ pfts.ptszFiles[pfts.totalFiles] = NULL;
+
+ FILE *file = _wfopen(path, L"rb");
+ if (file != NULL) {
+ _fseeki64(file, 0, SEEK_END);
+ pfts.totalBytes += _ftelli64(file);
+ fclose(file);
+ }
+ }
+
+ void AppendFormatData(const wchar_t *format, ...)
+ {
+ va_list args;
+ va_start(args, format);
+ data.AppendFormatV(format, args);
+ va_end(args);
+ }
+
+ const wchar_t* GetCurrentFilePath() const
+ {
+ return pfts.ptszFiles[pfts.currentFileNumber];
+ }
+
+ const wchar_t* GetCurrentRelativeFilePath() const
+ {
+ return &GetCurrentFilePath()[relativePathStart];
+ }
+
+ const wchar_t* GetCurrentFileName() const
+ {
+ return wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 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 uint64_t GetCurrentFileChunkSize() const
+ {
+ int 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;
+ ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&pfts);
+ }
+
+ void FirstFile()
+ {
+ CloseCurrentFile();
+
+ pfts.currentFileNumber = 0;
+ pfts.currentFileProgress = 0;
+ pfts.tszCurrentFile = wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 1;
+ ProtoBroadcastAck(MODULE, 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.tszCurrentFile = wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 1;
+ ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, (HANDLE)id, 0);
+
+ OpenCurrentFile();
+ CheckCurrentFile();
+
+ return true;
+ }
+
+ void SetStatus(int status, LPARAM param = 0)
+ {
+ ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, status, (HANDLE)id, param);
+ }
+};
+
+#endif //_FILE_TRANSFER_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/http_request.h b/plugins/CloudFile/src/http_request.h
new file mode 100644
index 0000000000..1308ae2291
--- /dev/null
+++ b/plugins/CloudFile/src/http_request.h
@@ -0,0 +1,166 @@
+#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_NODUMPSEND | NLHRF_DUMPASTEXT;
+ 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)
+ {
+ char cPair[128];
+ mir_snprintf(
+ cPair,
+ _countof(cPair),
+ "%s:%s",
+ szLogin,
+ szPassword);
+
+ char *ePair = (char *)mir_base64_encode((BYTE*)cPair, (UINT)mir_strlen(cPair));
+
+ char value[128];
+ mir_snprintf(
+ value,
+ _countof(value),
+ "Basic %s",
+ ePair);
+
+ mir_free(ePair);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = mir_strdup(value);
+ headersCount++;
+ }
+
+ void AddBearerAuthHeader(LPCSTR szValue)
+ {
+ char value[128];
+ mir_snprintf(
+ value,
+ _countof(value),
+ "Bearer %s",
+ szValue);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = mir_strdup(value);
+ headersCount++;
+ }
+
+ void AddOAuthHeader(LPCSTR szValue)
+ {
+ char value[128];
+ mir_snprintf(
+ value,
+ _countof(value),
+ "OAuth %s",
+ szValue);
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup("Authorization");
+ headers[headersCount].szValue = mir_strdup(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 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/plugins/CloudFile/src/icons.cpp b/plugins/CloudFile/src/icons.cpp
new file mode 100644
index 0000000000..5a3d562e18
--- /dev/null
+++ b/plugins/CloudFile/src/icons.cpp
@@ -0,0 +1,40 @@
+#include "stdafx.h"
+
+static IconItem iconList[] =
+{
+ { LPGEN("Upload file(s)"), "upload", IDI_UPLOAD },
+ { LPGEN("Dropbox"), "dropbox", IDI_DROPBOX },
+ { LPGEN("GDrive"), "gdrive", IDI_GDRIVE }
+};
+
+void InitializeIcons()
+{
+ Icon_Register(hInstance, "Protocols/" MODULE, iconList, _countof(iconList), MODULE);
+}
+
+HANDLE GetIconHandle(int iconId)
+{
+ for (int i = 0; i < _countof(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return iconList[i].hIcolib;
+
+ return NULL;
+}
+
+HANDLE GetIconHandle(const char *name)
+{
+ for (size_t i = 0; i < _countof(iconList); i++)
+ if (mir_strcmpi(iconList[i].szName, name) == 0)
+ return iconList[i].hIcolib;
+
+ return NULL;
+}
+
+HICON LoadIconEx(int iconId, bool big)
+{
+ for (int i = 0; i < _countof(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return IcoLib_GetIconByHandle(iconList[i].hIcolib, big);
+
+ return NULL;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/main.cpp b/plugins/CloudFile/src/main.cpp
new file mode 100644
index 0000000000..abdc3ba4ec
--- /dev/null
+++ b/plugins/CloudFile/src/main.cpp
@@ -0,0 +1,65 @@
+#include "stdafx.h"
+
+int hLangpack;
+HINSTANCE hInstance;
+HNETLIBUSER hNetlibConnection;
+
+PLUGININFOEX pluginInfo =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __AUTHOREMAIL,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {E876FE63-0701-4CDA-BED5-7C73A379C1D1}
+ { 0xe876fe63, 0x701, 0x4cda, { 0xbe, 0xd5, 0x7c, 0x73, 0xa3, 0x79, 0xc1, 0xd1 }}
+};
+
+DWORD WINAPI DllMain(HINSTANCE hInst, DWORD, LPVOID)
+{
+ hInstance = hInst;
+ return TRUE;
+}
+
+extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD)
+{
+ return &pluginInfo;
+}
+
+extern "C" int __declspec(dllexport) Load(void)
+{
+ mir_getLP(&pluginInfo);
+
+ PROTOCOLDESCRIPTOR pd = { sizeof(pd) };
+ pd.szName = MODULE;
+ pd.type = PROTOTYPE_VIRTUAL;
+ Proto_RegisterModule(&pd);
+
+ CreateServiceFunction(MODULE PS_GETCAPS, ProtoGetCaps);
+ CreateServiceFunction(MODULE PS_GETNAME, ProtoGetName);
+ CreateServiceFunction(MODULE PS_LOADICON, ProtoLoadIcon);
+
+ HookEvent(ME_PROTO_ACK, OnProtoAck);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = MODULE;
+ nlu.szDescriptiveName.w = _A2W("MODULE");
+ hNetlibConnection = Netlib_RegisterUser(&nlu);
+
+ InitServices();
+ InitializeIcons();
+ InitializeMenus();
+
+ return 0;
+}
+
+extern "C" int __declspec(dllexport) Unload(void)
+{
+ return 0;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/menus.cpp b/plugins/CloudFile/src/menus.cpp
new file mode 100644
index 0000000000..d9a6db9b3e
--- /dev/null
+++ b/plugins/CloudFile/src/menus.cpp
@@ -0,0 +1,65 @@
+#include "stdafx.h"
+
+HGENMENU hContactMenu;
+
+INT_PTR UploadMenuCommand(void *obj, WPARAM hContact, LPARAM)
+{
+ CCloudService *service = (CCloudService*)obj;
+
+ auto it = service->InterceptedContacts.find(hContact);
+ if (it == service->InterceptedContacts.end())
+ {
+ HWND hwnd = (HWND)CallService(MS_FILE_SENDFILE, hContact, 0);
+ service->InterceptedContacts[hContact] = hwnd;
+ }
+ else
+ SetActiveWindow(it->second);
+
+ return 0;
+}
+
+void InitializeMenus()
+{
+ CMenuItem mi;
+ SET_UID(mi, 0x93d4495b, 0x259b, 0x4fba, 0xbc, 0x14, 0xf9, 0x46, 0x2c, 0xda, 0xfc, 0x6d);
+ mi.name.a = LPGEN("Upload files to ...");
+ mi.position = -2000020001;
+ mi.hIcon = LoadIconEx(IDI_UPLOAD);
+ hContactMenu = Menu_AddContactMenuItem(&mi);
+
+ UNSET_UID(mi);
+ mi.flags |= CMIF_SYSTEM | CMIF_UNICODE;
+ mi.root = hContactMenu;
+
+ size_t count = Services.getCount();
+ for (size_t i = 0; i < count; i++) {
+ CCloudService *service = Services[i];
+
+ CMStringA serviceName(CMStringDataFormat::FORMAT, "%s/%s/Upload", MODULE, service->GetModule());
+ mi.pszService = serviceName.GetBuffer();
+ mi.name.w = (wchar_t*)service->GetText();
+ mi.position = i;
+ mi.hIcolibItem = Services[i]->GetIcon();
+ Menu_AddContactMenuItem(&mi);
+ CreateServiceFunctionObj(mi.pszService, UploadMenuCommand, service);
+ }
+}
+
+int OnPrebuildContactMenu(WPARAM hContact, LPARAM)
+{
+ bool bShow = false;
+ char *proto = GetContactProto(hContact);
+ if (proto) {
+ bool bHasIM = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND) != 0;
+ if (bHasIM) {
+ bool isProtoOnline = CallProtoService(proto, PS_GETSTATUS, 0, 0) > ID_STATUS_OFFLINE;
+ WORD status = db_get_w(hContact, proto, "Status", ID_STATUS_OFFLINE);
+ bool canSendOffline = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0;
+ if (isProtoOnline && (status != ID_STATUS_OFFLINE || canSendOffline))
+ bShow = true;
+ }
+ }
+ Menu_ShowItem(hContactMenu, bShow);
+
+ return 0;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/oauth.cpp b/plugins/CloudFile/src/oauth.cpp
new file mode 100644
index 0000000000..7815c27771
--- /dev/null
+++ b/plugins/CloudFile/src/oauth.cpp
@@ -0,0 +1,23 @@
+#include "stdafx.h"
+
+COAuthDlg::COAuthDlg(CCloudService *service, const char *authUrl, pThreadFuncOwner requestAccessTokenThread)
+ : CDlgBase(hInstance, IDD_OAUTH), m_service(service),
+ m_requestAccessTokenThread(requestAccessTokenThread),
+ m_auth(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);
+}
+
+void COAuthDlg::Code_OnChange(CCtrlBase*)
+{
+ ptrA requestToken(m_code.GetTextA());
+ m_ok.Enable(mir_strlen(requestToken) != 0);
+}
+
+void COAuthDlg::Ok_OnClick(CCtrlButton*)
+{
+ mir_forkthreadowner(m_requestAccessTokenThread, m_service, m_hwnd);
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/oauth.h b/plugins/CloudFile/src/oauth.h
new file mode 100644
index 0000000000..434927f791
--- /dev/null
+++ b/plugins/CloudFile/src/oauth.h
@@ -0,0 +1,22 @@
+#ifndef _OAUTH_H_
+#define _OAUTH_H_
+
+class COAuthDlg : public CDlgBase
+{
+private:
+ CCloudService *m_service;
+ pThreadFuncOwner m_requestAccessTokenThread;
+
+ CCtrlHyperlink m_auth;
+ CCtrlEdit m_code;
+ CCtrlButton m_ok;
+
+protected:
+ void Code_OnChange(CCtrlBase*);
+ void Ok_OnClick(CCtrlButton*);
+
+public:
+ COAuthDlg(CCloudService *service, const char *authUrl, pThreadFuncOwner requestAccessTokenThread);
+};
+
+#endif //_OAUTH_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/options.cpp b/plugins/CloudFile/src/options.cpp
new file mode 100644
index 0000000000..48a0f3c995
--- /dev/null
+++ b/plugins/CloudFile/src/options.cpp
@@ -0,0 +1,33 @@
+#include "stdafx.h"
+
+COptionsMain::COptionsMain()
+ : CPluginDlgBase(hInstance, IDD_OPTIONS_MAIN, MODULE),
+ m_urlAutoSend(this, IDC_URL_AUTOSEND),
+ m_urlPasteToMessageInputArea(this, IDC_URL_COPYTOMIA),
+ m_urlCopyToClipboard(this, IDC_URL_COPYTOCB)
+{
+ CreateLink(m_urlAutoSend, "UrlAutoSend", DBVT_BYTE, 1);
+ CreateLink(m_urlPasteToMessageInputArea, "UrlPasteToMessageInputArea", DBVT_BYTE, 0);
+ CreateLink(m_urlCopyToClipboard, "UrlCopyToClipboard", DBVT_BYTE, 0);
+}
+
+void COptionsMain::OnInitDialog()
+{
+ CDlgBase::OnInitDialog();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+int OnOptionsInitialized(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.szTitle.w = _A2W(MODULE);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_UNICODE | ODPF_DONTTRANSLATE;
+ odp.szGroup.w = LPGENW("Network");
+
+ odp.szTab.w = LPGENW("General");
+ odp.pDialog = new COptionsMain();
+ Options_AddPage(wParam, &odp);
+
+ return 0;
+}
diff --git a/plugins/CloudFile/src/options.h b/plugins/CloudFile/src/options.h
new file mode 100644
index 0000000000..3fb20157b1
--- /dev/null
+++ b/plugins/CloudFile/src/options.h
@@ -0,0 +1,18 @@
+#ifndef _OPTIONS_H_
+#define _OPTIONS_H_
+
+class COptionsMain : public CPluginDlgBase
+{
+private:
+ CCtrlCheck m_urlAutoSend;
+ CCtrlCheck m_urlPasteToMessageInputArea;
+ CCtrlCheck m_urlCopyToClipboard;
+
+protected:
+ void OnInitDialog();
+
+public:
+ COptionsMain();
+};
+
+#endif //_OPTIONS_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/proto.cpp b/plugins/CloudFile/src/proto.cpp
new file mode 100644
index 0000000000..9698534c50
--- /dev/null
+++ b/plugins/CloudFile/src/proto.cpp
@@ -0,0 +1,32 @@
+#include "stdafx.h"
+
+INT_PTR ProtoGetCaps(WPARAM wParam, LPARAM)
+{
+ switch (wParam) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_FILESEND;
+ case PFLAGNUM_2:
+ return PF2_ONLINE;
+ case PFLAGNUM_4:
+ return PF4_OFFLINEFILES;
+ }
+
+ return 0;
+}
+
+INT_PTR ProtoGetName(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam) {
+ mir_strncpy((char *)lParam, MODULE, wParam);
+ return 0;
+ }
+
+ return 1;
+}
+
+INT_PTR ProtoLoadIcon(WPARAM wParam, LPARAM)
+{
+ return (LOWORD(wParam) == PLI_PROTOCOL)
+ ? (INT_PTR)CopyIcon(LoadIconEx(IDI_UPLOAD))
+ : 0;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/resource.h b/plugins/CloudFile/src/resource.h
new file mode 100644
index 0000000000..70a9fd5629
--- /dev/null
+++ b/plugins/CloudFile/src/resource.h
@@ -0,0 +1,30 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by c:\Users\unsane\Projects\c++\miranda-ng\plugins\CloudFile\res\resource.rc
+//
+#define IDOK 1
+#define IDCANCEL 2
+#define IDI_UPLOAD 105
+#define IDI_DROPBOX 106
+#define IDI_ICON1 108
+#define IDI_GDRIVE 108
+#define IDD_OPTIONS_MAIN 109
+#define IDD_OAUTH 120
+#define IDC_URL_ISTEMPORARY 1004
+#define IDC_URL_COPYTOCB 1009
+#define IDC_URL_COPYTOMIA 1029
+#define IDC_URL_AUTOSEND 1030
+#define IDC_OAUTH_CODE 1082
+#define IDC_OAUTH_AUTHORIZE 1200
+#define IDC_STATIC -1
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 109
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1013
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/plugins/CloudFile/src/srmm.cpp b/plugins/CloudFile/src/srmm.cpp
new file mode 100644
index 0000000000..e903d90721
--- /dev/null
+++ b/plugins/CloudFile/src/srmm.cpp
@@ -0,0 +1,77 @@
+#include "stdafx.h"
+
+int OnSrmmToolbarLoaded(WPARAM, LPARAM)
+{
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULE;
+ bbd.bbbFlags = BBBF_ISIMBUTTON | BBBF_ISCHATBUTTON | BBBF_ISRSIDEBUTTON;
+
+ CMStringW tooltip(CMStringDataFormat::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);
+
+ return 0;
+}
+
+int OnSrmmWindowOpened(WPARAM, LPARAM lParam)
+{
+ MessageWindowEventData *ev = (MessageWindowEventData*)lParam;
+ if (ev->uType == MSG_WINDOW_EVT_OPENING && ev->hContact) {
+ char *proto = GetContactProto(ev->hContact);
+ bool isProtoOnline = CallProtoService(proto, PS_GETSTATUS, 0, 0) > ID_STATUS_OFFLINE;
+ WORD status = db_get_w(ev->hContact, proto, "Status", ID_STATUS_OFFLINE);
+ bool canSendOffline = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0;
+
+ BBButton bbd = {};
+ bbd.pszModuleName = MODULE;
+ bbd.dwButtonID = BBB_ID_FILE_SEND;
+ bbd.bbbFlags = BBSF_RELEASED;
+ if (!isProtoOnline || (status == ID_STATUS_OFFLINE && !canSendOffline))
+ bbd.bbbFlags = BBSF_DISABLED;
+
+ //Srmm_SetButtonState(ev->hContact, &bbd);
+ }
+
+ return 0;
+}
+
+int OnSrmmButtonPressed(WPARAM, LPARAM lParam)
+{
+ CustomButtonClickData *cbc = (CustomButtonClickData*)lParam;
+
+ if (mir_strcmp(cbc->pszModule, MODULE))
+ return 0;
+
+ if (cbc->dwButtonId != BBB_ID_FILE_SEND)
+ return 0;
+
+ HMENU hMenu = CreatePopupMenu();
+
+ size_t count = Services.getCount();
+ for (size_t i = 0; i < count; i++) {
+ CCloudService *service = Services[i];
+
+ InsertMenu(hMenu, i, MF_STRING, i + 1, service->GetText());
+ //HBITMAP hBitmap = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDI_UPLOAD), IMAGE_ICON, 16, 16, 0);
+ //SetMenuItemBitmaps(hMenu, i, MF_BITMAP, hBitmap, hBitmap);
+ }
+
+ int ind = TrackPopupMenu(hMenu, TPM_RETURNCMD, cbc->pt.x, cbc->pt.y, 0, cbc->hwndFrom, NULL);
+ if (ind > 0) {
+ CCloudService *service = Services[ind - 1];
+
+ auto it = service->InterceptedContacts.find(cbc->hContact);
+ if (it == service->InterceptedContacts.end())
+ {
+ HWND hwnd = (HWND)CallService(MS_FILE_SENDFILE, cbc->hContact, 0);
+ service->InterceptedContacts[cbc->hContact] = hwnd;
+ }
+ else
+ SetActiveWindow(it->second);
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/stdafx.cxx b/plugins/CloudFile/src/stdafx.cxx
new file mode 100644
index 0000000000..d8d6847175
--- /dev/null
+++ b/plugins/CloudFile/src/stdafx.cxx
@@ -0,0 +1,20 @@
+/*
+Copyright (C) 2012-17 Miranda NG project (http://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/plugins/CloudFile/src/stdafx.h b/plugins/CloudFile/src/stdafx.h
new file mode 100644
index 0000000000..4cbb6eace4
--- /dev/null
+++ b/plugins/CloudFile/src/stdafx.h
@@ -0,0 +1,115 @@
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <windows.h>
+#include <shlwapi.h>
+#include <commctrl.h>
+
+#include <malloc.h>
+#include <time.h>
+
+#include <map>
+
+#include <newpluginapi.h>
+
+#include <m_options.h>
+#include <m_database.h>
+#include <m_netlib.h>
+#include <m_clist.h>
+#include <m_skin.h>
+#include <m_icolib.h>
+#include <m_popup.h>
+#include <m_file.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_string.h>
+#include <m_gui.h>
+#include <m_chat.h>
+#include <m_http.h>
+#include <m_json.h>
+
+#include <m_protoint.h>
+#include <m_protosvc.h>
+
+#include "version.h"
+#include "resource.h"
+#include "options.h"
+
+extern HINSTANCE hInstance;
+extern HNETLIBUSER hNetlibConnection;
+
+class Exception
+{
+ CMStringA message;
+
+public:
+ Exception(const char *message) :
+ message(message)
+ {
+ }
+
+ const char* what() const throw()
+ {
+ return message.c_str();
+ }
+};
+
+#define MODULE "CloudFile"
+
+#define FILE_CHUNK_SIZE 1024 * 1024 //1 MB
+
+#include "http_request.h"
+#include "file_transfer.h"
+
+// services
+#include "cloud_service.h"
+#include "oauth.h"
+#include "Services\dropbox_service.h"
+#include "Services\google_service.h"
+#include "Services\yandex_service.h"
+extern LIST<CCloudService> Services;
+void InitServices();
+
+// proto
+INT_PTR ProtoGetCaps(WPARAM, LPARAM);
+INT_PTR ProtoGetName(WPARAM, LPARAM);
+INT_PTR ProtoLoadIcon(WPARAM, LPARAM);
+
+// events
+int OnModulesLoaded(WPARAM, LPARAM);
+int OnProtoAck(WPARAM, LPARAM);
+int OnFileDialogCanceled(void* obj, WPARAM hContact, 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 ProtoSendFile(void *obj, WPARAM, LPARAM lParam);
+INT_PTR ProtoSendFileInterceptor(void *obj, WPARAM wParam, LPARAM lParam);
+INT_PTR ProtoCancelFile(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);
+MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD flags, DWORD cbBlob, PBYTE pBlob);
+
+#endif //_COMMON_H_ \ No newline at end of file
diff --git a/plugins/CloudFile/src/transfers.cpp b/plugins/CloudFile/src/transfers.cpp
new file mode 100644
index 0000000000..0f79f970d9
--- /dev/null
+++ b/plugins/CloudFile/src/transfers.cpp
@@ -0,0 +1,69 @@
+#include "stdafx.h"
+
+LIST<FileTransferParam> Transfers(1, HandleKeySortT);
+
+INT_PTR ProtoSendFile(void *obj, WPARAM, LPARAM lParam)
+{
+ CCSDATA *pccsd = (CCSDATA*)lParam;
+ CCloudService *service = (CCloudService*)obj;
+
+ FileTransferParam *ftp = new FileTransferParam(pccsd->hContact);
+
+ const wchar_t *description = (wchar_t*)pccsd->wParam;
+ if (description && description[0])
+ ftp->AppendFormatData(L"%s\r\n", (wchar_t*)pccsd->wParam);
+
+ wchar_t **paths = (wchar_t**)pccsd->lParam;
+ 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, service, ftp);
+
+ return ftp->GetId();
+}
+
+INT_PTR ProtoSendFileInterceptor(void *obj, WPARAM wParam, LPARAM lParam)
+{
+ CCSDATA *pccsd = (CCSDATA*)lParam;
+ CCloudService *service = (CCloudService*)obj;
+
+ auto it = service->InterceptedContacts.find(pccsd->hContact);
+ if (it == service->InterceptedContacts.end())
+ return CALLSERVICE_NOTFOUND;
+ service->InterceptedContacts.erase(it);
+
+ return ProtoSendFile(obj, wParam, lParam);
+}
+
+INT_PTR ProtoCancelFile(WPARAM, LPARAM lParam)
+{
+ CCSDATA *pccsd = (CCSDATA*)lParam;
+
+ HANDLE hTransfer = (HANDLE)pccsd->wParam;
+ FileTransferParam *ftp = Transfers.find((FileTransferParam*)&hTransfer);
+ if (ftp)
+ ftp->Terminate();
+
+ return 0;
+}
+
+UINT UploadAndReportProgressThread(void *owner, void *arg)
+{
+ CCloudService *service = (CCloudService*)owner;
+ FileTransferParam *ftp = (FileTransferParam*)arg;
+
+ int res = service->Upload(ftp);
+ if (res == ACKRESULT_SUCCESS)
+ service->Report(ftp->GetHContact(), ftp->GetData());
+
+ Transfers.remove(ftp);
+ delete ftp;
+
+ return res;
+} \ No newline at end of file
diff --git a/plugins/CloudFile/src/utils.cpp b/plugins/CloudFile/src/utils.cpp
new file mode 100644
index 0000000000..cabcd7de97
--- /dev/null
+++ b/plugins/CloudFile/src/utils.cpp
@@ -0,0 +1,40 @@
+#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_ADDPOPUPT) && db_get_b(NULL, "Popup", "ModuleIsEnabled", 1))
+ {
+ POPUPDATAT ppd = { 0 };
+ ppd.lchContact = hContact;
+ wcsncpy(ppd.lpwzContactName, caption, MAX_CONTACTNAME);
+ wcsncpy(ppd.lpwzText, message, MAX_SECONDLINE);
+ ppd.lchIcon = IcoLib_GetIcon("Slack_main");
+
+ if (!PUAddPopupT(&ppd))
+ return;
+ }
+
+ MessageBox(NULL, message, caption, MB_OK | flags);
+}
+
+void ShowNotification(const wchar_t *message, int flags, MCONTACT hContact)
+{
+ ShowNotification(_A2W(MODULE), message, flags, hContact);
+}
+
+MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD flags, DWORD cbBlob, PBYTE pBlob)
+{
+ DBEVENTINFO dbei = {};
+ dbei.szModule = MODULE;
+ dbei.timestamp = time(NULL);
+ dbei.eventType = type;
+ dbei.cbBlob = cbBlob;
+ dbei.pBlob = pBlob;
+ dbei.flags = flags;
+ return db_event_add(hContact, &dbei);
+}
diff --git a/plugins/CloudFile/src/version.h b/plugins/CloudFile/src/version.h
new file mode 100644
index 0000000000..9351118eb8
--- /dev/null
+++ b/plugins/CloudFile/src/version.h
@@ -0,0 +1,14 @@
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 11
+#define __RELEASE_NUM 0
+#define __BUILD_NUM 1
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "CloudFile"
+#define __FILENAME "CloudFile.dll"
+#define __DESCRIPTION "Allows you to upload files via cloud services."
+#define __AUTHOR "Miranda NG Team"
+#define __AUTHOREMAIL ""
+#define __AUTHORWEB "http://miranda-ng.org/p/CloudFile/"
+#define __COPYRIGHT "© 2017 Miranda NG project"