BUG-2141: Fix for old browser version when embedding IE. And some other small imgur stability changes.

This commit is contained in:
Krom, Robertus 2017-07-31 09:13:08 +02:00
parent f695ce8182
commit b8009fe256
6 changed files with 194 additions and 57 deletions

View file

@ -9,6 +9,21 @@ All details to our tickets can be found here: https://greenshot.atlassian.net
@DETAILVERSION@ @DETAILVERSION@
Bugs fixed:
* [BUG-2235] - Imgur authentication issues due to imgur api change
* [BUG-2227] - NullReferenceException when accessing the Imgur History
* [BUG-2225] - NullReferenceException when changing the color of text
* [BUG-2219] - Korean is spelled incorrectly in the installer
* [BUG-2213] - NullReferenceException in the Freehand tool
* [BUG-2203] - ArgumentNullException in the Freehand tool
* [BUG-2141] - Imgur authentication issues due to old embedded IE
Features:
* [FEATURE-1064] - Added CTRL+Backspace & CTRL+A to the text tool
Greenshot 1.2.9.129-569de71 RELEASE
Bugs fixed: Bugs fixed:
* [BUG-2051] - Scroll-lock button not usable as hotkey * [BUG-2051] - Scroll-lock button not usable as hotkey
* [BUG-2056] - Cannot export to external command Paint.NET if .greenshot format is used * [BUG-2056] - Cannot export to external command Paint.NET if .greenshot format is used

View file

@ -39,6 +39,9 @@ namespace GreenshotPlugin.Controls {
public bool IsOk => DialogResult == DialogResult.OK; public bool IsOk => DialogResult == DialogResult.OK;
public OAuthLoginForm(string browserTitle, Size size, string authorizationLink, string callbackUrl) { public OAuthLoginForm(string browserTitle, Size size, string authorizationLink, string callbackUrl) {
// Make sure Greenshot uses the correct browser version
IEHelper.FixBrowserVersion(false);
_callbackUrl = callbackUrl; _callbackUrl = callbackUrl;
// Fix for BUG-2071 // Fix for BUG-2071
if (callbackUrl.EndsWith("/")) if (callbackUrl.EndsWith("/"))

View file

@ -18,7 +18,12 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Reflection;
using log4net;
using Microsoft.Win32; using Microsoft.Win32;
namespace GreenshotPlugin.Core { namespace GreenshotPlugin.Core {
@ -26,24 +31,124 @@ namespace GreenshotPlugin.Core {
/// Description of IEHelper. /// Description of IEHelper.
/// </summary> /// </summary>
public static class IEHelper { public static class IEHelper {
private static readonly ILog Log = LogManager.GetLogger(typeof(IEHelper));
// Internet explorer Registry key // Internet explorer Registry key
private const string IE_KEY = @"Software\Microsoft\Internet Explorer"; private const string IeKey = @"Software\Microsoft\Internet Explorer";
/// <summary> /// <summary>
/// Helper method to get the IE version /// Get the current browser version
/// </summary> /// </summary>
/// <returns></returns> /// <returns>int with browser version</returns>
public static int IEVersion() { public static int IEVersion
int version = 7; {
// Seeing if IE 9 is used, here we need another offset! get
using (RegistryKey ieKey = Registry.LocalMachine.OpenSubKey(IE_KEY, false)) { {
object versionKey = ieKey?.GetValue("Version"); var maxVer = 7;
if (versionKey != null) { using (var ieKey = Registry.LocalMachine.OpenSubKey(IeKey, false))
int.TryParse(versionKey.ToString().Substring(0,1), out version); {
foreach (var value in new[] { "svcVersion", "svcUpdateVersion", "Version", "W2kVersion" })
{
var objVal = ieKey.GetValue(value, "0");
var strVal = Convert.ToString(objVal);
var iPos = strVal.IndexOf('.');
if (iPos > 0)
{
strVal = strVal.Substring(0, iPos);
}
int res;
if (int.TryParse(strVal, out res))
{
maxVer = Math.Max(maxVer, res);
}
}
} }
return maxVer;
} }
return version;
} }
/// <summary>
/// Get the highest possible version for the embedded browser
/// </summary>
/// <param name="ignoreDoctype">true to ignore the doctype when loading a page</param>
/// <returns>IE Feature</returns>
public static int GetEmbVersion(bool ignoreDoctype = true)
{
var ieVersion = IEVersion;
if (ieVersion > 9)
{
return ieVersion * 1000 + (ignoreDoctype ? 1 : 0);
}
if (ieVersion > 7)
{
return ieVersion * 1111;
}
return 7000;
}
/// <summary>
/// Fix browser version to the highest possible
/// </summary>
/// <param name="ignoreDoctype">true to ignore the doctype when loading a page</param>
public static void FixBrowserVersion(bool ignoreDoctype = true)
{
var applicationName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);
FixBrowserVersion(applicationName, ignoreDoctype);
}
/// <summary>
/// Fix the browser version for the specified application
/// </summary>
/// <param name="applicationName">Name of the process</param>
/// <param name="ignoreDoctype">true to ignore the doctype when loading a page</param>
public static void FixBrowserVersion(string applicationName, bool ignoreDoctype = true)
{
FixBrowserVersion(applicationName, GetEmbVersion(ignoreDoctype));
}
/// <summary>
/// Fix the browser version for the specified application
/// </summary>
/// <param name="applicationName">Name of the process</param>
/// <param name="ieVersion">
/// Version, see
/// <a href="https://msdn.microsoft.com/en-us/library/ee330730(v=vs.85).aspx#browser_emulation">Browser Emulation</a>
/// </param>
public static void FixBrowserVersion(string applicationName, int ieVersion)
{
ModifyRegistry("HKEY_CURRENT_USER", applicationName + ".exe", ieVersion);
#if DEBUG
ModifyRegistry("HKEY_CURRENT_USER", applicationName + ".vshost.exe", ieVersion);
#endif
}
/// <summary>
/// Make the change to the registry
/// </summary>
/// <param name="root">HKEY_CURRENT_USER or something</param>
/// <param name="applicationName">Name of the executable</param>
/// <param name="ieFeatureVersion">Version to use</param>
private static void ModifyRegistry(string root, string applicationName, int ieFeatureVersion)
{
var regKey = root + @"\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION";
try
{
Registry.SetValue(regKey, applicationName, ieFeatureVersion);
}
catch (Exception ex)
{
// some config will hit access rights exceptions
// this is why we try with both LOCAL_MACHINE and CURRENT_USER
Log.Error(ex);
Log.ErrorFormat("couldn't modify the registry key {0}", regKey);
}
}
/// <summary> /// <summary>
/// Find the DirectUI window for MSAA (Accessible) /// Find the DirectUI window for MSAA (Accessible)
/// </summary> /// </summary>
@ -55,7 +160,7 @@ namespace GreenshotPlugin.Core {
} }
WindowDetails tmpWd = browserWindowDetails; WindowDetails tmpWd = browserWindowDetails;
// Since IE 9 the TabBandClass is less deep! // Since IE 9 the TabBandClass is less deep!
if (IEVersion() < 9) { if (IEVersion < 9) {
tmpWd = tmpWd.GetChild("CommandBarClass"); tmpWd = tmpWd.GetChild("CommandBarClass");
tmpWd = tmpWd?.GetChild("ReBarWindow32"); tmpWd = tmpWd?.GetChild("ReBarWindow32");
} }

View file

@ -127,26 +127,20 @@ namespace GreenshotPlugin.Core {
imageFormat = ImageFormat.Icon; imageFormat = ImageFormat.Icon;
break; break;
default: default:
// Problem with non-seekable streams most likely doesn't happen with Windows 7 (OS Version 6.1 and later)
// http://stackoverflow.com/questions/8349260/generic-gdi-error-on-one-machine-but-not-the-other
if (!stream.CanSeek) {
if (!Environment.OSVersion.IsWindows7OrLater())
{
useMemoryStream = true;
Log.Warn("Using memorystream prevent an issue with saving to a non seekable stream.");
}
}
imageFormat = ImageFormat.Png; imageFormat = ImageFormat.Png;
break; break;
} }
Log.DebugFormat("Saving image to stream with Format {0} and PixelFormat {1}", imageFormat, imageToSave.PixelFormat); Log.DebugFormat("Saving image to stream with Format {0} and PixelFormat {1}", imageFormat, imageToSave.PixelFormat);
// Check if we want to use a memory stream, to prevent a issue which happens with Windows before "7". // Check if we want to use a memory stream, to prevent issues with non seakable streams
// The save is made to the targetStream, this is directed to either the MemoryStream or the original // The save is made to the targetStream, this is directed to either the MemoryStream or the original
Stream targetStream = stream; Stream targetStream = stream;
if (useMemoryStream) { if (!stream.CanSeek)
memoryStream = new MemoryStream(); {
targetStream = memoryStream; useMemoryStream = true;
Log.Warn("Using memorystream prevent an issue with saving to a non seekable stream.");
memoryStream = new MemoryStream();
targetStream = memoryStream;
} }
if (Equals(imageFormat, ImageFormat.Jpeg)) if (Equals(imageFormat, ImageFormat.Jpeg))
@ -213,18 +207,20 @@ namespace GreenshotPlugin.Core {
} }
// Output the surface elements, size and marker to the stream // Output the surface elements, size and marker to the stream
if (outputSettings.Format == OutputFormat.greenshot) { if (outputSettings.Format != OutputFormat.greenshot)
using (MemoryStream tmpStream = new MemoryStream()) { {
long bytesWritten = surface.SaveElementsToStream(tmpStream); return;
using (BinaryWriter writer = new BinaryWriter(tmpStream)) { }
writer.Write(bytesWritten); using (MemoryStream tmpStream = new MemoryStream()) {
Version v = Assembly.GetExecutingAssembly().GetName().Version; long bytesWritten = surface.SaveElementsToStream(tmpStream);
byte[] marker = Encoding.ASCII.GetBytes($"Greenshot{v.Major:00}.{v.Minor:00}"); using (BinaryWriter writer = new BinaryWriter(tmpStream)) {
writer.Write(marker); writer.Write(bytesWritten);
tmpStream.WriteTo(stream); Version v = Assembly.GetExecutingAssembly().GetName().Version;
} byte[] marker = Encoding.ASCII.GetBytes($"Greenshot{v.Major:00}.{v.Minor:00}");
} writer.Write(marker);
} tmpStream.WriteTo(stream);
}
}
} }
finally finally
{ {

View file

@ -1002,12 +1002,21 @@ Greenshot received information from CloudServiceName. You can close this browser
// "expires_in":3920, // "expires_in":3920,
// "token_type":"Bearer", // "token_type":"Bearer",
// "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" // "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
settings.AccessToken = (string)refreshTokenResult[AccessToken]; if (refreshTokenResult.ContainsKey(AccessToken))
settings.RefreshToken = (string)refreshTokenResult[RefreshToken]; {
settings.AccessToken = (string)refreshTokenResult[AccessToken];
object seconds = refreshTokenResult[ExpiresIn]; }
if (seconds != null) { if (refreshTokenResult.ContainsKey(RefreshToken))
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); {
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; settings.Code = null;
} }
@ -1029,14 +1038,17 @@ Greenshot received information from CloudServiceName. You can close this browser
// Refresh the refresh token :) // Refresh the refresh token :)
settings.RefreshToken = callbackParameters[RefreshToken]; settings.RefreshToken = callbackParameters[RefreshToken];
} }
var expiresIn = callbackParameters[ExpiresIn]; if (callbackParameters.ContainsKey(ExpiresIn))
settings.AccessTokenExpires = DateTimeOffset.MaxValue;
if (expiresIn != null)
{ {
double seconds; var expiresIn = callbackParameters[ExpiresIn];
if (double.TryParse(expiresIn, out seconds)) settings.AccessTokenExpires = DateTimeOffset.MaxValue;
if (expiresIn != null)
{ {
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds); double seconds;
if (double.TryParse(expiresIn, out seconds))
{
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds);
}
} }
} }
settings.AccessToken = callbackParameters[AccessToken]; settings.AccessToken = callbackParameters[AccessToken];
@ -1085,16 +1097,22 @@ Greenshot received information from CloudServiceName. You can close this browser
} }
} }
settings.AccessToken = (string)accessTokenResult[AccessToken]; if (accessTokenResult.ContainsKey(AccessToken))
{
settings.AccessToken = (string) accessTokenResult[AccessToken];
settings.AccessTokenExpires = DateTimeOffset.MaxValue;
}
if (accessTokenResult.ContainsKey(RefreshToken)) { if (accessTokenResult.ContainsKey(RefreshToken)) {
// Refresh the refresh token :) // Refresh the refresh token :)
settings.RefreshToken = (string)accessTokenResult[RefreshToken]; settings.RefreshToken = (string)accessTokenResult[RefreshToken];
} }
object seconds = accessTokenResult[ExpiresIn]; if (accessTokenResult.ContainsKey(ExpiresIn))
if (seconds != null) { {
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double)seconds); object seconds = accessTokenResult[ExpiresIn];
} else { if (seconds != null)
settings.AccessTokenExpires = DateTimeOffset.MaxValue; {
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds((double) seconds);
}
} }
} }

View file

@ -38,7 +38,7 @@ environment:
secure: bjKXhFZkDqaq98XBrz5oQKQfT8CLpuv2ZAiBIwkzloaAPUs97b5yx6h/xFaE4NLS secure: bjKXhFZkDqaq98XBrz5oQKQfT8CLpuv2ZAiBIwkzloaAPUs97b5yx6h/xFaE4NLS
credentials_picasa_consumer_secret: credentials_picasa_consumer_secret:
secure: yNptTpmJWypbu9alOQtetxU66drr2FKxoPflNgRJdag= secure: yNptTpmJWypbu9alOQtetxU66drr2FKxoPflNgRJdag=
build_type: RC build_type: RC1
rsakey: rsakey:
secure: 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 secure: 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
before_build: before_build: