1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-02 20:34:07 +02:00

Fix login session export not working across different computers after a Chromium update

This commit is contained in:
chylex 2022-01-18 14:33:45 +01:00
parent eee72959e6
commit 655d334714
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
6 changed files with 82 additions and 100 deletions
Configuration
Dialogs/Settings
Management
Program.cs
lib
TweetLib.Utils/IO
TweetTest.Utils/IO

View File

@ -12,8 +12,6 @@ static class Arguments {
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated"; public const string ArgUpdated = "-updated";
// class data and methods // class data and methods
@ -30,8 +28,6 @@ public static string GetValue(string key) {
public static CommandLineArgs GetCurrentClean() { public static CommandLineArgs GetCurrentClean() {
CommandLineArgs args = Current.Clone(); CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart); args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated); args.RemoveFlag(ArgUpdated);
return args; return args;
} }

View File

@ -2,12 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Management; using TweetDuck.Management;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
using TweetLib.Utils.Static;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsManage : Form { sealed partial class DialogSettingsManage : Form {
@ -29,10 +27,6 @@ private ProfileManager.Items SelectedItems {
} }
} }
private bool SelectedItemsForceRestart {
get => _selectedItems.HasFlag(ProfileManager.Items.Session);
}
public bool IsRestarting { get; private set; } public bool IsRestarting { get; private set; }
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
@ -146,6 +140,10 @@ private void btnContinue_Click(object sender, EventArgs e) {
App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.Session)) {
ProfileManager.DeleteAuthCookie();
}
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) { if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) {
Program.Config.Plugins.Reset(); Program.Config.Plugins.Reset();
@ -156,13 +154,8 @@ private void btnContinue_Click(object sender, EventArgs e) {
} }
} }
if (SelectedItemsForceRestart) { if (requestedRestartFromConfig && FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgDeleteCookies } : StringUtils.EmptyArray); RestartProgram();
}
else if (requestedRestartFromConfig) {
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram();
}
} }
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@ -180,13 +173,8 @@ private void btnContinue_Click(object sender, EventArgs e) {
App.ConfigManager.SaveAll(); App.ConfigManager.SaveAll();
App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItemsForceRestart) { if (requestedRestartFromConfig && FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgImportCookies } : StringUtils.EmptyArray); RestartProgram();
}
else if (requestedRestartFromConfig) {
if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram();
}
} }
} }
@ -235,16 +223,16 @@ private void SetFlag(ProfileManager.Items flag, bool enable) {
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None; btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
if (currentState == State.Import) { if (currentState == State.Import) {
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile"; btnContinue.Text = "Import Profile";
} }
else if (currentState == State.Reset) { else if (currentState == State.Reset) {
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults"; btnContinue.Text = "Restore Defaults";
} }
} }
private void RestartProgram(params string[] extraArgs) { private void RestartProgram() {
IsRestarting = true; IsRestarting = true;
Program.Restart(extraArgs); Program.Restart();
} }
} }
} }

View File

@ -2,6 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using CefSharp;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
@ -10,13 +12,10 @@
namespace TweetDuck.Management { namespace TweetDuck.Management {
sealed class ProfileManager { sealed class ProfileManager {
private static readonly string CookiesPath = Path.Combine(App.StoragePath, "Cookies"); private const string AuthCookieUrl = "https://twitter.com";
private static readonly string LocalPrefsPath = Path.Combine(App.StoragePath, "LocalPrefs.json"); private const string AuthCookieName = "auth_token";
private const string AuthCookieDomain = ".twitter.com";
private static readonly string TempCookiesPath = Path.Combine(App.StoragePath, "CookiesTmp"); private const string AuthCookiePath = "/";
private static readonly string TempLocalPrefsPath = Path.Combine(App.StoragePath, "LocalPrefsTmp.json");
private const int SessionFileCount = 2;
[Flags] [Flags]
public enum Items { public enum Items {
@ -62,8 +61,14 @@ public bool Export(Items items) {
} }
if (items.HasFlag(Items.Session)) { if (items.HasFlag(Items.Session)) {
stream.WriteFile("cookies", CookiesPath); string authToken = ReadAuthCookie();
stream.WriteFile("localprefs", LocalPrefsPath);
if (authToken != null) {
stream.WriteString("cookie.auth", authToken);
}
else {
FormMessage.Warning("Export Profile", "Could not find any login session.", FormMessage.OK);
}
} }
stream.Flush(); stream.Flush();
@ -98,6 +103,7 @@ public Items FindImportItems() {
case "cookies": case "cookies":
case "localprefs": case "localprefs":
case "cookie.auth":
items |= Items.Session; items |= Items.Session;
break; break;
} }
@ -112,7 +118,7 @@ public Items FindImportItems() {
public bool Import(Items items) { public bool Import(Items items) {
try { try {
var missingPlugins = new HashSet<string>(); var missingPlugins = new HashSet<string>();
var sessionFiles = new HashSet<string>(); bool oldCookies = false;
using (CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) { using (CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) {
CombinedFileStream.Entry entry; CombinedFileStream.Entry entry;
@ -154,17 +160,30 @@ public bool Import(Items items) {
break; break;
case "cookies": case "cookies":
case "localprefs":
if (items.HasFlag(Items.Session)) { if (items.HasFlag(Items.Session)) {
entry.WriteToFile(TempCookiesPath); oldCookies = true;
sessionFiles.Add(entry.KeyName);
} }
break; break;
case "localprefs": case "cookie.auth":
if (items.HasFlag(Items.Session)) { if (items.HasFlag(Items.Session)) {
entry.WriteToFile(TempLocalPrefsPath); using ICookieManager cookies = Cef.GetGlobalCookieManager();
sessionFiles.Add(entry.KeyName);
var _ = cookies.SetCookieAsync(AuthCookieUrl, new Cookie {
Name = AuthCookieName,
Domain = AuthCookieDomain,
Path = AuthCookiePath,
Value = Encoding.UTF8.GetString(entry.Contents),
Expires = DateTime.Now.Add(TimeSpan.FromDays(365 * 5)),
HttpOnly = true,
Secure = true
}).ContinueWith(t => {
// ReSharper disable once AccessToDisposedClosure
// ReSharper disable once ConvertToLambdaExpression
return cookies.FlushStoreAsync();
}).Result;
} }
break; break;
@ -172,10 +191,8 @@ public bool Import(Items items) {
} }
} }
if (items.HasFlag(Items.Session) && sessionFiles.Count != SessionFileCount) { if (items.HasFlag(Items.Session) && oldCookies) {
FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK); FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK);
File.Delete(TempCookiesPath);
File.Delete(TempLocalPrefsPath);
return false; return false;
} }
@ -190,35 +207,6 @@ public bool Import(Items items) {
} }
} }
public static void ImportCookies() {
if (File.Exists(TempCookiesPath) && File.Exists(TempLocalPrefsPath)) {
try {
if (File.Exists(CookiesPath)) {
File.Delete(CookiesPath);
}
if (File.Exists(LocalPrefsPath)) {
File.Delete(LocalPrefsPath);
}
File.Move(TempCookiesPath, CookiesPath);
File.Move(TempLocalPrefsPath, LocalPrefsPath);
} catch (Exception e) {
App.ErrorHandler.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
}
}
}
public static void DeleteCookies() {
try {
if (File.Exists(CookiesPath)) {
File.Delete(CookiesPath);
}
} catch (Exception e) {
App.ErrorHandler.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root) { private static IEnumerable<PathInfo> EnumerateFilesRelative(string root) {
if (Directory.Exists(root)) { if (Directory.Exists(root)) {
int rootLength = root.Length; int rootLength = root.Length;
@ -238,5 +226,22 @@ public PathInfo(string fullPath, int rootLength) {
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
} }
} }
private static string ReadAuthCookie() {
using var cookieManager = Cef.GetGlobalCookieManager();
foreach (var cookie in cookieManager.VisitUrlCookiesAsync(AuthCookieUrl, true).Result) {
if (cookie.Name == AuthCookieName && cookie.Domain == AuthCookieDomain && cookie.Path == AuthCookiePath && cookie.HttpOnly && cookie.Secure) {
return cookie.Value;
}
}
return null;
}
public static void DeleteAuthCookie() {
using var cookieManager = Cef.GetGlobalCookieManager();
var _ = cookieManager.DeleteCookiesAsync(AuthCookieUrl, "auth_token").Result;
}
} }
} }

View File

@ -97,13 +97,6 @@ public bool TryLockDataFolder(string lockFile) {
} }
public void BeforeLaunch() { public void BeforeLaunch() {
if (Arguments.HasFlag(Arguments.ArgImportCookies)) {
ProfileManager.ImportCookies();
}
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)) {
ProfileManager.DeleteCookies();
}
if (Arguments.HasFlag(Arguments.ArgUpdated)) { if (Arguments.HasFlag(Arguments.ArgUpdated)) {
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, InstallerFolder), 8000); WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, InstallerFolder), 8000);
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, "Service Worker"), 4000); WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, "Service Worker"), 4000);
@ -175,10 +168,8 @@ private static void OnUnhandledException(object sender, UnhandledExceptionEventA
} }
} }
public static void Restart(params string[] extraArgs) { public static void Restart() {
CommandLineArgs args = Arguments.GetCurrentClean(); RestartWithArgs(Arguments.GetCurrentClean());
CommandLineArgs.ReadStringArray('-', extraArgs, args);
RestartWithArgs(args);
} }
public static void RestartWithArgs(CommandLineArgs args) { public static void RestartWithArgs(CommandLineArgs args) {

View File

@ -28,21 +28,23 @@ public CombinedFileStream(Stream stream) {
} }
public void WriteFile(string[] identifier, string path) { public void WriteFile(string[] identifier, string path) {
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
WriteStreamImpl(JoinIdentifier(identifier), fileStream); WriteStreamImpl(JoinIdentifier(identifier), fileStream);
} }
public void WriteFile(string identifier, string path) { public void WriteFile(string identifier, string path) {
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
WriteStreamImpl(ValidateIdentifier(identifier), fileStream); WriteStreamImpl(ValidateIdentifier(identifier), fileStream);
} }
public void WriteStream(string[] identifier, Stream sourceStream) { public void WriteString(string[] identifier, string contents) {
WriteStreamImpl(JoinIdentifier(identifier), sourceStream); using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(contents));
WriteStreamImpl(JoinIdentifier(identifier), memoryStream);
} }
public void WriteStream(string identifier, Stream sourceStream) { public void WriteString(string identifier, string contents) {
WriteStreamImpl(ValidateIdentifier(identifier), sourceStream); using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(contents));
WriteStreamImpl(ValidateIdentifier(identifier), memoryStream);
} }
private void WriteStreamImpl(string identifier, Stream sourceStream) { private void WriteStreamImpl(string identifier, Stream sourceStream) {

View File

@ -26,12 +26,12 @@ type internal TestData =
static member singleFile = static member singleFile =
TestData.setup (fun f -> TestData.setup (fun f ->
f.WriteStream("File 1", new MemoryStream(Encoding.UTF8.GetBytes("test file\n123"))) f.WriteString("File 1", "test file\n123")
) )
static member singleFileWithMultiIdentifier = static member singleFileWithMultiIdentifier =
TestData.setup (fun f -> TestData.setup (fun f ->
f.WriteStream([| "File 1"; "A"; "B" |], new MemoryStream(Encoding.UTF8.GetBytes("test file\n123"))) f.WriteString([| "File 1"; "A"; "B" |], "test file\n123")
) )
static member singleFileStreams = static member singleFileStreams =
@ -40,9 +40,9 @@ type internal TestData =
static member threeFiles = static member threeFiles =
TestData.setup (fun f -> TestData.setup (fun f ->
f.WriteStream("File 1", new MemoryStream(Encoding.UTF8.GetBytes("Contents of\nFile 1"))) f.WriteString("File 1", "Contents of\nFile 1")
f.WriteStream("File 2", new MemoryStream(Encoding.UTF8.GetBytes("Contents of\nFile 2"))) f.WriteString("File 2", "Contents of\nFile 2")
f.WriteStream("File 3", new MemoryStream(Encoding.UTF8.GetBytes("Contents of\nFile 3"))) f.WriteString("File 3", "Contents of\nFile 3")
) )
@ -52,21 +52,21 @@ module Validation =
let ``an identifier containing '|' throws`` () = let ``an identifier containing '|' throws`` () =
TestData.setup (fun f -> TestData.setup (fun f ->
Assert.Throws<ArgumentException>(fun () -> Assert.Throws<ArgumentException>(fun () ->
f.WriteStream("File|1", new MemoryStream(Array.empty)) f.WriteString("File|1", "")
) |> ignore ) |> ignore
) )
[<Fact>] [<Fact>]
let ``an identifier 255 bytes long does not throw`` () = let ``an identifier 255 bytes long does not throw`` () =
TestData.setup (fun f -> TestData.setup (fun f ->
f.WriteStream(String.replicate 255 "a", new MemoryStream(Array.empty)) f.WriteString(String.replicate 255 "a", "")
) )
[<Fact>] [<Fact>]
let ``an identifier 256 bytes long throws`` () = let ``an identifier 256 bytes long throws`` () =
TestData.setup (fun f -> TestData.setup (fun f ->
Assert.Throws<ArgumentOutOfRangeException>(fun () -> Assert.Throws<ArgumentOutOfRangeException>(fun () ->
f.WriteStream(String.replicate 256 "a", new MemoryStream(Array.empty)) f.WriteString(String.replicate 256 "a", "")
) |> ignore ) |> ignore
) )