How to set status code for certain URLs with IF in htaccess - apache

I have URLs like https://example.com/page/1234/ and https://example.com/page/9876/. For these URLs i want set certain status code via htaccess (Apache 2.4).
I try to do it with
<If "%{REQUEST_URI} =~ (1234|9876)\/$">
Header set Status "HTTP/1.1 410 Gone"
</If>
but i seem to have an error in the code, because i don't see the new status code as response in developer tools. How should it be done on the correct way?
PS: i can't use 'rewriteRule' - this kind of setting 410 triggers ErrorDocument, what i don't want. For these two URLs i only want to set the status code. For other URLs, which get 410 on the native way, ErrorDocument shold be triggered.

You may use this block with <If> expression that uses default ErrorDocument 410 for a specific URL pattern:
RewriteEngine On
<If "%{REQUEST_URI} =~ m#/(1234|9876)/?$#">
# only for above URL disable ErrorDocument
ErrorDocument 410 default
# set status=410
RewriteRule . - [L,G]
</If>

Are you wanting the "normal page response" (as generated by your application), but with a 410 HTTP status?
100% correct: usual page, but with response status 410 Gone
Triggering the 410 response in Apache will always serve the appropriate Apache ErrorDocument. What you could do is set the 410 ErrorDocument itself to the same URL and trigger the 410 response in the usual way. However, we need to be careful not to create a "rewrite-loop".
For example:
<If "%{REQUEST_URI} =~ m#^/page/(1234|9876)/$# && %{ENV:REDIRECT_STATUS} == ''">
# Capture the URL-path after the slash prefix
SetEnvIf Request_URI "^/(.+)" REL_URL_PATH=$1
# Dynamic ErrorDocument to the same as the requested URL
# The slash prefix in the directive is necessary to be seen as a local URL-path
ErrorDocument 410 /%{reqenv:REL_URL_PATH}
# Trigger 410 Gone
RewriteRule ^ - [G]
</If>
This requires Apache 2.4.13+ due to the expression syntax in the ErrorDocument directive.
The check against the REDIRECT_STATUS in the <If> expression is necessary to avoid a rewrite loop (500 response) when serving the error document itself.
The alternative is to set the 410 response code in your application itself, which would be my preference.
Aside: Setting the Status HTTP response header, which appears to be what you are trying to do in the question simply sets a Status HTTP response header, it does not change the HTTP response status itself. (The Status response header is a non-standard header used by CGI scripts to indicate to the webserver what response code should be set.)
UPDATE: in my tests if i add to htaccess only the rule Header set Status "HTTP/1.1 410 Gone", without any condition, it works like i expect: all URLs from this directory get the header 410 Gone but are still available (ErrorDocument isn't triggered).
Maybe your server is configured differently and is perhaps behind a proxy that sets the HTTP response? But as mentioned above, that simply sets a Status HTTP response header, it doesn't change the HTTP response code on the request. It doesn't "work" on my test server. (If used at all, the Status header doesn't normally contain the protocol, it would simply be Header set Status "410 Gone".)
So, if this does work for you then you just need to "correct" the syntax in your Apache expression.
For example:
<If "%{REQUEST_URI} =~ m#/(1234|9876)/$#">
Header set Status "HTTP/1.1 410 Gone"
</If>
The above matches any URL that simply ends with /1234/ or /9876/. Or, to be more specific and match the entire URL use m#^/page/(1234|9876)/$#. This uses the alternative syntax for delimiting regex (ie. m#<regex>#), instead of using slashes (ie. /<regex>/), in order to avoid having to escape the slashes in the pattern.
Alternatively, you don't need the Apache expression, you could use SetEnvIf and set the header conditionally based on the environment variable.
For example:
SetEnvIf Request_URI "/(1234|9876)/$" GONE=1
Header set Status "HTTP/1.1 410 Gone" env=GONE

With your shown samples please try following. Please do clear your browser cache before testing your URLs.
RewriteEngine ON
RewriteRule ^page/(1234|9876)/?$ - [NC,R=410,L]

Related

Apache - deny non-local requests that match a specific URL pattern

I need to configure my Apache (2.4) in a way, that it prevents answering specific requests
I have an application that runs on a server myapplication.com.
Browsing the application triggers some further ajax requests on the server, having the pattern myapplication.com/abc. These kind of "abc"-requests must be prohibited, when being called from a different context then this one. Thus, is should not be possible to call this request in a different tab. Neither should it be possible to call these requests outside a browser context, for example via curl or wget or any non-browser http client.
The directive must look something like this
<If "Request has not been triggered as ajax from within my application" && %{REQUEST_URL} contains 'abc' ">
Require all denied
</If>
This directive causes an error and I dont know why, changing =~ to !~ is semantically false but does not cause an error.
<If "%{HTTP_REFERER} !~ /myapplication.com/ && %{QUERY_STRING} =~ /abc/ ">
Require all denied
</If>
What can be wrong with that?
Any help debugging the apache config would also be appreciated - I don't get any information why this directive causes errors.
Any XMLHttpRequest or fetch call made from myapplication.com in browser will contain myapplication.com as referer header.
The following apache conf snippet will block requests which referrer isn't myapplication.com.
<Location "/abc">
RewriteEngine on
RewriteCond "%{HTTP_REFERER}" "!myapplication.com"
RewriteRule . - [R=403,L]
</Location>
This configuration requires mod_rewrite which can be enabled with a2enmod rewrite if not already done.

Apache htaccess return 404 code even if page exists without using rewrite mod:

To protect my server from bots, I want to return a 404 error page if certain files are requested EVEN IF THEY EXIST but without using the rewrite mod. It is possible?
You can use a mod_alias Redirect (or RedirectMatch) directive.
For example:
Redirect 404 /file1.html
Redirect 404 /file2.html
This doesn't actually trigger a "redirect", as in an external 3xx redirect. It sends the stated 404 response if one of the URLs is requested (regardless of whether that file exists or not).
Reference:
https://httpd.apache.org/docs/current/mod/mod_alias.html#redirect
If there is a pattern to these URLs then use a RedirectMatch directive instead, which matches against a regex rather than using simple prefix-matching. For example, to serve a 404 for all requests to the /secret subdirectory (and all files within) then use the following:
RedirectMatch 404 ^/secret($|/)
To customise the 404 response use an ErrorDocument directive:
ErrorDocument 404 /error-docs/404.html

Redirect 4xx to a custom page with 200 response code in httpd.conf

I am trying to get any 403 request to go to a custom error page with 200 OK request (for security reason).
Tried this to redirect any 4xx error code to a custom error page
ErrorDocument 403 /shared/error.html
But this will not change the response code (it will still be 404).
There this option but it will give 302 here
Tried this to change the error code:
RewriteEngine on
RewriteRule ^/shared/.*/$ /shared/error.html [R=200,L]
But somehow it doesn't redirect to the custom error page I want to.
Tried it with absolute path as well:
RewriteEngine on
RewriteRule ^/shared/.*/$ https://%{SERVER_NAME}/shared/error.html [R=200,L]
Still doesn't work. Is it not possible or am I missing something obvious? This is my first time modifying but I did some research already. Any help is appreciated.
If you can use ErrorDocument for the code 200:
ErrorDocument 200 /shared/error.html
RewriteEngine on
RewriteRule ^/shared/.*/$ anything [R=200]
When the rule is applied it will execute the redirection to the ErrorDocument straight away, and that is the reason why you can put "anything" in there.
Check the documentation for the R flag (https://httpd.apache.org/docs/current/rewrite/flags.html):
The status code specified need not necessarily be a redirect (3xx) status code. However, if a status code is outside the redirect range (300-399) then the substitution string is dropped entirely, and rewriting is stopped as if the L were used.
Beware that you might not want this redirection for the whole code, so you should apply it within a context. And take cara with your regex so you don't fall in a loop.
Source: https://www.askapache.com/htaccess/crazy-advanced-mod_rewrite-tutorial/
If you'd use a php-script for example instead of your static html-document, you can override the response code inside your script.
See: http://php.net/manual/en/function.http-response-code.php

Return empty response from Apache [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about a specific programming problem, a software algorithm, or software tools primarily used by programmers. If you believe the question would be on-topic on another Stack Exchange site, you can leave a comment to explain where the question may be able to be answered.
Closed 11 months ago.
Improve this question
I'm using Apache to return CORS responses to speed-up the requests (previously I handled this in application code but this was too slow). In my VirtualHost, I have the following Apache code:
SetEnvIfNoCase Access-Control-Request-Method "(GET|POST|PUT|DELETE|OPTIONS)" IsPreflight=1
SetEnvIfNoCase Origin "http(s)?://(myorigin.com)$" AccessControlAllowOrigin=$0$1
Header always set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" env=IsPreflight
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, Accept" env=IsPreflight
Header always set Access-Control-Max-Age "7200" env=IsPreflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteCond %{ENV:IsPreflight} 1
RewriteRule ^(.*)$ $1 [R=200,L]
This actually works surprinsigly good. It detects if the request is a preflight request, and if it is, it sends the appropriate headers. There is only one catch: the preflight request returns 200 (so the browser sends the normal request), but the body is a 200 ERROR (haha):
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>200 OK</title>
</head><body>
<h1>OK</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator at
you#example.com to inform them of the time this error occurred,
and the actions you performed just before this error.</p>
<p>More information about this error may be available
in the server error log.</p>
</body></html>
While this works correctly, I'd like to remove this ugly error from the preflight body, but I didn't find any way to tell Apache to actually returns an empty body.
Thanks!
Response with 204 Code (No Content) for OPTIONS requests.
For example:
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]
A 204 should be sufficient for CORS restriction. In other cases, however, an empty response might be desired with different status codes.
TL;DR
Be warned: ErrorDocument with 2xx and 3xx is undocumented behavior.
To send a response with an empty body, combine RewriteRule with ErrorDocument.
# for 2xx, 4xx, 5xx, with or without flag [L]
RewriteRule ^ - [R=code,L]
# for 3xx
RewriteRule pattern substitution [R=code,L]
# for apache 2.4.13 and later
ErrorDocument code %{unescape:%00}
# for older versions,
# this can't be done without an empty file,
# the closest you can get using only the configuration is
ErrorDocument code " "
# if you're willing to use an empty file
ErrorDocument code /path/to/empty/file
About 204 No Content
RewriteRule with 204 No Content works as expected except that major browsers handle it differently from other status codes such as 200 and 301 with empty content.
As specified in MDN: 204 No Content,
The HTTP 204 No Content success status response code indicates that the request has succeeded, but that the client doesn't need to go away from its current page.
If you visit a URL and it returns 204, Firefox and Chromium will act as if nothing has happened, leaving the content of the page and URL in the navigation bar unchanged.
This may be desired or not.
But if you want the same effect as a 200 response with empty content and care about Firefox/Chromium/etc, or want to use other HTTP status codes, then RewriteRule with 204 is not the solution.
Custom response body with ErrorDocument
The closest way I can find to return an empty response is to return a custom response body using ErrorDocument, and set the desired body content.
Despite Apache's Custom Error Responses documentation that states:
Customized error responses can be defined for any HTTP status code designated as an error condition - that is, any 4xx or 5xx status.
It turns out customized responses can be specified by ErrorDocument for 2xx and 3xx status codes.
And we can use RewriteRule's [R] flag to enter such status for ErrorDocument to take effect.
However, this is not the end of the story, as the configuration below,
ErrorDocument 200 ""
will raise this error message,
ErrorDocument takes two arguments, Change responses for HTTP errors
It seems the quoted "empty string" doesn't make way to the ErrorDocument directive, and Apache parses this configuration line as a directive with a single argument.
I can't find a satisfying explanation for this or any hint for an empty string as an argument after reading Apache's Syntax of the Configuration Files, and looking for "space" and "quote" in the documentation. It only mentions quotes in the case of arguments containing spaces.
Digging into ErrorDocument
As Apache's Custom Error Responses says,
The syntax of the ErrorDocument directive is:
ErrorDocument <3-digit-code> <action>
where the action will be treated as:
A local URL to redirect to (if the action begins with a "/").
An external URL to redirect to (if the action is a valid URL).
Text to be displayed (if none of the above). The text must be wrapped in quotes (") if it consists of more than one word.
Also noted in Apache's ErrorDocument Directive,
From 2.4.13, expression syntax can be used inside the directive to produce dynamic strings and URLs.
We should be able to use expressions in ErrorDocument from 2.4.13 on.
Digging into expressions
After some experiments, I find not all expressions work in ErrorDocument. But variable as specified in expression's BNF notation can be interpreted in ErrorDocument,
variable ::= "%{" varname "}"
| "%{" funcname ":" funcargs "}"
(rebackref might work too, but I'm not sure how that can be used in ErrorDocument.)
Now we only need some empty variables or functions that can evaluate to an empty string.
And there is an empty string expression, in the form of a function,
unescape
Unescape %hex encoded string, leaving encoded slashes alone; return an empty string if %00 is found
So here is the solution to return an empty response body,
ErrorDocument 200 %{unescape:%00}
More about ErrorDocument
Why is there a non-empty response body as mentioned in the question in the first place?
I find this in apache's ErrorDocument Directive,
In the event of a problem or error, Apache httpd can be configured to do one of four things,
output a simple hardcoded error message
output a customized message
internally redirect to a local URL-path to handle the problem/error
redirect to an external URL to handle the problem/error
Since the error document for 200 is not specified, Apache decided to use the hardcoded error message for 200.
Details about the hardcoded error message is not in the documentation and has to be foundd in the source code. But I guess there is no such error message for 200, and so we have this internal error/misconfiguration thing.
The exact same response as mentioned in the question can be obtained by
ErrorDocument 200 default
since Apache's ErrorDocument Directive mentions this,
Additionally, the special value default can be used to specify Apache httpd's simple hardcoded message.

mod_rewrite help to change Content-disposition based on URI

I have a directory of mp3 files want to have be able to serve them inline or giving the user an option to download based on the request URI.
/media/file1.mp3 -- in this case, I just want to serve the file and let the browser play it.
/media/download/file1.mp3 -- in this case, I want to make it easy for a user to download the file instead.
I have been able to accomplish this with mod_rewrite and php (using the header() and readfile() function) but I would rather do it all with mod_rewrite, mod_header etc if possible.
IfDefine will check variables set on start-up of Apache so that won't work. A valid config would be:
SetEnvIf Request_URI ^/media/download/ force-download
Header set Content-Disposition attachment env=force-download
Also changing the Content-Type is not necessary to force a download.
With mod_rewrite you can only change some specific header fields but to which the Content-Disposition header field doesn’t belong. You could only change the Content-Type header field:
RewriteRule ^media/[^/]+\.mp3$ - [L,T=audio/mpeg]
RewriteRule ^media/download/[^/]+$ - [L,T=application/octet-stream]
And if you want to use a mod_headers+mod_setenvif solution:
SetEnvIf Request_URI ^/media/download/ force-download
<IfDefine force-download>
Header set Content-Disposition attachment
Header set Content-Type application/octet-stream
</IfDefine>
If you want to base rule on parameters in URI, here is the logic / syntaxe (adapt RewriteCond) :
RewriteCond %{QUERY_STRING} ^dl=1$
RewriteRule ^ - [L,E=FORCEDOWNLOAD:1]
Header set Content-Disposition attachment env=FORCEDOWNLOAD
Here, we force download if the only parameter is "dl=1"