diff --git a/Directory.Build.props b/Directory.Build.props index 1a2e9d3a4..a15044b52 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -65,17 +65,17 @@ - - $(Box_ClientId) + + $(Box13_ClientId) - - $(Box_ClientSecret) + + $(Box13_ClientSecret) - - $(DropBox_ClientId) + + $(DropBox13_ClientId) - - $(DropBox_ClientSecret) + + $(DropBox13_ClientSecret) $(Flickr_ClientId) @@ -83,11 +83,11 @@ $(Flickr_ClientSecret) - - $(Imgur_ClientId) + + $(Imgur13_ClientId) - - $(Imgur_ClientSecret) + + $(Imgur13_ClientSecret) $(Photobucket_ClientId) diff --git a/Greenshot.sln b/Greenshot.sln index 1c0d74d59..f815631e2 100644 --- a/Greenshot.sln +++ b/Greenshot.sln @@ -34,7 +34,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreenshotDropboxPlugin", "G EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreenshotFlickrPlugin", "GreenshotFlickrPlugin\GreenshotFlickrPlugin.csproj", "{7EC72A5A-D73A-4B4B-9CA1-2216C7D92D5E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreenshotPicasaPlugin", "GreenshotPicasaPlugin\GreenshotPicasaPlugin.csproj", "{1893A2E4-A78A-4713-A8E7-E70058DABEE0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreenshotGooglePhotosPlugin", "GreenshotGooglePhotosPlugin\GreenshotGooglePhotosPlugin.csproj", "{1893A2E4-A78A-4713-A8E7-E70058DABEE0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreenshotOfficePlugin", "GreenshotOfficePlugin\GreenshotOfficePlugin.csproj", "{92599C09-FF29-4ABD-B6E6-C48ECD781BAB}" EndProject diff --git a/Greenshot/Drawing/CursorContainer.cs b/Greenshot/Drawing/CursorContainer.cs index 15078bd76..7b3078510 100644 --- a/Greenshot/Drawing/CursorContainer.cs +++ b/Greenshot/Drawing/CursorContainer.cs @@ -105,10 +105,6 @@ namespace Greenshot.Drawing { cursor.DrawStretched(graphics, Bounds); } - public override Size DefaultSize { - get { - return cursor.Size; - } - } + public override Size DefaultSize => cursor?.Size ?? new Size(16, 16); } } diff --git a/Greenshot/Drawing/DrawableContainer.cs b/Greenshot/Drawing/DrawableContainer.cs index 60eb1e98d..bc0973300 100644 --- a/Greenshot/Drawing/DrawableContainer.cs +++ b/Greenshot/Drawing/DrawableContainer.cs @@ -548,23 +548,11 @@ namespace Greenshot.Drawing return ScaleHelper.ShapeAngleRoundBehavior.Instance; } - public virtual bool HasContextMenu { - get { - return true; - } - } + public virtual bool HasContextMenu => true; - public virtual bool HasDefaultSize { - get { - return false; - } - } + public virtual bool HasDefaultSize => false; - public virtual Size DefaultSize { - get { - throw new NotSupportedException("Object doesn't have a default size"); - } - } + public virtual Size DefaultSize => throw new NotSupportedException("Object doesn't have a default size"); /// /// Allows to override the initializing of the fields, so we can actually have our own defaults diff --git a/Greenshot/Drawing/IconContainer.cs b/Greenshot/Drawing/IconContainer.cs index b35ce28ff..6d5c90986 100644 --- a/Greenshot/Drawing/IconContainer.cs +++ b/Greenshot/Drawing/IconContainer.cs @@ -62,7 +62,7 @@ namespace Greenshot.Drawing { Width = value.Width; Height = value.Height; } - get { return icon; } + get => icon; } /** @@ -78,27 +78,32 @@ namespace Greenshot.Drawing { base.Dispose(disposing); } - public void Load(string filename) { - if (File.Exists(filename)) - { - using Icon fileIcon = new Icon(filename); - Icon = fileIcon; - Log.Debug("Loaded file: " + filename + " with resolution: " + Height + "," + Width); - } + public void Load(string filename) + { + if (!File.Exists(filename)) + { + return; + } + using Icon fileIcon = new Icon(filename); + Icon = fileIcon; + Log.Debug("Loaded file: " + filename + " with resolution: " + Height + "," + Width); } - public override void Draw(Graphics graphics, RenderMode rm) { - if (icon != null) { - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.NearestNeighbor; - graphics.CompositingQuality = CompositingQuality.Default; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.DrawIcon(icon, Bounds); + public override void Draw(Graphics graphics, RenderMode rm) + { + if (icon == null) + { + return; } + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.NearestNeighbor; + graphics.CompositingQuality = CompositingQuality.Default; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.DrawIcon(icon, Bounds); } public override bool HasDefaultSize => true; - public override Size DefaultSize => icon.Size; + public override Size DefaultSize => icon?.Size ?? new Size(16,16); } } diff --git a/Greenshot/Drawing/ImageContainer.cs b/Greenshot/Drawing/ImageContainer.cs index fee072aa1..5840c9507 100644 --- a/Greenshot/Drawing/ImageContainer.cs +++ b/Greenshot/Drawing/ImageContainer.cs @@ -77,11 +77,14 @@ namespace Greenshot.Drawing { AddField(GetType(), FieldType.SHADOW, false); } - protected void BitmapContainer_OnFieldChanged(object sender, FieldChangedEventArgs e) { - if (sender.Equals(this)) { - if (FieldType.SHADOW.Equals(e.Field.FieldType)) { - ChangeShadowField(); - } + protected void BitmapContainer_OnFieldChanged(object sender, FieldChangedEventArgs e) + { + if (!sender.Equals(this)) + { + return; + } + if (FieldType.SHADOW.Equals(e.Field.FieldType)) { + ChangeShadowField(); } } @@ -189,12 +192,14 @@ namespace Greenshot.Drawing { /// This checks if a shadow is already generated /// /// - private void CheckShadow(bool shadow) { - if (shadow && _shadowBitmap == null) - { - using var matrix = new Matrix(); - _shadowBitmap = ImageHelper.ApplyEffect(image, new DropShadowEffect(), matrix); - } + private void CheckShadow(bool shadow) + { + if (!shadow || _shadowBitmap != null) + { + return; + } + using var matrix = new Matrix(); + _shadowBitmap = ImageHelper.ApplyEffect(image, new DropShadowEffect(), matrix); } /// @@ -202,25 +207,28 @@ namespace Greenshot.Drawing { /// /// /// - public override void Draw(Graphics graphics, RenderMode rm) { - if (image != null) { - bool shadow = GetFieldValueAsBool(FieldType.SHADOW); - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + public override void Draw(Graphics graphics, RenderMode rm) + { + if (image == null) + { + return; + } + bool shadow = GetFieldValueAsBool(FieldType.SHADOW); + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - if (shadow) { - CheckShadow(true); - graphics.DrawImage(_shadowBitmap, Bounds); - } else { - graphics.DrawImage(image, Bounds); - } + if (shadow) { + CheckShadow(true); + graphics.DrawImage(_shadowBitmap, Bounds); + } else { + graphics.DrawImage(image, Bounds); } } public override bool HasDefaultSize => true; - public override Size DefaultSize => image.Size; + public override Size DefaultSize => image?.Size ?? new Size(32, 32); } } diff --git a/Greenshot/Forms/AboutForm.Designer.cs b/Greenshot/Forms/AboutForm.Designer.cs index f68fd69ee..4112df177 100644 --- a/Greenshot/Forms/AboutForm.Designer.cs +++ b/Greenshot/Forms/AboutForm.Designer.cs @@ -21,6 +21,7 @@ using System.Windows.Forms; using Greenshot.Helpers; +using GreenshotPlugin.Core; namespace Greenshot.Forms { partial class AboutForm { diff --git a/Greenshot/Helpers/EnvironmentInfo.cs b/Greenshot/Helpers/EnvironmentInfo.cs deleted file mode 100644 index 9d8006416..000000000 --- a/Greenshot/Helpers/EnvironmentInfo.cs +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using GreenshotPlugin.Core; -using GreenshotPlugin.IniFile; -using GreenshotPlugin.UnmanagedHelpers; -using Microsoft.Win32; - -namespace Greenshot.Helpers -{ - /// - /// Description of EnvironmentInfo. - /// - public static class EnvironmentInfo - { - private static bool? _isWindows; - - public static bool IsWindows - { - get - { - if (_isWindows.HasValue) - { - return _isWindows.Value; - } - _isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); - return _isWindows.Value; - } - } - - public static bool IsNet45OrNewer() - { - // Class "ReflectionContext" exists from .NET 4.5 onwards. - return Type.GetType("System.Reflection.ReflectionContext", false) != null; - } - - public static string GetGreenshotVersion(bool shortVersion = false) - { - var executingAssembly = Assembly.GetExecutingAssembly(); - - // Use assembly version - string greenshotVersion = executingAssembly.GetName().Version.ToString(); - - // Use AssemblyFileVersion if available - var assemblyFileVersionAttribute = executingAssembly.GetCustomAttribute(); - if (!string.IsNullOrEmpty(assemblyFileVersionAttribute?.Version)) - { - var assemblyFileVersion = new Version(assemblyFileVersionAttribute.Version); - greenshotVersion = assemblyFileVersion.ToString(3); - } - - if (!shortVersion) - { - // Use AssemblyInformationalVersion if available - var informationalVersionAttribute = executingAssembly.GetCustomAttribute(); - if (!string.IsNullOrEmpty(informationalVersionAttribute?.InformationalVersion)) - { - greenshotVersion = informationalVersionAttribute.InformationalVersion; - } - } - - return greenshotVersion.Replace("+", " - "); - } - - public static string EnvironmentToString(bool newline) - { - StringBuilder environment = new StringBuilder(); - environment.Append("Software version: " + GetGreenshotVersion()); - if (IniConfig.IsPortable) { - environment.Append(" Portable"); - } - environment.Append(" (" + OsInfo.Bits + " bit)"); - - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - environment.Append(".NET runtime version: " + Environment.Version); - if (IsNet45OrNewer()) - { - environment.Append("+"); - - } - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - environment.Append("Time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss zzz")); - - if (IsWindows) - { - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - - environment.Append($"OS: {OsInfo.Name}"); - if (!string.IsNullOrEmpty(OsInfo.Edition)) - { - environment.Append($" {OsInfo.Edition}"); - - } - if (!string.IsNullOrEmpty(OsInfo.ServicePack)) - { - environment.Append($" {OsInfo.ServicePack}"); - - } - environment.Append($" x{OsInfo.Bits}"); - environment.Append($" {OsInfo.VersionString}"); - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - // Get some important information for fixing GDI related Problems - environment.AppendFormat("GDI object count: {0}", User32.GetGuiResourcesGDICount()); - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - environment.AppendFormat("User object count: {0}", User32.GetGuiResourcesUserCount()); - } - else - { - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - environment.AppendFormat("OS: {0}", Environment.OSVersion.Platform); - } - if (newline) - { - environment.AppendLine(); - } - else - { - environment.Append(", "); - } - // TODO: Is this needed? - // environment.AppendFormat("Surface count: {0}", Surface.Count); - - return environment.ToString(); - } - - public static string ExceptionToString(Exception ex) - { - if (ex == null) - return "null\r\n"; - - StringBuilder report = new StringBuilder(); - - report.AppendLine("Exception: " + ex.GetType()); - report.AppendLine("Message: " + ex.Message); - if (ex.Data.Count > 0) - { - report.AppendLine(); - report.AppendLine("Additional Information:"); - foreach (object key in ex.Data.Keys) - { - object data = ex.Data[key]; - if (data != null) - { - report.AppendLine(key + " : " + data); - } - } - } - if (ex is ExternalException externalException) - { - // e.g. COMException - report.AppendLine().AppendLine("ErrorCode: 0x" + externalException.ErrorCode.ToString("X")); - } - - report.AppendLine().AppendLine("Stack:").AppendLine(ex.StackTrace); - - if (ex is ReflectionTypeLoadException reflectionTypeLoadException) - { - report.AppendLine().AppendLine("LoaderExceptions: "); - foreach (Exception cbE in reflectionTypeLoadException.LoaderExceptions) - { - report.AppendLine(cbE.Message); - } - } - - if (ex.InnerException != null) - { - report.AppendLine("--- InnerException: ---"); - report.AppendLine(ExceptionToString(ex.InnerException)); - } - return report.ToString(); - } - - public static string BuildReport(Exception exception) - { - StringBuilder exceptionText = new StringBuilder(); - exceptionText.AppendLine(EnvironmentToString(true)); - exceptionText.AppendLine(ExceptionToString(exception)); - exceptionText.AppendLine("Configuration dump:"); - - return exceptionText.ToString(); - } - } - - /// - /// Provides detailed information about the host operating system. - /// Code is available at: http://www.csharp411.com/determine-windows-version-and-edition-with-c/ - /// - public static class OsInfo - { - /// - /// Determines if the current application is 32 or 64-bit. - /// - public static int Bits => IntPtr.Size * 8; - - private static string _sEdition; - - /// - /// Gets the edition of the operating system running on this computer. - /// - public static string Edition - { - get - { - if (_sEdition != null) - { - return _sEdition; //***** RETURN *****// - } - - string edition = string.Empty; - - OperatingSystem osVersion = Environment.OSVersion; - OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); - - if (GetVersionEx(ref osVersionInfo)) - { - int majorVersion = osVersion.Version.Major; - int minorVersion = osVersion.Version.Minor; - byte productType = osVersionInfo.ProductType; - ushort suiteMask = osVersionInfo.SuiteMask; - - if (majorVersion == 4) - { - if (productType == VER_NT_WORKSTATION) - { - // Windows NT 4.0 Workstation - edition = "Workstation"; - } - else if (productType == VER_NT_SERVER) - { - edition = (suiteMask & VER_SUITE_ENTERPRISE) != 0 ? "Enterprise Server" : "Standard Server"; - } - } - - else if (majorVersion == 5) - { - if (productType == VER_NT_WORKSTATION) - { - if ((suiteMask & VER_SUITE_PERSONAL) != 0) - { - // Windows XP Home Edition - edition = "Home"; - } - else - { - // Windows XP / Windows 2000 Professional - edition = "Professional"; - } - } - else if (productType == VER_NT_SERVER) - { - if (minorVersion == 0) - { - if ((suiteMask & VER_SUITE_DATACENTER) != 0) - { - // Windows 2000 Datacenter Server - edition = "Datacenter Server"; - } - else if ((suiteMask & VER_SUITE_ENTERPRISE) != 0) - { - // Windows 2000 Advanced Server - edition = "Advanced Server"; - } - else - { - // Windows 2000 Server - edition = "Server"; - } - } - else - { - if ((suiteMask & VER_SUITE_DATACENTER) != 0) - { - // Windows Server 2003 Datacenter Edition - edition = "Datacenter"; - } - else if ((suiteMask & VER_SUITE_ENTERPRISE) != 0) - { - // Windows Server 2003 Enterprise Edition - edition = "Enterprise"; - } - else if ((suiteMask & VER_SUITE_BLADE) != 0) - { - // Windows Server 2003 Web Edition - edition = "Web Edition"; - } - else - { - // Windows Server 2003 Standard Edition - edition = "Standard"; - } - } - } - } - - else if (majorVersion == 6) - { - if (GetProductInfo(majorVersion, minorVersion, osVersionInfo.ServicePackMajor, osVersionInfo.ServicePackMinor, out var ed)) - { - switch (ed) - { - case PRODUCT_BUSINESS: - edition = "Business"; - break; - case PRODUCT_BUSINESS_N: - edition = "Business N"; - break; - case PRODUCT_CLUSTER_SERVER: - edition = "HPC Edition"; - break; - case PRODUCT_DATACENTER_SERVER: - edition = "Datacenter Server"; - break; - case PRODUCT_DATACENTER_SERVER_CORE: - edition = "Datacenter Server (core installation)"; - break; - case PRODUCT_ENTERPRISE: - edition = "Enterprise"; - break; - case PRODUCT_ENTERPRISE_N: - edition = "Enterprise N"; - break; - case PRODUCT_ENTERPRISE_SERVER: - edition = "Enterprise Server"; - break; - case PRODUCT_ENTERPRISE_SERVER_CORE: - edition = "Enterprise Server (core installation)"; - break; - case PRODUCT_ENTERPRISE_SERVER_CORE_V: - edition = "Enterprise Server without Hyper-V (core installation)"; - break; - case PRODUCT_ENTERPRISE_SERVER_IA64: - edition = "Enterprise Server for Itanium-based Systems"; - break; - case PRODUCT_ENTERPRISE_SERVER_V: - edition = "Enterprise Server without Hyper-V"; - break; - case PRODUCT_HOME_BASIC: - edition = "Home Basic"; - break; - case PRODUCT_HOME_BASIC_N: - edition = "Home Basic N"; - break; - case PRODUCT_HOME_PREMIUM: - edition = "Home Premium"; - break; - case PRODUCT_HOME_PREMIUM_N: - edition = "Home Premium N"; - break; - case PRODUCT_HYPERV: - edition = "Microsoft Hyper-V Server"; - break; - case PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT: - edition = "Windows Essential Business Management Server"; - break; - case PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING: - edition = "Windows Essential Business Messaging Server"; - break; - case PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY: - edition = "Windows Essential Business Security Server"; - break; - case PRODUCT_SERVER_FOR_SMALLBUSINESS: - edition = "Windows Essential Server Solutions"; - break; - case PRODUCT_SERVER_FOR_SMALLBUSINESS_V: - edition = "Windows Essential Server Solutions without Hyper-V"; - break; - case PRODUCT_SMALLBUSINESS_SERVER: - edition = "Windows Small Business Server"; - break; - case PRODUCT_STANDARD_SERVER: - edition = "Standard Server"; - break; - case PRODUCT_STANDARD_SERVER_CORE: - edition = "Standard Server (core installation)"; - break; - case PRODUCT_STANDARD_SERVER_CORE_V: - edition = "Standard Server without Hyper-V (core installation)"; - break; - case PRODUCT_STANDARD_SERVER_V: - edition = "Standard Server without Hyper-V"; - break; - case PRODUCT_STARTER: - edition = "Starter"; - break; - case PRODUCT_STORAGE_ENTERPRISE_SERVER: - edition = "Enterprise Storage Server"; - break; - case PRODUCT_STORAGE_EXPRESS_SERVER: - edition = "Express Storage Server"; - break; - case PRODUCT_STORAGE_STANDARD_SERVER: - edition = "Standard Storage Server"; - break; - case PRODUCT_STORAGE_WORKGROUP_SERVER: - edition = "Workgroup Storage Server"; - break; - case PRODUCT_UNDEFINED: - edition = "Unknown product"; - break; - case PRODUCT_ULTIMATE: - edition = "Ultimate"; - break; - case PRODUCT_ULTIMATE_N: - edition = "Ultimate N"; - break; - case PRODUCT_WEB_SERVER: - edition = "Web Server"; - break; - case PRODUCT_WEB_SERVER_CORE: - edition = "Web Server (core installation)"; - break; - } - } - } - } - - _sEdition = edition; - return edition; - } - } - - private static string _name; - /// - /// Gets the name of the operating system running on this computer. - /// - public static string Name - { - get - { - if (_name != null) - { - return _name; //***** RETURN *****// - } - - string name = "unknown"; - - OperatingSystem osVersion = Environment.OSVersion; - OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); - if (GetVersionEx(ref osVersionInfo)) - { - int majorVersion = osVersion.Version.Major; - int minorVersion = osVersion.Version.Minor; - byte productType = osVersionInfo.ProductType; - ushort suiteMask = osVersionInfo.SuiteMask; - switch (osVersion.Platform) - { - case PlatformID.Win32Windows: - if (majorVersion == 4) - { - string csdVersion = osVersionInfo.ServicePackVersion; - switch (minorVersion) - { - case 0: - if (csdVersion == "B" || csdVersion == "C") - { - name = "Windows 95 OSR2"; - } - else - { - name = "Windows 95"; - } - break; - case 10: - name = csdVersion == "A" ? "Windows 98 Second Edition" : "Windows 98"; - break; - case 90: - name = "Windows Me"; - break; - } - } - break; - case PlatformID.Win32NT: - switch (majorVersion) - { - case 3: - name = "Windows NT 3.51"; - break; - case 4: - switch (productType) - { - case 1: - name = "Windows NT 4.0"; - break; - case 3: - name = "Windows NT 4.0 Server"; - break; - } - break; - case 5: - switch (minorVersion) - { - case 0: - name = "Windows 2000"; - break; - case 1: - name = suiteMask switch - { - 0x0200 => "Windows XP Professional", - _ => "Windows XP" - }; - break; - case 2: - name = suiteMask switch - { - 0x0200 => "Windows XP Professional x64", - 0x0002 => "Windows Server 2003 Enterprise", - 0x0080 => "Windows Server 2003 Data Center", - 0x0400 => "Windows Server 2003 Web Edition", - 0x8000 => "Windows Home Server", - _ => "Windows Server 2003" - }; - break; - } - break; - case 6: - switch (minorVersion) - { - case 0: - name = productType switch - { - 3 => "Windows Server 2008", - _ => "Windows Vista" - }; - break; - case 1: - name = productType switch - { - 3 => "Windows Server 2008 R2", - _ => "Windows 7" - }; - break; - case 2: - name = "Windows 8"; - break; - case 3: - name = "Windows 8.1"; - break; - } - break; - case 10: - string releaseId = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", "").ToString(); - name = $"Windows 10 {releaseId}"; - break; - } - break; - } - } - - _name = name; - return name; - } - } - - [DllImport("Kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool GetProductInfo( - int osMajorVersion, - int osMinorVersion, - int spMajorVersion, - int spMinorVersion, - out int edition); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetVersionEx(ref OSVERSIONINFOEX osVersionInfo); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private unsafe struct OSVERSIONINFOEX - { - /// - /// The size of this data structure, in bytes. Set this member to sizeof(OSVERSIONINFOEX). - /// - private int _dwOSVersionInfoSize; - - private readonly int _dwMajorVersion; - private readonly int _dwMinorVersion; - private readonly int _dwBuildNumber; - private readonly int _dwPlatformId; - private fixed char _szCSDVersion[128]; - private readonly short _wServicePackMajor; - private readonly short _wServicePackMinor; - private readonly ushort _wSuiteMask; - private readonly byte _wProductType; - private readonly byte _wReserved; - - /// - /// A null-terminated string, such as "Service Pack 3", that indicates the latest Service Pack installed on the system. - /// If no Service Pack has been installed, the string is empty. - /// - public string ServicePackVersion - { - get - { - fixed (char* servicePackVersion = _szCSDVersion) - { - return new string(servicePackVersion); - } - - } - } - - /// - /// The major version number of the latest Service Pack installed on the system. For example, for Service Pack 3, the - /// major version number is 3. - /// If no Service Pack has been installed, the value is zero. - /// - public short ServicePackMajor => _wServicePackMajor; - - /// - /// The minor version number of the latest Service Pack installed on the system. For example, for Service Pack 3, the - /// minor version number is 0. - /// - public short ServicePackMinor => _wServicePackMinor; - - /// - /// A bit mask that identifies the product suites available on the system. This member can be a combination of the - /// following values. - /// - public ushort SuiteMask => _wSuiteMask; - - /// - /// Any additional information about the system. - /// - public byte ProductType => _wProductType; - - /// - /// Factory for an empty OsVersionInfoEx - /// - /// OSVERSIONINFOEX - public static OSVERSIONINFOEX Create() - { - return new OSVERSIONINFOEX - { - _dwOSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) - }; - } - } - - private const int PRODUCT_UNDEFINED = 0x00000000; - private const int PRODUCT_ULTIMATE = 0x00000001; - private const int PRODUCT_HOME_BASIC = 0x00000002; - private const int PRODUCT_HOME_PREMIUM = 0x00000003; - private const int PRODUCT_ENTERPRISE = 0x00000004; - private const int PRODUCT_HOME_BASIC_N = 0x00000005; - private const int PRODUCT_BUSINESS = 0x00000006; - private const int PRODUCT_STANDARD_SERVER = 0x00000007; - private const int PRODUCT_DATACENTER_SERVER = 0x00000008; - private const int PRODUCT_SMALLBUSINESS_SERVER = 0x00000009; - private const int PRODUCT_ENTERPRISE_SERVER = 0x0000000A; - private const int PRODUCT_STARTER = 0x0000000B; - private const int PRODUCT_DATACENTER_SERVER_CORE = 0x0000000C; - private const int PRODUCT_STANDARD_SERVER_CORE = 0x0000000D; - private const int PRODUCT_ENTERPRISE_SERVER_CORE = 0x0000000E; - private const int PRODUCT_ENTERPRISE_SERVER_IA64 = 0x0000000F; - private const int PRODUCT_BUSINESS_N = 0x00000010; - private const int PRODUCT_WEB_SERVER = 0x00000011; - private const int PRODUCT_CLUSTER_SERVER = 0x00000012; - private const int PRODUCT_STORAGE_EXPRESS_SERVER = 0x00000014; - private const int PRODUCT_STORAGE_STANDARD_SERVER = 0x00000015; - private const int PRODUCT_STORAGE_WORKGROUP_SERVER = 0x00000016; - private const int PRODUCT_STORAGE_ENTERPRISE_SERVER = 0x00000017; - private const int PRODUCT_SERVER_FOR_SMALLBUSINESS = 0x00000018; - private const int PRODUCT_HOME_PREMIUM_N = 0x0000001A; - private const int PRODUCT_ENTERPRISE_N = 0x0000001B; - private const int PRODUCT_ULTIMATE_N = 0x0000001C; - private const int PRODUCT_WEB_SERVER_CORE = 0x0000001D; - private const int PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT = 0x0000001E; - private const int PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY = 0x0000001F; - private const int PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING = 0x00000020; - private const int PRODUCT_SERVER_FOR_SMALLBUSINESS_V = 0x00000023; - private const int PRODUCT_STANDARD_SERVER_V = 0x00000024; - private const int PRODUCT_ENTERPRISE_SERVER_V = 0x00000026; - private const int PRODUCT_STANDARD_SERVER_CORE_V = 0x00000028; - private const int PRODUCT_ENTERPRISE_SERVER_CORE_V = 0x00000029; - private const int PRODUCT_HYPERV = 0x0000002A; - - private const int VER_NT_WORKSTATION = 1; - private const int VER_NT_SERVER = 3; - private const int VER_SUITE_ENTERPRISE = 2; - private const int VER_SUITE_DATACENTER = 128; - private const int VER_SUITE_PERSONAL = 512; - private const int VER_SUITE_BLADE = 1024; - - /// - /// Gets the service pack information of the operating system running on this computer. - /// - public static string ServicePack - { - get - { - string servicePack = string.Empty; - OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); - - if (GetVersionEx(ref osVersionInfo)) - { - servicePack = osVersionInfo.ServicePackVersion; - } - - return servicePack; - } - } - - /// - /// Gets the full version string of the operating system running on this computer. - /// - public static string VersionString - { - get - { - if (WindowsVersion.IsWindows10OrLater) - { - return $"build {Environment.OSVersion.Version.Build}"; - } - if (Environment.OSVersion.Version.Revision != 0) - { - return $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor} build {Environment.OSVersion.Version.Build} revision {Environment.OSVersion.Version.Revision:X}"; - } - return $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor} build {Environment.OSVersion.Version.Build}"; - } - } - } -} \ No newline at end of file diff --git a/Greenshot/releases/innosetup/setup.iss b/Greenshot/releases/innosetup/setup.iss index 093c71ce9..91ab88e3c 100644 --- a/Greenshot/releases/innosetup/setup.iss +++ b/Greenshot/releases/innosetup/setup.iss @@ -99,8 +99,8 @@ Source: {#BaseDir}\GreenshotFlickrPlugin\Languages\language_flickr*.xml; DestDir Source: {#BaseDir}\GreenshotPhotobucketPlugin\{#BinDir}\GreenshotPhotobucketPlugin.dll; DestDir: {app}\Plugins\GreenshotPhotobucketPlugin; Components: plugins\photobucket; Flags: overwritereadonly recursesubdirs ignoreversion replacesameversion; Source: {#BaseDir}\GreenshotPhotobucketPlugin\Languages\language_photo*.xml; DestDir: {app}\Languages\Plugins\GreenshotPhotobucketPlugin; Components: plugins\photobucket; Flags: overwritereadonly ignoreversion replacesameversion; ;Picasa Plugin -Source: {#BaseDir}\GreenshotPicasaPlugin\{#BinDir}\GreenshotPicasaPlugin.dll; DestDir: {app}\Plugins\GreenshotPicasaPlugin; Components: plugins\picasa; Flags: overwritereadonly recursesubdirs ignoreversion replacesameversion; -Source: {#BaseDir}\GreenshotPicasaPlugin\Languages\language_picasa*.xml; DestDir: {app}\Languages\Plugins\GreenshotPicasaPlugin; Components: plugins\picasa; Flags: overwritereadonly ignoreversion replacesameversion; +;Source: {#BaseDir}\GreenshotPicasaPlugin\{#BinDir}\GreenshotPicasaPlugin.dll; DestDir: {app}\Plugins\GreenshotPicasaPlugin; Components: plugins\picasa; Flags: overwritereadonly recursesubdirs ignoreversion replacesameversion; +;Source: {#BaseDir}\GreenshotPicasaPlugin\Languages\language_picasa*.xml; DestDir: {app}\Languages\Plugins\GreenshotPicasaPlugin; Components: plugins\picasa; Flags: overwritereadonly ignoreversion replacesameversion; ;Confluence Plugin Source: {#BaseDir}\GreenshotConfluencePlugin\{#BinDir}\GreenshotConfluencePlugin.dll; DestDir: {app}\Plugins\GreenshotConfluencePlugin; Components: plugins\confluence; Flags: overwritereadonly recursesubdirs ignoreversion replacesameversion; Source: {#BaseDir}\GreenshotConfluencePlugin\Languages\language_confluence*.xml; DestDir: {app}\Languages\Plugins\GreenshotConfluencePlugin; Components: plugins\confluence; Flags: overwritereadonly ignoreversion replacesameversion; @@ -491,7 +491,7 @@ Name: "plugins\imgur"; Description: {cm:imgur}; Types: default full custom; Flag Name: "plugins\jira"; Description: {cm:jira}; Types: full custom; Flags: disablenouninstallwarning Name: "plugins\office"; Description: {cm:office}; Types: default full custom; Flags: disablenouninstallwarning Name: "plugins\photobucket"; Description: {cm:photobucket}; Types: full custom; Flags: disablenouninstallwarning -Name: "plugins\picasa"; Description: {cm:picasa}; Types: full custom; Flags: disablenouninstallwarning +;Name: "plugins\picasa"; Description: {cm:picasa}; Types: full custom; Flags: disablenouninstallwarning Name: "plugins\win10"; Description: {cm:win10}; Types: default full custom; Flags: disablenouninstallwarning; Check: IsWindows10OrNewer() Name: "languages"; Description: {cm:language}; Types: full custom; Flags: disablenouninstallwarning Name: "languages\arSY"; Description: {cm:arSY}; Types: full custom; Flags: disablenouninstallwarning; Check: hasLanguageGroup('d') diff --git a/GreenshotBoxPlugin/BoxDestination.cs b/GreenshotBoxPlugin/BoxDestination.cs index 06402ca89..4283a178c 100644 --- a/GreenshotBoxPlugin/BoxDestination.cs +++ b/GreenshotBoxPlugin/BoxDestination.cs @@ -30,17 +30,9 @@ namespace GreenshotBoxPlugin { _plugin = plugin; } - public override string Designation { - get { - return "Box"; - } - } + public override string Designation => "Box"; - public override string Description { - get { - return Language.GetString("box", LangKey.upload_menu_item); - } - } + public override string Description => Language.GetString("box", LangKey.upload_menu_item); public override Image DisplayIcon { get { diff --git a/GreenshotBoxPlugin/BoxUtils.cs b/GreenshotBoxPlugin/BoxUtils.cs index ec9ef0144..a20447c86 100644 --- a/GreenshotBoxPlugin/BoxUtils.cs +++ b/GreenshotBoxPlugin/BoxUtils.cs @@ -21,10 +21,10 @@ using GreenshotPlugin.Core; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Runtime.Serialization.Json; using System.Text; +using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; namespace GreenshotBoxPlugin { @@ -73,9 +73,8 @@ namespace GreenshotBoxPlugin { CloudServiceName = "Box", ClientId = BoxCredentials.ClientId, ClientSecret = BoxCredentials.ClientSecret, - RedirectUrl = "https://www.box.com/home/", - BrowserSize = new Size(1060, 600), - AuthorizeMode = OAuth2AuthorizeMode.EmbeddedBrowser, + RedirectUrl = "https://getgreenshot.org/authorize/box", + AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver, RefreshToken = Config.RefreshToken, AccessToken = Config.AccessToken, AccessTokenExpires = Config.AccessTokenExpires diff --git a/GreenshotBoxPlugin/GreenshotBoxPlugin.Credentials.template b/GreenshotBoxPlugin/GreenshotBoxPlugin.Credentials.template index 9ad35eced..72cfa4b79 100644 --- a/GreenshotBoxPlugin/GreenshotBoxPlugin.Credentials.template +++ b/GreenshotBoxPlugin/GreenshotBoxPlugin.Credentials.template @@ -25,7 +25,7 @@ namespace GreenshotBoxPlugin { /// You can set your own values here /// public static class BoxCredentials { - public static string ClientId = "${Box_ClientId}"; - public static string ClientSecret = "${Box_ClientSecret}"; + public static string ClientId = "${Box13_ClientId}"; + public static string ClientSecret = "${Box13_ClientSecret}"; } } diff --git a/GreenshotDropboxPlugin/DropboxDestination.cs b/GreenshotDropboxPlugin/DropboxDestination.cs index 57d0a1326..9762652c4 100644 --- a/GreenshotDropboxPlugin/DropboxDestination.cs +++ b/GreenshotDropboxPlugin/DropboxDestination.cs @@ -1,20 +1,20 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * + * (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -32,7 +32,7 @@ namespace GreenshotDropboxPlugin { public DropboxDestination(DropboxPlugin plugin) { _plugin = plugin; } - + public override string Designation => "Dropbox"; public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item); @@ -43,10 +43,10 @@ namespace GreenshotDropboxPlugin { return (Image)resources.GetObject("Dropbox"); } } - + public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) { ExportInformation exportInformation = new ExportInformation(Designation, Description); - bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); + bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); if (uploaded) { exportInformation.Uri = uploadUrl; exportInformation.ExportMade = true; diff --git a/GreenshotDropboxPlugin/DropboxPlugin.cs b/GreenshotDropboxPlugin/DropboxPlugin.cs index 0d3ca827d..2977c2d36 100644 --- a/GreenshotDropboxPlugin/DropboxPlugin.cs +++ b/GreenshotDropboxPlugin/DropboxPlugin.cs @@ -1,27 +1,26 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.ComponentModel; using System.Drawing; -using System.IO; using System.Windows.Forms; using GreenshotPlugin.Controls; using GreenshotPlugin.Core; @@ -45,13 +44,12 @@ namespace GreenshotDropboxPlugin { GC.SuppressFinalize(this); } - protected void Dispose(bool disposing) { - if (disposing) { - if (_itemPlugInConfig != null) { - _itemPlugInConfig.Dispose(); - _itemPlugInConfig = null; - } - } + private void Dispose(bool disposing) + { + if (!disposing) return; + if (_itemPlugInConfig == null) return; + _itemPlugInConfig.Dispose(); + _itemPlugInConfig = null; } /// @@ -102,20 +100,16 @@ namespace GreenshotDropboxPlugin { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { uploadUrl = null; SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); - try { - string dropboxUrl = null; - new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), + try + { + bool result = false; + new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), delegate { - string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); - dropboxUrl = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, filename); + result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails); } ); - if (dropboxUrl == null) { - return false; - } - uploadUrl = dropboxUrl; - return true; + return result; } catch (Exception e) { Log.Error(e); MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message); diff --git a/GreenshotDropboxPlugin/DropboxPluginConfiguration.cs b/GreenshotDropboxPlugin/DropboxPluginConfiguration.cs index 52baabdef..b13622db2 100644 --- a/GreenshotDropboxPlugin/DropboxPluginConfiguration.cs +++ b/GreenshotDropboxPlugin/DropboxPluginConfiguration.cs @@ -1,23 +1,25 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + +using System; using System.Windows.Forms; using GreenshotDropboxPlugin.Forms; using GreenshotPlugin.Core; @@ -39,10 +41,18 @@ namespace GreenshotDropboxPlugin { [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Dropbox link to clipboard.", DefaultValue = "true")] public bool AfterUploadLinkToClipBoard { get; set; } - [IniProperty("DropboxToken", Description = "The Dropbox token", Encrypted = true, ExcludeIfNull = true)] - public string DropboxToken { get; set; } - [IniProperty("DropboxTokenSecret", Description = "The Dropbox token secret", Encrypted = true, ExcludeIfNull = true)] - public string DropboxTokenSecret { get; set; } + [IniProperty("RefreshToken", Description = "Dropbox refresh Token", Encrypted = true, ExcludeIfNull = true)] + public string RefreshToken { get; set; } + + /// + /// AccessToken, not stored + /// + public string AccessToken { get; set; } + + /// + /// AccessTokenExpires, not stored + /// + public DateTimeOffset AccessTokenExpires { get; set; } /// /// A form for token diff --git a/GreenshotDropboxPlugin/DropboxUtils.cs b/GreenshotDropboxPlugin/DropboxUtils.cs index 717b41fe3..8bf1d57b5 100644 --- a/GreenshotDropboxPlugin/DropboxUtils.cs +++ b/GreenshotDropboxPlugin/DropboxUtils.cs @@ -1,30 +1,32 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; -using System.Drawing; +using System.IO; using GreenshotPlugin.Core; +using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; +using Newtonsoft.Json; namespace GreenshotDropboxPlugin { /// @@ -37,49 +39,54 @@ namespace GreenshotDropboxPlugin { private DropboxUtils() { } - public static string UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string filename) { - var oAuth = new OAuthSession(DropBoxCredentials.CONSUMER_KEY, DropBoxCredentials.CONSUMER_SECRET) - { - BrowserSize = new Size(1080, 650), - CheckVerifier = false, - AccessTokenUrl = "https://api.dropbox.com/1/oauth/access_token", - AuthorizeUrl = "https://api.dropbox.com/1/oauth/authorize", - RequestTokenUrl = "https://api.dropbox.com/1/oauth/request_token", - LoginTitle = "Dropbox authorization", - Token = DropboxConfig.DropboxToken, - TokenSecret = DropboxConfig.DropboxTokenSecret + public static bool UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, ICaptureDetails captureDetails) + { + var oauth2Settings = new OAuth2Settings + { + AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}", + TokenUrl = "https://api.dropbox.com/oauth2/token", + RedirectUrl = "https://getgreenshot.org/authorize/dropbox", + CloudServiceName = "Dropbox", + ClientId = DropBoxCredentials.CONSUMER_KEY, + ClientSecret = DropBoxCredentials.CONSUMER_SECRET, + AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver, + RefreshToken = DropboxConfig.RefreshToken, + AccessToken = DropboxConfig.AccessToken, + AccessTokenExpires = DropboxConfig.AccessTokenExpires }; + try + { + string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails)); + SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename); - try { - SurfaceContainer imageToUpload = new SurfaceContainer(surfaceToUpload, outputSettings, filename); - string uploadResponse = oAuth.MakeOAuthRequest(HTTPMethod.POST, "https://api-content.dropbox.com/1/files_put/sandbox/" + OAuthSession.UrlEncode3986(filename), null, null, imageToUpload); - Log.DebugFormat("Upload response: {0}", uploadResponse); - } catch (Exception ex) { + IDictionary arguments = new Dictionary + { + { "autorename", true }, + { "mute", true }, + { "path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')} + }; + IDictionary headers = new Dictionary + { + { "Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)} + }; + var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://content.dropboxapi.com/2/files/upload", oauth2Settings); + + NetworkHelper.Post(webRequest, headers, image); + var responseString = NetworkHelper.GetResponseAsString(webRequest); + Log.DebugFormat("Upload response: {0}", responseString); + var response = JsonConvert.DeserializeObject>(responseString); + return response.ContainsKey("id"); + } + catch (Exception ex) { Log.Error("Upload error: ", ex); throw; } finally { - if (!string.IsNullOrEmpty(oAuth.Token)) { - DropboxConfig.DropboxToken = oAuth.Token; - } - if (!string.IsNullOrEmpty(oAuth.TokenSecret)) { - DropboxConfig.DropboxTokenSecret = oAuth.TokenSecret; - } - } - - // Try to get a URL to the uploaded image - try { - string responseString = oAuth.MakeOAuthRequest(HTTPMethod.GET, "https://api.dropbox.com/1/shares/sandbox/" + OAuthSession.UrlEncode3986(filename), null, null, null); - if (responseString != null) { - Log.DebugFormat("Parsing output: {0}", responseString); - IDictionary returnValues = JSONHelper.JsonDecode(responseString); - if (returnValues.ContainsKey("url")) { - return returnValues["url"] as string; - } - } - } catch (Exception ex) { - Log.Error("Can't parse response.", ex); - } - return null; + DropboxConfig.RefreshToken = oauth2Settings.RefreshToken; + DropboxConfig.AccessToken = oauth2Settings.AccessToken; + DropboxConfig.AccessTokenExpires = oauth2Settings.AccessTokenExpires; + DropboxConfig.IsDirty = true; + IniConfig.Save(); + } } } } diff --git a/GreenshotDropboxPlugin/GreenshotDropboxPlugin.Credentials.template b/GreenshotDropboxPlugin/GreenshotDropboxPlugin.Credentials.template index 6a13eb6d7..f674f1bb7 100644 --- a/GreenshotDropboxPlugin/GreenshotDropboxPlugin.Credentials.template +++ b/GreenshotDropboxPlugin/GreenshotDropboxPlugin.Credentials.template @@ -25,7 +25,7 @@ namespace GreenshotDropboxPlugin { /// You can set your own values here /// public static class DropBoxCredentials { - public static string CONSUMER_KEY = "${DropBox_ClientId}"; - public static string CONSUMER_SECRET = "${DropBox_ClientSecret}"; + public static string CONSUMER_KEY = "${DropBox13_ClientId}"; + public static string CONSUMER_SECRET = "${DropBox13_ClientSecret}"; } } diff --git a/GreenshotFlickrPlugin/FlickrUtils.cs b/GreenshotFlickrPlugin/FlickrUtils.cs index 6713e3702..9337c51f6 100644 --- a/GreenshotFlickrPlugin/FlickrUtils.cs +++ b/GreenshotFlickrPlugin/FlickrUtils.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Drawing; using System.Xml; using GreenshotPlugin.Core; +using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; diff --git a/GreenshotPicasaPlugin/Forms/PicasaForm.cs b/GreenshotGooglePhotosPlugin/Forms/GooglePhotosForm.cs similarity index 85% rename from GreenshotPicasaPlugin/Forms/PicasaForm.cs rename to GreenshotGooglePhotosPlugin/Forms/GooglePhotosForm.cs index cea5a2f35..10b18bebe 100644 --- a/GreenshotPicasaPlugin/Forms/PicasaForm.cs +++ b/GreenshotGooglePhotosPlugin/Forms/GooglePhotosForm.cs @@ -1,5 +1,5 @@ /* - * A Picasa Plugin for Greenshot + * A GooglePhotos Plugin for Greenshot * Copyright (C) 2011 Francis Noel * * For more information see: http://getgreenshot.org/ @@ -20,7 +20,7 @@ using GreenshotPlugin.Controls; -namespace GreenshotPicasaPlugin.Forms { - public class PicasaForm : GreenshotForm { +namespace GreenshotGooglePhotosPlugin.Forms { + public class GooglePhotosForm : GreenshotForm { } } diff --git a/GreenshotPicasaPlugin/Forms/SettingsForm.Designer.cs b/GreenshotGooglePhotosPlugin/Forms/SettingsForm.Designer.cs similarity index 91% rename from GreenshotPicasaPlugin/Forms/SettingsForm.Designer.cs rename to GreenshotGooglePhotosPlugin/Forms/SettingsForm.Designer.cs index 38555c179..cea2b6acf 100644 --- a/GreenshotPicasaPlugin/Forms/SettingsForm.Designer.cs +++ b/GreenshotGooglePhotosPlugin/Forms/SettingsForm.Designer.cs @@ -1,147 +1,147 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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 . - */ -namespace GreenshotPicasaPlugin.Forms { - partial class SettingsForm { - /// - /// Designer variable used to keep track of non-visual components. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Disposes resources used by the form. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing) { - if (components != null) { - components.Dispose(); - } - } - base.Dispose(disposing); - } - - /// - /// This method is required for Windows Forms designer support. - /// Do not change the method contents inside the source code editor. The Forms designer might - /// not be able to load this method if it was changed manually. - /// - private void InitializeComponent() - { - this.buttonOK = new GreenshotPlugin.Controls.GreenshotButton(); - this.buttonCancel = new GreenshotPlugin.Controls.GreenshotButton(); - this.combobox_uploadimageformat = new GreenshotPlugin.Controls.GreenshotComboBox(); - this.label_upload_format = new GreenshotPlugin.Controls.GreenshotLabel(); - this.label_AfterUpload = new GreenshotPlugin.Controls.GreenshotLabel(); - this.checkboxAfterUploadLinkToClipBoard = new GreenshotPlugin.Controls.GreenshotCheckBox(); - this.SuspendLayout(); - // - // buttonOK - // - this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK; - this.buttonOK.LanguageKey = "OK"; - this.buttonOK.Location = new System.Drawing.Point(267, 78); - this.buttonOK.Name = "buttonOK"; - this.buttonOK.Size = new System.Drawing.Size(75, 23); - this.buttonOK.TabIndex = 10; - this.buttonOK.UseVisualStyleBackColor = true; - // - // buttonCancel - // - this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.buttonCancel.LanguageKey = "CANCEL"; - this.buttonCancel.Location = new System.Drawing.Point(348, 78); - this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(75, 23); - this.buttonCancel.TabIndex = 11; - this.buttonCancel.UseVisualStyleBackColor = true; - // - // combobox_uploadimageformat - // - this.combobox_uploadimageformat.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.combobox_uploadimageformat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combobox_uploadimageformat.FormattingEnabled = true; - this.combobox_uploadimageformat.Location = new System.Drawing.Point(197, 12); - this.combobox_uploadimageformat.Name = "combobox_uploadimageformat"; - this.combobox_uploadimageformat.PropertyName = "UploadFormat"; - this.combobox_uploadimageformat.SectionName = "Picasa"; - this.combobox_uploadimageformat.Size = new System.Drawing.Size(225, 21); - this.combobox_uploadimageformat.TabIndex = 1; - // - // label_upload_format - // - this.label_upload_format.LanguageKey = "picasa.label_upload_format"; - this.label_upload_format.Location = new System.Drawing.Point(10, 18); - this.label_upload_format.Name = "label_upload_format"; - this.label_upload_format.Size = new System.Drawing.Size(181, 33); - this.label_upload_format.TabIndex = 4; - // - // label_AfterUpload - // - this.label_AfterUpload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.label_AfterUpload.LanguageKey = "picasa.label_AfterUpload"; - this.label_AfterUpload.Location = new System.Drawing.Point(10, 51); - this.label_AfterUpload.Name = "label_AfterUpload"; - this.label_AfterUpload.Size = new System.Drawing.Size(181, 29); - this.label_AfterUpload.TabIndex = 8; - // - // checkboxAfterUploadLinkToClipBoard - // - this.checkboxAfterUploadLinkToClipBoard.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.checkboxAfterUploadLinkToClipBoard.LanguageKey = "picasa.label_AfterUploadLinkToClipBoard"; - this.checkboxAfterUploadLinkToClipBoard.Location = new System.Drawing.Point(197, 50); - this.checkboxAfterUploadLinkToClipBoard.Name = "checkboxAfterUploadLinkToClipBoard"; - this.checkboxAfterUploadLinkToClipBoard.PropertyName = "AfterUploadLinkToClipBoard"; - this.checkboxAfterUploadLinkToClipBoard.SectionName = "Picasa"; - this.checkboxAfterUploadLinkToClipBoard.Size = new System.Drawing.Size(104, 17); - this.checkboxAfterUploadLinkToClipBoard.TabIndex = 2; - this.checkboxAfterUploadLinkToClipBoard.UseVisualStyleBackColor = true; - // - // SettingsForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(432, 110); - this.Controls.Add(this.checkboxAfterUploadLinkToClipBoard); - this.Controls.Add(this.label_AfterUpload); - this.Controls.Add(this.label_upload_format); - this.Controls.Add(this.combobox_uploadimageformat); - this.Controls.Add(this.buttonCancel); - this.Controls.Add(this.buttonOK); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.LanguageKey = "picasa.settings_title"; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "SettingsForm"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - private GreenshotPlugin.Controls.GreenshotComboBox combobox_uploadimageformat; - private GreenshotPlugin.Controls.GreenshotLabel label_upload_format; - private GreenshotPlugin.Controls.GreenshotButton buttonCancel; - private GreenshotPlugin.Controls.GreenshotButton buttonOK; - private GreenshotPlugin.Controls.GreenshotLabel label_AfterUpload; - private GreenshotPlugin.Controls.GreenshotCheckBox checkboxAfterUploadLinkToClipBoard; - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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 . + */ +namespace GreenshotGooglePhotosPlugin.Forms { + partial class SettingsForm { + /// + /// Designer variable used to keep track of non-visual components. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Disposes resources used by the form. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing) { + if (components != null) { + components.Dispose(); + } + } + base.Dispose(disposing); + } + + /// + /// This method is required for Windows Forms designer support. + /// Do not change the method contents inside the source code editor. The Forms designer might + /// not be able to load this method if it was changed manually. + /// + private void InitializeComponent() + { + this.buttonOK = new GreenshotPlugin.Controls.GreenshotButton(); + this.buttonCancel = new GreenshotPlugin.Controls.GreenshotButton(); + this.combobox_uploadimageformat = new GreenshotPlugin.Controls.GreenshotComboBox(); + this.label_upload_format = new GreenshotPlugin.Controls.GreenshotLabel(); + this.label_AfterUpload = new GreenshotPlugin.Controls.GreenshotLabel(); + this.checkboxAfterUploadLinkToClipBoard = new GreenshotPlugin.Controls.GreenshotCheckBox(); + this.SuspendLayout(); + // + // buttonOK + // + this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.buttonOK.LanguageKey = "OK"; + this.buttonOK.Location = new System.Drawing.Point(267, 78); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(75, 23); + this.buttonOK.TabIndex = 10; + this.buttonOK.UseVisualStyleBackColor = true; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.LanguageKey = "CANCEL"; + this.buttonCancel.Location = new System.Drawing.Point(348, 78); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(75, 23); + this.buttonCancel.TabIndex = 11; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // combobox_uploadimageformat + // + this.combobox_uploadimageformat.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.combobox_uploadimageformat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combobox_uploadimageformat.FormattingEnabled = true; + this.combobox_uploadimageformat.Location = new System.Drawing.Point(197, 12); + this.combobox_uploadimageformat.Name = "combobox_uploadimageformat"; + this.combobox_uploadimageformat.PropertyName = "UploadFormat"; + this.combobox_uploadimageformat.SectionName = "GooglePhotos"; + this.combobox_uploadimageformat.Size = new System.Drawing.Size(225, 21); + this.combobox_uploadimageformat.TabIndex = 1; + // + // label_upload_format + // + this.label_upload_format.LanguageKey = "googlephotos.label_upload_format"; + this.label_upload_format.Location = new System.Drawing.Point(10, 18); + this.label_upload_format.Name = "label_upload_format"; + this.label_upload_format.Size = new System.Drawing.Size(181, 33); + this.label_upload_format.TabIndex = 4; + // + // label_AfterUpload + // + this.label_AfterUpload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label_AfterUpload.LanguageKey = "googlephotos.label_AfterUpload"; + this.label_AfterUpload.Location = new System.Drawing.Point(10, 51); + this.label_AfterUpload.Name = "label_AfterUpload"; + this.label_AfterUpload.Size = new System.Drawing.Size(181, 29); + this.label_AfterUpload.TabIndex = 8; + // + // checkboxAfterUploadLinkToClipBoard + // + this.checkboxAfterUploadLinkToClipBoard.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.checkboxAfterUploadLinkToClipBoard.LanguageKey = "googlephotos.label_AfterUploadLinkToClipBoard"; + this.checkboxAfterUploadLinkToClipBoard.Location = new System.Drawing.Point(197, 50); + this.checkboxAfterUploadLinkToClipBoard.Name = "checkboxAfterUploadLinkToClipBoard"; + this.checkboxAfterUploadLinkToClipBoard.PropertyName = "AfterUploadLinkToClipBoard"; + this.checkboxAfterUploadLinkToClipBoard.SectionName = "GooglePhotos"; + this.checkboxAfterUploadLinkToClipBoard.Size = new System.Drawing.Size(104, 17); + this.checkboxAfterUploadLinkToClipBoard.TabIndex = 2; + this.checkboxAfterUploadLinkToClipBoard.UseVisualStyleBackColor = true; + // + // SettingsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(432, 110); + this.Controls.Add(this.checkboxAfterUploadLinkToClipBoard); + this.Controls.Add(this.label_AfterUpload); + this.Controls.Add(this.label_upload_format); + this.Controls.Add(this.combobox_uploadimageformat); + this.Controls.Add(this.buttonCancel); + this.Controls.Add(this.buttonOK); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.LanguageKey = "googlephotos.settings_title"; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SettingsForm"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + private GreenshotPlugin.Controls.GreenshotComboBox combobox_uploadimageformat; + private GreenshotPlugin.Controls.GreenshotLabel label_upload_format; + private GreenshotPlugin.Controls.GreenshotButton buttonCancel; + private GreenshotPlugin.Controls.GreenshotButton buttonOK; + private GreenshotPlugin.Controls.GreenshotLabel label_AfterUpload; + private GreenshotPlugin.Controls.GreenshotCheckBox checkboxAfterUploadLinkToClipBoard; + } +} diff --git a/GreenshotPicasaPlugin/Forms/SettingsForm.cs b/GreenshotGooglePhotosPlugin/Forms/SettingsForm.cs similarity index 86% rename from GreenshotPicasaPlugin/Forms/SettingsForm.cs rename to GreenshotGooglePhotosPlugin/Forms/SettingsForm.cs index a6691a424..be31f1d44 100644 --- a/GreenshotPicasaPlugin/Forms/SettingsForm.cs +++ b/GreenshotGooglePhotosPlugin/Forms/SettingsForm.cs @@ -1,38 +1,38 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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 . - */ - -namespace GreenshotPicasaPlugin.Forms { - /// - /// Description of PasswordRequestForm. - /// - public partial class SettingsForm : PicasaForm { - - public SettingsForm() - { - // - // The InitializeComponent() call is required for Windows Forms designer support. - // - InitializeComponent(); - CancelButton = buttonCancel; - AcceptButton = buttonOK; - } - - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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 . + */ + +namespace GreenshotGooglePhotosPlugin.Forms { + /// + /// Description of PasswordRequestForm. + /// + public partial class SettingsForm : GooglePhotosForm { + + public SettingsForm() + { + // + // The InitializeComponent() call is required for Windows Forms designer support. + // + InitializeComponent(); + CancelButton = buttonCancel; + AcceptButton = buttonOK; + } + + } +} diff --git a/GreenshotGooglePhotosPlugin/GooglePhotos.png b/GreenshotGooglePhotosPlugin/GooglePhotos.png new file mode 100644 index 000000000..ea7824f9d Binary files /dev/null and b/GreenshotGooglePhotosPlugin/GooglePhotos.png differ diff --git a/GreenshotPicasaPlugin/PicasaConfiguration.cs b/GreenshotGooglePhotosPlugin/GooglePhotosConfiguration.cs similarity index 72% rename from GreenshotPicasaPlugin/PicasaConfiguration.cs rename to GreenshotGooglePhotosPlugin/GooglePhotosConfiguration.cs index 202929459..7e1b614b2 100644 --- a/GreenshotPicasaPlugin/PicasaConfiguration.cs +++ b/GreenshotGooglePhotosPlugin/GooglePhotosConfiguration.cs @@ -1,93 +1,93 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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.Windows.Forms; -using GreenshotPlugin.Core; -using System; -using GreenshotPicasaPlugin.Forms; -using GreenshotPlugin.IniFile; - -namespace GreenshotPicasaPlugin { - /// - /// Description of PicasaConfiguration. - /// - [IniSection("Picasa", Description = "Greenshot Picasa Plugin configuration")] - public class PicasaConfiguration : IniSection { - [IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] - public OutputFormat UploadFormat { get; set; } - - [IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] - public int UploadJpegQuality { get; set; } - - [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Picasa link to clipboard.", DefaultValue = "true")] - public bool AfterUploadLinkToClipBoard { get; set; } - [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; - } - - [IniProperty("RefreshToken", Description = "Picasa 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 - /// - /// bool true if OK was pressed, false if cancel - public bool ShowConfigDialog() { - DialogResult result = new SettingsForm().ShowDialog(); - if (result == DialogResult.OK) { - return true; - } - return false; - } - - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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.Windows.Forms; +using GreenshotPlugin.Core; +using System; +using GreenshotGooglePhotosPlugin.Forms; +using GreenshotPlugin.IniFile; + +namespace GreenshotGooglePhotosPlugin { + /// + /// Description of GooglePhotosConfiguration. + /// + [IniSection("GooglePhotos", Description = "Greenshot GooglePhotos Plugin configuration")] + public class GooglePhotosConfiguration : IniSection { + [IniProperty("UploadFormat", Description="What file type to use for uploading", DefaultValue="png")] + public OutputFormat UploadFormat { get; set; } + + [IniProperty("UploadJpegQuality", Description="JPEG file save quality in %.", DefaultValue="80")] + public int UploadJpegQuality { get; set; } + + [IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send GooglePhotos link to clipboard.", DefaultValue = "true")] + public bool AfterUploadLinkToClipBoard { get; set; } + [IniProperty("AddFilename", Description = "Is the filename passed on to GooglePhotos", DefaultValue = "False")] + public bool AddFilename { + get; + set; + } + + [IniProperty("UploadUser", Description = "The GooglePhotos user to upload to", DefaultValue = "default")] + public string UploadUser { + get; + set; + } + + [IniProperty("UploadAlbum", Description = "The GooglePhotos album to upload to", DefaultValue = "default")] + public string UploadAlbum { + get; + set; + } + + [IniProperty("RefreshToken", Description = "GooglePhotos 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 + /// + /// bool true if OK was pressed, false if cancel + public bool ShowConfigDialog() { + DialogResult result = new SettingsForm().ShowDialog(); + if (result == DialogResult.OK) { + return true; + } + return false; + } + + } +} diff --git a/GreenshotPicasaPlugin/PicasaDestination.cs b/GreenshotGooglePhotosPlugin/GooglePhotosDestination.cs similarity index 75% rename from GreenshotPicasaPlugin/PicasaDestination.cs rename to GreenshotGooglePhotosPlugin/GooglePhotosDestination.cs index 4107ecd48..586a6e842 100644 --- a/GreenshotPicasaPlugin/PicasaDestination.cs +++ b/GreenshotGooglePhotosPlugin/GooglePhotosDestination.cs @@ -1,54 +1,54 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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.ComponentModel; -using System.Drawing; -using GreenshotPlugin.Core; -using GreenshotPlugin.Interfaces; - -namespace GreenshotPicasaPlugin { - public class PicasaDestination : AbstractDestination { - private readonly PicasaPlugin _plugin; - public PicasaDestination(PicasaPlugin plugin) { - _plugin = plugin; - } - - public override string Designation => "Picasa"; - - public override string Description => Language.GetString("picasa", LangKey.upload_menu_item); - - public override Image DisplayIcon { - get { - ComponentResourceManager resources = new ComponentResourceManager(typeof(PicasaPlugin)); - return (Image)resources.GetObject("Picasa"); - } - } - - public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { - ExportInformation exportInformation = new ExportInformation(Designation, Description); - bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); - if (uploaded) { - exportInformation.ExportMade = true; - exportInformation.Uri = uploadUrl; - } - ProcessExport(exportInformation, surface); - return exportInformation; - } - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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.ComponentModel; +using System.Drawing; +using GreenshotPlugin.Core; +using GreenshotPlugin.Interfaces; + +namespace GreenshotGooglePhotosPlugin { + public class GooglePhotosDestination : AbstractDestination { + private readonly GooglePhotosPlugin _plugin; + public GooglePhotosDestination(GooglePhotosPlugin plugin) { + _plugin = plugin; + } + + public override string Designation => "GooglePhotos"; + + public override string Description => Language.GetString("googlephotos", LangKey.upload_menu_item); + + public override Image DisplayIcon { + get { + ComponentResourceManager resources = new ComponentResourceManager(typeof(GooglePhotosPlugin)); + return (Image)resources.GetObject("GooglePhotos"); + } + } + + public override ExportInformation ExportCapture(bool manuallyInitiated, ISurface surface, ICaptureDetails captureDetails) { + ExportInformation exportInformation = new ExportInformation(Designation, Description); + bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); + if (uploaded) { + exportInformation.ExportMade = true; + exportInformation.Uri = uploadUrl; + } + ProcessExport(exportInformation, surface); + return exportInformation; + } + } +} diff --git a/GreenshotPicasaPlugin/PicasaPlugin.cs b/GreenshotGooglePhotosPlugin/GooglePhotosPlugin.cs similarity index 68% rename from GreenshotPicasaPlugin/PicasaPlugin.cs rename to GreenshotGooglePhotosPlugin/GooglePhotosPlugin.cs index a8f312484..b5d37f62c 100644 --- a/GreenshotPicasaPlugin/PicasaPlugin.cs +++ b/GreenshotGooglePhotosPlugin/GooglePhotosPlugin.cs @@ -1,125 +1,124 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -using System; -using System.ComponentModel; -using System.Drawing; -using System.IO; -using System.Windows.Forms; -using GreenshotPlugin.Controls; -using GreenshotPlugin.Core; -using GreenshotPlugin.IniFile; -using GreenshotPlugin.Interfaces; -using GreenshotPlugin.Interfaces.Plugin; - -namespace GreenshotPicasaPlugin { - /// - /// This is the Picasa base code - /// - [Plugin("Picasa", true)] - public class PicasaPlugin : IGreenshotPlugin { - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(PicasaPlugin)); - private static PicasaConfiguration _config; - private ComponentResourceManager _resources; - private ToolStripMenuItem _itemPlugInRoot; - - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected void Dispose(bool disposing) { - if (disposing) { - if (_itemPlugInRoot != null) { - _itemPlugInRoot.Dispose(); - _itemPlugInRoot = null; - } - } - } - - /// - /// Implementation of the IGreenshotPlugin.Initialize - /// - public bool Initialize() { - SimpleServiceProvider.Current.AddService(new PicasaDestination(this)); - - // Get configuration - _config = IniConfig.GetIniSection(); - _resources = new ComponentResourceManager(typeof(PicasaPlugin)); - - _itemPlugInRoot = new ToolStripMenuItem - { - Text = Language.GetString("picasa", LangKey.Configure), - Image = (Image) _resources.GetObject("Picasa") - }; - _itemPlugInRoot.Click += ConfigMenuClick; - PluginUtils.AddToContextMenu(_itemPlugInRoot); - Language.LanguageChanged += OnLanguageChanged; - return true; - } - - public void OnLanguageChanged(object sender, EventArgs e) { - if (_itemPlugInRoot != null) { - _itemPlugInRoot.Text = Language.GetString("picasa", LangKey.Configure); - } - } - - public void Shutdown() { - Log.Debug("Picasa Plugin shutdown."); - Language.LanguageChanged -= OnLanguageChanged; - //host.OnImageEditorOpen -= new OnImageEditorOpenHandler(ImageEditorOpened); - } - - /// - /// Implementation of the IPlugin.Configure - /// - public void Configure() { - _config.ShowConfigDialog(); - } - - public void ConfigMenuClick(object sender, EventArgs eventArgs) { - Configure(); - } - - public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { - SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality); - try { - string url = null; - new PleaseWaitForm().ShowAndWait("Picasa", Language.GetString("picasa", LangKey.communication_wait), - delegate - { - string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); - url = PicasaUtils.UploadToPicasa(surfaceToUpload, outputSettings, captureDetails.Title, filename); - } - ); - uploadUrl = url; - - if (uploadUrl != null && _config.AfterUploadLinkToClipBoard) { - ClipboardHelper.SetClipboardData(uploadUrl); - } - return true; - } catch (Exception e) { - Log.Error("Error uploading.", e); - MessageBox.Show(Language.GetString("picasa", LangKey.upload_failure) + " " + e.Message); - } - uploadUrl = null; - return false; - } - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using GreenshotPlugin.Controls; +using GreenshotPlugin.Core; +using GreenshotPlugin.IniFile; +using GreenshotPlugin.Interfaces; +using GreenshotPlugin.Interfaces.Plugin; + +namespace GreenshotGooglePhotosPlugin { + /// + /// This is the GooglePhotos base code + /// + [Plugin("GooglePhotos", true)] + public class GooglePhotosPlugin : IGreenshotPlugin { + private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosPlugin)); + private static GooglePhotosConfiguration _config; + private ComponentResourceManager _resources; + private ToolStripMenuItem _itemPlugInRoot; + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposing) return; + if (_itemPlugInRoot == null) return; + _itemPlugInRoot.Dispose(); + _itemPlugInRoot = null; + } + + /// + /// Implementation of the IGreenshotPlugin.Initialize + /// + public bool Initialize() { + SimpleServiceProvider.Current.AddService(new GooglePhotosDestination(this)); + + // Get configuration + _config = IniConfig.GetIniSection(); + _resources = new ComponentResourceManager(typeof(GooglePhotosPlugin)); + + _itemPlugInRoot = new ToolStripMenuItem + { + Text = Language.GetString("googlephotos", LangKey.Configure), + Image = (Image) _resources.GetObject("GooglePhotos") + }; + _itemPlugInRoot.Click += ConfigMenuClick; + PluginUtils.AddToContextMenu(_itemPlugInRoot); + Language.LanguageChanged += OnLanguageChanged; + return true; + } + + public void OnLanguageChanged(object sender, EventArgs e) { + if (_itemPlugInRoot != null) { + _itemPlugInRoot.Text = Language.GetString("googlephotos", LangKey.Configure); + } + } + + public void Shutdown() { + Log.Debug("GooglePhotos Plugin shutdown."); + Language.LanguageChanged -= OnLanguageChanged; + //host.OnImageEditorOpen -= new OnImageEditorOpenHandler(ImageEditorOpened); + } + + /// + /// Implementation of the IPlugin.Configure + /// + public void Configure() { + _config.ShowConfigDialog(); + } + + public void ConfigMenuClick(object sender, EventArgs eventArgs) { + Configure(); + } + + public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { + SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality); + try { + string url = null; + new PleaseWaitForm().ShowAndWait("GooglePhotos", Language.GetString("googlephotos", LangKey.communication_wait), + delegate + { + string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); + url = GooglePhotosUtils.UploadToGooglePhotos(surfaceToUpload, outputSettings, captureDetails.Title, filename); + } + ); + uploadUrl = url; + + if (uploadUrl != null && _config.AfterUploadLinkToClipBoard) { + ClipboardHelper.SetClipboardData(uploadUrl); + } + return true; + } catch (Exception e) { + Log.Error("Error uploading.", e); + MessageBox.Show(Language.GetString("googlephotos", LangKey.upload_failure) + " " + e.Message); + } + uploadUrl = null; + return false; + } + } +} diff --git a/GreenshotPicasaPlugin/PicasaPlugin.resx b/GreenshotGooglePhotosPlugin/GooglePhotosPlugin.resx similarity index 96% rename from GreenshotPicasaPlugin/PicasaPlugin.resx rename to GreenshotGooglePhotosPlugin/GooglePhotosPlugin.resx index de5045226..240e4a53e 100644 --- a/GreenshotPicasaPlugin/PicasaPlugin.resx +++ b/GreenshotGooglePhotosPlugin/GooglePhotosPlugin.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - picasa.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + GooglePhotos.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/GreenshotPicasaPlugin/PicasaUtils.cs b/GreenshotGooglePhotosPlugin/GooglePhotosUtils.cs similarity index 74% rename from GreenshotPicasaPlugin/PicasaUtils.cs rename to GreenshotGooglePhotosPlugin/GooglePhotosUtils.cs index eb2b2be2e..27aa13d8f 100644 --- a/GreenshotPicasaPlugin/PicasaUtils.cs +++ b/GreenshotGooglePhotosPlugin/GooglePhotosUtils.cs @@ -1,119 +1,120 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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; -using System; -using System.Xml; -using GreenshotPlugin.IniFile; -using GreenshotPlugin.Interfaces; -using GreenshotPlugin.Interfaces.Plugin; - -namespace GreenshotPicasaPlugin { - /// - /// Description of PicasaUtils. - /// - 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=code&client_id={ClientId}&redirect_uri={RedirectUrl}&state={State}&scope=" + PicasaScope; - 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}"; - - /// - /// Do the actual upload to Picasa - /// - /// Image to upload - /// - /// - /// - /// PicasaResponse - public static string UploadToPicasa(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) { - // Fill the OAuth2Settings - var settings = new OAuth2Settings - { - AuthUrlPattern = AuthUrl, - TokenUrl = TokenUrl, - CloudServiceName = "Picasa", - ClientId = PicasaCredentials.ClientId, - ClientSecret = PicasaCredentials.ClientSecret, - AuthorizeMode = OAuth2AuthorizeMode.LocalServer, - RefreshToken = Config.RefreshToken, - AccessToken = Config.AccessToken, - AccessTokenExpires = Config.AccessTokenExpires - }; - - // Copy the settings from the config, which is kept in memory and on the disk - - try { - 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.GetResponseAsString(webRequest); - - return ParseResponse(response); - } 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; - IniConfig.Save(); - } - } - - /// - /// Parse the upload URL from the response - /// - /// - /// - public static string ParseResponse(string response) { - if (response == null) { - return null; - } - try { - XmlDocument doc = new XmlDocument(); - doc.LoadXml(response); - XmlNodeList nodes = doc.GetElementsByTagName("link", "*"); - if(nodes.Count > 0) { - string url = null; - foreach(XmlNode node in nodes) { - if (node.Attributes != null) { - url = node.Attributes["href"].Value; - string rel = node.Attributes["rel"].Value; - // Pictures with rel="http://schemas.google.com/photos/2007#canonical" are the direct link - if (rel != null && rel.EndsWith("canonical")) { - break; - } - } - } - return url; - } - } catch(Exception e) { - Log.ErrorFormat("Could not parse Picasa response due to error {0}, response was: {1}", e.Message, response); - } - return null; - } - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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; +using System; +using System.Xml; +using GreenshotPlugin.Core.OAuth; +using GreenshotPlugin.IniFile; +using GreenshotPlugin.Interfaces; +using GreenshotPlugin.Interfaces.Plugin; + +namespace GreenshotGooglePhotosPlugin { + /// + /// Description of GooglePhotosUtils. + /// + public static class GooglePhotosUtils { + private const string GooglePhotosScope = "https://picasaweb.google.com/data/"; + private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(GooglePhotosUtils)); + private static readonly GooglePhotosConfiguration Config = IniConfig.GetIniSection(); + private const string AuthUrl = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={ClientId}&redirect_uri={RedirectUrl}&state={State}&scope=" + GooglePhotosScope; + 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}"; + + /// + /// Do the actual upload to GooglePhotos + /// + /// Image to upload + /// SurfaceOutputSettings + /// string + /// string + /// GooglePhotosResponse + public static string UploadToGooglePhotos(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string title, string filename) { + // Fill the OAuth2Settings + var settings = new OAuth2Settings + { + AuthUrlPattern = AuthUrl, + TokenUrl = TokenUrl, + CloudServiceName = "GooglePhotos", + ClientId = GooglePhotosCredentials.ClientId, + ClientSecret = GooglePhotosCredentials.ClientSecret, + AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver, + RefreshToken = Config.RefreshToken, + AccessToken = Config.AccessToken, + AccessTokenExpires = Config.AccessTokenExpires + }; + + // Copy the settings from the config, which is kept in memory and on the disk + + try { + 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.GetResponseAsString(webRequest); + + return ParseResponse(response); + } 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; + IniConfig.Save(); + } + } + + /// + /// Parse the upload URL from the response + /// + /// + /// + public static string ParseResponse(string response) { + if (response == null) { + return null; + } + try { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(response); + XmlNodeList nodes = doc.GetElementsByTagName("link", "*"); + if(nodes.Count > 0) { + string url = null; + foreach(XmlNode node in nodes) { + if (node.Attributes != null) { + url = node.Attributes["href"].Value; + string rel = node.Attributes["rel"].Value; + // Pictures with rel="http://schemas.google.com/photos/2007#canonical" are the direct link + if (rel != null && rel.EndsWith("canonical")) { + break; + } + } + } + return url; + } + } catch(Exception e) { + Log.ErrorFormat("Could not parse GooglePhotos response due to error {0}, response was: {1}", e.Message, response); + } + return null; + } + } +} diff --git a/GreenshotPicasaPlugin/GreenshotPicasaPlugin.Credentials.template b/GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.Credentials.template similarity index 83% rename from GreenshotPicasaPlugin/GreenshotPicasaPlugin.Credentials.template rename to GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.Credentials.template index 47172f2d4..452922112 100644 --- a/GreenshotPicasaPlugin/GreenshotPicasaPlugin.Credentials.template +++ b/GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.Credentials.template @@ -19,13 +19,13 @@ * along with this program. If not, see . */ -namespace GreenshotPicasaPlugin { +namespace GreenshotGooglePhotosPlugin { /// /// This class is merely a placeholder for the file keeping the API key and secret for dropbox integration. /// You can set your own values here /// - public static class PicasaCredentials { - public static string ClientId = "${Picasa_ClientId}"; - public static string ClientSecret = "${Picasa_ClientSecret}"; + public static class GooglePhotosCredentials { + public static string ClientId = "${GooglePhotos_ClientId}"; + public static string ClientSecret = "${GooglePhotos_ClientSecret}"; } } diff --git a/GreenshotPicasaPlugin/GreenshotPicasaPlugin.csproj b/GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.csproj similarity index 62% rename from GreenshotPicasaPlugin/GreenshotPicasaPlugin.csproj rename to GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.csproj index ac827cfec..3fb62aa99 100644 --- a/GreenshotPicasaPlugin/GreenshotPicasaPlugin.csproj +++ b/GreenshotGooglePhotosPlugin/GreenshotGooglePhotosPlugin.csproj @@ -1,21 +1,15 @@ - - - - GreenshotPicasaPlugin - GreenshotPicasaPlugin - - - - - PreserveNewest - - - - - - - - - - + + + + PreserveNewest + + + + + + + + + + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/LanguageKeys.cs b/GreenshotGooglePhotosPlugin/LanguageKeys.cs similarity index 89% rename from GreenshotPicasaPlugin/LanguageKeys.cs rename to GreenshotGooglePhotosPlugin/LanguageKeys.cs index c22e4c404..1a7a73996 100644 --- a/GreenshotPicasaPlugin/LanguageKeys.cs +++ b/GreenshotGooglePhotosPlugin/LanguageKeys.cs @@ -1,29 +1,29 @@ -/* - * A Picasa Plugin for Greenshot - * Copyright (C) 2011 Francis Noel - * - * For more information see: http://getgreenshot.org/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, 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 . - */ - -namespace GreenshotPicasaPlugin { - public enum LangKey - { - upload_menu_item, - upload_failure, - communication_wait, - Configure - } -} +/* + * A GooglePhotos Plugin for Greenshot + * Copyright (C) 2011 Francis Noel + * + * For more information see: http://getgreenshot.org/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, 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 . + */ + +namespace GreenshotGooglePhotosPlugin { + public enum LangKey + { + upload_menu_item, + upload_failure, + communication_wait, + Configure + } +} diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-cs-CZ.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-cs-CZ.xml similarity index 74% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-cs-CZ.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-cs-CZ.xml index 4fe14494b..7168b569c 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-cs-CZ.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-cs-CZ.xml @@ -1,29 +1,29 @@ - - - - Zrušit - Probíhá komunikace s Picasem. Prosím počkejte ... - Konfigurace - Opravdu chcete odstranit obrázek {0} z Picasa? - Odstranit Picasa {0} - Historie - Neplatná oprávnění. Otevřít nastavení pro provedené změn. - Po odeslání - Kopírovat odkaz do schránky - Zobrazit historii - Výchozí velikost - Heslo - Formát obrázku - Jméno - OK - Originál URL - Čtvercové náhledy URL ??? - Webová adresa URL - Nastavení Picasa - Nahrát - Nahrání obrázku do Picasa se nezdařilo: - Nahrát do Picasa - Úspěšně odeslaný obrázek do Picasa! - Prosím ověřit aplikaci Picasa. Otevřít nastavení obrazovky. ??? - + + + + Zrušit + Probíhá komunikace s Picasem. Prosím počkejte ... + Konfigurace + Opravdu chcete odstranit obrázek {0} z GooglePhotos? + Odstranit GooglePhotos {0} + Historie + Neplatná oprávnění. Otevřít nastavení pro provedené změn. + Po odeslání + Kopírovat odkaz do schránky + Zobrazit historii + Výchozí velikost + Heslo + Formát obrázku + Jméno + OK + Originál URL + Čtvercové náhledy URL ??? + Webová adresa URL + Nastavení GooglePhotos + Nahrát + Nahrání obrázku do GooglePhotos se nezdařilo: + Nahrát do GooglePhotos + Úspěšně odeslaný obrázek do GooglePhotos! + Prosím ověřit aplikaci GooglePhotos. Otevřít nastavení obrazovky. ??? + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-de-DE.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-de-DE.xml similarity index 74% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-de-DE.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-de-DE.xml index 6b30f0214..0a437a443 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-de-DE.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-de-DE.xml @@ -8,25 +8,25 @@ Anschliessend - Picasa konfigurieren + GooglePhotos konfigurieren - Hochladen zu Picasa + Hochladen zu GooglePhotos - Picasa Einstellungen + GooglePhotos Einstellungen - Hochladen zu Picasa war erfolgreich ! + Hochladen zu GooglePhotos war erfolgreich ! - Fehler beim Hochladen zu Picasa: + Fehler beim Hochladen zu GooglePhotos: Grafikformat - Übermittle Daten zu Picasa. Bitte warten... + Übermittle Daten zu GooglePhotos. Bitte warten... diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-en-US.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-en-US.xml similarity index 73% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-en-US.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-en-US.xml index e99c84ed7..70ec9f14a 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-en-US.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-en-US.xml @@ -8,25 +8,25 @@ After upload - Configure Picasa + Configure GooglePhotos - Upload to Picasa + Upload to GooglePhotos - Picasa settings + GooglePhotos settings - Successfully uploaded image to Picasa! + Successfully uploaded image to GooglePhotos! - An error occured while uploading to Picasa: + An error occured while uploading to GooglePhotos: Image format - Communicating with Picasa. Please wait... + Communicating with GooglePhotos. Please wait... \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-fr-FR.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-fr-FR.xml similarity index 62% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-fr-FR.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-fr-FR.xml index 589d55613..553d5725e 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-fr-FR.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-fr-FR.xml @@ -1,14 +1,14 @@ - - - - Communication en cours avec Picasa. Veuillez patientez... - Configurer Picasa - Après téléversement - Copier le lien dans le presse-papier - Format image - Paramètres Picasa - Une erreur s'est produite lors du téléversement vers Picasa : - Téléverser vers Picasa - Image téléversée avec succès vers Picasa ! - + + + + Communication en cours avec GooglePhotos. Veuillez patientez... + Configurer GooglePhotos + Après téléversement + Copier le lien dans le presse-papier + Format image + Paramètres GooglePhotos + Une erreur s'est produite lors du téléversement vers GooglePhotos : + Téléverser vers GooglePhotos + Image téléversée avec succès vers GooglePhotos ! + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-id-ID.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-id-ID.xml similarity index 58% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-id-ID.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-id-ID.xml index 5632a3daa..c906bb08e 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-id-ID.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-id-ID.xml @@ -1,14 +1,14 @@ - - - - Menyambungkan ke Picasa. Tunggu sebentar... - Konfigurasi Picasa - Sesudah mengunggah - Sambung ke papanklip - Format gambar - Pengaturan Picasa - Kesalahan terjadi ketika mengunggah ke Picasa: - Unggah ke Picasa - Berhasil mengunggah gambar ke Picasa! - + + + + Menyambungkan ke GooglePhotos. Tunggu sebentar... + Konfigurasi GooglePhotos + Sesudah mengunggah + Sambung ke papanklip + Format gambar + Pengaturan GooglePhotos + Kesalahan terjadi ketika mengunggah ke GooglePhotos: + Unggah ke GooglePhotos + Berhasil mengunggah gambar ke GooglePhotos! + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-it-IT.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-it-IT.xml similarity index 73% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-it-IT.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-it-IT.xml index 6f0b76c78..801e57cfd 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-it-IT.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-it-IT.xml @@ -1,32 +1,32 @@ - - - - - Collegamento agli Appunti - - - Dopo il caricamento - - - Impostazioni Picasa - - - Carica su Picasa - - - Impostazioni Picasa - - - Caricamento immagine su Picasa completato! - - - Si è verificato un errore durante il caricamento su Picasa: - - - Formato immagine - - - Comunicazione con Picasa... - - - + + + + + Collegamento agli Appunti + + + Dopo il caricamento + + + Impostazioni GooglePhotos + + + Carica su GooglePhotos + + + Impostazioni GooglePhotos + + + Caricamento immagine su GooglePhotos completato! + + + Si è verificato un errore durante il caricamento su GooglePhotos: + + + Formato immagine + + + Comunicazione con GooglePhotos... + + + diff --git a/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ja-JP.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ja-JP.xml new file mode 100644 index 000000000..19c1ffc6c --- /dev/null +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ja-JP.xml @@ -0,0 +1,14 @@ + + + + リンクをクリップボードへコピー + アップロード後 + GooglePhotos の設定 + GooglePhotos にアップロード + GooglePhotos の設定 + GooglePhotos へのアップロードに成功しました! + GooglePhotos へのアップロード中にエラーが発生しました: + 画像フォーマット + GooglePhotos に接続中です。しばらくお待ち下さい... + + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-kab-DZ.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-kab-DZ.xml similarity index 59% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-kab-DZ.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-kab-DZ.xml index 367287108..dd5c784d4 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-kab-DZ.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-kab-DZ.xml @@ -1,14 +1,14 @@ - S tidett tebɣiḍ ad tekkseḍ amazray adigan n Picasa ?... - Swel Picasa + S tidett tebɣiḍ ad tekkseḍ amazray adigan n GooglePhotos ?... + Swel GooglePhotos Ticki yemmed usali Nɣel aseɣwen ɣef afus Amsal n tugna - Iɣewwaṛen Picasa - Teḍra-d tuccḍa deg usali ɣer Picasa : - Sali ɣer Picasa - Tugna tuli ɣer Picasa ! + Iɣewwaṛen GooglePhotos + Teḍra-d tuccḍa deg usali ɣer GooglePhotos : + Sali ɣer GooglePhotos + Tugna tuli ɣer GooglePhotos ! diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ko-KR.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ko-KR.xml similarity index 74% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-ko-KR.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ko-KR.xml index f452a9618..d095f9f72 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ko-KR.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ko-KR.xml @@ -8,25 +8,25 @@ 얼로드 후 - Picasa 설정 + GooglePhotos 설정 - Picasa로 업로드 + GooglePhotos로 업로드 - Picasa 설정 + GooglePhotos 설정 - Picasa로 이미지 업로드 성공! + GooglePhotos로 이미지 업로드 성공! - Picasa로 업로드시 오류 발생: + GooglePhotos로 업로드시 오류 발생: 이미지 형식 - Picasa와 연결 중 잠시 기다리세요... + GooglePhotos와 연결 중 잠시 기다리세요... \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-lv-LV.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-lv-LV.xml similarity index 75% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-lv-LV.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-lv-LV.xml index c33bdc519..7e0e0b8ca 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-lv-LV.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-lv-LV.xml @@ -9,25 +9,25 @@ Pēc augšupielādes - Picasa iestatījumi + GooglePhotos iestatījumi - Augšupieladēt uz Picasa + Augšupieladēt uz GooglePhotos - Picasa iestatījumi + GooglePhotos iestatījumi - Attēls veiksmīgi augšupielādēts uz Picasa! + Attēls veiksmīgi augšupielādēts uz GooglePhotos! - Kļūda augšuplādējot uz Picasa: + Kļūda augšuplādējot uz GooglePhotos: Attēla formāts - Savienojos ar Picasa. Lūdzu uzgaidiet... + Savienojos ar GooglePhotos. Lūdzu uzgaidiet... diff --git a/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pl-PL.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pl-PL.xml new file mode 100644 index 000000000..be521cb06 --- /dev/null +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pl-PL.xml @@ -0,0 +1,14 @@ + + + + Trwa komunikacja z GooglePhotos. Proszę czekać... + Konfiguruj GooglePhotos + Po wysłaniu + Link do schowka + Format obrazów + Ustawienia GooglePhotos + Wystąpił błąd przy wysyłaniu do GooglePhotos: + Wyślij do GooglePhotos + Wysyłanie obrazu do GooglePhotos powiodło się! + + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-pt-PT.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pt-PT.xml similarity index 73% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-pt-PT.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pt-PT.xml index 4649e8005..c3194dad0 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-pt-PT.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-pt-PT.xml @@ -8,25 +8,25 @@ Após enviar - Configurar o Picasa + Configurar o GooglePhotos - enviar para o Picasa + enviar para o GooglePhotos - Definições Picasa + Definições GooglePhotos - Imagem enviada com êxito para o Picasa! + Imagem enviada com êxito para o GooglePhotos! - Ocorreu um erro ao enviar para o Picasa: + Ocorreu um erro ao enviar para o GooglePhotos: Formato da imagem - A comunicar com o Picasa. Por favor aguarde... + A comunicar com o GooglePhotos. Por favor aguarde... \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ru-RU.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ru-RU.xml similarity index 61% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-ru-RU.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ru-RU.xml index 7a273bce7..c7aa0317d 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ru-RU.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-ru-RU.xml @@ -1,14 +1,14 @@ - - - - Обмен данными с Picasa. Подождите... - Настройка Picasa - После загрузки - Ссылки в буфер обмена - Формат изображения - Настройки Picasa - Произошла ошибка при загрузке на Picasa: - Загрузить на Picasa - Изображение успешно загружено на Picasa! - + + + + Обмен данными с GooglePhotos. Подождите... + Настройка GooglePhotos + После загрузки + Ссылки в буфер обмена + Формат изображения + Настройки GooglePhotos + Произошла ошибка при загрузке на GooglePhotos: + Загрузить на GooglePhotos + Изображение успешно загружено на GooglePhotos! + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-sr-RS.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sr-RS.xml similarity index 98% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-sr-RS.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sr-RS.xml index f43494f01..88c045edd 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-sr-RS.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sr-RS.xml @@ -1,14 +1,14 @@ - - - - Комуницирам с Пикасом. Сачекајте… - Поставке Пикасе - Након отпремања: - Веза ка остави - Формат слике: - Поставке Пикасе - Дошло је до грешке при отпремању на Пикасу: - Отпреми на Пикасу - Слика је успешно отпремљена на Пикасу. - + + + + Комуницирам с Пикасом. Сачекајте… + Поставке Пикасе + Након отпремања: + Веза ка остави + Формат слике: + Поставке Пикасе + Дошло је до грешке при отпремању на Пикасу: + Отпреми на Пикасу + Слика је успешно отпремљена на Пикасу. + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-sv-SE.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sv-SE.xml similarity index 73% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-sv-SE.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sv-SE.xml index d3469d802..86a0aec4c 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-sv-SE.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-sv-SE.xml @@ -8,25 +8,25 @@ Vid uppladdning - Konfigurera Picasa + Konfigurera GooglePhotos - Ladda upp till Picasa + Ladda upp till GooglePhotos - Picasa-inställningar + GooglePhotos-inställningar - Skärmdumpen laddades upp till Picasa! + Skärmdumpen laddades upp till GooglePhotos! - Ett fel uppstod vid uppladdning till Picasa: + Ett fel uppstod vid uppladdning till GooglePhotos: Bildformat - Kommunicerar med Picasa. Vänta... + Kommunicerar med GooglePhotos. Vänta... \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-uk-UA.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-uk-UA.xml similarity index 53% rename from GreenshotPicasaPlugin/Languages/language_picasaplugin-uk-UA.xml rename to GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-uk-UA.xml index c1ecd0a4c..9be4ab5b6 100644 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-uk-UA.xml +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-uk-UA.xml @@ -1,14 +1,14 @@ - - - - Посилання в буфер обміну - Після вивантаження - Налаштувати Picasa - Вивантажити на Picasa - Параметри Picasa - Зображення вдало вивантажено на Picasa! - Відбулась помилка під час вивантаження на Picasa: - Формат зображення - З’єднання з Picasa. Будь ласка, зачекайте... - - + + + + Посилання в буфер обміну + Після вивантаження + Налаштувати GooglePhotos + Вивантажити на GooglePhotos + Параметри GooglePhotos + Зображення вдало вивантажено на GooglePhotos! + Відбулась помилка під час вивантаження на GooglePhotos: + Формат зображення + З’єднання з GooglePhotos. Будь ласка, зачекайте... + + diff --git a/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-CN.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-CN.xml new file mode 100644 index 000000000..0323ba38d --- /dev/null +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-CN.xml @@ -0,0 +1,14 @@ + + + + 正在连接到GooglePhotos。请稍后... + 配置 GooglePhotos + 上传之后 + 复制链接到剪贴板 + 图片格式 + GooglePhotos设置 + 上传到GooglePhotos时发生错误: + 上传到GooglePhotos + 图片已成功上传到了GooglePhotos! + + \ No newline at end of file diff --git a/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-TW.xml b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-TW.xml new file mode 100644 index 000000000..34fa14d3f --- /dev/null +++ b/GreenshotGooglePhotosPlugin/Languages/language_googlephotosplugin-zh-TW.xml @@ -0,0 +1,14 @@ + + + + 正在與 GooglePhotos 通訊,請稍候... + 組態 GooglePhotos + 上傳後 + 連結到剪貼簿 + 圖片格式 + GooglePhotos 設定 + 上傳到 GooglePhotos 時發生錯誤: + 上傳到 GooglePhotos + 上傳圖片到 GooglePhotos 成功! + + \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Properties/AssemblyInfo.cs b/GreenshotGooglePhotosPlugin/Properties/AssemblyInfo.cs similarity index 94% rename from GreenshotPicasaPlugin/Properties/AssemblyInfo.cs rename to GreenshotGooglePhotosPlugin/Properties/AssemblyInfo.cs index 3a917ddcd..d4fa2878a 100644 --- a/GreenshotPicasaPlugin/Properties/AssemblyInfo.cs +++ b/GreenshotGooglePhotosPlugin/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyDescription("A plugin to upload images to Picasa")] +[assembly: AssemblyDescription("A plugin to upload images to GooglePhotos")] // This sets the default COM visibility of types in the assembly to invisible. // If you need to expose a type to COM, use [ComVisible(true)] on that type. diff --git a/GreenshotPicasaPlugin/README b/GreenshotGooglePhotosPlugin/README similarity index 100% rename from GreenshotPicasaPlugin/README rename to GreenshotGooglePhotosPlugin/README diff --git a/GreenshotImgurPlugin/GreenshotImgurPlugin.Credentials.template b/GreenshotImgurPlugin/GreenshotImgurPlugin.Credentials.template index 42dd7986f..4c6948fea 100644 --- a/GreenshotImgurPlugin/GreenshotImgurPlugin.Credentials.template +++ b/GreenshotImgurPlugin/GreenshotImgurPlugin.Credentials.template @@ -25,7 +25,7 @@ namespace GreenshotImgurPlugin { /// You can set your own values here /// public static class ImgurCredentials { - public static string CONSUMER_KEY = "${Imgur_ClientId}"; - public static string CONSUMER_SECRET = "${Imgur_ClientSecret}"; + public static string CONSUMER_KEY = "${Imgur13_ClientId}"; + public static string CONSUMER_SECRET = "${Imgur13_ClientSecret}"; } } diff --git a/GreenshotImgurPlugin/ImgurUtils.cs b/GreenshotImgurPlugin/ImgurUtils.cs index ecbbf775b..6c4b06674 100644 --- a/GreenshotImgurPlugin/ImgurUtils.cs +++ b/GreenshotImgurPlugin/ImgurUtils.cs @@ -20,11 +20,11 @@ */ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Net; using GreenshotPlugin.Core; +using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; @@ -37,10 +37,8 @@ namespace GreenshotImgurPlugin { private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ImgurUtils)); private const string SmallUrlPattern = "http://i.imgur.com/{0}s.jpg"; private static readonly ImgurConfiguration Config = IniConfig.GetIniSection(); - private const string AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}"; - private const string TokenUrl = "https://api.imgur.com/oauth2/token"; - /// + /// /// Check if we need to load the history /// /// @@ -162,20 +160,20 @@ namespace GreenshotImgurPlugin { responseString = reader.ReadToEnd(); } } catch (Exception ex) { - Log.Error("Upload to imgur gave an exeption: ", ex); + Log.Error("Upload to imgur gave an exception: ", ex); throw; } } else { var oauth2Settings = new OAuth2Settings { - AuthUrlPattern = AuthUrlPattern, - TokenUrl = TokenUrl, - RedirectUrl = "https://getgreenshot.org/oauth/imgur", + AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}", + TokenUrl = "https://api.imgur.com/oauth2/token", + RedirectUrl = "https://getgreenshot.org/authorize/imgur", CloudServiceName = "Imgur", ClientId = ImgurCredentials.CONSUMER_KEY, ClientSecret = ImgurCredentials.CONSUMER_SECRET, - AuthorizeMode = OAuth2AuthorizeMode.OutOfBoundAuto, + AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver, RefreshToken = Config.RefreshToken, AccessToken = Config.AccessToken, AccessTokenExpires = Config.AccessTokenExpires @@ -221,7 +219,7 @@ namespace GreenshotImgurPlugin { Log.InfoFormat("Retrieving Imgur image for {0} with url {1}", imgurInfo.Hash, imgurInfo.SmallSquare); HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(string.Format(SmallUrlPattern, imgurInfo.Hash), HTTPMethod.GET); webRequest.ServicePoint.Expect100Continue = false; - // Not for getting the thumbnail, in anonymous modus + // Not for getting the thumbnail, in anonymous mode //SetClientId(webRequest); using WebResponse response = webRequest.GetResponse(); LogRateLimitInfo(response); @@ -304,7 +302,7 @@ namespace GreenshotImgurPlugin { } } } - // Make sure we remove it from the history, if no error occured + // Make sure we remove it from the history, if no error occurred Config.runtimeImgurHistory.Remove(imgurInfo.Hash); Config.ImgurUploadHistory.Remove(imgurInfo.Hash); imgurInfo.Image = null; diff --git a/GreenshotPhotobucketPlugin/PhotobucketUtils.cs b/GreenshotPhotobucketPlugin/PhotobucketUtils.cs index 4226e57c4..bb59ff09a 100644 --- a/GreenshotPhotobucketPlugin/PhotobucketUtils.cs +++ b/GreenshotPhotobucketPlugin/PhotobucketUtils.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Drawing; using System.Xml; using GreenshotPlugin.Core; +using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ja-JP.xml b/GreenshotPicasaPlugin/Languages/language_picasaplugin-ja-JP.xml deleted file mode 100644 index ac8216e96..000000000 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-ja-JP.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - リンクをクリップボードへコピー - アップロード後 - Picasa の設定 - Picasa にアップロード - Picasa の設定 - Picasa へのアップロードに成功しました! - Picasa へのアップロード中にエラーが発生しました: - 画像フォーマット - Picasa に接続中です。しばらくお待ち下さい... - - \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-pl-PL.xml b/GreenshotPicasaPlugin/Languages/language_picasaplugin-pl-PL.xml deleted file mode 100644 index c4be12b84..000000000 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-pl-PL.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - Trwa komunikacja z Picasa. Proszę czekać... - Konfiguruj Picasa - Po wysłaniu - Link do schowka - Format obrazów - Ustawienia Picasa - Wystąpił błąd przy wysyłaniu do Picasa: - Wyślij do Picasa - Wysyłanie obrazu do Picasa powiodło się! - - \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-CN.xml b/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-CN.xml deleted file mode 100644 index 38601fc93..000000000 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-CN.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - 正在连接到Picasa。请稍后... - 配置 Picasa - 上传之后 - 复制链接到剪贴板 - 图片格式 - Picasa设置 - 上传到Picasa时发生错误: - 上传到Picasa - 图片已成功上传到了Picasa! - - \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-TW.xml b/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-TW.xml deleted file mode 100644 index a818bc748..000000000 --- a/GreenshotPicasaPlugin/Languages/language_picasaplugin-zh-TW.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - 正在與 Picasa 通訊,請稍候... - 組態 Picasa - 上傳後 - 連結到剪貼簿 - 圖片格式 - Picasa 設定 - 上傳到 Picasa 時發生錯誤: - 上傳到 Picasa - 上傳圖片到 Picasa 成功! - - \ No newline at end of file diff --git a/GreenshotPicasaPlugin/Picasa.png b/GreenshotPicasaPlugin/Picasa.png deleted file mode 100644 index 97b70cf86..000000000 Binary files a/GreenshotPicasaPlugin/Picasa.png and /dev/null differ diff --git a/GreenshotPlugin/Controls/OAuthLoginForm.cs b/GreenshotPlugin/Controls/OAuthLoginForm.cs index e2b036f97..998b938bb 100644 --- a/GreenshotPlugin/Controls/OAuthLoginForm.cs +++ b/GreenshotPlugin/Controls/OAuthLoginForm.cs @@ -1,20 +1,20 @@ /* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * + * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -32,9 +32,8 @@ namespace GreenshotPlugin.Controls { public sealed partial class OAuthLoginForm : Form { private static readonly ILog LOG = LogManager.GetLogger(typeof(OAuthLoginForm)); private readonly string _callbackUrl; - private IDictionary _callbackParameters; - - public IDictionary CallbackParameters => _callbackParameters; + + public IDictionary CallbackParameters { get; private set; } public bool IsOk => DialogResult == DialogResult.OK; @@ -94,7 +93,7 @@ namespace GreenshotPlugin.Controls { 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; } @@ -102,7 +101,7 @@ namespace GreenshotPlugin.Controls { private void AddressTextBox_KeyPress(object sender, KeyPressEventArgs e) { //Cancel the key press so the user can't enter a new url - e.Handled = true; + e.Handled = true; } } } diff --git a/GreenshotPlugin/Core/EnvironmentInfo.cs b/GreenshotPlugin/Core/EnvironmentInfo.cs new file mode 100644 index 000000000..01eb11772 --- /dev/null +++ b/GreenshotPlugin/Core/EnvironmentInfo.cs @@ -0,0 +1,793 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using GreenshotPlugin.IniFile; +using GreenshotPlugin.UnmanagedHelpers; +using Microsoft.Win32; + +namespace GreenshotPlugin.Core +{ + /// + /// Description of EnvironmentInfo. + /// + public static class EnvironmentInfo + { + private static bool? _isWindows; + + public static bool IsWindows + { + get + { + if (_isWindows.HasValue) + { + return _isWindows.Value; + } + _isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); + return _isWindows.Value; + } + } + + public static bool IsNet45OrNewer() + { + // Class "ReflectionContext" exists from .NET 4.5 onwards. + return Type.GetType("System.Reflection.ReflectionContext", false) != null; + } + + public static string GetGreenshotVersion(bool shortVersion = false) + { + var executingAssembly = Assembly.GetExecutingAssembly(); + + // Use assembly version + string greenshotVersion = executingAssembly.GetName().Version.ToString(); + + // Use AssemblyFileVersion if available + var assemblyFileVersionAttribute = executingAssembly.GetCustomAttribute(); + if (!string.IsNullOrEmpty(assemblyFileVersionAttribute?.Version)) + { + var assemblyFileVersion = new Version(assemblyFileVersionAttribute.Version); + greenshotVersion = assemblyFileVersion.ToString(3); + } + + if (!shortVersion) + { + // Use AssemblyInformationalVersion if available + var informationalVersionAttribute = executingAssembly.GetCustomAttribute(); + if (!string.IsNullOrEmpty(informationalVersionAttribute?.InformationalVersion)) + { + greenshotVersion = informationalVersionAttribute.InformationalVersion; + } + } + + return greenshotVersion.Replace("+", " - "); + } + + public static string EnvironmentToString(bool newline) + { + StringBuilder environment = new StringBuilder(); + environment.Append("Software version: " + GetGreenshotVersion()); + if (IniConfig.IsPortable) { + environment.Append(" Portable"); + } + environment.Append(" (" + OsInfo.Bits + " bit)"); + + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + environment.Append(".NET runtime version: " + Environment.Version); + if (IsNet45OrNewer()) + { + environment.Append("+"); + + } + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + environment.Append("Time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss zzz")); + + if (IsWindows) + { + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + + environment.Append($"OS: {OsInfo.Name}"); + if (!string.IsNullOrEmpty(OsInfo.Edition)) + { + environment.Append($" {OsInfo.Edition}"); + + } + if (!string.IsNullOrEmpty(OsInfo.ServicePack)) + { + environment.Append($" {OsInfo.ServicePack}"); + + } + environment.Append($" x{OsInfo.Bits}"); + environment.Append($" {OsInfo.VersionString}"); + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + // Get some important information for fixing GDI related Problems + environment.AppendFormat("GDI object count: {0}", User32.GetGuiResourcesGDICount()); + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + environment.AppendFormat("User object count: {0}", User32.GetGuiResourcesUserCount()); + } + else + { + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + environment.AppendFormat("OS: {0}", Environment.OSVersion.Platform); + } + if (newline) + { + environment.AppendLine(); + } + else + { + environment.Append(", "); + } + // TODO: Is this needed? + // environment.AppendFormat("Surface count: {0}", Surface.Count); + + return environment.ToString(); + } + + public static string ExceptionToString(Exception ex) + { + if (ex == null) + return "null\r\n"; + + StringBuilder report = new StringBuilder(); + + report.AppendLine("Exception: " + ex.GetType()); + report.AppendLine("Message: " + ex.Message); + if (ex.Data.Count > 0) + { + report.AppendLine(); + report.AppendLine("Additional Information:"); + foreach (object key in ex.Data.Keys) + { + object data = ex.Data[key]; + if (data != null) + { + report.AppendLine(key + " : " + data); + } + } + } + if (ex is ExternalException externalException) + { + // e.g. COMException + report.AppendLine().AppendLine("ErrorCode: 0x" + externalException.ErrorCode.ToString("X")); + } + + report.AppendLine().AppendLine("Stack:").AppendLine(ex.StackTrace); + + if (ex is ReflectionTypeLoadException reflectionTypeLoadException) + { + report.AppendLine().AppendLine("LoaderExceptions: "); + foreach (Exception cbE in reflectionTypeLoadException.LoaderExceptions) + { + report.AppendLine(cbE.Message); + } + } + + if (ex.InnerException != null) + { + report.AppendLine("--- InnerException: ---"); + report.AppendLine(ExceptionToString(ex.InnerException)); + } + return report.ToString(); + } + + public static string BuildReport(Exception exception) + { + StringBuilder exceptionText = new StringBuilder(); + exceptionText.AppendLine(EnvironmentToString(true)); + exceptionText.AppendLine(ExceptionToString(exception)); + exceptionText.AppendLine("Configuration dump:"); + + return exceptionText.ToString(); + } + } + + /// + /// Provides detailed information about the host operating system. + /// Code is available at: http://www.csharp411.com/determine-windows-version-and-edition-with-c/ + /// + public static class OsInfo + { + /// + /// Determines if the current application is 32 or 64-bit. + /// + public static int Bits => IntPtr.Size * 8; + + private static string _sEdition; + + /// + /// Gets the edition of the operating system running on this computer. + /// + public static string Edition + { + get + { + if (_sEdition != null) + { + return _sEdition; //***** RETURN *****// + } + + string edition = string.Empty; + + OperatingSystem osVersion = Environment.OSVersion; + OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); + + if (GetVersionEx(ref osVersionInfo)) + { + int majorVersion = osVersion.Version.Major; + int minorVersion = osVersion.Version.Minor; + byte productType = osVersionInfo.ProductType; + ushort suiteMask = osVersionInfo.SuiteMask; + + if (majorVersion == 4) + { + if (productType == VER_NT_WORKSTATION) + { + // Windows NT 4.0 Workstation + edition = "Workstation"; + } + else if (productType == VER_NT_SERVER) + { + edition = (suiteMask & VER_SUITE_ENTERPRISE) != 0 ? "Enterprise Server" : "Standard Server"; + } + } + + else if (majorVersion == 5) + { + if (productType == VER_NT_WORKSTATION) + { + if ((suiteMask & VER_SUITE_PERSONAL) != 0) + { + // Windows XP Home Edition + edition = "Home"; + } + else + { + // Windows XP / Windows 2000 Professional + edition = "Professional"; + } + } + else if (productType == VER_NT_SERVER) + { + if (minorVersion == 0) + { + if ((suiteMask & VER_SUITE_DATACENTER) != 0) + { + // Windows 2000 Datacenter Server + edition = "Datacenter Server"; + } + else if ((suiteMask & VER_SUITE_ENTERPRISE) != 0) + { + // Windows 2000 Advanced Server + edition = "Advanced Server"; + } + else + { + // Windows 2000 Server + edition = "Server"; + } + } + else + { + if ((suiteMask & VER_SUITE_DATACENTER) != 0) + { + // Windows Server 2003 Datacenter Edition + edition = "Datacenter"; + } + else if ((suiteMask & VER_SUITE_ENTERPRISE) != 0) + { + // Windows Server 2003 Enterprise Edition + edition = "Enterprise"; + } + else if ((suiteMask & VER_SUITE_BLADE) != 0) + { + // Windows Server 2003 Web Edition + edition = "Web Edition"; + } + else + { + // Windows Server 2003 Standard Edition + edition = "Standard"; + } + } + } + } + + else if (majorVersion == 6) + { + if (GetProductInfo(majorVersion, minorVersion, osVersionInfo.ServicePackMajor, osVersionInfo.ServicePackMinor, out var ed)) + { + switch (ed) + { + case PRODUCT_BUSINESS: + edition = "Business"; + break; + case PRODUCT_BUSINESS_N: + edition = "Business N"; + break; + case PRODUCT_CLUSTER_SERVER: + edition = "HPC Edition"; + break; + case PRODUCT_DATACENTER_SERVER: + edition = "Datacenter Server"; + break; + case PRODUCT_DATACENTER_SERVER_CORE: + edition = "Datacenter Server (core installation)"; + break; + case PRODUCT_ENTERPRISE: + edition = "Enterprise"; + break; + case PRODUCT_ENTERPRISE_N: + edition = "Enterprise N"; + break; + case PRODUCT_ENTERPRISE_SERVER: + edition = "Enterprise Server"; + break; + case PRODUCT_ENTERPRISE_SERVER_CORE: + edition = "Enterprise Server (core installation)"; + break; + case PRODUCT_ENTERPRISE_SERVER_CORE_V: + edition = "Enterprise Server without Hyper-V (core installation)"; + break; + case PRODUCT_ENTERPRISE_SERVER_IA64: + edition = "Enterprise Server for Itanium-based Systems"; + break; + case PRODUCT_ENTERPRISE_SERVER_V: + edition = "Enterprise Server without Hyper-V"; + break; + case PRODUCT_HOME_BASIC: + edition = "Home Basic"; + break; + case PRODUCT_HOME_BASIC_N: + edition = "Home Basic N"; + break; + case PRODUCT_HOME_PREMIUM: + edition = "Home Premium"; + break; + case PRODUCT_HOME_PREMIUM_N: + edition = "Home Premium N"; + break; + case PRODUCT_HYPERV: + edition = "Microsoft Hyper-V Server"; + break; + case PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT: + edition = "Windows Essential Business Management Server"; + break; + case PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING: + edition = "Windows Essential Business Messaging Server"; + break; + case PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY: + edition = "Windows Essential Business Security Server"; + break; + case PRODUCT_SERVER_FOR_SMALLBUSINESS: + edition = "Windows Essential Server Solutions"; + break; + case PRODUCT_SERVER_FOR_SMALLBUSINESS_V: + edition = "Windows Essential Server Solutions without Hyper-V"; + break; + case PRODUCT_SMALLBUSINESS_SERVER: + edition = "Windows Small Business Server"; + break; + case PRODUCT_STANDARD_SERVER: + edition = "Standard Server"; + break; + case PRODUCT_STANDARD_SERVER_CORE: + edition = "Standard Server (core installation)"; + break; + case PRODUCT_STANDARD_SERVER_CORE_V: + edition = "Standard Server without Hyper-V (core installation)"; + break; + case PRODUCT_STANDARD_SERVER_V: + edition = "Standard Server without Hyper-V"; + break; + case PRODUCT_STARTER: + edition = "Starter"; + break; + case PRODUCT_STORAGE_ENTERPRISE_SERVER: + edition = "Enterprise Storage Server"; + break; + case PRODUCT_STORAGE_EXPRESS_SERVER: + edition = "Express Storage Server"; + break; + case PRODUCT_STORAGE_STANDARD_SERVER: + edition = "Standard Storage Server"; + break; + case PRODUCT_STORAGE_WORKGROUP_SERVER: + edition = "Workgroup Storage Server"; + break; + case PRODUCT_UNDEFINED: + edition = "Unknown product"; + break; + case PRODUCT_ULTIMATE: + edition = "Ultimate"; + break; + case PRODUCT_ULTIMATE_N: + edition = "Ultimate N"; + break; + case PRODUCT_WEB_SERVER: + edition = "Web Server"; + break; + case PRODUCT_WEB_SERVER_CORE: + edition = "Web Server (core installation)"; + break; + } + } + } + } + + _sEdition = edition; + return edition; + } + } + + private static string _name; + + /// + /// Gets the name of the operating system running on this computer. + /// + public static string Name + { + get + { + if (_name != null) + { + return _name; //***** RETURN *****// + } + + string name = "unknown"; + + OperatingSystem osVersion = Environment.OSVersion; + OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); + if (GetVersionEx(ref osVersionInfo)) + { + int majorVersion = osVersion.Version.Major; + int minorVersion = osVersion.Version.Minor; + byte productType = osVersionInfo.ProductType; + ushort suiteMask = osVersionInfo.SuiteMask; + switch (osVersion.Platform) + { + case PlatformID.Win32Windows: + if (majorVersion == 4) + { + string csdVersion = osVersionInfo.ServicePackVersion; + switch (minorVersion) + { + case 0: + if (csdVersion == "B" || csdVersion == "C") + { + name = "Windows 95 OSR2"; + } + else + { + name = "Windows 95"; + } + + break; + case 10: + name = csdVersion == "A" ? "Windows 98 Second Edition" : "Windows 98"; + break; + case 90: + name = "Windows Me"; + break; + } + } + + break; + case PlatformID.Win32NT: + switch (majorVersion) + { + case 3: + name = "Windows NT 3.51"; + break; + case 4: + switch (productType) + { + case 1: + name = "Windows NT 4.0"; + break; + case 3: + name = "Windows NT 4.0 Server"; + break; + } + + break; + case 5: + switch (minorVersion) + { + case 0: + name = "Windows 2000"; + break; + case 1: + name = suiteMask switch + { + 0x0200 => "Windows XP Professional", + _ => "Windows XP" + }; + break; + case 2: + name = suiteMask switch + { + 0x0200 => "Windows XP Professional x64", + 0x0002 => "Windows Server 2003 Enterprise", + 0x0080 => "Windows Server 2003 Data Center", + 0x0400 => "Windows Server 2003 Web Edition", + 0x8000 => "Windows Home Server", + _ => "Windows Server 2003" + }; + break; + } + + break; + case 6: + switch (minorVersion) + { + case 0: + name = productType switch + { + 3 => "Windows Server 2008", + _ => "Windows Vista" + }; + break; + case 1: + name = productType switch + { + 3 => "Windows Server 2008 R2", + _ => "Windows 7" + }; + break; + case 2: + name = "Windows 8"; + break; + case 3: + name = "Windows 8.1"; + break; + } + + break; + case 10: + string releaseId = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", "").ToString(); + name = $"Windows 10 {releaseId}"; + break; + } + + break; + } + } + + _name = name; + return name; + } + } + + [DllImport("Kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetProductInfo( + int osMajorVersion, + int osMinorVersion, + int spMajorVersion, + int spMinorVersion, + out int edition); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetVersionEx(ref OSVERSIONINFOEX osVersionInfo); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private unsafe struct OSVERSIONINFOEX + { + /// + /// The size of this data structure, in bytes. Set this member to sizeof(OSVERSIONINFOEX). + /// + private int _dwOSVersionInfoSize; + + private readonly int _dwMajorVersion; + private readonly int _dwMinorVersion; + private readonly int _dwBuildNumber; + private readonly int _dwPlatformId; + private fixed char _szCSDVersion[128]; + private readonly short _wServicePackMajor; + private readonly short _wServicePackMinor; + private readonly ushort _wSuiteMask; + private readonly byte _wProductType; + private readonly byte _wReserved; + + /// A null-terminated string, such as "Service Pack 3", that indicates the latest Service Pack installed on the system. + /// If no Service Pack has been installed, the string is empty. + /// + public string ServicePackVersion + { + get + { + fixed (char* servicePackVersion = _szCSDVersion) + { + return new string(servicePackVersion); + } + + } + } + + /// + /// The major version number of the latest Service Pack installed on the system. For example, for Service Pack 3, the + /// major version number is 3. + /// If no Service Pack has been installed, the value is zero. + /// + public short ServicePackMajor => _wServicePackMajor; + + /// + /// The minor version number of the latest Service Pack installed on the system. For example, for Service Pack 3, the + /// minor version number is 0. + /// + public short ServicePackMinor => _wServicePackMinor; + + /// + /// A bit mask that identifies the product suites available on the system. This member can be a combination of the + /// following values. + /// + public ushort SuiteMask => _wSuiteMask; + + /// + /// Any additional information about the system. + /// + public byte ProductType => _wProductType; + + /// + /// Factory for an empty OsVersionInfoEx + /// + /// OSVERSIONINFOEX + public static OSVERSIONINFOEX Create() + { + return new OSVERSIONINFOEX + { + _dwOSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) + }; + } + } + + private const int PRODUCT_UNDEFINED = 0x00000000; + private const int PRODUCT_ULTIMATE = 0x00000001; + private const int PRODUCT_HOME_BASIC = 0x00000002; + private const int PRODUCT_HOME_PREMIUM = 0x00000003; + private const int PRODUCT_ENTERPRISE = 0x00000004; + private const int PRODUCT_HOME_BASIC_N = 0x00000005; + private const int PRODUCT_BUSINESS = 0x00000006; + private const int PRODUCT_STANDARD_SERVER = 0x00000007; + private const int PRODUCT_DATACENTER_SERVER = 0x00000008; + private const int PRODUCT_SMALLBUSINESS_SERVER = 0x00000009; + private const int PRODUCT_ENTERPRISE_SERVER = 0x0000000A; + private const int PRODUCT_STARTER = 0x0000000B; + private const int PRODUCT_DATACENTER_SERVER_CORE = 0x0000000C; + private const int PRODUCT_STANDARD_SERVER_CORE = 0x0000000D; + private const int PRODUCT_ENTERPRISE_SERVER_CORE = 0x0000000E; + private const int PRODUCT_ENTERPRISE_SERVER_IA64 = 0x0000000F; + private const int PRODUCT_BUSINESS_N = 0x00000010; + private const int PRODUCT_WEB_SERVER = 0x00000011; + private const int PRODUCT_CLUSTER_SERVER = 0x00000012; + private const int PRODUCT_STORAGE_EXPRESS_SERVER = 0x00000014; + private const int PRODUCT_STORAGE_STANDARD_SERVER = 0x00000015; + private const int PRODUCT_STORAGE_WORKGROUP_SERVER = 0x00000016; + private const int PRODUCT_STORAGE_ENTERPRISE_SERVER = 0x00000017; + private const int PRODUCT_SERVER_FOR_SMALLBUSINESS = 0x00000018; + private const int PRODUCT_HOME_PREMIUM_N = 0x0000001A; + private const int PRODUCT_ENTERPRISE_N = 0x0000001B; + private const int PRODUCT_ULTIMATE_N = 0x0000001C; + private const int PRODUCT_WEB_SERVER_CORE = 0x0000001D; + private const int PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT = 0x0000001E; + private const int PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY = 0x0000001F; + private const int PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING = 0x00000020; + private const int PRODUCT_SERVER_FOR_SMALLBUSINESS_V = 0x00000023; + private const int PRODUCT_STANDARD_SERVER_V = 0x00000024; + private const int PRODUCT_ENTERPRISE_SERVER_V = 0x00000026; + private const int PRODUCT_STANDARD_SERVER_CORE_V = 0x00000028; + private const int PRODUCT_ENTERPRISE_SERVER_CORE_V = 0x00000029; + private const int PRODUCT_HYPERV = 0x0000002A; + + private const int VER_NT_WORKSTATION = 1; + private const int VER_NT_SERVER = 3; + private const int VER_SUITE_ENTERPRISE = 2; + private const int VER_SUITE_DATACENTER = 128; + private const int VER_SUITE_PERSONAL = 512; + private const int VER_SUITE_BLADE = 1024; + + /// + /// Gets the service pack information of the operating system running on this computer. + /// + public static string ServicePack + { + get + { + string servicePack = string.Empty; + OSVERSIONINFOEX osVersionInfo = OSVERSIONINFOEX.Create(); + + if (GetVersionEx(ref osVersionInfo)) + { + servicePack = osVersionInfo.ServicePackVersion; + } + + return servicePack; + } + } + + /// Gets the full version string of the operating system running on this computer. + /// + public static string VersionString + { + get + { + if (WindowsVersion.IsWindows10OrLater) + { + return $"build {Environment.OSVersion.Version.Build}"; + } + + if (Environment.OSVersion.Version.Revision != 0) + { + return + $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor} build {Environment.OSVersion.Version.Build} revision {Environment.OSVersion.Version.Revision:X}"; + } + + return $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor} build {Environment.OSVersion.Version.Build}"; + } + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs index 9b7186d16..3159c9dcc 100644 --- a/GreenshotPlugin/Core/NetworkHelper.cs +++ b/GreenshotPlugin/Core/NetworkHelper.cs @@ -32,77 +32,83 @@ using GreenshotPlugin.IniFile; using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces.Plugin; -namespace GreenshotPlugin.Core { - /// - /// HTTP Method to make sure we have the correct method - /// - public enum HTTPMethod { - GET, - POST, - PUT, - DELETE +namespace GreenshotPlugin.Core +{ + /// + /// HTTP Method to make sure we have the correct method + /// + public enum HTTPMethod + { + GET, + POST, + PUT, + DELETE }; - /// - /// Description of NetworkHelper. - /// - public static class NetworkHelper { - private static readonly ILog Log = LogManager.GetLogger(typeof(NetworkHelper)); - private static readonly CoreConfiguration Config = IniConfig.GetIniSection(); - - static NetworkHelper() { - try - { - // Disable certificate checking - ServicePointManager.ServerCertificateValidationCallback += delegate { - return true; - }; - } - catch (Exception ex) - { - Log.Warn("An error has occurred while allowing self-signed certificates:", ex); - } - } + /// + /// Description of NetworkHelper. + /// + public static class NetworkHelper + { + private static readonly ILog Log = LogManager.GetLogger(typeof(NetworkHelper)); + private static readonly CoreConfiguration Config = IniConfig.GetIniSection(); + static NetworkHelper() + { + try + { + // Disable certificate checking + ServicePointManager.ServerCertificateValidationCallback += delegate { return true; }; + } + catch (Exception ex) + { + Log.Warn("An error has occurred while allowing self-signed certificates:", ex); + } + } /// - /// Download the uri into a memory stream, without catching exceptions - /// - /// Of an image - /// MemoryStream which is already seek-ed to 0 - public static MemoryStream GetAsMemoryStream(string url) { - var request = CreateWebRequest(url); + /// Download the uri into a memory stream, without catching exceptions + /// + /// Of an image + /// MemoryStream which is already seek-ed to 0 + public static MemoryStream GetAsMemoryStream(string url) + { + var request = CreateWebRequest(url); using var response = (HttpWebResponse)request.GetResponse(); var memoryStream = new MemoryStream(); - using (var responseStream = response.GetResponseStream()) { + using (var responseStream = response.GetResponseStream()) + { responseStream?.CopyTo(memoryStream); // Make sure it can be used directly memoryStream.Seek(0, SeekOrigin.Begin); } + return memoryStream; } - /// - /// Download the uri to Bitmap - /// - /// Of an image - /// Bitmap - public static Image DownloadImage(string url) - { - var extensions = new StringBuilder(); - foreach (var extension in ImageHelper.StreamConverters.Keys) - { - if (string.IsNullOrEmpty(extension)) - { - continue; - } - extensions.AppendFormat(@"\.{0}|", extension); - } - extensions.Length--; + /// + /// Download the uri to Bitmap + /// + /// Of an image + /// Bitmap + public static Image DownloadImage(string url) + { + var extensions = new StringBuilder(); + foreach (var extension in ImageHelper.StreamConverters.Keys) + { + if (string.IsNullOrEmpty(extension)) + { + continue; + } + + extensions.AppendFormat(@"\.{0}|", extension); + } + + extensions.Length--; var imageUrlRegex = new Regex($@"(http|https)://.*(?{extensions})"); - var match = imageUrlRegex.Match(url); - try + var match = imageUrlRegex.Match(url); + try { using var memoryStream = GetAsMemoryStream(url); try @@ -117,10 +123,12 @@ namespace GreenshotPlugin.Core { { content = streamReader.ReadLine(); } + if (string.IsNullOrEmpty(content)) { throw; } + match = imageUrlRegex.Match(content); if (!match.Success) { @@ -131,441 +139,588 @@ namespace GreenshotPlugin.Core { return ImageHelper.FromStream(memoryStream2, match.Groups["extension"]?.Value); } } - catch (Exception e) - { - Log.Error("Problem downloading the image from: " + url, e); - } - return null; - } + catch (Exception e) + { + Log.Error("Problem downloading the image from: " + url, e); + } - /// - /// 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)); - } + return null; + } - /// - /// 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 + /// + /// 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 - /// - /// Uri with uri to connect to - /// Method to use - /// WebRequest - public static HttpWebRequest CreateWebRequest(Uri uri, HTTPMethod method) { - var webRequest = CreateWebRequest(uri); - webRequest.Method = method.ToString(); - return webRequest; - } + /// + /// 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, eventually with proxy - /// - /// Uri with uri to connect to - /// WebRequest - public static HttpWebRequest CreateWebRequest(Uri uri) { - var webRequest = (HttpWebRequest)WebRequest.Create(uri); - webRequest.Proxy = Config.UseProxy ? CreateProxy(uri) : null; - // Make sure the default credentials are available - webRequest.Credentials = CredentialCache.DefaultCredentials; + /// + /// 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) + { + var webRequest = CreateWebRequest(uri); + webRequest.Method = method.ToString(); + return webRequest; + } - // 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; - webRequest.ReadWriteTimeout = Config.WebRequestReadWriteTimeout*1000; - return webRequest; - } + /// + /// Helper method to create a web request, eventually with proxy + /// + /// Uri with uri to connect to + /// WebRequest + public static HttpWebRequest CreateWebRequest(Uri uri) + { + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + webRequest.Proxy = Config.UseProxy ? CreateProxy(uri) : null; + // Make sure the default credentials are available + webRequest.Credentials = CredentialCache.DefaultCredentials; - /// - /// Create a IWebProxy Object which can be used to access the Internet - /// This method will check the configuration if the proxy is allowed to be used. - /// Usages can be found in the DownloadFavIcon or Jira and Confluence plugins - /// - /// - /// IWebProxy filled with all the proxy details or null if none is set/wanted - public static IWebProxy CreateProxy(Uri uri) { - IWebProxy proxyToUse = null; + // 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; + webRequest.ReadWriteTimeout = Config.WebRequestReadWriteTimeout * 1000; + return webRequest; + } + + /// + /// Create a IWebProxy Object which can be used to access the Internet + /// This method will check the configuration if the proxy is allowed to be used. + /// Usages can be found in the DownloadFavIcon or Jira and Confluence plugins + /// + /// + /// IWebProxy filled with all the proxy details or null if none is set/wanted + public static IWebProxy CreateProxy(Uri uri) + { + IWebProxy proxyToUse = null; if (!Config.UseProxy) { return proxyToUse; } + proxyToUse = WebRequest.DefaultWebProxy; - if (proxyToUse != null) { + if (proxyToUse != null) + { proxyToUse.Credentials = CredentialCache.DefaultCredentials; if (!Log.IsDebugEnabled) { return proxyToUse; } + // check the proxy for the Uri - if (!proxyToUse.IsBypassed(uri)) { + if (!proxyToUse.IsBypassed(uri)) + { var proxyUri = proxyToUse.GetProxy(uri); - if (proxyUri != null) { + if (proxyUri != null) + { Log.Debug("Using proxy: " + proxyUri + " for " + uri); - } else { + } + else + { Log.Debug("No proxy found!"); } - } else { + } + else + { Log.Debug("Proxy bypass for: " + uri); } - } else { + } + else + { Log.Debug("No proxy found!"); } + return proxyToUse; - } + } - /// - /// UrlEncodes a string without the requirement for System.Web - /// - /// - /// - // [Obsolete("Use System.Uri.EscapeDataString instead")] - public static string UrlEncode(string text) { - if (!string.IsNullOrEmpty(text)) { - // System.Uri provides reliable parsing, but doesn't encode spaces. - return Uri.EscapeDataString(text).Replace("%20", "+"); - } - return null; - } + /// + /// UrlEncodes a string without the requirement for System.Web + /// + /// + /// + // [Obsolete("Use System.Uri.EscapeDataString instead")] + public static string UrlEncode(string text) + { + if (!string.IsNullOrEmpty(text)) + { + // System.Uri provides reliable parsing, but doesn't encode spaces. + return Uri.EscapeDataString(text).Replace("%20", "+"); + } - /// - /// A wrapper around the EscapeDataString, as the limit is 32766 characters - /// See: http://msdn.microsoft.com/en-us/library/system.uri.escapedatastring%28v=vs.110%29.aspx - /// - /// - /// escaped data string - public static string EscapeDataString(string text) { - if (!string.IsNullOrEmpty(text)) { - var result = new StringBuilder(); - int currentLocation = 0; - while (currentLocation < text.Length) { - string process = text.Substring(currentLocation, Math.Min(16384, text.Length - currentLocation)); - result.Append(Uri.EscapeDataString(process)); - currentLocation += 16384; - } - return result.ToString(); - } - return null; - } + return null; + } - /// - /// UrlDecodes a string without requiring System.Web - /// - /// String to decode. - /// decoded string - public static string UrlDecode(string text) { - // pre-process for + sign space formatting since System.Uri doesn't handle it - // plus literals are encoded as %2b normally so this should be safe - text = text.Replace("+", " "); - return Uri.UnescapeDataString(text); - } + /// + /// A wrapper around the EscapeDataString, as the limit is 32766 characters + /// See: http://msdn.microsoft.com/en-us/library/system.uri.escapedatastring%28v=vs.110%29.aspx + /// + /// + /// escaped data string + public static string EscapeDataString(string text) + { + if (!string.IsNullOrEmpty(text)) + { + var result = new StringBuilder(); + int currentLocation = 0; + while (currentLocation < text.Length) + { + string process = text.Substring(currentLocation, Math.Min(16384, text.Length - currentLocation)); + result.Append(Uri.EscapeDataString(process)); + currentLocation += 16384; + } - /// - /// ParseQueryString without the requirement for System.Web - /// - /// - /// IDictionary string, string - public static IDictionary ParseQueryString(string queryString) { - IDictionary parameters = new SortedDictionary(); - // remove anything other than query string from uri - if (queryString.Contains("?")) { - queryString = queryString.Substring(queryString.IndexOf('?') + 1); - } - foreach (string vp in Regex.Split(queryString, "&")) { - if (string.IsNullOrEmpty(vp)) { - continue; - } - string[] singlePair = Regex.Split(vp, "="); - if (parameters.ContainsKey(singlePair[0])) { - parameters.Remove(singlePair[0]); - } - parameters.Add(singlePair[0], singlePair.Length == 2 ? singlePair[1] : string.Empty); - } - return parameters; - } + return result.ToString(); + } - /// - /// Generate the query parameters - /// - /// the list of query parameters - /// a string with the query parameters - public static string GenerateQueryParameters(IDictionary queryParameters) { - if (queryParameters == null || queryParameters.Count == 0) { - return string.Empty; - } + return null; + } - queryParameters = new SortedDictionary(queryParameters); + /// + /// UrlDecodes a string without requiring System.Web + /// + /// String to decode. + /// decoded string + public static string UrlDecode(string text) + { + // pre-process for + sign space formatting since System.Uri doesn't handle it + // plus literals are encoded as %2b normally so this should be safe + text = text.Replace("+", " "); + return Uri.UnescapeDataString(text); + } - var sb = new StringBuilder(); - foreach(string key in queryParameters.Keys) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode($"{queryParameters[key]}")); - } - sb.Remove(sb.Length-1,1); + /// + /// ParseQueryString without the requirement for System.Web + /// + /// + /// IDictionary string, string + public static IDictionary ParseQueryString(string queryString) + { + IDictionary parameters = new SortedDictionary(); + // remove anything other than query string from uri + if (queryString.Contains("?")) + { + queryString = queryString.Substring(queryString.IndexOf('?') + 1); + } - return sb.ToString(); - } + foreach (string vp in Regex.Split(queryString, "&")) + { + if (string.IsNullOrEmpty(vp)) + { + continue; + } - /// - /// Write Multipart Form Data directly to the HttpWebRequest - /// - /// HttpWebRequest to write the multipart form data to - /// Parameters to include in the multipart form data - public static void WriteMultipartFormData(HttpWebRequest webRequest, IDictionary postParameters) { - string boundary = $"----------{Guid.NewGuid():N}"; - webRequest.ContentType = "multipart/form-data; boundary=" + boundary; + string[] singlePair = Regex.Split(vp, "="); + if (parameters.ContainsKey(singlePair[0])) + { + parameters.Remove(singlePair[0]); + } + + parameters.Add(singlePair[0], singlePair.Length == 2 ? singlePair[1] : string.Empty); + } + + return parameters; + } + + /// + /// Generate the query parameters + /// + /// the list of query parameters + /// a string with the query parameters + public static string GenerateQueryParameters(IDictionary queryParameters) + { + if (queryParameters == null || queryParameters.Count == 0) + { + return string.Empty; + } + + queryParameters = new SortedDictionary(queryParameters); + + var sb = new StringBuilder(); + foreach (string key in queryParameters.Keys) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode($"{queryParameters[key]}")); + } + + sb.Remove(sb.Length - 1, 1); + + return sb.ToString(); + } + + /// + /// Write Multipart Form Data directly to the HttpWebRequest + /// + /// HttpWebRequest to write the multipart form data to + /// Parameters to include in the multipart form data + public static void WriteMultipartFormData(HttpWebRequest webRequest, IDictionary postParameters) + { + string boundary = $"----------{Guid.NewGuid():N}"; + webRequest.ContentType = "multipart/form-data; boundary=" + boundary; using Stream formDataStream = webRequest.GetRequestStream(); WriteMultipartFormData(formDataStream, boundary, postParameters); } + /// + /// Write Multipart Form Data to a Stream, content-type should be set before this! + /// + /// Stream to write the multipart form data to + /// String boundary for the multipart/form-data + /// Parameters to include in the multipart form data + public static void WriteMultipartFormData(Stream formDataStream, string boundary, IDictionary postParameters) + { + bool needsClrf = false; + foreach (var param in postParameters) + { + // Add a CRLF to allow multiple parameters to be added. + // Skip it on the first parameter, add it to subsequent parameters. + if (needsClrf) + { + formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n")); + } + + needsClrf = true; + + if (param.Value is IBinaryContainer binaryContainer) + { + binaryContainer.WriteFormDataToStream(boundary, param.Key, formDataStream); + } + else + { + string postData = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{param.Key}\"\r\n\r\n{param.Value}"; + formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData)); + } + } + + // Add the end of the request. Start with a newline + string footer = "\r\n--" + boundary + "--\r\n"; + formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer)); + } /// - /// Write Multipart Form Data to a Stream, content-type should be set before this! - /// - /// Stream to write the multipart form data to - /// String boundary for the multipart/form-data - /// Parameters to include in the multipart form data - public static void WriteMultipartFormData(Stream formDataStream, string boundary, IDictionary postParameters) { - bool needsClrf = false; - foreach (var param in postParameters) { - // Add a CRLF to allow multiple parameters to be added. - // Skip it on the first parameter, add it to subsequent parameters. - if (needsClrf) { - formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n")); - } + /// Post content HttpWebRequest + /// + /// HttpWebRequest to write the multipart form data to + /// IDictionary with the headers + /// IBinaryContainer + public static void Post(HttpWebRequest webRequest, IDictionary headers, IBinaryContainer binaryContainer = null) + { + foreach (var header in headers) + { + switch (header.Key) + { + case "Content-Type": + webRequest.ContentType = header.Value as string; + break; + case "Accept": + webRequest.Accept = header.Value as string; + break; + default: + webRequest.Headers.Add(header.Key, Convert.ToString(header.Value)); + break; + } + } + if (!headers.ContainsKey("Content-Type")) + { + webRequest.ContentType = "application/octet-stream"; + } - needsClrf = true; + if (binaryContainer != null) + { + using var requestStream = webRequest.GetRequestStream(); + binaryContainer.WriteToStream(requestStream); + } + } - if (param.Value is IBinaryContainer binaryContainer) { - binaryContainer.WriteFormDataToStream(boundary, param.Key, formDataStream); - } else { - string postData = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{param.Key}\"\r\n\r\n{param.Value}"; - formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData)); - } - } + /// + /// Post content HttpWebRequest + /// + /// HttpWebRequest to write the multipart form data to + /// IDictionary with the headers + /// string + public static void Post(HttpWebRequest webRequest, IDictionary headers, string jsonString) + { + if (headers != null) + { + foreach (var header in headers) + { + switch (header.Key) + { + case "Content-Type": + webRequest.ContentType = header.Value as string; + break; + case "Accept": + webRequest.Accept = header.Value as string; + break; + default: + webRequest.Headers.Add(header.Key, Convert.ToString(header.Value)); + break; + } + } + if (!headers.ContainsKey("Content-Type")) + { + webRequest.ContentType = "application/json"; + } + } + else + { + webRequest.ContentType = "application/json"; + } - // Add the end of the request. Start with a newline - string footer = "\r\n--" + boundary + "--\r\n"; - formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer)); - } + if (jsonString != null) + { + using var requestStream = webRequest.GetRequestStream(); + using var streamWriter = new StreamWriter(requestStream); + streamWriter.Write(jsonString); + } + } - /// - /// Post the parameters "x-www-form-urlencoded" - /// - /// - /// - public static void UploadFormUrlEncoded(HttpWebRequest webRequest, IDictionary parameters) { - webRequest.ContentType = "application/x-www-form-urlencoded"; - string urlEncoded = GenerateQueryParameters(parameters); + /// + /// Post the parameters "x-www-form-urlencoded" + /// + /// + /// + public static void UploadFormUrlEncoded(HttpWebRequest webRequest, IDictionary parameters) + { + webRequest.ContentType = "application/x-www-form-urlencoded"; + string urlEncoded = GenerateQueryParameters(parameters); - byte[] data = Encoding.UTF8.GetBytes(urlEncoded); + byte[] data = Encoding.UTF8.GetBytes(urlEncoded); using var requestStream = webRequest.GetRequestStream(); requestStream.Write(data, 0, data.Length); } - /// - /// 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]); - } - } + /// + /// Log the headers of the WebResponse, if IsDebugEnabled + /// + /// WebResponse + private static void DebugHeaders(WebResponse response) + { + if (!Log.IsDebugEnabled) + { + return; + } - /// - /// Process the web response. - /// - /// The request object. - /// The response data. - /// TODO: This method should handle the StatusCode better! - public static string GetResponseAsString(HttpWebRequest webRequest) { - return GetResponseAsString(webRequest, false); - } + 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]); + } + } - /// - /// Read the response as string - /// - /// - /// string or null - private static string GetResponseAsString(HttpWebResponse response) - { - string responseData = null; - if (response == null) - { - return null; - } - using (response) - { - Stream responseStream = response.GetResponseStream(); - if (responseStream != null) + /// + /// Process the web response. + /// + /// The request object. + /// The response data. + /// TODO: This method should handle the StatusCode better! + public static string GetResponseAsString(HttpWebRequest webRequest) + { + return GetResponseAsString(webRequest, false); + } + + /// + /// Read the response as string + /// + /// + /// string or null + private static string GetResponseAsString(HttpWebResponse response) + { + string responseData = null; + if (response == null) + { + return null; + } + + using (response) + { + Stream responseStream = response.GetResponseStream(); + if (responseStream != null) { using StreamReader reader = new StreamReader(responseStream, true); responseData = reader.ReadToEnd(); } - } - return responseData; - } + } - /// - /// - /// - /// - /// - /// - public static string GetResponseAsString(HttpWebRequest webRequest, bool alsoReturnContentOnError) { - string responseData = null; - HttpWebResponse response = null; - bool isHttpError = false; - try { - response = (HttpWebResponse)webRequest.GetResponse(); - Log.InfoFormat("Response status: {0}", response.StatusCode); - isHttpError = (int)response.StatusCode >= 300; - if (isHttpError) - { - Log.ErrorFormat("HTTP error {0}", response.StatusCode); - } - DebugHeaders(response); - responseData = GetResponseAsString(response); - if (isHttpError) - { - Log.ErrorFormat("HTTP response {0}", responseData); - } - } - catch (WebException e) { - response = (HttpWebResponse) e.Response; - HttpStatusCode statusCode = HttpStatusCode.Unused; - if (response != null) { - statusCode = response.StatusCode; - Log.ErrorFormat("HTTP error {0}", statusCode); - string errorContent = GetResponseAsString(response); - if (alsoReturnContentOnError) - { - return errorContent; - } - Log.ErrorFormat("Content: {0}", errorContent); - } - Log.Error("WebException: ", e); - if (statusCode == HttpStatusCode.Unauthorized) - { - throw new UnauthorizedAccessException(e.Message); - } - throw; - } - finally - { - if (response != null) - { - if (isHttpError) - { - Log.ErrorFormat("HTTP error {0} with content: {1}", response.StatusCode, responseData); - } - response.Close(); - } - } - return responseData; - } + return responseData; + } + + /// + /// + /// + /// + /// + /// + public static string GetResponseAsString(HttpWebRequest webRequest, bool alsoReturnContentOnError) + { + string responseData = null; + HttpWebResponse response = null; + bool isHttpError = false; + try + { + response = (HttpWebResponse)webRequest.GetResponse(); + Log.InfoFormat("Response status: {0}", response.StatusCode); + isHttpError = (int)response.StatusCode >= 300; + if (isHttpError) + { + Log.ErrorFormat("HTTP error {0}", response.StatusCode); + } + + DebugHeaders(response); + responseData = GetResponseAsString(response); + if (isHttpError) + { + Log.ErrorFormat("HTTP response {0}", responseData); + } + } + catch (WebException e) + { + response = (HttpWebResponse)e.Response; + HttpStatusCode statusCode = HttpStatusCode.Unused; + if (response != null) + { + statusCode = response.StatusCode; + Log.ErrorFormat("HTTP error {0}", statusCode); + string errorContent = GetResponseAsString(response); + if (alsoReturnContentOnError) + { + return errorContent; + } + + Log.ErrorFormat("Content: {0}", errorContent); + } + + Log.Error("WebException: ", e); + if (statusCode == HttpStatusCode.Unauthorized) + { + throw new UnauthorizedAccessException(e.Message); + } + + throw; + } + finally + { + if (response != null) + { + if (isHttpError) + { + Log.ErrorFormat("HTTP error {0} with content: {1}", response.StatusCode, responseData); + } + + response.Close(); + } + } + + return responseData; + } + + } + /// + /// This interface can be used to pass binary information around, like byte[] or Image + /// + public interface IBinaryContainer + { + void WriteFormDataToStream(string boundary, string name, Stream formDataStream); + void WriteToStream(Stream formDataStream); + string ToBase64String(Base64FormattingOptions formattingOptions); + byte[] ToByteArray(); + void Upload(HttpWebRequest webRequest); + + string ContentType { get; } + string Filename { get; set; } } - /// - /// This interface can be used to pass binary information around, like byte[] or Image - /// - public interface IBinaryContainer { - void WriteFormDataToStream(string boundary, string name, Stream formDataStream); - void WriteToStream(Stream formDataStream); - string ToBase64String(Base64FormattingOptions formattingOptions); - byte[] ToByteArray(); - void Upload(HttpWebRequest webRequest); + /// A container to supply surfaces to a Multi-part form data upload + /// + public class SurfaceContainer : IBinaryContainer + { + private readonly ISurface _surface; + private readonly SurfaceOutputSettings _outputSettings; - string ContentType { get; } - string Filename { get; set; } - } + public SurfaceContainer(ISurface surface, SurfaceOutputSettings outputSettings, string filename) + { + _surface = surface; + _outputSettings = outputSettings; + Filename = filename; + } - /// - /// A container to supply surfaces to a Multi-part form data upload - /// - public class SurfaceContainer : IBinaryContainer { - private readonly ISurface _surface; - private readonly SurfaceOutputSettings _outputSettings; - - public SurfaceContainer(ISurface surface, SurfaceOutputSettings outputSettings, string filename) { - _surface = surface; - _outputSettings = outputSettings; - Filename = filename; - } - - /// - /// Create a Base64String from the Surface by saving it to a memory stream and converting it. - /// Should be avoided if possible, as this uses a lot of memory. - /// - /// string - public string ToBase64String(Base64FormattingOptions formattingOptions) + /// + /// Create a Base64String from the Surface by saving it to a memory stream and converting it. + /// Should be avoided if possible, as this uses a lot of memory. + /// + /// string + public string ToBase64String(Base64FormattingOptions formattingOptions) { using MemoryStream stream = new MemoryStream(); ImageOutput.SaveToStream(_surface, stream, _outputSettings); return Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length, formattingOptions); } - /// - /// Create a byte[] from the image by saving it to a memory stream. - /// Should be avoided if possible, as this uses a lot of memory. - /// - /// byte[] - public byte[] ToByteArray() + /// + /// Create a byte[] from the image by saving it to a memory stream. + /// Should be avoided if possible, as this uses a lot of memory. + /// + /// byte[] + public byte[] ToByteArray() { using MemoryStream stream = new MemoryStream(); ImageOutput.SaveToStream(_surface, stream, _outputSettings); return stream.ToArray(); } - /// - /// Write Multipart Form Data directly to the HttpWebRequest response stream - /// - /// Multipart separator - /// Name of the thing - /// Stream to write to - public void WriteFormDataToStream(string boundary, string name, Stream formDataStream) { - // Add just the first part of this param, since we will write the file data directly to the Stream - string header = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"; filename=\"{Filename ?? name}\";\r\nContent-Type: {ContentType}\r\n\r\n"; + /// + /// Write Multipart Form Data directly to the HttpWebRequest response stream + /// + /// Multipart separator + /// Name of the thing + /// Stream to write to + public void WriteFormDataToStream(string boundary, string name, Stream formDataStream) + { + // Add just the first part of this param, since we will write the file data directly to the Stream + string header = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"; filename=\"{Filename ?? name}\";\r\nContent-Type: {ContentType}\r\n\r\n"; - formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, Encoding.UTF8.GetByteCount(header)); - ImageOutput.SaveToStream(_surface, formDataStream, _outputSettings); - } + formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, Encoding.UTF8.GetByteCount(header)); + ImageOutput.SaveToStream(_surface, formDataStream, _outputSettings); + } - /// - /// A plain "write data to stream" - /// - /// - public void WriteToStream(Stream dataStream) { - // Write the file data directly to the Stream, rather than serializing it to a string. - ImageOutput.SaveToStream(_surface, dataStream, _outputSettings); - } + /// + /// A plain "write data to stream" + /// + /// + public void WriteToStream(Stream dataStream) + { + // Write the file data directly to the Stream, rather than serializing it to a string. + ImageOutput.SaveToStream(_surface, dataStream, _outputSettings); + } - /// - /// Upload the Surface as image to the webrequest - /// - /// - public void Upload(HttpWebRequest webRequest) { - webRequest.ContentType = ContentType; + /// + /// Upload the Surface as image to the webrequest + /// + /// + public void Upload(HttpWebRequest webRequest) + { + webRequest.ContentType = ContentType; using var requestStream = webRequest.GetRequestStream(); WriteToStream(requestStream); } - public string ContentType => "image/" + _outputSettings.Format; - public string Filename { get; set; } - } -} + public string ContentType => "image/" + _outputSettings.Format; + public string Filename { get; set; } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/LocalJsonReceiver.cs b/GreenshotPlugin/Core/OAuth/LocalJsonReceiver.cs new file mode 100644 index 000000000..7b363b2fa --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/LocalJsonReceiver.cs @@ -0,0 +1,191 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using log4net; +using Newtonsoft.Json; + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// 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 LocalJsonReceiver + { + private static readonly ILog Log = LogManager.GetLogger(typeof(LocalJsonReceiver)); + private readonly ManualResetEvent _ready = new ManualResetEvent(true); + private IDictionary _returnValues; + + /// + /// The url format for the website to post to. Expects one port parameter. + /// Default: http://localhost:{0}/authorize/ + /// + public string ListeningUrlFormat { get; set; } = "http://localhost:{0}/authorize/"; + + private string _listeningUri; + /// + /// The URL where the server is listening + /// + public string ListeningUri { + get { + if (string.IsNullOrEmpty(_listeningUri)) + { + _listeningUri = string.Format(ListeningUrlFormat, GetRandomUnusedPort()); + } + return _listeningUri; + } + set => _listeningUri = value; + } + + /// + /// This action is called when the URI must be opened, default is just to run Process.Start + /// + public Action OpenUriAction + { + set; + get; + } = authorizationUrl => + { + Log.DebugFormat("Open a browser with: {0}", authorizationUrl); + using var process = Process.Start(authorizationUrl); + }; + + /// + /// Timeout for waiting for the website to respond + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(4); + + /// + /// The OAuth code receiver + /// + /// OAuth2Settings + /// Dictionary with values + public IDictionary ReceiveCode(OAuth2Settings oauth2Settings) { + using var listener = new HttpListener(); + // Make sure the port is stored in the state, so the website can process this. + oauth2Settings.State = new Uri(ListeningUri).Port.ToString(); + listener.Prefixes.Add(ListeningUri); + try { + listener.Start(); + _ready.Reset(); + + listener.BeginGetContext(ListenerCallback, listener); + OpenUriAction(oauth2Settings.FormattedAuthUrl); + _ready.WaitOne(Timeout, true); + } catch (Exception) { + // Make sure we can clean up, also if the thead is aborted + _ready.Set(); + throw; + } finally { + listener.Close(); + } + + return _returnValues; + } + + /// + /// Handle a connection async, this allows us to break the waiting + /// + /// IAsyncResult + private void ListenerCallback(IAsyncResult result) { + HttpListener listener = (HttpListener)result.AsyncState; + + //If not listening return immediately as this method is called one last time after Close() + if (!listener.IsListening) { + return; + } + + // Use EndGetContext to complete the asynchronous operation. + HttpListenerContext context = listener.EndGetContext(result); + + // Handle request + HttpListenerRequest request = context.Request; + + if (request.HasEntityBody) + { + // Process the body + using var body = request.InputStream; + using var reader = new StreamReader(body, request.ContentEncoding); + using var jsonTextReader = new JsonTextReader(reader); + var serializer = new JsonSerializer(); + _returnValues = serializer.Deserialize>(jsonTextReader); + } + + // Create the response. + using (HttpListenerResponse response = context.Response) + { + if (request.HttpMethod == "OPTIONS") + { + response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); + response.AddHeader("Access-Control-Allow-Methods", "POST"); + response.AddHeader("Access-Control-Max-Age", "1728000"); + } + + response.AppendHeader("Access-Control-Allow-Origin", "*"); + if (request.HasEntityBody) + { + response.ContentType = "application/json"; + // currently only return the version, more can be added later + string jsonContent = "{\"version\": \"" + EnvironmentInfo.GetGreenshotVersion(true) + "\"}"; + + // Write a "close" response. + byte[] buffer = Encoding.UTF8.GetBytes(jsonContent); + // Write to response stream. + response.ContentLength64 = buffer.Length; + using var stream = response.OutputStream; + stream.Write(buffer, 0, buffer.Length); + } + } + + if (_returnValues != null) + { + _ready.Set(); + } + else + { + // Make sure the next request is processed + listener.BeginGetContext(ListenerCallback, listener); + } + } + + /// + /// 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(); + } + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/LocalServerCodeReceiver.cs b/GreenshotPlugin/Core/OAuth/LocalServerCodeReceiver.cs new file mode 100644 index 000000000..a4915e8b6 --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/LocalServerCodeReceiver.cs @@ -0,0 +1,183 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using log4net; + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// 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 readonly ManualResetEvent _ready = new ManualResetEvent(true); + + /// + /// The call back format. Expects one port parameter. + /// Default: http://localhost:{0}/authorize/ + /// + public string LoopbackCallbackUrl { get; set; } = "http://localhost:{0}/authorize/"; + + /// + /// HTML code to to return the _browser, default it will try to close the _browser / tab, this won't always work. + /// You can use CloudServiceName where you want to show the CloudServiceName from your OAuth2 settings + /// + public string ClosePageResponse { get; set; } = @" +OAuth 2.0 Authentication CloudServiceName + +Greenshot received information from CloudServiceName. You can close this browser / tab if it is not closed itself... + + +"; + + private string _redirectUri; + /// + /// The URL to redirect to + /// + protected string RedirectUri { + get { + if (!string.IsNullOrEmpty(_redirectUri)) { + return _redirectUri; + } + + return _redirectUri = string.Format(LoopbackCallbackUrl, GetRandomUnusedPort()); + } + } + + private string _cloudServiceName; + + private readonly IDictionary _returnValues = new Dictionary(); + + + /// + /// The OAuth code receiver + /// + /// + /// Dictionary with values + public IDictionary ReceiveCode(OAuth2Settings oauth2Settings) { + // Set the redirect URL on the settings + oauth2Settings.RedirectUrl = RedirectUri; + _cloudServiceName = oauth2Settings.CloudServiceName; + 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.BeginGetContext(ListenerCallback, listener); + _ready.Reset(); + + while (!context.AsyncWaitHandle.WaitOne(1000, true)) { + Log.Debug("Waiting for response"); + } + } catch (Exception) { + // Make sure we can clean up, also if the thead is aborted + _ready.Set(); + throw; + } finally { + _ready.WaitOne(); + listener.Close(); + } + } + return _returnValues; + } + + /// + /// Handle a connection async, this allows us to break the waiting + /// + /// IAsyncResult + private void ListenerCallback(IAsyncResult result) { + HttpListener listener = (HttpListener)result.AsyncState; + + //If not listening return immediately as this method is called one last time after Close() + if (!listener.IsListening) { + return; + } + + // Use EndGetContext to complete the asynchronous operation. + HttpListenerContext context = listener.EndGetContext(result); + + + // Handle request + HttpListenerRequest request = context.Request; + try { + NameValueCollection nameValueCollection = request.QueryString; + + // Get response object. + using (HttpListenerResponse response = context.Response) { + // Write a "close" response. + byte[] buffer = Encoding.UTF8.GetBytes(ClosePageResponse.Replace("CloudServiceName", _cloudServiceName)); + // Write to response stream. + response.ContentLength64 = buffer.Length; + using var stream = response.OutputStream; + stream.Write(buffer, 0, buffer.Length); + } + + // Create a new response URL with a dictionary that contains all the response query parameters. + foreach (var name in nameValueCollection.AllKeys) { + if (!_returnValues.ContainsKey(name)) { + _returnValues.Add(name, nameValueCollection[name]); + } + } + } catch (Exception) { + context.Response.OutputStream.Close(); + throw; + } + _ready.Set(); + } + + /// + /// 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(); + } + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/OAuth2AuthorizeMode.cs b/GreenshotPlugin/Core/OAuth/OAuth2AuthorizeMode.cs new file mode 100644 index 000000000..98d4515f9 --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/OAuth2AuthorizeMode.cs @@ -0,0 +1,33 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// Specify the authorize mode that is used to get the token from the cloud service. + /// + 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 + JsonReceiver, // Will start a local HttpListener and wait for a Json post + EmbeddedBrowser // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs b/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs new file mode 100644 index 000000000..b80d62b3d --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/OAuth2Helper.cs @@ -0,0 +1,372 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Net; +using GreenshotPlugin.Controls; + +namespace GreenshotPlugin.Core.OAuth { + /// + /// Code to simplify OAuth 2 + /// + public static class OAuth2Helper { + private const string RefreshToken = "refresh_token"; + private const string AccessToken = "access_token"; + private const string Code = "code"; + private const string Error = "error"; + private const string ClientId = "client_id"; + private const string ClientSecret = "client_secret"; + private const string GrantType = "grant_type"; + private const string AuthorizationCode = "authorization_code"; + private const string RedirectUri = "redirect_uri"; + private const string ExpiresIn = "expires_in"; + + /// + /// Generate an OAuth 2 Token by using the supplied code + /// + /// OAuth2Settings to update with the information that was retrieved + public static void GenerateRefreshToken(OAuth2Settings settings) { + IDictionary data = new Dictionary + { + // Use the returned code to get a refresh code + { Code, settings.Code }, + { ClientId, settings.ClientId }, + { ClientSecret, settings.ClientSecret }, + { GrantType, AuthorizationCode } + }; + foreach (string key in settings.AdditionalAttributes.Keys) { + data.Add(key, settings.AdditionalAttributes[key]); + } + + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.TokenUrl, HTTPMethod.POST); + NetworkHelper.UploadFormUrlEncoded(webRequest, data); + string accessTokenJsonResult = NetworkHelper.GetResponseAsString(webRequest, true); + + IDictionary refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); + if (refreshTokenResult.ContainsKey("error")) + { + if (refreshTokenResult.ContainsKey("error_description")) { + throw new Exception($"{refreshTokenResult["error"]} - {refreshTokenResult["error_description"]}"); + } + throw new Exception((string)refreshTokenResult["error"]); + } + + // 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" + if (refreshTokenResult.ContainsKey(AccessToken)) + { + settings.AccessToken = (string)refreshTokenResult[AccessToken]; + } + if (refreshTokenResult.ContainsKey(RefreshToken)) + { + settings.RefreshToken = (string)refreshTokenResult[RefreshToken]; + } + if (refreshTokenResult.ContainsKey(ExpiresIn)) + { + object seconds = refreshTokenResult[ExpiresIn]; + if (seconds != null) + { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); + } + } + settings.Code = null; + } + + /// + /// Used to update the settings with the callback information + /// + /// OAuth2Settings + /// IDictionary + /// true if the access token is already in the callback + private static bool UpdateFromCallback(OAuth2Settings settings, IDictionary callbackParameters) + { + if (!callbackParameters.ContainsKey(AccessToken)) + { + return false; + } + if (callbackParameters.ContainsKey(RefreshToken)) + { + // Refresh the refresh token :) + settings.RefreshToken = callbackParameters[RefreshToken]; + } + if (callbackParameters.ContainsKey(ExpiresIn)) + { + var expiresIn = callbackParameters[ExpiresIn]; + settings.AccessTokenExpires = DateTimeOffset.MaxValue; + if (expiresIn != null) + { + if (double.TryParse(expiresIn, out var seconds)) + { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds); + } + } + } + settings.AccessToken = callbackParameters[AccessToken]; + return true; + } + + /// + /// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings + /// Will update the access token, refresh token, expire date + /// + /// + public static void GenerateAccessToken(OAuth2Settings settings) { + IDictionary data = new Dictionary + { + { RefreshToken, settings.RefreshToken }, + { ClientId, settings.ClientId }, + { ClientSecret, settings.ClientSecret }, + { GrantType, RefreshToken } + }; + foreach (string key in settings.AdditionalAttributes.Keys) { + data.Add(key, settings.AdditionalAttributes[key]); + } + + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.TokenUrl, HTTPMethod.POST); + NetworkHelper.UploadFormUrlEncoded(webRequest, data); + string accessTokenJsonResult = NetworkHelper.GetResponseAsString(webRequest, true); + + // 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); + if (accessTokenResult.ContainsKey("error")) + { + if ("invalid_grant" == (string)accessTokenResult["error"]) { + // Refresh token has also expired, we need a new one! + settings.RefreshToken = null; + settings.AccessToken = null; + settings.AccessTokenExpires = DateTimeOffset.MinValue; + settings.Code = null; + return; + } + + if (accessTokenResult.ContainsKey("error_description")) { + throw new Exception($"{accessTokenResult["error"]} - {accessTokenResult["error_description"]}"); + } + + throw new Exception((string)accessTokenResult["error"]); + } + + if (accessTokenResult.ContainsKey(AccessToken)) + { + settings.AccessToken = (string) accessTokenResult[AccessToken]; + settings.AccessTokenExpires = DateTimeOffset.MaxValue; + } + if (accessTokenResult.ContainsKey(RefreshToken)) { + // Refresh the refresh token :) + settings.RefreshToken = (string)accessTokenResult[RefreshToken]; + } + if (accessTokenResult.ContainsKey(ExpiresIn)) + { + object seconds = accessTokenResult[ExpiresIn]; + if (seconds != null) + { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double) seconds); + } + } + } + + /// + /// Authorize by using the mode specified in the settings + /// + /// OAuth2Settings + /// false if it was canceled, true if it worked, exception if not + public static bool Authorize(OAuth2Settings settings) { + var completed = settings.AuthorizeMode switch + { + OAuth2AuthorizeMode.LocalServer => AuthorizeViaLocalServer(settings), + OAuth2AuthorizeMode.EmbeddedBrowser => AuthorizeViaEmbeddedBrowser(settings), + OAuth2AuthorizeMode.JsonReceiver => AuthorizeViaDefaultBrowser(settings), + _ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."), + }; + return completed; + } + + /// + /// Authorize via the default browser, via the Greenshot website. + /// It will wait for a Json post. + /// If this works, return the code + /// + /// OAuth2Settings with the Auth / Token url etc + /// true if completed, false if canceled + private static bool AuthorizeViaDefaultBrowser(OAuth2Settings settings) + { + var codeReceiver = new LocalJsonReceiver(); + IDictionary result = codeReceiver.ReceiveCode(settings); + + if (result == null || result.Count == 0) + { + return false; + } + foreach (var key in result.Keys) + { + switch (key) + { + case AccessToken: + settings.AccessToken = result[key]; + break; + case ExpiresIn: + if (int.TryParse(result[key], out var seconds)) + { + settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds); + } + break; + case RefreshToken: + settings.RefreshToken = result[key]; + break; + } + } + + if (result.TryGetValue("error", out var error)) + { + if (result.TryGetValue("error_description", out var errorDescription)) + { + throw new Exception(errorDescription); + } + if ("access_denied" == error) + { + throw new UnauthorizedAccessException("Access denied"); + } + throw new Exception(error); + } + if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) + { + settings.Code = code; + GenerateRefreshToken(settings); + return !string.IsNullOrEmpty(settings.AccessToken); + } + + return true; + } + + /// + /// Authorize via an embedded browser + /// If this works, return the code + /// + /// OAuth2Settings with the Auth / Token url etc + /// true if completed, false if canceled + private static bool AuthorizeViaEmbeddedBrowser(OAuth2Settings settings) { + if (string.IsNullOrEmpty(settings.CloudServiceName)) { + throw new ArgumentNullException(nameof(settings.CloudServiceName)); + } + if (settings.BrowserSize == Size.Empty) { + throw new ArgumentNullException(nameof(settings.BrowserSize)); + } + OAuthLoginForm loginForm = new OAuthLoginForm($"Authorize {settings.CloudServiceName}", settings.BrowserSize, settings.FormattedAuthUrl, settings.RedirectUrl); + loginForm.ShowDialog(); + if (!loginForm.IsOk) return false; + if (loginForm.CallbackParameters.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) { + settings.Code = code; + GenerateRefreshToken(settings); + return true; + } + return UpdateFromCallback(settings, loginForm.CallbackParameters); + } + + /// + /// Authorize via a local server by using the LocalServerCodeReceiver + /// If this works, return the code + /// + /// OAuth2Settings with the Auth / Token url etc + /// true if completed + private static bool AuthorizeViaLocalServer(OAuth2Settings settings) { + var codeReceiver = new LocalServerCodeReceiver(); + IDictionary result = codeReceiver.ReceiveCode(settings); + + if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) { + settings.Code = code; + GenerateRefreshToken(settings); + return true; + } + + if (result.TryGetValue("error", out var error)) { + if (result.TryGetValue("error_description", out var errorDescription)) { + throw new Exception(errorDescription); + } + if ("access_denied" == error) { + throw new UnauthorizedAccessException("Access denied"); + } + throw new Exception(error); + } + return false; + } + + /// + /// 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); + } + } + + /// + /// Check and authenticate or refresh tokens + /// + /// OAuth2Settings + public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) { + // Get Refresh / Access token + if (string.IsNullOrEmpty(settings.RefreshToken)) { + if (!Authorize(settings)) { + throw new Exception("Authentication cancelled"); + } + } + if (settings.IsAccessTokenExpired) { + GenerateAccessToken(settings); + // Get Refresh / Access token + if (string.IsNullOrEmpty(settings.RefreshToken)) { + if (!Authorize(settings)) { + throw new Exception("Authentication cancelled"); + } + GenerateAccessToken(settings); + } + } + if (settings.IsAccessTokenExpired) { + throw new Exception("Authentication failed"); + } + } + + /// + /// CreateWebRequest ready for OAuth 2 access + /// + /// HTTPMethod + /// + /// OAuth2Settings + /// HttpWebRequest + public static HttpWebRequest CreateOAuth2WebRequest(HTTPMethod method, string url, OAuth2Settings settings) { + CheckAndAuthenticateOrRefresh(settings); + + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, method); + AddOAuth2Credentials(webRequest, settings); + return webRequest; + } + } +} diff --git a/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs b/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs new file mode 100644 index 000000000..57b566aaa --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/OAuth2Settings.cs @@ -0,0 +1,181 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// Settings for the OAuth 2 protocol + /// + public class OAuth2Settings { + public OAuth2Settings() { + AdditionalAttributes = new Dictionary(); + // Create a default state + var state = Guid.NewGuid().ToString(); + // Only store a small part of the GUID + State = state.Substring(0, state.IndexOf('-')-1); + 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; + } + + /// + /// 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 authorization 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 => AuthUrlPattern.FormatWith(this); + + /// + /// The URL to get a Token + /// + public string TokenUrl { + get; + set; + } + + /// + /// 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 { + if (AccessTokenExpires == default) + { + return false; + } + bool expired = true; + if (!string.IsNullOrEmpty(AccessToken)) { + 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; + } + + /// + /// This contains the code returned from the authorization, but only shortly after it was received. + /// It will be cleared as soon as it was used. + /// + public string Code { + get; + set; + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/OAuthSession.cs b/GreenshotPlugin/Core/OAuth/OAuthSession.cs new file mode 100644 index 000000000..9a40c0b8d --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/OAuthSession.cs @@ -0,0 +1,629 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using GreenshotPlugin.Controls; +using log4net; + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// An OAuth 1 session object + /// + public class OAuthSession { + private static readonly ILog Log = LogManager.GetLogger(typeof(OAuthSession)); + protected const string OAUTH_VERSION = "1.0"; + protected const string OAUTH_PARAMETER_PREFIX = "oauth_"; + + // + // List of know and used oauth parameters' names + // + protected const string OAUTH_CONSUMER_KEY_KEY = "oauth_consumer_key"; + protected const string OAUTH_CALLBACK_KEY = "oauth_callback"; + protected const string OAUTH_VERSION_KEY = "oauth_version"; + protected const string OAUTH_SIGNATURE_METHOD_KEY = "oauth_signature_method"; + protected const string OAUTH_TIMESTAMP_KEY = "oauth_timestamp"; + protected const string OAUTH_NONCE_KEY = "oauth_nonce"; + protected const string OAUTH_TOKEN_KEY = "oauth_token"; + protected const string OAUTH_VERIFIER_KEY = "oauth_verifier"; + protected const string OAUTH_TOKEN_SECRET_KEY = "oauth_token_secret"; + protected const string OAUTH_SIGNATURE_KEY = "oauth_signature"; + + protected const string HMACSHA1SignatureType = "HMAC-SHA1"; + protected const string PlainTextSignatureType = "PLAINTEXT"; + + protected static Random random = new Random(); + + protected const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; + + private string _userAgent = "Greenshot"; + private IDictionary _requestTokenResponseParameters; + + public IDictionary RequestTokenParameters { get; } = new Dictionary(); + + /// + /// Parameters of the last called getAccessToken + /// + public IDictionary AccessTokenResponseParameters { get; private set; } + + /// + /// Parameters of the last called getRequestToken + /// + public IDictionary RequestTokenResponseParameters => _requestTokenResponseParameters; + + private readonly string _consumerKey; + private readonly string _consumerSecret; + + // default _browser size + + public HTTPMethod RequestTokenMethod { + get; + set; + } + public HTTPMethod AccessTokenMethod { + get; + set; + } + public string RequestTokenUrl { + get; + set; + } + public string AuthorizeUrl { + get; + set; + } + public string AccessTokenUrl { + get; + set; + } + public string Token { + get; + set; + } + public string TokenSecret { + get; + set; + } + public string Verifier { + get; + set; + } + public OAuthSignatureTypes SignatureType { + get; + set; + } + + public bool UseMultipartFormData { get; set; } + public string UserAgent { + get { + return _userAgent; + } + set { + _userAgent = value; + } + } + public string CallbackUrl { get; set; } = "http://getgreenshot.org"; + + public bool CheckVerifier { get; set; } = true; + + public Size BrowserSize { get; set; } = new Size(864, 587); + + public string LoginTitle { get; set; } = "Authorize Greenshot access"; + + public bool UseHttpHeadersForAuthorization { get; set; } = true; + + public bool AutoLogin { + get; + set; + } + + /// + /// Create an OAuthSession with the consumerKey / consumerSecret + /// + /// "Public" key for the encoding. When using RSASHA1 this is the path to the private key file + /// "Private" key for the encoding. when usin RSASHA1 this is the password for the private key file + public OAuthSession(string consumerKey, string consumerSecret) { + _consumerKey = consumerKey; + _consumerSecret = consumerSecret; + UseMultipartFormData = true; + RequestTokenMethod = HTTPMethod.GET; + AccessTokenMethod = HTTPMethod.GET; + SignatureType = OAuthSignatureTypes.HMACSHA1; + AutoLogin = true; + } + + /// + /// Helper function to compute a hash value + /// + /// The hashing algorithm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function + /// The data to hash + /// a Base64 string of the hash value + private static string ComputeHash(HashAlgorithm hashAlgorithm, string data) { + if (hashAlgorithm == null) { + throw new ArgumentNullException(nameof(hashAlgorithm)); + } + + if (string.IsNullOrEmpty(data)) { + throw new ArgumentNullException(nameof(data)); + } + + byte[] dataBuffer = Encoding.UTF8.GetBytes(data); + byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer); + + return Convert.ToBase64String(hashBytes); + } + + /// + /// Generate the normalized paramter string + /// + /// the list of query parameters + /// a string with the normalized query parameters + private static string GenerateNormalizedParametersString(IDictionary queryParameters) { + if (queryParameters == null || queryParameters.Count == 0) { + return string.Empty; + } + + queryParameters = new SortedDictionary(queryParameters); + + StringBuilder sb = new StringBuilder(); + foreach (string key in queryParameters.Keys) { + if (queryParameters[key] is string) { + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode3986($"{queryParameters[key]}")); + } + } + sb.Remove(sb.Length - 1, 1); + + return sb.ToString(); + } + + /// + /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case. + /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth + /// The resulting string is for UTF-8 encoding! + /// + /// The value to Url encode + /// Returns a Url encoded string (unicode) with UTF-8 encoded % values + public static string UrlEncode3986(string value) { + StringBuilder result = new StringBuilder(); + + foreach (char symbol in value) { + if (UnreservedChars.IndexOf(symbol) != -1) { + result.Append(symbol); + } else { + byte[] utf8Bytes = Encoding.UTF8.GetBytes(symbol.ToString()); + foreach(byte utf8Byte in utf8Bytes) { + result.AppendFormat("%{0:X2}", utf8Byte); + } + } + } + + return result.ToString(); + } + + /// + /// Generate the timestamp for the signature + /// + /// + public static string GenerateTimeStamp() { + // Default implementation of UNIX time of the current UTC time + TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + + /// + /// Generate a nonce + /// + /// + public static string GenerateNonce() { + // Just a simple implementation of a random number between 123400 and 9999999 + return random.Next(123400, 9999999).ToString(); + } + + /// + /// Get the request token using the consumer key and secret. Also initializes tokensecret + /// + /// response, this doesn't need to be used!! + private string GetRequestToken() { + IDictionary parameters = new Dictionary(); + foreach(var value in RequestTokenParameters) { + parameters.Add(value); + } + Sign(RequestTokenMethod, RequestTokenUrl, parameters); + string response = MakeRequest(RequestTokenMethod, RequestTokenUrl, null, parameters, null); + if (!string.IsNullOrEmpty(response)) { + response = NetworkHelper.UrlDecode(response); + Log.DebugFormat("Request token response: {0}", response); + _requestTokenResponseParameters = NetworkHelper.ParseQueryString(response); + if (_requestTokenResponseParameters.TryGetValue(OAUTH_TOKEN_KEY, out var value)) { + Token = value; + TokenSecret = _requestTokenResponseParameters[OAUTH_TOKEN_SECRET_KEY]; + } + } + return response; + } + + /// + /// Authorize the token by showing the dialog + /// + /// Pass the response from the server's request token, so if there is something wrong we can show it. + /// The request token. + private string GetAuthorizeToken(string requestTokenResponse) { + if (string.IsNullOrEmpty(Token)) { + Exception e = new Exception("The request token is not set, service responded with: " + requestTokenResponse); + throw e; + } + Log.DebugFormat("Opening AuthorizationLink: {0}", AuthorizationLink); + OAuthLoginForm oAuthLoginForm = new OAuthLoginForm(LoginTitle, BrowserSize, AuthorizationLink, CallbackUrl); + oAuthLoginForm.ShowDialog(); + if (oAuthLoginForm.IsOk) { + if (oAuthLoginForm.CallbackParameters != null) { + if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_TOKEN_KEY, out var tokenValue)) { + Token = tokenValue; + } + + if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_VERIFIER_KEY, out var verifierValue)) { + Verifier = verifierValue; + } + } + } + if (CheckVerifier) { + if (!string.IsNullOrEmpty(Verifier)) { + return Token; + } + return null; + } + return Token; + } + + /// + /// Get the access token + /// + /// The access token. + private string GetAccessToken() { + if (string.IsNullOrEmpty(Token) || (CheckVerifier && string.IsNullOrEmpty(Verifier))) { + Exception e = new Exception("The request token and verifier were not set"); + throw e; + } + + IDictionary parameters = new Dictionary(); + Sign(AccessTokenMethod, AccessTokenUrl, parameters); + string response = MakeRequest(AccessTokenMethod, AccessTokenUrl, null, parameters, null); + if (!string.IsNullOrEmpty(response)) { + response = NetworkHelper.UrlDecode(response); + Log.DebugFormat("Access token response: {0}", response); + AccessTokenResponseParameters = NetworkHelper.ParseQueryString(response); + if (AccessTokenResponseParameters.TryGetValue(OAUTH_TOKEN_KEY, out var tokenValue) && tokenValue != null) { + Token = tokenValue; + } + + if (AccessTokenResponseParameters.TryGetValue(OAUTH_TOKEN_SECRET_KEY, out var secretValue) && secretValue != null) { + TokenSecret = secretValue; + } + } + + return Token; + } + + /// + /// This method goes through the whole authorize process, including a Authorization window. + /// + /// true if the process is completed + public bool Authorize() { + Token = null; + TokenSecret = null; + Verifier = null; + Log.Debug("Creating Token"); + string requestTokenResponse; + try { + requestTokenResponse = GetRequestToken(); + } catch (Exception ex) { + Log.Error(ex); + throw new NotSupportedException("Service is not available: " + ex.Message); + } + if (string.IsNullOrEmpty(GetAuthorizeToken(requestTokenResponse))) { + Log.Debug("User didn't authenticate!"); + return false; + } + try { + Thread.Sleep(1000); + return GetAccessToken() != null; + } catch (Exception ex) { + Log.Error(ex); + throw; + } + } + + /// + /// Get the link to the authorization page for this application. + /// + /// The url with a valid request token, or a null string. + private string AuthorizationLink => AuthorizeUrl + "?" + OAUTH_TOKEN_KEY + "=" + Token + "&" + OAUTH_CALLBACK_KEY + "=" + UrlEncode3986(CallbackUrl); + + /// + /// Submit a web request using oAuth. + /// + /// GET or POST + /// The full url, including the querystring for the signing/request + /// Parameters for the request, which need to be signed + /// Parameters for the request, which do not need to be signed + /// Data to post (MemoryStream) + /// The web server response. + public string MakeOAuthRequest(HTTPMethod method, string requestUrl, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { + return MakeOAuthRequest(method, requestUrl, requestUrl, null, parametersToSign, additionalParameters, postData); + } + + /// + /// Submit a web request using oAuth. + /// + /// GET or POST + /// The full url, including the querystring for the signing/request + /// Header values + /// Parameters for the request, which need to be signed + /// Parameters for the request, which do not need to be signed + /// Data to post (MemoryStream) + /// The web server response. + public string MakeOAuthRequest(HTTPMethod method, string requestUrl, IDictionary headers, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { + return MakeOAuthRequest(method, requestUrl, requestUrl, headers, parametersToSign, additionalParameters, postData); + } + + /// + /// Submit a web request using oAuth. + /// + /// GET or POST + /// The full url, including the querystring for the signing + /// The full url, including the querystring for the request + /// Parameters for the request, which need to be signed + /// Parameters for the request, which do not need to be signed + /// Data to post (MemoryStream) + /// The web server response. + public string MakeOAuthRequest(HTTPMethod method, string signUrl, string requestUrl, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { + return MakeOAuthRequest(method, signUrl, requestUrl, null, parametersToSign, additionalParameters, postData); + } + + /// + /// Submit a web request using oAuth. + /// + /// GET or POST + /// The full url, including the querystring for the signing + /// The full url, including the querystring for the request + /// Headers for the request + /// Parameters for the request, which need to be signed + /// Parameters for the request, which do not need to be signed + /// Data to post (MemoryStream) + /// The web server response. + public string MakeOAuthRequest(HTTPMethod method, string signUrl, string requestUrl, IDictionary headers, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { + if (parametersToSign == null) { + parametersToSign = new Dictionary(); + } + int retries = 2; + Exception lastException = null; + while (retries-- > 0) { + // If we are not trying to get a Authorization or Accestoken, and we don't have a token, create one + if (string.IsNullOrEmpty(Token)) { + if (!AutoLogin || !Authorize()) { + throw new Exception("Not authorized"); + } + } + try { + Sign(method, signUrl, parametersToSign); + + // Join all parameters + IDictionary newParameters = new Dictionary(); + foreach (var parameter in parametersToSign) { + newParameters.Add(parameter); + } + if (additionalParameters != null) { + foreach (var parameter in additionalParameters) { + newParameters.Add(parameter); + } + } + return MakeRequest(method, requestUrl, headers, newParameters, postData); + } catch (UnauthorizedAccessException uaEx) { + lastException = uaEx; + Token = null; + TokenSecret = null; + // Remove oauth keys, so they aren't added double + List keysToDelete = new List(); + foreach (string parameterKey in parametersToSign.Keys) + { + if (parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) + { + keysToDelete.Add(parameterKey); + } + } + foreach (string keyToDelete in keysToDelete) + { + parametersToSign.Remove(keyToDelete); + } + } + } + if (lastException != null) { + throw lastException; + } + throw new Exception("Not authorized"); + } + + /// + /// OAuth sign the parameters, meaning all oauth parameters are added to the supplied dictionary. + /// And additionally a signature is added. + /// + /// Method (POST,PUT,GET) + /// Url to call + /// IDictionary of string and string + private void Sign(HTTPMethod method, string requestUrl, IDictionary parameters) { + if (parameters == null) { + throw new ArgumentNullException(nameof(parameters)); + } + // Build the signature base + StringBuilder signatureBase = new StringBuilder(); + + // Add Method to signature base + signatureBase.Append(method).Append("&"); + + // Add normalized URL + Uri url = new Uri(requestUrl); + string normalizedUrl = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", url.Scheme, url.Host); + if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) { + normalizedUrl += ":" + url.Port; + } + normalizedUrl += url.AbsolutePath; + signatureBase.Append(UrlEncode3986(normalizedUrl)).Append("&"); + + // Add normalized parameters + parameters.Add(OAUTH_VERSION_KEY, OAUTH_VERSION); + parameters.Add(OAUTH_NONCE_KEY, GenerateNonce()); + parameters.Add(OAUTH_TIMESTAMP_KEY, GenerateTimeStamp()); + switch(SignatureType) { + case OAuthSignatureTypes.PLAINTEXT: + parameters.Add(OAUTH_SIGNATURE_METHOD_KEY, PlainTextSignatureType); + break; + default: + parameters.Add(OAUTH_SIGNATURE_METHOD_KEY, HMACSHA1SignatureType); + break; + } + parameters.Add(OAUTH_CONSUMER_KEY_KEY, _consumerKey); + if (CallbackUrl != null && RequestTokenUrl != null && requestUrl.StartsWith(RequestTokenUrl)) { + parameters.Add(OAUTH_CALLBACK_KEY, CallbackUrl); + } + if (!string.IsNullOrEmpty(Verifier)) { + parameters.Add(OAUTH_VERIFIER_KEY, Verifier); + } + if (!string.IsNullOrEmpty(Token)) { + parameters.Add(OAUTH_TOKEN_KEY, Token); + } + signatureBase.Append(UrlEncode3986(GenerateNormalizedParametersString(parameters))); + Log.DebugFormat("Signature base: {0}", signatureBase); + string key = string.Format(CultureInfo.InvariantCulture, "{0}&{1}", UrlEncode3986(_consumerSecret), string.IsNullOrEmpty(TokenSecret) ? string.Empty : UrlEncode3986(TokenSecret)); + switch (SignatureType) { + case OAuthSignatureTypes.PLAINTEXT: + parameters.Add(OAUTH_SIGNATURE_KEY, key); + break; + default: + // Generate Signature and add it to the parameters + HMACSHA1 hmacsha1 = new HMACSHA1 {Key = Encoding.UTF8.GetBytes(key)}; + string signature = ComputeHash(hmacsha1, signatureBase.ToString()); + parameters.Add(OAUTH_SIGNATURE_KEY, signature); + break; + } + } + + /// + /// Make the actual OAuth request, all oauth parameters are passed as header (default) and the others are placed in the url or post data. + /// Any additional parameters added after the Sign call are not in the signature, this could be by design! + /// + /// + /// + /// + /// + /// IBinaryParameter + /// Response from server + private string MakeRequest(HTTPMethod method, string requestUrl, IDictionary headers, IDictionary parameters, IBinaryContainer postData) { + if (parameters == null) { + throw new ArgumentNullException(nameof(parameters)); + } + IDictionary requestParameters; + // Add oAuth values as HTTP headers, if this is allowed + StringBuilder authHeader = null; + if (UseHttpHeadersForAuthorization) { + authHeader = new StringBuilder(); + requestParameters = new Dictionary(); + foreach (string parameterKey in parameters.Keys) { + if (parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) { + authHeader.AppendFormat(CultureInfo.InvariantCulture, "{0}=\"{1}\", ", parameterKey, UrlEncode3986($"{parameters[parameterKey]}")); + } else if (!requestParameters.ContainsKey(parameterKey)) { + requestParameters.Add(parameterKey, parameters[parameterKey]); + } + } + // Remove trailing comma and space and add it to the headers + if (authHeader.Length > 0) { + authHeader.Remove(authHeader.Length - 2, 2); + } + } else { + requestParameters = parameters; + } + + if (HTTPMethod.GET == method || postData != null) { + if (requestParameters.Count > 0) { + // Add the parameters to the request + requestUrl += "?" + NetworkHelper.GenerateQueryParameters(requestParameters); + } + } + // Create webrequest + HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestUrl, method); + webRequest.ServicePoint.Expect100Continue = false; + webRequest.UserAgent = _userAgent; + + if (UseHttpHeadersForAuthorization && authHeader != null) { + Log.DebugFormat("Authorization: OAuth {0}", authHeader); + webRequest.Headers.Add("Authorization: OAuth " + authHeader); + } + + if (headers != null) { + foreach(string key in headers.Keys) { + webRequest.Headers.Add(key, headers[key]); + } + } + + if ((HTTPMethod.POST == method || HTTPMethod.PUT == method) && postData == null && requestParameters.Count > 0) { + if (UseMultipartFormData) { + NetworkHelper.WriteMultipartFormData(webRequest, requestParameters); + } else { + StringBuilder form = new StringBuilder(); + foreach (string parameterKey in requestParameters.Keys) + { + var binaryParameter = parameters[parameterKey] as IBinaryContainer; + form.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", UrlEncode3986(parameterKey), binaryParameter != null ? UrlEncode3986(binaryParameter.ToBase64String(Base64FormattingOptions.None)) : UrlEncode3986($"{parameters[parameterKey]}")); + } + // Remove trailing & + if (form.Length > 0) { + form.Remove(form.Length - 1, 1); + } + webRequest.ContentType = "application/x-www-form-urlencoded"; + byte[] data = Encoding.UTF8.GetBytes(form.ToString()); + using var requestStream = webRequest.GetRequestStream(); + requestStream.Write(data, 0, data.Length); + } + } else if (postData != null) { + postData.Upload(webRequest); + } else { + webRequest.ContentLength = 0; + } + + string responseData; + try { + responseData = NetworkHelper.GetResponseAsString(webRequest); + Log.DebugFormat("Response: {0}", responseData); + } catch (Exception ex) { + Log.Error("Couldn't retrieve response: ", ex); + throw; + } + + return responseData; + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuth/OAuthSignatureTypes.cs b/GreenshotPlugin/Core/OAuth/OAuthSignatureTypes.cs new file mode 100644 index 000000000..af2bfc710 --- /dev/null +++ b/GreenshotPlugin/Core/OAuth/OAuthSignatureTypes.cs @@ -0,0 +1,31 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace GreenshotPlugin.Core.OAuth +{ + /// + /// Provides a predefined set of algorithms that are supported officially by the OAuth 1.x protocol + /// + public enum OAuthSignatureTypes { + HMACSHA1, + PLAINTEXT, + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/OAuthHelper.cs b/GreenshotPlugin/Core/OAuthHelper.cs deleted file mode 100644 index 352934ef8..000000000 --- a/GreenshotPlugin/Core/OAuthHelper.cs +++ /dev/null @@ -1,1253 +0,0 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using 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.Net; -using System.Net.Sockets; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Windows.Forms; -using GreenshotPlugin.Hooking; - -namespace GreenshotPlugin.Core { - /// - /// Provides a predefined set of algorithms that are supported officially by the OAuth 1.x protocol - /// - public enum OAuthSignatureTypes { - HMACSHA1, - PLAINTEXT, - } - - /// - /// Specify the authorize mode that is used to get the token from the cloud service. - /// - 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 - EmbeddedBrowser, // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect - OutOfBoundAuto - } - - /// - /// Settings for the OAuth 2 protocol - /// - public class OAuth2Settings { - public OAuth2Settings() { - 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; - } - - /// - /// 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 => AuthUrlPattern.FormatWith(this); - - /// - /// The URL to get a Token - /// - public string TokenUrl { - get; - set; - } - - /// - /// 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)) { - 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; - } - - /// - /// This contains the code returned from the authorization, but only shortly after it was received. - /// It will be cleared as soon as it was used. - /// - public string Code { - 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"; - protected const string OAUTH_PARAMETER_PREFIX = "oauth_"; - - // - // List of know and used oauth parameters' names - // - protected const string OAUTH_CONSUMER_KEY_KEY = "oauth_consumer_key"; - protected const string OAUTH_CALLBACK_KEY = "oauth_callback"; - protected const string OAUTH_VERSION_KEY = "oauth_version"; - protected const string OAUTH_SIGNATURE_METHOD_KEY = "oauth_signature_method"; - protected const string OAUTH_TIMESTAMP_KEY = "oauth_timestamp"; - protected const string OAUTH_NONCE_KEY = "oauth_nonce"; - protected const string OAUTH_TOKEN_KEY = "oauth_token"; - protected const string OAUTH_VERIFIER_KEY = "oauth_verifier"; - protected const string OAUTH_TOKEN_SECRET_KEY = "oauth_token_secret"; - protected const string OAUTH_SIGNATURE_KEY = "oauth_signature"; - - protected const string HMACSHA1SignatureType = "HMAC-SHA1"; - protected const string PlainTextSignatureType = "PLAINTEXT"; - - protected static Random random = new Random(); - - protected const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; - - private string _userAgent = "Greenshot"; - private IDictionary _requestTokenResponseParameters; - - public IDictionary RequestTokenParameters { get; } = new Dictionary(); - - /// - /// Parameters of the last called getAccessToken - /// - public IDictionary AccessTokenResponseParameters { get; private set; } - - /// - /// Parameters of the last called getRequestToken - /// - public IDictionary RequestTokenResponseParameters => _requestTokenResponseParameters; - - private readonly string _consumerKey; - private readonly string _consumerSecret; - - // default _browser size - - public HTTPMethod RequestTokenMethod { - get; - set; - } - public HTTPMethod AccessTokenMethod { - get; - set; - } - public string RequestTokenUrl { - get; - set; - } - public string AuthorizeUrl { - get; - set; - } - public string AccessTokenUrl { - get; - set; - } - public string Token { - get; - set; - } - public string TokenSecret { - get; - set; - } - public string Verifier { - get; - set; - } - public OAuthSignatureTypes SignatureType { - get; - set; - } - - public bool UseMultipartFormData { get; set; } - public string UserAgent { - get { - return _userAgent; - } - set { - _userAgent = value; - } - } - public string CallbackUrl { get; set; } = "http://getgreenshot.org"; - - public bool CheckVerifier { get; set; } = true; - - public Size BrowserSize { get; set; } = new Size(864, 587); - - public string LoginTitle { get; set; } = "Authorize Greenshot access"; - - public bool UseHttpHeadersForAuthorization { get; set; } = true; - - public bool AutoLogin { - get; - set; - } - - /// - /// Create an OAuthSession with the consumerKey / consumerSecret - /// - /// "Public" key for the encoding. When using RSASHA1 this is the path to the private key file - /// "Private" key for the encoding. when usin RSASHA1 this is the password for the private key file - public OAuthSession(string consumerKey, string consumerSecret) { - _consumerKey = consumerKey; - _consumerSecret = consumerSecret; - UseMultipartFormData = true; - RequestTokenMethod = HTTPMethod.GET; - AccessTokenMethod = HTTPMethod.GET; - SignatureType = OAuthSignatureTypes.HMACSHA1; - AutoLogin = true; - } - - /// - /// Helper function to compute a hash value - /// - /// The hashing algorithm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function - /// The data to hash - /// a Base64 string of the hash value - private static string ComputeHash(HashAlgorithm hashAlgorithm, string data) { - if (hashAlgorithm == null) { - throw new ArgumentNullException(nameof(hashAlgorithm)); - } - - if (string.IsNullOrEmpty(data)) { - throw new ArgumentNullException(nameof(data)); - } - - byte[] dataBuffer = Encoding.UTF8.GetBytes(data); - byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer); - - return Convert.ToBase64String(hashBytes); - } - - /// - /// Generate the normalized paramter string - /// - /// the list of query parameters - /// a string with the normalized query parameters - private static string GenerateNormalizedParametersString(IDictionary queryParameters) { - if (queryParameters == null || queryParameters.Count == 0) { - return string.Empty; - } - - queryParameters = new SortedDictionary(queryParameters); - - StringBuilder sb = new StringBuilder(); - foreach (string key in queryParameters.Keys) { - if (queryParameters[key] is string) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode3986($"{queryParameters[key]}")); - } - } - sb.Remove(sb.Length - 1, 1); - - return sb.ToString(); - } - - /// - /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case. - /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth - /// The resulting string is for UTF-8 encoding! - /// - /// The value to Url encode - /// Returns a Url encoded string (unicode) with UTF-8 encoded % values - public static string UrlEncode3986(string value) { - StringBuilder result = new StringBuilder(); - - foreach (char symbol in value) { - if (UnreservedChars.IndexOf(symbol) != -1) { - result.Append(symbol); - } else { - byte[] utf8Bytes = Encoding.UTF8.GetBytes(symbol.ToString()); - foreach(byte utf8Byte in utf8Bytes) { - result.AppendFormat("%{0:X2}", utf8Byte); - } - } - } - - return result.ToString(); - } - - /// - /// Generate the timestamp for the signature - /// - /// - public static string GenerateTimeStamp() { - // Default implementation of UNIX time of the current UTC time - TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); - return Convert.ToInt64(ts.TotalSeconds).ToString(); - } - - /// - /// Generate a nonce - /// - /// - public static string GenerateNonce() { - // Just a simple implementation of a random number between 123400 and 9999999 - return random.Next(123400, 9999999).ToString(); - } - - /// - /// Get the request token using the consumer key and secret. Also initializes tokensecret - /// - /// response, this doesn't need to be used!! - private string GetRequestToken() { - IDictionary parameters = new Dictionary(); - foreach(var value in RequestTokenParameters) { - parameters.Add(value); - } - Sign(RequestTokenMethod, RequestTokenUrl, parameters); - string response = MakeRequest(RequestTokenMethod, RequestTokenUrl, null, parameters, null); - if (!string.IsNullOrEmpty(response)) { - response = NetworkHelper.UrlDecode(response); - Log.DebugFormat("Request token response: {0}", response); - _requestTokenResponseParameters = NetworkHelper.ParseQueryString(response); - if (_requestTokenResponseParameters.TryGetValue(OAUTH_TOKEN_KEY, out var value)) { - Token = value; - TokenSecret = _requestTokenResponseParameters[OAUTH_TOKEN_SECRET_KEY]; - } - } - return response; - } - - /// - /// Authorize the token by showing the dialog - /// - /// Pass the response from the server's request token, so if there is something wrong we can show it. - /// The request token. - private string GetAuthorizeToken(string requestTokenResponse) { - if (string.IsNullOrEmpty(Token)) { - Exception e = new Exception("The request token is not set, service responded with: " + requestTokenResponse); - throw e; - } - Log.DebugFormat("Opening AuthorizationLink: {0}", AuthorizationLink); - OAuthLoginForm oAuthLoginForm = new OAuthLoginForm(LoginTitle, BrowserSize, AuthorizationLink, CallbackUrl); - oAuthLoginForm.ShowDialog(); - if (oAuthLoginForm.IsOk) { - if (oAuthLoginForm.CallbackParameters != null) { - if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_TOKEN_KEY, out var tokenValue)) { - Token = tokenValue; - } - - if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_VERIFIER_KEY, out var verifierValue)) { - Verifier = verifierValue; - } - } - } - if (CheckVerifier) { - if (!string.IsNullOrEmpty(Verifier)) { - return Token; - } - return null; - } - return Token; - } - - /// - /// Get the access token - /// - /// The access token. - private string GetAccessToken() { - if (string.IsNullOrEmpty(Token) || (CheckVerifier && string.IsNullOrEmpty(Verifier))) { - Exception e = new Exception("The request token and verifier were not set"); - throw e; - } - - IDictionary parameters = new Dictionary(); - Sign(AccessTokenMethod, AccessTokenUrl, parameters); - string response = MakeRequest(AccessTokenMethod, AccessTokenUrl, null, parameters, null); - if (!string.IsNullOrEmpty(response)) { - response = NetworkHelper.UrlDecode(response); - Log.DebugFormat("Access token response: {0}", response); - AccessTokenResponseParameters = NetworkHelper.ParseQueryString(response); - if (AccessTokenResponseParameters.TryGetValue(OAUTH_TOKEN_KEY, out var tokenValue) && tokenValue != null) { - Token = tokenValue; - } - - if (AccessTokenResponseParameters.TryGetValue(OAUTH_TOKEN_SECRET_KEY, out var secretValue) && secretValue != null) { - TokenSecret = secretValue; - } - } - - return Token; - } - - /// - /// This method goes through the whole authorize process, including a Authorization window. - /// - /// true if the process is completed - public bool Authorize() { - Token = null; - TokenSecret = null; - Verifier = null; - Log.Debug("Creating Token"); - string requestTokenResponse; - try { - requestTokenResponse = GetRequestToken(); - } catch (Exception ex) { - Log.Error(ex); - throw new NotSupportedException("Service is not available: " + ex.Message); - } - if (string.IsNullOrEmpty(GetAuthorizeToken(requestTokenResponse))) { - Log.Debug("User didn't authenticate!"); - return false; - } - try { - Thread.Sleep(1000); - return GetAccessToken() != null; - } catch (Exception ex) { - Log.Error(ex); - throw; - } - } - - /// - /// Get the link to the authorization page for this application. - /// - /// The url with a valid request token, or a null string. - private string AuthorizationLink => AuthorizeUrl + "?" + OAUTH_TOKEN_KEY + "=" + Token + "&" + OAUTH_CALLBACK_KEY + "=" + UrlEncode3986(CallbackUrl); - - /// - /// Submit a web request using oAuth. - /// - /// GET or POST - /// The full url, including the querystring for the signing/request - /// Parameters for the request, which need to be signed - /// Parameters for the request, which do not need to be signed - /// Data to post (MemoryStream) - /// The web server response. - public string MakeOAuthRequest(HTTPMethod method, string requestUrl, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { - return MakeOAuthRequest(method, requestUrl, requestUrl, null, parametersToSign, additionalParameters, postData); - } - - /// - /// Submit a web request using oAuth. - /// - /// GET or POST - /// The full url, including the querystring for the signing - /// The full url, including the querystring for the request - /// Parameters for the request, which need to be signed - /// Parameters for the request, which do not need to be signed - /// Data to post (MemoryStream) - /// The web server response. - public string MakeOAuthRequest(HTTPMethod method, string signUrl, string requestUrl, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { - return MakeOAuthRequest(method, signUrl, requestUrl, null, parametersToSign, additionalParameters, postData); - } - - /// - /// Submit a web request using oAuth. - /// - /// GET or POST - /// The full url, including the querystring for the signing - /// The full url, including the querystring for the request - /// Headers for the request - /// Parameters for the request, which need to be signed - /// Parameters for the request, which do not need to be signed - /// Data to post (MemoryStream) - /// The web server response. - public string MakeOAuthRequest(HTTPMethod method, string signUrl, string requestUrl, IDictionary headers, IDictionary parametersToSign, IDictionary additionalParameters, IBinaryContainer postData) { - if (parametersToSign == null) { - parametersToSign = new Dictionary(); - } - int retries = 2; - Exception lastException = null; - while (retries-- > 0) { - // If we are not trying to get a Authorization or Accestoken, and we don't have a token, create one - if (string.IsNullOrEmpty(Token)) { - if (!AutoLogin || !Authorize()) { - throw new Exception("Not authorized"); - } - } - try { - Sign(method, signUrl, parametersToSign); - - // Join all parameters - IDictionary newParameters = new Dictionary(); - foreach (var parameter in parametersToSign) { - newParameters.Add(parameter); - } - if (additionalParameters != null) { - foreach (var parameter in additionalParameters) { - newParameters.Add(parameter); - } - } - return MakeRequest(method, requestUrl, headers, newParameters, postData); - } catch (UnauthorizedAccessException uaEx) { - lastException = uaEx; - Token = null; - TokenSecret = null; - // Remove oauth keys, so they aren't added double - List keysToDelete = new List(); - foreach (string parameterKey in parametersToSign.Keys) - { - if (parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) - { - keysToDelete.Add(parameterKey); - } - } - foreach (string keyToDelete in keysToDelete) - { - parametersToSign.Remove(keyToDelete); - } - } - } - if (lastException != null) { - throw lastException; - } - throw new Exception("Not authorized"); - } - - /// - /// OAuth sign the parameters, meaning all oauth parameters are added to the supplied dictionary. - /// And additionally a signature is added. - /// - /// Method (POST,PUT,GET) - /// Url to call - /// IDictionary of string and string - private void Sign(HTTPMethod method, string requestUrl, IDictionary parameters) { - if (parameters == null) { - throw new ArgumentNullException(nameof(parameters)); - } - // Build the signature base - StringBuilder signatureBase = new StringBuilder(); - - // Add Method to signature base - signatureBase.Append(method).Append("&"); - - // Add normalized URL - Uri url = new Uri(requestUrl); - string normalizedUrl = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", url.Scheme, url.Host); - if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) { - normalizedUrl += ":" + url.Port; - } - normalizedUrl += url.AbsolutePath; - signatureBase.Append(UrlEncode3986(normalizedUrl)).Append("&"); - - // Add normalized parameters - parameters.Add(OAUTH_VERSION_KEY, OAUTH_VERSION); - parameters.Add(OAUTH_NONCE_KEY, GenerateNonce()); - parameters.Add(OAUTH_TIMESTAMP_KEY, GenerateTimeStamp()); - switch(SignatureType) { - case OAuthSignatureTypes.PLAINTEXT: - parameters.Add(OAUTH_SIGNATURE_METHOD_KEY, PlainTextSignatureType); - break; - default: - parameters.Add(OAUTH_SIGNATURE_METHOD_KEY, HMACSHA1SignatureType); - break; - } - parameters.Add(OAUTH_CONSUMER_KEY_KEY, _consumerKey); - if (CallbackUrl != null && RequestTokenUrl != null && requestUrl.StartsWith(RequestTokenUrl)) { - parameters.Add(OAUTH_CALLBACK_KEY, CallbackUrl); - } - if (!string.IsNullOrEmpty(Verifier)) { - parameters.Add(OAUTH_VERIFIER_KEY, Verifier); - } - if (!string.IsNullOrEmpty(Token)) { - parameters.Add(OAUTH_TOKEN_KEY, Token); - } - signatureBase.Append(UrlEncode3986(GenerateNormalizedParametersString(parameters))); - Log.DebugFormat("Signature base: {0}", signatureBase); - string key = string.Format(CultureInfo.InvariantCulture, "{0}&{1}", UrlEncode3986(_consumerSecret), string.IsNullOrEmpty(TokenSecret) ? string.Empty : UrlEncode3986(TokenSecret)); - switch (SignatureType) { - case OAuthSignatureTypes.PLAINTEXT: - parameters.Add(OAUTH_SIGNATURE_KEY, key); - break; - default: - // Generate Signature and add it to the parameters - HMACSHA1 hmacsha1 = new HMACSHA1 {Key = Encoding.UTF8.GetBytes(key)}; - string signature = ComputeHash(hmacsha1, signatureBase.ToString()); - parameters.Add(OAUTH_SIGNATURE_KEY, signature); - break; - } - } - - /// - /// Make the actual OAuth request, all oauth parameters are passed as header (default) and the others are placed in the url or post data. - /// Any additional parameters added after the Sign call are not in the signature, this could be by design! - /// - /// - /// - /// - /// - /// IBinaryParameter - /// Response from server - private string MakeRequest(HTTPMethod method, string requestUrl, IDictionary headers, IDictionary parameters, IBinaryContainer postData) { - if (parameters == null) { - throw new ArgumentNullException(nameof(parameters)); - } - IDictionary requestParameters; - // Add oAuth values as HTTP headers, if this is allowed - StringBuilder authHeader = null; - if (UseHttpHeadersForAuthorization) { - authHeader = new StringBuilder(); - requestParameters = new Dictionary(); - foreach (string parameterKey in parameters.Keys) { - if (parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) { - authHeader.AppendFormat(CultureInfo.InvariantCulture, "{0}=\"{1}\", ", parameterKey, UrlEncode3986($"{parameters[parameterKey]}")); - } else if (!requestParameters.ContainsKey(parameterKey)) { - requestParameters.Add(parameterKey, parameters[parameterKey]); - } - } - // Remove trailing comma and space and add it to the headers - if (authHeader.Length > 0) { - authHeader.Remove(authHeader.Length - 2, 2); - } - } else { - requestParameters = parameters; - } - - if (HTTPMethod.GET == method || postData != null) { - if (requestParameters.Count > 0) { - // Add the parameters to the request - requestUrl += "?" + NetworkHelper.GenerateQueryParameters(requestParameters); - } - } - // Create webrequest - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestUrl, method); - webRequest.ServicePoint.Expect100Continue = false; - webRequest.UserAgent = _userAgent; - - if (UseHttpHeadersForAuthorization && authHeader != null) { - Log.DebugFormat("Authorization: OAuth {0}", authHeader); - webRequest.Headers.Add("Authorization: OAuth " + authHeader); - } - - if (headers != null) { - foreach(string key in headers.Keys) { - webRequest.Headers.Add(key, headers[key]); - } - } - - if ((HTTPMethod.POST == method || HTTPMethod.PUT == method) && postData == null && requestParameters.Count > 0) { - if (UseMultipartFormData) { - NetworkHelper.WriteMultipartFormData(webRequest, requestParameters); - } else { - StringBuilder form = new StringBuilder(); - foreach (string parameterKey in requestParameters.Keys) - { - var binaryParameter = parameters[parameterKey] as IBinaryContainer; - form.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}&", UrlEncode3986(parameterKey), binaryParameter != null ? UrlEncode3986(binaryParameter.ToBase64String(Base64FormattingOptions.None)) : UrlEncode3986($"{parameters[parameterKey]}")); - } - // Remove trailing & - if (form.Length > 0) { - form.Remove(form.Length - 1, 1); - } - webRequest.ContentType = "application/x-www-form-urlencoded"; - byte[] data = Encoding.UTF8.GetBytes(form.ToString()); - using var requestStream = webRequest.GetRequestStream(); - requestStream.Write(data, 0, data.Length); - } - } else if (postData != null) { - postData.Upload(webRequest); - } else { - webRequest.ContentLength = 0; - } - - string responseData; - try { - responseData = NetworkHelper.GetResponseAsString(webRequest); - Log.DebugFormat("Response: {0}", responseData); - } catch (Exception ex) { - Log.Error("Couldn't retrieve response: ", ex); - throw; - } - - 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 readonly ManualResetEvent _ready = new ManualResetEvent(true); - - /// - /// The call back format. Expects one port parameter. - /// Default: http://localhost:{0}/authorize/ - /// - public string LoopbackCallbackUrl { get; set; } = "http://localhost:{0}/authorize/"; - - /// - /// HTML code to to return the _browser, default it will try to close the _browser / tab, this won't always work. - /// You can use CloudServiceName where you want to show the CloudServiceName from your OAuth2 settings - /// - public string ClosePageResponse { get; set; } = @" -OAuth 2.0 Authentication CloudServiceName - -Greenshot received information from CloudServiceName. You can close this browser / tab if it is not closed itself... - - -"; - - private string _redirectUri; - /// - /// The URL to redirect to - /// - protected string RedirectUri { - get { - if (!string.IsNullOrEmpty(_redirectUri)) { - return _redirectUri; - } - - return _redirectUri = string.Format(LoopbackCallbackUrl, GetRandomUnusedPort()); - } - } - - private string _cloudServiceName; - - private readonly IDictionary _returnValues = new Dictionary(); - - - /// - /// The OAuth code receiver - /// - /// - /// Dictionary with values - public IDictionary ReceiveCode(OAuth2Settings oauth2Settings) { - // Set the redirect URL on the settings - oauth2Settings.RedirectUrl = RedirectUri; - _cloudServiceName = oauth2Settings.CloudServiceName; - 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.BeginGetContext(ListenerCallback, listener); - _ready.Reset(); - - while (!context.AsyncWaitHandle.WaitOne(1000, true)) { - Log.Debug("Waiting for response"); - } - } catch (Exception) { - // Make sure we can clean up, also if the thead is aborted - _ready.Set(); - throw; - } finally { - _ready.WaitOne(); - listener.Close(); - } - } - return _returnValues; - } - - /// - /// Handle a connection async, this allows us to break the waiting - /// - /// IAsyncResult - private void ListenerCallback(IAsyncResult result) { - HttpListener listener = (HttpListener)result.AsyncState; - - //If not listening return immediately as this method is called one last time after Close() - if (!listener.IsListening) { - return; - } - - // Use EndGetContext to complete the asynchronous operation. - HttpListenerContext context = listener.EndGetContext(result); - - - // Handle request - HttpListenerRequest request = context.Request; - try { - NameValueCollection nameValueCollection = request.QueryString; - - // Get response object. - using (HttpListenerResponse response = context.Response) { - // Write a "close" response. - byte[] buffer = Encoding.UTF8.GetBytes(ClosePageResponse.Replace("CloudServiceName", _cloudServiceName)); - // Write to response stream. - response.ContentLength64 = buffer.Length; - using var stream = response.OutputStream; - stream.Write(buffer, 0, buffer.Length); - } - - // Create a new response URL with a dictionary that contains all the response query parameters. - foreach (var name in nameValueCollection.AllKeys) { - if (!_returnValues.ContainsKey(name)) { - _returnValues.Add(name, nameValueCollection[name]); - } - } - } catch (Exception) { - context.Response.OutputStream.Close(); - throw; - } - _ready.Set(); - } - - /// - /// 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(); - } - } - } - - /// - /// Code to simplify OAuth 2 - /// - public static class OAuth2Helper { - private const string RefreshToken = "refresh_token"; - private const string AccessToken = "access_token"; - private const string Code = "code"; - private const string ClientId = "client_id"; - private const string ClientSecret = "client_secret"; - private const string GrantType = "grant_type"; - private const string AuthorizationCode = "authorization_code"; - private const string RedirectUri = "redirect_uri"; - private const string ExpiresIn = "expires_in"; - - /// - /// Generate an OAuth 2 Token by using the supplied code - /// - /// OAuth2Settings to update with the information that was retrieved - public static void GenerateRefreshToken(OAuth2Settings settings) { - IDictionary data = new Dictionary - { - // Use the returned code to get a refresh code - { Code, settings.Code }, - { ClientId, settings.ClientId }, - { RedirectUri, settings.RedirectUrl }, - { ClientSecret, settings.ClientSecret }, - { GrantType, AuthorizationCode } - }; - foreach (string key in settings.AdditionalAttributes.Keys) { - data.Add(key, settings.AdditionalAttributes[key]); - } - - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.TokenUrl, HTTPMethod.POST); - NetworkHelper.UploadFormUrlEncoded(webRequest, data); - string accessTokenJsonResult = NetworkHelper.GetResponseAsString(webRequest, true); - - IDictionary refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult); - if (refreshTokenResult.ContainsKey("error")) - { - if (refreshTokenResult.ContainsKey("error_description")) { - throw new Exception($"{refreshTokenResult["error"]} - {refreshTokenResult["error_description"]}"); - } - throw new Exception((string)refreshTokenResult["error"]); - } - - // 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" - if (refreshTokenResult.ContainsKey(AccessToken)) - { - settings.AccessToken = (string)refreshTokenResult[AccessToken]; - } - if (refreshTokenResult.ContainsKey(RefreshToken)) - { - settings.RefreshToken = (string)refreshTokenResult[RefreshToken]; - } - if (refreshTokenResult.ContainsKey(ExpiresIn)) - { - object seconds = refreshTokenResult[ExpiresIn]; - if (seconds != null) - { - settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); - } - } - settings.Code = null; - } - - /// - /// Used to update the settings with the callback information - /// - /// OAuth2Settings - /// IDictionary - /// true if the access token is already in the callback - private static bool UpdateFromCallback(OAuth2Settings settings, IDictionary callbackParameters) - { - if (!callbackParameters.ContainsKey(AccessToken)) - { - return false; - } - if (callbackParameters.ContainsKey(RefreshToken)) - { - // Refresh the refresh token :) - settings.RefreshToken = callbackParameters[RefreshToken]; - } - if (callbackParameters.ContainsKey(ExpiresIn)) - { - var expiresIn = callbackParameters[ExpiresIn]; - settings.AccessTokenExpires = DateTimeOffset.MaxValue; - if (expiresIn != null) - { - if (double.TryParse(expiresIn, out var seconds)) - { - settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds); - } - } - } - settings.AccessToken = callbackParameters[AccessToken]; - return true; - } - - /// - /// 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 - { - { RefreshToken, settings.RefreshToken }, - { ClientId, settings.ClientId }, - { ClientSecret, settings.ClientSecret }, - { GrantType, RefreshToken } - }; - foreach (string key in settings.AdditionalAttributes.Keys) { - data.Add(key, settings.AdditionalAttributes[key]); - } - - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.TokenUrl, HTTPMethod.POST); - NetworkHelper.UploadFormUrlEncoded(webRequest, data); - string accessTokenJsonResult = NetworkHelper.GetResponseAsString(webRequest, true); - - // 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); - if (accessTokenResult.ContainsKey("error")) { - if ("invalid_grant" == (string)accessTokenResult["error"]) { - // Refresh token has also expired, we need a new one! - settings.RefreshToken = null; - settings.AccessToken = null; - settings.AccessTokenExpires = DateTimeOffset.MinValue; - settings.Code = null; - return; - } else { - if (accessTokenResult.ContainsKey("error_description")) { - throw new Exception($"{accessTokenResult["error"]} - {accessTokenResult["error_description"]}"); - } else { - throw new Exception((string)accessTokenResult["error"]); - } - } - } - - if (accessTokenResult.ContainsKey(AccessToken)) - { - settings.AccessToken = (string) accessTokenResult[AccessToken]; - settings.AccessTokenExpires = DateTimeOffset.MaxValue; - } - if (accessTokenResult.ContainsKey(RefreshToken)) { - // Refresh the refresh token :) - settings.RefreshToken = (string)accessTokenResult[RefreshToken]; - } - if (accessTokenResult.ContainsKey(ExpiresIn)) - { - object seconds = accessTokenResult[ExpiresIn]; - if (seconds != null) - { - settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double) seconds); - } - } - } - - /// - /// Authenticate by using the mode specified in the settings - /// - /// OAuth2Settings - /// false if it was canceled, true if it worked, exception if not - public static bool Authenticate(OAuth2Settings settings) { - var completed = settings.AuthorizeMode switch - { - OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings), - OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings), - OAuth2AuthorizeMode.OutOfBoundAuto => AuthenticateViaDefaultBrowser(settings), - _ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."), - }; - return completed; - } - - /// - /// Authenticate via the default browser - /// If this works, return the code - /// - /// OAuth2Settings with the Auth / Token url etc - /// true if completed, false if canceled - private static bool AuthenticateViaDefaultBrowser(OAuth2Settings settings) - { - var monitor = new WindowsTitleMonitor(); - - string[] code = new string[1]; - monitor.TitleChangeEvent += args => - { - if (args.Title.Contains(settings.State)) - { - code[0] = args.Title; - settings.Code = args.Title; - } - }; - using (var process = Process.Start(settings.FormattedAuthUrl)) - { - while (string.IsNullOrEmpty(code[0])) - { - Application.DoEvents(); - } - }; - - return true; - } - - /// - /// Authenticate via an embedded browser - /// If this works, return the code - /// - /// OAuth2Settings with the Auth / Token url etc - /// true if completed, false if canceled - private static bool AuthenticateViaEmbeddedBrowser(OAuth2Settings settings) { - if (string.IsNullOrEmpty(settings.CloudServiceName)) { - throw new ArgumentNullException(nameof(settings.CloudServiceName)); - } - if (settings.BrowserSize == Size.Empty) { - throw new ArgumentNullException(nameof(settings.BrowserSize)); - } - OAuthLoginForm loginForm = new OAuthLoginForm($"Authorize {settings.CloudServiceName}", settings.BrowserSize, settings.FormattedAuthUrl, settings.RedirectUrl); - loginForm.ShowDialog(); - if (loginForm.IsOk) { - if (loginForm.CallbackParameters.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) { - settings.Code = code; - GenerateRefreshToken(settings); - return true; - } - return UpdateFromCallback(settings, loginForm.CallbackParameters); - } - return false; - } - - /// - /// Authenticate via a local server by using the LocalServerCodeReceiver - /// If this works, return the code - /// - /// OAuth2Settings with the Auth / Token url etc - /// true if completed - private static bool AuthenticateViaLocalServer(OAuth2Settings settings) { - var codeReceiver = new LocalServerCodeReceiver(); - IDictionary result = codeReceiver.ReceiveCode(settings); - - if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) { - settings.Code = code; - GenerateRefreshToken(settings); - return true; - } - - if (result.TryGetValue("error", out var error)) { - if (result.TryGetValue("error_description", out var errorDescription)) { - throw new Exception(errorDescription); - } - if ("access_denied" == error) { - throw new UnauthorizedAccessException("Access denied"); - } - throw new Exception(error); - } - return false; - } - - /// - /// 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); - } - } - - /// - /// Check and authenticate or refresh tokens - /// - /// OAuth2Settings - public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) { - // Get Refresh / Access token - if (string.IsNullOrEmpty(settings.RefreshToken)) { - if (!Authenticate(settings)) { - throw new Exception("Authentication cancelled"); - } - } - if (settings.IsAccessTokenExpired) { - GenerateAccessToken(settings); - // Get Refresh / Access token - if (string.IsNullOrEmpty(settings.RefreshToken)) { - if (!Authenticate(settings)) { - throw new Exception("Authentication cancelled"); - } - GenerateAccessToken(settings); - } - } - if (settings.IsAccessTokenExpired) { - throw new Exception("Authentication failed"); - } - } - - /// - /// CreateWebRequest ready for OAuth 2 access - /// - /// HTTPMethod - /// - /// OAuth2Settings - /// HttpWebRequest - public static HttpWebRequest CreateOAuth2WebRequest(HTTPMethod method, string url, OAuth2Settings settings) { - CheckAndAuthenticateOrRefresh(settings); - - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, method); - AddOAuth2Credentials(webRequest, settings); - return webRequest; - } - } -} diff --git a/GreenshotPlugin/Core/WindowDetails.cs b/GreenshotPlugin/Core/WindowDetails.cs index 903483c0c..7c9a2b905 100644 --- a/GreenshotPlugin/Core/WindowDetails.cs +++ b/GreenshotPlugin/Core/WindowDetails.cs @@ -266,33 +266,6 @@ namespace GreenshotPlugin.Core } } - /// - /// Retrieve all windows with a certain title or classname - /// - /// IEnumerable - /// The regexp to look for in the title - /// The regexp to look for in the classname - /// IEnumerable WindowDetails with all the found windows - private static IEnumerable FindWindow(IEnumerable windows, string titlePattern, string classnamePattern) { - Regex titleRegexp = null; - Regex classnameRegexp = null; - - if (titlePattern != null && titlePattern.Trim().Length > 0) { - titleRegexp = new Regex(titlePattern); - } - if (classnamePattern != null && classnamePattern.Trim().Length > 0) { - classnameRegexp = new Regex(classnamePattern); - } - - foreach(WindowDetails window in windows) { - if (titleRegexp != null && titleRegexp.IsMatch(window.Text)) { - yield return window; - } else if (classnameRegexp != null && classnameRegexp.IsMatch(window.ClassName)) { - yield return window; - } - } - } - /// /// Retrieve the child with matching classname /// diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 21d640496..369e584cb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,14 +43,14 @@ stages: platform: $(buildPlatform) configuration: $(buildConfiguration) env: - Box_ClientId: $(Box_ClientId) - Box_ClientSecret: $(Box_ClientSecret) - DropBox_ClientId: $(DropBox_ClientId) - DropBox_ClientSecret: $(DropBox_ClientSecret) + Box13_ClientId: $(Box13_ClientId) + Box13_ClientSecret: $(Box13_ClientSecret) + DropBox13_ClientId: $(DropBox13_ClientId) + DropBox13_ClientSecret: $(DropBox13_ClientSecret) Flickr_ClientId: $(Flickr_ClientId) Flickr_ClientSecret: $(Flickr_ClientSecret) - Imgur_ClientId: $(Imgur_ClientId) - Imgur_ClientSecret: $(Imgur_ClientSecret) + Imgur13_ClientId: $(Imgur13_ClientId) + Imgur13_ClientSecret: $(Imgur13_ClientSecret) Photobucket_ClientId: $(Photobucket_ClientId) Photobucket_ClientSecret: $(Photobucket_ClientSecret) Picasa_ClientId: $(Picasa_ClientId)