From 71ff4c6286f295cd6d11b50b06160a5343eb2151 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 10 Apr 2015 13:57:20 +0200 Subject: [PATCH 01/20] Fix for missing LastSaveWithVersion, this is needed to recognize with what version the configuration was stored. [skip ci] --- GreenshotPlugin/Core/CoreConfiguration.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/GreenshotPlugin/Core/CoreConfiguration.cs b/GreenshotPlugin/Core/CoreConfiguration.cs index dff34dda4..60f2021ee 100644 --- a/GreenshotPlugin/Core/CoreConfiguration.cs +++ b/GreenshotPlugin/Core/CoreConfiguration.cs @@ -407,6 +407,17 @@ namespace GreenshotPlugin.Core { return base.PreCheckValue(propertyName, propertyValue); } + /// + /// This method will be called before writing the configuration + /// + public override void BeforeSave() { + try { + // Store version, this can be used later to fix settings after an update + LastSaveWithVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(); + } catch { + } + } + /// /// This method will be called after reading the configuration, so eventually some corrections can be made /// From 29c7f466ec27a2a5cd4d62a7098b5e1cecce6545 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 10 Apr 2015 14:04:16 +0200 Subject: [PATCH 02/20] BUG-1770: Fix problems when a font doesn't want to draw itself. [skip ci] --- Greenshot/Controls/FontFamilyComboBox.cs | 39 +++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Greenshot/Controls/FontFamilyComboBox.cs b/Greenshot/Controls/FontFamilyComboBox.cs index c4df132c3..410c87402 100644 --- a/Greenshot/Controls/FontFamilyComboBox.cs +++ b/Greenshot/Controls/FontFamilyComboBox.cs @@ -57,30 +57,47 @@ namespace Greenshot.Controls { if (e.Index > -1) { FontFamily fontFamily = Items[e.Index] as FontFamily; - FontStyle fs = FontStyle.Regular; + FontStyle fontStyle = FontStyle.Regular; if (!fontFamily.IsStyleAvailable(FontStyle.Regular)) { if (fontFamily.IsStyleAvailable(FontStyle.Bold)) { - fs = FontStyle.Bold; + fontStyle = FontStyle.Bold; } else if (fontFamily.IsStyleAvailable(FontStyle.Italic)) { - fs = FontStyle.Italic; + fontStyle = FontStyle.Italic; } else if (fontFamily.IsStyleAvailable(FontStyle.Strikeout)) { - fs = FontStyle.Strikeout; + fontStyle = FontStyle.Strikeout; } else if (fontFamily.IsStyleAvailable(FontStyle.Underline)) { - fs = FontStyle.Underline; + fontStyle = FontStyle.Underline; } } - using (Font font = new Font(fontFamily, this.Font.Size + 5, fs, GraphicsUnit.Pixel)) { - // Make sure the text is visible by centering it in the line - using(StringFormat stringFormat = new StringFormat()) { - stringFormat.LineAlignment = StringAlignment.Center; - e.Graphics.DrawString(fontFamily.Name, font, Brushes.Black, e.Bounds, stringFormat); - } + try { + DrawText(e.Graphics, fontFamily, fontStyle, e.Bounds, fontFamily.Name); + } catch { + // If the drawing failed, BUG-1770 seems to have a weird case that causes: Font 'Lucida Sans Typewriter' does not support style 'Regular' + DrawText(e.Graphics, FontFamily.GenericSansSerif, FontStyle.Regular, e.Bounds, fontFamily.Name); } } // Uncomment this if you actually like the way the focus rectangle looks //e.DrawFocusRectangle (); } + /// + /// Helper method to draw the string + /// + /// + /// + /// + /// + /// + private void DrawText(Graphics graphics, FontFamily fontFamily, FontStyle fontStyle, Rectangle bounds, string text) { + using (Font font = new Font(fontFamily, this.Font.Size + 5, fontStyle, GraphicsUnit.Pixel)) { + // Make sure the text is visible by centering it in the line + using (StringFormat stringFormat = new StringFormat()) { + stringFormat.LineAlignment = StringAlignment.Center; + graphics.DrawString(text, font, Brushes.Black, bounds, stringFormat); + } + } + } + void BindableToolStripComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Text")); From ea4631af3d507e7253721fe6eeb19d3bff6c5191 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 14 Apr 2015 17:11:33 +0200 Subject: [PATCH 03/20] BUG-1769: Added OAuth 2 for Picasa --- Greenshot/Destinations/FileDestination.cs | 2 +- GreenshotPicasaPlugin/PicasaConfiguration.cs | 43 ++- GreenshotPicasaPlugin/PicasaUtils.cs | 170 +++++++-- GreenshotPlugin/Core/NetworkHelper.cs | 17 +- GreenshotPlugin/Core/OAuthHelper.cs | 323 +++++++++++++++++- ...ncryptionHelper.cs => StringExtensions.cs} | 242 ++++++++----- GreenshotPlugin/GreenshotPlugin.csproj | 2 +- 7 files changed, 660 insertions(+), 139 deletions(-) rename GreenshotPlugin/Core/{EncryptionHelper.cs => StringExtensions.cs} (56%) diff --git a/Greenshot/Destinations/FileDestination.cs b/Greenshot/Destinations/FileDestination.cs index 3841ac302..24af9bd1d 100644 --- a/Greenshot/Destinations/FileDestination.cs +++ b/Greenshot/Destinations/FileDestination.cs @@ -135,7 +135,7 @@ namespace Greenshot.Destinations { string filepath = FilenameHelper.FillVariables(conf.OutputFilePath, false); try { fullPath = Path.Combine(filepath, filename); - } catch (ArgumentException ae) { + } catch (ArgumentException) { // configured filename or path not valid, show error message... LOG.InfoFormat("Generated path or filename not valid: {0}, {1}", filepath, filename); diff --git a/GreenshotPicasaPlugin/PicasaConfiguration.cs b/GreenshotPicasaPlugin/PicasaConfiguration.cs index 849539ae7..c392dce2b 100644 --- a/GreenshotPicasaPlugin/PicasaConfiguration.cs +++ b/GreenshotPicasaPlugin/PicasaConfiguration.cs @@ -20,6 +20,7 @@ using System.Windows.Forms; using Greenshot.IniFile; using GreenshotPlugin.Core; +using System; namespace GreenshotPicasaPlugin { /// @@ -36,11 +37,45 @@ namespace GreenshotPicasaPlugin { [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Picasa link to clipboard.", DefaultValue = "true")] public bool AfterUploadLinkToClipBoard; - [IniProperty("PicasaToken", Description = "Picasa Token", Encrypted = true)] - public string PicasaToken; + [IniProperty("RefreshToken", Description = "Picasa refresh Token", Encrypted = true)] + public string RefreshToken { + get; + set; + } - [IniProperty("PicasaTokenSecret", Description = "PicasaTokenSecret", Encrypted = true)] - public string PicasaTokenSecret; + [IniProperty("AddFilename", Description = "Is the filename passed on to Picasa", DefaultValue = "False")] + public bool AddFilename { + get; + set; + } + + [IniProperty("UploadUser", Description = "The picasa user to upload to", DefaultValue = "default")] + public string UploadUser { + get; + set; + } + + [IniProperty("UploadAlbum", Description = "The picasa album to upload to", DefaultValue = "default")] + public string UploadAlbum { + get; + set; + } + + /// + /// Not stored + /// + public string AccessToken { + get; + set; + } + + /// + /// Not stored + /// + public DateTimeOffset AccessTokenExpires { + get; + set; + } /// /// A form for token diff --git a/GreenshotPicasaPlugin/PicasaUtils.cs b/GreenshotPicasaPlugin/PicasaUtils.cs index e8c106a7e..c8c7c296e 100644 --- a/GreenshotPicasaPlugin/PicasaUtils.cs +++ b/GreenshotPicasaPlugin/PicasaUtils.cs @@ -24,17 +24,102 @@ using System.Xml; using Greenshot.IniFile; using Greenshot.Plugin; using GreenshotPlugin.Core; +using System.Net; namespace GreenshotPicasaPlugin { /// /// Description of PicasaUtils. /// - public class PicasaUtils { - private const string GoogleAccountUri = "https://www.google.com/accounts/"; + public static class PicasaUtils { + private const string PicasaScope = "https://picasaweb.google.com/data/"; private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(PicasaUtils)); private static readonly PicasaConfiguration Config = IniConfig.GetIniSection(); + private const string AuthUrl = "https://accounts.google.com/o/oauth2/auth?response_type={response_type}&client_id={ClientId}&redirect_uri={RedirectUrl}&state={State}&scope={scope}"; + private const string TokenUrl = "https://www.googleapis.com/oauth2/v3/token"; + private const string UploadUrl = "https://picasaweb.google.com/data/feed/api/user/{0}/albumid/{1}"; - private PicasaUtils() { + /// + /// Authenticate by using the LocalServerCodeReceiver + /// If this works, generate a token + /// + /// + private static void Authenticate(OAuth2Settings settings) { + var codeReceiver = new LocalServerCodeReceiver(); + IDictionary result = codeReceiver.ReceiveCode(settings); + + string code; + if (result.TryGetValue("code", out code)) { + GenerateToken(code, settings); + } + } + + /// + /// Upload parameters by post + /// + /// + /// + /// response + public static string HttpPost(string url, IDictionary parameters, OAuth2Settings settings) { + var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); + webRequest.Method = "POST"; + webRequest.KeepAlive = true; + webRequest.Credentials = CredentialCache.DefaultCredentials; + + if (!string.IsNullOrEmpty(settings.AccessToken)) { + webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken); + } + return NetworkHelper.UploadFormUrlEncoded(webRequest, parameters); + } + + private static void GenerateToken(string code, OAuth2Settings settings) { + // Use the returned code to get a refresh code + IDictionary data = new Dictionary(); + data.Add("code", code); + data.Add("client_id", settings.ClientId); + data.Add("redirect_uri", settings.RedirectUrl); + data.Add("client_secret", settings.ClientSecret); + data.Add("grant_type", "authorization_code"); + + var accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); + IDictionary refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); + // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp + // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", + // "expires_in":3920, + // "token_type":"Bearer", + // "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" + settings.AccessToken = (string)refreshTokenResult["access_token"] as string; + settings.RefreshToken = (string)refreshTokenResult["refresh_token"] as string; + + object seconds = refreshTokenResult["expires_in"]; + if (seconds != null) { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); + } + } + + /// + /// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings + /// Will upate the access token, refresh token, expire date + /// + /// + private static void GenerateAccessToken(OAuth2Settings settings) { + IDictionary data = new Dictionary(); + data.Add("refresh_token", settings.RefreshToken); + data.Add("client_id", settings.ClientId); + data.Add("client_secret", settings.ClientSecret); + data.Add("grant_type", "refresh_token"); + + var accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); + // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp + // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", + // "expires_in":3920, + // "token_type":"Bearer", + + IDictionary accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); + settings.AccessToken = (string)accessTokenResult["access_token"] as string; + object seconds = accessTokenResult["expires_in"]; + if (seconds != null) { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); + } } /// @@ -46,46 +131,57 @@ namespace GreenshotPicasaPlugin { /// /// PicasaResponse public static string UploadToPicasa(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) { - OAuthSession oAuth = new OAuthSession(PicasaCredentials.ConsumerKey, PicasaCredentials.ConsumerSecret); - oAuth.BrowserSize = new Size(1020, 590); - oAuth.AccessTokenUrl = GoogleAccountUri + "OAuthGetAccessToken"; - oAuth.AuthorizeUrl = GoogleAccountUri + "OAuthAuthorizeToken"; - oAuth.RequestTokenUrl = GoogleAccountUri + "OAuthGetRequestToken"; - oAuth.LoginTitle = "Picasa authorization"; - oAuth.Token = Config.PicasaToken; - oAuth.TokenSecret = Config.PicasaTokenSecret; - oAuth.RequestTokenParameters.Add("scope", "https://picasaweb.google.com/data/"); - oAuth.RequestTokenParameters.Add("xoauth_displayname", "Greenshot"); - if (string.IsNullOrEmpty(oAuth.Token)) { - if (!oAuth.Authorize()) { - return null; - } - if (!string.IsNullOrEmpty(oAuth.Token)) { - Config.PicasaToken = oAuth.Token; - } - if (!string.IsNullOrEmpty(oAuth.TokenSecret)) { - Config.PicasaTokenSecret = oAuth.TokenSecret; - } - IniConfig.Save(); - } + // Fill the OAuth2Settings + OAuth2Settings settings = new OAuth2Settings(); + settings.AuthUrlPattern = AuthUrl; + settings.TokenUrlPattern = TokenUrl; + settings.AdditionalAttributes.Add("response_type", "code"); + settings.AdditionalAttributes.Add("scope", PicasaScope); + settings.ClientId = PicasaCredentials.ClientId; + settings.ClientSecret = PicasaCredentials.ClientSecret; + + // Copy the settings from the config, which is kept in memory + settings.RefreshToken = Config.RefreshToken; + settings.AccessToken = Config.AccessToken; + settings.AccessTokenExpires = Config.AccessTokenExpires; + try { - IDictionary headers = new Dictionary(); - headers.Add("slug", OAuthSession.UrlEncode3986(filename)); - string response = oAuth.MakeOAuthRequest(HTTPMethod.POST, "https://picasaweb.google.com/data/feed/api/user/default/albumid/default", headers, null, null, new SurfaceContainer(surfaceToUpload, outputSettings, filename)); + // Get Refresh / Access token + if (string.IsNullOrEmpty(settings.RefreshToken)) { + Authenticate(settings); + } + + if (settings.IsAccessTokenExpired) { + GenerateAccessToken(settings); + } + + var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum)); + webRequest.Method = "POST"; + webRequest.KeepAlive = true; + webRequest.Credentials = CredentialCache.DefaultCredentials; + webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken); + if (Config.AddFilename) { + webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename)); + } + SurfaceContainer container = new SurfaceContainer(surfaceToUpload, outputSettings, filename); + container.Upload(webRequest); + + string response = NetworkHelper.GetResponse(webRequest); + return ParseResponse(response); - } catch (Exception ex) { - LOG.Error("Upload error: ", ex); - throw; } finally { - if (!string.IsNullOrEmpty(oAuth.Token)) { - Config.PicasaToken = oAuth.Token; - } - if (!string.IsNullOrEmpty(oAuth.TokenSecret)) { - Config.PicasaTokenSecret = oAuth.TokenSecret; - } + // Copy the settings back to the config + Config.RefreshToken = settings.RefreshToken; + Config.AccessToken = settings.AccessToken; + Config.AccessTokenExpires = settings.AccessTokenExpires; } } + /// + /// Parse the upload URL from the response + /// + /// + /// public static string ParseResponse(string response) { if (response == null) { return null; diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index 6fcfb6854..7a4244dfb 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -320,7 +320,22 @@ namespace GreenshotPlugin.Core { string footer = "\r\n--" + boundary + "--\r\n"; formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer)); } - + + /// + /// Post the parameters "x-www-form-urlencoded" + /// + /// + /// + public static string UploadFormUrlEncoded(HttpWebRequest webRequest, IDictionary parameters) { + webRequest.ContentType = "application/x-www-form-urlencoded"; + string urlEncoded = NetworkHelper.GenerateQueryParameters(parameters); + using (var requestStream = webRequest.GetRequestStream()) + using (var streamWriter = new StreamWriter(requestStream, Encoding.UTF8)) { + streamWriter.Write(urlEncoded); + } + return GetResponse(webRequest); + } + /// /// Process the web response. /// diff --git a/GreenshotPlugin/Core/OAuthHelper.cs b/GreenshotPlugin/Core/OAuthHelper.cs index ba44650f7..b4e797bdd 100644 --- a/GreenshotPlugin/Core/OAuthHelper.cs +++ b/GreenshotPlugin/Core/OAuthHelper.cs @@ -19,30 +19,170 @@ * along with this program. If not, see . */ +using GreenshotPlugin.Controls; +using log4net; using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; using System.Drawing; using System.Globalization; +using System.IO; using System.Net; +using System.Net.Sockets; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; -using GreenshotPlugin.Controls; -using System.Security.Cryptography.X509Certificates; -using log4net; namespace GreenshotPlugin.Core { /// - /// Provides a predefined set of algorithms that are supported officially by the protocol + /// Provides a predefined set of algorithms that are supported officially by the OAuth 1.x protocol /// public enum OAuthSignatureTypes { HMACSHA1, PLAINTEXT, RSASHA1 } - + + /// + /// Used HTTP Method, this is for the OAuth 1.x protocol + /// public enum HTTPMethod { GET, POST, PUT, DELETE }; + /// + /// Settings for the OAuth 2 protocol + /// + public class OAuth2Settings { + public OAuth2Settings() { + AdditionalAttributes = new Dictionary(); + // Create a default state + State = Guid.NewGuid().ToString(); + } + + /// + /// The OAuth 2 client id + /// + public string ClientId { + get; + set; + } + + /// + /// The OAuth 2 client secret + /// + public string ClientSecret { + get; + set; + } + + /// + /// The OAuth 2 state, this is something that is passed to the server, is not processed but returned back to the client. + /// e.g. a correlation ID + /// Default this is filled with a new Guid + /// + public string State { + get; + set; + } + + /// + /// The autorization URL where the values of this class can be "injected" + /// + public string AuthUrlPattern { + get; + set; + } + + /// + /// Get formatted Auth url (this will call a FormatWith(this) on the AuthUrlPattern + /// + public string FormattedAuthUrl { + get { + return AuthUrlPattern.FormatWith(this); + } + } + + /// + /// The URL to get a Token + /// + public string TokenUrlPattern { + get; + set; + } + + /// + /// Get formatted Token url (this will call a FormatWith(this) on the TokenUrlPattern + /// + public string FormattedTokenUrl { + get { + return TokenUrlPattern.FormatWith(this); + } + } + + /// + /// This is the redirect URL, in some implementations this is automatically set (LocalServerCodeReceiver) + /// In some implementations this could be e.g. urn:ietf:wg:oauth:2.0:oob or urn:ietf:wg:oauth:2.0:oob:auto + /// + public string RedirectUrl { + get; + set; + } + + /// + /// Bearer token for accessing OAuth 2 services + /// + public string AccessToken { + get; + set; + } + + /// + /// Expire time for the AccessToken, this this time (-60 seconds) is passed a new AccessToken needs to be generated with the RefreshToken + /// + public DateTimeOffset AccessTokenExpires { + get; + set; + } + + /// + /// Return true if the access token is expired. + /// Important "side-effect": if true is returned the AccessToken will be set to null! + /// + public bool IsAccessTokenExpired { + get { + bool expired = true; + if (!string.IsNullOrEmpty(AccessToken) && AccessTokenExpires != null) { + expired = DateTimeOffset.Now.AddSeconds(60) > AccessTokenExpires; + } + // Make sure the token is not usable + if (expired) { + AccessToken = null; + } + return expired; + } + } + + /// + /// Token used to get a new Access Token + /// + public string RefreshToken { + get; + set; + } + + /// + /// Put anything in here which is needed for the OAuth 2 implementation of this specific service but isn't generic, e.g. for Google there is a "scope" + /// + public IDictionary AdditionalAttributes { + get; + set; + } + } + + /// + /// An OAuth 1 session object + /// public class OAuthSession { private static readonly ILog LOG = LogManager.GetLogger(typeof(OAuthSession)); protected const string OAUTH_VERSION = "1.0"; @@ -732,4 +872,177 @@ namespace GreenshotPlugin.Core { return responseData; } } + + /// + /// OAuth 2.0 verification code receiver that runs a local server on a free port + /// and waits for a call with the authorization verification code. + /// + public class LocalServerCodeReceiver { + private static readonly ILog LOG = LogManager.GetLogger(typeof(LocalServerCodeReceiver)); + + private string _loopbackCallback = "http://localhost:{0}/authorize/"; + /// + /// The call back format. Expects one port parameter. + /// Default: http://localhost:{0}/authorize/ + /// + public string LoopbackCallbackUrl { + get { + return _loopbackCallback; + } + set { + _loopbackCallback = value; + } + } + + private string _closePageResponse = +@" +OAuth 2.0 Authentication Token Received + +Greenshot received verification code. You can close this browser / tab if it is not closed itself... + + +"; + + /// + /// HTML code to to return the browser, default it will try to close the browser / tab, this won't always work. + /// + public string ClosePageResponse { + get { + return _closePageResponse; + } + set { + _closePageResponse = value; + } + } + + private string _redirectUri; + /// + /// The URL to redirect to + /// + protected string RedirectUri { + get { + if (!string.IsNullOrEmpty(_redirectUri)) { + return _redirectUri; + } + + return _redirectUri = string.Format(_loopbackCallback, GetRandomUnusedPort()); + } + } + + /// + /// The OAuth code receiver + /// + /// + /// Dictionary with values + public IDictionary ReceiveCode(OAuth2Settings oauth2Settings) { + // Set the redirect URL on the settings + oauth2Settings.RedirectUrl = RedirectUri; + using (var listener = new HttpListener()) { + listener.Prefixes.Add(oauth2Settings.RedirectUrl); + try { + listener.Start(); + + // Get the formatted FormattedAuthUrl + string authorizationUrl = oauth2Settings.FormattedAuthUrl; + LOG.DebugFormat("Open a browser with: {0}", authorizationUrl); + Process.Start(authorizationUrl); + + // Wait to get the authorization code response. + var context = listener.GetContext(); + try { + NameValueCollection nameValueCollection = context.Request.QueryString; + + // Write a "close" response. + using (var writer = new StreamWriter(context.Response.OutputStream)) { + writer.WriteLine(ClosePageResponse); + writer.Flush(); + } + + // Create a new response URL with a dictionary that contains all the response query parameters. + IDictionary returnValues = new Dictionary(); + foreach (var name in nameValueCollection.AllKeys) { + if (!returnValues.ContainsKey(name)) { + returnValues.Add(name, nameValueCollection[name]); + } + } + return returnValues; + } finally { + context.Response.OutputStream.Close(); + } + } finally { + listener.Close(); + } + } + } + + /// + /// Returns a random, unused port. + /// + /// port to use + private static int GetRandomUnusedPort() { + var listener = new TcpListener(IPAddress.Loopback, 0); + try { + listener.Start(); + return ((IPEndPoint)listener.LocalEndpoint).Port; + } finally { + listener.Stop(); + } + } + } + + /// + /// Class to hold all the Properties for the OAuth 2 Token request + /// + public class OAuth2TokenRequest { + public string Code { + get; + set; + } + public string ClientId { + get; + set; + } + public string ClientSecret { + get; + set; + } + public string RedirectUri { + get; + set; + } + public string GrantType { + get; + set; + } + } + + /// + /// Class to hold all the Properties for the OAuth 2 Token response + /// + public class OAuth2TokenResponse { + public string AccessToken { + get; + set; + } + public string ExpiresIn { + get; + set; + } + public string TokenType { + get; + set; + } + public string RefreshToken { + get; + set; + } + } } diff --git a/GreenshotPlugin/Core/EncryptionHelper.cs b/GreenshotPlugin/Core/StringExtensions.cs similarity index 56% rename from GreenshotPlugin/Core/EncryptionHelper.cs rename to GreenshotPlugin/Core/StringExtensions.cs index bd76e53a7..bb4f433a2 100644 --- a/GreenshotPlugin/Core/EncryptionHelper.cs +++ b/GreenshotPlugin/Core/StringExtensions.cs @@ -1,90 +1,152 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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.IO; -using System.Security.Cryptography; -using System.Text; -using log4net; - -namespace GreenshotPlugin.Core { - public static class EncryptionHelper { - private static readonly ILog LOG = LogManager.GetLogger(typeof(EncryptionHelper)); - private const string RGBIV = "dlgjowejgogkklwj"; - private const string KEY = "lsjvkwhvwujkagfauguwcsjgu2wueuff"; - - /// - /// A simply rijndael aes encryption, can be used to store passwords - /// - /// the string to call upon - /// an encryped string in base64 form - public static string Encrypt(this string ClearText) { - string returnValue = ClearText; - try { - byte[] clearTextBytes = Encoding.ASCII.GetBytes(ClearText); - SymmetricAlgorithm rijn = SymmetricAlgorithm.Create(); - - using (MemoryStream ms = new MemoryStream()) { - byte[] rgbIV = Encoding.ASCII.GetBytes(RGBIV); - byte[] key = Encoding.ASCII.GetBytes(KEY); - using (CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write)) { - cs.Write(clearTextBytes, 0, clearTextBytes.Length); - cs.FlushFinalBlock(); - - returnValue = Convert.ToBase64String(ms.ToArray()); - } - } - } catch (Exception ex) { - LOG.ErrorFormat("Error encrypting, error: ", ex.Message); - } - return returnValue; - } - - /// - /// A simply rijndael aes decryption, can be used to store passwords - /// - /// a base64 encoded rijndael encrypted string - /// Decrypeted text - public static string Decrypt(this string EncryptedText) { - string returnValue = EncryptedText; - try { - byte[] encryptedTextBytes = Convert.FromBase64String(EncryptedText); - using (MemoryStream ms = new MemoryStream()) { - SymmetricAlgorithm rijn = SymmetricAlgorithm.Create(); - - - byte[] rgbIV = Encoding.ASCII.GetBytes(RGBIV); - byte[] key = Encoding.ASCII.GetBytes(KEY); - - using (CryptoStream cs = new CryptoStream(ms, rijn.CreateDecryptor(key, rgbIV), CryptoStreamMode.Write)) { - cs.Write(encryptedTextBytes, 0, encryptedTextBytes.Length); - cs.FlushFinalBlock(); - returnValue = Encoding.ASCII.GetString(ms.ToArray()); - } - - } - } catch (Exception ex) { - LOG.ErrorFormat("Error decrypting {0}, error: ", EncryptedText, ex.Message); - } - - return returnValue; - } - } -} +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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.IO; +using System.Security.Cryptography; +using System.Text; +using log4net; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Reflection; + +namespace GreenshotPlugin.Core { + public static class StringExtensions { + private static readonly ILog LOG = LogManager.GetLogger(typeof(StringExtensions)); + private const string RGBIV = "dlgjowejgogkklwj"; + private const string KEY = "lsjvkwhvwujkagfauguwcsjgu2wueuff"; + + /// + /// Format a string with the specified object + /// + /// String with formatting, like {name} + /// Object used for the formatting + /// Formatted string + public static string FormatWith(this string format, object source) { + return FormatWith(format, null, source); + } + + /// + /// Format the string "format" with the source + /// + /// + /// + /// object with properties, if a property has the type IDictionary string,string it can used these parameters too + /// Formatted string + public static string FormatWith(this string format, IFormatProvider provider, object source) { + if (format == null) { + throw new ArgumentNullException("format"); + } + + IDictionary properties = new Dictionary(); + foreach(var propertyInfo in source.GetType().GetProperties()) { + if (propertyInfo.CanRead && propertyInfo.CanWrite) { + object value = propertyInfo.GetValue(source, null); + if (propertyInfo.PropertyType != typeof(IDictionary)) { + properties.Add(propertyInfo.Name, value); + } else { + IDictionary dictionary = (IDictionary)value; + foreach (var propertyKey in dictionary.Keys) { + properties.Add(propertyKey, dictionary[propertyKey]); + } + } + } + } + + Regex r = new Regex(@"(?\{)+(?[\w\.\[\]]+)(?:[^}]+)?(?\})+", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + + List values = new List(); + string rewrittenFormat = r.Replace(format, delegate(Match m) { + Group startGroup = m.Groups["start"]; + Group propertyGroup = m.Groups["property"]; + Group formatGroup = m.Groups["format"]; + Group endGroup = m.Groups["end"]; + + object value; + if (properties.TryGetValue(propertyGroup.Value, out value)) { + values.Add(value); + } else { + values.Add(source); + } + return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value + new string('}', endGroup.Captures.Count); + }); + + return string.Format(provider, rewrittenFormat, values.ToArray()); + } + + /// + /// A simply rijndael aes encryption, can be used to store passwords + /// + /// the string to call upon + /// an encryped string in base64 form + public static string Encrypt(this string ClearText) { + string returnValue = ClearText; + try { + byte[] clearTextBytes = Encoding.ASCII.GetBytes(ClearText); + SymmetricAlgorithm rijn = SymmetricAlgorithm.Create(); + + using (MemoryStream ms = new MemoryStream()) { + byte[] rgbIV = Encoding.ASCII.GetBytes(RGBIV); + byte[] key = Encoding.ASCII.GetBytes(KEY); + using (CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write)) { + cs.Write(clearTextBytes, 0, clearTextBytes.Length); + cs.FlushFinalBlock(); + + returnValue = Convert.ToBase64String(ms.ToArray()); + } + } + } catch (Exception ex) { + LOG.ErrorFormat("Error encrypting, error: ", ex.Message); + } + return returnValue; + } + + /// + /// A simply rijndael aes decryption, can be used to store passwords + /// + /// a base64 encoded rijndael encrypted string + /// Decrypeted text + public static string Decrypt(this string EncryptedText) { + string returnValue = EncryptedText; + try { + byte[] encryptedTextBytes = Convert.FromBase64String(EncryptedText); + using (MemoryStream ms = new MemoryStream()) { + SymmetricAlgorithm rijn = SymmetricAlgorithm.Create(); + + + byte[] rgbIV = Encoding.ASCII.GetBytes(RGBIV); + byte[] key = Encoding.ASCII.GetBytes(KEY); + + using (CryptoStream cs = new CryptoStream(ms, rijn.CreateDecryptor(key, rgbIV), CryptoStreamMode.Write)) { + cs.Write(encryptedTextBytes, 0, encryptedTextBytes.Length); + cs.FlushFinalBlock(); + returnValue = Encoding.ASCII.GetString(ms.ToArray()); + } + + } + } catch (Exception ex) { + LOG.ErrorFormat("Error decrypting {0}, error: ", EncryptedText, ex.Message); + } + + return returnValue; + } + } +} diff --git a/GreenshotPlugin/GreenshotPlugin.csproj b/GreenshotPlugin/GreenshotPlugin.csproj index bac1fe128..c7a6740af 100644 --- a/GreenshotPlugin/GreenshotPlugin.csproj +++ b/GreenshotPlugin/GreenshotPlugin.csproj @@ -155,7 +155,7 @@ - + From 2b6e7d14cd97153d83d8975f177c5c41fc6f4650 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 14 Apr 2015 17:26:02 +0200 Subject: [PATCH 04/20] BUG-1769: Fix for credentials & when the denied is selected when the authorize is shown in the browser. --- GreenshotPicasaPlugin/PicasaCredentials.cs | 4 ++-- GreenshotPicasaPlugin/PicasaUtils.cs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/GreenshotPicasaPlugin/PicasaCredentials.cs b/GreenshotPicasaPlugin/PicasaCredentials.cs index 046d188f9..83c6059b6 100644 --- a/GreenshotPicasaPlugin/PicasaCredentials.cs +++ b/GreenshotPicasaPlugin/PicasaCredentials.cs @@ -25,7 +25,7 @@ namespace GreenshotPicasaPlugin { /// You can set your own values here /// public static class PicasaCredentials { - public static string ConsumerKey = "@credentials_picasa_consumer_key@"; - public static string ConsumerSecret = "@credentials_picasa_consumer_secret@"; + public static string ClientId = "@credentials_picasa_consumer_key@"; + public static string ClientSecret = "@credentials_picasa_consumer_secret@"; } } diff --git a/GreenshotPicasaPlugin/PicasaUtils.cs b/GreenshotPicasaPlugin/PicasaUtils.cs index c8c7c296e..6faea9ac6 100644 --- a/GreenshotPicasaPlugin/PicasaUtils.cs +++ b/GreenshotPicasaPlugin/PicasaUtils.cs @@ -51,6 +51,14 @@ namespace GreenshotPicasaPlugin { if (result.TryGetValue("code", out code)) { GenerateToken(code, settings); } + string error; + if (result.TryGetValue("error", out error)) { + if ("access_denied" == error) { + throw new UnauthorizedAccessException("Access denied"); + } else { + throw new Exception(error); + } + } } /// From 6f8434d46f926b7f2d7e892df8010799770e300c Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 14 Apr 2015 20:06:39 +0200 Subject: [PATCH 05/20] Updated readme for the next bugfix release. [skip ci] --- Greenshot/releases/additional_files/readme.txt.template | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Greenshot/releases/additional_files/readme.txt.template b/Greenshot/releases/additional_files/readme.txt.template index 4bdafc6ab..dbb83dbde 100644 --- a/Greenshot/releases/additional_files/readme.txt.template +++ b/Greenshot/releases/additional_files/readme.txt.template @@ -9,6 +9,13 @@ All details to our tickets can be found here: https://greenshot.atlassian.net @DETAILVERSION@ +Bugs Resolved: +* BUG-1769: Switched to OAuth 2 for Picasa Authentication, +* BUG-1770: Fix problems when a font doesn't want to draw itself. + + +1.2.5.19-RELEASE-63412d3b9e29 + Bugs Resolved: * BUG-1578, BUG-1657: by default, Imgur plugin no longer passes title and filename to Imgur (for new installations, existing configuration files won't be affected) * BUG-1655: Greenshot uses the default proxy, even if the "use default proxy" check-box is not checked. From ea8c27449fa2700b4cc3a674fca0dfa8a8fa3889 Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 15 Apr 2015 13:16:26 +0200 Subject: [PATCH 06/20] Moved some code around, for better re-usage. Also added some debug information, and wrote some comments. [skip ci] --- GreenshotImgurPlugin/ImgurUtils.cs | 90 ++++++++++++---- GreenshotPicasaPlugin/PicasaUtils.cs | 109 ++------------------ GreenshotPlugin/Core/NetworkHelper.cs | 15 +++ GreenshotPlugin/Core/OAuthHelper.cs | 142 +++++++++++++++++++------- 4 files changed, 196 insertions(+), 160 deletions(-) diff --git a/GreenshotImgurPlugin/ImgurUtils.cs b/GreenshotImgurPlugin/ImgurUtils.cs index efa6de7cc..ca03e84fe 100644 --- a/GreenshotImgurPlugin/ImgurUtils.cs +++ b/GreenshotImgurPlugin/ImgurUtils.cs @@ -31,14 +31,14 @@ namespace GreenshotImgurPlugin { /// /// Description of ImgurUtils. /// - public class ImgurUtils { + public static class ImgurUtils { private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ImgurUtils)); private const string IMGUR_ANONYMOUS_API_KEY = "8116a978913f3cf5dfc8e1117a055056"; private static ImgurConfiguration config = IniConfig.GetIniSection(); - private ImgurUtils() { - } - + /// + /// Load the complete history of the imgur uploads, with the corresponding information + /// public static void LoadHistory() { if (config.runtimeImgurHistory.Count == config.ImgurUploadHistory.Count) { return; @@ -90,6 +90,14 @@ namespace GreenshotImgurPlugin { } } + /// + /// Use this to make sure Imgur knows from where the upload comes. + /// + /// + private static void SetClientId(HttpWebRequest webRequest) { + webRequest.Headers.Add("Authorization", "Client-ID " + ImgurCredentials.CONSUMER_KEY); + } + /// /// Do the actual upload to Imgur /// For more details on the available parameters, see: http://api.imgur.com/resources_anon @@ -118,6 +126,8 @@ namespace GreenshotImgurPlugin { webRequest.Method = "POST"; webRequest.ContentType = "image/" + outputSettings.Format.ToString(); webRequest.ServicePoint.Expect100Continue = false; + + SetClientId(webRequest); try { using (var requestStream = webRequest.GetRequestStream()) { ImageOutput.SaveToStream(surfaceToUpload, requestStream, outputSettings); @@ -127,7 +137,7 @@ namespace GreenshotImgurPlugin { using (StreamReader reader = new StreamReader(response.GetResponseStream(), true)) { responseString = reader.ReadToEnd(); } - LogCredits(response); + LogRateLimitInfo(response); } } catch (Exception ex) { LOG.Error("Upload to imgur gave an exeption: ", ex); @@ -174,6 +184,10 @@ namespace GreenshotImgurPlugin { return ImgurInfo.ParseResponse(responseString); } + /// + /// Retrieve the thumbnail of an imgur image + /// + /// public static void RetrieveImgurThumbnail(ImgurInfo imgurInfo) { if (imgurInfo.SmallSquare == null) { LOG.Warn("Imgur URL was null, not retrieving thumbnail."); @@ -183,25 +197,32 @@ namespace GreenshotImgurPlugin { HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(imgurInfo.SmallSquare); webRequest.Method = "GET"; webRequest.ServicePoint.Expect100Continue = false; - + SetClientId(webRequest); using (WebResponse response = webRequest.GetResponse()) { - LogCredits(response); + LogRateLimitInfo(response); Stream responseStream = response.GetResponseStream(); imgurInfo.Image = Image.FromStream(responseStream); } return; } + /// + /// Retrieve information on an imgur image + /// + /// + /// + /// ImgurInfo public static ImgurInfo RetrieveImgurInfo(string hash, string deleteHash) { string url = config.ImgurApiUrl + "/image/" + hash; LOG.InfoFormat("Retrieving Imgur info for {0} with url {1}", hash, url); HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); webRequest.Method = "GET"; webRequest.ServicePoint.Expect100Continue = false; + SetClientId(webRequest); string responseString; try { using (WebResponse response = webRequest.GetResponse()) { - LogCredits(response); + LogRateLimitInfo(response); using (StreamReader reader = new StreamReader(response.GetResponseStream(), true)) { responseString = reader.ReadToEnd(); } @@ -220,6 +241,10 @@ namespace GreenshotImgurPlugin { return imgurInfo; } + /// + /// Delete an imgur image, this is done by specifying the delete hash + /// + /// public static void DeleteImgurImage(ImgurInfo imgurInfo) { LOG.InfoFormat("Deleting Imgur image for {0}", imgurInfo.DeleteHash); @@ -230,10 +255,10 @@ namespace GreenshotImgurPlugin { //webRequest.Method = "DELETE"; webRequest.Method = "GET"; webRequest.ServicePoint.Expect100Continue = false; - + SetClientId(webRequest); string responseString; using (WebResponse response = webRequest.GetResponse()) { - LogCredits(response); + LogRateLimitInfo(response); using (StreamReader reader = new StreamReader(response.GetResponseStream(), true)) { responseString = reader.ReadToEnd(); } @@ -252,17 +277,42 @@ namespace GreenshotImgurPlugin { config.ImgurUploadHistory.Remove(imgurInfo.Hash); imgurInfo.Image = null; } - - private static void LogCredits(WebResponse response) { - try { - int credits = 0; - if (int.TryParse(response.Headers["X-RateLimit-Remaining"], out credits)) { - config.Credits = credits; + + /// + /// Helper for logging + /// + /// + /// + private static void LogHeader(IDictionary nameValues, string key) { + if (nameValues.ContainsKey(key)) { + LOG.InfoFormat("key={0}", nameValues[key]); + } + } + + /// + /// Log the current rate-limit information + /// + /// + private static void LogRateLimitInfo(WebResponse response) { + IDictionary nameValues = new Dictionary(); + foreach (string key in response.Headers.AllKeys) { + if (!nameValues.ContainsKey(key)) { + nameValues.Add(key, response.Headers[key]); } - LOG.InfoFormat("X-RateLimit-Limit={0}", response.Headers["X-RateLimit-Limit"]); - LOG.InfoFormat("X-RateLimit-Remaining={0}", response.Headers["X-RateLimit-Remaining"]); - - } catch {} + } + LogHeader(nameValues, "X-RateLimit-Limit"); + LogHeader(nameValues, "X-RateLimit-Remaining"); + LogHeader(nameValues, "X-RateLimit-UserLimit"); + LogHeader(nameValues, "X-RateLimit-UserRemaining"); + LogHeader(nameValues, "X-RateLimit-UserReset"); + LogHeader(nameValues, "X-RateLimit-ClientLimit"); + LogHeader(nameValues, "X-RateLimit-ClientRemaining"); + + // Update the credits in the config, this is shown in a form + int credits = 0; + if (int.TryParse(nameValues["X-RateLimit-Remaining"], out credits)) { + config.Credits = credits; + } } } } diff --git a/GreenshotPicasaPlugin/PicasaUtils.cs b/GreenshotPicasaPlugin/PicasaUtils.cs index 6faea9ac6..04be91926 100644 --- a/GreenshotPicasaPlugin/PicasaUtils.cs +++ b/GreenshotPicasaPlugin/PicasaUtils.cs @@ -17,14 +17,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Xml; + using Greenshot.IniFile; using Greenshot.Plugin; using GreenshotPlugin.Core; +using System; using System.Net; +using System.Xml; namespace GreenshotPicasaPlugin { /// @@ -38,98 +37,6 @@ namespace GreenshotPicasaPlugin { private const string TokenUrl = "https://www.googleapis.com/oauth2/v3/token"; private const string UploadUrl = "https://picasaweb.google.com/data/feed/api/user/{0}/albumid/{1}"; - /// - /// Authenticate by using the LocalServerCodeReceiver - /// If this works, generate a token - /// - /// - private static void Authenticate(OAuth2Settings settings) { - var codeReceiver = new LocalServerCodeReceiver(); - IDictionary result = codeReceiver.ReceiveCode(settings); - - string code; - if (result.TryGetValue("code", out code)) { - GenerateToken(code, settings); - } - string error; - if (result.TryGetValue("error", out error)) { - if ("access_denied" == error) { - throw new UnauthorizedAccessException("Access denied"); - } else { - throw new Exception(error); - } - } - } - - /// - /// Upload parameters by post - /// - /// - /// - /// response - public static string HttpPost(string url, IDictionary parameters, OAuth2Settings settings) { - var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - webRequest.Method = "POST"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - - if (!string.IsNullOrEmpty(settings.AccessToken)) { - webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken); - } - return NetworkHelper.UploadFormUrlEncoded(webRequest, parameters); - } - - private static void GenerateToken(string code, OAuth2Settings settings) { - // Use the returned code to get a refresh code - IDictionary data = new Dictionary(); - data.Add("code", code); - data.Add("client_id", settings.ClientId); - data.Add("redirect_uri", settings.RedirectUrl); - data.Add("client_secret", settings.ClientSecret); - data.Add("grant_type", "authorization_code"); - - var accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); - IDictionary refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); - // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp - // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", - // "expires_in":3920, - // "token_type":"Bearer", - // "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" - settings.AccessToken = (string)refreshTokenResult["access_token"] as string; - settings.RefreshToken = (string)refreshTokenResult["refresh_token"] as string; - - object seconds = refreshTokenResult["expires_in"]; - if (seconds != null) { - settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); - } - } - - /// - /// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings - /// Will upate the access token, refresh token, expire date - /// - /// - private static void GenerateAccessToken(OAuth2Settings settings) { - IDictionary data = new Dictionary(); - data.Add("refresh_token", settings.RefreshToken); - data.Add("client_id", settings.ClientId); - data.Add("client_secret", settings.ClientSecret); - data.Add("grant_type", "refresh_token"); - - var accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); - // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp - // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", - // "expires_in":3920, - // "token_type":"Bearer", - - IDictionary accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); - settings.AccessToken = (string)accessTokenResult["access_token"] as string; - object seconds = accessTokenResult["expires_in"]; - if (seconds != null) { - settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); - } - } - /// /// Do the actual upload to Picasa /// @@ -148,7 +55,7 @@ namespace GreenshotPicasaPlugin { settings.ClientId = PicasaCredentials.ClientId; settings.ClientSecret = PicasaCredentials.ClientSecret; - // Copy the settings from the config, which is kept in memory + // Copy the settings from the config, which is kept in memory and on the disk settings.RefreshToken = Config.RefreshToken; settings.AccessToken = Config.AccessToken; settings.AccessTokenExpires = Config.AccessTokenExpires; @@ -156,18 +63,18 @@ namespace GreenshotPicasaPlugin { try { // Get Refresh / Access token if (string.IsNullOrEmpty(settings.RefreshToken)) { - Authenticate(settings); + OAuth2Helper.AuthenticateViaLocalServer(settings); } if (settings.IsAccessTokenExpired) { - GenerateAccessToken(settings); + OAuth2Helper.GenerateAccessToken(settings); } var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum)); webRequest.Method = "POST"; webRequest.KeepAlive = true; webRequest.Credentials = CredentialCache.DefaultCredentials; - webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken); + OAuth2Helper.AddOAuth2Credentials(webRequest, settings); if (Config.AddFilename) { webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename)); } @@ -178,7 +85,7 @@ namespace GreenshotPicasaPlugin { return ParseResponse(response); } finally { - // Copy the settings back to the config + // Copy the settings back to the config, so they are stored. Config.RefreshToken = settings.RefreshToken; Config.AccessToken = settings.AccessToken; Config.AccessTokenExpires = settings.AccessTokenExpires; diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index 7a4244dfb..7840cd06c 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -336,6 +336,20 @@ namespace GreenshotPlugin.Core { return GetResponse(webRequest); } + /// + /// Log the headers of the WebResponse, if IsDebugEnabled + /// + /// WebResponse + private static void DebugHeaders(WebResponse response) { + if (!LOG.IsDebugEnabled) { + return; + } + LOG.DebugFormat("Debug information on the response from {0} :", response.ResponseUri); + foreach (string key in response.Headers.AllKeys) { + LOG.DebugFormat("Reponse-header: {0}={1}", key, response.Headers[key]); + } + } + /// /// Process the web response. /// @@ -348,6 +362,7 @@ namespace GreenshotPlugin.Core { HttpWebResponse response = (HttpWebResponse) webRequest.GetResponse(); LOG.InfoFormat("Response status: {0}", response.StatusCode); bool isHttpError = (int) response.StatusCode >= 300; + DebugHeaders(response); Stream responseStream = response.GetResponseStream(); if (responseStream != null) { using (StreamReader reader = new StreamReader(responseStream, true)) { diff --git a/GreenshotPlugin/Core/OAuthHelper.cs b/GreenshotPlugin/Core/OAuthHelper.cs index b4e797bdd..a327264a7 100644 --- a/GreenshotPlugin/Core/OAuthHelper.cs +++ b/GreenshotPlugin/Core/OAuthHelper.cs @@ -999,50 +999,114 @@ Greenshot received verification code. You can close this browser / tab if it is } /// - /// Class to hold all the Properties for the OAuth 2 Token request + /// Code to simplify OAuth 2 /// - public class OAuth2TokenRequest { - public string Code { - get; - set; - } - public string ClientId { - get; - set; - } - public string ClientSecret { - get; - set; - } - public string RedirectUri { - get; - set; - } - public string GrantType { - get; - set; - } - } + public static class OAuth2Helper { + /// + /// Upload parameters by post + /// + /// + /// Form-Url-Parameters + /// OAuth2Settings + /// response + public static string HttpPost(string url, IDictionary parameters, OAuth2Settings settings) { + HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); + webRequest.Method = "POST"; + webRequest.KeepAlive = true; + webRequest.Credentials = CredentialCache.DefaultCredentials; - /// - /// Class to hold all the Properties for the OAuth 2 Token response - /// - public class OAuth2TokenResponse { - public string AccessToken { - get; - set; + AddOAuth2Credentials(webRequest, settings); + return NetworkHelper.UploadFormUrlEncoded(webRequest, parameters); } - public string ExpiresIn { - get; - set; + + /// + /// Generate an OAuth 2 Token by using the supplied code + /// + /// Code to get the RefreshToken + /// OAuth2Settings to update with the information that was retrieved + public static void GenerateRefreshToken(string code, OAuth2Settings settings) { + // Use the returned code to get a refresh code + IDictionary data = new Dictionary(); + data.Add("code", code); + data.Add("client_id", settings.ClientId); + data.Add("redirect_uri", settings.RedirectUrl); + data.Add("client_secret", settings.ClientSecret); + data.Add("grant_type", "authorization_code"); + + string accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); + IDictionary refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); + // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp + // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", + // "expires_in":3920, + // "token_type":"Bearer", + // "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" + settings.AccessToken = (string)refreshTokenResult["access_token"] as string; + settings.RefreshToken = (string)refreshTokenResult["refresh_token"] as string; + + object seconds = refreshTokenResult["expires_in"]; + if (seconds != null) { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); + } } - public string TokenType { - get; - set; + + /// + /// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings + /// Will upate the access token, refresh token, expire date + /// + /// + public static void GenerateAccessToken(OAuth2Settings settings) { + IDictionary data = new Dictionary(); + data.Add("refresh_token", settings.RefreshToken); + data.Add("client_id", settings.ClientId); + data.Add("client_secret", settings.ClientSecret); + data.Add("grant_type", "refresh_token"); + + string accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings); + // gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp + // "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", + // "expires_in":3920, + // "token_type":"Bearer", + + IDictionary accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); + settings.AccessToken = (string)accessTokenResult["access_token"] as string; + object seconds = accessTokenResult["expires_in"]; + if (seconds != null) { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); + } } - public string RefreshToken { - get; - set; + + /// + /// Authenticate via a local server by using the LocalServerCodeReceiver + /// If this works, immediately generate a refresh token afterwards, otherwise this throws an exception + /// + /// OAuth2Settings with the Auth / Token url etc + public static void AuthenticateViaLocalServer(OAuth2Settings settings) { + var codeReceiver = new LocalServerCodeReceiver(); + IDictionary result = codeReceiver.ReceiveCode(settings); + + string code; + if (result.TryGetValue("code", out code)) { + GenerateRefreshToken(code, settings); + } + string error; + if (result.TryGetValue("error", out error)) { + if ("access_denied" == error) { + throw new UnauthorizedAccessException("Access denied"); + } else { + throw new Exception(error); + } + } + } + + /// + /// Simple helper to add the Authorization Bearer header + /// + /// WebRequest + /// OAuth2Settings + public static void AddOAuth2Credentials(HttpWebRequest webRequest, OAuth2Settings settings) { + if (!string.IsNullOrEmpty(settings.AccessToken)) { + webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken); + } } } } From 5d8ad4d02137b552a47cdb656eba9978ceb8a12d Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 15 Apr 2015 13:59:36 +0200 Subject: [PATCH 07/20] FEATURE-838: Added support for dragging an image from google image search results directly into the editor. --- GreenshotPlugin/Core/NetworkHelper.cs | 46 ++++++++++++++++++++---- GreenshotPlugin/Core/StringExtensions.cs | 14 ++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index 7840cd06c..fcd258e30 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -86,6 +86,26 @@ namespace GreenshotPlugin.Core { return null; } + /// + /// Download the uri into a memorystream, without catching exceptions + /// + /// Of an image + /// MemoryStream which is already seeked to 0 + public static MemoryStream GetAsMemoryStream(string url) { + HttpWebRequest request = CreateWebRequest(url); + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { + MemoryStream memoryStream = new MemoryStream(); + using (Stream responseStream = response.GetResponseStream()) { + if (responseStream != null) { + responseStream.CopyTo(memoryStream); + } + // Make sure it can be used directly + memoryStream.Seek(0, SeekOrigin.Begin); + } + return memoryStream; + } + } + /// /// Download the uri to Bitmap /// @@ -93,15 +113,27 @@ namespace GreenshotPlugin.Core { /// Bitmap public static Image DownloadImage(string url) { try { - HttpWebRequest request = CreateWebRequest(url); - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - if (request.HaveResponse) { - using (Stream responseStream = response.GetResponseStream()) { - if (responseStream != null) { - using (Image image = Image.FromStream(responseStream)) { - return ImageHelper.Clone(image, PixelFormat.Format32bppArgb); + string content; + using (MemoryStream memoryStream = GetAsMemoryStream(url)) { + try { + using (Image image = Image.FromStream(memoryStream)) { + return ImageHelper.Clone(image, PixelFormat.Format32bppArgb); + } + } catch (Exception) { + // If we arrive here, the image loading didn't work, try to see if the response has a http(s) URL to an image and just take this instead. + using (StreamReader streamReader = new StreamReader(memoryStream, Encoding.UTF8, true)) { + content = streamReader.ReadLine(); + } + Regex imageUrlRegex = new Regex(@"(http|https)://.*(\.png|\.gif|\.jpg|\.tiff|\.jpeg|\.bmp)"); + Match match = imageUrlRegex.Match(content); + if (match.Success) { + using (MemoryStream memoryStream2 = GetAsMemoryStream(match.Value)) { + using (Image image = Image.FromStream(memoryStream2)) { + return ImageHelper.Clone(image, PixelFormat.Format32bppArgb); + } } } + throw; } } } catch (Exception e) { diff --git a/GreenshotPlugin/Core/StringExtensions.cs b/GreenshotPlugin/Core/StringExtensions.cs index bb4f433a2..9cbec97b4 100644 --- a/GreenshotPlugin/Core/StringExtensions.cs +++ b/GreenshotPlugin/Core/StringExtensions.cs @@ -148,5 +148,19 @@ namespace GreenshotPlugin.Core { return returnValue; } + + /// + /// Read "streamextensions" :) + /// + /// Stream + /// Stream + public static void CopyTo(this Stream input, Stream output) { + byte[] buffer = new byte[16 * 1024]; // Fairly arbitrary size + int bytesRead; + + while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0) { + output.Write(buffer, 0, bytesRead); + } + } } } From eed5b661164818dd8e415318e4af95243f1f7cb7 Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 15 Apr 2015 14:00:13 +0200 Subject: [PATCH 08/20] Fix readme.txt.template --- Greenshot/releases/additional_files/readme.txt.template | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Greenshot/releases/additional_files/readme.txt.template b/Greenshot/releases/additional_files/readme.txt.template index dbb83dbde..ab4eee5e9 100644 --- a/Greenshot/releases/additional_files/readme.txt.template +++ b/Greenshot/releases/additional_files/readme.txt.template @@ -13,6 +13,9 @@ Bugs Resolved: * BUG-1769: Switched to OAuth 2 for Picasa Authentication, * BUG-1770: Fix problems when a font doesn't want to draw itself. +Features: +* FEATURE-838: Added support for dragging an image from google image search results directly into the editor. + 1.2.5.19-RELEASE-63412d3b9e29 From 7938ab5dad6b60fa7cfed42cf6bdc9baaf1dbadc Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 17 Apr 2015 11:06:57 +0200 Subject: [PATCH 09/20] This commit is made due to BUG-1775. Although it might not fix it, it ensures that every window has an icon and only than when it loaded. Also added improved error handling. --- Greenshot/Forms/AboutForm.cs | 3 --- Greenshot/Forms/BugReportForm.cs | 1 - Greenshot/Forms/CaptureForm.Designer.cs | 2 +- Greenshot/Forms/CaptureForm.cs | 3 ++- Greenshot/Forms/DropShadowSettingsForm.cs | 1 - Greenshot/Forms/ImageEditorForm.cs | 2 -- Greenshot/Forms/MainForm.cs | 1 - Greenshot/Forms/PrintOptionsDialog.cs | 1 - Greenshot/Forms/ResizeSettingsForm.cs | 1 - Greenshot/Forms/SettingsForm.cs | 1 - Greenshot/Forms/TornEdgeSettingsForm.cs | 1 - Greenshot/Helpers/CaptureHelper.cs | 12 +++++++++--- GreenshotBoxPlugin/Forms/SettingsForm.cs | 2 -- GreenshotDropboxPlugin/Forms/SettingsForm.cs | 2 +- GreenshotExternalCommandPlugin/SettingsForm.cs | 1 - GreenshotExternalCommandPlugin/SettingsFormDetail.cs | 1 - GreenshotFlickrPlugin/Forms/FlickrForm.cs | 1 + GreenshotFlickrPlugin/Forms/SettingsForm.cs | 1 - GreenshotImgurPlugin/Forms/ImgurForm.cs | 4 +++- GreenshotImgurPlugin/Forms/ImgurHistory.cs | 1 - GreenshotImgurPlugin/Forms/SettingsForm.cs | 1 - GreenshotJiraPlugin/Forms/SettingsForm.cs | 1 - GreenshotOCRPlugin/OCRForm.cs | 4 +++- GreenshotOCRPlugin/SettingsForm.cs | 1 - GreenshotPhotobucketPlugin/Forms/SettingsForm.cs | 1 - GreenshotPicasaPlugin/Forms/SettingsForm.cs | 1 - GreenshotPlugin/Controls/AnimatingForm.cs | 9 ++++++++- GreenshotPlugin/Controls/GreenshotForm.cs | 5 ++++- GreenshotPlugin/Controls/QualityDialog.cs | 1 - 29 files changed, 32 insertions(+), 34 deletions(-) diff --git a/Greenshot/Forms/AboutForm.cs b/Greenshot/Forms/AboutForm.cs index 1fef03d31..64934dea0 100644 --- a/Greenshot/Forms/AboutForm.cs +++ b/Greenshot/Forms/AboutForm.cs @@ -146,9 +146,6 @@ namespace Greenshot { // Only use double-buffering when we are NOT in a Terminal Server session DoubleBuffered = !isTerminalServerSession; - // Not needed for a Tool Window, but still for the task manager it's important - Icon = GreenshotResources.getGreenshotIcon(); - // Use the self drawn image, first we create the background to be the backcolor (as we animate from this) gBitmap = ImageHelper.CreateEmpty(90, 90, PixelFormat.Format24bppRgb, BackColor, 96, 96); pictureBox1.Image = gBitmap; diff --git a/Greenshot/Forms/BugReportForm.cs b/Greenshot/Forms/BugReportForm.cs index 8850a4ea8..dd1112acf 100644 --- a/Greenshot/Forms/BugReportForm.cs +++ b/Greenshot/Forms/BugReportForm.cs @@ -31,7 +31,6 @@ namespace Greenshot.Forms { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - Icon = GreenshotResources.getGreenshotIcon(); WindowDetails.ToForeground(Handle); } diff --git a/Greenshot/Forms/CaptureForm.Designer.cs b/Greenshot/Forms/CaptureForm.Designer.cs index 2722f29b0..72deb4ffd 100644 --- a/Greenshot/Forms/CaptureForm.Designer.cs +++ b/Greenshot/Forms/CaptureForm.Designer.cs @@ -61,7 +61,7 @@ namespace Greenshot.Forms { this.Cursor = System.Windows.Forms.Cursors.Cross; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.Name = "CaptureForm"; - this.ShowIcon = false; + this.ShowIcon = true; this.ShowInTaskbar = false; this.TopMost = true; this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.CaptureFormKeyDown); diff --git a/Greenshot/Forms/CaptureForm.cs b/Greenshot/Forms/CaptureForm.cs index e16291510..998b27618 100644 --- a/Greenshot/Forms/CaptureForm.cs +++ b/Greenshot/Forms/CaptureForm.cs @@ -123,6 +123,7 @@ namespace Greenshot.Forms { LOG.Debug("Closing captureform"); WindowDetails.UnregisterIgnoreHandle(Handle); } + /// /// This creates the capture form /// @@ -130,7 +131,7 @@ namespace Greenshot.Forms { /// public CaptureForm(ICapture capture, List windows) { if (_currentForm != null) { - LOG.Debug("Found currentForm, Closing already opened CaptureForm"); + LOG.Warn("Found currentForm, Closing already opened CaptureForm"); _currentForm.Close(); _currentForm = null; Application.DoEvents(); diff --git a/Greenshot/Forms/DropShadowSettingsForm.cs b/Greenshot/Forms/DropShadowSettingsForm.cs index 2eee65565..1952c6058 100644 --- a/Greenshot/Forms/DropShadowSettingsForm.cs +++ b/Greenshot/Forms/DropShadowSettingsForm.cs @@ -31,7 +31,6 @@ namespace Greenshot.Forms { public DropShadowSettingsForm(DropShadowEffect effect) { this.effect = effect; InitializeComponent(); - this.Icon = GreenshotResources.getGreenshotIcon(); ShowSettings(); } diff --git a/Greenshot/Forms/ImageEditorForm.cs b/Greenshot/Forms/ImageEditorForm.cs index 1562d642d..c5cc2ddba 100644 --- a/Greenshot/Forms/ImageEditorForm.cs +++ b/Greenshot/Forms/ImageEditorForm.cs @@ -162,8 +162,6 @@ namespace Greenshot { } private void updateUI() { - Icon = GreenshotResources.getGreenshotIcon(); - // Disable access to the settings, for feature #3521446 preferencesToolStripMenuItem.Visible = !coreConfiguration.DisableSettings; toolStripSeparator12.Visible = !coreConfiguration.DisableSettings; diff --git a/Greenshot/Forms/MainForm.cs b/Greenshot/Forms/MainForm.cs index 81e7e9165..5e91d2147 100644 --- a/Greenshot/Forms/MainForm.cs +++ b/Greenshot/Forms/MainForm.cs @@ -359,7 +359,6 @@ namespace Greenshot { throw; } notifyIcon.Icon = GreenshotResources.getGreenshotIcon(); - Icon = GreenshotResources.getGreenshotIcon(); // Disable access to the settings, for feature #3521446 contextmenu_settings.Visible = !_conf.DisableSettings; diff --git a/Greenshot/Forms/PrintOptionsDialog.cs b/Greenshot/Forms/PrintOptionsDialog.cs index 87e6fc7bb..1175bf1dc 100644 --- a/Greenshot/Forms/PrintOptionsDialog.cs +++ b/Greenshot/Forms/PrintOptionsDialog.cs @@ -33,7 +33,6 @@ namespace Greenshot.Forms { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - Icon = GreenshotResources.getGreenshotIcon(); checkbox_dontaskagain.Checked = false; } diff --git a/Greenshot/Forms/ResizeSettingsForm.cs b/Greenshot/Forms/ResizeSettingsForm.cs index c3441be33..db8cac328 100644 --- a/Greenshot/Forms/ResizeSettingsForm.cs +++ b/Greenshot/Forms/ResizeSettingsForm.cs @@ -37,7 +37,6 @@ namespace Greenshot.Forms { public ResizeSettingsForm(ResizeEffect effect) { this.effect = effect; InitializeComponent(); - this.Icon = GreenshotResources.getGreenshotIcon(); value_pixel = Language.GetString("editor_resize_pixel"); value_percent = Language.GetString("editor_resize_percent"); combobox_width.Items.Add(value_pixel); diff --git a/Greenshot/Forms/SettingsForm.cs b/Greenshot/Forms/SettingsForm.cs index 819c3d573..b64fdfaf0 100644 --- a/Greenshot/Forms/SettingsForm.cs +++ b/Greenshot/Forms/SettingsForm.cs @@ -57,7 +57,6 @@ namespace Greenshot { protected override void OnLoad(EventArgs e) { base.OnLoad(e); - Icon = GreenshotResources.getGreenshotIcon(); // Fix for Vista/XP differences if (Environment.OSVersion.Version.Major >= 6) { diff --git a/Greenshot/Forms/TornEdgeSettingsForm.cs b/Greenshot/Forms/TornEdgeSettingsForm.cs index edd57a266..63bae471a 100644 --- a/Greenshot/Forms/TornEdgeSettingsForm.cs +++ b/Greenshot/Forms/TornEdgeSettingsForm.cs @@ -30,7 +30,6 @@ namespace Greenshot.Forms { public TornEdgeSettingsForm(TornEdgeEffect effect) { this.effect = effect; InitializeComponent(); - this.Icon = GreenshotResources.getGreenshotIcon(); ShowSettings(); } diff --git a/Greenshot/Helpers/CaptureHelper.cs b/Greenshot/Helpers/CaptureHelper.cs index 943a00d26..301b97c14 100644 --- a/Greenshot/Helpers/CaptureHelper.cs +++ b/Greenshot/Helpers/CaptureHelper.cs @@ -967,7 +967,13 @@ namespace Greenshot.Helpers { //} using (CaptureForm captureForm = new CaptureForm(_capture, _windows)) { - DialogResult result = captureForm.ShowDialog(); + // Make sure the form is hidden after showing, even if an exception occurs, so all errors will be shown + DialogResult result = DialogResult.Cancel; + try { + result = captureForm.ShowDialog(MainForm.Instance); + } finally { + captureForm.Hide(); + } if (result == DialogResult.OK) { _selectedCaptureWindow = captureForm.SelectedCaptureWindow; _captureRect = captureForm.CaptureRectangle; @@ -975,11 +981,11 @@ namespace Greenshot.Helpers { if (_selectedCaptureWindow != null) { _capture.CaptureDetails.Title = _selectedCaptureWindow.Text; } - + if (_captureRect.Height > 0 && _captureRect.Width > 0) { // Take the captureRect, this already is specified as bitmap coordinates _capture.Crop(_captureRect); - + // save for re-capturing later and show recapture context menu option // Important here is that the location needs to be offsetted back to screen coordinates! Rectangle tmpRectangle = _captureRect; diff --git a/GreenshotBoxPlugin/Forms/SettingsForm.cs b/GreenshotBoxPlugin/Forms/SettingsForm.cs index 2e860a665..07b32373d 100644 --- a/GreenshotBoxPlugin/Forms/SettingsForm.cs +++ b/GreenshotBoxPlugin/Forms/SettingsForm.cs @@ -39,8 +39,6 @@ namespace GreenshotBoxPlugin { InitializeComponent(); AcceptButton = buttonOK; CancelButton = buttonCancel; - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); - } } } diff --git a/GreenshotDropboxPlugin/Forms/SettingsForm.cs b/GreenshotDropboxPlugin/Forms/SettingsForm.cs index 99d6738b8..280ded7f1 100644 --- a/GreenshotDropboxPlugin/Forms/SettingsForm.cs +++ b/GreenshotDropboxPlugin/Forms/SettingsForm.cs @@ -18,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + using System; using System.Windows.Forms; using GreenshotDropboxPlugin.Forms; @@ -39,7 +40,6 @@ namespace GreenshotDropboxPlugin { InitializeComponent(); AcceptButton = buttonOK; CancelButton = buttonCancel; - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); } } diff --git a/GreenshotExternalCommandPlugin/SettingsForm.cs b/GreenshotExternalCommandPlugin/SettingsForm.cs index 025cdb68e..247e38b59 100644 --- a/GreenshotExternalCommandPlugin/SettingsForm.cs +++ b/GreenshotExternalCommandPlugin/SettingsForm.cs @@ -37,7 +37,6 @@ namespace ExternalCommand { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); AcceptButton = buttonOk; CancelButton = buttonCancel; UpdateView(); diff --git a/GreenshotExternalCommandPlugin/SettingsFormDetail.cs b/GreenshotExternalCommandPlugin/SettingsFormDetail.cs index 1fff7fc62..ae9d4b030 100644 --- a/GreenshotExternalCommandPlugin/SettingsFormDetail.cs +++ b/GreenshotExternalCommandPlugin/SettingsFormDetail.cs @@ -38,7 +38,6 @@ namespace ExternalCommand { public SettingsFormDetail(string commando) { InitializeComponent(); - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); AcceptButton = buttonOk; CancelButton = buttonCancel; this.commando = commando; diff --git a/GreenshotFlickrPlugin/Forms/FlickrForm.cs b/GreenshotFlickrPlugin/Forms/FlickrForm.cs index 5aaf18262..59358436e 100644 --- a/GreenshotFlickrPlugin/Forms/FlickrForm.cs +++ b/GreenshotFlickrPlugin/Forms/FlickrForm.cs @@ -18,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + using System; using GreenshotPlugin.Controls; diff --git a/GreenshotFlickrPlugin/Forms/SettingsForm.cs b/GreenshotFlickrPlugin/Forms/SettingsForm.cs index 92bd39374..0835e2ea5 100644 --- a/GreenshotFlickrPlugin/Forms/SettingsForm.cs +++ b/GreenshotFlickrPlugin/Forms/SettingsForm.cs @@ -37,7 +37,6 @@ namespace GreenshotFlickrPlugin { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - Icon = GreenshotResources.getGreenshotIcon(); CancelButton = buttonCancel; AcceptButton = buttonOK; } diff --git a/GreenshotImgurPlugin/Forms/ImgurForm.cs b/GreenshotImgurPlugin/Forms/ImgurForm.cs index 15de31281..d476f9313 100644 --- a/GreenshotImgurPlugin/Forms/ImgurForm.cs +++ b/GreenshotImgurPlugin/Forms/ImgurForm.cs @@ -18,13 +18,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + using System; +using GreenshotPlugin.Controls; namespace GreenshotImgurPlugin { /// /// This class is needed for design-time resolving of the language files /// - public class ImgurForm : GreenshotPlugin.Controls.GreenshotForm { + public class ImgurForm : GreenshotForm { public ImgurForm() : base() { } } diff --git a/GreenshotImgurPlugin/Forms/ImgurHistory.cs b/GreenshotImgurPlugin/Forms/ImgurHistory.cs index 25c501a1e..597134103 100644 --- a/GreenshotImgurPlugin/Forms/ImgurHistory.cs +++ b/GreenshotImgurPlugin/Forms/ImgurHistory.cs @@ -53,7 +53,6 @@ namespace GreenshotImgurPlugin { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); AcceptButton = finishedButton; CancelButton = finishedButton; // Init sorting diff --git a/GreenshotImgurPlugin/Forms/SettingsForm.cs b/GreenshotImgurPlugin/Forms/SettingsForm.cs index bda6270c6..8a35fc06e 100644 --- a/GreenshotImgurPlugin/Forms/SettingsForm.cs +++ b/GreenshotImgurPlugin/Forms/SettingsForm.cs @@ -35,7 +35,6 @@ namespace GreenshotImgurPlugin { InitializeComponent(); CancelButton = buttonCancel; AcceptButton = buttonOK; - this.Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); ImgurUtils.LoadHistory(); diff --git a/GreenshotJiraPlugin/Forms/SettingsForm.cs b/GreenshotJiraPlugin/Forms/SettingsForm.cs index 383ec7fbf..1ecaecfbf 100644 --- a/GreenshotJiraPlugin/Forms/SettingsForm.cs +++ b/GreenshotJiraPlugin/Forms/SettingsForm.cs @@ -34,7 +34,6 @@ namespace GreenshotJiraPlugin { InitializeComponent(); AcceptButton = buttonOK; CancelButton = buttonCancel; - Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); } } } diff --git a/GreenshotOCRPlugin/OCRForm.cs b/GreenshotOCRPlugin/OCRForm.cs index e7051dccc..f6fe326a2 100644 --- a/GreenshotOCRPlugin/OCRForm.cs +++ b/GreenshotOCRPlugin/OCRForm.cs @@ -19,11 +19,13 @@ * along with this program. If not, see . */ +using GreenshotPlugin.Controls; + namespace GreenshotOCR { /// /// This class is needed for design-time resolving of the language files /// - public class OCRForm : GreenshotPlugin.Controls.GreenshotForm { + public class OCRForm : GreenshotForm { public OCRForm() : base() { } } diff --git a/GreenshotOCRPlugin/SettingsForm.cs b/GreenshotOCRPlugin/SettingsForm.cs index 9d34347c3..88b1d029a 100644 --- a/GreenshotOCRPlugin/SettingsForm.cs +++ b/GreenshotOCRPlugin/SettingsForm.cs @@ -37,7 +37,6 @@ namespace GreenshotOCR { InitializeComponent(); AcceptButton = buttonOK; CancelButton = buttonCancel; - this.Icon = GreenshotResources.getGreenshotIcon(); comboBox_languages.Items.Clear(); int index=0; diff --git a/GreenshotPhotobucketPlugin/Forms/SettingsForm.cs b/GreenshotPhotobucketPlugin/Forms/SettingsForm.cs index 83298e841..560a29b39 100644 --- a/GreenshotPhotobucketPlugin/Forms/SettingsForm.cs +++ b/GreenshotPhotobucketPlugin/Forms/SettingsForm.cs @@ -35,7 +35,6 @@ namespace GreenshotPhotobucketPlugin { InitializeComponent(); AcceptButton = buttonOK; CancelButton = buttonCancel; - Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); } } } diff --git a/GreenshotPicasaPlugin/Forms/SettingsForm.cs b/GreenshotPicasaPlugin/Forms/SettingsForm.cs index 2916a5630..edb7597f0 100644 --- a/GreenshotPicasaPlugin/Forms/SettingsForm.cs +++ b/GreenshotPicasaPlugin/Forms/SettingsForm.cs @@ -38,7 +38,6 @@ namespace GreenshotPicasaPlugin { InitializeComponent(); CancelButton = buttonCancel; AcceptButton = buttonOK; - Icon = GreenshotPlugin.Core.GreenshotResources.getGreenshotIcon(); } } diff --git a/GreenshotPlugin/Controls/AnimatingForm.cs b/GreenshotPlugin/Controls/AnimatingForm.cs index 17897a5c2..17f8baa38 100644 --- a/GreenshotPlugin/Controls/AnimatingForm.cs +++ b/GreenshotPlugin/Controls/AnimatingForm.cs @@ -18,17 +18,20 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + using System; using System.Windows.Forms; using GreenshotPlugin.Core; using GreenshotPlugin.UnmanagedHelpers; using Greenshot.IniFile; +using log4net; namespace GreenshotPlugin.Controls { /// /// Extend this Form to have the possibility for animations on your form /// public class AnimatingForm : GreenshotForm { + private static readonly ILog LOG = LogManager.GetLogger(typeof(AnimatingForm)); private const int DEFAULT_VREFRESH = 60; private int vRefresh = 0; private Timer timer = null; @@ -110,7 +113,11 @@ namespace GreenshotPlugin.Controls { /// /// void timer_Tick(object sender, EventArgs e) { - Animate(); + try { + Animate(); + } catch (Exception ex) { + LOG.Warn("An exception occured while animating:", ex); + } } /// diff --git a/GreenshotPlugin/Controls/GreenshotForm.cs b/GreenshotPlugin/Controls/GreenshotForm.cs index 62c01bb9f..a872c83c9 100644 --- a/GreenshotPlugin/Controls/GreenshotForm.cs +++ b/GreenshotPlugin/Controls/GreenshotForm.cs @@ -131,6 +131,9 @@ namespace GreenshotPlugin.Controls { } protected override void OnLoad(EventArgs e) { + // Every GreenshotForm should have it's default icon + // And it might not ne needed for a Tool Window, but still for the task manager / switcher it's important + Icon = GreenshotResources.getGreenshotIcon(); if (!DesignMode) { if (!applyLanguageManually) { ApplyLanguage(); @@ -413,7 +416,7 @@ namespace GreenshotPlugin.Controls { if (section != null) { IniValue iniValue = null; if (!section.Values.TryGetValue(configBindable.PropertyName, out iniValue)) { - LOG.WarnFormat("Wrong property '{0}' configured for field '{1}'",configBindable.PropertyName,field.Name); + LOG.DebugFormat("Wrong property '{0}' configured for field '{1}'",configBindable.PropertyName,field.Name); continue; } diff --git a/GreenshotPlugin/Controls/QualityDialog.cs b/GreenshotPlugin/Controls/QualityDialog.cs index 338a396d1..35b9a53d7 100644 --- a/GreenshotPlugin/Controls/QualityDialog.cs +++ b/GreenshotPlugin/Controls/QualityDialog.cs @@ -41,7 +41,6 @@ namespace GreenshotPlugin.Controls { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - Icon = GreenshotResources.getGreenshotIcon(); checkBox_reduceColors.Checked = Settings.ReduceColors; trackBarJpegQuality.Enabled = OutputFormat.jpg.Equals(outputSettings.Format); From 9d7299e5ea1648cf7db242e84493e445388bab05 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 17 Apr 2015 11:20:30 +0200 Subject: [PATCH 10/20] Improving the logic to make sure new windows are moved to the front, so the user can't miss them. --- Greenshot/Forms/BugReportForm.cs | 2 +- Greenshot/Forms/CaptureForm.cs | 2 +- GreenshotPlugin/Controls/GreenshotForm.cs | 65 ++++++++++++++-------- GreenshotPlugin/Controls/OAuthLoginForm.cs | 7 +++ GreenshotPlugin/Controls/QualityDialog.cs | 2 +- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/Greenshot/Forms/BugReportForm.cs b/Greenshot/Forms/BugReportForm.cs index dd1112acf..77cc8929f 100644 --- a/Greenshot/Forms/BugReportForm.cs +++ b/Greenshot/Forms/BugReportForm.cs @@ -31,7 +31,7 @@ namespace Greenshot.Forms { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - WindowDetails.ToForeground(Handle); + BringToFront = true; } public BugReportForm(string bugText) : this() { diff --git a/Greenshot/Forms/CaptureForm.cs b/Greenshot/Forms/CaptureForm.cs index 998b27618..1347e215b 100644 --- a/Greenshot/Forms/CaptureForm.cs +++ b/Greenshot/Forms/CaptureForm.cs @@ -177,7 +177,7 @@ namespace Greenshot.Forms { ResumeLayout(); // Fix missing focus - WindowDetails.ToForeground(Handle); + BringToFront = true; TopMost = true; } diff --git a/GreenshotPlugin/Controls/GreenshotForm.cs b/GreenshotPlugin/Controls/GreenshotForm.cs index a872c83c9..ef15c0d76 100644 --- a/GreenshotPlugin/Controls/GreenshotForm.cs +++ b/GreenshotPlugin/Controls/GreenshotForm.cs @@ -38,11 +38,11 @@ namespace GreenshotPlugin.Controls { protected static CoreConfiguration coreConfiguration; private static IDictionary reflectionCache = new Dictionary(); private IComponentChangeService m_changeService; - private bool isDesignModeLanguageSet = false; - private bool applyLanguageManually = false; - private bool storeFieldsManually = false; - private IDictionary designTimeControls; - private IDictionary designTimeToolStripItems; + private bool _isDesignModeLanguageSet = false; + private bool _applyLanguageManually = false; + private bool _storeFieldsManually = false; + private IDictionary _designTimeControls; + private IDictionary _designTimeToolStripItems; static GreenshotForm() { if (!IsInDesignMode) { @@ -68,29 +68,37 @@ namespace GreenshotPlugin.Controls { protected bool ManualLanguageApply { get { - return applyLanguageManually; + return _applyLanguageManually; } set { - applyLanguageManually = value; + _applyLanguageManually = value; } } protected bool ManualStoreFields { get { - return storeFieldsManually; + return _storeFieldsManually; } set { - storeFieldsManually = value; + _storeFieldsManually = value; } } + /// + /// When this is set, the form will be brought to the foreground as soon as it is shown. + /// + protected bool BringToFront { + get; + set; + } + /// /// Code to initialize the language etc during design time /// protected void InitializeForDesigner() { if (DesignMode) { - designTimeControls = new Dictionary(); - designTimeToolStripItems = new Dictionary(); + _designTimeControls = new Dictionary(); + _designTimeToolStripItems = new Dictionary(); try { ITypeResolutionService typeResService = GetService(typeof(ITypeResolutionService)) as ITypeResolutionService; @@ -119,8 +127,8 @@ namespace GreenshotPlugin.Controls { /// protected override void OnPaint(PaintEventArgs e) { if (DesignMode) { - if (!isDesignModeLanguageSet) { - isDesignModeLanguageSet = true; + if (!_isDesignModeLanguageSet) { + _isDesignModeLanguageSet = true; try { ApplyLanguage(); } catch (Exception) { @@ -135,7 +143,7 @@ namespace GreenshotPlugin.Controls { // And it might not ne needed for a Tool Window, but still for the task manager / switcher it's important Icon = GreenshotResources.getGreenshotIcon(); if (!DesignMode) { - if (!applyLanguageManually) { + if (!_applyLanguageManually) { ApplyLanguage(); } FillFields(); @@ -148,12 +156,23 @@ namespace GreenshotPlugin.Controls { } } + /// + /// Make sure the form is visible, if this is wanted + /// + /// EventArgs + protected override void OnShown(EventArgs e) { + base.OnShown(e); + if (BringToFront) { + WindowDetails.ToForeground(Handle); + } + } + /// /// check if the form was closed with an OK, if so store the values in the GreenshotControls /// /// protected override void OnClosed(EventArgs e) { - if (!DesignMode && !storeFieldsManually) { + if (!DesignMode && !_storeFieldsManually) { if (DialogResult == DialogResult.OK) { LOG.Info("Form was closed with OK: storing field values."); StoreFields(); @@ -233,17 +252,17 @@ namespace GreenshotPlugin.Controls { if (ce.Component != null && ((IComponent)ce.Component).Site != null) { Control control = ce.Component as Control; if (control != null) { - if (!designTimeControls.ContainsKey(control.Name)) { - designTimeControls.Add(control.Name, control); + if (!_designTimeControls.ContainsKey(control.Name)) { + _designTimeControls.Add(control.Name, control); } else { - designTimeControls[control.Name] = control; + _designTimeControls[control.Name] = control; } } else if (ce.Component is ToolStripItem) { ToolStripItem item = ce.Component as ToolStripItem; - if (!designTimeControls.ContainsKey(item.Name)) { - designTimeToolStripItems.Add(item.Name, item); + if (!_designTimeControls.ContainsKey(item.Name)) { + _designTimeToolStripItems.Add(item.Name, item); } else { - designTimeToolStripItems[item.Name] = item; + _designTimeToolStripItems[item.Name] = item; } } } @@ -363,10 +382,10 @@ namespace GreenshotPlugin.Controls { } if (DesignMode) { - foreach (Control designControl in designTimeControls.Values) { + foreach (Control designControl in _designTimeControls.Values) { ApplyLanguage(designControl); } - foreach (ToolStripItem designToolStripItem in designTimeToolStripItems.Values) { + foreach (ToolStripItem designToolStripItem in _designTimeToolStripItems.Values) { ApplyLanguage(designToolStripItem); } } diff --git a/GreenshotPlugin/Controls/OAuthLoginForm.cs b/GreenshotPlugin/Controls/OAuthLoginForm.cs index 59e8f4088..f619927e9 100644 --- a/GreenshotPlugin/Controls/OAuthLoginForm.cs +++ b/GreenshotPlugin/Controls/OAuthLoginForm.cs @@ -60,7 +60,14 @@ namespace GreenshotPlugin.Controls { browser.ScriptErrorsSuppressed = false; browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted); browser.Navigate(new Uri(authorizationLink)); + } + /// + /// Make sure the form is visible + /// + /// EventArgs + protected override void OnShown(EventArgs e) { + base.OnShown(e); WindowDetails.ToForeground(Handle); } diff --git a/GreenshotPlugin/Controls/QualityDialog.cs b/GreenshotPlugin/Controls/QualityDialog.cs index 35b9a53d7..bba47e895 100644 --- a/GreenshotPlugin/Controls/QualityDialog.cs +++ b/GreenshotPlugin/Controls/QualityDialog.cs @@ -47,7 +47,7 @@ namespace GreenshotPlugin.Controls { trackBarJpegQuality.Value = Settings.JPGQuality; textBoxJpegQuality.Enabled = OutputFormat.jpg.Equals(outputSettings.Format); textBoxJpegQuality.Text = Settings.JPGQuality.ToString(); - WindowDetails.ToForeground(Handle); + BringToFront = true; } void Button_okClick(object sender, EventArgs e) { From 1f80d56b1093821523a1f54825cb4e70f9fcac9f Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 17 Apr 2015 15:44:27 +0200 Subject: [PATCH 11/20] Reused new OAuth 2 code for the Box plug-in, this was possible by adding the embedded browser. Also refactored code to be more readable, and have more reuse. Fixed problems with Picasa upload and pressing cancel on the PleaseWaitForm. [skip ci] --- Greenshot/Forms/BugReportForm.cs | 2 +- Greenshot/Forms/CaptureForm.cs | 2 +- Greenshot/Help/HelpFileLoader.cs | 34 ++- GreenshotBoxPlugin/BoxConfiguration.cs | 41 ++- GreenshotBoxPlugin/BoxUtils.cs | 159 ++++------- GreenshotImgurPlugin/ImgurUtils.cs | 14 +- GreenshotPicasaPlugin/PicasaConfiguration.cs | 12 +- GreenshotPicasaPlugin/PicasaUtils.cs | 20 +- GreenshotPlugin/Controls/GreenshotForm.cs | 4 +- .../Controls/OAuthLoginForm.Designer.cs | 44 ++-- GreenshotPlugin/Controls/OAuthLoginForm.cs | 45 ++-- GreenshotPlugin/Controls/QualityDialog.cs | 2 +- GreenshotPlugin/Core/NetworkHelper.cs | 50 +++- GreenshotPlugin/Core/OAuthHelper.cs | 247 ++++++++++++++---- GreenshotPlugin/Core/SourceForgeHelper.cs | 4 +- GreenshotPlugin/IniFile/IniSection.cs | 5 +- 16 files changed, 408 insertions(+), 277 deletions(-) diff --git a/Greenshot/Forms/BugReportForm.cs b/Greenshot/Forms/BugReportForm.cs index 77cc8929f..2f6fb0247 100644 --- a/Greenshot/Forms/BugReportForm.cs +++ b/Greenshot/Forms/BugReportForm.cs @@ -31,7 +31,7 @@ namespace Greenshot.Forms { // The InitializeComponent() call is required for Windows Forms designer support. // InitializeComponent(); - BringToFront = true; + ToFront = true; } public BugReportForm(string bugText) : this() { diff --git a/Greenshot/Forms/CaptureForm.cs b/Greenshot/Forms/CaptureForm.cs index 1347e215b..528cd6515 100644 --- a/Greenshot/Forms/CaptureForm.cs +++ b/Greenshot/Forms/CaptureForm.cs @@ -177,7 +177,7 @@ namespace Greenshot.Forms { ResumeLayout(); // Fix missing focus - BringToFront = true; + ToFront = true; TopMost = true; } diff --git a/Greenshot/Help/HelpFileLoader.cs b/Greenshot/Help/HelpFileLoader.cs index fe79de7b4..93759a850 100644 --- a/Greenshot/Help/HelpFileLoader.cs +++ b/Greenshot/Help/HelpFileLoader.cs @@ -1,10 +1,22 @@ /* - * Created by SharpDevelop. - * User: jens - * Date: 09.04.2012 - * Time: 19:24 + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom * - * To change this template use Tools | Options | Coding | Edit Standard Headers. + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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 GreenshotPlugin.Core; @@ -28,7 +40,7 @@ namespace Greenshot.Help } public static void LoadHelp() { - string uri = findOnlineHelpUrl(Language.CurrentLanguage); + string uri = FindOnlineHelpUrl(Language.CurrentLanguage); if(uri == null) { uri = Language.HelpFilePath; } @@ -36,7 +48,7 @@ namespace Greenshot.Help } /// URL of help file in selected ietf, or (if not present) default ietf, or null (if not present, too. probably indicating that there is no internet connection) - private static string findOnlineHelpUrl(string currentIETF) { + private static string FindOnlineHelpUrl(string currentIETF) { string ret = null; string extHelpUrlForCurrrentIETF = EXT_HELP_URL; @@ -45,12 +57,12 @@ namespace Greenshot.Help extHelpUrlForCurrrentIETF += currentIETF.ToLower() + "/"; } - HttpStatusCode? httpStatusCode = getHttpStatus(extHelpUrlForCurrrentIETF); + HttpStatusCode? httpStatusCode = GetHttpStatus(extHelpUrlForCurrrentIETF); if(httpStatusCode == HttpStatusCode.OK) { ret = extHelpUrlForCurrrentIETF; } else if(httpStatusCode != null && !extHelpUrlForCurrrentIETF.Equals(EXT_HELP_URL)) { LOG.DebugFormat("Localized online help not found at {0}, will try {1} as fallback", extHelpUrlForCurrrentIETF, EXT_HELP_URL); - httpStatusCode = getHttpStatus(EXT_HELP_URL); + httpStatusCode = GetHttpStatus(EXT_HELP_URL); if(httpStatusCode == HttpStatusCode.OK) { ret = EXT_HELP_URL; } else { @@ -68,9 +80,9 @@ namespace Greenshot.Help /// /// URL for which the HTTP status is to be checked /// An HTTP status code, or null if there is none (probably indicating that there is no internet connection available - private static HttpStatusCode? getHttpStatus(string url) { + private static HttpStatusCode? GetHttpStatus(string url) { try { - HttpWebRequest req = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); + HttpWebRequest req = NetworkHelper.CreateWebRequest(url); HttpWebResponse res = (HttpWebResponse)req.GetResponse(); return res.StatusCode; } catch(WebException e) { diff --git a/GreenshotBoxPlugin/BoxConfiguration.cs b/GreenshotBoxPlugin/BoxConfiguration.cs index e82773acf..b7632f865 100644 --- a/GreenshotBoxPlugin/BoxConfiguration.cs +++ b/GreenshotBoxPlugin/BoxConfiguration.cs @@ -18,9 +18,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + using System.Windows.Forms; using Greenshot.IniFile; using GreenshotPlugin.Core; +using System; namespace GreenshotBoxPlugin { /// @@ -38,10 +40,43 @@ namespace GreenshotBoxPlugin { public bool AfterUploadLinkToClipBoard; [IniProperty("UseSharedLink", Description = "Use the shared link, instead of the private, on the clipboard", DefaultValue = "True")] - public bool UseSharedLink; + public bool UseSharedLink { + get; + set; + } + [IniProperty("FolderId", Description = "Folder ID to upload to, only change if you know what you are doing!", DefaultValue = "0")] + public string FolderId { + get; + set; + } - [IniProperty("BoxToken", Description = "Token.", DefaultValue = "")] - public string BoxToken; + [IniProperty("AddFilename", Description = "Is the filename passed on to Box", DefaultValue = "False")] + public bool AddFilename { + get; + set; + } + + [IniProperty("RefreshToken", Description = "Box authorization refresh Token", Encrypted = true)] + public string RefreshToken { + get; + set; + } + + /// + /// Not stored + /// + public string AccessToken { + get; + set; + } + + /// + /// Not stored + /// + public DateTimeOffset AccessTokenExpires { + get; + set; + } /// /// A form for token diff --git a/GreenshotBoxPlugin/BoxUtils.cs b/GreenshotBoxPlugin/BoxUtils.cs index bca46b0bf..2cb343c06 100644 --- a/GreenshotBoxPlugin/BoxUtils.cs +++ b/GreenshotBoxPlugin/BoxUtils.cs @@ -18,16 +18,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -using System; + +using Greenshot.IniFile; +using GreenshotPlugin.Core; using System.Collections.Generic; using System.Drawing; -using System.Net; -using System.Text; -using Greenshot.IniFile; -using GreenshotPlugin.Controls; -using GreenshotPlugin.Core; -using System.Runtime.Serialization.Json; using System.IO; +using System.Runtime.Serialization.Json; +using System.Text; namespace GreenshotBoxPlugin { @@ -37,101 +35,24 @@ namespace GreenshotBoxPlugin { public static class BoxUtils { private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(BoxUtils)); private static readonly BoxConfiguration Config = IniConfig.GetIniSection(); - private const string RedirectUri = "https://www.box.com/home/"; private const string UploadFileUri = "https://upload.box.com/api/2.0/files/content"; - private const string AuthorizeUri = "https://www.box.com/api/oauth2/authorize"; - private const string TokenUri = "https://www.box.com/api/oauth2/token"; private const string FilesUri = "https://www.box.com/api/2.0/files/{0}"; - private static bool Authorize() { - string authorizeUrl = string.Format("{0}?client_id={1}&response_type=code&state=dropboxplugin&redirect_uri={2}", AuthorizeUri, BoxCredentials.ClientId, RedirectUri); - - OAuthLoginForm loginForm = new OAuthLoginForm("Box Authorize", new Size(1060, 600), authorizeUrl, RedirectUri); - loginForm.ShowDialog(); - if (!loginForm.isOk) { - return false; - } - var callbackParameters = loginForm.CallbackParameters; - if (callbackParameters == null || !callbackParameters.ContainsKey("code")) { - return false; - } - - string authorizationResponse = PostAndReturn(new Uri(TokenUri), string.Format("grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}", callbackParameters["code"], BoxCredentials.ClientId, BoxCredentials.ClientSecret)); - var authorization = JsonSerializer.Deserialize(authorizationResponse); - - Config.BoxToken = authorization.AccessToken; - IniConfig.Save(); - return true; - } - /// - /// Download a url response as string - /// - /// An Uri to specify the download location - /// - /// string with the file content - public static string PostAndReturn(Uri url, string postMessage) { - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url); - webRequest.Method = "POST"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - webRequest.ContentType = "application/x-www-form-urlencoded"; - byte[] data = Encoding.UTF8.GetBytes(postMessage); - using (var requestStream = webRequest.GetRequestStream()) { - requestStream.Write(data, 0, data.Length); - } - return NetworkHelper.GetResponse(webRequest); - } - - /// - /// Upload parameters by post - /// - /// - /// - /// response - public static string HttpPost(string url, IDictionary parameters) { - var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - webRequest.Method = "POST"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken); - NetworkHelper.WriteMultipartFormData(webRequest, parameters); - - return NetworkHelper.GetResponse(webRequest); - } - - /// - /// Upload file by PUT + /// Put string /// /// /// + /// OAuth2Settings /// response - public static string HttpPut(string url, string content) { - var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - webRequest.Method = "PUT"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken); + public static string HttpPut(string url, string content, OAuth2Settings settings) { + var webRequest= OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.PUT, url, settings); + byte[] data = Encoding.UTF8.GetBytes(content); using (var requestStream = webRequest.GetRequestStream()) { requestStream.Write(data, 0, data.Length); } - return NetworkHelper.GetResponse(webRequest); - } - - - /// - /// Get REST request - /// - /// - /// response - public static string HttpGet(string url) { - var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - webRequest.Method = "GET"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken); - return NetworkHelper.GetResponse(webRequest); + return NetworkHelper.GetResponseAsString(webRequest); } /// @@ -143,45 +64,53 @@ namespace GreenshotBoxPlugin { /// Filename of box upload /// url to uploaded image public static string UploadToBox(SurfaceContainer image, string title, string filename) { - while (true) { - const string folderId = "0"; - if (string.IsNullOrEmpty(Config.BoxToken)) { - if (!Authorize()) { - return null; - } - } + // Fill the OAuth2Settings + OAuth2Settings settings = new OAuth2Settings(); + + settings.AuthUrlPattern = "https://www.box.com/api/oauth2/authorize?client_id={ClientId}&response_type={response_type}&state{State}&redirect_uri={RedirectUrl}"; + settings.TokenUrlPattern = "https://www.box.com/api/oauth2/token"; + settings.CloudServiceName = "Box"; + settings.ClientId = BoxCredentials.ClientId; + settings.ClientSecret = BoxCredentials.ClientSecret; + settings.RedirectUrl = "https://www.box.com/home/"; + settings.BrowserSize = new Size(1060, 600); + settings.AuthorizeMode = OAuth2AuthorizeMode.EmbeddedBrowser; + + // Copy the settings from the config, which is kept in memory and on the disk + settings.RefreshToken = Config.RefreshToken; + settings.AccessToken = Config.AccessToken; + settings.AccessTokenExpires = Config.AccessTokenExpires; + + try { + var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, FilesUri, settings); IDictionary parameters = new Dictionary(); - parameters.Add("filename", image); - parameters.Add("parent_id", folderId); - - var response = ""; - try { - response = HttpPost(UploadFileUri, parameters); - } catch (WebException ex) { - if (ex.Status == WebExceptionStatus.ProtocolError) { - Config.BoxToken = null; - continue; - } + if (Config.AddFilename) { + parameters.Add("filename", image); } + parameters.Add("parent_id", Config.FolderId); + + NetworkHelper.WriteMultipartFormData(webRequest, parameters); + + var response = NetworkHelper.GetResponseAsString(webRequest); LOG.DebugFormat("Box response: {0}", response); - // Check if the token is wrong - if ("wrong auth token".Equals(response)) { - Config.BoxToken = null; - IniConfig.Save(); - continue; - } var upload = JsonSerializer.Deserialize(response); if (upload == null || upload.Entries == null || upload.Entries.Count == 0) return null; if (Config.UseSharedLink) { - string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}"); + string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}", settings); var file = JsonSerializer.Deserialize(filesResponse); return file.SharedLink.Url; } return string.Format("http://www.box.com/files/0/f/0/1/f_{0}", upload.Entries[0].Id); + } finally { + // Copy the settings back to the config, so they are stored. + Config.RefreshToken = settings.RefreshToken; + Config.AccessToken = settings.AccessToken; + Config.AccessTokenExpires = settings.AccessTokenExpires; + Config.IsDirty = true; } } } diff --git a/GreenshotImgurPlugin/ImgurUtils.cs b/GreenshotImgurPlugin/ImgurUtils.cs index ca03e84fe..833657964 100644 --- a/GreenshotImgurPlugin/ImgurUtils.cs +++ b/GreenshotImgurPlugin/ImgurUtils.cs @@ -122,8 +122,7 @@ namespace GreenshotImgurPlugin { if (config.AnonymousAccess) { // add key, we only use the other parameters for the AnonymousAccess otherParameters.Add("key", IMGUR_ANONYMOUS_API_KEY); - HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters)); - webRequest.Method = "POST"; + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters), HTTPMethod.POST); webRequest.ContentType = "image/" + outputSettings.Format.ToString(); webRequest.ServicePoint.Expect100Continue = false; @@ -194,8 +193,7 @@ namespace GreenshotImgurPlugin { return; } LOG.InfoFormat("Retrieving Imgur image for {0} with url {1}", imgurInfo.Hash, imgurInfo.SmallSquare); - HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(imgurInfo.SmallSquare); - webRequest.Method = "GET"; + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(imgurInfo.SmallSquare, HTTPMethod.GET); webRequest.ServicePoint.Expect100Continue = false; SetClientId(webRequest); using (WebResponse response = webRequest.GetResponse()) { @@ -215,8 +213,7 @@ namespace GreenshotImgurPlugin { public static ImgurInfo RetrieveImgurInfo(string hash, string deleteHash) { string url = config.ImgurApiUrl + "/image/" + hash; LOG.InfoFormat("Retrieving Imgur info for {0} with url {1}", hash, url); - HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - webRequest.Method = "GET"; + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET); webRequest.ServicePoint.Expect100Continue = false; SetClientId(webRequest); string responseString; @@ -250,10 +247,7 @@ namespace GreenshotImgurPlugin { try { string url = config.ImgurApiUrl + "/delete/" + imgurInfo.DeleteHash; - HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url); - - //webRequest.Method = "DELETE"; - webRequest.Method = "GET"; + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET); webRequest.ServicePoint.Expect100Continue = false; SetClientId(webRequest); string responseString; diff --git a/GreenshotPicasaPlugin/PicasaConfiguration.cs b/GreenshotPicasaPlugin/PicasaConfiguration.cs index c392dce2b..c7b81e3c2 100644 --- a/GreenshotPicasaPlugin/PicasaConfiguration.cs +++ b/GreenshotPicasaPlugin/PicasaConfiguration.cs @@ -37,12 +37,6 @@ namespace GreenshotPicasaPlugin { [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Picasa link to clipboard.", DefaultValue = "true")] public bool AfterUploadLinkToClipBoard; - [IniProperty("RefreshToken", Description = "Picasa refresh Token", Encrypted = true)] - public string RefreshToken { - get; - set; - } - [IniProperty("AddFilename", Description = "Is the filename passed on to Picasa", DefaultValue = "False")] public bool AddFilename { get; @@ -61,6 +55,12 @@ namespace GreenshotPicasaPlugin { set; } + [IniProperty("RefreshToken", Description = "Picasa authorization refresh Token", Encrypted = true)] + public string RefreshToken { + get; + set; + } + /// /// Not stored /// diff --git a/GreenshotPicasaPlugin/PicasaUtils.cs b/GreenshotPicasaPlugin/PicasaUtils.cs index 04be91926..8ea4d57c6 100644 --- a/GreenshotPicasaPlugin/PicasaUtils.cs +++ b/GreenshotPicasaPlugin/PicasaUtils.cs @@ -50,10 +50,12 @@ namespace GreenshotPicasaPlugin { OAuth2Settings settings = new OAuth2Settings(); settings.AuthUrlPattern = AuthUrl; settings.TokenUrlPattern = TokenUrl; + settings.CloudServiceName = "Picasa"; settings.AdditionalAttributes.Add("response_type", "code"); settings.AdditionalAttributes.Add("scope", PicasaScope); settings.ClientId = PicasaCredentials.ClientId; settings.ClientSecret = PicasaCredentials.ClientSecret; + settings.AuthorizeMode = OAuth2AuthorizeMode.LocalServer; // Copy the settings from the config, which is kept in memory and on the disk settings.RefreshToken = Config.RefreshToken; @@ -61,27 +63,14 @@ namespace GreenshotPicasaPlugin { settings.AccessTokenExpires = Config.AccessTokenExpires; try { - // Get Refresh / Access token - if (string.IsNullOrEmpty(settings.RefreshToken)) { - OAuth2Helper.AuthenticateViaLocalServer(settings); - } - - if (settings.IsAccessTokenExpired) { - OAuth2Helper.GenerateAccessToken(settings); - } - - var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum)); - webRequest.Method = "POST"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - OAuth2Helper.AddOAuth2Credentials(webRequest, settings); + var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum), settings); if (Config.AddFilename) { webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename)); } SurfaceContainer container = new SurfaceContainer(surfaceToUpload, outputSettings, filename); container.Upload(webRequest); - string response = NetworkHelper.GetResponse(webRequest); + string response = NetworkHelper.GetResponseAsString(webRequest); return ParseResponse(response); } finally { @@ -89,6 +78,7 @@ namespace GreenshotPicasaPlugin { Config.RefreshToken = settings.RefreshToken; Config.AccessToken = settings.AccessToken; Config.AccessTokenExpires = settings.AccessTokenExpires; + Config.IsDirty = true; } } diff --git a/GreenshotPlugin/Controls/GreenshotForm.cs b/GreenshotPlugin/Controls/GreenshotForm.cs index ef15c0d76..ab151675a 100644 --- a/GreenshotPlugin/Controls/GreenshotForm.cs +++ b/GreenshotPlugin/Controls/GreenshotForm.cs @@ -87,7 +87,7 @@ namespace GreenshotPlugin.Controls { /// /// When this is set, the form will be brought to the foreground as soon as it is shown. /// - protected bool BringToFront { + protected bool ToFront { get; set; } @@ -162,7 +162,7 @@ namespace GreenshotPlugin.Controls { /// EventArgs protected override void OnShown(EventArgs e) { base.OnShown(e); - if (BringToFront) { + if (ToFront) { WindowDetails.ToForeground(Handle); } } diff --git a/GreenshotPlugin/Controls/OAuthLoginForm.Designer.cs b/GreenshotPlugin/Controls/OAuthLoginForm.Designer.cs index d81bb942e..f7d47f4c5 100644 --- a/GreenshotPlugin/Controls/OAuthLoginForm.Designer.cs +++ b/GreenshotPlugin/Controls/OAuthLoginForm.Designer.cs @@ -44,37 +44,37 @@ namespace GreenshotPlugin.Controls { /// the contents of this method with the code editor. /// private void InitializeComponent() { - this.addressTextBox = new System.Windows.Forms.TextBox(); - this.browser = new ExtendedWebBrowser(); + this._addressTextBox = new System.Windows.Forms.TextBox(); + this._browser = new ExtendedWebBrowser(); this.SuspendLayout(); // - // addressTextBox + // _addressTextBox // - this.addressTextBox.Cursor = System.Windows.Forms.Cursors.Arrow; - this.addressTextBox.Dock = System.Windows.Forms.DockStyle.Top; - this.addressTextBox.Enabled = false; - this.addressTextBox.Location = new System.Drawing.Point(0, 0); - this.addressTextBox.Name = "addressTextBox"; - this.addressTextBox.Size = new System.Drawing.Size(595, 20); - this.addressTextBox.TabIndex = 3; - this.addressTextBox.TabStop = false; + this._addressTextBox.Cursor = System.Windows.Forms.Cursors.Arrow; + this._addressTextBox.Dock = System.Windows.Forms.DockStyle.Top; + this._addressTextBox.Enabled = false; + this._addressTextBox.Location = new System.Drawing.Point(0, 0); + this._addressTextBox.Name = "addressTextBox"; + this._addressTextBox.Size = new System.Drawing.Size(595, 20); + this._addressTextBox.TabIndex = 3; + this._addressTextBox.TabStop = false; // - // browser + // _browser // - this.browser.Dock = System.Windows.Forms.DockStyle.Fill; - this.browser.Location = new System.Drawing.Point(0, 20); - this.browser.MinimumSize = new System.Drawing.Size(100, 100); - this.browser.Name = "browser"; - this.browser.Size = new System.Drawing.Size(595, 295); - this.browser.TabIndex = 4; + this._browser.Dock = System.Windows.Forms.DockStyle.Fill; + this._browser.Location = new System.Drawing.Point(0, 20); + this._browser.MinimumSize = new System.Drawing.Size(100, 100); + this._browser.Name = "browser"; + this._browser.Size = new System.Drawing.Size(595, 295); + this._browser.TabIndex = 4; // // OAuthLoginForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(595, 315); - this.Controls.Add(this.browser); - this.Controls.Add(this.addressTextBox); + this.Controls.Add(this._browser); + this.Controls.Add(this._addressTextBox); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "OAuthLoginForm"; @@ -85,8 +85,8 @@ namespace GreenshotPlugin.Controls { #endregion - private System.Windows.Forms.TextBox addressTextBox; - private ExtendedWebBrowser browser; + private System.Windows.Forms.TextBox _addressTextBox; + private ExtendedWebBrowser _browser; } } diff --git a/GreenshotPlugin/Controls/OAuthLoginForm.cs b/GreenshotPlugin/Controls/OAuthLoginForm.cs index f619927e9..ce721def5 100644 --- a/GreenshotPlugin/Controls/OAuthLoginForm.cs +++ b/GreenshotPlugin/Controls/OAuthLoginForm.cs @@ -35,31 +35,33 @@ namespace GreenshotPlugin.Controls { /// public partial class OAuthLoginForm : Form { private static readonly ILog LOG = LogManager.GetLogger(typeof(OAuthLoginForm)); - private string callbackUrl = null; - private IDictionary callbackParameters = null; + private string _callbackUrl = null; + private IDictionary _callbackParameters = null; public IDictionary CallbackParameters { - get { return callbackParameters; } + get { + return _callbackParameters; + } } - public bool isOk { + public bool IsOk { get { return DialogResult == DialogResult.OK; } } public OAuthLoginForm(string browserTitle, Size size, string authorizationLink, string callbackUrl) { - this.callbackUrl = callbackUrl; + _callbackUrl = callbackUrl; InitializeComponent(); ClientSize = size; Icon = GreenshotResources.getGreenshotIcon(); Text = browserTitle; - addressTextBox.Text = authorizationLink; + _addressTextBox.Text = authorizationLink; // The script errors are suppressed by using the ExtendedWebBrowser - browser.ScriptErrorsSuppressed = false; - browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted); - browser.Navigate(new Uri(authorizationLink)); + _browser.ScriptErrorsSuppressed = false; + _browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Browser_DocumentCompleted); + _browser.Navigate(new Uri(authorizationLink)); } /// @@ -71,33 +73,24 @@ namespace GreenshotPlugin.Controls { WindowDetails.ToForeground(Handle); } - private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { - LOG.DebugFormat("document completed with url: {0}", browser.Url); - checkUrl(); - } - private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) { - LOG.DebugFormat("Navigating to url: {0}", browser.Url); - addressTextBox.Text = e.Url.ToString(); + private void Browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { + LOG.DebugFormat("document completed with url: {0}", _browser.Url); + CheckUrl(); } - private void browser_Navigated(object sender, WebBrowserNavigatedEventArgs e) { - LOG.DebugFormat("Navigated to url: {0}", browser.Url); - checkUrl(); - } - - private void checkUrl() { - if (browser.Url.ToString().StartsWith(callbackUrl)) { - string queryParams = browser.Url.Query; + private void CheckUrl() { + if (_browser.Url.ToString().StartsWith(_callbackUrl)) { + string queryParams = _browser.Url.Query; if (queryParams.Length > 0) { queryParams = NetworkHelper.UrlDecode(queryParams); //Store the Token and Token Secret - callbackParameters = NetworkHelper.ParseQueryString(queryParams); + _callbackParameters = NetworkHelper.ParseQueryString(queryParams); } DialogResult = DialogResult.OK; } } - private void addressTextBox_KeyPress(object sender, KeyPressEventArgs e) { + private void AddressTextBox_KeyPress(object sender, KeyPressEventArgs e) { //Cancel the key press so the user can't enter a new url e.Handled = true; } diff --git a/GreenshotPlugin/Controls/QualityDialog.cs b/GreenshotPlugin/Controls/QualityDialog.cs index bba47e895..c601c8e88 100644 --- a/GreenshotPlugin/Controls/QualityDialog.cs +++ b/GreenshotPlugin/Controls/QualityDialog.cs @@ -47,7 +47,7 @@ namespace GreenshotPlugin.Controls { trackBarJpegQuality.Value = Settings.JPGQuality; textBoxJpegQuality.Enabled = OutputFormat.jpg.Equals(outputSettings.Format); textBoxJpegQuality.Text = Settings.JPGQuality.ToString(); - BringToFront = true; + ToFront = true; } void Button_okClick(object sender, EventArgs e) { diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index fcd258e30..4ff398158 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -33,6 +33,17 @@ using System.Text; using System.Text.RegularExpressions; namespace GreenshotPlugin.Core { + /// + /// HTTP Method to make sure we have the correct method + /// + public enum HTTPMethod { + GET, + POST, + PUT, + DELETE, + HEAD + }; + /// /// Description of NetworkHelper. /// @@ -55,10 +66,7 @@ namespace GreenshotPlugin.Core { /// string with the file content public static string GetAsString(Uri uri) { HttpWebRequest webRequest = CreateWebRequest(uri); - webRequest.Method = "GET"; - webRequest.KeepAlive = true; - webRequest.Credentials = CredentialCache.DefaultCredentials; - return GetResponse(webRequest); + return GetResponseAsString(CreateWebRequest(uri)); } /// @@ -143,14 +151,36 @@ namespace GreenshotPlugin.Core { } /// - /// Helper method to create a web request, eventually with proxy + /// Helper method to create a web request with a lot of default settings /// /// string with uri to connect to /// WebRequest public static HttpWebRequest CreateWebRequest(string uri) { return CreateWebRequest(new Uri(uri)); } + + /// + /// Helper method to create a web request with a lot of default settings + /// + /// string with uri to connect to + /// /// Method to use + /// WebRequest + public static HttpWebRequest CreateWebRequest(string uri, HTTPMethod method) { + return CreateWebRequest(new Uri(uri), method); + } + /// + /// Helper method to create a web request with a lot of default settings + /// + /// Uri with uri to connect to + /// Method to use + /// WebRequest + public static HttpWebRequest CreateWebRequest(Uri uri, HTTPMethod method) { + HttpWebRequest webRequest = CreateWebRequest(uri); + webRequest.Method = method.ToString(); + return webRequest; + } + /// /// Helper method to create a web request, eventually with proxy /// @@ -164,6 +194,10 @@ namespace GreenshotPlugin.Core { // BUG-1655: Fix that Greenshot always uses the default proxy even if the "use default proxy" checkbox is unset webRequest.Proxy = null; } + // Make sure the default credentials are available + webRequest.Credentials = CredentialCache.DefaultCredentials; + + // Allow redirect, this is usually needed so that we don't get a problem when a service moves webRequest.AllowAutoRedirect = true; // Set default timeouts webRequest.Timeout = Config.WebRequestTimeout*1000; @@ -365,7 +399,7 @@ namespace GreenshotPlugin.Core { using (var streamWriter = new StreamWriter(requestStream, Encoding.UTF8)) { streamWriter.Write(urlEncoded); } - return GetResponse(webRequest); + return GetResponseAsString(webRequest); } /// @@ -388,7 +422,7 @@ namespace GreenshotPlugin.Core { /// The request object. /// The response data. /// TODO: This method should handle the StatusCode better! - public static string GetResponse(HttpWebRequest webRequest) { + public static string GetResponseAsString(HttpWebRequest webRequest) { string responseData = null; try { HttpWebResponse response = (HttpWebResponse) webRequest.GetResponse(); @@ -429,7 +463,7 @@ namespace GreenshotPlugin.Core { public static DateTime GetLastModified(Uri uri) { try { HttpWebRequest webRequest = CreateWebRequest(uri); - webRequest.Method = "HEAD"; + webRequest.Method = HTTPMethod.HEAD.ToString(); HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); LOG.DebugFormat("RSS feed was updated at {0}", webResponse.LastModified); return webResponse.LastModified; diff --git a/GreenshotPlugin/Core/OAuthHelper.cs b/GreenshotPlugin/Core/OAuthHelper.cs index a327264a7..2a8995131 100644 --- a/GreenshotPlugin/Core/OAuthHelper.cs +++ b/GreenshotPlugin/Core/OAuthHelper.cs @@ -46,9 +46,15 @@ namespace GreenshotPlugin.Core { } /// - /// Used HTTP Method, this is for the OAuth 1.x protocol + /// Specify the autorize mode that is used to get the token from the cloud service. /// - public enum HTTPMethod { GET, POST, PUT, DELETE }; + public enum OAuth2AuthorizeMode { + Unknown, // Will give an exception, caller needs to specify another value + LocalServer, // Will specify a redirect URL to http://localhost:port/authorize, while having a HttpListener + MonitorTitle, // Not implemented yet: Will monitor for title changes + Pin, // Not implemented yet: Will ask the user to enter the shown PIN + EmbeddedBrowser // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect + } /// /// Settings for the OAuth 2 protocol @@ -58,6 +64,28 @@ namespace GreenshotPlugin.Core { AdditionalAttributes = new Dictionary(); // Create a default state State = Guid.NewGuid().ToString(); + AuthorizeMode = OAuth2AuthorizeMode.Unknown; + } + + public OAuth2AuthorizeMode AuthorizeMode { + get; + set; + } + + /// + /// Specify the name of the cloud service, so it can be used in window titles, logs etc + /// + public string CloudServiceName { + get; + set; + } + + /// + /// Specify the size of the embedded Browser, if using this + /// + public Size BrowserSize { + get; + set; } /// @@ -242,7 +270,7 @@ namespace GreenshotPlugin.Core { private readonly string _consumerKey; private readonly string _consumerSecret; - // default browser size + // default _browser size private Size _browserSize = new Size(864, 587); private string _loginTitle = "Authorize Greenshot access"; @@ -479,7 +507,7 @@ namespace GreenshotPlugin.Core { LOG.DebugFormat("Opening AuthorizationLink: {0}", AuthorizationLink); OAuthLoginForm oAuthLoginForm = new OAuthLoginForm(LoginTitle, BrowserSize, AuthorizationLink, CallbackUrl); oAuthLoginForm.ShowDialog(); - if (oAuthLoginForm.isOk) { + if (oAuthLoginForm.IsOk) { if (oAuthLoginForm.CallbackParameters != null) { string tokenValue; if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_TOKEN_KEY, out tokenValue)) { @@ -812,11 +840,9 @@ namespace GreenshotPlugin.Core { } } // Create webrequest - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestURL); - webRequest.Method = method.ToString(); + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestURL, method); webRequest.ServicePoint.Expect100Continue = false; webRequest.UserAgent = _userAgent; - webRequest.Timeout = 100000; if (UseHTTPHeadersForAuthorization && authHeader != null) { LOG.DebugFormat("Authorization: OAuth {0}", authHeader); @@ -860,7 +886,7 @@ namespace GreenshotPlugin.Core { string responseData; try { - responseData = NetworkHelper.GetResponse(webRequest); + responseData = NetworkHelper.GetResponseAsString(webRequest); LOG.DebugFormat("Response: {0}", responseData); } catch (Exception ex) { LOG.Error("Couldn't retrieve response: ", ex); @@ -879,6 +905,7 @@ namespace GreenshotPlugin.Core { /// public class LocalServerCodeReceiver { private static readonly ILog LOG = LogManager.GetLogger(typeof(LocalServerCodeReceiver)); + private readonly ManualResetEvent _ready = new ManualResetEvent(true); private string _loopbackCallback = "http://localhost:{0}/authorize/"; /// @@ -896,9 +923,9 @@ namespace GreenshotPlugin.Core { private string _closePageResponse = @" -OAuth 2.0 Authentication Token Received +OAuth 2.0 Authentication CloudServiceName -Greenshot received verification code. You can close this browser / tab if it is not closed itself... +Greenshot received information from CloudServiceName. You can close this browser / tab if it is not closed itself...