Status code 400: NSHTTPURLResponse on calls made using URLSession.shared.dataTask - ios11

This is an issue that I started having after I updated my iOS 11 (15A5304i) and Xcode 9 (9M137d) to the latest beta.
When I revert back to the previous beta, it goes away.
Making any GET https calls to a website results in a 400 response from the servers.
networkError(FoodAPI.NetworkError.responseStatusError(status: 400, message: "<NSHTTPURLResponse: 0x1c4239700> { URL: https://stackoverflow.com/ } { status code: 400, headers {\n Date = \"Thu, 22 Jun 2017 17:41:03 GMT\";\n} }"))
This is how I'm making the call
func callTask(_ request: URLRequest, completion: #escaping (ResultType<Data, NetworkError>) -> Void) {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let response = response as? HTTPURLResponse else {
completion(.failure(.responseInvalidFormat))
return
}
if response.statusCode >= 400 {
let error = response.description
completion(.failure(.responseStatusError(status: response.statusCode, message: error)))
return
}
guard let data = data else {
completion(.failure(.responseNoDataError))
return
}
completion(.success(data))
}
task.resume()
}
and this is how the request is generated:
private func generateRequest(urlString: String,
method: HttpMethod = .get,
contentType: ContentType = .json,
headers: [String: String] = ["":""],
body: [String: Any]? = nil) throws -> URLRequest {
guard let url = URL(string: urlString) else {
throw NetworkError.requestGenerationFail(url: urlString, httpMethod: method)
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.setValue("***** by Reza: info: reza#example.com", forHTTPHeaderField: "User-Agent")
request.allHTTPHeaderFields = headers
if contentType == .urlEncoded && headers["content-type"] ?? "" != ContentType.urlEncoded.rawValue {
request.addValue(ContentType.urlEncoded.rawValue, forHTTPHeaderField: "content-type")
}
if let body = body {
request.httpBody = try self.generateBody(body: body, contentType: contentType)
}
return request
}
Again, this works fine on the previous iOS 11 beta, however the new one results in a 400 response on https calls to web servers, with little to no data.
It's worth noting that I first make a call to Yelp's endpoint API which is hosted on https://api.yelp.com/. This connection succeeded without any issue. Any further calls to other host (https://stackoverflow.com, https://www.yelp.com) comes back with a 400.
When I run the code in simulator using the old beta, it works fine, when I deploy to my iPhone 6 that uses the new iOS it results in 400 from servers.
I haven't tested this against a server I own so I can view logs, but something along the way is breaking and causing servers to comeback with a bad request response. These same calls work fine using curl, postman, browsers and previous iOS builds.
I was hoping someone who has faced the same issue could shed some light.

I have pinpoint the issue. Although the reason is still elusive.
In the generateRequest function I pass a dictionary of [String:String] with default values ["":""] for the request headers.
It appears that request.allHTTPHeaderFields = ["":""] will break your request.
In prior builds this did not cause any issues but this will result in a bad request on the current beta.
Update
I filled a bug with Apple when I first came across this issue and this was their response:
The problem was that in CFNetwork-856.4.7 (part of seed 1) we made a mistake and removed headers that were blank. In Seed 2, (CFNetwork-861.1) we limited the headers we would remove to 4 “special” headers (Content-Type, User-Agent, Accept-Language, Accept-Encoding). So, that’s why you saw the change in behavior.
macOS El Capitan also sends the “=“ header that results the 400 HTTP status code. So, this is behaving correctly now in Seed 2. Seed 1 was an anomaly and was wrong.
Please let us know whether the issue is resolved for you by updating your bug report.

Related

How can I set cookie header using react and request-promise

"Cookie" header is never sent, even when browser console shows otherwise
I am trying to use request-promise and Reactjs to send request from my web application to SAP B1 Service Layer. First, I tried using the "headers" option in request-promise, then tried setting a cookie using rp.cookie("...") and jar.setCookie("...") but got errors both times.
The browser console is showing it is sending the "Cookie" header, but when I intercept the request using Burp Suite the header is never sent.
var rp = require('request-promise').defaults({jar:true});
var rd= require('request-debug');
rd(rp);
const b1jar=rp.jar();
let options = {
method: method,
rejectUnauthorized: false,
uri: url,
insecure:true,
jar:b1jar,
json: true,
resolveWithFullResponse:fullres,
// I TRIED TO USE THIS FIRST
headers:
{ 'Cookie': "B1SESSION="+(sessionStorage.SessionID||'')+';',
}
};
//THEN DELETED THE headers OPTION AND TRIED WITH THE jar OPTION
var b1cookie=rp.cookie('B1SESSION='+sessionStorage.SessionID);
b1cookie.setExpires(moment().add(sessionStorage.SessionTimeout,'m')
.toDate());
b1jar.setCookie(b1cookie,config.server);
res = await rp(options);
The expected result is that the B1SESSION Cookie should be sent when using rp(options), but it is not.
That API is pretty bad, try axios...
https://www.npmjs.com/package/react-axios

How to read post request in node using different ports in cloud 9

Okay, so I'm currently making an ionic app in cloud9. I've come to the point where I have to send a post request to my node.js server. Here's my problem:
When I post the data from my controller in angular and then console.log the data in my server.js my data seems missing
server.js
var http = require('http');
http.createServer(function(request, response) {
var headers = request.headers;
var method = request.method;
var url = request.url;
var body = [];
request.on('error', function(err) {
console.error(err);
}).on('data', function(chunk) {
body.push(chunk);
}).on('end', function() {
body = Buffer.concat(body).toString();
console.log(method);
console.log(request.data);
console.log("hi");
});
}).listen(8081, process.env.IP); // Activates this server, listening on port 8081.
server-side console:
POST
undefined
hi
client-side:
$http({
method : 'POST',
url : 'https://face-find-splacorn.c9users.io:8081',
data : "blah", //forms user object
headers : {'Content-Type': 'application/x-www-form-urlencoded'}
})
Also, I'm getting this error on the client side:
XMLHttpRequest cannot load https://face-find-splacorn.c9users.io:8081/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://face-find-splacorn.c9users.io' is therefore not allowed access. The response had HTTP status code 503.
(since it might be considered as a separate domain and will be declared unsafe^)
But at the end of the day, my posts requests still gets sent through because my server outputs the data that it knows.
I'm using ionic in cloud 9 so I need to run the server.js on a different port so the two "servers" won't conflict each other
But where is my post data? I would be very grateful if someone looked over my code and thanks in advance for helping me out!

Error 400 when POST'ing JSON in angularjs + Spark Single Page Application

I'm new to Single Page Application area and I try to develop app using angularjs and Spark framework. I get error 400 bad request when I want to post JSON from my website. Here is code fragment from client side:
app.controller('PostTripCtrl', function($scope, $http) {
$scope.newTrip = {};
$scope.submitForm = function() {
$http({
method : 'POST',
url : 'http://localhost:4567/trips/add',
data : $scope.newTrip,
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
}).success(function(data) {
console.log("ok");
}).error(function(data) {
console.log("error");
console.log($scope.newTrip);
});
};
});
Values that are to be assigned to newTrip are read from appropriate inputs in html file. Here is server-side fragment:
post("/trips/add", (req, res) -> {
String tripOwner = req.queryParams("tripOwner");
String startDate = req.queryParams("startDate");
String startingPlace = req.queryParams("startingPlace");
String tripDestination = req.queryParams("tripDestination");
int tripPrice = Integer.parseInt(req.queryParams("tripPrice"));
int maxNumberOfSeats = Integer.parseInt(req.queryParams("maxNumberOfSeats"));
int seatsAlreadyOccupied = Integer.parseInt(req.queryParams("seatsAlreadyOccupied"));
tripService.createTrip(tripOwner, startDate, startingPlace, tripDestination, tripPrice, maxNumberOfSeats,
seatsAlreadyOccupied);
res.status(201);
return null;
} , json());
At the end I obtain error 400 bad request. It is strange for me that when I want to see output on the console
System.out.println(req.queryParams());
I get json array of objects with values written by me on the website. However, when I want to see such output
System.out.println(req.queryParams("tripOwner"));
I get null. Does anyone have idea what is wrong here?
I think the main problem is that you are sending data to your Spark webservice with the 'Content-Type' : 'application/x-www-form-urlencoded' header. Try sending it as 'Content-Type' : 'application/json' instead, then in your Java code declare a String to receive req.body(), you'll see all your data in there.
Note: When you try to acces your data like this req.queryParams("tripOwner"); you're not accessing post data, but you're seeking for a get parameter called tripOwner, one that could be sent like this http://localhost:8080/trips/add?tripOwner=MyValue.
I would advise using postman to post a request to your server and see if it works. Try a different content type too. Try using curl and play with the various headers you are sending. 400 suggests the wrong data is being sent or expected data is missing or the data is the wrong type but based on your code you've provided I can see nothing wrong (but see below).
When your server receives a request log all request headers being received and see what changing them does. If it works in postman then you can change your client code to mirror the headers postman is using.
Does your spark server validate the data being sent before your controller code is hit? If so ensure you are adhering to all validation rules
Also on looking at your code again your client is sending the data in the post data but your server is expecting the data in the query string and not in the post data?
What happens if your server just sends a 201 response and does nothing else? Does your client get a 201 back? If so it suggests the hook up is working but there is something wrong with the code before you return a 201, build it up slowly to fix this.
Ok, I managed to cope with that using another approach. I used Jackson and ObjectMapper according to Spark documentantion. Thanks for your answers.
You can see more about that here: https://sparktutorials.github.io/2015/04/03/spark-lombok-jackson-reduce-boilerplate.html
You're probably just needed to enable CORS(Cross-origin resource sharing) in your Spark Server, which would have allowed you to access the REST resources outside the original domain of the request.
Spark.options("/*", (request,response)->{
String accessControlRequestHeaders = request.headers("Access-Control-Request-Headers");
if (accessControlRequestHeaders != null) {
response.header("Access-Control-Allow-Headers", accessControlRequestHeaders);
}
String accessControlRequestMethod = request.headers("Access-Control-Request-Method");
if(accessControlRequestMethod != null){
response.header("Access-Control-Allow-Methods", accessControlRequestMethod);
}
return "OK";
});
Spark.before((request,response)->{
response.header("Access-Control-Allow-Origin", "*");
});
Read more about pre-flighted requests here.

Internet Explorer 9 and angular-file-upload not working properly

I'm trying to use upload a file using angular and it works very well except on IE9.
I tried https://github.com/danialfarid/ng-file-upload but requires Flash when working with non-HTML5 browsers, so it does not work for me.
After that I tried https://github.com/nervgh/angular-file-upload and works! Except that after uploading the file I do some processing and maybe return an error by Bad Request. And this does not work in IE9. If the upload is successful my code does not see the Bad Request.
Well, I really don't think that the problem is my code, so I wont post anything here.
What I want is someone who had the same problems to shed me some light in what to do.
EDIT: In other words. In Chrome, status is 400 and in IE9 is 200.
uploader.onCompleteItem = function (fileItem, response, status, headers)
EDIT2: I think I found the source of the error. This is a angular-file-upload function
iframe.bind('load', function() {
try {
// Fix for legacy IE browsers that loads internal error page
// when failed WS response received. In consequence iframe
// content access denied error is thrown becouse trying to
// access cross domain page. When such thing occurs notifying
// with empty response object. See more info at:
// http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
// Note that if non standard 4xx or 5xx error code returned
// from WS then response content can be accessed without error
// but 'XHR' status becomes 200. In order to avoid confusion
// returning response via same 'success' event handler.
// fixed angular.contents() for iframes
var html = iframe[0].contentDocument.body.innerHTML;
} catch (e) {}
var xhr = {response: html, status: 200, dummy: true};
var headers = {};
var response = that._transformResponse(xhr.response, headers);
that._onSuccessItem(item, response, xhr.status, headers);
that._onCompleteItem(item, response, xhr.status, headers);
But my response is always undefined
I figure out a fix. In my project it only enters the catch statement if the server returned an error. So there I fire the event onError.

NancyFx RequiresAuthentication extension returns 303 and not 403

I'm not sure if I'm doing something wrong or there's a bug of some kind. If it is a bug, then I'd think this would have cropped up immediately. Either way:
I'm using the extension method RequiresAuthentication from here:
https://github.com/NancyFx/Nancy/blob/9d3c650575cba109e620ab163f4fda26a0536036/src/Nancy/Security/ModuleSecurity.cs
Which uses SecurityHooks
https://github.com/NancyFx/Nancy/blob/9d3c650575cba109e620ab163f4fda26a0536036/src/Nancy/Security/SecurityHooks.cs
Or the code specifically:
public static void RequiresAuthentication(this INancyModule module)
{
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
}
SecurityHooks:
public static Func<NancyContext, Response> RequiresAuthentication()
{
return UnauthorizedIfNot(ctx => ctx.CurrentUser.IsAuthenticated());
}
private static Func<NancyContext, Response> UnauthorizedIfNot(Func<NancyContext, bool> test)
{
return HttpStatusCodeIfNot(HttpStatusCode.Unauthorized, test);
}
private static Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test)
{
return (ctx) =>
{
Response response = null;
if (!test(ctx))
{
response = new Response { StatusCode = statusCode };
}
return response;
};
}
As you can see HttpStatusCode.Unauthorized should be returned if the user isn't authenticated. Seems easy enough. When I make an AJAX call to a route like:
Post["/comment/flag/{Id}"] = s =>
{
this.RequiresAuthentication();
return new IdResponse { Id = commentRepo.FlagComment(Context.CurrentUser.UserName, s.Id) };
};
I'm getting back a 303, which then redirects to the login URL with a 200. Yucky. Not expected at all.
Just to make sure I'm not totally crazy, I did the following and it worked fine:
Post["/comment/{Id}"] = s =>
{
if ((Context.CurrentUser == null) ||
String.IsNullOrWhiteSpace(Context.CurrentUser.UserName))
{
return HttpStatusCode.Forbidden;
}
var req = this.Bind<CommentRequest>();
return commentRepo.AddComment(Context.CurrentUser.UserName, s.Id, req.Comment);
};
This returns the expected 403. But who wants to paste this all over the place...not me. I'm trying to figure out how to write my own thing that executes before the route is run but I'm new to Nancy.
Curiously, if I change the:
return HttpStatusCode.Forbidden;
into a
return new Response { StatusCode = HttpStatusCode.Forbidden };
Then I get the 303 and 200 responses again. Something seems broken. Can anyone give me some insight or an answer of what I'm doing wrong? Nancy seems broken to me, specifically the Response class...
I had the same problem as you - Nothing is broken.
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
This code adds functions to the before and after pipelines. It takes every 401 and and changes it to a 303, since you've defined that the user should be redirected if he's not logged in.
In order to prevent that from happening, you can set the DisableRedirect property to true. And you can actually do this automatically on all Ajax Requests like this:
new FormsAuthenticationConfiguration()
{
...,
DisableRedirect = context.Request.IsAjaxRequest()
};
Also, make sure your request is setting the X-Requested-With header to XMLHttpRequest otherwise nanacy can't detect it's an ajax request. You can of course feel free to detect the proper requests differently (based on url, ...) in order to set DisableRedirect to true.
Currently it returns HttpStatusCode.Unauthorized or a 401 which seems to be correct. I tried setting the ContentType to application/json as well and that didn't help.
When I changed it to return a 403 it sends back a 403. Although I suppose technically this isn't the correct response, it is better then the mysterious 303 I was getting before.

Resources