mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
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:
@@ -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 }}
|
||||||
|
|||||||
@@ -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};";
|
||||||
}
|
}
|
||||||
|
|||||||
+190
-218
@@ -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;
|
||||||
@@ -30,15 +32,16 @@ namespace VRCX
|
|||||||
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,8 +160,9 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -143,12 +172,12 @@ namespace VRCX
|
|||||||
|
|
||||||
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>();
|
||||||
@@ -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) ||
|
||||||
@@ -192,9 +221,10 @@ 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()) }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -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)
|
private async Task<HttpRequestMessage> BuildImageUploadRequest(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 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)
|
||||||
{
|
{
|
||||||
@@ -357,39 +334,18 @@ namespace VRCX
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
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,15 +353,14 @@ 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)
|
||||||
@@ -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) {
|
||||||
|
|||||||
Vendored
+2
-1
@@ -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 {};
|
||||||
|
|||||||
Reference in New Issue
Block a user