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); + } } } }