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 @@ - +