diff options
-rw-r--r-- | docs/changes.xml | 7 | ||||
-rw-r--r-- | src/nxt_php_sapi.c | 90 | ||||
-rw-r--r-- | src/nxt_router.c | 6 | ||||
-rw-r--r-- | src/nxt_unit_request.h | 2 | ||||
-rw-r--r-- | test/test_php_application.py | 44 |
5 files changed, 143 insertions, 6 deletions
diff --git a/docs/changes.xml b/docs/changes.xml index 59c55fea..014f243d 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -85,6 +85,13 @@ fix error in connection statistics when using proxy. </para> </change> +<change type="bugfix"> +<para> +PHP directory URLs without a trailing '/' would give a 503 error (fixed with +a 301 re-direct). +</para> +</change> + </changes> diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index ca49b289..924dd4e6 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -14,6 +14,7 @@ #include <nxt_router.h> #include <nxt_unit.h> #include <nxt_unit_request.h> +#include <nxt_http.h> #if (PHP_VERSION_ID >= 50400) @@ -100,6 +101,8 @@ static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t); static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t); nxt_inline u_char *nxt_realpath(const void *c); +static nxt_int_t nxt_php_do_301(nxt_unit_request_info_t *req); + static void nxt_php_request_handler(nxt_unit_request_info_t *req); static void nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r); @@ -920,6 +923,63 @@ nxt_realpath(const void *c) } +static nxt_int_t +nxt_php_do_301(nxt_unit_request_info_t *req) +{ + char *p, *url, *port; + uint32_t size; + const char *proto; + nxt_unit_request_t *r; + + r = req->request; + + url = nxt_malloc(sizeof("https://") - 1 + + r->server_name_length + + r->local_port_length + 1 + + r->path_length + 1 + + r->query_length + 1 + + 1); + if (nxt_slow_path(url == NULL)) { + return NXT_UNIT_ERROR; + } + + proto = r->tls ? "https://" : "http://"; + p = nxt_cpymem(url, proto, strlen(proto)); + p = nxt_cpymem(p, nxt_unit_sptr_get(&r->server_name), + r->server_name_length); + + port = nxt_unit_sptr_get(&r->local_port); + if (r->local_port_length > 0 + && !(r->tls && strcmp(port, "443") == 0) + && !(!r->tls && strcmp(port, "80") == 0)) + { + *p++ = ':'; + p = nxt_cpymem(p, port, r->local_port_length); + } + + p = nxt_cpymem(p, nxt_unit_sptr_get(&r->path), r->path_length); + *p++ = '/'; + + if (r->query_length > 0) { + *p++ = '?'; + p = nxt_cpymem(p, nxt_unit_sptr_get(&r->query), r->query_length); + } + + *p = '\0'; + + size = p - url; + + nxt_unit_response_init(req, NXT_HTTP_MOVED_PERMANENTLY, 1, + nxt_length("Location") + size); + nxt_unit_response_add_field(req, "Location", nxt_length("Location"), + url, size); + + nxt_free(url); + + return NXT_UNIT_OK; +} + + static void nxt_php_request_handler(nxt_unit_request_info_t *req) { @@ -975,15 +1035,33 @@ nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) } else if (path.start[path.length - 1] == '/') { script_name = *ctx->index; - } else { - if (nxt_slow_path(path.length < 4 - || nxt_memcmp(path.start + (path.length - 4), - ".php", 4))) - { - nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR); + } else if (path.length < 4 + || nxt_memcmp(path.start + (path.length - 4), ".php", 4) != 0) + { + char tpath[PATH_MAX]; + nxt_int_t ec; + struct stat sb; + + ec = NXT_UNIT_ERROR; + + if (ctx->root->length + path.length + 1 > PATH_MAX) { + nxt_unit_request_done(ctx->req, ec); return; } + + p = nxt_cpymem(tpath, ctx->root->start, ctx->root->length); + p = nxt_cpymem(p, path.start, path.length); + *p = '\0'; + + ret = stat(tpath, &sb); + if (ret == 0 && S_ISDIR(sb.st_mode)) { + ec = nxt_php_do_301(ctx->req); + } + + nxt_unit_request_done(ctx->req, ec); + + return; } ctx->script_filename.length = ctx->root->length diff --git a/src/nxt_router.c b/src/nxt_router.c index 6430aea4..cfd79bf5 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -5254,6 +5254,12 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r, p = nxt_cpymem(p, nxt_sockaddr_address(r->local), r->local->address_length); *p++ = '\0'; + req->local_port_length = nxt_sockaddr_port_length(r->local); + nxt_unit_sptr_set(&req->local_port, p); + p = nxt_cpymem(p, nxt_sockaddr_port(r->local), + nxt_sockaddr_port_length(r->local)); + *p++ = '\0'; + req->tls = r->tls; req->websocket_handshake = r->websocket_handshake; diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index f6b96838..a6ebf0b6 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -19,6 +19,7 @@ struct nxt_unit_request_s { uint8_t version_length; uint8_t remote_length; uint8_t local_addr_length; + uint8_t local_port_length; uint8_t tls; uint8_t websocket_handshake; uint8_t app_target; @@ -39,6 +40,7 @@ struct nxt_unit_request_s { nxt_unit_sptr_t version; nxt_unit_sptr_t remote; nxt_unit_sptr_t local_addr; + nxt_unit_sptr_t local_port; nxt_unit_sptr_t server_name; nxt_unit_sptr_t target; nxt_unit_sptr_t path; diff --git a/test/test_php_application.py b/test/test_php_application.py index f1dcc995..f442f551 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -4,6 +4,7 @@ import re import shutil import signal import time +from pathlib import Path import pytest from unit.applications.lang.php import TestApplicationPHP @@ -620,6 +621,49 @@ opcache.preload_user = %(user)s assert resp['status'] == 200, 'status' assert resp['body'] != '', 'body not empty' + def test_php_application_trailing_slash(self, temp_dir): + new_root = temp_dir + "/php-root" + os.makedirs(new_root + '/path') + + Path(new_root + '/path/index.php').write_text('<?php echo "OK\n"; ?>') + + addr = temp_dir + '/sock' + + assert 'success' in self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/php-path"}, + "unix:" + addr: {"pass": "applications/php-path"}, + }, + "applications": { + "php-path": { + "type": self.get_application_type(), + "processes": {"spare": 0}, + "root": new_root, + } + }, + } + ), 'configure trailing slash' + + assert self.get(url='/path/')['status'] == 200, 'uri with trailing /' + + resp = self.get(url='/path?q=a') + assert resp['status'] == 301, 'uri without trailing /' + assert ( + resp['headers']['Location'] == 'http://localhost:7080/path/?q=a' + ), 'Location with query string' + + resp = self.get( + sock_type='unix', + addr=addr, + url='/path', + headers={'Host': 'foo', 'Connection': 'close'}, + ) + assert resp['status'] == 301, 'uri without trailing /' + assert ( + resp['headers']['Location'] == 'http://foo/path/' + ), 'Location with custom Host over UDS' + def test_php_application_extension_check(self, temp_dir): self.load('phpinfo') |