CEF: redirect or create new `CefResourceHandler` for `file://` request to use `https://' instead - chromium

CEF treats all protocol relative URLs as file:// request in my local app, and I want to intercept and change it to point to https:// instead.
I have overloaded following two methods from CefRequestHandler:
virtual CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request);
virtual cef_return_value_t OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefRequestCallback> callback);
Where cef_return_value_t is defined as:
typedef enum {
///
// Cancel immediately.
///
RV_CANCEL = 0,
///
// Continue immediately.
///
RV_CONTINUE,
///
// Continue asynchronously (usually via a callback).
///
RV_CONTINUE_ASYNC,
} cef_return_value_t;
In OnBeforeResourceLoad, I can only ask CEF to either cancel or go-ahead with the request. So, I am returning RV_CONTINUE.
And in GetResourceHandler, I can provide a custom CefResourceHandler.
But problem is that, I could not find a CEF api to help me create my own https:// request handler.

In the file cef/tests/ceftests/navigation_unittest.cc file following class can be used as reference to implement a RedirectRequestHandler. You need to modify the internals and can use either of http:302 or redirectUrl to achieve redirection.
class RedirectSchemeHandler : public CefResourceHandler {
public:
RedirectSchemeHandler() : offset_(0), status_(0) {}
bool ProcessRequest(CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO));
std::string url = request->GetURL();
if (url == kRNav1) {
// Redirect using HTTP 302
g_got_nav1_request = true;
status_ = 302;
location_ = kRNav2;
content_ = "<html><body>Redirected Nav1</body></html>";
} else if (url == kRNav3) {
// Redirect using redirectUrl
g_got_nav3_request = true;
status_ = -1;
location_ = kRNav4;
content_ = "<html><body>Redirected Nav3</body></html>";
} else if (url == kRNav4) {
g_got_nav4_request = true;
status_ = 200;
content_ = "<html><body>Nav4</body></html>";
}
if (status_ != 0) {
callback->Continue();
return true;
} else {
g_got_invalid_request = true;
return false;
}
}
void GetResponseHeaders(CefRefPtr<CefResponse> response,
int64& response_length,
CefString& redirectUrl) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO));
EXPECT_NE(status_, 0);
response->SetStatus(status_);
response->SetMimeType("text/html");
response_length = content_.size();
if (status_ == 302) {
// Redirect using HTTP 302
EXPECT_GT(location_.size(), static_cast<size_t>(0));
response->SetStatusText("Found");
CefResponse::HeaderMap headers;
response->GetHeaderMap(headers);
headers.insert(std::make_pair("Location", location_));
response->SetHeaderMap(headers);
} else if (status_ == -1) {
// Rdirect using redirectUrl
EXPECT_GT(location_.size(), static_cast<size_t>(0));
redirectUrl = location_;
}
}
void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); }
bool ReadResponse(void* data_out,
int bytes_to_read,
int& bytes_read,
CefRefPtr<CefCallback> callback) override {
EXPECT_TRUE(CefCurrentlyOn(TID_IO));
size_t size = content_.size();
if (offset_ < size) {
int transfer_size =
std::min(bytes_to_read, static_cast<int>(size - offset_));
memcpy(data_out, content_.c_str() + offset_, transfer_size);
offset_ += transfer_size;
bytes_read = transfer_size;
return true;
}
return false;
}
protected:
std::string content_;
size_t offset_;
int status_;
std::string location_;
IMPLEMENT_REFCOUNTING(RedirectSchemeHandler);
};
And an instance of RedirectSchemeHandler should be returned from following callback:
virtual CefRefPtr<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request)
{
return new RedirectSchemeHandler();
}

Related

Returning css or 404 from a netcoreapp3.1 controller action

I want to make a controller action to return css.
The browser will only user the CSS if the response type is text/css.
Here is my action.
[HttpGet("{tenant}/stylesheet")]
[Produces("text/css")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<ActionResult<BrandingDTO>> Stylesheet(string tenant)
{
Branding b = dbContext.Branding.SingleOrDefault(z => z.Tenant == tenant);
if (t == null)
{
return NotFound(); // Actually returns 406 unacceptable.
}
return Ok(Content($#"
nav.navbar {{
background-color: {b.PrimaryBrandingColour};
}}
", "text/css"));
}
In the not found case I don't need the response to be application/problem+json, but it happens that that's what it is.
I've had to create a custom output formatter.
/// <summary>
/// https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-6.0
/// </summary>
internal class CssOutputFormatter : TextOutputFormatter
{
public CssOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/css"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type? type)
{
return type == typeof(ContentResult) || type == typeof(string);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
string buffer = context.Object.ToString();
ContentResult content = context.Object as ContentResult;
if(content != null)
{
buffer = content.Content;
}
await context.HttpContext.Response.WriteAsync(buffer, selectedEncoding);
}
}
The problem is that in the not found case the respose is actually 406 not acceptable; I would expect 404 and an empty body of type text/css or a non-empty body of type application/problem+json.
Can this work?
Or should I make do with the response being either 202 with text/css else broken?
Hope this will help you.
HttpResponseMessage message=new HttpResponseMessage(HttpStatusCode.OK);
string color = "black";
string content = $"nav.navbar {{background-color: {color};}}";
message.Content=new StringContent(content, System.Text.Encoding.UTF8, "text/plain");
return Ok(message);

Getting ActionContext of an action from another

Can I get an ActionContext or ActionDescriptor or something that can describe a specific action based on a route name ?
Having the following controller.
public class Ctrl : ControllerBase
{
[HttpGet]
public ActionResult Get() { ... }
[HttpGet("{id}", Name = "GetUser")]
public ActionResult Get(int id) { ... }
}
What I want to do is when "Get" is invoked, to be able to have access to "GetUser" metadata like verb, route parameters , etc
Something like
ActionContext/Description/Metadata info = somerService.Get(routeName : "GetUser")
or
ActionContext/Description/Metadata info = somerService["GetUser"];
something in this idea.
There is a nuget package, AspNetCore.RouteAnalyzer, that may provide what you want. It exposes strings for the HTTP verb, mvc area, path and invocation.
Internally it uses ActionDescriptorCollectionProvider to get at that information:
List<RouteInformation> ret = new List<RouteInformation>();
var routes = m_actionDescriptorCollectionProvider.ActionDescriptors.Items;
foreach (ActionDescriptor _e in routes)
{
RouteInformation info = new RouteInformation();
// Area
if (_e.RouteValues.ContainsKey("area"))
{
info.Area = _e.RouteValues["area"];
}
// Path and Invocation of Razor Pages
if (_e is PageActionDescriptor)
{
var e = _e as PageActionDescriptor;
info.Path = e.ViewEnginePath;
info.Invocation = e.RelativePath;
}
// Path of Route Attribute
if (_e.AttributeRouteInfo != null)
{
var e = _e;
info.Path = $"/{e.AttributeRouteInfo.Template}";
}
// Path and Invocation of Controller/Action
if (_e is ControllerActionDescriptor)
{
var e = _e as ControllerActionDescriptor;
if (info.Path == "")
{
info.Path = $"/{e.ControllerName}/{e.ActionName}";
}
info.Invocation = $"{e.ControllerName}Controller.{e.ActionName}";
}
// Extract HTTP Verb
if (_e.ActionConstraints != null && _e.ActionConstraints.Select(t => t.GetType()).Contains(typeof(HttpMethodActionConstraint)))
{
HttpMethodActionConstraint httpMethodAction =
_e.ActionConstraints.FirstOrDefault(a => a.GetType() == typeof(HttpMethodActionConstraint)) as HttpMethodActionConstraint;
if(httpMethodAction != null)
{
info.HttpMethod = string.Join(",", httpMethodAction.HttpMethods);
}
}
// Special controller path
if (info.Path == "/RouteAnalyzer_Main/ShowAllRoutes")
{
info.Path = RouteAnalyzerRouteBuilderExtensions.RouteAnalyzerUrlPath;
}
// Additional information of invocation
info.Invocation += $" ({_e.DisplayName})";
// Generating List
ret.Add(info);
}
// Result
return ret;
}
Try this:
// Initialize via constructor dependency injection
private readonly IActionDescriptorCollectionProvider _provider;
var info = _provider.ActionDescriptors.Items.Where(x => x.AttributeRouteInfo.Name == "GetUser");

What is the best possible way to send custom error responses in .net core web api

I'm making a .net Core WebApi using .Net Core 2.2. The API is ready but the failure message and response is where I'm stuck at.
Right now, I'm getting respose like below
json
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
I created a json formatter class and wrote the below code
public class SuperJsonOutputFormatter : JsonOutputFormatter
{
public SuperJsonOutputFormatter(
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool) : base(serializerSettings, charPool)
{
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (selectedEncoding == null)
throw new ArgumentNullException(nameof(selectedEncoding));
using (TextWriter writer =
context.WriterFactory(
context.HttpContext.Response.Body,
selectedEncoding))
{
var rewrittenValue = new
{
resultCode = context.HttpContext.Response.StatusCode,
resultMessage =
((HttpStatusCode)context.HttpContext.Response.StatusCode)
.ToString(),
result = context.Object
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
}
I expect all the error codes to be sent as generic error messages like the JSON below.
FOR STATUS OKAY:
{
"status" : True,
"error" : null,
"data" : {
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
}
}
FOR OTHER STATUS LIKE 404, 500, 400, 204
{
"status" : False,
"error" : {
"error code" : 404,
"error description" : Not Found
},
"data" : null
}
I expect all the error codes to be sent as generic error messages like the JSON below
You're almost there. What you need to do is enabling your SuperJsonOutputFormatter.
A Little Change to Your Formatter
Firstly, your formatter didn't return a json with the same schema as you want. So I create a dummy class to hold the information for error code and error description:
public class ErrorDescription{
public ErrorDescription(HttpStatusCode statusCode)
{
this.Code = (int)statusCode;
this.Description = statusCode.ToString();
}
[JsonProperty("error code")]
public int Code {get;set;}
[JsonProperty("error description")]
public string Description {get;set;}
}
And change your WriteResponseBodyAsync() method as below:
...
using (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) {
var statusCode = context.HttpContext.Response.StatusCode;
var rewrittenValue = new {
status = IsSucceeded(statusCode),
error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
data = context.Object,
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
Here the IsSucceeded(statusCode) is a simple helper method that you can custom as you need:
private bool IsSucceeded(int statusCode){
// I don't think 204 indicates that's an error.
// However, you could comment out it if you like
if(statusCode >= 400 /* || statusCode==204 */ ) { return false; }
return true;
}
Enable your Formatter
Secondly, to enable your custom Formatter, you have two approaches: One way is to register it as an global Formatter, the other way is to enable it for particular Controller or Action. Personally, I believe the 2nd way is better. So I create a Action Filter to enable your formatter.
Here's an implementation of the Filter that enables your custom formatter dynamically:
public class SuperJsonOutputFormatterFilter : IAsyncActionFilter{
private readonly SuperJsonOutputFormatter _formatter;
// inject your SuperJsonOutputFormatter service
public SuperJsonOutputFormatterFilter(SuperJsonOutputFormatter formatter){
this._formatter = formatter;
}
// a helper method that provides an ObjectResult wrapper over the raw object
private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj){
var wrapper = new ObjectResult(obj);
wrapper.Formatters.Add(this._formatter);
context.Result= wrapper;
return wrapper;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ActionExecutedContext resultContext = await next();
// in case we get a 500
if(resultContext.Exception != null && ! resultContext.ExceptionHandled){
var ewrapper = this.WrapObjectResult(resultContext, new {});
ewrapper.StatusCode = (int) HttpStatusCode.InternalServerError;
resultContext.ExceptionHandled = true;
return;
}
else {
switch(resultContext.Result){
case BadRequestObjectResult b : // 400 with an object
var bwrapper=this.WrapObjectResult(resultContext,b.Value);
bwrapper.StatusCode = b.StatusCode;
break;
case NotFoundObjectResult n : // 404 with an object
var nwrapper=this.WrapObjectResult(resultContext,n.Value);
nwrapper.StatusCode = n.StatusCode;
break;
case ObjectResult o : // plain object
this.WrapObjectResult(resultContext,o.Value);
break;
case JsonResult j : // plain json
this.WrapObjectResult(resultContext,j.Value);
break;
case StatusCodeResult s: // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case
var swrapper = this.WrapObjectResult(resultContext, new {});
swrapper.StatusCode = s.StatusCode;
break;
}
}
}
}
And don't forget to register your formatter as a service :
services.AddScoped<SuperJsonOutputFormatter>();
Finally, when you want to enable your formatter, just add a [TypeFilter(typeof(SuperJsonOutputFormatterFilter))] annotation for the controller or action.
Demo
Let's create an action method for Test:
[TypeFilter(typeof(SuperJsonOutputFormatterFilter))]
public IActionResult Test(int status)
{
// test json result(200)
if(status == 200){ return Json(new { Id = 1, }); }
// test 400 object result
else if(status == 400){ return BadRequest( new {}); }
// test 404 object result
else if(status == 404){ return NotFound(new { Id = 1, }); }
// test exception
else if(status == 500){ throw new Exception("unexpected exception"); }
// test status code result
else if(status == 204){ return new StatusCodeResult(204); }
// test normal object result(200)
var raw = new ObjectResult(new XModel{
empId=1999,
empName = "Conroy, Deborah",
enrollmentStatus=true,
primaryFingerprintScore=65,
secondaryFingerprintScore=60,
primaryFingerprint = null,
secondaryFingerprint= null,
primaryFingerprintType=null,
secondaryFingerprintType=null
});
return raw;
}
Screenshot:

.net core - How to return 403 on AuthorizationHandler?

I implemented my custom AuthorizationHandler.
On that i check i the user can resolved and is active.
If the user isn't active then i would like to return an 403 status.
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserRequirement requirement)
{
var userId = context.User.FindFirstValue( ClaimTypes.NameIdentifier );
if (userId != null)
{
var user = await _userManager.GetUserAsync(userId);
if (user != null)
{
_httpContextAccessor.HttpContext.AddCurrentUser(user);
if (user.Active)
{
context.Succeed(requirement);
return;
}
else
{
_log.LogWarning(string.Format("User ´{1}´ with id: ´{0} isn't active", userId, user.UserName), null);
}
}
else
{
_log.LogWarning(string.Format("Can't find user with id: ´{0}´", userId), null);
}
} else
{
_log.LogWarning(string.Format("Can't get user id from token"), null);
}
context.Fail();
var response = _httpContextAccessor.HttpContext.Response;
response.StatusCode = 403;
}
But i receive a 401. Can you please help me?
Could you check that on the end of your function? I'm using that in my custom middleware to rewrite status code to 401 in some cases but in your scenario should also work
var filterContext = context.Resource as AuthorizationFilterContext;
var response = filterContext?.HttpContext.Response;
response?.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = 403;
//await response.Body.WriteAsync(message, 0, message.Length); only when you want to pass a message
});
According to the Single Responsibility Principle , we should not use the HandleRequirementAsync() method to redirect reponse , we should use middleware or Controller to do that instead . If you put the redirect logic in HandleRequirementAsync() , how about if you want to use it in View page ?
You can remove the redirection-related code to somewhere else (outside) , and now you inject an IAuthorizationService to authorize anything as you like , even a resource-based authorization :
public class YourController : Controller{
private readonly IAuthorizationService _authorizationService;
public YourController(IAuthorizationService authorizationService)
{
this._authorizationService = authorizationService;
}
[Authorize("YYY")]
public async Task<IActionResult> Index()
{
var resource /* = ... */ ;
var x = await this._authorizationService.AuthorizeAsync(User,resource , "UserNameActiveCheck");
if (x.Succeeded)
{
return View();
}
else {
return new StatusCodeResult(403);
}
}
}
in .NET core 6.0 you can use the Fail method
AuthorizationHandlerContext.Fail Method
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAuthorizationRequirement requirement)
{
context.Fail(); //Use this
}

Get Unity WWW response status code

I'm working with Unity WWW for some Rest API request. But it don't support to get response status (only return text and error). Any solution for it? Thanks!
edit: Since the time I asked this question, Unity released a new framework for HTTP communications called UnityWebRequest. It's much more modern than WWW, and provides definitive access to the Response Code, as well as more flexibility around headers, HTTP verbs, etc. You should probably use that instead of WWW.
apparently you need to parse it from the response headers yourself.
this seems to do the trick:
public static int getResponseCode(WWW request) {
int ret = 0;
if (request.responseHeaders == null) {
Debug.LogError("no response headers.");
}
else {
if (!request.responseHeaders.ContainsKey("STATUS")) {
Debug.LogError("response headers has no STATUS.");
}
else {
ret = parseResponseCode(request.responseHeaders["STATUS"]);
}
}
return ret;
}
public static int parseResponseCode(string statusLine) {
int ret = 0;
string[] components = statusLine.Split(' ');
if (components.Length < 3) {
Debug.LogError("invalid response status: " + statusLine);
}
else {
if (!int.TryParse(components[1], out ret)) {
Debug.LogError("invalid response code: " + components[1]);
}
}
return ret;
}