Colons not being URL encoded in resource parameters - angularjs

I'm using Angular's Resource module to access a web API, but I'm having problems as the API uses URIs as the primary key.
Whenever I try to make a call to this API, passing in a URI as a string parameter, I'm getting 400 Bad Request errors. On closer inspection, Resource is escaping all the forward slashes in the URI but not the colon at the start. It's doing a GET on a URL that looks like this: http://myserver/api/objects/http:%2F%2Fexample.comk%2FmyURI%2F, which is of course invalid. I've also tried escaping the colon with a backslash, but that doesn't work either.
How can I make Resource escape my parameters properly? I've tried replacing the colon with %3A before making the call, but that results in the % being encoded again, returning 404 Not Found.
The service handling Resource looks like this:
angular.module('adminApp').factory('MyObject', myObject);
function myObject($resource) {
return $resource('/api/objects/:uri');
};
and I'm calling it like this:
MyObject.get({ uri: myUri }, function(result) {
...
});

I've got around this issue by passing the URI as a query parameter instead of as part of the request URL. I did this by changing my resource service to this:
angular.module('adminApp').factory('MyObject', myObject);
function myObject($resource) {
return $resource('/api/objects');
};
and leaving the calling code this the same. ngResource then creates a GET that looks like http://myserver/api/objects?uri=http:%2F%2Fexample.comk%2FmyURI%2F, which is fine.
Basically, if you're using unusual characters in your API parameters, put them in a query string rather than in the URL! :-)

Related

How do you append a url as a query param value

My website has a search form where someone can search a URL beginning with http:// like this:
https://www.google.com
which should then be encoded and appended as a query parameter value like this:
localhost:4000/api/https%3A%2F%2Fwww.google.com
When I run it (above) locally, it works, but when deployed (below):
https://api.mysite.com/search/api/https%3A%2F%2Fwww.google.com%2F
=> returns 404.
If I type this in:
http://localhost:4000/api/https://www.google.com
I get this error:
Phoenix.Router.NoRouteError at GET /api/v1/https://www.google.com
no route found for GET /api/v1/https:/www.google.com (ExternalPing.Router)
I'm not sure if these are related. What is the correct way to append a url as a query parameter value?
I have already tried encoding with URI.encode and URI.encode_www_form but they didn't resolve this
Now you haven't posted your server code, so I am just going to assume here.
I think the problem is that you didn't encode the second string, since it contains / in the url you have problems.
The url is:
http://localhost:4000/api/https://www.google.com
The server will interpret it wrong. So you are asking for a route called:
/api/https:/
With a parameter called /www.google.com
You need to encode the query string.
But again this is guessing since I have no idea how your server looks.
I just tried calling an endpoint at my iis server with a unencoded url as a parameter, and this is what it gave me back:
<Error>
<Message>The request is invalid.</Message>
</Error>

WebApi and Ampersands in name

So my angular website has a webapi with the following method.
[Route("items/{itemName}")]
public object GetMcguffinsByItem(string itemName)
{
return _mcguffinsService.GetAllByItemName(itemName);
}
However, an item name can have an ampersand as a valid character. However when attempting to use items that do have an ampersand, the method will return a 400 badrequest.
Im not sure how to go about fixing this problem.
For more verification: I was under the impression that encoding and using %26 is all required to pass an ampersand to part of the URI. It seems to be a common answer when searching my problem. I have excluded the angular as I can verify that it builds the string correctly, and other names produce the desired result.
The javascript method encodeURIComponent() followed by using the angular service double encodes the item name, and returns a 404.
EDIT:
Sample Input:
A&B 266
After Encoding:
A%26B%20266
Console:
angular.js:10722 GET http://localhost:60894/api/v1/mcguffins/items/A%26B%20266 404 (Not Found)
Using the browser on api directly with same input gives this error:
[HttpException (0x80004005): A potentially dangerous Request.Path value was detected from the client (&).]
System.Web.HttpRequest.ValidateInputIfRequiredByConfig() +11944671
System.Web.PipelineStepManager.ValidateHelper(HttpContext context) +55

How do I test my HTTP call when I use a config to build the URL?

In my project I build some of my HTTP requests like so:
var options = {
params:{
foo: 'bar'
hello: world
}
};
$http.get("my/service", options)
Which means that the final HTTP call looks something like my/service?foo=bar&hello=worldVar
How do I setup my $httpBackend to account for this?
The problems I see are:
I'm not guaranteed order in my parameters with this style, which means setting up the first parameter in expectGet will be hard.
Its hard to test calls when I really don't care about the parameters
The best solution I've found to this is to use the override of expect/when that accepts a function, and to mix it in with this answer. That gets me to something like
$httpBackend.expectGET(function(url){
var parser = document.createElement('a');
parser.href=url;
return parser.search.indexOf('foo=bar') > -1 &&
parser.search.indexOf('hello=world') > -1;
})
or if I don't care about the parameters I can just test for parser.pathname.
However, this still isnt perfect because its a pain to test that I dont have extra parameters, so Im still actively looking for an alternative solution.
if you don't care about the parameters, you can just use regex to match to the static part of the URL:
$httpBackend.whenGET(/my\/service/)

What is the use of 'Route' in Restangular functions oneUrl() and allUrl()

That's the signature for oneUrl function: oneUrl(route, url)
And from the documentation:
oneUrl(route, url): This will create a new Restangular object that is
just a pointer to one element with the specified URL.
To me, it seems useless to set Route when you are giving a url for the resource. Why does it exist in the argument list? Why is it mandatory? And how can it be used?
In my use of oneUrl I've found the route name is used to build the URL for subsequent PUT and DELETE operations. For example (pseudo code):
// "GET /api/v2/users/3/ HTTP/1.1" 200 184
var user = Restangular.oneUrl('myuser', 'http://localhost:8000/api/v2/users/3').get();
user.name = 'Fred';
// the following uses the route name and not the URL:
// "PUT /api/v2/myuser HTTP/1.1 404 100
user.put();
I was surprised by this behavior. I expected put() to use the same URL as get(); which would be helpful in my case.
My API uses absolute URLs within the JSON payloads to navigate to all related resources and I wanted to use oneUrl() to GET/PUT instances without recreating the routes in the JS code. But I'm pretty new to Restangular so I might not have the mental model correct.

Making calls from the Javascript client library with #Named and unnamed parameters makes no sense

I have a Cloud Endpoints method that looks like this:
//HTTP POST
#ApiMethod(name = "hylyts.insert")
public Hylyt insertHylyt(#Named("url") String url, Hylyt hylyt, User user)
throws OAuthRequestException{
log.info("Trying to save hylyt '"+hylyt+"' with id '"+hylyt.getId());
if (user== null) throw new OAuthRequestException("Your token is no good here.");
hylyt.setArticle(getArticleKey(url, user));
ofy().save().entity(hylyt);
return hylyt;
}
I call it from the Javascript Client Library using this:
gapi.client.hylytit.hylyts.insert({PARAMS}).execute(callback);
Now, if I structure {PARAMS} as suggested in the docs (second example),
{
'url': url,
'resource': {
'hylyt': {
'contentType': 'application/json',
'data': hylyt
}
}
}
I get a null object in the endpoint (not to mention that the whole point of this library is to make these calls simple, which this structure clearly violates).
When I structure {PARAMS} as these answers suggest,
{
'url': url,
'resource': hylyt
}
I get a null object in the endpoint again. The correct syntax is this:
{
'url': url,
'id': hylyt.id
'text': hylyt.text
}
Which just blows my mind. Am I doing this all wrong? Is this a bug? Is it only happening because gapi is also passing the auth token in the background?
Yes, I could use the request syntax instead, but, again, why even use the library if it's just as complex as making the XHRs in pure javascript? I wouldn't mind the complexity if Google explained in the docs why things are happening. But the docs, paraphrased, just say use these methods and the auth, CORS, and XHR magic will happen behind closed doors.
Is the API method correctly recognized as POST method?
The resource parameter which is sent as POST body won't work correctly in a GET request.
The way it looks you are actually sending a GET request with the Hylyt properties in the query string.
To make sure you can change the method annotation to this:
#ApiMethod(name = "hylyts.insert", httpMethod = HttpMethod.POST)
Yup, agreed it's a bug. caused me great pains as well.
So i guess the work around is to create a combined object to pass to your api all named and un named parameters. Rather than hardcode each.. a quick loop might be better.
var param = {};
param["url"] = url;
for (var prop in hylyt) {
param[prop] = hylyt[prop];
}
gapi.client.hylytit.hylyts.insert(param).execute(callback);
That mashing together of parameters / objects can become a slick function if you really want.. but it's a band aid for what I'd consider a defect.
I see in the related question (cloud endpoints resource attribute for transmitting named params & body not working), you actually logged a defect.. Good stuff. Though there still appears no movement on this one. fingers crossed for someday!
The bug has been resolved. The correct syntax is
gapi.client.hylytit.hylyts.insert({url: url}, hylyt).execute(callback);

Resources