nginx sent_http header variable not truthy - http

I would like nginx to change its behavior based on a response header from a backend.
My backend (also nginx) returns a Foo header:
HTTP/1.1 200 OK
Server: nginx/1.15.5
Foo: true
If the Foo header is set, I would like to change the behavior.
If I use the following config, $sent_http_foo doesn't appear to be truthy and the header isn't set.
if ($sent_http_foo) {
add_header "Foo-Header-Set" "true";
}
However, if I use this config instead:
add_header "Foo-Header-Value" "$sent_http_foo";
I see the new header with true in my response.
Is it possible to use the $sent_http_* variables in this way?

This is quite an old mailing list but it is related to your question - Nginx mailing
In the mailing list you can read -
Both "set" and "if" directives you mentioned are executed before
a request is sent to upstream.
and at this point there is no foo header in the response....
"if" or "set" directives isn't going to work, and this is what
causes behavior you see.
Moreover, you can read further about If Is Evil
Directive if has problems when used in location context, in some cases it doesn’t do what you expect but something completely different instead. In some cases it even segfaults. It’s generally a good idea to avoid it if possible.
In conclusion - the way you do is totally fine:
add_header "Foo-Header-Value" "$sent_http_foo";

Related

Restrict access to specific endpoint unless certain header value given

As I explained in title I want to restrict access unless the header is satisfied. In the given link, it is explained how to restrict a specific path access, however I couldn't find anything about how to add conditions to this restriction from my researches. Any idea about this problem?
Instead of deny/allow directives you can check any HTTP header value via $http_<name> variable and use return 403 to forbid access. For example, lets call our header X-Secret-Token:
location /some/path {
if ($http_x_secret_token != 'my-super-secret-password') {
return 403; # HTTP 403 Forbidden, the same code as generated by 'deny' directive
}
...
}
If you need some complex checks, you can use several chained map blocks (see the example).
From the research I've made it seems like the token approach presented here before seems to be the only suitable solution, a similar question has been asked here.

Angular: Custom headers are ignored by $http and $resource. Why?

I'm trying to access a REST service I don't control. First problem is that the service doesn't include a Access-Control-Allow-Origin header, which is a problem that, if I understand correctly, immediately limits me to JSONP.
Also, by default, this service sends XML rather than JSON, though it's capable of sending JSON. I think it should respond to my Accept header, the people responsible for the service say it looks at my Content-Type. That would mean I'd need to do a POST rather than a GET (though get makes more sense when I'm just getting some static data, right?).
Stubborn as I am, I'm trying my Accept header first. Since Angular only accepts JSON, I'd expect it to use the Accept: application/json header by default, but it doesn't, and it ignores my attempts to set it manually:
app.config(['$httpProvider', function($httpProvider){
console.log($httpProvider.defaults.headers.common);
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.headers.post['Accept'] = 'application/json, text/javascript';
$httpProvider.defaults.headers.post['Content-Type'] = 'application/json; charset=utf-8';
$httpProvider.defaults.headers.post['Access-Control-Max-Age'] = '1728000';
$httpProvider.defaults.headers.common['Access-Control-Max-Age'] = '1728000';
$httpProvider.defaults.headers.common['Accept'] = 'application/json, text/javascript';
$httpProvider.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8';
$httpProvider.defaults.useXDomain = true;
}]);
I do this again in the actual resource:
return $resource('http://foo.com/getStuff', {}, {
fetch: {
method:'JSONP',
params: params,
headers: {
'Accept':'application/json, text/javascript',
'Content-Type':'application/json; charset=utf-8'
},
isArray:false,
callback: 'JSON_CALLBACK'
}
});
But still, the request headers contain Accept: */*.
My question is: WHY? Why does Angular ignore my headers? And how do I get it to use the proper headers anyway?
And also: is there a way to use JSONP in a POST?
Edit: Originally I used Angular 1.0.7, but I just tried it with 1.2.3 and got the same results. Headers are ignored, yet everybody claims that this is the way to do it.
I also tried doing it directly with $http, rather than with $resource, with the same results.
Edit 2: Here's a JSFiddle. It's anonymized and doesn't use my real server, but using Firebug/developer tools, you can verify that it sends Accept: */* on both calls, despite my many attempts to set application/json headers. And that is my real problem here. On my real server, I'm getting an XML result because of that, despite my real server's ability to send JSON.
(Whether the real server supports jsonp is less relevant at the moment. This dummy server clearly doesn't, but that's okay. I just care about the headers.)
Edit 3: I've tried both solutions suggested below:
$http.defaults.headers.common['Accept'] = 'application/json, text/javascript';
$http.defaults.transformRequest.push(function (data, headersGetter) {
headersGetter().Accept = "application/json, text/javascript";
return data;
});
I've tried both statements separately. In the controller, and then in the service just before the http call itself. Still doesn't work.
Can someone give me a JsFiddle where this is shown to work?
Edit 4: I notice that when I use GET rather than JSONP, the Accept header is correct. But then the response is rejected because it doesn't have the correct header.
What kind of headers should a JSONP call have? Because there's a lot more headers in the JSONP call, but nothing that identifies it as JSONP. Does the server have to have explicit JSONP support for this to work? I suddenly realize I don't know nearly enough about jsonp.
I think your answer is here. According to the wiki, A JSONP call is executed through injection of a <script> tag to load the script from the host server, which responds by calling your callback, passing the data. A <script> tag generates a regular browser request (not an XmlHttpRequest), and the browser will send its own Accept header (it also sends its own User-Agent header, for example).
I would hope there is an easier client-side way to do this, but I think the only way may be the one suggested in the referenced post:
So, if you want to be able to set request headers for cross domain calls
you will have to setup a server side script on your domain that will
delegate the call to the remote domain (and set the respective
headers) and then send the AJAX request to your script.
EDIT: here is a (rejected) jQuery bug report about this same problem.
Some more background info:
In angular, callbacks are managed automagically, so if your say this:
$http({
method: "JSONP",
url: "http://headers.jsontest.com?callback=JSON_CALLBACK",
}).success(function(data) {
console.log('Return value:');
console.log(data);
}).error(function(data) {
console.log('Error!');
console.log(data);
})
a <script> tag will be created that looks more or less like this:
<script type="application/javascript"
src="http://headers.jsontest.com/?callback=angular.callbacks._1">
</script>
The content of the response to http://headers.jsontest.com/?callback=angular.callbacks._1 will be:
angular.callbacks._1({key1: "value1", key2: "value2"});
angular.callbacks._1 will contain your success function, and it will be called with the data.
While what you have is supposed to work according to the docs, my experience has been a bit different. To get around this issue, we did the following:
Create a "base controller" that gets added to the page either on the body or html tag.
In that controller, make the assignment using $http instead of $httpProvider. Because your base controller loads when the initial page loads, it is there for all other controllers and services that will run in your app.
I don't know why this works and the proscribed method does not, and I'd love to see an answer to your question that is better than this work-around, but at least this can get you moving forward with development again.
The following works for me - however, I do that during "runtime" with $http and I am not using $httpProvider during bootstrapping.
function SomeCtrl($http) {
$http.defaults.transformRequest.push(function (data, headersGetter) {
headersGetter().Accept = "application/json, text/javascript";
return data;
});
}
Edit
Here is a working jsFiddle version. Check the request which is done with Developer Tools/Firebug and see that "application/json, text/javascript" is requested.

How to do this without using if?

I have a list of blacklisted URLs that I would like to check to see if they are the http_referer for a particular request. If they are I am trying to set a cookie. I had tried doing it with the code below:
set $blackListUrls "www.somesite.com,www.anothersite.com,www.yetanother.com";
location / {
if($blackListUrls ~* $http_referer){...}
}
But while reading more about the if statement in nginx it sounds like I shouldn't use it. How can I do what I am talking about above without using an if statement?
Also - I have no experience with nginx so if you see a different way I should be checking this feel free to point it out.
If you want to add a cookie based on Referer, something like this should work:
map $http_referer $setcookie {
default "";
http://some.exact.url "cookiename=cookievalue";
~*example\.com "cookiename=cookievalue";
...
}
server {
...
location / {
add_header Set-Cookie $setcookie;
...
}
}
It will map the $http_referer variable (i.e. Referer header) into the $setcookie variable, empty by default (and not empty if referer is listed in the map). The add_header directive is used to add Set-Cookie header with the $setcookie value. The header will not be added if $addcookie evaluates to an empty string.
In the map you may use exact strings or regular expressions (with "~" or "~*" prefix).
See here for docs:
http://nginx.org/en/docs/http/ngx_http_map_module.html
http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
You may also want take a look at nginx referer module. It is designed to do a whitelisting of referrers though, not blacklisting, and approach using map would be easier for a blacklist.
If you read http://wiki.nginx.org/IfIsEvil carefully you'll notice that:
if's directly in the server block are OK
if's in a location block on the other hand are unsave unless they hold only a return or rewrite directive
So to following would actually be perfectly save:
server {
location / {
set blackListUrls "www.somesite.com,www.anothersite.com,www.yetanother.com";
if($blackListUrls ~* $http_referer){return 403;}
}
}
In other words you don't need to get rid of all ifs
Please note: nginx has referer module.

Is it possible to set some http headers while http-redirect(302 or 307)?

Is it possible to set some http headers while http-redirect(302 or 307)?
<?
header("some-header: xxx");
header("Location: http://other.domain.com/foo.php",TRUE,307);
?>
You can basically set whatever http headers you want either as the server or the client.
If you are indicating a redirect you should supply the Location header as your example suggests. You should also ensure that your response headers refer to that response rather than the resource that the client is being redirected to. i.e. your headers here could include Content-Length: 0, omit the Content-Type header and so on.
Not sure if this is what you're after - this question could do with a bit more detail.
You can always do the redirection 301/307.
There are ways to do it
1) Do it through java code :
response.setStatus(307);
response.setHeader("Location",url);
2) THe same thing can be done in JSPs.
A tip here is: Always use the setHeader function and not the addHeader function as they behave in different ways.

Can I send a HTTP redirect to a url with an anchor?

Is it possible to send a response with 302 status code to a url like this:
http://mysite.com/something/#somethingelse
Yes, you can use the fragment identifier. This was a known bug in the HTTP spec, fixed in subsequent revisions of the spec. See RFC 9110.
Following the HTTP specification, the value for the Location header field must be an absoluteURI value. And that is according to the RFC 3986 (they just changed the name from absoluteURI to absolute-URI):
absolute-URI = scheme ":" hier-part [ "?" query ]
So theoretically the fragment is not allowed as part of the value. But the browsers can handle it.
With strict reading RFC2616 does not allow fragments in Location header values, since they are not part of absolute URIs. However, with the IETF's HTTP rewrite draft this was fixed.
Recently Julian put up a comparison how browsers handle URI fragments (that's what the HTML anchor tags deal with) in the Location header: http://www.greenbytes.de/tech/tc2231/redirects.html
So the answer ist: Yes, you can put fragments in Location header URIs.
There appears to be no problem in doing so from PHP:
Header(
"Location: http://en.wikipedia.org/wiki/HTTP#Status_codes",
true,
302
);
Yes. It's the browser which doesn't send the hash to the server, not the other way around.
While the original RFC 2616 allowed only absoluteURI in the Location header, as the other (older) answers explain, the current RFC 7231 allows any URI-reference, either a full URI (with fragment possibly included), or even a relative URI, resolved against the current effective URI. And it even explicitly describes the behavior of fragments during redirection (if the Location header includes the fragment, it is used, if not, the original fragment is inherited and applied after the redirection).
I.e. yes, you can, it is even officially standardized now.

Resources