Rework WebApi.cs to use HttpClient (#1584)

* Rewrk webapi.cs to use HttpClient

* only run code signing when secrets are configured

* Workaround to check if secret is present or not

* apply it to all azure steps...

* dont replace cookie container, reuse managed object

* Fix adding MD5

---------

Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
Luc ♥
2026-01-17 22:16:31 +01:00
committed by GitHub
parent 2d812d7c05
commit 56bf69f64e
6 changed files with 215 additions and 243 deletions
+7
View File
@@ -51,13 +51,19 @@ jobs:
${{ runner.os }}-nuget- ${{ runner.os }}-nuget-
- name: Build Cef .NET Application - name: Build Cef .NET Application
run: dotnet build Dotnet/VRCX-Cef.csproj -p:Configuration=Release -p:WarningLevel=0 -p:Platform=x64 -p:PlatformTarget=x64 -p:RestorePackagesConfig=true -t:"Restore;Clean;Build" -m -a x64 --self-contained run: dotnet build Dotnet/VRCX-Cef.csproj -p:Configuration=Release -p:WarningLevel=0 -p:Platform=x64 -p:PlatformTarget=x64 -p:RestorePackagesConfig=true -t:"Restore;Clean;Build" -m -a x64 --self-contained
- name: Check if we want to sign
id: check_sign
run: |
echo "sign=${{ secrets.AZURE_CLIENT_ID != '' }}" >> $GITHUB_OUTPUT
- name: Azure login - name: Azure login
if: steps.check_sign.outputs.sign == 'true'
uses: azure/login@v2 uses: azure/login@v2
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Dotnet executables - name: Sign Dotnet executables
if: steps.check_sign.outputs.sign == 'true'
uses: azure/trusted-signing-action@v0 uses: azure/trusted-signing-action@v0
with: with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -103,6 +109,7 @@ jobs:
mv Installer/VRCX_Setup.exe $file_name mv Installer/VRCX_Setup.exe $file_name
- name: Sign Cef setup - name: Sign Cef setup
uses: azure/trusted-signing-action@v0 uses: azure/trusted-signing-action@v0
if: steps.check_sign.outputs.sign == 'true'
with: with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
+2 -2
View File
@@ -57,10 +57,10 @@ internal static class ImageCache
var cookieString = string.Empty; var cookieString = string.Empty;
if (WebApi.Instance != null && if (WebApi.Instance != null &&
WebApi.Instance._cookieContainer != null && WebApi.Instance.CookieContainer != null &&
uri.Host == "api.vrchat.cloud") uri.Host == "api.vrchat.cloud")
{ {
CookieCollection cookies = WebApi.Instance._cookieContainer.GetCookies(new Uri("https://api.vrchat.cloud")); CookieCollection cookies = WebApi.Instance.CookieContainer.GetCookies(new Uri("https://api.vrchat.cloud"));
foreach (Cookie cookie in cookies) foreach (Cookie cookie in cookies)
cookieString += $"{cookie.Name}={cookie.Value};"; cookieString += $"{cookie.Name}={cookie.Value};";
} }
+200 -228
View File
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -25,20 +27,21 @@ namespace VRCX
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static WebApi Instance; public static WebApi Instance;
public static bool ProxySet; public static bool ProxySet;
public static string ProxyUrl = ""; public static string ProxyUrl = "";
public static IWebProxy Proxy = WebRequest.DefaultWebProxy; public static IWebProxy Proxy = WebRequest.DefaultWebProxy;
public CookieContainer _cookieContainer; public CookieContainer CookieContainer;
private bool _cookieDirty; private bool _cookieDirty;
private Timer _timer; private Timer _timer;
private HttpClient _httpClient;
private SocketsHttpHandler _httpHandler;
static WebApi() static WebApi()
{ {
Instance = new WebApi(); Instance = new WebApi();
ServicePointManager.DefaultConnectionLimit = 10;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
} }
// leave this as public, private makes nodeapi angry // leave this as public, private makes nodeapi angry
@@ -48,7 +51,7 @@ namespace VRCX
if (Instance == null) if (Instance == null)
Instance = this; Instance = this;
#endif #endif
_cookieContainer = new CookieContainer(); CookieContainer = new CookieContainer();
_timer = new Timer(TimerCallback, null, -1, -1); _timer = new Timer(TimerCallback, null, -1, -1);
} }
@@ -67,10 +70,32 @@ namespace VRCX
public void Init() public void Init()
{ {
SetProxy(); SetProxy();
InitializeHttpClient();
LoadCookies(); LoadCookies();
_timer.Change(1000, 1000); _timer.Change(1000, 1000);
} }
private void InitializeHttpClient()
{
_httpHandler = new SocketsHttpHandler
{
CookieContainer = CookieContainer,
UseCookies = true,
AutomaticDecompression = DecompressionMethods.All,
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 10
};
if (ProxySet)
{
_httpHandler.Proxy = Proxy;
_httpHandler.UseProxy = true;
}
_httpClient = new HttpClient(_httpHandler);
_httpClient.DefaultRequestHeaders.Add("User-Agent", Program.Version);
}
private void SetProxy() private void SetProxy()
{ {
if (!string.IsNullOrEmpty(StartupArgs.LaunchArguments.ProxyUrl)) if (!string.IsNullOrEmpty(StartupArgs.LaunchArguments.ProxyUrl))
@@ -95,7 +120,8 @@ namespace VRCX
{ {
VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty); VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty);
VRCXStorage.Instance.Save(); VRCXStorage.Instance.Save();
const string 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
@@ -115,13 +141,15 @@ namespace VRCX
#if !LINUX #if !LINUX
Cef.GetGlobalCookieManager().DeleteCookies(); Cef.GetGlobalCookieManager().DeleteCookies();
#endif #endif
_cookieContainer = new CookieContainer(); CookieContainer = new CookieContainer();
InitializeHttpClient();
SaveCookies(); SaveCookies();
} }
private void LoadCookies() private void LoadCookies()
{ {
SQLite.Instance.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS `cookies` (`key` TEXT PRIMARY KEY, `value` TEXT)"); SQLite.Instance.ExecuteNonQuery(
"CREATE TABLE IF NOT EXISTS `cookies` (`key` TEXT PRIMARY KEY, `value` TEXT)");
var values = SQLite.Instance.Execute("SELECT `value` FROM `cookies` WHERE `key` = @key", var values = SQLite.Instance.Execute("SELECT `value` FROM `cookies` WHERE `key` = @key",
new Dictionary<string, object> new Dictionary<string, object>
{ {
@@ -132,25 +160,26 @@ namespace VRCX
{ {
var item = values[0]; var item = values[0];
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));
InitializeHttpClient();
} }
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() private List<Cookie> GetAllCookies()
{ {
var cookieTable = (Hashtable)_cookieContainer.GetType().InvokeMember("m_domainTable", var cookieTable = (Hashtable)CookieContainer.GetType().InvokeMember("m_domainTable",
BindingFlags.NonPublic | BindingFlags.NonPublic |
BindingFlags.GetField | BindingFlags.GetField |
BindingFlags.Instance, BindingFlags.Instance,
null, null,
_cookieContainer, CookieContainer,
new object[] { }); new object[] { });
var uniqueCookies = new Dictionary<string, Cookie>(); var uniqueCookies = new Dictionary<string, Cookie>();
foreach (var item in cookieTable.Keys) foreach (var item in cookieTable.Keys)
{ {
@@ -165,7 +194,7 @@ namespace VRCX
if (!Uri.TryCreate(address, UriKind.Absolute, out var uri)) if (!Uri.TryCreate(address, UriKind.Absolute, out var uri))
continue; continue;
foreach (Cookie cookie in _cookieContainer.GetCookies(uri)) foreach (Cookie cookie in CookieContainer.GetCookies(uri))
{ {
var key = $"{domain}.{cookie.Name}"; var key = $"{domain}.{cookie.Name}";
if (!uniqueCookies.TryGetValue(key, out var value) || if (!uniqueCookies.TryGetValue(key, out var value) ||
@@ -184,7 +213,7 @@ namespace VRCX
{ {
if (!_cookieDirty) if (!_cookieDirty)
return; return;
try try
{ {
var cookies = GetAllCookies(); var cookies = GetAllCookies();
@@ -192,12 +221,13 @@ namespace VRCX
System.Text.Json.JsonSerializer.Serialize(memoryStream, cookies); System.Text.Json.JsonSerializer.Serialize(memoryStream, cookies);
SQLite.Instance.ExecuteNonQuery( SQLite.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"}, {
{"@value", Convert.ToBase64String(memoryStream.ToArray())} { "@key", "default" },
{ "@value", Convert.ToBase64String(memoryStream.ToArray()) }
} }
); );
_cookieDirty = false; _cookieDirty = false;
} }
catch (Exception e) catch (Exception e)
@@ -219,130 +249,77 @@ namespace VRCX
{ {
using (var stream = new MemoryStream(Convert.FromBase64String(cookies))) using (var stream = new MemoryStream(Convert.FromBase64String(cookies)))
{ {
_cookieContainer = new CookieContainer(); CookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
} }
_cookieDirty = true; // force cookies to be saved for lastUserLoggedIn _cookieDirty = true; // force cookies to be saved for lastUserLoggedIn
} }
private static async Task LegacyImageUpload(HttpWebRequest request, IDictionary<string, object> options) private async Task<HttpRequestMessage> BuildLegacyImageUploadRequest(string url, IDictionary<string, object> options)
{ {
if (ProxySet) var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Proxy = Proxy;
request.AutomaticDecompression = DecompressionMethods.All;
request.Method = "POST";
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary; var content = new MultipartFormDataContent(boundary);
var requestStream = request.GetRequestStream();
if (options.TryGetValue("postData", out var postDataObject)) if (options.TryGetValue("postData", out var postDataObject))
{ {
var postData = new Dictionary<string, string>(); content.Add(new StringContent((string)postDataObject), "data");
postData.Add("data", (string)postDataObject);
const string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n";
foreach (var key in postData.Keys)
{
var item = string.Format(formDataTemplate, boundary, key, postData[key]);
var itemBytes = Encoding.UTF8.GetBytes(item);
await requestStream.WriteAsync(itemBytes);
}
} }
var imageData = options["imageData"] as string; var imageData = options["imageData"] as string;
var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false); var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false);
const string fileFormKey = "image"; var imageContent = new ByteArrayContent(fileToUpload);
const string fileName = "image.png"; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
const string fileMimeType = "image/png"; content.Add(imageContent, "image", "image.png");
const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n";
var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType); request.Content = content;
var headerBytes = Encoding.UTF8.GetBytes(header); return request;
await requestStream.WriteAsync(headerBytes);
using (var fileStream = new MemoryStream(fileToUpload))
{
var buffer = new byte[1024];
var bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
fileStream.Close();
}
var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes);
var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes);
requestStream.Close();
} }
private static async Task UploadFilePut(HttpWebRequest request, IDictionary<string, object> options) private async Task<HttpRequestMessage> BuildUploadFilePutRequest(string url, IDictionary<string, object> options)
{ {
if (ProxySet) var request = new HttpRequestMessage(HttpMethod.Put, url);
request.Proxy = Proxy;
request.AutomaticDecompression = DecompressionMethods.All;
request.Method = "PUT";
request.ContentType = options["fileMIME"] as string;
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; var content = new ByteArrayContent(sentData);
await using var sendStream = request.GetRequestStream(); content.Headers.ContentType = new MediaTypeHeaderValue(options["fileMIME"] as string);
await sendStream.WriteAsync(sentData); if (options.TryGetValue("fileMD5", out var fileMd5))
sendStream.Close(); content.Headers.ContentMD5 = Convert.FromBase64String(fileMd5 as string);
request.Content = content;
return request;
} }
private static async Task ImageUpload(HttpWebRequest request, IDictionary<string, object> options)
{
if (ProxySet)
request.Proxy = Proxy;
request.AutomaticDecompression = DecompressionMethods.All; private async Task<HttpRequestMessage> BuildImageUploadRequest(string url, IDictionary<string, object> options)
request.Method = "POST"; {
var request = new HttpRequestMessage(HttpMethod.Post, url);
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary; var content = new MultipartFormDataContent(boundary);
var requestStream = request.GetRequestStream();
if (options.TryGetValue("postData", out object postDataObject)) if (options.TryGetValue("postData", out var postDataObject))
{ {
var jsonPostData = (JObject)JsonConvert.DeserializeObject((string)postDataObject); var jsonPostData = (JObject)JsonConvert.DeserializeObject((string)postDataObject);
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)
{ {
var item = string.Format(formDataTemplate, boundary, data.Key, data.Value); content.Add(new StringContent(data.Value?.ToString() ?? string.Empty), data.Key);
var itemBytes = Encoding.UTF8.GetBytes(item);
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;
var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions); var fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions);
const string fileFormKey = "file"; var imageContent = new ByteArrayContent(fileToUpload);
const string fileName = "blob"; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
const string fileMimeType = "image/png"; content.Add(imageContent, "file", "blob");
const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n";
var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType); request.Content = content;
var headerBytes = Encoding.UTF8.GetBytes(header); return request;
await requestStream.WriteAsync(headerBytes);
using (var fileStream = new MemoryStream(fileToUpload))
{
var buffer = new byte[1024];
var bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
fileStream.Close();
}
var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes);
var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes);
requestStream.Close();
} }
private static async Task PrintImageUpload(HttpWebRequest request, IDictionary<string, object> options) private async Task<HttpRequestMessage> BuildPrintImageUploadRequest(string url, IDictionary<string, object> options)
{ {
if (options.TryGetValue("cropWhiteBorder", out var cropWhiteBorder) && (bool)cropWhiteBorder) if (options.TryGetValue("cropWhiteBorder", out var cropWhiteBorder) && (bool)cropWhiteBorder)
{ {
@@ -356,40 +333,19 @@ namespace VRCX
options["imageData"] = Convert.ToBase64String(ms2.ToArray()); options["imageData"] = Convert.ToBase64String(ms2.ToArray());
} }
} }
if (ProxySet)
request.Proxy = Proxy;
request.AutomaticDecompression = DecompressionMethods.All; var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Method = "POST";
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary; var content = new MultipartFormDataContent(boundary);
var requestStream = request.GetRequestStream();
var imageData = options["imageData"] as string; var imageData = options["imageData"] as string;
var fileToUpload = Program.AppApiInstance.ResizePrintImage(Convert.FromBase64String(imageData)); var fileToUpload = Program.AppApiInstance.ResizePrintImage(Convert.FromBase64String(imageData));
const string fileFormKey = "image";
const string fileName = "image"; var imageContent = new ByteArrayContent(fileToUpload);
const string fileMimeType = "image/png"; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var fileSize = fileToUpload.Length; imageContent.Headers.ContentLength = fileToUpload.Length;
const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\nContent-Length: {4}\r\n"; content.Add(imageContent, "image", "image");
var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType, fileSize);
var headerBytes = Encoding.UTF8.GetBytes(header);
await requestStream.WriteAsync(headerBytes);
var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes);
using (var fileStream = new MemoryStream(fileToUpload))
{
var buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
fileStream.Close();
}
const string textContentType = "text/plain; charset=utf-8";
const string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\nContent-Type: {2}\r\nContent-Length: {3}\r\n\r\n{4}\r\n";
await requestStream.WriteAsync(newlineBytes);
if (options.TryGetValue("postData", out var postDataObject)) if (options.TryGetValue("postData", out var postDataObject))
{ {
var jsonPostData = JsonConvert.DeserializeObject<Dictionary<string, string>>(postDataObject.ToString()); var jsonPostData = JsonConvert.DeserializeObject<Dictionary<string, string>>(postDataObject.ToString());
@@ -397,17 +353,16 @@ namespace VRCX
{ {
foreach (var (key, value) in jsonPostData) foreach (var (key, value) in jsonPostData)
{ {
var section = string.Format(formDataTemplate, boundary, key, textContentType, value.Length, value); var stringContent = new StringContent(value, Encoding.UTF8, "text/plain");
var sectionBytes = Encoding.UTF8.GetBytes(section); content.Add(stringContent, key);
await requestStream.WriteAsync(sectionBytes);
} }
} }
} }
var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes); request.Content = content;
requestStream.Close(); return request;
} }
public async Task<string> ExecuteJson(string options) public async Task<string> ExecuteJson(string options)
{ {
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(options); var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(options);
@@ -423,112 +378,112 @@ namespace VRCX
{ {
try try
{ {
// TODO: switch to HttpClient var url = (string)options["url"];
#pragma warning disable SYSLIB0014 // Type or member is obsolete HttpRequestMessage request;
var request = WebRequest.CreateHttp((string)options["url"]);
#pragma warning restore SYSLIB0014 // Type or member is obsolete
if (ProxySet)
request.Proxy = Proxy;
request.CookieContainer = _cookieContainer; // Handle special upload types
request.KeepAlive = true; if (options.TryGetValue("uploadImageLegacy", out _))
request.UserAgent = Program.Version; {
request.AutomaticDecompression = DecompressionMethods.All; request = await BuildLegacyImageUploadRequest(url, options);
}
else if (options.TryGetValue("uploadFilePUT", out _))
{
request = await BuildUploadFilePutRequest(url, options);
}
else if (options.TryGetValue("uploadImage", out _))
{
request = await BuildImageUploadRequest(url, options);
}
else if (options.TryGetValue("uploadImagePrint", out _))
{
request = await BuildPrintImageUploadRequest(url, options);
}
else
{
// Standard request
var httpMethod = HttpMethod.Get;
if (options.TryGetValue("method", out var methodObj))
{
httpMethod = HttpMethod.Parse(methodObj.ToString());
}
request = new HttpRequestMessage(httpMethod, url);
// Handle body for non-GET requests
if (httpMethod != HttpMethod.Get && options.TryGetValue("body", out var body))
{
var bodyContent = new StringContent((string)body, Encoding.UTF8);
// Set content type if specified in headers
if (options.TryGetValue("headers", out var headersObj))
{
var headersDict = ParseHeaders(headersObj);
if (headersDict.TryGetValue("Content-Type", out var contentType))
{
bodyContent.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
}
}
request.Content = bodyContent;
}
}
// Apply headers
if (options.TryGetValue("headers", out var headers)) if (options.TryGetValue("headers", out var headers))
{ {
Dictionary<string, string> headersDict; var headersDict = ParseHeaders(headers);
if (headers.GetType() == typeof(JObject))
{
headersDict = ((JObject)headers).ToObject<Dictionary<string, string>>();
}
else
{
var headersKvp = (IEnumerable<KeyValuePair<string, object>>)headers;
headersDict = new Dictionary<string, string>();
foreach (var (key, value) in headersKvp)
headersDict.Add(key, value.ToString());
}
foreach (var (key, value) in headersDict) foreach (var (key, value) in headersDict)
{ {
if (string.Compare(key, "Content-Type", StringComparison.OrdinalIgnoreCase) == 0) // Skip Content-Type as it's set on content
request.ContentType = value; if (string.Equals(key, "Content-Type", StringComparison.OrdinalIgnoreCase))
else if (string.Compare(key, "Referer", StringComparison.OrdinalIgnoreCase) == 0) continue;
request.Referer = value;
if (string.Equals(key, "Referer", StringComparison.OrdinalIgnoreCase))
{
request.Headers.Referrer = new Uri(value);
}
else else
request.Headers.Add(key, value); {
request.Headers.TryAddWithoutValidation(key, value);
}
} }
} }
if (options.TryGetValue("method", out var method)) using var response = await _httpClient.SendAsync(request);
{
request.Method = (string)method;
if (string.Compare(request.Method, "GET", StringComparison.OrdinalIgnoreCase) != 0 &&
options.TryGetValue("body", out var body))
{
await using var bodyStream = await request.GetRequestStreamAsync();
await using var streamWriter = new StreamWriter(bodyStream);
await streamWriter.WriteAsync((string)body);
}
}
if (options.TryGetValue("uploadImage", out _)) // Check if cookies were modified
await ImageUpload(request, options); if (response.Headers.Contains("Set-Cookie"))
if (options.TryGetValue("uploadFilePUT", out _))
await UploadFilePut(request, options);
if (options.TryGetValue("uploadImageLegacy", out _))
await LegacyImageUpload(request, options);
if (options.TryGetValue("uploadImagePrint", out _))
await PrintImageUpload(request, options);
using var response = await request.GetResponseAsync() as HttpWebResponse;
if (response?.Headers["Set-Cookie"] != null)
_cookieDirty = true; _cookieDirty = true;
await using var imageStream = response.GetResponseStream(); var contentTypeResponse = response.Content.Headers.ContentType?.MediaType ?? string.Empty;
using var streamReader = new StreamReader(imageStream);
if (response.ContentType.Contains("image/") || if (contentTypeResponse.Contains("image/") || contentTypeResponse.Contains("application/octet-stream"))
response.ContentType.Contains("application/octet-stream"))
{ {
// base64 response data for image // Base64 response data for image
using var memoryStream = new MemoryStream(); var imageBytes = await response.Content.ReadAsByteArrayAsync();
await imageStream.CopyToAsync(memoryStream);
return new Tuple<int, string>( return new Tuple<int, string>(
(int)response.StatusCode, (int)response.StatusCode,
$"data:image/png;base64,{Convert.ToBase64String(memoryStream.ToArray())}" $"data:image/png;base64,{Convert.ToBase64String(imageBytes)}"
); );
} }
var responseBody = await response.Content.ReadAsStringAsync();
return new Tuple<int, string>( return new Tuple<int, string>(
(int)response.StatusCode, (int)response.StatusCode,
await streamReader.ReadToEndAsync() responseBody
); );
} }
catch (WebException webException) catch (HttpRequestException httpException)
{ {
if (webException.Response is HttpWebResponse response) if (httpException.InnerException != null)
{ Logger.Error($"{httpException.Message} | {httpException.InnerException}");
if (response.Headers["Set-Cookie"] != null)
_cookieDirty = true;
await using var stream = response.GetResponseStream(); // Try to get status code if available
using var streamReader = new StreamReader(stream); var statusCode = httpException.StatusCode.HasValue ? (int)httpException.StatusCode.Value : -1;
return new Tuple<int, string>(
(int)response.StatusCode,
await streamReader.ReadToEndAsync()
);
}
if (webException.InnerException != null)
Logger.Error($"{webException.Message} | {webException.InnerException}");
return new Tuple<int, string>( return new Tuple<int, string>(
-1, statusCode,
webException.Message httpException.Message
); );
} }
catch (Exception e) catch (Exception e)
@@ -542,5 +497,22 @@ namespace VRCX
); );
} }
} }
private static Dictionary<string, string> ParseHeaders(object headers)
{
Dictionary<string, string> headersDict;
if (headers.GetType() == typeof(JObject))
{
headersDict = ((JObject)headers).ToObject<Dictionary<string, string>>();
}
else
{
var headersKvp = (IEnumerable<KeyValuePair<string, object>>)headers;
headersDict = new Dictionary<string, string>();
foreach (var (key, value) in headersKvp)
headersDict.Add(key, value.ToString());
}
return headersDict;
}
} }
} }
@@ -199,9 +199,7 @@
uploadFilePUT: true, uploadFilePUT: true,
fileData: avatarImage.value.base64File, fileData: avatarImage.value.base64File,
fileMIME: 'image/png', fileMIME: 'image/png',
headers: { fileMD5: avatarImage.value.fileMd5
'Content-MD5': avatarImage.value.fileMd5
}
}); });
if (json.status !== 200) { if (json.status !== 200) {
@@ -252,9 +250,7 @@
uploadFilePUT: true, uploadFilePUT: true,
fileData: avatarImage.value.base64SignatureFile, fileData: avatarImage.value.base64SignatureFile,
fileMIME: 'application/x-rsync-signature', fileMIME: 'application/x-rsync-signature',
headers: { fileMD5: avatarImage.value.signatureMd5
'Content-MD5': avatarImage.value.signatureMd5
}
}); });
if (json.status !== 200) { if (json.status !== 200) {
@@ -192,9 +192,7 @@
uploadFilePUT: true, uploadFilePUT: true,
fileData: worldImage.value.base64File, fileData: worldImage.value.base64File,
fileMIME: 'image/png', fileMIME: 'image/png',
headers: { fileMD5: worldImage.value.fileMd5
'Content-MD5': worldImage.value.fileMd5
}
}); });
if (json.status !== 200) { if (json.status !== 200) {
@@ -245,9 +243,7 @@
uploadFilePUT: true, uploadFilePUT: true,
fileData: worldImage.value.base64SignatureFile, fileData: worldImage.value.base64SignatureFile,
fileMIME: 'application/x-rsync-signature', fileMIME: 'application/x-rsync-signature',
headers: { fileMD5: worldImage.value.signatureMd5
'Content-MD5': worldImage.value.signatureMd5
}
}); });
if (json.status !== 200) { if (json.status !== 200) {
+2 -1
View File
@@ -408,10 +408,11 @@ declare global {
uploadFilePUT?: boolean; uploadFilePUT?: boolean;
fileData?: string; fileData?: string;
fileMIME?: string; fileMIME?: string;
fileMD5?: string;
headers?: Record<string, string>; headers?: Record<string, string>;
data?: any; data?: any;
}): Promise<{ status: number; data: string }>; }): Promise<{ status: number; data: string }>;
}; };
} }
export { }; export {};