From 0b9fa1d90f8d0aff7118837ceb1211b578a5a9c8 Mon Sep 17 00:00:00 2001
From: aunsane <aunsane@gmail.com>
Date: Sun, 16 Apr 2017 01:32:19 +0300
Subject: CloudFile: initial commit - Dropbox (worked) - Yandex.Disk (worked) -
 GDrive (not worked)

---
 plugins/CloudFile/CloudFile.vcxproj                |  47 ++++
 plugins/CloudFile/CloudFile.vcxproj.filters        |   4 +
 plugins/CloudFile/res/dropbox.ico                  | Bin 0 -> 5430 bytes
 plugins/CloudFile/res/gdrive.ico                   | Bin 0 -> 5430 bytes
 plugins/CloudFile/res/resource.rc                  | 163 ++++++++++++
 plugins/CloudFile/res/upload.ico                   | Bin 0 -> 103999 bytes
 plugins/CloudFile/res/version.rc                   |  38 +++
 plugins/CloudFile/src/Services/dropbox_api.h       | 200 +++++++++++++++
 plugins/CloudFile/src/Services/dropbox_service.cpp | 277 +++++++++++++++++++++
 plugins/CloudFile/src/Services/dropbox_service.h   |  33 +++
 plugins/CloudFile/src/Services/google_api.h        | 101 ++++++++
 plugins/CloudFile/src/Services/google_service.cpp  | 216 ++++++++++++++++
 plugins/CloudFile/src/Services/google_service.h    |  31 +++
 plugins/CloudFile/src/Services/yandex_api.h        |  84 +++++++
 plugins/CloudFile/src/Services/yandex_service.cpp  | 219 ++++++++++++++++
 plugins/CloudFile/src/Services/yandex_service.h    |  31 +++
 plugins/CloudFile/src/cloud_service.cpp            | 164 ++++++++++++
 plugins/CloudFile/src/cloud_service.h              |  41 +++
 plugins/CloudFile/src/events.cpp                   |  62 +++++
 plugins/CloudFile/src/file_transfer.h              | 245 ++++++++++++++++++
 plugins/CloudFile/src/http_request.h               | 166 ++++++++++++
 plugins/CloudFile/src/icons.cpp                    |  40 +++
 plugins/CloudFile/src/main.cpp                     |  65 +++++
 plugins/CloudFile/src/menus.cpp                    |  65 +++++
 plugins/CloudFile/src/oauth.cpp                    |  23 ++
 plugins/CloudFile/src/oauth.h                      |  22 ++
 plugins/CloudFile/src/options.cpp                  |  33 +++
 plugins/CloudFile/src/options.h                    |  18 ++
 plugins/CloudFile/src/proto.cpp                    |  32 +++
 plugins/CloudFile/src/resource.h                   |  30 +++
 plugins/CloudFile/src/srmm.cpp                     |  77 ++++++
 plugins/CloudFile/src/stdafx.cxx                   |  20 ++
 plugins/CloudFile/src/stdafx.h                     | 115 +++++++++
 plugins/CloudFile/src/transfers.cpp                |  69 +++++
 plugins/CloudFile/src/utils.cpp                    |  40 +++
 plugins/CloudFile/src/version.h                    |  14 ++
 36 files changed, 2785 insertions(+)
 create mode 100644 plugins/CloudFile/CloudFile.vcxproj
 create mode 100644 plugins/CloudFile/CloudFile.vcxproj.filters
 create mode 100644 plugins/CloudFile/res/dropbox.ico
 create mode 100644 plugins/CloudFile/res/gdrive.ico
 create mode 100644 plugins/CloudFile/res/resource.rc
 create mode 100644 plugins/CloudFile/res/upload.ico
 create mode 100644 plugins/CloudFile/res/version.rc
 create mode 100644 plugins/CloudFile/src/Services/dropbox_api.h
 create mode 100644 plugins/CloudFile/src/Services/dropbox_service.cpp
 create mode 100644 plugins/CloudFile/src/Services/dropbox_service.h
 create mode 100644 plugins/CloudFile/src/Services/google_api.h
 create mode 100644 plugins/CloudFile/src/Services/google_service.cpp
 create mode 100644 plugins/CloudFile/src/Services/google_service.h
 create mode 100644 plugins/CloudFile/src/Services/yandex_api.h
 create mode 100644 plugins/CloudFile/src/Services/yandex_service.cpp
 create mode 100644 plugins/CloudFile/src/Services/yandex_service.h
 create mode 100644 plugins/CloudFile/src/cloud_service.cpp
 create mode 100644 plugins/CloudFile/src/cloud_service.h
 create mode 100644 plugins/CloudFile/src/events.cpp
 create mode 100644 plugins/CloudFile/src/file_transfer.h
 create mode 100644 plugins/CloudFile/src/http_request.h
 create mode 100644 plugins/CloudFile/src/icons.cpp
 create mode 100644 plugins/CloudFile/src/main.cpp
 create mode 100644 plugins/CloudFile/src/menus.cpp
 create mode 100644 plugins/CloudFile/src/oauth.cpp
 create mode 100644 plugins/CloudFile/src/oauth.h
 create mode 100644 plugins/CloudFile/src/options.cpp
 create mode 100644 plugins/CloudFile/src/options.h
 create mode 100644 plugins/CloudFile/src/proto.cpp
 create mode 100644 plugins/CloudFile/src/resource.h
 create mode 100644 plugins/CloudFile/src/srmm.cpp
 create mode 100644 plugins/CloudFile/src/stdafx.cxx
 create mode 100644 plugins/CloudFile/src/stdafx.h
 create mode 100644 plugins/CloudFile/src/transfers.cpp
 create mode 100644 plugins/CloudFile/src/utils.cpp
 create mode 100644 plugins/CloudFile/src/version.h

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
Binary files /dev/null and b/plugins/CloudFile/res/dropbox.ico differ
diff --git a/plugins/CloudFile/res/gdrive.ico b/plugins/CloudFile/res/gdrive.ico
new file mode 100644
index 0000000000..a34123e95b
Binary files /dev/null and b/plugins/CloudFile/res/gdrive.ico 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
Binary files /dev/null and b/plugins/CloudFile/res/upload.ico 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"
-- 
cgit v1.2.3