1
\$\begingroup\$

Any suggestion is more than welcome! I had to do a few tricks in rewrite rules (checks on ENV:REDIRECT_STATUS) to avoid infinite loops, but it works fine.

error.php

<?php

$code = 0;

if (isset($_GET['code']))
    $code = (int)$_GET['code'];
else if (isset($_SERVER['REDIRECT_STATUS']))
    $code = (int)$_SERVER['REDIRECT_STATUS'];

switch ($code)  
{
    case 400:
        $message = 'Bad Request';
        break;

    case 401:
        $message = 'Unauthorized';
        break;

    case 402:
        $message = 'Payment Required';
        break;

    case 403:
        $message = 'Forbidden';
        break;

    case 404:
        $message = 'Not Found';
        break;

    case 405:
        $message = 'Method Not Allowed';
        break;

    case 406:
        $message = 'Not Acceptable';
        break;

    case 407:
        $message = 'Proxy Authentication Required';
        break;

    case 408:
        $message = 'Request Timeout';
        break;

    case 409:
        $message = 'Conflict';
        break;

    case 410:
        $message = 'Gone';
        break;

    case 411:
        $message = 'Length Required';
        break;

    case 412:
        $message = 'Precondition Failed';
        break;

    case 413:
        $message = 'Request Entity Too Large';
        break;

    case 414:
        $message = 'Request-URI Too Long';
        break;

    case 415:
        $message = 'Unsupported Media Type';
        break;

    case 416:
        $message = 'Requested Range Not Satisfiable';
        break;

    case 417:
        $message = 'Expectation Failed';
        break;

    case 418:
        $message = 'I\'m a Teapot';
        break;

    case 421:
        $message = 'Misdirected Request';
        break;

    case 422:
        $message = 'Unprocessable Entity';
        break;

    case 423:
        $message = 'Locked';
        break;

    case 424:
        $message = 'Failed Dependency';
        break;

    case 425:
        $message = 'Unordered Collection';
        break;

    case 426:
        $message = 'Upgrade Required';
        break;

    case 428:
        $message = 'Precondition Required';
        break;

    case 429:
        $message = 'Too Many Requests';
        break;

    case 431:
        $message = 'Request Header Fields Too Large';
        break;

    case 451:
        $message = 'Unavailable For Legal Reasons';
        break;

    case 500:
        $message = 'Internal Server Error';
        break;

    case 501:
        $message = 'Not Implemented';
        break;

    case 502:
        $message = 'Bad Gateway';
        break;

    case 503:
        $message = 'Service Unavailable';
        break;

    case 504:
        $message = 'Gateway Timeout';
        break;

    case 505:
        $message = 'HTTP Version Not Supported';
        break;

    case 506:
        $message = 'Variant Also Negotiates';
        break;
    case 507:
        $message = 'Insufficient Storage';
        break;

    case 508:
        $message = 'Loop Detected';
        break;

    case 509:
        $message = 'Bandwidth Limit Exceeded';
        break;

    case 510:
        $message = 'Not Extended';
        break;

    case 511:
        $message = 'Network Authentication Required';
        break;

    default:
        $code = '???';
        $message = 'Unknown Error';
        break;
}

echo $code.' '.$message;

?>

.htaccess

############
# SETTINGS #
############

Options All +FollowSymlinks -ExecCGI -Indexes -MultiViews

DirectoryIndex index.php
DirectorySlash On
IndexIgnore *

SetEnv TZ [Your Time Zone]

############
# BROWSERS #
############

BrowserMatch MSIE\ 6\. image-toolbar
BrowserMatch MSIE\ (?:8|9|10)\. legacy-document

Header set imagetoolbar "no" env=image-toolbar
Header set X-UA-Compatible "IE=edge,chrome=1" env=legacy-document

<FilesMatch "\.(?:atom|bin|bmp|css|cur|eot|exe|gif|g?zip|gz|ico|jp(?:eg?|g)|js(?:on)?|og[agv]|opus|otf|pdf|png|rdf|rss|svgz?|swf|tiff?|tt[cf]|txt|webp|woff2?|xml)$">
    Header unset X-UA-Compatible env=legacy-document
</FilesMatch>

###################
# FILES - CACHING #
###################

FileETag None

Header unset ETag
Header unset Pragma

ExpiresActive on
ExpiresDefault A2592000

# Data Interchange Files
ExpiresByType application/atom+xml      A3600
ExpiresByType application/json          A0
ExpiresByType application/rdf+xml       A3600
ExpiresByType application/rss+xml       A3600
ExpiresByType application/xml           A0

# Image Files
ExpiresByType image/bmp             A31536000
ExpiresByType image/gif             A31536000
ExpiresByType image/jpeg            A31536000
ExpiresByType image/png             A31536000
ExpiresByType image/svg+xml         A31536000
ExpiresByType image/tiff            A31536000
ExpiresByType image/webp            A31536000
ExpiresByType image/x-icon          A31536000

# Script Files
ExpiresByType application/javascript        A31536000

# Style Files
ExpiresByType text/css              A31536000

# Other Files
ExpiresByType text/html             A0

#######################
# FILES - COMPRESSION #
#######################

SetEnvIfNoCase ^(?:Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^(?:(?:gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ has-accept-encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=has-accept-encoding

AddOutputFilterByType DEFLATE application/atom+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE image/bmp
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/tiff
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain

AddEncoding gzip .svgz

####################
# FILES - ENCODING #
####################

AddLanguage en .atom
AddLanguage en .css
AddLanguage en .js
AddLanguage en .json
AddLanguage en .rdf
AddLanguage en .rss
AddLanguage en .txt
AddLanguage en .xml

AddDefaultCharset UTF-8
AddCharset UTF-8 .atom
AddCharset UTF-8 .css
AddCharset UTF-8 .js
AddCharset UTF-8 .json
AddCharset UTF-8 .rdf
AddCharset UTF-8 .rss
AddCharset UTF-8 .xml

####################
# FILES - HANDLERS #
####################

AddHandler application/x-httpd-php .css
AddHandler application/x-httpd-php .js

######################
# FILES - MIME TYPES #
######################

# Archive Files
AddType application/x-gzip      .gz .gzip
AddType application/zip         .zip

# Data Interchange Files
AddType application/atom+xml        .atom
AddType application/json        .json
AddType application/rdf+xml     .rdf
AddType application/rss+xml     .rss
AddType application/xml         .xml

# Font Files
AddType application/font-woff       .woff
AddType application/font-woff2      .woff2
AddType application/vnd.ms-fontobject   .eot
AddType application/x-font-ttf      .ttc .ttf
AddType font/opentype           .otf

# Image Files
AddType image/bmp           .bmp
AddType image/gif           .gif
AddType image/jpeg          .jpe .jpeg .jpg
AddType image/png           .png
AddType image/svg+xml           .svg .svgz
AddType image/tiff          .tif .tiff
AddType image/webp          .webp
AddType image/x-icon            .cur .ico

# Media Files
AddType application/x-shockwave-flash   .swf
AddType audio/ogg           .oga .ogg .opus
AddType video/ogg           .ogv

# Script Files
AddType application/javascript      .js

# Style Files
AddType text/css            .css

# Text Files
AddType application/pdf         .pdf
AddType text/plain          .txt

# Other Files
AddType application/octet-stream    .bin .exe

###########################
# SECURITY - CROSS-ORIGIN #
###########################

<FilesMatch "\.(?:bmp|cur|gif|ico|jp(?:eg?|g)|png|svgz?|tiff?|webp)$">
    SetEnvIf Origin ":" is-cors
    Header set Access-Control-Allow-Origin "*" env=is-cors
</FilesMatch>

<FilesMatch "\.(?:eot|otf|tt[cf]|woff2?)$">
    Header set Access-Control-Allow-Origin "*"
</FilesMatch>

#####################
# SECURITY - ERRORS #
#####################

ErrorDocument 400 /error/400/
ErrorDocument 401 /error/401/
ErrorDocument 402 /error/402/
ErrorDocument 403 /error/403/
ErrorDocument 404 /error/404/
ErrorDocument 405 /error/405/
ErrorDocument 406 /error/406/
ErrorDocument 407 /error/407/
ErrorDocument 408 /error/408/
ErrorDocument 409 /error/409/
ErrorDocument 410 /error/410/
ErrorDocument 411 /error/411/
ErrorDocument 412 /error/412/
ErrorDocument 413 /error/413/
ErrorDocument 414 /error/414/
ErrorDocument 415 /error/415/
ErrorDocument 416 /error/416/
ErrorDocument 417 /error/417/
ErrorDocument 422 /error/422/
ErrorDocument 423 /error/423/
ErrorDocument 424 /error/424/
ErrorDocument 426 /error/426/
ErrorDocument 428 /error/428/
ErrorDocument 429 /error/429/
ErrorDocument 431 /error/431/
ErrorDocument 500 /error/500/
ErrorDocument 501 /error/501/
ErrorDocument 502 /error/502/
ErrorDocument 503 /error/503/
ErrorDocument 504 /error/504/
ErrorDocument 505 /error/505/
ErrorDocument 506 /error/506/
ErrorDocument 507 /error/507/
ErrorDocument 508 /error/508/
ErrorDocument 510 /error/510/
ErrorDocument 511 /error/511/

############################
# SECURITY - MISCELLANEOUS #
############################

Header set X-Content-Type-Options "nosniff"
Header unset Server
Header unset X-Powered-By

LimitRequestBody 1048576

ServerSignature Off

############
# REWRITES #
############

# Settings
RewriteEngine On
RewriteBase /

# Security: Protected Resources
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_URI} (?:/|^)\. [OR]
RewriteCond %{REQUEST_URI} \.(?:ba?k|cfg|conf|dist|fla|in[ci](?:\.php)?|info|log|po|psd|sh|sql|sw[op])$ [NC,OR]
RewriteCond %{REQUEST_URI} ^#.*#$ [OR]
RewriteCond %{REQUEST_URI} ~$
RewriteRule ^ - [F,L]

# Security: Vulnerabilities
RewriteCond %{REQUEST_METHOD} (?:DELETE|DEBUG|HEAD|TRACE|TRACK) [NC,OR]
RewriteCond %{THE_REQUEST} (?:\\n|\\r|%00|%0A|%0D|cgi-bin|etc/passwd) [NC,OR]
RewriteCond %{THE_REQUEST} (?:/\*|\?)\ HTTP/ [NC,OR]
RewriteCond %{QUERY_STRING} (?:/self/|cPath=) [NC,OR]
RewriteCond %{QUERY_STRING} (?:127\.0\.0\.1|localhost|loopback) [NC,OR]
RewriteCond %{QUERY_STRING} (?:<|%3C).*(?:embed|iframe|object|script|(?:[^e]*e)+mbed|(?:[^i]*i)+frame|(?:[^o]*o)+bject|(?:[^s]*s)+cript).*(?:>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} (?:\.{1,}/)+(?:bin|etc|motd) [NC,OR]
RewriteCond %{QUERY_STRING} (?:\.|%2E)(?:\.|%2E)(?:/|%2F) [NC,OR]
RewriteCond %{QUERY_STRING} (?:\\n|\\r|%00|%0A|%0D) [NC,OR]
RewriteCond %{QUERY_STRING} (?:BASE64_(?:EN|DE)CODE|LOAD_FILE|MD5|OUTFILE|SHA1|SP_EXECUTESQL) [NC,OR]
RewriteCond %{QUERY_STRING} (?:CONCAT[^\(]*\(|UNION(?:(?:[^A]*A)+LL)?(?:[^S]*S)+ELECT) [NC,OR]
RewriteCond %{QUERY_STRING} (?:ftp|https?)(?::|%3A)(?:/|%2F)?(?:/|%2F)? [NC,OR]
RewriteCond %{QUERY_STRING} (?:GLOBALS|_REQUEST)(?:=|\[|\%[a-z0-9]{0,2}) [NC,OR]
RewriteCond %{QUERY_STRING} =\|w\| [NC,OR]
RewriteCond %{QUERY_STRING} =PHP[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12} [NC,OR]
RewriteCond %{QUERY_STRING} [a-z0-9_]=(?:(?:\.\.//?)+|/(?:[a-z0-9._]//?)$) [NC,OR]
RewriteCond %{QUERY_STRING} mosConfig_[a-z_]{1,21}(?:=|%3D) [NC,OR]
RewriteCond %{QUERY_STRING} proc/self/environ [NC]
RewriteRule ^ - [F,L]

# Cosmetics: Remove WWW Subdomain
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteCond %{HTTPS}s ^on(s)|
RewriteRule ^ http%2://%1%{REQUEST_URI} [R=301,L]

# Cosmetics: Remove Trailing Slashes
RewriteCond %{THE_REQUEST} //
RewriteRule .* $0 [R=301,L]

# Cosmetics: Remove PHP Extension
RewriteCond %{ENV:REDIRECT_STATUS} !(?:^4|^5)
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\ .*\.php
RewriteRule ^(.*)\.php /$1/ [R=301,L]

# Cosmetics: Remove Index
RewriteRule ^(.*)index/$ /$1 [R=301,L]

# Cosmetics: Force Trailing Slash
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule .*[^/]$ $0/ [R=301,L]

# Functions: Dynamic Versioning
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)\.[0-9]+\.(bmp|css|cur|gif|ico|jp(?:eg?|g)|js|png|svgz?|tiff?|webp)$ $1.$2 [L]

# Functions: Pretty URLs
RewriteRule ^error/([^/]+)/$ /error.php?code=$1 [L]
RewriteRule ^threads/([0-9]+)/$ /thread.php?id=$1 [L]
RewriteRule ^users/([0-9]+)/$ /users.php?id=$1 [L]

# Functions: Resources Lookup
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteCond %{REQUEST_URI} /.*/$
RewriteRule ^(.*)/$ $1\.php [L]
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

This is not a full answer, as I'm not that guru about Apache (and your .htaccess covers a lot of cases).

But merely regarding your error.php script, I suggest a reduced code, improving maintainability:

$messages = [
  400 => 'Bad Request',
  401 => 'Unauthorized',
  402 => 'Payment Required',
  403 => 'Forbidden',
  404 => 'Not Found',
  405 => 'Method Not Allowed',
  406 => 'Not Acceptable',
  407 => 'Proxy Authentication Required',
  408 => 'Request Timeout',
  409 => 'Conflict',
  410 => 'Gone',
  411 => 'Length Required',
  412 => 'Precondition Failed',
  413 => 'Request Entity Too Large',
  414 => 'Request-URI Too Long',
  415 => 'Unsupported Media Type',
  416 => 'Requested Range Not Satisfiable',
  417 => 'Expectation Failed',
  418 => 'I\'m a Teapot',
  421 => 'Misdirected Request',
  422 => 'Unprocessable Entity',
  423 => 'Locked',
  424 => 'Failed Dependency',
  425 => 'Unordered Collection',
  426 => 'Upgrade Required',
  428 => 'Precondition Required',
  429 => 'Too Many Requests',
  431 => 'Request Header Fields Too Large',
  451 => 'Unavailable For Legal Reasons',
  500 => 'Internal Server Error',
  501 => 'Not Implemented',
  502 => 'Bad Gateway',
  503 => 'Service Unavailable',
  504 => 'Gateway Timeout',
  505 => 'HTTP Version Not Supported',
  506 => 'Variant Also Negotiates',
  507 => 'Insufficient Storage',
  508 => 'Loop Detected',
  509 => 'Bandwidth Limit Exceeded',
  510 => 'Not Extended',
  511 => 'Network Authentication Required',
  '???' => 'Unknown Error',
];
array_key_exists(
  ($code =
    @$_GET['code'] ? $_GET['code'] : (
      @$_SERVER['REDIRECT_STATUS'] ? $_SERVER['REDIRECT_STATUS'] : 0)
    ), $messages ? $code : '???'
);
echo $code . ' ' . $messages[$code];

You may note the use of @: I know there is an eternal controversy about it, but I'm firmly partisan of using it in a few clearly identified cases such this one (i.e. when the only "error" which may happen and be masked is just the equivalent of isset()).

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.