How to properly encode Web API parameter in URL path? - c#

I have endpoint used to find book by book reference.
The book reference is string that can contain white-space and any kind of special characters, e.g. mybook, my book, my-book, my/book, book++
// GET api/books/reference/{reference}
[HttpGet("reference/{reference}")]
public ActionResult<BookItem> FindByReference(string reference)
This is what I get when testing:
GET api/books/reference/mybook
OK
GET api/books/reference/my book
OK
GET api/books/reference/my-book
OK
GET api/books/reference/my+book
404 Not found
GET api/books/reference/my/book
404 Not found
GET api/books/reference/book++
404 Not found
What is a proper way to encode this reference parameter IN THE URL PATH so that it gets properly resolved by routing? Is that even possible?

The encoding of the URLs is the responsibility of the client calling the API, a client must supply a valid URL if it wants a proper response. If you encode your examples you will get:
GET api/books/reference/mybook
GET api/books/reference/my%20book
GET api/books/reference/my-book
GET api/books/reference/my%2Bbook
GET api/books/reference/my%2Fbook
GET api/books/reference/book%2B%2B
These should now work.
If you want help with the actual encoding you will have to edit your question with the client source code.

Related

414. The request URL is too long. asp.net

I'm getting the error "HTTP Error 414. The request URL is too long." From the following article, I understand that this is due to a very long query string:
http://www.mytecbits.com/microsoft/iis/query-string-too-long
In web.config, I have maxQueryStringLength="2097151". Is this the maximum value?
In order to solve this problem, should I set maxUrl in web.config? If so, what's the maximum value supported?
What should I do to fix this error?
The GET request should never be this long. You need to change it to POST method instead since it was designed to transmit block of data such as forms.
An excerpt from the RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1:
The POST method is used to request that the origin server accept the
entity enclosed in the request as a new subordinate of the resource
identified by the Request-URI in the Request-Line. POST is designed to
allow a uniform method to cover the following functions:
Annotation of existing resources;
Posting a message to a bulletinboard, newsgroup, mailing list, or
similar group of articles;
Providing a block of data, such as the result of submitting a
form, to a data-handling process;
Extending a database through an appendoperation.

CustomVision API returns “Operation returned an invalid status code: 'Bad Request'”

I get this error when using the CustomVisionPredictionClient like so:
var predictionApi = new CustomVisionPredictionClient()
{
ApiKey = _predictionKey,
Endpoint = "https://westeurope.api.cognitive.microsoft.com"
};
var result = await predictionApi.ClassifyImageAsync(project.Id, _modelName, imageData);
The project/project id is retrieved via the training API, on which I can call GetProjects() without a problem. It should be correct, if I change it to something wrong I get a "not found" exception.
_modelName is the published name of the iteration ("xxxRecognition", see screenshot below), it should also be correct, when I change it I get "not found".
imageData is just a FileStream from a PNG image.
The problem was that I created an "Object Detection" type project and tried to use it with ClassifyImage() which has to be used with "Classification" type projects. So I have to use DetectImage() instead. :)
There's two items to address here.
Your particular "Bad Request"
Your example, specifically, has one or more of these problems that you haven't really included.
The _modelName is malformed
The imageData is not formatted properly
Some configuration, likely of request headers, is missing or incorrect
That's about the most we can provide from the example you've given. But here's the other concern that will benefit you greatly in the future: "Bad Request" tells you a lot about what's happened.
More about "Bad Request" in general
If you look at ranges within HTTP status codes you'll notice a pattern in the "error" ranges.
In 4xx the requester (you) did something wrong and you can correct it.
In 5xx the responder did something wrong and you cannot correct it.
Beneath that:
In 404 Not Found it seems the request was formed well but the responder cannot find what you've asked for
In 401 Unauthorized you didn't provide any kind of identity
In 403 Forbidden you did provide an identity but you're not allowed to perform this action
But in 400 Bad Request the responder couldn't validate your request as good input at all. That means you can look at the API documentation again, compare it with your implementation, and try again.

USPS Address Validation Fail

When validating an address, I get this error:
ex = {"Error Loading XML: The following tags were not closed: AddressValidateRequest, Address, Address1.\r\n"}
or I get another error saying the address cannot be found. Is there a better way to validate this address?
Here is my URL:
http://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=<AddressValidateRequest USERID="402JMAWE3481"><Address ID="1"><Address1>123 Main St</Address1><Address2></Address2><City>Watertown</City><State>MA</State><Zip5>02472</Zip5><Zip4></Zip4></Address></AddressValidateRequest>
According what I see on the error description, the problem could be that you need to remove \r\n from the xml before adding it to the url. Don't forget also to url encode it.
You actually need to HtmlEncode it and then UrlEncode it.
This is becasue you're actually sending XML (which requires & instead of &) but its a URL so it needs encoding to make each & into %26
Here's a complete working URL - you just need to put in your USERID.
http://production.shippingapis.com/ShippingAPI.dll?API=Verify&XML=<AddressValidateRequest USERID="123USERID567"><Address ID="1"><Address1></Address1><Address2>10051+Orr+%26amp%3b+Day+Rd</Address2><City>santa+fe+springs</City><State>ca</State><Zip5>90670</Zip5><Zip4></Zip4></Address></AddressValidateRequest>
You'll see it contains this funky looking string:
10051+Orr+%26amp%3b+Day+Rd
Which I got by doing this :
HttpUtility.UrlEncode(HttpUtility.HtmlEncode("10061 Orr & Day Rd"))
[This specific error I got back when I didn't encode properly was Error Loading XML: Whitespace is not allowed at this location]

How to get the address of a redirected page?

The goal of my program is to grab a webpage and then generate a list of Absolute links with the pages it links to.
The problem I am having is when a page redirects to another page without the program knowing, it makes all the relative links wrong.
For example:
I give my program this link: moodle.pgmb.si/moodle/course/view.php?id=1
On this page, if it finds the link href="signup.php" meaning signup.php in the current directory, it errors because there is no directory above the root.
However this error is invalid because the page's real location is:
moodle.pgmb.si/moodle/login/index.php
Meaning that "signup.php" is linking to moodle.pgmb.si/signup.php which is a valid page, not moodle.pgmb.si/moodle/course/signup.php like my program thinks.
So my question is how is my program supposed to know that the page it received is at another location?
I am doing this in C Sharp using the follownig code to get the HTML
WebRequest wrq = WebRequest.Create(address);
WebResponse wrs = wrq.GetResponse();
StreamReader strdr = new StreamReader(wrs.GetResponseStream());
string html = strdr.ReadToEnd();
strdr.Close();
wrs.Close();
You should be able to use ResponseUri method of WebResponse class. This will contain the URI of the internet resource that actually provided the response data, as opposed to the resource that was requested. You can then use this URI to build correct links.
http://msdn.microsoft.com/en-us/library/system.net.webresponse.responseuri.aspx
What I would do is first check if each link is absolute or relative by searching for an "http://" within it. If it's absolute, you're done. If it's relative, then you need to append the path to the page you're scanning in front of it.
There are a number of ways you could get the current path: you could Split() it on the slashes ("/"), then recombine all but the last one. Or you could search for the last occurrence of a slash and then take a substring of up to and including that position.
Edit: Re-reading the question, I'm not sure I am understanding. href="signup.php" is a relative link, which should go to the /signup.php. So the current behavior you mentioned is correct "moodle.pgmb.si/moodle/course/signup.php."
The problem is that, if the URL isn't a relative or absolute URL, then you have no way of knowing where it goes unless you request it. Even then, it might not actually be being served from where you think it is located. This is because it might actually be implemented as a HTTP Redirect or similar server side.
So if you want to be exhaustive, what you can do is:
Use your current technique to grab a list of all links on the page.
Attempt to request each of those pages. Then if you:
Get a 200 responce code then all is good - it's there.
Get a 404 response code you know the page does not exist
Get a 3XX response code then you know where the web server
expects that content to actually orginate form.
Your (Http)WebResponse object should have a ResponseCode property. Note that you should also handle any possible WebException errors - these too will have a WebResponse with a ResponseCode in (usually 5xx).
You can also look at the HttpWebResponse Headers property - the Location header.

Why is my S3 pre-signed request invalid when I set a response header override that contains a "+"?

I'm using the Amazon .NET SDK to generate a pre-signed URL like this:
public System.Web.Mvc.ActionResult AsActionResult(string contentType, string contentDisposition)
{
ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
headerOverrides.ContentType = contentType;
if (!string.IsNullOrWhiteSpace(contentDisposition))
{
headerOverrides.ContentDisposition = contentDisposition;
}
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
.WithBucketName(bucketName)
.WithKey(objectKey)
.WithProtocol(Protocol.HTTPS)
.WithExpires(DateTime.Now.AddMinutes(6))
.WithResponseHeaderOverrides(headerOverrides);
string url = S3Client.GetPreSignedURL(request);
return new RedirectResult(url, permanent: false);
}
This works perfectly, except if my contentType contains a + in it. This happens when I try to get an SVG file, for example, which gets a content type of image/svg+xml. In this case, S3 throws a SignatureDoesNotMatch error.
The error message shows the StringToSign like this:
GET 1234567890 /blah/blabh/blah.svg?response-content-disposition=filename="blah.svg"&response-content-type=image/svg xml
Notice there's a space in the response-content-type, where it now says image/svg xml instead of image/svg+xml. It seems to me like that's what is causing the problem, but what's the right way to fix it?
Should I be encoding my content type? Enclose it within quotes or something? The documentation doesn't say anything about this.
Update
This bug has been fixed as of Version 1.4.1.0 of the SDK.
Workaround
This is a confirmed bug in the AWS SDK, so until they issue a fix I'm going with this hack to make things work:
Specify the content type exactly how you want it to look like in the response header. So, if you want S3 to return a content type of image/svg+xml, set it exactly like this:
ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
headerOverrides.ContentType = "image/svg+xml";
Now, go ahead and generate the pre signed request as usual:
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
.WithBucketName(bucketName)
.WithKey(objectKey)
.WithProtocol(Protocol.HTTPS)
.WithExpires(DateTime.Now.AddMinutes(6))
.WithResponseHeaderOverrides(headerOverrides);
string url = S3Client.GetPreSignedURL(request);
Finally, "fix" the resulting URL with the properly URL encoded value for your content type:
url = url.Replace(contentType, HttpUtility.UrlEncode(contentType));
Yes, it's a dirty workaround but, hey, it works for me! :)
Strange indeed - I've been able reproduce this easily, with the following observed behavior:
replacing + in the the URL generated by GetPreSignedURL() with its encoded form %2B yields a working URL/signature
this holds true, no matter whether / is replaced with its encoded form %2F or not
encoding the contentType upfront before calling GetPreSignedURL(), e.g. via the HttpUtility.UrlEncode Method, yields invalid signatures regardless of any variation of the generated URL
Given how long this functionality is available already, this is somewhat surprising, but I'd still consider it to be a bug - accordingly it might be best to inquiry about this in the Amazon Simple Storage Service forum.
Update: I just realized you asked the same question there already and the bug got confirmed indeed, so the correct answer can be figured out over time by monitoring the AWS team response ;)
Update: This bug has been fixed as of Version 1.4.1.0 of the SDK.

Categories

Resources