Angular does not encode the semi-colon character in an url - angularjs

We need an encoded semi-colon character in our url parameter but angular does not encode this character.
The resource we use looks something like this:
app.factory('TestResource', ['$resource', function ($resource) {
return $resource('http://somedomain.com');
}]);
app.run(['TestResource', function (TestResource) {
TestResource.query({
q: 'sin;sout'
});
}]);
This is the result we get:
http://somedomain.com/?q=sin;sout
We want the url to be:
http://somedomain.com/?q=sin%3Bsout
But if we pre-encode the parameter before sending the % char get's encoded like this:
http://somedomain.com/?q=sin%253Bsout
How can we get the desired result? (http://somedomain.com/?q=sin%3Bsout)

Angular uses its own function to encode the URL. And that function doesn't encode characters that usually don't need to be encoded like / or ?. ; is such a character, too. It's used in matrix URIs, e.g. So Angular's behavior actually conforms to the standard, but it could be a problem when such characters are used literally, i.e. without a special meaning. That seems to be the case here.
Since the URL is build just before sending the request, there's not much we can do. There is kind of a loophole though: Only parameters are encoded, not the base URL. So any workaround would involve creating the URL or at least a part of it yourself.
You could add an interceptor, that adds the properly encoded parameters to the URL before Angular does it. You could even completely replace Angular's behavior that way.
UPDATE:
Another solution came to my mind. The $http service delegates sending the actual request to the $httpBackend service which receives the already constructed URL. Using a decorator you can replace either the ; or the incorrectly encoded %253B with %3B right before the request is sent:
app.config(function($provide) {
$provide.decorator('$httpBackend', function($delegate) {
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
url = url.replace(';', '%3B');
$delegate(method, url, post, callback, headers, timeout, withCredentials, responseType);
};
})
});

Building on zeroflagL's answer one could consider changing the replace to change all occurrences of the semi-colon (as it is written it will only replace the first semi-colon):
url = url.replace(/;/g, '%3B');

There is a closed issue for this https://github.com/angular/angular.js/issues/9224
path: Don't escape semicolons. Breaks matrix parameters, and no evidence that Angular's current behavior is problematic.
query: Escaping the entire query string would be unwise, as it breaks an established (albeit archaic) expectation for servers to be able to handle semicolon-delimited query strings.
query values: Probably safe to escape, especially if the server interprets semicolons as query delimiters. Passing complex data types (ie. JSON) via query parameters is a bad idea, but "real-world" string values often contain semicolons, and it seems like a good idea to escape them.
I'll go with zeroflagL's answer but only changing the query values. Like this:
$provide.decorator('$httpBackend', function($delegate) {
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
var a = document.createElement('a');
a.href = url;
if(a.search.length > 1) {
var params = a.search.substring(1).split('&');
for (var i=0; i<params.length; i++) {
var param = params[i].split('=');
if(param[1]) {
params[i] = [param[0], param[1].replace(';', '%3B')].join('=');
}
}
url = url.substring(0, url.lastIndexOf('?')) + '?' + params.join('&');
}
$delegate(method, url, post, callback, headers, timeout, withCredentials, responseType);
};
});

You can use encodeURIComponent() method:
q: encodeURIComponent('sin;sout')

Related

Angular http.get charset

I'm having some trouble getting my angular application to parse my json-data correctly.
When my json-feed contains e.g. { "title": "Halldórsson Pourié" }
my application shows Halld�rsson Pouri�
I figure the problem is the charset, but i can't find where to change it.
Currently i am using ng-bind-html and using $sce.trustAsHtml(), and I'm very sure the problem occurs when $http.get(url) parses my json.
So how do i tell $http.get(url) to parse the data with a specific charset?
I had a similar issue and resolved it by using:
encodeURIComponent(JSON.stringify(p_Query))
where p_Query is a JSON containing the details of the request (i.e. your { "title": "Halldórsson Pourié" }).
EDIT:
You may also need to add to the header of your GET request the following:
'Content-Type': 'application/x-www-form-urlencoded ; charset=UTF-8'
Had same problem with accented characters and some scientific notation in text/JSON fields and found that AngularJS (or whatever native JavaScript XHR/fetch functions it is using) was flattening everything to UTF-8 no matter what else we tried.
The other respondent here claimed that UTF-8 should somehow still be accommodating your extended character set, but we found otherwise: taking samples of the source data directly, we loaded it into text editor with different encodings and UTF-8 would still squash extended characters to that � placeholder.
In our case, the encoding was ISO-8859-15 but you may be sufficient with ISO-8859-1.
Try adding this $http configuration to your $http.get() call, accordingly:
$http.get(url, {
responseType: 'arraybuffer',
transformResponse: function(data) {
let textDecoder = new TextDecoder('ISO-8859-15'); // your encoding may vary!
return JSON.parse(textDecoder.decode(data));
}
});
This should intercept AngularJS default response transformation and instead apply the function here. You will, of course, want to consider adding some error handling, etc.
If there are better ways to do this without a custom transformResponse function, I have yet to find anything in the AngularJS documentation.
$http.get(url, {responseType: 'arraybuffer', }).then(function (response) { var textDecoder = new TextDecoder('ISO-8859-1'); console.log(textDecoder.decode(response.data));});
This code will replace all your � placeholders into normal characters.

AngularJS $http GET comma separated query string parameters

I'm trying to do a HTTP GET call using Angular's $http. I've written a generic function like this,
function doGETRequest(url, params) {
return $http({
method: 'GET',
url: baseURL + url,
headers: {
'Authorization': 'Bearer ' + access_token
},
params: params
});
}
I want to create a URL like this,
https://content.googleapis.com/drive/v2/about?fields=quotaBytesTotal%2CquotaBytesUsed%2CrootFolderId%2Cuser but I cannot for the life of me figure out the query param part. So far I tried the following:
doGETRequest('/about', {fields:'user,quotaBytesTotal,quotaBytesUsed,rootFolderId'})
doGETRequest('/about', {fields:['user','quotaBytesTotal','quotaBytesUsed','rootFolderId']})
doGETRequest('/about', {fields:encodeURIComponent('user,quotaBytesTotal,quotaBytesUsed,rootFolderId')})
It does work for the second one, but it parses it as https://content.googleapis.com/drive/v2/about?fields=user&fields=quotaBytesTotal&fields=quotaBytesUsed&fields=rootFolderId and Google just sends response for the first query and ignores the rest (as expected).
So the question is how do I pass comma separated values in query params? Do I have to manually write URL encoded values? Doesn't this beat the purpose of having a params field?
Okay, for those who stumbled upon the same problem as I did, I found two ways of solving this.
First, as #kiro112 suggested in the comments, I write the params directly in the URL. Since $http doesn't encode the URL it'll work. So the code will be something like
doGETRequest('/about?fields='+encodeURIComponent("user,quotaBytesTotal,quotaBytesUsed,rootFolderId"))
and we'll have to get rid of the params part in the doGetRequest() method. Personally, I don't like this one as it beats the purpose of having the params part in $http.
Second method is overriding the default $httpParamSerializer and supplying our own. To do that, the doGetRequest() method has to be modified like so,
function doGETRequest(url, params) {
return $http({
method: 'GET',
url: baseURL + url,
headers: {
'Authorization': 'Bearer ' +access_token
},
params: params,
paramSerializer: function (value){
return Object.keys(value)+'='+encodeURIComponent(Object.values(value));
}
});
}
What happens here is, we do not let angular encode the params. Let me tell you why. As per the docs here, angular only does encoding like so,
{'foo': 'bar'} results in foo=bar
{'foo': Date.now()} results in foo=2015-04-01T09%3A50%3A49.262Z (toISOString() and encoded representation of a Date object)
{'foo': ['bar', 'baz']} results in foo=bar&foo=baz (repeated key for each array element)
{'foo': {'bar':'baz'}} results in foo=%7B%22bar%22%3A%22baz%22%7D" (stringified and encoded representation of an object)
Whereas we require something in the lines of {'foo':['bar', 'baz']} resulting in foo=bar%2Cbaz. So we override the paramSerializer and write our own serializer.
The second approach looks much cleaner to me as far as using generic methods is concerned. However, I am still on a look out for a better option.

Is it possible to perform a Restangular GET with multiple query parameters?

I've been attempting to perform a Restangular GET with multiple query parameters (for simple pagination) and it doesn't seem possible--at least with what I'm trying to do. Here's what I'm attempting to do:
Restangular.all('elevation').get({ pageParam: page, pageSizeParam: pageSize }).then(function(data) {
console.log(data);
});
WIth the expected response looking something like:
{ totalRecords: 233, elevations: {...} }
This does not work and results in the following:
GET http://localhost:24287/elevation/[object%20Object] 404 (Not Found)
I also attempting utilizing customGET as well which results in the same problem as above.
The only way I'm actually able to pass multiple query parameters is by using getList. Unfortunately when utilizing the getList unsurprisingly the following error is thrown:
Error: Response for getList SHOULD be an array and not an object or something else
To resolve this issue, the Restangular documentation states in the My response is actually wrapped with some metadata. How do I get the data in that case? section that I need to use addResponseInterceptor which I've done like so:
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newResponse = [];
if(response.data.totalRecords !== undefined) {
newResponse.elevationData= {};
newResponse.elevationData.totalRecords = response.data.totalRecords;
newResponse.elevationData.elevations = response.data.elevations;
}
return newResponse;
});
After jumping through hoops this convoluted solution does in fact work. Is there not an easier way to simply call Restangular's get with multiple query parameters and get an object returned?
At this point, it would be a magnitude easier to take Restangular out of the question and simply use the $http service like so:
$http.get('http://localhost:24287/elevation?page=' + page + '&pageSize=' + pageSize).then(function(result) {
console.log(result.data.totalRecords);
console.log(result.data.elevations);
});
Though I really want to figure out a better way to do this with Restangular.
Any ideas would be greatly appreciated! Thanks!
That's deadly simple, querying a ressource URL without an id, even with specific query params, and returning a single object is just not RESTful.
Error: Response for getList SHOULD be an array and not an object or something else
It's precisely telling you the truth : your query could return an array, because you haven't specified a unique identifier (everything else is not unique).
At this point, it would be a magnitude easier to take Restangular out of the question and simply use the $http service
Definately, since Restangular focuses on RESTful resource proxying, or you're gonna have to tweak it (interceptors, customGET, ...)

Encoding rest path variable in angularjs

I am having a peculiar issue.
I am calling a restful service with path param from angularjs controller/service.
Below is the url format
/payment/{id}/credit/{creditId}/fetch/options
Now, creditId has special charachters in it for eg 'abcd%xyz-433'
I am calling a service which makes a GET rest call as below in angualarjs controller
creditService.creditOption('abc','abcd%xyz-433').success(function(data, status) {
$log.log('log the status ' + status);
});
The service is as below
creditOption: function(id, creditId) {
return $http({
method: 'GET',
url: '/payment/'+id+'/credit/'+creditId+'/fetch/options'
});
}
The Restful service signature is as follows
#RequestMapping(method = RequestMethod.GET, value = "/payment/{id}/credit/{creditId}/fetch/options", produces = APPLICATION_JSON)
public boolean getCreditOption(#PathVariable String id, #PathVariable String creditId) {
return true
}
This is giving me 404 all the time i execute this code. But when i remove the special charachters it works fine and hits the restful service. I also tried to use encodeURIComponent() and then send the value.. but it is still the same case.
Please let me know if there is any way out for this.
Thanks for any help
The % symbol is used for encoding values in URls, see rfc3986 section 2.1. In particular it's expected that there will be two hex digits after the % - which it seems like you are exactly stumbling on to.
You may have to escape the percent (use %25) or choose a different character to substitute.
You can try it on this page, the address is:
http://stackoverflow.com/questions/27641286/encoding-rest-path-variable-in-angularjs
A 1 percent encoded is %31, so you could just as easily go here:
http://stackoverflow.com/questions/2764%31286/encoding-rest-path-variable-in-angularjs

How to mock get(id) requests

I am building an application prototype and try to mock the REST web-services.
Here is my code:
var mock = angular.module('mock', ['ngMockE2E']);
mock.run(function($httpBackend){
users = [{id:1,name:'John'},{id:2,name:'Jack'}];
$httpBackend.whenGET('/users').respond(users);
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(users[0]);
}
Everything is ok, my resource User.query() returns all users, and User.get({id:1}) and User.get({id:2}) returns the same user (John).
Now to improve my prototype, I would like to return the appropriate user, matching the good id.
I read in the angular documentation I should be able to replace the RegExp URI by a function. The idea is to extract the id from the url to use it in respond method.
I then tried this:
$httpBackend.whenGET(new function(url){
alert(url);
var regexp = new RegExp('\\/users\\/([0-9]+)');
id = url.match(regexp)[1];
return regexp.test(url);
}).respond(users[id]);
The problem is the url parameter is always undefined. Any idea to achieve my goal?
By using new function(url) your app tries to instantiate a new object from your anonymous function and pass that new object as the first argument of the $httpBackend.whenGET() call.
Of course, at the time of calling whenGET() no URL is provided, thus it is always undefined.
You should pass the function itself (and not an object instanciated using the function). E.g.:
$httpBackend.whenGET(function (url) {
...
}).respond(users[id]);
UPDATE:
After some more digging it turned out that the option to pass a function as the first argument to whenGET was added in version 1.3.0-beta.3. The docs you were reading probably referred to the latest beta version, while you were using an earlier version.
(Note that even versions 1.3.0-beta.1 and 2 did not provide this option.)
Without getting into much detail, responsible for verifying a matching URL is MockHttpExpectation's matchUrl method:
function MockHttpExpectation(method, url, data, headers) {
...
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
if (angular.isFunction(url)) return url(u); // <<<<< this line does the trick
return url == u;
};
The line if (angular.isFunction(url)) return url(u); is the one that gives the option to directly pass a function and was added in version 1.3.0-beta.3 (as already mentioned).
But, if you still want to pass a function to a previous AngularJS version, you could "trick" angular into believing you passed a RegExp, by providing an object with a test method.
I.e. replace:
.whenGET(function (url) {...})
with:
.whenGET({test: function (url) {...}})
See, also, this short demo.
I found a solution by using a function in the respond part instead of the when part:
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(
function(method, url){
var regexp = new RegExp('\\/users\\/([0-9]+)');
var mockId = url.match(regexp)[1];
return [200, users[mockId]];
}
});

Resources