├── EXPERIMENTAL ├── CREDITS ├── config.m4 ├── LICENSE ├── README.md ├── php_incpath.h ├── tests └── 001.phpt └── incpath.c /EXPERIMENTAL: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Etsy 2 | Authors: Keyur Govande, Rasmus Lerdorf 3 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl PHP incpath extension 2 | dnl 3 | dnl Copyright (c) 2013 Etsy 4 | dnl Authors: Keyur Govande, Rasmus Lerdorf 5 | dnl 6 | dnl See the LICENSE file in this directory for details. 7 | 8 | PHP_ARG_ENABLE(incpath, whether to enable incpath support, 9 | [ --enable-incpath Enable incpath support]) 10 | 11 | if test "$PHP_INCPATH" != "no"; then 12 | PHP_NEW_EXTENSION(incpath, incpath.c, $ext_shared) 13 | fi 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person 2 | obtaining a copy of this software and associated documentation 3 | files (the "Software"), to deal in the Software without 4 | restriction, including without limitation the rights to use, 5 | copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the 7 | Software is furnished to do so, subject to the following 8 | conditions: 9 | 10 | The above copyright notice and this permission notice shall be 11 | included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | === Introduction === 2 | 3 | incpath is a PHP extension to "resolve" a portion of include_path set in PHP's configuration INI files. 4 | 5 | There are 3 configuration values: 6 | * search_replace_pattern: This is the path string to look for in include_path. incpath applies a simple string comparison to determine a match: no regexes or wildcards allowed. If no match is found for the pattern, incpath will do nothing. If a match is found, incpath will replace the entire matched string with the "resolved" path depending on the SAPI configuration. 7 | * realpath_sapi_list: Comma-separated list of SAPIs where incpath will realpath(3) the search_replace_pattern and in-place replace it in include_path (this is only done if a match was found). 8 | * docroot_sapi_list: Comma-separated list of SAPIs where incpath will in-place replace search_replace_pattern with $_SERVER['DOCUMENT_ROOT'] in include_path (this is only done if a match was found). 9 | 10 | incpath is intended to be used for atomic changes to a large, deployed PHP application in conjunction with mod_realdoc. Usually such an application has 2 deploy locations: one active and the other inactive. A symlink to the active one is referenced in the DOCUMENT_ROOT in the web server's configuration, and in PHP's include_path. 11 | 12 | By hooking in before any actual PHP code executes, incpath "resolves" the symlink exactly once, and all subsequent users of include_path (like require/require_once/include/include_once) never have to resolve it again, thereby ensuring the entire request references code in only one location. 13 | 14 | === Installation === 15 | * phpize 16 | * ./configure 17 | * make 18 | * make install 19 | -------------------------------------------------------------------------------- /php_incpath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Etsy 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, 8 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #ifndef PHP_INCPATH_H 28 | #define PHP_INCPATH_H 29 | 30 | extern zend_module_entry incpath_module_entry; 31 | #define phpext_incpath_ptr &incpath_module_entry 32 | 33 | #ifdef PHP_WIN32 34 | # define PHP_INCPATH_API __declspec(dllexport) 35 | #elif defined(__GNUC__) && __GNUC__ >= 4 36 | # define PHP_INCPATH_API __attribute__ ((visibility("default"))) 37 | #else 38 | # define PHP_INCPATH_API 39 | #endif 40 | 41 | ZEND_BEGIN_MODULE_GLOBALS(incpath) 42 | char *search_replace_pattern; 43 | char *docroot_sapi_list; 44 | char *realpath_sapi_list; 45 | ZEND_END_MODULE_GLOBALS(incpath) 46 | 47 | #ifdef ZTS 48 | #include "TSRM.h" 49 | #define INCPATH_G(v) TSRMG(incpath_globals_id, zend_incpath_globals *, v) 50 | #else 51 | #define INCPATH_G(v) (incpath_globals.v) 52 | #endif 53 | 54 | PHP_MINIT_FUNCTION(incpath); 55 | PHP_MSHUTDOWN_FUNCTION(incpath); 56 | PHP_RINIT_FUNCTION(incpath); 57 | PHP_RSHUTDOWN_FUNCTION(incpath); 58 | PHP_MINFO_FUNCTION(incpath); 59 | 60 | #endif /* PHP_INCPATH_H */ 61 | 62 | /* 63 | * Local variables: 64 | * tab-width: 4 65 | * c-basic-offset: 4 66 | * End: 67 | * vim600: noet sw=4 ts=4 fdm=marker 68 | * vim<600: noet sw=4 ts=4 69 | */ 70 | -------------------------------------------------------------------------------- /tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check for correct realpath'ing of incpath 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | $linked_include_path, 18 | 'replacement_pattern' => $linked_include_path, 19 | 'verification_include_path' => $real_include_path, 20 | ); 21 | $variations[] = array( 22 | 'original_include_path' => $linked_include_path . ':/tmp', 23 | 'replacement_pattern' => $linked_include_path, 24 | 'verification_include_path' => $real_include_path . ':/tmp', 25 | ); 26 | $variations[] = array( 27 | 'original_include_path' => '/tmp:' . $linked_include_path, 28 | 'replacement_pattern' => $linked_include_path, 29 | 'verification_include_path' => '/tmp:' . $real_include_path, 30 | ); 31 | $variations[] = array( 32 | 'original_include_path' => '/bin:' . $linked_include_path . ':/tmp', 33 | 'replacement_pattern' => $linked_include_path, 34 | 'verification_include_path' => '/bin:' . $real_include_path . ':/tmp', 35 | ); 36 | $variations[] = array( 37 | 'original_include_path' => $linked_include_path . ':.:/tmp', 38 | 'replacement_pattern' => $linked_include_path . ':.', 39 | 'verification_include_path' => $real_include_path . ":$cwd:" . '/tmp', 40 | ); 41 | $variations[] = array( 42 | 'original_include_path' => '/bin:' . $linked_include_path . ':.:/tmp', 43 | 'replacement_pattern' => $linked_include_path . ':.', 44 | 'verification_include_path' => '/bin:' . $real_include_path . ":$cwd" . ':/tmp', 45 | ); 46 | $variations[] = array( 47 | 'original_include_path' => '/bin:' . $linked_include_path . ':.', 48 | 'replacement_pattern' => $linked_include_path . ':.', 49 | 'verification_include_path' => '/bin:' . $real_include_path . ":$cwd", 50 | ); 51 | $variations[] = array( 52 | 'original_include_path' => '/bin:.', 53 | 'replacement_pattern' => $linked_include_path . ':.', 54 | 'verification_include_path' => '/bin:.', 55 | ); 56 | 57 | if (mkdir($real_include_path, 0777, true) !== true) { 58 | echo "failed to mkdir $real_include_path\n"; 59 | die(); 60 | } 61 | if (symlink($real_include_path, $linked_include_path) !== true) { 62 | echo "failed to symlink $real_include_path to $linked_include_path\n"; 63 | die(); 64 | } 65 | 66 | // The following command sets include_path to $linked_include_path using -d, but expects 67 | // the output of the echo to be $real_include_path 68 | 69 | foreach ($variations as $v) { 70 | $command = "php -d \"incpath.realpath_sapi_list=cli\" -d \"include_path={$v['original_include_path']}\""; 71 | $command .= " -d \"incpath.search_replace_pattern={$v['replacement_pattern']}\""; 72 | $command .= " -r \"echo get_include_path();\""; 73 | 74 | $output = exec($command); 75 | if ($output === false) { 76 | echo "Failed to execute $command\n"; 77 | die(); 78 | } 79 | 80 | $output = trim($output); 81 | 82 | if ($output != $v['verification_include_path']) { 83 | echo "$output does not match $real_include_path\n"; 84 | die(); 85 | } 86 | } 87 | 88 | echo "Successfully verified\n"; 89 | 90 | ?> 91 | --CLEAN-- 92 | 98 | --EXPECT-- 99 | incpath extension is available 100 | Successfully verified 101 | -------------------------------------------------------------------------------- /incpath.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Etsy 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, 8 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #ifdef HAVE_CONFIG_H 27 | #include "config.h" 28 | #endif 29 | 30 | #include 31 | #include "php.h" 32 | #include "php_ini.h" 33 | #include "ext/standard/info.h" 34 | #include "php_incpath.h" 35 | #include "main/php_reentrancy.h" 36 | #include "ext/standard/php_string.h" 37 | #include "main/SAPI.h" 38 | 39 | ZEND_DECLARE_MODULE_GLOBALS(incpath) 40 | 41 | /* True global resources - no need for thread safety here */ 42 | static int le_incpath; 43 | 44 | /* {{{ incpath_module_entry 45 | */ 46 | zend_module_entry incpath_module_entry = { 47 | #if ZEND_MODULE_API_NO >= 20010901 48 | STANDARD_MODULE_HEADER, 49 | #endif 50 | "incpath", 51 | NULL, 52 | PHP_MINIT(incpath), 53 | PHP_MSHUTDOWN(incpath), 54 | PHP_RINIT(incpath), 55 | PHP_RSHUTDOWN(incpath), 56 | PHP_MINFO(incpath), 57 | #if ZEND_MODULE_API_NO >= 20010901 58 | "0.1", /* version number */ 59 | #endif 60 | STANDARD_MODULE_PROPERTIES 61 | }; 62 | /* }}} */ 63 | 64 | #ifdef COMPILE_DL_INCPATH 65 | ZEND_GET_MODULE(incpath) 66 | #endif 67 | 68 | /* {{{ PHP_INI 69 | */ 70 | PHP_INI_BEGIN() 71 | STD_PHP_INI_ENTRY("incpath.search_replace_pattern", NULL, PHP_INI_SYSTEM, OnUpdateString, search_replace_pattern, zend_incpath_globals, incpath_globals) 72 | STD_PHP_INI_ENTRY("incpath.realpath_sapi_list", NULL, PHP_INI_SYSTEM, OnUpdateString, realpath_sapi_list, zend_incpath_globals, incpath_globals) 73 | STD_PHP_INI_ENTRY("incpath.docroot_sapi_list", NULL, PHP_INI_SYSTEM, OnUpdateString, docroot_sapi_list, zend_incpath_globals, incpath_globals) 74 | PHP_INI_END() 75 | /* }}} */ 76 | 77 | /* {{{ PHP_MINIT_FUNCTION 78 | * */ 79 | PHP_MINIT_FUNCTION(incpath) 80 | { 81 | REGISTER_INI_ENTRIES(); 82 | return SUCCESS; 83 | } 84 | /* }}} */ 85 | 86 | /* {{{ PHP_MSHUTDOWN_FUNCTION 87 | * */ 88 | PHP_MSHUTDOWN_FUNCTION(incpath) 89 | { 90 | UNREGISTER_INI_ENTRIES(); 91 | return SUCCESS; 92 | } 93 | /* }}} */ 94 | 95 | /* {{{ php_incpath_init_globals 96 | */ 97 | static void php_incpath_init_globals(zend_incpath_globals *incpath_globals) 98 | { 99 | incpath_globals->search_replace_pattern = NULL; 100 | incpath_globals->docroot_sapi_list = NULL; 101 | incpath_globals->realpath_sapi_list = NULL; 102 | } 103 | /* }}} */ 104 | 105 | #define ACTION_NOTHING 0 106 | #define ACTION_REPLACE_WITH_DOCROOT 1 107 | #define ACTION_REPLACE_WITH_REALPATH 2 108 | 109 | /* 110 | * If sapi_list contains the current SAPI name, returns true. 111 | * Else returns false. 112 | */ 113 | static int evaluate_sapi(char* sapi_list) { 114 | char *sapi_list_copy; 115 | char *current_sapi; 116 | char *strtok_last; 117 | 118 | sapi_list_copy = estrdup(sapi_list); 119 | current_sapi = php_strtok_r(sapi_list_copy, ",", &strtok_last); 120 | while (current_sapi) { 121 | char *trimmed_sapi = php_trim(current_sapi, (int)(strlen(current_sapi)), NULL, 0, NULL, 3 TSRMLS_CC); 122 | if (!strcmp(sapi_module.name, trimmed_sapi)) { 123 | efree(trimmed_sapi); 124 | efree(sapi_list_copy); 125 | return 1; 126 | } 127 | 128 | efree(trimmed_sapi); 129 | current_sapi = php_strtok_r(NULL, ",", &strtok_last); 130 | } 131 | efree(sapi_list_copy); 132 | return 0; 133 | } 134 | 135 | static void realloc_include_path_string(char **str_ptr, int *str_len, int expected_len) { 136 | if (expected_len > *str_len) { 137 | *str_len = expected_len; 138 | *str_ptr = erealloc(*str_ptr, *str_len); 139 | } 140 | 141 | } 142 | 143 | /* {{{ PHP_RINIT_FUNCTION 144 | */ 145 | PHP_RINIT_FUNCTION(incpath) 146 | { 147 | char *original_include_path; 148 | char *pattern_substr_start; 149 | int action = ACTION_NOTHING; 150 | 151 | if (INCPATH_G(docroot_sapi_list) && evaluate_sapi(INCPATH_G(docroot_sapi_list))) 152 | action = ACTION_REPLACE_WITH_DOCROOT; 153 | else if (INCPATH_G(realpath_sapi_list) && evaluate_sapi(INCPATH_G(realpath_sapi_list))) 154 | action = ACTION_REPLACE_WITH_REALPATH; 155 | else 156 | return SUCCESS; 157 | 158 | original_include_path = zend_ini_string("include_path", sizeof("include_path"), 0); 159 | if (INCPATH_G(search_replace_pattern) && ((pattern_substr_start = strstr(original_include_path, INCPATH_G(search_replace_pattern))) != NULL)) { 160 | int new_include_path_offset = 0; 161 | int original_include_path_len = strlen(original_include_path); 162 | int new_include_path_len = (2 * original_include_path_len) + 1; // Should help avoid multiple realloc'ing 163 | char *new_include_path = ecalloc(new_include_path_len, sizeof(char)); 164 | int remaining_length = 0; 165 | int search_replace_pattern_len = strlen(INCPATH_G(search_replace_pattern)); 166 | 167 | if (pattern_substr_start != original_include_path) { 168 | new_include_path_offset = pattern_substr_start - original_include_path; 169 | strncpy(new_include_path, original_include_path, new_include_path_offset); 170 | } 171 | 172 | if (action == ACTION_REPLACE_WITH_DOCROOT) { 173 | zval **server_doc_root; 174 | char *doc_root_ptr; 175 | int doc_root_ptr_len; 176 | zval **array; 177 | 178 | // This will load it up, if not already loaded. 179 | zend_is_auto_global("_SERVER", strlen("_SERVER")); 180 | if ((zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &array) == SUCCESS) && 181 | (Z_TYPE_PP(array) == IS_ARRAY) && 182 | (zend_hash_find(Z_ARRVAL_PP(array), "DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT"), (void **) &server_doc_root) == SUCCESS) && 183 | (Z_TYPE_PP(server_doc_root) == IS_STRING) && 184 | (doc_root_ptr_len = strlen(doc_root_ptr = Z_STRVAL_PP(server_doc_root)))) { 185 | realloc_include_path_string(&new_include_path, &new_include_path_len, (new_include_path_offset + doc_root_ptr_len + 1)); 186 | strncpy(new_include_path + new_include_path_offset, doc_root_ptr, doc_root_ptr_len); 187 | new_include_path_offset += doc_root_ptr_len; 188 | } else { 189 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 190 | "incpath called for docroot replacement, but $_SERVER['DOCUMENT_ROOT'] does not exist or is an empty string"); 191 | goto err; 192 | } 193 | 194 | } else { // if (action == ACTION_REPLACE_WITH_REALPATH) 195 | char *current_token; 196 | char *strtok_last; 197 | char *pattern_copy; 198 | char *realpath_str; 199 | int append_delim = 0; 200 | pattern_copy = estrdup(INCPATH_G(search_replace_pattern)); 201 | current_token = php_strtok_r(pattern_copy, ":", &strtok_last); 202 | while (current_token) { 203 | if (append_delim) { 204 | new_include_path[new_include_path_offset] = ':'; 205 | ++new_include_path_offset; 206 | append_delim = 0; 207 | } 208 | char *realpath_str = realpath(current_token, NULL); 209 | if (realpath_str == NULL) { 210 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 211 | "realpath() in incpath failed for %s with errno %d", current_token, errno); 212 | efree(pattern_copy); 213 | goto err; 214 | } else { 215 | int realpath_len = strlen(realpath_str); 216 | realloc_include_path_string(&new_include_path, &new_include_path_len, (new_include_path_offset + realpath_len + 2)); 217 | strncpy(new_include_path + new_include_path_offset, realpath_str, realpath_len); 218 | new_include_path_offset += realpath_len; 219 | append_delim = 1; 220 | free(realpath_str); 221 | } 222 | 223 | current_token = php_strtok_r(NULL, ":", &strtok_last); 224 | } 225 | 226 | efree(pattern_copy); 227 | } 228 | 229 | // Replace remaining string into new_include_path 230 | remaining_length = strlen(pattern_substr_start + search_replace_pattern_len); 231 | realloc_include_path_string(&new_include_path, &new_include_path_len, (new_include_path_offset + remaining_length + 1)); 232 | 233 | if (remaining_length) { 234 | strncpy(new_include_path + new_include_path_offset, pattern_substr_start + search_replace_pattern_len, remaining_length); 235 | new_include_path_offset += remaining_length; 236 | } 237 | new_include_path[new_include_path_offset] = '\0'; 238 | if (zend_alter_ini_entry_ex("include_path", sizeof("include_path"), new_include_path, new_include_path_offset, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC) == FAILURE) { 239 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 240 | "Error setting include_path INI variable"); 241 | efree(new_include_path); 242 | goto err; 243 | } 244 | 245 | efree(new_include_path); 246 | } 247 | 248 | 249 | err: // Not exiting with FAILED because then Apache/CLI will fail to start at all! 250 | return SUCCESS; 251 | } 252 | /* }}} */ 253 | 254 | /* {{{ PHP_RSHUTDOWN_FUNCTION 255 | */ 256 | PHP_RSHUTDOWN_FUNCTION(incpath) 257 | { 258 | zend_restore_ini_entry("include_path", sizeof("include_path"), PHP_INI_STAGE_RUNTIME); 259 | return SUCCESS; 260 | } 261 | /* }}} */ 262 | 263 | /* {{{ PHP_MINFO_FUNCTION 264 | */ 265 | PHP_MINFO_FUNCTION(incpath) 266 | { 267 | php_info_print_table_start(); 268 | php_info_print_table_header(2, "incpath support", "enabled"); 269 | php_info_print_table_end(); 270 | 271 | DISPLAY_INI_ENTRIES(); 272 | } 273 | /* }}} */ 274 | 275 | /* 276 | * Local variables: 277 | * tab-width: 4 278 | * c-basic-offset: 4 279 | * End: 280 | * vim600: noet sw=4 ts=4 fdm=marker 281 | * vim<600: noet sw=4 ts=4 282 | */ 283 | --------------------------------------------------------------------------------