Fix cookie bug

This commit is contained in:
Natsumi
2025-03-03 17:02:56 +13:00
parent ea12d25cef
commit 747a7ca683
8 changed files with 180 additions and 147 deletions

View File

@@ -36,14 +36,6 @@ namespace VRCX
MainForm.Instance.Browser.ShowDevTools(); MainForm.Instance.Browser.ShowDevTools();
} }
/// <summary>
/// Deletes all cookies from the global cef cookie manager.
/// </summary>
public override void DeleteAllCookies()
{
Cef.GetGlobalCookieManager().DeleteCookies();
}
public override void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) public override void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand)
{ {
Program.VRCXVRInstance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); Program.VRCXVRInstance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand);

View File

@@ -8,7 +8,6 @@ namespace VRCX
{ {
// AppApi // AppApi
public abstract void ShowDevTools(); public abstract void ShowDevTools();
public abstract void DeleteAllCookies();
public abstract void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand); public abstract void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand);
public abstract void RefreshVR(); public abstract void RefreshVR();
public abstract void RestartVR(); public abstract void RestartVR();

View File

@@ -226,9 +226,17 @@ namespace VRCX
if (File.Exists(filePath)) if (File.Exists(filePath))
return null; return null;
var success = await ImageCache.SaveImageToFile(url, filePath); try
{
return success ? filePath : null; await ImageCache.SaveImageToFile(url, filePath);
}
catch (Exception ex)
{
logger.Error(ex, "Failed to save print to file");
return null;
}
return filePath;
} }
public async Task<string> SaveStickerToFile(string url, string ugcFolderPath, string monthFolder, string fileName) public async Task<string> SaveStickerToFile(string url, string ugcFolderPath, string monthFolder, string fileName)
@@ -239,9 +247,17 @@ namespace VRCX
if (File.Exists(filePath)) if (File.Exists(filePath))
return null; return null;
var success = await ImageCache.SaveImageToFile(url, filePath); try
{
return success ? filePath : null; await ImageCache.SaveImageToFile(url, filePath);
}
catch (Exception ex)
{
logger.Error(ex, "Failed to save print to file");
return null;
}
return filePath;
} }
} }
} }

View File

@@ -10,10 +10,6 @@ namespace VRCX
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public override void DeleteAllCookies()
{
}
public override void ShowDevTools() public override void ShowDevTools()
{ {
} }

View File

@@ -81,11 +81,11 @@ namespace VRCX
var vueDevtoolsCrxPath = Path.Join(Program.BaseDirectory, @"..\..\build-tools\Vue-js-devtools.crx"); var vueDevtoolsCrxPath = Path.Join(Program.BaseDirectory, @"..\..\build-tools\Vue-js-devtools.crx");
if (File.Exists(vueDevtoolsCrxPath)) if (File.Exists(vueDevtoolsCrxPath))
{ {
var VueDevtoolsPath = Path.Join(extensionsPath, "Vue-js-devtools"); var vueDevtoolsPath = Path.Join(extensionsPath, "Vue-js-devtools");
if (!Directory.Exists(VueDevtoolsPath)) if (!Directory.Exists(vueDevtoolsPath))
{ {
Directory.CreateDirectory(VueDevtoolsPath); Directory.CreateDirectory(vueDevtoolsPath);
ZipFile.ExtractToDirectory(vueDevtoolsCrxPath, VueDevtoolsPath); ZipFile.ExtractToDirectory(vueDevtoolsCrxPath, vueDevtoolsPath);
} }
} }

View File

@@ -5,11 +5,13 @@ using System.Net;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog;
namespace VRCX; namespace VRCX;
internal static class ImageCache internal static class ImageCache
{ {
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly string cacheLocation; private static readonly string cacheLocation;
private static readonly HttpClient httpClient; private static readonly HttpClient httpClient;
private static readonly List<string> ImageHosts = private static readonly List<string> ImageHosts =
@@ -47,6 +49,31 @@ internal static class ImageCache
} }
} }
private static async Task<Stream> FetchImage(string url)
{
var uri = new Uri(url);
if (!ImageHosts.Contains(uri.Host))
throw new ArgumentException("Invalid image host", url);
var cookieString = string.Empty;
if (WebApi.Instance != null &&
WebApi.Instance._cookieContainer != null &&
uri.Host == "api.vrchat.cloud")
{
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
foreach (Cookie cookie in cookies)
cookieString += $"{cookie.Name}={cookie.Value};";
}
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrEmpty(cookieString))
request.Headers.Add("Cookie", cookieString);
using var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync();
}
public static async Task<string> GetImage(string url, string fileId, string version) public static async Task<string> GetImage(string url, string fileId, string version)
{ {
var directoryLocation = Path.Join(cacheLocation, fileId); var directoryLocation = Path.Join(cacheLocation, fileId);
@@ -62,29 +89,17 @@ internal static class ImageCache
Directory.Delete(directoryLocation, true); Directory.Delete(directoryLocation, true);
Directory.CreateDirectory(directoryLocation); Directory.CreateDirectory(directoryLocation);
var uri = new Uri(url); try
if (!ImageHosts.Contains(uri.Host))
throw new ArgumentException("Invalid image host", url);
var cookieString = string.Empty;
if (WebApi.Instance != null && WebApi.Instance._cookieContainer != null)
{ {
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud")); var stream = await FetchImage(url);
foreach (Cookie cookie in cookies) await using var fileStream =
cookieString += $"{cookie.Name}={cookie.Value};"; new FileStream(fileLocation, FileMode.Create, FileAccess.Write, FileShare.None);
await stream.CopyToAsync(fileStream);
} }
catch (Exception ex)
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrEmpty(cookieString))
request.Headers.Add("Cookie", cookieString);
using (var response = await httpClient.SendAsync(request))
{ {
response.EnsureSuccessStatusCode(); logger.Error(ex, "Failed to fetch image");
await using (var fileStream = new FileStream(fileLocation, FileMode.Create, FileAccess.Write, FileShare.None)) return string.Empty;
{
await response.Content.CopyToAsync(fileStream);
}
} }
var cacheSize = Directory.GetDirectories(cacheLocation).Length; var cacheSize = Directory.GetDirectories(cacheLocation).Length;
@@ -94,6 +109,13 @@ internal static class ImageCache
return fileLocation; return fileLocation;
} }
public static async Task SaveImageToFile(string url, string path)
{
var stream = await FetchImage(url);
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
await stream.CopyToAsync(fileStream);
}
private static void CleanImageCache() private static void CleanImageCache()
{ {
var dirInfo = new DirectoryInfo(cacheLocation); var dirInfo = new DirectoryInfo(cacheLocation);
@@ -103,31 +125,4 @@ internal static class ImageCache
folder.Delete(true); folder.Delete(true);
} }
} }
public static async Task<bool> SaveImageToFile(string url, string path)
{
var uri = new Uri(url);
if (!ImageHosts.Contains(uri.Host))
throw new ArgumentException("Invalid image host", url);
var cookieString = string.Empty;
if (WebApi.Instance != null && WebApi.Instance._cookieContainer != null)
{
var cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
foreach (Cookie cookie in cookies)
cookieString += $"{cookie.Name}={cookie.Value};";
}
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrEmpty(cookieString))
request.Headers.Add("Cookie", cookieString);
using var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return false;
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
await response.Content.CopyToAsync(fileStream);
return true;
}
} }

View File

@@ -1,7 +1,10 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -37,7 +40,7 @@ namespace VRCX
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
} }
public WebApi() private WebApi()
{ {
#if LINUX #if LINUX
if (Instance == null) if (Instance == null)
@@ -89,7 +92,7 @@ namespace VRCX
catch (UriFormatException) catch (UriFormatException)
{ {
VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty); VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty);
var message = "The proxy server URI you used is invalid.\nVRCX will close, please correct the proxy URI."; const string message = "The proxy server URI you used is invalid.\nVRCX will close, please correct the proxy URI.";
#if !LINUX #if !LINUX
System.Windows.Forms.MessageBox.Show(message, "Invalid Proxy URI", MessageBoxButtons.OK, MessageBoxIcon.Error); System.Windows.Forms.MessageBox.Show(message, "Invalid Proxy URI", MessageBoxButtons.OK, MessageBoxIcon.Error);
#endif #endif
@@ -106,6 +109,9 @@ namespace VRCX
public void ClearCookies() public void ClearCookies()
{ {
#if !LINUX
Cef.GetGlobalCookieManager().DeleteCookies();
#endif
_cookieContainer = new CookieContainer(); _cookieContainer = new CookieContainer();
SaveCookies(); SaveCookies();
} }
@@ -125,38 +131,70 @@ namespace VRCX
using var stream = new MemoryStream(Convert.FromBase64String((string)item[0])); using var stream = new MemoryStream(Convert.FromBase64String((string)item[0]));
_cookieContainer = new CookieContainer(); _cookieContainer = new CookieContainer();
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream)); _cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
// _cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(stream); // from .NET framework
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error($"Failed to load cookies: {e.Message}"); Logger.Error($"Failed to load cookies: {e.Message}");
} }
} }
private List<Cookie> GetAllCookies()
{
var cookieTable = (Hashtable)_cookieContainer.GetType().InvokeMember("m_domainTable",
BindingFlags.NonPublic |
BindingFlags.GetField |
BindingFlags.Instance,
null,
_cookieContainer,
new object[] { });
var uniqueCookies = new Dictionary<string, Cookie>();
foreach (var item in cookieTable.Keys)
{
var domain = (string)item;
if (string.IsNullOrEmpty(domain))
continue;
if (domain.StartsWith('.'))
domain = domain[1..];
var address = $"http://{domain}/";
if (!Uri.TryCreate(address, UriKind.Absolute, out var uri))
continue;
foreach (Cookie cookie in _cookieContainer.GetCookies(uri))
{
var key = $"{domain}.{cookie.Name}";
if (!uniqueCookies.TryGetValue(key, out var value) ||
cookie.TimeStamp > value.TimeStamp)
{
cookie.Expires = DateTime.MaxValue;
uniqueCookies[key] = cookie;
}
}
}
return uniqueCookies.Values.ToList();
}
public void SaveCookies() public void SaveCookies()
{ {
if (_cookieDirty == false) if (!_cookieDirty)
{
return; return;
}
foreach (Cookie cookie in _cookieContainer.GetAllCookies())
{
cookie.Expires = DateTime.MaxValue;
}
try try
{ {
using (var memoryStream = new MemoryStream()) var cookies = GetAllCookies();
{ using var memoryStream = new MemoryStream();
System.Text.Json.JsonSerializer.Serialize(memoryStream, _cookieContainer.GetAllCookies()); System.Text.Json.JsonSerializer.Serialize(memoryStream, cookies);
//new BinaryFormatter().Serialize(memoryStream, _cookieContainer); SQLiteLegacy.Instance.ExecuteNonQuery(
SQLiteLegacy.Instance.ExecuteNonQuery( "INSERT OR REPLACE INTO `cookies` (`key`, `value`) VALUES (@key, @value)",
"INSERT OR REPLACE INTO `cookies` (`key`, `value`) VALUES (@key, @value)", new Dictionary<string, object>() {
new Dictionary<string, object>() { {"@key", "default"},
{"@key", "default"}, {"@value", Convert.ToBase64String(memoryStream.ToArray())}
{"@value", Convert.ToBase64String(memoryStream.ToArray())} }
} );
);
}
_cookieDirty = false; _cookieDirty = false;
} }
catch (Exception e) catch (Exception e)
@@ -170,7 +208,7 @@ namespace VRCX
_cookieDirty = true; // force cookies to be saved for lastUserLoggedIn _cookieDirty = true; // force cookies to be saved for lastUserLoggedIn
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
System.Text.Json.JsonSerializer.Serialize(memoryStream, _cookieContainer.GetAllCookies()); System.Text.Json.JsonSerializer.Serialize(memoryStream, GetAllCookies());
return Convert.ToBase64String(memoryStream.ToArray()); return Convert.ToBase64String(memoryStream.ToArray());
} }
@@ -192,44 +230,44 @@ namespace VRCX
request.AutomaticDecompression = DecompressionMethods.All; request.AutomaticDecompression = DecompressionMethods.All;
request.Method = "POST"; request.Method = "POST";
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary; request.ContentType = "multipart/form-data; boundary=" + boundary;
Stream requestStream = request.GetRequestStream(); var requestStream = request.GetRequestStream();
if (options.TryGetValue("postData", out object postDataObject) == true) if (options.TryGetValue("postData", out var postDataObject))
{ {
Dictionary<string, string> postData = new Dictionary<string, string>(); var postData = new Dictionary<string, string>();
postData.Add("data", (string)postDataObject); postData.Add("data", (string)postDataObject);
string FormDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n"; const string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n";
foreach (string key in postData.Keys) foreach (var key in postData.Keys)
{ {
string item = string.Format(FormDataTemplate, boundary, key, postData[key]); var item = string.Format(formDataTemplate, boundary, key, postData[key]);
byte[] itemBytes = Encoding.UTF8.GetBytes(item); var itemBytes = Encoding.UTF8.GetBytes(item);
await requestStream.WriteAsync(itemBytes, 0, itemBytes.Length); await requestStream.WriteAsync(itemBytes);
} }
} }
var imageData = options["imageData"] as string; var imageData = options["imageData"] as string;
byte[] fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false); var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false);
string fileFormKey = "image"; const string fileFormKey = "image";
string fileName = "image.png"; const string fileName = "image.png";
string fileMimeType = "image/png"; const string fileMimeType = "image/png";
string HeaderTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n"; const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n";
string header = string.Format(HeaderTemplate, boundary, fileFormKey, fileName, fileMimeType); var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType);
byte[] headerbytes = Encoding.UTF8.GetBytes(header); var headerBytes = Encoding.UTF8.GetBytes(header);
await requestStream.WriteAsync(headerbytes, 0, headerbytes.Length); await requestStream.WriteAsync(headerBytes);
using (MemoryStream fileStream = new MemoryStream(fileToUpload)) using (var fileStream = new MemoryStream(fileToUpload))
{ {
byte[] buffer = new byte[1024]; var buffer = new byte[1024];
int bytesRead = 0; var bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{ {
await requestStream.WriteAsync(buffer, 0, bytesRead); await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
} }
fileStream.Close(); fileStream.Close();
} }
byte[] newlineBytes = Encoding.UTF8.GetBytes("\r\n"); var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes, 0, newlineBytes.Length); await requestStream.WriteAsync(newlineBytes);
byte[] endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--"); var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes, 0, endBytes.Length); await requestStream.WriteAsync(endBytes);
requestStream.Close(); requestStream.Close();
} }
@@ -244,11 +282,9 @@ namespace VRCX
var fileData = options["fileData"] as string; var fileData = options["fileData"] as string;
var sentData = Convert.FromBase64CharArray(fileData.ToCharArray(), 0, fileData.Length); var sentData = Convert.FromBase64CharArray(fileData.ToCharArray(), 0, fileData.Length);
request.ContentLength = sentData.Length; request.ContentLength = sentData.Length;
using (var sendStream = request.GetRequestStream()) await using var sendStream = request.GetRequestStream();
{ await sendStream.WriteAsync(sentData);
await sendStream.WriteAsync(sentData, 0, sentData.Length); sendStream.Close();
sendStream.Close();
}
} }
private static async Task ImageUpload(HttpWebRequest request, IDictionary<string, object> options) private static async Task ImageUpload(HttpWebRequest request, IDictionary<string, object> options)
@@ -258,48 +294,48 @@ namespace VRCX
request.AutomaticDecompression = DecompressionMethods.All; request.AutomaticDecompression = DecompressionMethods.All;
request.Method = "POST"; request.Method = "POST";
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary; request.ContentType = "multipart/form-data; boundary=" + boundary;
Stream requestStream = request.GetRequestStream(); var requestStream = request.GetRequestStream();
if (options.TryGetValue("postData", out object postDataObject)) if (options.TryGetValue("postData", out object postDataObject))
{ {
var jsonPostData = (JObject)JsonConvert.DeserializeObject((string)postDataObject); var jsonPostData = (JObject)JsonConvert.DeserializeObject((string)postDataObject);
string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n"; const string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n";
if (jsonPostData != null) if (jsonPostData != null)
{ {
foreach (var data in jsonPostData) foreach (var data in jsonPostData)
{ {
string item = string.Format(formDataTemplate, boundary, data.Key, data.Value); var item = string.Format(formDataTemplate, boundary, data.Key, data.Value);
byte[] itemBytes = Encoding.UTF8.GetBytes(item); var itemBytes = Encoding.UTF8.GetBytes(item);
await requestStream.WriteAsync(itemBytes, 0, itemBytes.Length); await requestStream.WriteAsync(itemBytes);
} }
} }
} }
var imageData = options["imageData"] as string; var imageData = options["imageData"] as string;
var matchingDimensions = options["matchingDimensions"] as bool? ?? false; var matchingDimensions = options["matchingDimensions"] as bool? ?? false;
byte[] fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions); var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions);
string fileFormKey = "file"; const string fileFormKey = "file";
string fileName = "blob"; const string fileName = "blob";
string fileMimeType = "image/png"; const string fileMimeType = "image/png";
string HeaderTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n"; const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n";
string header = string.Format(HeaderTemplate, boundary, fileFormKey, fileName, fileMimeType); var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType);
byte[] headerbytes = Encoding.UTF8.GetBytes(header); var headerBytes = Encoding.UTF8.GetBytes(header);
await requestStream.WriteAsync(headerbytes, 0, headerbytes.Length); await requestStream.WriteAsync(headerBytes);
using (MemoryStream fileStream = new MemoryStream(fileToUpload)) using (var fileStream = new MemoryStream(fileToUpload))
{ {
byte[] buffer = new byte[1024]; var buffer = new byte[1024];
int bytesRead = 0; var bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{ {
await requestStream.WriteAsync(buffer, 0, bytesRead); await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
} }
fileStream.Close(); fileStream.Close();
} }
byte[] newlineBytes = Encoding.UTF8.GetBytes("\r\n"); var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes, 0, newlineBytes.Length); await requestStream.WriteAsync(newlineBytes);
byte[] endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--"); var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes, 0, endBytes.Length); await requestStream.WriteAsync(endBytes);
requestStream.Close(); requestStream.Close();
} }

View File

@@ -15453,8 +15453,7 @@ console.log(`isLinux: ${LINUX}`);
API.getAvatarImages = function (params) { API.getAvatarImages = function (params) {
return this.call(`file/${params.fileId}`, { return this.call(`file/${params.fileId}`, {
method: 'GET', method: 'GET'
params
}).then((json) => { }).then((json) => {
var args = { var args = {
json, json,