URI in DELETE request's URI with MVC 5 - c#

We have a version control service which should be accessible through our REST API. One of those operations allows to delete a directory in SVN. Ideally, I'd like to send a DELETE request with the URI of the target to delete, something like this: http://service:4711/directory/http%3A%2F%2Fsome%2Fdirectory
What happens isn't new and there are plenty of answers out there. Unfortunately, they do not work for me. Depending on what I try, I get a 404 or a 403 (due to the malicious colon).
Let me show you some code and what I've tried without success so far:
// The action in my controller
[HttpDelete]
[Route("directory/{uri}/")]
public void DeleteDirectory(string uri)
{
var x = HttpUtility.UrlDecode(uri);
}
I am using MVC version 5.2.3.0.
I've tried:
[System.Web.Mvc.ValidateInput(false)] on the action and/or the class.
Setting runAllManagedModulesForAllRequests="true" in the web.config.
Setting requestPathInvalidCharacters="" in the web.config.
Setting requestValidationMode="true" in the web.config.
Right now, I see four possible solutions:
I've done something wrong with the previous approaches.
I have to create a custom RequestValidator.
I have to double encode the URI in the request.
Send a POST request instead of DELETE.
One may say, put it in the body of the DELETE request. But this option is highly controversial, so I'd like to ignore this one from the very beginning.
So what have I done wrong and what do you suggest to do?
Best regards,
Carsten

Colons in URI's in MVC are not allowed to be used until after the querystring '?' character in an URL, even when it is encoded as %3A.
Therefore, unless the SVN is http/s independent you could drop the initial http: from the parameter passed in an append it in the code.

Related

How to proxy a complete website with .NET Core?

I'm trying to implement a simple proxy within a .NET Core REST service, so I can inject additional authentication headers, and then return it to any client like a normal website.
In a simplified form it looks like this:
[HttpGet]
public async Task<ContentResult> Get()
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://google.com");
/* some extra headers injection happens here */
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
return Content(result, "text/html", Encoding.UTF8);
}
The problem is that while the response is correctly rendered by any browser as the original HTML page, any script or link (any relative URL) inclusion in the returned page fails.
What is missing in the code above to make browsers resolve inner relative URL-s correctly?
In the above example, if I run it, I get google.com page displayed from my https://localhost:44307/api/test, except images and other stuff from relative URL-s is missing, as they fail to resolve inner relative URL-s.
In a confusion, I tried to play with such properties as Referer and Host within request and response, but didn't make any progress.
Where it is needed. We need to use a third-party website via IFRAME, and that website requires Authorization header present, so the proxy above is supposed to do just that, and then return the website, so the API link can be used directly, like this: <iframe src="https://localhost:44307/api/test"> - this example should render complete google.com website inside the iframe, but it renders HTML only.
A ton of websites out there use relative paths to grab their resources (scripts/links/images/etc.) because it is convenient and allows them to have different environments in which things work. For example, having a development server, staging server, and a production server requires that each one be able to load the appropriate content. With that being said, there are a couple of options for you but they will require you to parse there content:
You can replace all of their references to internal sources with links to your proxy so that your headers get added for each of the resources.
You can replace all of their relative paths with absolute paths to the original domain so that all resource requests bypass your proxy. There are a few issues that can come up with this depending on their security.
As some have mentioned, neither of these solutions will make it easy to have a robust solution and will require parsing the CSS and JavaScript as well for relative paths. Not exactly an easy task, unfortunately, but probably far easier than trying to use some kind of virtualization.
To replace the content you can use something like HTMLAgilityPack. I've used it on a few projects and it works great and has a pretty good community.
This gentleman has posted an example of how to do something very similar HERE.

ASP.NET Core 2.1 get current web hostname and port in Startup.cs

I want to register my WebAPI to Consul service discovery and for that I should provide URL of my WebAPI (for example: http://service1.com) and health check endpoint (http://service1.com/health/check). How can I get that URL?
I found this piece of code:
var features = app.Properties["server.Features"] as FeatureCollection;
var addresses = features.Get<IServerAddressesFeature>();
var address = addresses.Addresses.First();
var uri = new Uri(address);
It returns 127.0.0.1:16478 instead of localhost:5600. I think first one used by dotnet.exe and second one is IIS which forwards 5600 to 16478. How can I get localhost:5600 in Startup.cs?
Well, there are multiple solutions for this problem. Your address is this:
string myurl = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
It returns 127.0.0.1:16478 instead of localhost:5600
You got this right yes. One is from IIS and one from dotnet. So, you have a problem of trying to get correct url. Ok, so what happens if you service is behind reverse proxy? https://en.wikipedia.org/wiki/Reverse_proxy
Then your service will not be exposed directly to internet, but requests made to specific url will be passed from reverse proxy to your service. Also, you can configure reverse proxy to forward additional headers that are specifying where the original request came from. I think that most reverse proxies are using X-Forwarded-For (Some of them are using X-Original-Host etc).
So if you have proper header set on RP, then you can get url like this:
url = url.RequestContext.HttpContext.Request.Headers["X-Forwarded-For"]
Url is of type UrlHelper. To simplify this method, you can create extension method (GetHostname(this UrlHelper url)) and then us it in your controller or wherever you want. Hope it helps
I don't think it is possible since there is usually a reverse proxy in production that handles public address and the application itself should not be exposed to public and, therefore, be aware of public address. But there can be some workarounds:
Place URL is some kind of config file that can be updated during deploy steps to have the correct URL.
Application can get full URL of the request like this, so after first actual request to the application we can get hostname.
EDIT: I reread your question. You wanted to know how to do this in Startup.cs. You can, but with fewer fallbacks. Your only choices are configuration or raw DNS.GetHostName(), which are less than ideal. Instead, upon any request to your service, lazily register your API. This is when you have context. Prior to that, your service knows nothing Jon Snow. The first request to your API is likely going to be health-checks, so that will kick off your registration with consul.
A solution I've used is a combination of configuration and headers in a fallback scenario.
Rely first on the X-Forwarded-For header. If there are cases where that doesn't apply or you have a need to... you can fallback to configuration.
This works for your use case, discovery. That said, it also works when you want to generate links for any reason, (e.g. for hypermedia for JSON API or your own REST implementation).
The fallback can be useful when there are reconfigurations occuring, and you have a dynamic configuration source that doesn't require a redeployment.
In the ASP.NET Core world, you can create a class and inject it into your controllers and services. This class would have a method that knows to try config first (to see if overriding is needed) and then the X-Forwarded-For header, and if neither is appropriate, fallback further to HttpContext.Request to get relevant URI parts.
What you're doing is enabling your API to be contextless and resiliency (to change) by giving it some contextual awareness of where "it lives".
This happens when you try to get current URL in Startup.cs. I've faced this issue before. What i did as Solution for my problem is. I Just declared Custom Setting in AppSettings in web.config(For Local) file and web.release.config(For Live)
like following
in web.config
<appSettings>
<add key="MyHost" value="http://localhost:5600" />
</appSettings>
in web.release.config
<appSettings>
<add key="MyHost" value="http://myLiveURL.com" />
</appSettings>
in startup.cs
string hostSetting = ConfigurationSettings.AppSettings["MyHost"];
And different host in release file. so what it helped is i can get Localhost URL in local website from web.config and Live URL from web.release.config.
if you are using Azure for live. it will be more easier for live(you would not need to add setting web.release.config file). add app setting in your website application setting https://learn.microsoft.com/en-us/azure/app-service/configure-common
In Case of ASP.NET Core you can use appsettings.json instead of web.config

Pass data to a URL on a different web server without the QueryString

I've got an .ashx handler which, upon finishing processing will redirect to a success or error page, based on how the processing went. The handler is in my site, but the success or error pages might not be (this is something the user can configure).
Is there any way that I can pass the error details to the error page without putting it in the query string?
I've tried:
Adding a custom header that contains the error details, but since I'm using a Response.Redirect, the headers get cleared
Using Server.Transfer, instead of Response.Redirect, but this will not work for URLs not in my site
I know that I can pass data in the query string, but in some cases the data I need to pass might be too long for the query string. Do I have any other options?
Essentially, no. The only way to pass additional data in a GET request (i.e. a redirect) is to pass it in the query string.
The important thing to realise is that this is not a limitation of WebForms, this is just how HTTP works. If you're redirecting to another page that's outside of your site (and thus don't have the option of cookies/session data), you're going to have to send information directly in the request and that means using a query string.
Things like Server.Transfer and Response.Redirect are just abstractions over a simple HTTP request; no framework feature can defy how HTTP actually works.
You do, of course, have all kinds of options as to what you pass in the query string, but you're going to have to pass something. If you really want to shorten the URL, maybe you can pass an error code and expose an API that will let the receiving page fetch further information:
Store transaction information (or detailed error messages) in a database with an ID.
Pass the ID in the query string.
Expose a web method or similar API to allow the receiving page to request additional information.
There are plenty of hacky ways you could create the illusion of passing data in a redirect outside of a form post (such as returning a page containing a form and Javascript to immediately do a cross-domain form post) but the query string is the proper way of passing data in a GET request, so why try to hack around it?
If you must perform a redirect, you will need to pass some kind of information in the Query String, because that's how browser redirects work. You can be creative about how you pass it, though.
You could pass an error code, and have the consuming system know what various error codes mean.
You could pass a token, and have the consuming system know how to ask your system about the error information for the given token behind-the-scenes.
Also, if you have any flexibility around whether it's actually performing a redirect, you could use an AJAX request in the first place, and send back some kind of JSON object that the browser's javascript could interpret and send via a POST parameter or something like that.
A redirect is executed by most browsers as a GET, which means you'd have to put the data in the query string.
One trick (posted in two other answers) to do a "redirect" as a POST is to turn the response into a form that POSTs itself to the target site:
Response.Clear();
StringBuilder sb = new StringBuilder();
sb.Append("<html>");
sb.AppendFormat(#"<body onload='document.forms[""form""].submit()'>");
sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
<!-- POST values go here -->
sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
sb.Append("</form>");
sb.Append("</body>");
sb.Append("</html>");
Response.Write(sb.ToString());
Response.End();
But I would read the comments on both to understand the limitations.
Basically there are two usual HTTP ways to send some data - GET and POST.
When you redirect to another URL with additional parameters, you make the client browser to send the GET request to the target server. Technically, your server responds to the browser with specific HTTP error code 307 + the URL to go (including the GET parameters).
Alternatively, you may want/need to make a POST request to the target URL. In that case you should respond with a simple HTML form, which consists of several hidden fields pre-filled with certain values. The form's action should point the target URL, method should be "POST", and of course your HTML should include javascript, which automatically submits the form once the document is loaded. This way the client browser would send the POST request instead of the GET one.

Why does DotNetNuke log me out on post ajax requests?

Previously, when I tried to do an ajax call to an ashx as a non-superuser account (i.e. as portal specific user) my web server would return cookies to clear my authorization. I posted a question about this and it seemed the answer was to make sure that the portalid=xx was specified in my GET parameters.
However, I have just found out that if I add portalid=xx in a POST request, DotNetNuke seems to ignore and and log out any non-superuser account.
How can I keep authorization during DNN POST ajax requests?
I think I have a good handle on the whole situation, and unfortunately it appears that the only true solution is to make sure each child portal has its own subdomain rather than a sub-url (e.g. portal.domain.com rather than domain.com/portal).
The problem is that when your portal 0 is domain.com but portal 1 is domain.com/portal everything works correctly until you need to access an .ashx file via ajax. What happens then is the URL that's requested is instead domain.com/DesktopModules/MyModule/Handler.ashx, which does not contain the /portal/ in it, thus causing DNN to think you are doing a request on portal 0 and logging you out.
While GET requests can overcome this with a portal=1 parameter, this does not seem to work for POST requests.
Therefore, the best solution it seems is to have your portal on a distinct subdomain (portal.domain.com), and then you don't risk missing something like this.
I've found a few things for you to check out and see if any of them solve your problem.
Make sure you are using a ScriptManagerProxy. This allows ascx pages to use AJAX while the parent page is also using AJAX.
There have been many reports of people not being able to run AJAX with DNN if Page State Persistence is set to "Memory". Those who experience this have been able to fix it by switching Page State Persistence to "Page". The easiest way to do this is to run this query:
update HostSettings
set SettingValue='P'
where SettingName='PageStatePersister'
After you run that, you'll need to recycle the application. If you don't have access to the server, just add a space or carriage return to your web.config file (that will force the app to recycle).
Lastly, you might see if you have this line in your web.config. Sometimes removing it will help:
<system.web>
<xhtmlConformance mode="Legacy" />
</system.web>

URL with no query parameters - How to distinguish

Env: .NET 1.1
I got into this situation. Where I need to give a URL that someone could redirect them to our page. When they redirect they also need to tell us, what message I need to display on the page. Initially I thought of something like this.
http://example.com/a.aspx?reason=100
http://example.com/a.aspx?reason=101
...
http://example.com/a.aspx?reason=115
So when we get this url based on 'reason' we can display different message.
But the problem turns out to be that they can not send any query parameters at all. They want 15 difference URL's since they can't send query params. It doesn't make any sense to me to created 15 pages just to display a message.
Any smart ideas,that have one URL and pass the 'reason' thru some means?
EDIT: Options I'm thinking based on Answers
Try HttpRequest.PathInfo
or Second option I was thinking was to have a httphanlder read
read the path like this - HttpContext.Request.Path
based on path act. Ofcourse I will have some 15 entries like this in web.config.
<add verb="*" path="reason1.ashx" type="WebApplication1.Class1, WebApplication1" />
<add verb="*" path="reason2.ashx" type="WebApplication1.Class1, WebApplication1" />
Does that look clean?
Thoughts:
Path Info: http://msdn.microsoft.com/en-us/library/system.web.httprequest.pathinfo.aspx
urls would be http://example.com/a.aspx/reason100, http://example.com/a.aspx/reason101, etc
URL Rewriting : http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
urls would be http://example.com/a/reason/100.aspx, http://example.com/a/reason/100.aspx, etc.
edit: both these approaches involve only one aspx page, but multiple urls pointing to it.
Assuming IIS (I run this on IIS 6 but I expect it would run on 5 as well) you could install IIRF. You could then configure different "friendly" urls a la Apache's mod-rewrite and send them as query params to a single as*x page.
Can they send POST variables?
Too bad you are at 1.1 because the later versions support routing which allows for RESTful URLs.
Another option would to be write a custom HttpModule and intercept the incoming requests.

Categories