diff --git a/GreenshotDropboxPlugin/DropboxDestination.cs b/GreenshotDropboxPlugin/DropboxDestination.cs index 261ead03d..8b14de6cf 100644 --- a/GreenshotDropboxPlugin/DropboxDestination.cs +++ b/GreenshotDropboxPlugin/DropboxDestination.cs @@ -1,20 +1,20 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * 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, either version 1 of the License, or * (at your option) any later version. - * + * * 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 . */ @@ -32,7 +32,7 @@ namespace GreenshotDropboxPlugin { public DropboxDestination(DropboxPlugin plugin) { _plugin = plugin; } - + public override string Designation => "Dropbox"; public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item); @@ -43,8 +43,8 @@ namespace GreenshotDropboxPlugin { return (Image)resources.GetObject("Dropbox"); } } - - public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) { + + public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) { ExportInformation exportInformation = new ExportInformation(Designation, Description); bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); if (uploaded) { diff --git a/GreenshotDropboxPlugin/DropboxPlugin.cs b/GreenshotDropboxPlugin/DropboxPlugin.cs index 3ee7b73cc..bf8842ecb 100644 --- a/GreenshotDropboxPlugin/DropboxPlugin.cs +++ b/GreenshotDropboxPlugin/DropboxPlugin.cs @@ -1,27 +1,26 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * 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, either version 1 of the License, or * (at your option) any later version. - * + * * 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 . */ using System; using System.ComponentModel; using System.Drawing; -using System.IO; using System.Windows.Forms; using GreenshotPlugin.Controls; using GreenshotPlugin.Core; @@ -102,20 +101,16 @@ namespace GreenshotDropboxPlugin { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { uploadUrl = null; SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); - try { - string dropboxUrl = null; - new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), + try + { + bool result = false; + new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), delegate { - string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); - dropboxUrl = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, filename); + result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails); } ); - if (dropboxUrl == null) { - return false; - } - uploadUrl = dropboxUrl; - return true; + return result; } catch (Exception e) { Log.Error(e); MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message); diff --git a/GreenshotDropboxPlugin/DropboxUtils.cs b/GreenshotDropboxPlugin/DropboxUtils.cs index c864c8cd3..eeb2b6405 100644 --- a/GreenshotDropboxPlugin/DropboxUtils.cs +++ b/GreenshotDropboxPlugin/DropboxUtils.cs @@ -20,11 +20,13 @@ */ using System; using System.Collections.Generic; +using System.IO; using GreenshotPlugin.Core; using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; +using Newtonsoft.Json; namespace GreenshotDropboxPlugin { /// @@ -37,11 +39,11 @@ namespace GreenshotDropboxPlugin { private DropboxUtils() { } - public static string UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string filename) { - + public static bool UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, ICaptureDetails captureDetails) + { var oauth2Settings = new OAuth2Settings { - AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}&token_access_type=offline", + AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}", TokenUrl = "https://api.dropbox.com/oauth2/token", RedirectUrl = "https://getgreenshot.org/authorize/dropbox", CloudServiceName = "Dropbox", @@ -55,20 +57,28 @@ namespace GreenshotDropboxPlugin { try { + string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails)); SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename); - IDictionary parameters = new Dictionary + IDictionary arguments = new Dictionary { - { "file", image }, { "autorename", true }, - { "mute", true}, - { "path", filename} + { "mute", true }, + { "path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')} }; - var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://api.dropbox.com//2/files/upload", oauth2Settings); - NetworkHelper.WriteMultipartFormData(webRequest, parameters); - var response = NetworkHelper.GetResponseAsString(webRequest); - Log.DebugFormat("Upload response: {0}", response); - } catch (Exception ex) { + IDictionary headers = new Dictionary + { + { "Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)} + }; + var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://content.dropboxapi.com/2/files/upload", oauth2Settings); + + NetworkHelper.Post(webRequest, headers, image); + var responseString = NetworkHelper.GetResponseAsString(webRequest); + Log.DebugFormat("Upload response: {0}", responseString); + var response = JsonConvert.DeserializeObject>(responseString); + return response.ContainsKey("id"); + } + catch (Exception ex) { Log.Error("Upload error: ", ex); throw; } finally { @@ -78,7 +88,6 @@ namespace GreenshotDropboxPlugin { DropboxConfig.IsDirty = true; IniConfig.Save(); } - return null; - } + } } } diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index c3fcff8ab..8d275cf21 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -358,6 +358,84 @@ namespace GreenshotPlugin.Core { WriteMultipartFormData(formDataStream, boundary, postParameters); } + + /// + /// Post content HttpWebRequest + /// + /// HttpWebRequest to write the multipart form data to + /// IDictionary with the headers + /// IBinaryContainer + public static void Post(HttpWebRequest webRequest, IDictionary headers, IBinaryContainer binaryContainer = null) + { + foreach (var header in headers) + { + switch (header.Key) + { + case "Content-Type": + webRequest.ContentType = header.Value as string; + break; + case "Accept": + webRequest.Accept = header.Value as string; + break; + default: + webRequest.Headers.Add(header.Key, Convert.ToString(header.Value)); + break; + } + } + if (!headers.ContainsKey("Content-Type")) + { + webRequest.ContentType = "application/octet-stream"; + } + + if (binaryContainer != null) + { + using var requestStream = webRequest.GetRequestStream(); + binaryContainer.WriteToStream(requestStream); + } + } + + /// + /// Post content HttpWebRequest + /// + /// HttpWebRequest to write the multipart form data to + /// IDictionary with the headers + /// string + public static void Post(HttpWebRequest webRequest, IDictionary headers, string jsonString) + { + if (headers != null) + { + foreach (var header in headers) + { + switch (header.Key) + { + case "Content-Type": + webRequest.ContentType = header.Value as string; + break; + case "Accept": + webRequest.Accept = header.Value as string; + break; + default: + webRequest.Headers.Add(header.Key, Convert.ToString(header.Value)); + break; + } + } + if (!headers.ContainsKey("Content-Type")) + { + webRequest.ContentType = "application/json"; + } + } + else + { + webRequest.ContentType = "application/json"; + } + + if (jsonString != null) + { + using var requestStream = webRequest.GetRequestStream(); + using var streamWriter = new StreamWriter(requestStream); + streamWriter.Write(jsonString); + } + } /// /// Write Multipart Form Data to the HttpListenerResponse /// diff --git a/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs b/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs index 2436fb90d..b80d62b3d 100644 --- a/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs +++ b/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs @@ -51,7 +51,6 @@ namespace GreenshotPlugin.Core.OAuth { // Use the returned code to get a refresh code { Code, settings.Code }, { ClientId, settings.ClientId }, - { RedirectUri, settings.RedirectUrl }, { ClientSecret, settings.ClientSecret }, { GrantType, AuthorizationCode } }; @@ -194,29 +193,29 @@ namespace GreenshotPlugin.Core.OAuth { } /// - /// Authenticate by using the mode specified in the settings + /// Authorize by using the mode specified in the settings /// /// OAuth2Settings /// false if it was canceled, true if it worked, exception if not - public static bool Authenticate(OAuth2Settings settings) { + public static bool Authorize(OAuth2Settings settings) { var completed = settings.AuthorizeMode switch { - OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings), - OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings), - OAuth2AuthorizeMode.JsonReceiver => AuthenticateViaDefaultBrowser(settings), + OAuth2AuthorizeMode.LocalServer => AuthorizeViaLocalServer(settings), + OAuth2AuthorizeMode.EmbeddedBrowser => AuthorizeViaEmbeddedBrowser(settings), + OAuth2AuthorizeMode.JsonReceiver => AuthorizeViaDefaultBrowser(settings), _ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."), }; return completed; } /// - /// Authenticate via the default browser, via the Greenshot website. + /// Authorize via the default browser, via the Greenshot website. /// It will wait for a Json post. /// If this works, return the code /// /// OAuth2Settings with the Auth / Token url etc /// true if completed, false if canceled - private static bool AuthenticateViaDefaultBrowser(OAuth2Settings settings) + private static bool AuthorizeViaDefaultBrowser(OAuth2Settings settings) { var codeReceiver = new LocalJsonReceiver(); IDictionary result = codeReceiver.ReceiveCode(settings); @@ -258,20 +257,21 @@ namespace GreenshotPlugin.Core.OAuth { } if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) { - settings.Code = code; + settings.Code = code; GenerateRefreshToken(settings); + return !string.IsNullOrEmpty(settings.AccessToken); } return true; } /// - /// Authenticate via an embedded browser + /// Authorize via an embedded browser /// If this works, return the code /// /// OAuth2Settings with the Auth / Token url etc /// true if completed, false if canceled - private static bool AuthenticateViaEmbeddedBrowser(OAuth2Settings settings) { + private static bool AuthorizeViaEmbeddedBrowser(OAuth2Settings settings) { if (string.IsNullOrEmpty(settings.CloudServiceName)) { throw new ArgumentNullException(nameof(settings.CloudServiceName)); } @@ -290,12 +290,12 @@ namespace GreenshotPlugin.Core.OAuth { } /// - /// Authenticate via a local server by using the LocalServerCodeReceiver + /// Authorize via a local server by using the LocalServerCodeReceiver /// If this works, return the code /// /// OAuth2Settings with the Auth / Token url etc /// true if completed - private static bool AuthenticateViaLocalServer(OAuth2Settings settings) { + private static bool AuthorizeViaLocalServer(OAuth2Settings settings) { var codeReceiver = new LocalServerCodeReceiver(); IDictionary result = codeReceiver.ReceiveCode(settings); @@ -335,7 +335,7 @@ namespace GreenshotPlugin.Core.OAuth { public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) { // Get Refresh / Access token if (string.IsNullOrEmpty(settings.RefreshToken)) { - if (!Authenticate(settings)) { + if (!Authorize(settings)) { throw new Exception("Authentication cancelled"); } } @@ -343,7 +343,7 @@ namespace GreenshotPlugin.Core.OAuth { GenerateAccessToken(settings); // Get Refresh / Access token if (string.IsNullOrEmpty(settings.RefreshToken)) { - if (!Authenticate(settings)) { + if (!Authorize(settings)) { throw new Exception("Authentication cancelled"); } GenerateAccessToken(settings); diff --git a/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs b/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs index 1b8bd1042..57b566aaa 100644 --- a/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs +++ b/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs @@ -137,6 +137,10 @@ namespace GreenshotPlugin.Core.OAuth /// public bool IsAccessTokenExpired { get { + if (AccessTokenExpires == default) + { + return false; + } bool expired = true; if (!string.IsNullOrEmpty(AccessToken)) { expired = DateTimeOffset.Now.AddSeconds(60) > AccessTokenExpires;