diff options
Diffstat (limited to 'sapi/cli')
| -rw-r--r-- | sapi/cli/config.m4 | 19 | ||||
| -rw-r--r-- | sapi/cli/config.w32 | 4 | ||||
| -rw-r--r-- | sapi/cli/php_cli.c | 18 | ||||
| -rw-r--r-- | sapi/cli/php_cli_process_title.c | 80 | ||||
| -rw-r--r-- | sapi/cli/php_cli_process_title.h | 43 | ||||
| -rw-r--r-- | sapi/cli/php_cli_server.c | 191 | ||||
| -rw-r--r-- | sapi/cli/php_cli_server.h | 1 | ||||
| -rw-r--r-- | sapi/cli/ps_title.c | 437 | ||||
| -rw-r--r-- | sapi/cli/ps_title.h | 42 | ||||
| -rw-r--r-- | sapi/cli/tests/bug64544.phpt | 20 | ||||
| -rw-r--r-- | sapi/cli/tests/cli_process_title_unix.phpt | 49 | ||||
| -rw-r--r-- | sapi/cli/tests/cli_process_title_windows.phpt | 83 | ||||
| -rw-r--r-- | sapi/cli/tests/php_cli_server_011.phpt | 41 | ||||
| -rw-r--r-- | sapi/cli/tests/php_cli_server_013.phpt | 6 | ||||
| -rw-r--r-- | sapi/cli/tests/php_cli_server_014.phpt | 2 | ||||
| -rw-r--r-- | sapi/cli/tests/php_cli_server_019.phpt | 68 |
16 files changed, 1024 insertions, 80 deletions
diff --git a/sapi/cli/config.m4 b/sapi/cli/config.m4 index cdfa1f7daf..9a1b98da46 100644 --- a/sapi/cli/config.m4 +++ b/sapi/cli/config.m4 @@ -6,6 +6,23 @@ PHP_ARG_ENABLE(cli,, [ --disable-cli Disable building CLI version of PHP (this forces --without-pear)], yes, no) +AC_CHECK_FUNCS(setproctitle) + +AC_CHECK_HEADERS([sys/pstat.h]) + +AC_CACHE_CHECK([for PS_STRINGS], [cli_cv_var_PS_STRINGS], +[AC_TRY_LINK( +[#include <machine/vmparam.h> +#include <sys/exec.h> +], +[PS_STRINGS->ps_nargvstr = 1; +PS_STRINGS->ps_argvstr = "foo";], +[cli_cv_var_PS_STRINGS=yes], +[cli_cv_var_PS_STRINGS=no])]) +if test "$cli_cv_var_PS_STRINGS" = yes ; then + AC_DEFINE([HAVE_PS_STRINGS], [], [Define to 1 if the PS_STRINGS thing exists.]) +fi + AC_MSG_CHECKING(for CLI build) if test "$PHP_CLI" != "no"; then PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/cli/Makefile.frag) @@ -14,7 +31,7 @@ if test "$PHP_CLI" != "no"; then SAPI_CLI_PATH=sapi/cli/php dnl Select SAPI - PHP_SELECT_SAPI(cli, program, php_cli.c php_http_parser.c php_cli_server.c,, '$(SAPI_CLI_PATH)') + PHP_SELECT_SAPI(cli, program, php_cli.c php_http_parser.c php_cli_server.c ps_title.c php_cli_process_title.c,, '$(SAPI_CLI_PATH)') case $host_alias in *aix*) diff --git a/sapi/cli/config.w32 b/sapi/cli/config.w32 index 4d0dad58e8..adcbb2b496 100644 --- a/sapi/cli/config.w32 +++ b/sapi/cli/config.w32 @@ -6,7 +6,7 @@ ARG_ENABLE('crt-debug', 'Enable CRT memory dumps for debugging sent to STDERR', ARG_ENABLE('cli-win32', 'Build console-less CLI version of PHP', 'no'); if (PHP_CLI == "yes") { - SAPI('cli', 'php_cli.c php_http_parser.c php_cli_server.c', 'php.exe'); + SAPI('cli', 'php_cli.c php_http_parser.c php_cli_server.c php_cli_process_title.c ps_title.c', 'php.exe'); ADD_FLAG("LIBS_CLI", "ws2_32.lib"); if (PHP_CRT_DEBUG == "yes") { ADD_FLAG("CFLAGS_CLI", "/D PHP_WIN32_DEBUG_HEAP"); @@ -15,7 +15,7 @@ if (PHP_CLI == "yes") { } if (PHP_CLI_WIN32 == "yes") { - SAPI('cli_win32', 'cli_win32.c', 'php-win.exe'); + SAPI('cli_win32', 'cli_win32.c php_cli_process_title.c ps_title.c', 'php-win.exe'); ADD_FLAG("LDFLAGS_CLI_WIN32", "/stack:8388608"); } diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 07371ee8e3..9daa382eec 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -86,6 +86,9 @@ #include "php_cli_server.h" #endif +#include "ps_title.h" +#include "php_cli_process_title.h" + #ifndef PHP_WIN32 # define php_select(m, r, w, e, t) select(m, r, w, e, t) #else @@ -478,6 +481,8 @@ ZEND_END_ARG_INFO() static const zend_function_entry additional_functions[] = { ZEND_FE(dl, arginfo_dl) + PHP_FE(cli_set_process_title, arginfo_cli_set_process_title) + PHP_FE(cli_get_process_title, arginfo_cli_get_process_title) {NULL, NULL, NULL} }; @@ -1201,6 +1206,7 @@ int main(int argc, char *argv[]) int argc = __argc; char **argv = __argv; #endif + int c; int exit_status = SUCCESS; int module_started = 0, sapi_started = 0; @@ -1212,6 +1218,12 @@ int main(int argc, char *argv[]) int ini_ignore = 0; sapi_module_struct *sapi_module = &cli_sapi_module; + /* + * Do not move this initialization. It needs to happen before argv is used + * in any way. + */ + argv = save_ps_args(argc, argv); + cli_sapi_module.additional_functions = additional_functions; #if defined(PHP_WIN32) && defined(_DEBUG) && defined(PHP_WIN32_DEBUG_HEAP) @@ -1300,6 +1312,7 @@ int main(int argc, char *argv[]) #ifndef PHP_CLI_WIN32_NO_CONSOLE case 'S': sapi_module = &cli_server_sapi_module; + cli_server_sapi_module.additional_functions = server_additional_functions; break; #endif case 'h': /* help & quit */ @@ -1386,6 +1399,11 @@ out: tsrm_shutdown(); #endif + /* + * Do not move this de-initialization. It needs to happen right before + * exiting. + */ + cleanup_ps_args(argv); exit(exit_status); } /* }}} */ diff --git a/sapi/cli/php_cli_process_title.c b/sapi/cli/php_cli_process_title.c new file mode 100644 index 0000000000..dec5613670 --- /dev/null +++ b/sapi/cli/php_cli_process_title.c @@ -0,0 +1,80 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Keyur Govande (kgovande@gmail.com) | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_cli_process_title.h" +#include "ps_title.h" + +/* {{{ proto boolean cli_set_process_title(string arg) + Return a boolean to confirm if the process title was successfully changed or not */ +PHP_FUNCTION(cli_set_process_title) +{ + char *title = NULL; + int title_len; + int rc; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &title, &title_len) == FAILURE) { + return; + } + + rc = set_ps_title(title); + if (rc == PS_TITLE_SUCCESS) { + RETURN_TRUE; + } + + php_error_docref(NULL TSRMLS_CC, E_WARNING, "cli_set_process_title had an error: %s", ps_title_errno(rc)); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto string cli_get_process_title() + Return a string with the current process title. NULL if error. */ +PHP_FUNCTION(cli_get_process_title) +{ + int length = 0; + const char* title = NULL; + int rc; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + rc = get_ps_title(&length, &title); + if (rc != PS_TITLE_SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "cli_get_process_title had an error: %s", ps_title_errno(rc)); + RETURN_NULL(); + } + + RETURN_STRINGL(title, length, 1); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/sapi/cli/php_cli_process_title.h b/sapi/cli/php_cli_process_title.h new file mode 100644 index 0000000000..bd44d99111 --- /dev/null +++ b/sapi/cli/php_cli_process_title.h @@ -0,0 +1,43 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Keyur Govande (kgovande@gmail.com) | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_PS_TITLE_HEADER +#define PHP_PS_TITLE_HEADER + +ZEND_BEGIN_ARG_INFO(arginfo_cli_set_process_title, 0) + ZEND_ARG_INFO(0, title) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_cli_get_process_title, 0) +ZEND_END_ARG_INFO() + +PHP_FUNCTION(cli_set_process_title); +PHP_FUNCTION(cli_get_process_title); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 2425cc0c3e..88eb5743ba 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -105,6 +105,8 @@ #include "php_http_parser.h" #include "php_cli_server.h" +#include "php_cli_process_title.h" + #define OUTPUT_NOT_CHECKED -1 #define OUTPUT_IS_TTY 1 #define OUTPUT_NOT_TTY 0 @@ -131,6 +133,7 @@ typedef struct php_cli_server_request { char *query_string; size_t query_string_len; HashTable headers; + HashTable headers_original_case; char *content; size_t content_len; const char *ext; @@ -254,7 +257,7 @@ static php_cli_server_http_response_status_code_pair status_map[] = { static php_cli_server_http_response_status_code_pair template_map[] = { { 400, "<h1>%s</h1><p>Your browser sent a request that this server could not understand.</p>" }, - { 404, "<h1>%s</h1><p>The requested resource %s was not found on this server.</p>" }, + { 404, "<h1>%s</h1><p>The requested resource <code class=\"url\">%s</code> was not found on this server.</p>" }, { 500, "<h1>%s</h1><p>The server is temporarily unavailable.</p>" }, { 501, "<h1>%s</h1><p>Request method not supported.</p>" } }; @@ -268,12 +271,52 @@ static php_cli_server_ext_mime_type_pair mime_type_map[] = { { "jpg", "image/jpeg" }, { "jpeg", "image/jpeg" }, { "jpe", "image/jpeg" }, + { "pdf", "application/pdf" }, { "png", "image/png" }, { "svg", "image/svg+xml" }, { "txt", "text/plain" }, { "webm", "video/webm" }, { "ogv", "video/ogg" }, { "ogg", "audio/ogg" }, + { "3gp", "video/3gpp" }, /* This is standard video format used for MMS in phones */ + { "apk", "application/vnd.android.package-archive" }, + { "avi", "video/x-msvideo" }, + { "bmp", "image/x-ms-bmp" }, + { "csv", "text/comma-separated-values" }, + { "doc", "application/msword" }, + { "docx", "application/msword" }, + { "flac", "audio/flac" }, + { "gz", "application/x-gzip" }, + { "gzip", "application/x-gzip" }, + { "ics", "text/calendar" }, + { "kml", "application/vnd.google-earth.kml+xml" }, + { "kmz", "application/vnd.google-earth.kmz" }, + { "m4a", "audio/mp4" }, + { "mp3", "audio/mpeg" }, + { "mp4", "video/mp4" }, + { "mpg", "video/mpeg" }, + { "mpeg", "video/mpeg" }, + { "mov", "video/quicktime" }, + { "odp", "application/vnd.oasis.opendocument.presentation" }, + { "ods", "application/vnd.oasis.opendocument.spreadsheet" }, + { "odt", "application/vnd.oasis.opendocument.text" }, + { "oga", "audio/ogg" }, + { "pdf", "application/pdf" }, + { "pptx", "application/vnd.ms-powerpoint" }, + { "pps", "application/vnd.ms-powerpoint" }, + { "qt", "video/quicktime" }, + { "swf", "application/x-shockwave-flash" }, + { "tar", "application/x-tar" }, + { "text", "text/plain" }, + { "tif", "image/tiff" }, + { "wav", "audio/wav" }, + { "wmv", "video/x-ms-wmv" }, + { "xls", "application/vnd.ms-excel" }, + { "xlsx", "application/vnd.ms-excel" }, + { "zip", "application/x-zip-compressed" }, + { "xml", "application/xml" }, + { "xsl", "application/xml" }, + { "xsd", "application/xml" }, { NULL, NULL } }; @@ -291,8 +334,10 @@ ZEND_DECLARE_MODULE_GLOBALS(cli_server); * copied from ext/standard/info.c */ static const char php_cli_server_css[] = "<style>\n" \ - "body { background-color: #ffffff; color: #000000; }\n" \ - "h1 { font-family: sans-serif; font-size: 150%; background-color: #9999cc; font-weight: bold; color: #000000; margin-top: 0;}\n" \ + "body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }\n" \ + "h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }\n" \ + "h1, p { padding-left: 10px; }\n" \ + "code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}\n" \ "</style>\n"; /* }}} */ @@ -432,6 +477,75 @@ static const char *get_mime_type(const char *ext, size_t ext_len) /* {{{ */ return NULL; } /* }}} */ +PHP_FUNCTION(apache_request_headers) /* {{{ */ +{ + php_cli_server_client *client; + HashTable *headers; + char *key; + uint key_len; + char **value_pointer; + HashPosition pos; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + client = SG(server_context); + headers = &client->request.headers_original_case; + + array_init_size(return_value, zend_hash_num_elements(headers)); + + zend_hash_internal_pointer_reset_ex(headers, &pos); + while (zend_hash_get_current_data_ex(headers, (void **)&value_pointer, &pos) == SUCCESS) { + zend_hash_get_current_key_ex(headers, &key, &key_len, NULL, 0, &pos); + add_assoc_string_ex(return_value, key, key_len, *value_pointer, 1); + zend_hash_move_forward_ex(headers, &pos); + } +} +/* }}} */ + +static void add_response_header(sapi_header_struct *h, zval *return_value TSRMLS_DC) /* {{{ */ +{ + char *s, *p; + int len; + ALLOCA_FLAG(use_heap) + + if (h->header_len > 0) { + p = strchr(h->header, ':'); + len = p - h->header; + if (p && (len > 0)) { + while (len > 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) { + len--; + } + if (len) { + s = do_alloca(len + 1, use_heap); + memcpy(s, h->header, len); + s[len] = 0; + do { + p++; + } while (*p == ' ' || *p == '\t'); + add_assoc_stringl_ex(return_value, s, len+1, p, h->header_len - (p - h->header), 1); + free_alloca(s, use_heap); + } + } + } +} +/* }}} */ + +PHP_FUNCTION(apache_response_headers) /* {{{ */ +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!&SG(sapi_headers).headers) { + RETURN_FALSE; + } + array_init(return_value); + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value TSRMLS_CC); +} +/* }}} */ + /* {{{ cli_server module */ @@ -476,6 +590,18 @@ zend_module_entry cli_server_module_entry = { }; /* }}} */ +ZEND_BEGIN_ARG_INFO(arginfo_no_args, 0) +ZEND_END_ARG_INFO() + +const zend_function_entry server_additional_functions[] = { + PHP_FE(cli_set_process_title, arginfo_cli_set_process_title) + PHP_FE(cli_get_process_title, arginfo_cli_get_process_title) + PHP_FE(apache_request_headers, arginfo_no_args) + PHP_FE(apache_response_headers, arginfo_no_args) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_no_args) + {NULL, NULL, NULL} +}; + static int sapi_cli_server_startup(sapi_module_struct *sapi_module) /* {{{ */ { if (php_module_startup(sapi_module, &cli_server_module_entry, 1) == FAILURE) { @@ -1291,6 +1417,7 @@ static int php_cli_server_request_ctor(php_cli_server_request *req) /* {{{ */ req->query_string = NULL; req->query_string_len = 0; zend_hash_init(&req->headers, 0, NULL, (void(*)(void*))char_ptr_dtor_p, 1); + zend_hash_init(&req->headers_original_case, 0, NULL, NULL, 1); req->content = NULL; req->content_len = 0; req->ext = NULL; @@ -1316,6 +1443,7 @@ static void php_cli_server_request_dtor(php_cli_server_request *req) /* {{{ */ pefree(req->query_string, 1); } zend_hash_destroy(&req->headers); + zend_hash_destroy(&req->headers_original_case); if (req->content) { pefree(req->content, 1); } @@ -1560,6 +1688,7 @@ static int php_cli_server_client_read_request_on_header_value(php_http_parser *p { char *header_name = zend_str_tolower_dup(client->current_header_name, client->current_header_name_len); zend_hash_add(&client->request.headers, header_name, client->current_header_name_len + 1, &value, sizeof(char *), NULL); + zend_hash_add(&client->request.headers_original_case, client->current_header_name, client->current_header_name_len + 1, &value, sizeof(char *), NULL); efree(header_name); } @@ -1985,40 +2114,38 @@ static int php_cli_server_request_shutdown(php_cli_server *server, php_cli_serve static int php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ { int decline = 0; - if (!php_handle_special_queries(TSRMLS_C)) { - zend_file_handle zfd; - char *old_cwd; - - ALLOCA_FLAG(use_heap) - old_cwd = do_alloca(MAXPATHLEN, use_heap); - old_cwd[0] = '\0'; - php_ignore_value(VCWD_GETCWD(old_cwd, MAXPATHLEN - 1)); - - zfd.type = ZEND_HANDLE_FILENAME; - zfd.filename = server->router; - zfd.handle.fp = NULL; - zfd.free_filename = 0; - zfd.opened_path = NULL; - - zend_try { - zval *retval = NULL; - if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, &retval, 1, &zfd)) { - if (retval) { - decline = Z_TYPE_P(retval) == IS_BOOL && !Z_LVAL_P(retval); - zval_ptr_dtor(&retval); - } - } else { - decline = 1; + zend_file_handle zfd; + char *old_cwd; + + ALLOCA_FLAG(use_heap) + old_cwd = do_alloca(MAXPATHLEN, use_heap); + old_cwd[0] = '\0'; + php_ignore_value(VCWD_GETCWD(old_cwd, MAXPATHLEN - 1)); + + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = server->router; + zfd.handle.fp = NULL; + zfd.free_filename = 0; + zfd.opened_path = NULL; + + zend_try { + zval *retval = NULL; + if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, &retval, 1, &zfd)) { + if (retval) { + decline = Z_TYPE_P(retval) == IS_BOOL && !Z_LVAL_P(retval); + zval_ptr_dtor(&retval); } - } zend_end_try(); - - if (old_cwd[0] != '\0') { - php_ignore_value(VCWD_CHDIR(old_cwd)); + } else { + decline = 1; } + } zend_end_try(); - free_alloca(old_cwd, use_heap); + if (old_cwd[0] != '\0') { + php_ignore_value(VCWD_CHDIR(old_cwd)); } + free_alloca(old_cwd, use_heap); + return decline; } /* }}} */ diff --git a/sapi/cli/php_cli_server.h b/sapi/cli/php_cli_server.h index f8dff05d02..a8d7f46e7c 100644 --- a/sapi/cli/php_cli_server.h +++ b/sapi/cli/php_cli_server.h @@ -23,6 +23,7 @@ #include "SAPI.h" +extern const zend_function_entry server_additional_functions[]; extern sapi_module_struct cli_server_sapi_module; extern int do_cli_server(int argc, char **argv TSRMLS_DC); diff --git a/sapi/cli/ps_title.c b/sapi/cli/ps_title.c new file mode 100644 index 0000000000..53cd5fc9a0 --- /dev/null +++ b/sapi/cli/ps_title.c @@ -0,0 +1,437 @@ +/* + * PostgreSQL is released under the PostgreSQL License, a liberal Open Source + * license, similar to the BSD or MIT licenses. + * PostgreSQL Database Management System (formerly known as Postgres, then as + * Postgres95) + * + * Portions Copyright (c) 1996-2014, The PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written + * agreement is hereby granted, provided that the above copyright notice + * and this paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, + * EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN + * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * The following code is adopted from the PostgreSQL's ps_status(.h/.c). + */ + +#include "ps_title.h" +#include <stdio.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <string.h> +#include <stdlib.h> + +#ifdef PHP_WIN32 +#include "config.w32.h" +#include <windows.h> +#include <process.h> +#else +#include "php_config.h" +extern char** environ; +#endif + +#ifdef HAVE_SYS_PSTAT_H +#include <sys/pstat.h> /* for HP-UX */ +#endif +#ifdef HAVE_PS_STRINGS +#include <machine/vmparam.h> /* for old BSD */ +#include <sys/exec.h> +#endif +#if defined(DARWIN) +#include <crt_externs.h> +#endif + +/* + * Ways of updating ps display: + * + * PS_USE_SETPROCTITLE + * use the function setproctitle(const char *, ...) + * (newer BSD systems) + * PS_USE_PSTAT + * use the pstat(PSTAT_SETCMD, ) + * (HPUX) + * PS_USE_PS_STRINGS + * assign PS_STRINGS->ps_argvstr = "string" + * (some BSD systems) + * PS_USE_CHANGE_ARGV + * assign argv[0] = "string" + * (some other BSD systems) + * PS_USE_CLOBBER_ARGV + * write over the argv and environment area + * (Linux and most SysV-like systems) + * PS_USE_WIN32 + * push the string out as the name of a Windows event + * PS_USE_NONE + * don't update ps display + * (This is the default, as it is safest.) + */ +#if defined(HAVE_SETPROCTITLE) +#define PS_USE_SETPROCTITLE +#elif defined(HAVE_SYS_PSTAT_H) && defined(PSTAT_SETCMD) +#define PS_USE_PSTAT +#elif defined(HAVE_PS_STRINGS) +#define PS_USE_PS_STRINGS +#elif defined(BSD) && !defined(DARWIN) +#define PS_USE_CHANGE_ARGV +#elif defined(__linux__) || defined(_AIX) || defined(__sgi) || (defined(sun) && !defined(BSD)) || defined(ultrix) || defined(__osf__) || defined(DARWIN) +#define PS_USE_CLOBBER_ARGV +#elif defined(PHP_WIN32) +#define PS_USE_WIN32 +#else +#define PS_USE_NONE +#endif + +/* Different systems want the buffer padded differently */ +#if defined(_AIX) || defined(__linux__) || defined(DARWIN) +#define PS_PADDING '\0' +#else +#define PS_PADDING ' ' +#endif + +#ifdef PS_USE_WIN32 +static char windows_error_details[64]; +static char ps_buffer[MAX_PATH]; +static const size_t ps_buffer_size = MAX_PATH; +#elif defined(PS_USE_CLOBBER_ARGV) +static char *ps_buffer; /* will point to argv area */ +static size_t ps_buffer_size; /* space determined at run time */ +static char *empty_environ[] = {0}; /* empty environment */ +#else +#define PS_BUFFER_SIZE 256 +static char ps_buffer[PS_BUFFER_SIZE]; +static const size_t ps_buffer_size = PS_BUFFER_SIZE; +#endif + +static size_t ps_buffer_cur_len; /* actual string length in ps_buffer */ + +/* save the original argv[] location here */ +static int save_argc; +static char** save_argv; + +/* + * This holds the 'locally' allocated environ from the save_ps_args method. + * This is subsequently free'd at exit. + */ +static char** frozen_environ, **new_environ; + +/* + * Call this method early, before any code has used the original argv passed in + * from main(). + * If needed, this code will make deep copies of argv and environ and return + * these to the caller for further use. The original argv is then 'clobbered' + * to store the process title. + */ +char** save_ps_args(int argc, char** argv) +{ + save_argc = argc; + save_argv = argv; + +#if defined(PS_USE_CLOBBER_ARGV) + /* + * If we're going to overwrite the argv area, count the available space. + * Also move the environment to make additional room. + */ + { + char* end_of_area = NULL; + int non_contiguous_area = 0; + int i; + + /* + * check for contiguous argv strings + */ + for (i = 0; (non_contiguous_area == 0) && (i < argc); i++) + { + if (i != 0 && end_of_area + 1 != argv[i]) + non_contiguous_area = 1; + end_of_area = argv[i] + strlen(argv[i]); + } + + /* + * check for contiguous environ strings following argv + */ + for (i = 0; (non_contiguous_area == 0) && (environ[i] != NULL); i++) + { + if (end_of_area + 1 != environ[i]) + non_contiguous_area = 1; + end_of_area = environ[i] + strlen(environ[i]); + } + + if (non_contiguous_area != 0) + goto clobber_error; + + ps_buffer = argv[0]; + ps_buffer_size = end_of_area - argv[0]; + + /* + * move the environment out of the way + */ + new_environ = (char **) malloc((i + 1) * sizeof(char *)); + frozen_environ = (char **) malloc((i + 1) * sizeof(char *)); + if (!new_environ || !frozen_environ) + goto clobber_error; + for (i = 0; environ[i] != NULL; i++) + { + new_environ[i] = strdup(environ[i]); + if (!new_environ[i]) + goto clobber_error; + } + new_environ[i] = NULL; + environ = new_environ; + memcpy((char *)frozen_environ, (char *)new_environ, sizeof(char *) * (i + 1)); + + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) + /* + * If we're going to change the original argv[] then make a copy for + * argument parsing purposes. + * + * (NB: do NOT think to remove the copying of argv[]! + * On some platforms, getopt() keeps pointers into the argv array, and + * will get horribly confused when it is re-called to analyze a subprocess' + * argument string if the argv storage has been clobbered meanwhile. + * Other platforms have other dependencies on argv[].) + */ + { + char** new_argv; + int i; + + new_argv = (char **) malloc((argc + 1) * sizeof(char *)); + if (!new_argv) + goto clobber_error; + for (i = 0; i < argc; i++) + { + new_argv[i] = strdup(argv[i]); + if (!new_argv[i]) + goto clobber_error; + } + new_argv[argc] = NULL; + +#if defined(DARWIN) + /* + * Darwin (and perhaps other NeXT-derived platforms?) has a static + * copy of the argv pointer, which we may fix like so: + */ + *_NSGetArgv() = new_argv; +#endif + + argv = new_argv; + + } +#endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ + +#if defined(PS_USE_CLOBBER_ARGV) + { + /* make extra argv slots point at end_of_area (a NUL) */ + int i; + for (i = 1; i < save_argc; i++) + save_argv[i] = ps_buffer + ps_buffer_size; + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#ifdef PS_USE_CHANGE_ARGV + save_argv[0] = ps_buffer; /* ps_buffer here is a static const array of size PS_BUFFER_SIZE */ + save_argv[1] = NULL; +#endif /* PS_USE_CHANGE_ARGV */ + + return argv; + +#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) +clobber_error: + /* probably can't happen?! + * if we ever get here, argv still points to originally passed + * in argument + */ + save_argv = NULL; + save_argc = 0; + ps_buffer = NULL; + ps_buffer_size = 0; + return argv; +#endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ +} + +/* + * Returns PS_TITLE_SUCCESS if the OS supports this functionality + * and the init function was called. + * Otherwise returns NOT_AVAILABLE or NOT_INITIALIZED + */ +int is_ps_title_available() +{ +#ifdef PS_USE_NONE + return PS_TITLE_NOT_AVAILABLE; /* disabled functionality */ +#endif + + if (!save_argv) + return PS_TITLE_NOT_INITIALIZED; + +#ifdef PS_USE_CLOBBER_ARGV + if (!ps_buffer) + return PS_TITLE_BUFFER_NOT_AVAILABLE; +#endif /* PS_USE_CLOBBER_ARGV */ + + return PS_TITLE_SUCCESS; +} + +/* + * Convert error codes into error strings + */ +const char* ps_title_errno(int rc) +{ + switch(rc) + { + case PS_TITLE_SUCCESS: + return "Success"; + + case PS_TITLE_NOT_AVAILABLE: + return "Not available on this OS"; + + case PS_TITLE_NOT_INITIALIZED: + return "Not initialized correctly"; + + case PS_TITLE_BUFFER_NOT_AVAILABLE: + return "Buffer not contiguous"; + +#ifdef PS_USE_WIN32 + case PS_TITLE_WINDOWS_ERROR: + sprintf(windows_error_details, "Windows error code: %d", GetLastError()); + return windows_error_details; +#endif + } + + return "Unknown error code"; +} + +/* + * Set a new process title. + * Returns the appropriate error code if if there's an error + * (like the functionality is compile time disabled, or the + * save_ps_args() was not called. + * Else returns 0 on success. + */ +int set_ps_title(const char* title) +{ + int rc = is_ps_title_available(); + if (rc != PS_TITLE_SUCCESS) + return rc; + + strncpy(ps_buffer, title, ps_buffer_size); + ps_buffer[ps_buffer_size - 1] = '\0'; + ps_buffer_cur_len = strlen(ps_buffer); + +#ifdef PS_USE_SETPROCTITLE + setproctitle("%s", ps_buffer); +#endif + +#ifdef PS_USE_PSTAT + { + union pstun pst; + + pst.pst_command = ps_buffer; + pstat(PSTAT_SETCMD, pst, ps_buffer_cur_len, 0, 0); + } +#endif /* PS_USE_PSTAT */ + +#ifdef PS_USE_PS_STRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = ps_buffer; +#endif /* PS_USE_PS_STRINGS */ + +#ifdef PS_USE_CLOBBER_ARGV + /* pad unused memory */ + if (ps_buffer_cur_len < ps_buffer_size) + { + memset(ps_buffer + ps_buffer_cur_len, PS_PADDING, + ps_buffer_size - ps_buffer_cur_len); + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#ifdef PS_USE_WIN32 + { + if (!SetConsoleTitle(ps_buffer)) + return PS_TITLE_WINDOWS_ERROR; + } +#endif /* PS_USE_WIN32 */ + + return PS_TITLE_SUCCESS; +} + +/* + * Returns the current ps_buffer value into string. On some platforms + * the string will not be null-terminated, so return the effective + * length into *displen. + * The return code indicates the error. + */ +int get_ps_title(int *displen, const char** string) +{ + int rc = is_ps_title_available(); + if (rc != PS_TITLE_SUCCESS) + return rc; + +#ifdef PS_USE_WIN32 + if (!(ps_buffer_cur_len = GetConsoleTitle(ps_buffer, ps_buffer_size))) + return PS_TITLE_WINDOWS_ERROR; +#endif + *displen = (int)ps_buffer_cur_len; + *string = ps_buffer; + return PS_TITLE_SUCCESS; +} + +/* + * Clean up the allocated argv and environ if applicable. Only call + * this right before exiting. + * This isn't needed per-se because the OS will clean-up anyway, but + * having and calling this will ensure Valgrind doesn't output 'false + * positives'. + */ +void cleanup_ps_args(char **argv) +{ +#ifndef PS_USE_NONE + if (save_argv) + { + save_argv = NULL; + save_argc = 0; + +#ifdef PS_USE_CLOBBER_ARGV + { + int i; + for (i = 0; frozen_environ[i] != NULL; i++) + free(frozen_environ[i]); + free(frozen_environ); + free(new_environ); + /* leave a sane environment behind since some atexit() handlers + call getenv(). */ + environ = empty_environ; + } +#endif /* PS_USE_CLOBBER_ARGV */ + +#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) + { + int i; + for (i=0; argv[i] != NULL; i++) + free(argv[i]); + free(argv); + } +#endif /* PS_USE_CHANGE_ARGV or PS_USE_CLOBBER_ARGV */ + } +#endif /* PS_USE_NONE */ + + return; +} diff --git a/sapi/cli/ps_title.h b/sapi/cli/ps_title.h new file mode 100644 index 0000000000..09650fed08 --- /dev/null +++ b/sapi/cli/ps_title.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Keyur Govande <kgovande@gmail.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PS_TITLE_HEADER +#define PS_TITLE_HEADER + +#define PS_TITLE_SUCCESS 0 +#define PS_TITLE_NOT_AVAILABLE 1 +#define PS_TITLE_NOT_INITIALIZED 2 +#define PS_TITLE_BUFFER_NOT_AVAILABLE 3 +#define PS_TITLE_WINDOWS_ERROR 4 + +extern char** save_ps_args(int argc, char** argv); + +extern int set_ps_title(const char* new_str); + +extern int get_ps_title(int* displen, const char** string); + +extern const char* ps_title_errno(int rc); + +extern int is_ps_title_available(); + +extern void cleanup_ps_args(char **argv); + +#endif // PS_TITLE_HEADER diff --git a/sapi/cli/tests/bug64544.phpt b/sapi/cli/tests/bug64544.phpt new file mode 100644 index 0000000000..cc49545c16 --- /dev/null +++ b/sapi/cli/tests/bug64544.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #64544 (Valgrind warnings after using putenv) +--SKIPIF-- +<?php +if (substr(PHP_OS, 0, 3) == "WIN") { + die("skip non windows test"); +} +?> +--FILE-- +<?php + +putenv("HOME=/tmp"); +var_dump(getenv("HOME")); + +putenv("FOO=BAR"); +var_dump(getenv("FOO")); +?> +--EXPECTF-- +string(4) "/tmp" +string(3) "BAR" diff --git a/sapi/cli/tests/cli_process_title_unix.phpt b/sapi/cli/tests/cli_process_title_unix.phpt new file mode 100644 index 0000000000..c2632704c5 --- /dev/null +++ b/sapi/cli/tests/cli_process_title_unix.phpt @@ -0,0 +1,49 @@ +--TEST-- +Check cli_process_title support on Unix +--SKIPIF-- +<?php +if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') + die("skip"); +?> +--FILE-- +<?php +echo "*** Testing setting the process title ***\n"; + +$set_title = $original_title = uniqid("title", true); +$pid = getmypid(); + +if (cli_set_process_title($original_title) === true) + echo "Successfully set title\n"; + +$ps_output = shell_exec("ps -p $pid -o command | tail -n 1"); + +if ($ps_output === null) +{ + echo "ps failed\n"; + die(); +} + +$loaded_title = trim($ps_output); +if (strpos(strtoupper(substr(PHP_OS, 0, 13)), "BSD") !== false) +{ + // Fix up title for BSD + $set_title = "php: $original_title (php)"; +} + +if ($loaded_title == $set_title) + echo "Successfully verified title using ps\n"; +else + echo "Actually loaded from ps: $loaded_title\n"; + +$read_title = cli_get_process_title(); +if ($read_title == $original_title) + echo "Successfully verified title using get\n"; +else + echo "Actually loaded from get: $read_title\n"; + +?> +--EXPECTF-- +*** Testing setting the process title *** +Successfully set title +Successfully verified title using ps +Successfully verified title using get diff --git a/sapi/cli/tests/cli_process_title_windows.phpt b/sapi/cli/tests/cli_process_title_windows.phpt new file mode 100644 index 0000000000..472f9c10fe --- /dev/null +++ b/sapi/cli/tests/cli_process_title_windows.phpt @@ -0,0 +1,83 @@ +--TEST-- +Check cli_process_title support in Windows +--SKIPIF-- +<?php +if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + die("skip"); +?> +--FILE-- +<?php + +// On Windows 8 and Server 2012, this test does not work the same way. When the PowerShell +// command "get-process" is executed using shell_exec, it overwrites the ConsoleTitle with +// "Windows PowerShell" and this title ONLY clears away when the php.exe process exits +// i.e. the test finishes. +// On older versions like Windows 7 though, running the command appends +// "Windows PowerShell" to the ConsoleTitle temporarily and the title reverts +// back to the original once shell_exec is done. +// Hence on Windows 8, we don't verify that the title is actually set by +// cli_set_process_title(). We're only making the API calls to ensure there are +// no warnings/errors. + +$is_windows8 = false; +$ps_output = shell_exec("PowerShell -NoProfile \"(Get-Host).UI.RawUI.WindowTitle\""); +if ($ps_output === null) +{ + echo "Get-Host failed\n"; + die(); +} + +$ps_output = trim($ps_output); +$end_title_windows8 = ": Windows PowerShell"; +if (($ps_output == "Windows PowerShell") || (strlen($ps_output) > strlen($end_title_windows8) && substr($ps_output,-strlen($end_title_windows8)) === $end_title_windows8)) + $is_windows8 = true; + +echo "*** Testing setting the process title ***\n"; + +$original_title = uniqid("title", true); +$pid = getmypid(); + +if (cli_set_process_title($original_title) === true) + echo "Successfully set title\n"; + +if ($is_windows8) +{ + $loaded_title = $original_title; +} +else +{ + $loaded_title = shell_exec("PowerShell -NoProfile \"get-process cmd*,powershell* | Select-Object mainWindowTitle | ft -hide\""); + + if ($loaded_title === null) + { + echo "Reading title using get-process failed\n"; + die(); + } + + // Kind of convoluted. So when the test is run on Windows 7 or older, the console where + // the run-tests.php is executed forks a php.exe, which forks a cmd.exe, which then forks + // a final php.exe to run the actual test. But the console title is set for the original console. + // I couldn't figure out a good way to navigate this, so we're "grep'ing" all possible + // console windows for our very unique title. It should occur exactly once in the grep + // output. + if (substr_count($loaded_title, $original_title, 0) == 1) + $loaded_title = $original_title; +} + +if ($loaded_title == $original_title) + echo "Successfully verified title using get-process\n"; +else + echo "Actually loaded from get-process: $loaded_title\n"; + +$read_title = cli_get_process_title(); +if (substr_count($read_title, $original_title, 0) == 1) + echo "Successfully verified title using get\n"; +else + echo "Actually loaded from get: $read_title\n"; + +?> +--EXPECTF-- +*** Testing setting the process title *** +Successfully set title +Successfully verified title using get-process +Successfully verified title using get
\ No newline at end of file diff --git a/sapi/cli/tests/php_cli_server_011.phpt b/sapi/cli/tests/php_cli_server_011.phpt deleted file mode 100644 index a957a8ed4c..0000000000 --- a/sapi/cli/tests/php_cli_server_011.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Bug #60180 ($_SERVER["PHP_SELF"] incorrect) ---SKIPIF-- -<?php -include "skipif.inc"; -?> ---FILE-- -<?php -include "php_cli_server.inc"; -php_cli_server_start('sytanx error;', TRUE); - -list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); -$port = intval($port)?:80; - -$fp = fsockopen($host, $port, $errno, $errstr, 0.5); -if (!$fp) { - die("connect failed"); -} - -$logo_id = php_logo_guid(); - -if(fwrite($fp, <<<HEADER -GET /?={$logo_id} HTTP/1.1 -Host: {$host} - - -HEADER -)) { - while (!feof($fp)) { - if (("Content-Type: image/gif") == trim(fgets($fp))) { - echo "okey"; - break; - } - } -} - -fclose($fp); - -?> ---EXPECTF-- -okey diff --git a/sapi/cli/tests/php_cli_server_013.phpt b/sapi/cli/tests/php_cli_server_013.phpt index 570798a880..0e3f4ff74f 100644 --- a/sapi/cli/tests/php_cli_server_013.phpt +++ b/sapi/cli/tests/php_cli_server_013.phpt @@ -88,7 +88,7 @@ Content-Type: text/html; charset=UTF-8 Content-Length: %d <!doctype html><html><head><title>404 Not Found</title><style>AAA</style> -</head><body><h1>Not Found</h1><p>The requested resource / was not found on this server.</p></body></html> +</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/</code> was not found on this server.</p></body></html> HTTP/1.1 404 Not Found Host: %s Connection: close @@ -96,7 +96,7 @@ Content-Type: text/html; charset=UTF-8 Content-Length: %d <!doctype html><html><head><title>404 Not Found</title><style>AAA</style> -</head><body><h1>Not Found</h1><p>The requested resource /main/style.css was not found on this server.</p></body></html> +</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/main/style.css</code> was not found on this server.</p></body></html> HTTP/1.1 404 Not Found Host: %s Connection: close @@ -104,5 +104,5 @@ Content-Type: text/html; charset=UTF-8 Content-Length: %d <!doctype html><html><head><title>404 Not Found</title><style>AAA</style> -</head><body><h1>Not Found</h1><p>The requested resource /main/foo/bar was not found on this server.</p></body></html> +</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/main/foo/bar</code> was not found on this server.</p></body></html> diff --git a/sapi/cli/tests/php_cli_server_014.phpt b/sapi/cli/tests/php_cli_server_014.phpt index f8a9905613..e8bb5fa8a2 100644 --- a/sapi/cli/tests/php_cli_server_014.phpt +++ b/sapi/cli/tests/php_cli_server_014.phpt @@ -77,4 +77,4 @@ Content-Type: %s Content-Length: %d <!doctype html><html><head><title>404 Not Found</title><style>AAA</style> -</head><body><h1>Not Found</h1><p>The requested resource /main/no-exists.php was not found on this server.</p></body></html> +</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/main/no-exists.php</code> was not found on this server.</p></body></html> diff --git a/sapi/cli/tests/php_cli_server_019.phpt b/sapi/cli/tests/php_cli_server_019.phpt new file mode 100644 index 0000000000..2b983e5c0a --- /dev/null +++ b/sapi/cli/tests/php_cli_server_019.phpt @@ -0,0 +1,68 @@ +--TEST-- +Implement Req #65917 (getallheaders() is not supported by the built-in web server) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'PHP' +header('Bar-Foo: Foo'); +var_dump(getallheaders()); +var_dump(apache_request_headers()); +var_dump(apache_response_headers()); +PHP +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.1 +Host: {$host} +Foo-Bar: Bar + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: %s +Bar-Foo: Foo +Content-type: text/html + +array(2) { + ["Host"]=> + string(9) "localhost" + ["Foo-Bar"]=> + string(3) "Bar" +} +array(2) { + ["Host"]=> + string(9) "localhost" + ["Foo-Bar"]=> + string(3) "Bar" +} +array(3) { + ["X-Powered-By"]=> + string(%d) "P%s" + ["Bar-Foo"]=> + string(3) "Foo" + ["Content-type"]=> + string(9) "text/html" +} |
