2022-05-12 17:29:34 +08:00
|
|
|
using System.Net;
|
2022-05-11 19:07:21 +08:00
|
|
|
using System.Net.Http;
|
2022-05-11 00:20:57 +08:00
|
|
|
using Jint;
|
|
|
|
using Jint.Native;
|
2022-05-12 17:29:34 +08:00
|
|
|
using Jint.Native.Array;
|
2022-05-11 00:20:57 +08:00
|
|
|
using Jint.Native.Function;
|
2022-05-12 17:29:34 +08:00
|
|
|
using Jint.Native.Object;
|
|
|
|
using Jint.Runtime;
|
2022-05-11 00:20:57 +08:00
|
|
|
|
|
|
|
namespace BililiveRecorder.Core.Scripting.Runtime
|
|
|
|
{
|
|
|
|
public class JintFetchSync : FunctionInstance
|
|
|
|
{
|
2022-05-12 17:29:34 +08:00
|
|
|
private static readonly JsString functionName = new JsString("fetchSync");
|
2022-05-11 00:20:57 +08:00
|
|
|
|
|
|
|
public JintFetchSync(Engine engine) : base(engine, engine.Realm, functionName)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override JsValue Call(JsValue thisObject, JsValue[] arguments)
|
|
|
|
{
|
2022-05-12 17:29:34 +08:00
|
|
|
if (arguments.Length == 0)
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "1 argument required, but only 0 present.");
|
2022-05-11 19:07:21 +08:00
|
|
|
|
2022-05-12 17:29:34 +08:00
|
|
|
if (arguments[0] is not JsString urlString)
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "Only url string is supported as the 1st argument.");
|
|
|
|
|
|
|
|
ObjectInstance? initObject = null;
|
|
|
|
if (arguments.Length > 1)
|
|
|
|
initObject = arguments[1] is not ObjectInstance arg1
|
|
|
|
? throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "The provided value is not of type 'RequestInit'.")
|
|
|
|
: arg1;
|
|
|
|
|
|
|
|
HttpClientHandler handler = new HttpClientHandler();
|
|
|
|
HttpClient? httpClient = new HttpClient(handler);
|
|
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Get, urlString.ToString());
|
|
|
|
bool throwOnRedirect = false;
|
|
|
|
|
|
|
|
if (initObject is not null)
|
|
|
|
{
|
|
|
|
foreach (var kv in initObject.GetOwnProperties())
|
|
|
|
{
|
|
|
|
var key = kv.Key;
|
|
|
|
var value = kv.Value;
|
|
|
|
|
|
|
|
if (!key.IsString())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (key.AsString())
|
|
|
|
{
|
|
|
|
case "body":
|
|
|
|
this.SetRequestBody(requestMessage, value.Value);
|
|
|
|
break;
|
|
|
|
case "headers":
|
|
|
|
this.SetRequestHeader(requestMessage, value.Value);
|
|
|
|
break;
|
|
|
|
case "method":
|
|
|
|
this.SetRequestMethod(requestMessage, value.Value);
|
|
|
|
break;
|
|
|
|
case "redirect":
|
|
|
|
{
|
|
|
|
var redirect = value.Value;
|
|
|
|
if (redirect is JsNull or JsUndefined)
|
|
|
|
break;
|
|
|
|
switch (redirect.ToString())
|
|
|
|
{
|
|
|
|
case "follow":
|
|
|
|
handler.AllowAutoRedirect = true;
|
|
|
|
break;
|
|
|
|
case "manual":
|
|
|
|
handler.AllowAutoRedirect = false;
|
|
|
|
break;
|
|
|
|
case "error":
|
|
|
|
handler.AllowAutoRedirect = false;
|
|
|
|
throwOnRedirect = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, $"'{redirect}' is not a valid value for 'redirect'.");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "referrer":
|
|
|
|
{
|
|
|
|
var referrer = value.Value;
|
|
|
|
if (referrer is JsNull or JsUndefined)
|
|
|
|
break;
|
|
|
|
requestMessage.Headers.Referrer = new System.Uri(referrer.ToString());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "cache":
|
|
|
|
case "credentials":
|
|
|
|
case "integrity":
|
|
|
|
case "keepalive":
|
|
|
|
case "mode":
|
|
|
|
case "referrerPolicy":
|
|
|
|
case "signal":
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
|
|
|
|
#pragma warning disable VSTHRD104 // Offer async methods
|
|
|
|
var resp = httpClient.SendAsync(requestMessage).Result;
|
|
|
|
|
|
|
|
if (throwOnRedirect && (resp.StatusCode is (HttpStatusCode)301 or (HttpStatusCode)302 or (HttpStatusCode)303 or (HttpStatusCode)307 or (HttpStatusCode)308))
|
2022-05-11 19:07:21 +08:00
|
|
|
{
|
2022-05-12 17:29:34 +08:00
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, $"'Failed to fetch, Status code: {(int)resp.StatusCode}.");
|
|
|
|
}
|
|
|
|
|
|
|
|
var respString = resp.Content.ReadAsStringAsync().Result;
|
|
|
|
#pragma warning restore VSTHRD104 // Offer async methods
|
|
|
|
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
|
|
|
|
|
|
|
var respHeaders = new ObjectInstance(this._engine);
|
|
|
|
foreach (var respHeader in resp.Headers)
|
|
|
|
respHeaders.Set(respHeader.Key, string.Join(", ", respHeader.Value));
|
|
|
|
|
|
|
|
var result = new ObjectInstance(this._engine);
|
|
|
|
result.Set("body", respString);
|
|
|
|
result.Set("headers", respHeaders);
|
|
|
|
result.Set("ok", resp.IsSuccessStatusCode);
|
|
|
|
result.Set("status", (int)resp.StatusCode);
|
|
|
|
result.Set("statusText", resp.ReasonPhrase);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void SetRequestMethod(HttpRequestMessage requestMessage, JsValue value)
|
|
|
|
{
|
|
|
|
if (value is JsNull or JsUndefined)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var method = value.ToString();
|
|
|
|
requestMessage.Method = method.ToUpperInvariant() switch
|
|
|
|
{
|
|
|
|
"HEAD" => HttpMethod.Head,
|
|
|
|
"GET" => HttpMethod.Get,
|
|
|
|
"POST" => HttpMethod.Post,
|
|
|
|
"PUT" => HttpMethod.Put,
|
|
|
|
"DELETE" => HttpMethod.Delete,
|
|
|
|
"OPTIONS" => HttpMethod.Options,
|
|
|
|
"TRACE" => HttpMethod.Trace,
|
|
|
|
_ => new HttpMethod(method),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private void SetRequestHeader(HttpRequestMessage requestMessage, JsValue value)
|
|
|
|
{
|
|
|
|
if (value is JsNull or JsUndefined)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (value is ObjectInstance objectInstance)
|
|
|
|
{
|
|
|
|
foreach (var header in objectInstance.GetOwnProperties())
|
|
|
|
{
|
|
|
|
var headerName = header.Key.ToString();
|
|
|
|
var headerValue = header.Value.Value.ToString();
|
|
|
|
|
|
|
|
requestMessage.Headers.Remove(headerName);
|
|
|
|
requestMessage.Headers.TryAddWithoutValidation(headerName, headerValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (value is ArrayInstance arrayInstance)
|
|
|
|
{
|
|
|
|
foreach (ArrayInstance header in arrayInstance)
|
|
|
|
{
|
|
|
|
if (header.Length != 2)
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "The header object must contain exactly two elements.");
|
|
|
|
|
|
|
|
var headerName = header[0].ToString();
|
|
|
|
var headerValue = header[1].ToString();
|
|
|
|
|
|
|
|
requestMessage.Headers.Remove(headerName);
|
|
|
|
requestMessage.Headers.TryAddWithoutValidation(headerName, headerValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "Only object or array is supported for 'header'.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void SetRequestBody(HttpRequestMessage requestMessage, JsValue value)
|
|
|
|
{
|
|
|
|
if (value is JsNull or JsUndefined)
|
|
|
|
return;
|
2022-05-11 19:07:21 +08:00
|
|
|
|
2022-05-12 17:29:34 +08:00
|
|
|
if (value is not JsString jsString)
|
|
|
|
throw new JavaScriptException(this._engine.Realm.Intrinsics.Error, "Only string is supported for 'body'.");
|
2022-05-11 19:07:21 +08:00
|
|
|
|
2022-05-12 17:29:34 +08:00
|
|
|
requestMessage.Content = new StringContent(jsString.ToString());
|
2022-05-11 00:20:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|