├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── config.m4 ├── config.w32 ├── gae_runtime_module.cc ├── gae_runtime_module.h ├── gae_runtime_module_stub.cc ├── gae_runtime_module_stub.h ├── php_api_rpc.h ├── php_constants.h ├── php_features_stub.cc ├── php_readonly_filesystem_wrapper.cc ├── php_readonly_filesystem_wrapper.h ├── php_redirect_filesystem_wrapper.cc ├── php_redirect_filesystem_wrapper.h ├── php_rpc_utils_stub.cc ├── php_rpc_utils_stub.h ├── php_runtime_sapi_stub.cc ├── php_runtime_sapi_stub.h ├── php_runtime_utils_stub.cc ├── php_runtime_utils_stub.h ├── php_stream_wrapper.cc ├── php_stream_wrapper.h ├── remote_api.proto ├── urlfetch_service.proto ├── urlfetch_stream_wrapper.cc └── urlfetch_stream_wrapper.h /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreements 2 | 3 | We'd love to accept your code patches! However, before we can take them, we have to clear a couple of legal hurdles. 4 | 5 | - Please fill out either the individual or corporate Contributor License Agreement. 6 | - If you are an individual writing original source code and are sure you own the intellectual property, then you'll need to sign an individual CLA available at: 7 | 8 | https://developers.google.com/open-source/cla/individual. 9 | 10 | - If you work for a company that wants to allow you to contribute your work to this SDK, then you'll need to sign a corporate CLA available at: 11 | 12 | https://developers.google.com/open-source/cla/corporate. 13 | 14 | Follow either of the two links above to access the appropriate CLA and instructions on how to sign and return it. Once we receive the CLA, we'll add you to the official list of contributors and will be able to accept your patches. 15 | 16 | # Submitting Patches 17 | 18 | - Sign a Contributor License Agreement (see above). 19 | - Create an issue on the issue tracker if there isn't one already. Use this issue to co-ordinate the changes with the SDK maintainers. 20 | - Fork the SDK, make the changes and send a [pull request](https://help.github.com/articles/using-pull-requests). 21 | - The SDK maintainers will work with you to review and apply the patch. 22 | 23 | # If you can't become a contributor 24 | 25 | If you can't become a contributor, but wish to share some code that illustrates an issue / shows how an issue may be fixed, then you can attach your changes on the issue tracker. We will use this code to troubleshoot the issue and fix it, but will not use this code in the library unless the steps to submit patches are done. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Google App Engine](https://cloud.google.com/appengine/) PHP Runtime Extension 2 | 3 | This repository contains the GAE PHP runtime extension, which enables emulation of the App Engine environment for local development. 4 | 5 | ## Building 6 | 7 | 1. Install [Protocol Buffer complier](https://developers.google.com/protocol-buffers/) on your platform, following the [C++ Installation Instructions](https://github.com/google/protobuf/blob/master/src/README.md). 8 | 9 | 1. Generate C++ source and header files for [remote_api.proto](remote_api.proto) and [urlfetch_service.proto](urlfetch_service.proto). 10 | 11 | protoc --cpp_out=. remote_api.proto 12 | protoc --cpp_out=. urlfetch_service.proto 13 | 14 | 1. Familiarize yourself with the [PHP extension building](http://www.phpinternalsbook.com/build_system/building_extensions.html#building-extensions-using-phpize) process, and run the following commands. Set ```` and ```` to the absolute path to the protobuf headers and libraries installed in the previous step, usually ``/usr/local/include`` and ``/usr/local/lib``. 15 | 16 | phpize 17 | ./configure --enable-gae --with-protobuf_inc= --with-protobuf_lib= 18 | make 19 | 20 | 1. The compiled extension can be found in ``modules/gae_runtime_module.so``. Use the ``--php_gae_extension_path`` flag to load the extension when running the [development server](https://cloud.google.com/appengine/docs/php/tools/devserver). 21 | 22 | ## Contributing 23 | Have a patch that will benefit this project? Awesome! Follow these steps to have it accepted. 24 | 25 | 1. Please sign our [Contributor License Agreement](CONTRIBUTING.md). 26 | 1. Fork this Git repository and make your changes. 27 | 1. Create a Pull Request 28 | 1. Incorporate review feedback to your changes. 29 | 1. Accepted! 30 | 31 | ## License 32 | All files in this repository are under the [Apache v2](LICENSE) unless noted otherwise. 33 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl Copyright 2016 Google Inc. All Rights Reserved. 2 | dnl 3 | dnl Licensed under the Apache License, Version 2.0 (the "License"); 4 | dnl you may not use this file except in compliance with the License. 5 | dnl You may obtain a copy of the License at 6 | dnl 7 | dnl http://www.apache.org/licenses/LICENSE-2.0 8 | dnl 9 | dnl Unless required by applicable law or agreed to in writing, software 10 | dnl distributed under the License is distributed on an "AS-IS" BASIS, 11 | dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | dnl See the License for the specific language governing permissions and 13 | dnl limitations under the License. 14 | dnl 15 | 16 | PHP_ARG_WITH(protobuf_inc, for protobuf headers, 17 | [ --with-protobuf_inc[=DIR] Define the location of the protobuf headers.]) 18 | 19 | PHP_ARG_WITH(protobuf_lib, for protobuf libraries, 20 | [ --with-protobuf_lib[=DIR] Define the location of the protobuf libraries.]) 21 | 22 | dnl Must specify as the last PHP_ARG_* for $ext_shared to work correctly below. 23 | PHP_ARG_ENABLE(gae, weather to enable Google App Engine support, 24 | [ --enable-gae Enable Google App Engine support.]) 25 | 26 | if test "$PHP_GAE" != "no"; then 27 | PHP_REQUIRE_CXX() 28 | 29 | gae_src="gae_runtime_module.cc 30 | gae_runtime_module_stub.cc 31 | php_features_stub.cc 32 | php_stream_wrapper.cc 33 | php_readonly_filesystem_wrapper.cc 34 | php_redirect_filesystem_wrapper.cc 35 | php_rpc_utils_stub.cc 36 | php_runtime_utils_stub.cc 37 | php_runtime_sapi_stub.cc 38 | remote_api.pb.cc 39 | urlfetch_service.pb.cc 40 | urlfetch_stream_wrapper.cc" 41 | 42 | if test -r "$PHP_PROTOBUF_INC/google/protobuf/message.h"; then 43 | protobuf_inc=$PHP_PROTOBUF_INC 44 | else 45 | AC_MSG_ERROR([Invalid protobuf include path $PHP_PROTOBUF_INC]) 46 | fi 47 | 48 | if test -d "$PHP_PROTOBUF_LIB"; then 49 | protobuf_lib=$PHP_PROTOBUF_LIB 50 | else 51 | AC_MSG_ERROR([Invalid protobuf library path $PHP_PROTOBUF_LIB]) 52 | fi 53 | 54 | FLAGS="-DUSE_REMOTE_API -fPIC -I$protobuf_inc" 55 | 56 | dnl Assume libprotobuf.dylib is built using libc++. 57 | if [[[ "$host_alias" == *darwin* ]]]; then 58 | FLAGS="$FLAGS -stdlib=libc++" 59 | fi 60 | 61 | PHP_NEW_EXTENSION(gae_runtime_module, $gae_src, $ext_shared, , $FLAGS) 62 | 63 | GAE_RUNTIME_MODULE_SHARED_LIBADD="-L$protobuf_lib -lprotobuf" 64 | PHP_SUBST(GAE_RUNTIME_MODULE_SHARED_LIBADD) 65 | fi 66 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | ARG_ENABLE("gae", "Google App Engine runtime", "yes"); 17 | ARG_WITH("protobuf-lib", "Location of libprotobuf.lib", ""); 18 | ARG_WITH("protobuf-inc", "Location of protobuf header files", ""); 19 | 20 | if (PHP_GAE != "no") { 21 | if (!CHECK_LIB("libprotobuf.lib", "gae_runtime_module", PHP_PROTOBUF_LIB)) { 22 | ERROR("Unable to find libprotobuf.lib"); 23 | } 24 | 25 | var GAE_SRC="gae_runtime_module.cc " + 26 | "gae_runtime_module_stub.cc " + 27 | "php_features_stub.cc " + 28 | "php_stream_wrapper.cc " + 29 | "php_readonly_filesystem_wrapper.cc " + 30 | "php_redirect_filesystem_wrapper.cc " + 31 | "php_rpc_utils_stub.cc " + 32 | "php_runtime_utils_stub.cc " + 33 | "php_runtime_sapi_stub.cc " + 34 | "remote_api.pb.cc " + 35 | "urlfetch_service.pb.cc " + 36 | "urlfetch_stream_wrapper.cc"; 37 | 38 | ADD_FLAG("CFLAGS_GAE_RUNTIME_MODULE", "/D COMPILE_DL_GAE_RUNTIME_MODULE"); 39 | ADD_FLAG("CFLAGS_GAE_RUNTIME_MODULE", "/D USE_REMOTE_API"); 40 | ADD_FLAG("CFLAGS_GAE_RUNTIME_MODULE", "/I " + PHP_PROTOBUF_INC); 41 | 42 | // Must build shared extension as the module is written in C++. 43 | EXTENSION("gae_runtime_module", GAE_SRC, true); 44 | } 45 | -------------------------------------------------------------------------------- /gae_runtime_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | 18 | #include "gae_runtime_module.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "php_constants.h" 26 | #include "php_stream_wrapper.h" 27 | #include "php_readonly_filesystem_wrapper.h" 28 | #include "php_redirect_filesystem_wrapper.h" 29 | #include "urlfetch_stream_wrapper.h" 30 | 31 | #include "gae_runtime_module_stub.h" 32 | #include "php_rpc_utils_stub.h" 33 | #include "php_runtime_utils_stub.h" 34 | #include "php_runtime_sapi_stub.h" 35 | 36 | extern "C" { 37 | #ifdef __google_internal__ 38 | #include "php/main/php.h" 39 | #include "php/main/php_ini.h" 40 | #include "php/main/SAPI.h" 41 | #include "php/main/php_streams.h" 42 | #include "php/main/rfc1867.h" 43 | #include "php/ext/standard/php_filestat.h" 44 | #include "php/ext/sockets/php_sockets.h" 45 | #include "php/ext/spl/spl_exceptions.h" 46 | #include "php/ext/standard/file.h" 47 | #include "php/ext/standard/info.h" 48 | #include "php/Zend/zend_API.h" 49 | #include "php/Zend/zend_exceptions.h" 50 | #include "php/Zend/zend_hash.h" 51 | #include "php/Zend/zend_interfaces.h" 52 | #else 53 | #include "main/php.h" 54 | #include "main/php_ini.h" 55 | #include "main/php_streams.h" 56 | #include "main/SAPI.h" 57 | #include "main/rfc1867.h" 58 | #include "ext/standard/php_filestat.h" 59 | #include "ext/sockets/php_sockets.h" 60 | #include "ext/spl/spl_exceptions.h" 61 | #include "ext/standard/file.h" 62 | #include "ext/standard/info.h" 63 | #include "Zend/zend_API.h" 64 | #include "Zend/zend_exceptions.h" 65 | #include "Zend/zend_hash.h" 66 | #include "Zend/zend_interfaces.h" 67 | #endif 68 | } 69 | 70 | ZEND_DECLARE_MODULE_GLOBALS(gae_runtime_module) 71 | 72 | #ifdef ZTS 73 | #define GAERT_G(v) TSRMG(gae_runtime_module_globals_id, \ 74 | zend_gae_runtime_module_globals*, \ 75 | v) 76 | #else 77 | #define GAERT_G(v) (gae_runtime_module_globals.v) 78 | #endif 79 | 80 | using appengine::JoinString; 81 | using appengine::SplitString; 82 | using appengine::SplitStringWithMaxSplit; 83 | using appengine::TrimWhitespaceASCII; 84 | using appengine::urlfetch_stream_wrapper_module_entry; 85 | 86 | using std::pair; 87 | using std::set; 88 | using std::string; 89 | using std::vector; 90 | 91 | #pragma GCC diagnostic ignored "-Wwritable-strings" // PHP APIs not const safe. 92 | 93 | DEFINE_string(php_delete_functions, 94 | "dl, mb_send_mail", 95 | "Comma seperated list of functions to delete from the PHP global " 96 | "function table. This will result in these functions being " 97 | "undefined. Use php_disable_functions to disable functions but " 98 | "leave them defined."); 99 | 100 | DEFINE_string(php_disable_functions, 101 | "diskfreespace, disk_free_space, disk_total_space, " 102 | "escapeshellarg, escapeshellcmd, exec, " 103 | "highlight_file, link, lchgrp, lchown, passthru, " 104 | "pclose, popen, proc_close, proc_get_status, proc_nice, " 105 | "proc_open, proc_terminate, set_time_limit, shell_exec, " 106 | "show_source, symlink, system", 107 | "Comma separated list of functions to disable."); 108 | 109 | DEFINE_string(php_disable_classes, 110 | "", 111 | "Comma separated list of classes to disable."); 112 | 113 | DEFINE_string(php_unregister_streams, 114 | "php, data", 115 | "Comma seperated list of streams to unregister, making them " 116 | "unavailable."); 117 | 118 | DEFINE_string(php_unregister_xports, 119 | "sslv2, sslv3, tls, udg, udp", 120 | "Comma seperated list of stream transports to unregister, making " 121 | "them unavailable."); 122 | 123 | DEFINE_string(php_additional_soft_disable_functions, 124 | "", 125 | "Comma separated list of functions to be added to the built in " 126 | "list of disabled functions that will be disabled unless " 127 | "the user explicitly enables them in their user.ini file."); 128 | 129 | DECLARE_bool(php_enable_direct_uploads); 130 | DECLARE_bool(php_enable_additional_cloud_storage_headers); 131 | DECLARE_bool(php_enable_tempnam); 132 | DECLARE_bool(php_enable_cross_stream_wrapper_rename); 133 | DECLARE_bool(php_enable_glob_replacement); 134 | DECLARE_bool(php_enforce_filesystem_readonly); 135 | DECLARE_bool(php_enable_mail_replacement); 136 | DECLARE_bool(php_remove_glob_stream_wrapper); 137 | DECLARE_bool(php_enable_gcs_stat_cache); 138 | DECLARE_bool(php_unregister_unix_xport); 139 | DECLARE_bool(php_enable_syslog_replacement); 140 | DECLARE_bool(php_allow_file_redirect); 141 | DECLARE_bool(php_enable_gcs_default_keyword); 142 | 143 | DECLARE_bool(enable_socket_api); 144 | 145 | using appengine::php_input_stream_wrapper; 146 | using appengine::php_redirect_stream_wrapper; 147 | using appengine::PhpApiRpc; 148 | using appengine::PhpRpcUtils; 149 | using appengine::StringCaseEqual; 150 | 151 | // Keep track of user supplied ini variables, which can be defined in a file 152 | // names 'php.ini' in the application root. 153 | static HashTable* user_ini_hash_table = NULL; 154 | 155 | // Keep track of redirect path mapping, which can be supplied in the users 156 | // php.ini file. 157 | static HashTable* redirect_path_hash_table = NULL; 158 | 159 | // Keep track of memory we've created disabling functions, so we can clean it 160 | // up on module exit 161 | static vector* disabled_function_names = NULL; 162 | 163 | // Keep track of memory we've created for overridden INI values. 164 | static vector* overriden_ini_values = NULL; 165 | 166 | // Mutex to ensure that only one request can be initialized at a time. 167 | static Mutex request_init_mutex(base::LINKER_INITIALIZED); 168 | 169 | static Mutex redirect_path_ht_mutex(base::LINKER_INITIALIZED); 170 | 171 | // Comma separated list of default soft disable functions - These functions will 172 | // be disabled unless the user enables them in the applications php.ini by 173 | // adding them to the google_app_engine.enable_functions setting. 174 | static const char kSoftDisableFunctions[] = "phpinfo, getmypid, getmyuid, " 175 | "gc_collect_cycles, gc_enable, gc_disable, gc_enabled, php_uname, " 176 | "php_sapi_name, getrusage, getmyinode, getmygid, get_current_user," 177 | "libxml_disable_entity_loader"; 178 | 179 | // Comma separated list of socket related functions that should only be 180 | // available to apps that have socket access. The list is taken from 181 | // http://www.php.net/manual/en/ref.network.php sans gethostname(). 182 | static const char kSocketFunctions[] = "checkdnsrr, dns_check_record, " 183 | "dns_get_mx, dns_get_record, fsockopen, gethostbyaddr, gethostbyname, " 184 | "gethostbynamel, getmxrr, getprotobyname, getprotobynumber, getservbyname, " 185 | "getservbyport, pfsockopen, socket_get_status, socket_set_blocking, " 186 | "socket_set_timeout"; 187 | 188 | // Comma separated list of FTP related functions that should only be available 189 | // to apps that have socket access. This list is taken from 190 | // http://www.php.net/manual/en/ref.ftp.php 191 | static const char kFtpFunctions[] = "ftp_alloc, ftp_cdup, ftp_chdir, " 192 | "ftp_chmod, ftp_close, ftp_connect, ftp_delete, ftp_exec, ftp_fget, " 193 | "ftp_fput, ftp_get_option, ftp_get, ftp_login, ftp_mdtm, ftp_mkdir, " 194 | "ftp_nb_continue, ftp_nb_fget, ftp_nb_fput, ftp_nb_get, ftp_nb_put, " 195 | "ftp_nlist, ftp_pasv, ftp_put, ftp_pwd, ftp_quit, ftp_raw, ftp_rawlist, " 196 | "ftp_rename, ftp_rmdir, ftp_set_option, ftp_site, ftp_size, " 197 | "ftp_ssl_connect, ftp_systype"; 198 | 199 | static const char kEnableFunctions[] = "google_app_engine.enable_functions"; 200 | static const char kGoogleStorageProtocol[] = "gs"; 201 | static const char kGlobFunctionName[] = 202 | "google\\appengine\\runtime\\Glob::doGlob"; 203 | static const char kTempnamFunctionName[] = 204 | "google\\appengine\\runtime\\SplOverride::tempnam"; 205 | static const char kSysGetTempDirFunctionName[] = 206 | "google\\appengine\\runtime\\SplOverride::sys_get_temp_dir"; 207 | static const char kMailFunctionName[] = 208 | "google\\appengine\\runtime\\Mail::sendMail"; 209 | static const char kClearGcsStatCacheFunctionName[] = 210 | "google\\appengine\\ext\\cloud_storage_streams\\" 211 | "CloudStorageClient::clearStatCache"; 212 | static const char kGetHostnameFunctionName[] = 213 | "google\\appengine\\runtime\\SplOverride::gethostname"; 214 | static const char kMoveUploadedFileFunctionName[] = 215 | "google\\appengine\\runtime\\SplOverride::move_uploaded_file"; 216 | static const char kLogFunctionName[] = 217 | "google\\appengine\\api\\log\\LogService::log"; 218 | static const char kRedirectProtocol[] = "redirect"; 219 | static const char kRedirectPrependString[] = "redirect://"; 220 | static const char kTilde[] = "~"; 221 | static const char kBaseDirectory[] = "~/"; 222 | static const char kVfsPrefix[] = "vfs://"; 223 | static const char kVirtualFileSystemInitializeFunctionName[] = 224 | "google\\appengine\\runtime\\VirtualFileSystem::getInstance"; 225 | static const char kVirtualFileSystemIntiailizeMethodName[] = "initialize"; 226 | 227 | static const int kSyslogPriorityMask = 0x07; 228 | // Map syslog severity levels to appengine app_log levels. 229 | const int kSyslogPriorityMap[] = {4, 4, 4, 3, 2, 1, 1, 0}; 230 | 231 | static void replace_builtin_functions(INIT_FUNC_ARGS); 232 | 233 | static bool initialize_virtual_filesystem(TSRMLS_D); 234 | 235 | static int get_user_ini_string(const char* key_name, const char** result); 236 | 237 | PHP_FUNCTION(memory_tmpfile); 238 | 239 | // Dummy function that displays that this function has been soft disabled 240 | ZEND_FUNCTION(display_soft_disabled_function) { 241 | zend_error(E_WARNING, "%s() has been disabled for security reasons. " 242 | "It can be re-enabled by adding it to the %s ini variable in your " 243 | "applications php.ini", get_active_function_name(TSRMLS_C), 244 | kEnableFunctions); 245 | } 246 | 247 | static zend_function_entry soft_disabled_function_table[] = { 248 | ZEND_FE(display_soft_disabled_function, NULL) 249 | ZEND_FE_END 250 | }; 251 | 252 | // Dummy function that displays that this socket functions are not available 253 | // for this application 254 | ZEND_FUNCTION(display_socket_disabled_function) { 255 | zend_error(E_WARNING, "%s() has been disabled for your application.", 256 | get_active_function_name(TSRMLS_C)); 257 | } 258 | 259 | static zend_function_entry socket_disabled_function_table[] = { 260 | ZEND_FE(display_socket_disabled_function, NULL) 261 | ZEND_FE_END 262 | }; 263 | 264 | // Callback function to free up persistent memory from the redirect hash table. 265 | void free_redirect_destination(char** str_p) { 266 | pefree(*str_p, 1); 267 | } 268 | 269 | int redirect_path_lookup(const char* path, char** new_path TSRMLS_DC) { 270 | char* tmp_path; 271 | if (appengine::is_redirect_path(path, &tmp_path TSRMLS_CC)) { 272 | *new_path = tmp_path; 273 | return 1; 274 | } 275 | return 0; 276 | } 277 | 278 | // Soft disable the supplied function. 279 | static int replace_function(const char* function_name, 280 | zend_function_entry* replacement TSRMLS_DC) { 281 | if (zend_hash_del(CG(function_table), 282 | function_name, 283 | strlen(function_name) + 1) == FAILURE) { 284 | return FAILURE; 285 | } 286 | 287 | char* name_copy = pestrdup(function_name, 1); 288 | 289 | CHECK(disabled_function_names); 290 | disabled_function_names->push_back(name_copy); 291 | 292 | replacement[0].fname = name_copy; 293 | 294 | return zend_register_functions(NULL, 295 | replacement, 296 | CG(function_table), 297 | MODULE_PERSISTENT TSRMLS_CC); 298 | } 299 | 300 | // Delete functions from the compiler global function table. Typically we 301 | // undefine functions that we want to re-implement in an appengine specific way, 302 | // such as mail. 303 | static void delete_functions(INIT_FUNC_ARGS) { 304 | if (!FLAGS_php_delete_functions.empty()) { 305 | vector functions; 306 | SplitString(FLAGS_php_delete_functions, ",", &functions); 307 | for (int i = 0; i < functions.size() ; ++i) { 308 | TrimWhitespaceASCII(functions[i], &functions[i]); 309 | if (!functions[i].empty()) { 310 | zend_hash_del(CG(function_table), 311 | functions[i].c_str(), 312 | functions[i].length() + 1); 313 | } 314 | } 315 | } 316 | } 317 | 318 | // Remove streams from the protocol table. This makes these streams unavailable. 319 | // Typically these are removed due to potential security vulnerabilities. 320 | static void unregister_streams(INIT_FUNC_ARGS) { 321 | if (!FLAGS_php_unregister_streams.empty()) { 322 | vector streams; 323 | SplitString(FLAGS_php_unregister_streams, ",", &streams); 324 | if (FLAGS_php_remove_glob_stream_wrapper) { 325 | streams.push_back("glob"); 326 | } 327 | for (int i = 0; i < streams.size(); ++i) { 328 | string& stream_name = streams[i]; 329 | TrimWhitespaceASCII(stream_name, &stream_name); 330 | if (!stream_name.empty()) { 331 | php_unregister_url_stream_wrapper( 332 | const_cast(stream_name.c_str()) TSRMLS_CC); 333 | } 334 | } 335 | } 336 | } 337 | 338 | // Remove stream transports that are not supported from the transport table. 339 | static void unregister_stream_xports(INIT_FUNC_ARGS) { 340 | if (!FLAGS_php_unregister_xports.empty()) { 341 | vector xports; 342 | SplitString(FLAGS_php_unregister_xports, ",", &xports); 343 | for (int i = 0; i < xports.size(); ++i) { 344 | string& xport_name = xports[i]; 345 | TrimWhitespaceASCII(xport_name, &xport_name); 346 | if (!xport_name.empty()) { 347 | php_stream_xport_unregister( 348 | const_cast(xport_name.c_str()) TSRMLS_CC); 349 | } 350 | } 351 | } 352 | 353 | if (FLAGS_php_unregister_unix_xport) { 354 | php_stream_xport_unregister("unix" TSRMLS_CC); 355 | } 356 | } 357 | 358 | // Disable socket related transports and functions, if required. 359 | static void disable_socket_functions(INIT_FUNC_ARGS) { 360 | // Sockets are only enabled if the borg flag is set (and ultimately if 361 | // billing is enabled). 362 | if (FLAGS_enable_socket_api) { 363 | return; 364 | } 365 | // Disable the transports 366 | php_stream_xport_unregister("ssl" TSRMLS_CC); 367 | php_stream_xport_unregister("tcp" TSRMLS_CC); 368 | 369 | // Disable all of the functions in the socket extension. 370 | zend_module_entry* sockets_module; 371 | if (zend_hash_find(&module_registry, "sockets", sizeof("sockets"), 372 | reinterpret_cast(&sockets_module)) == SUCCESS && 373 | sockets_module != NULL && sockets_module->functions != NULL) { 374 | const zend_function_entry* function_ptr = sockets_module->functions; 375 | while (function_ptr->fname != NULL) { 376 | replace_function(function_ptr->fname, 377 | socket_disabled_function_table TSRMLS_CC); 378 | ++function_ptr; 379 | } 380 | } 381 | 382 | // Disable all of the functions in the kSocketFunction list. 383 | vector socket_functions_list; 384 | SplitString(kSocketFunctions, ",", &socket_functions_list); 385 | for (int i = 0; i < socket_functions_list.size(); ++i) { 386 | TrimWhitespaceASCII(socket_functions_list[i], 387 | &socket_functions_list[i]); 388 | replace_function(socket_functions_list[i].c_str(), 389 | socket_disabled_function_table TSRMLS_CC); 390 | } 391 | // Disable all of the functions in the kFtpFunctions list. 392 | SplitString(kFtpFunctions, ",", &socket_functions_list); 393 | for (int i = 0; i < socket_functions_list.size(); ++i) { 394 | TrimWhitespaceASCII(socket_functions_list[i], 395 | &socket_functions_list[i]); 396 | replace_function(socket_functions_list[i].c_str(), 397 | socket_disabled_function_table TSRMLS_CC); 398 | } 399 | } 400 | 401 | static void gae_runtime_module_init_globals( 402 | zend_gae_runtime_module_globals* module_globals) { 403 | module_globals->recorded_errors_array = NULL; 404 | module_globals->disable_readonly_filesystem = 0; 405 | module_globals->enable_curl_lite = 0; 406 | module_globals->enable_mail_replacement = FLAGS_php_enable_mail_replacement; 407 | module_globals->enable_gcs_stat_cache = FLAGS_php_enable_gcs_stat_cache; 408 | module_globals->vfs_initialized = false; 409 | } 410 | 411 | static void zend_update_ini_entry( 412 | const char* key, const char* value TSRMLS_DC) { 413 | 414 | zend_ini_entry *entry; 415 | if (zend_hash_find(EG(ini_directives), 416 | key, 417 | strlen(key) + 1, 418 | reinterpret_cast(&entry)) == FAILURE) { 419 | php_error(E_ERROR, "Fail to find existing ini directive %s.", key); 420 | return; 421 | } 422 | 423 | char* duplicate = pestrdup(value, 1); 424 | overriden_ini_values->push_back(duplicate); 425 | entry->value = duplicate; 426 | entry->value_length = strlen(duplicate) + 1; 427 | } 428 | 429 | static void zend_merge_with_user_ini_string( 430 | const char* key_name, const string& value TSRMLS_DC) { 431 | vector strings; 432 | strings.push_back(value); 433 | 434 | const char* ini_value; 435 | if (get_user_ini_string(key_name, &ini_value) == SUCCESS) { 436 | strings.push_back(ini_value); 437 | } 438 | 439 | set string_set; 440 | for (int i = 0; i < strings.size(); ++i) { 441 | vector splited; 442 | SplitString(strings[i], ",", &splited); 443 | for (int i = 0; i < splited.size(); ++i) { 444 | TrimWhitespaceASCII(splited[i], &splited[i]); 445 | string_set.insert(splited[i]); 446 | } 447 | } 448 | 449 | string merged = JoinString( 450 | vector(string_set.begin(), string_set.end()), ","); 451 | zend_update_ini_entry(key_name, merged.c_str() TSRMLS_CC); 452 | } 453 | 454 | static void override_ini_values(INIT_FUNC_ARGS) { 455 | if (!FLAGS_php_enable_tempnam) { 456 | if (!FLAGS_php_disable_functions.empty()) { 457 | FLAGS_php_disable_functions.append(", "); 458 | } 459 | FLAGS_php_disable_functions.append("tempnam"); 460 | } 461 | 462 | zend_merge_with_user_ini_string("disable_functions", 463 | FLAGS_php_disable_functions TSRMLS_CC); 464 | zend_merge_with_user_ini_string("disable_classes", 465 | FLAGS_php_disable_classes TSRMLS_CC); 466 | zend_update_ini_entry("file_uploads", 467 | FLAGS_php_enable_direct_uploads ? "1" : "0" TSRMLS_CC); 468 | zend_update_ini_entry("google_app_engine.direct_file_upload", 469 | FLAGS_php_enable_direct_uploads ? "1" : "0" TSRMLS_CC); 470 | 471 | zend_update_ini_entry( 472 | "google_app_engine.enable_additional_cloud_storage_headers", 473 | FLAGS_php_enable_additional_cloud_storage_headers ? "1" : "0" TSRMLS_CC); 474 | 475 | zend_update_ini_entry( 476 | "google_app_engine.enable_gcs_stat_cache", 477 | FLAGS_php_enable_gcs_stat_cache ? "1" : "0" TSRMLS_CC); 478 | 479 | zend_update_ini_entry( 480 | "google_app_engine.gcs_default_keyword", 481 | FLAGS_php_enable_gcs_default_keyword ? "1" : "0" TSRMLS_CC); 482 | } 483 | 484 | static void load_user_ini(INIT_FUNC_ARGS) { 485 | CHECK(user_ini_hash_table); 486 | // Read in the users php.ini file 487 | const char* const application_basedir = 488 | appengine::PhpRuntimeSapi::GetApplicationBasedir(); 489 | 490 | if (!application_basedir) { 491 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 492 | "Unable to load users's php.ini due to missing application base dir."); 493 | return; 494 | } 495 | 496 | php_parse_user_ini_file(application_basedir, 497 | "php.ini", 498 | user_ini_hash_table TSRMLS_CC); 499 | } 500 | 501 | static int get_user_ini_string(const char* key_name, const char** result) { 502 | CHECK(user_ini_hash_table); 503 | zval* tmp; 504 | if (zend_hash_find(user_ini_hash_table, 505 | key_name, 506 | strlen(key_name) + 1, 507 | reinterpret_cast(&tmp)) == FAILURE) { 508 | *result = NULL; 509 | return FAILURE; 510 | } 511 | *result = Z_STRVAL_P(tmp); 512 | return SUCCESS; 513 | } 514 | 515 | static void soft_disable_functions(INIT_FUNC_ARGS) { 516 | vector soft_disable_functions_list; 517 | SplitString(kSoftDisableFunctions, ",", &soft_disable_functions_list); 518 | SplitString(FLAGS_php_additional_soft_disable_functions, 519 | ",", 520 | &soft_disable_functions_list); 521 | 522 | vector user_enable_functions_list; 523 | const char* enabled_functions; 524 | 525 | if (get_user_ini_string(kEnableFunctions, &enabled_functions) == SUCCESS) { 526 | SplitString(enabled_functions, ",", &user_enable_functions_list); 527 | } 528 | 529 | for (int i = 0; i < soft_disable_functions_list.size(); ++i) { 530 | TrimWhitespaceASCII(soft_disable_functions_list[i], 531 | &soft_disable_functions_list[i]); 532 | } 533 | 534 | set disable_functions(soft_disable_functions_list.begin(), 535 | soft_disable_functions_list.end()); 536 | 537 | for (int i = 0; i < user_enable_functions_list.size(); ++i) { 538 | TrimWhitespaceASCII(user_enable_functions_list[i], 539 | &user_enable_functions_list[i]); 540 | disable_functions.erase(user_enable_functions_list[i]); 541 | } 542 | 543 | for (set::iterator it = disable_functions.begin(); 544 | it != disable_functions.end(); 545 | ++it) { 546 | replace_function((*it).c_str(), soft_disabled_function_table TSRMLS_CC); 547 | } 548 | } 549 | 550 | static void enable_allowed_include_streams(INIT_FUNC_ARGS) { 551 | // If the user has declared buckets that can be included then enable include 552 | // without them having to specifically enable the "gs" stream. 553 | bool enable_gs_include = false; 554 | const char* allowed_buckets; 555 | if (get_user_ini_string("google_app_engine.allow_include_gs_buckets", 556 | &allowed_buckets) == SUCCESS) { 557 | if (strlen(allowed_buckets) > 0) { 558 | enable_gs_include = true; 559 | } 560 | REGISTER_STRINGL_CONSTANT("GAE_INCLUDE_GS_BUCKETS", 561 | const_cast(allowed_buckets), 562 | strlen(allowed_buckets), 563 | CONST_CS | CONST_PERSISTENT); 564 | } else { 565 | REGISTER_STRING_CONSTANT("GAE_INCLUDE_GS_BUCKETS", 566 | "", 567 | CONST_CS | CONST_PERSISTENT); 568 | } 569 | // As the gs:// stream is defined in user space, register a constant here 570 | // that we check in setup.php to configure the access. 571 | REGISTER_LONG_CONSTANT("GAE_INCLUDE_REQUIRE_GS_STREAMS", 572 | enable_gs_include ? 1 : 0, 573 | CONST_CS | CONST_PERSISTENT); 574 | } 575 | 576 | static void allocate_static_resources(INIT_FUNC_ARGS) { 577 | if (user_ini_hash_table == NULL) { 578 | user_ini_hash_table = reinterpret_cast( 579 | pemalloc(sizeof(HashTable), 1)); 580 | zend_hash_init(user_ini_hash_table, 581 | 0, 582 | NULL, 583 | (dtor_func_t) config_zval_dtor, 584 | 1); 585 | } 586 | if (redirect_path_hash_table == NULL) { 587 | redirect_path_hash_table = reinterpret_cast( 588 | pemalloc(sizeof(HashTable), 1)); 589 | zend_hash_init(redirect_path_hash_table, 590 | 16, 591 | NULL, 592 | reinterpret_cast(free_redirect_destination), 593 | 1); 594 | } 595 | if (disabled_function_names == NULL) { 596 | disabled_function_names = new vector(); 597 | } 598 | if (overriden_ini_values == NULL) { 599 | overriden_ini_values = new vector(); 600 | } 601 | } 602 | 603 | template 604 | static bool sort_string_key_lengths(const T& s1, const T& s2) { 605 | return s1.first.length() > s2.first.length(); 606 | } 607 | 608 | static void load_redirect_paths(INIT_FUNC_ARGS) { 609 | vector > split_paths; 610 | vector path_pairs; 611 | SplitString(GAERT_G(redirct_paths), ";", &path_pairs); 612 | 613 | for (int i = 0; i < path_pairs.size(); ++i) { 614 | TrimWhitespaceASCII(path_pairs[i], &path_pairs[i]); 615 | if (!path_pairs[i].empty()) { 616 | vector paths = SplitStringWithMaxSplit(path_pairs[i], ":", 1); 617 | if (paths.size() == 2) { 618 | TrimWhitespaceASCII(paths[0], &paths[0]); 619 | TrimWhitespaceASCII(paths[1], &paths[1]); 620 | if (paths[0].length() > 0 && paths[1].length() > 0) { 621 | // Do not allow users to redirect the base directory, as it stops the 622 | // app from working. 623 | if (paths[0] != kBaseDirectory) { 624 | split_paths.push_back(std::make_pair(paths[0], paths[1])); 625 | } else { 626 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 627 | "Cannot redirect the path ~/, redirection ignored."); 628 | } 629 | } else { 630 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 631 | "Invalid redirect from_path(%s) or to_path(%s)", 632 | paths[0].c_str(), 633 | paths[1].c_str()); 634 | } 635 | } else { 636 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 637 | "Invalid redirect path pair: %s", path_pairs[i].c_str()); 638 | } 639 | } 640 | } 641 | 642 | // Exapand any redirect paths that start with ~/ to be the applications 643 | // base directory. 644 | const char* const application_basedir = 645 | appengine::PhpRuntimeSapi::GetApplicationBasedir(); 646 | 647 | for (int i = 0; i < split_paths.size(); ++i) { 648 | if (split_paths[i].first.find(kBaseDirectory) == 0) { 649 | split_paths[i].first.replace(0, 650 | strlen(kTilde), 651 | application_basedir); 652 | } 653 | } 654 | 655 | 656 | // Sort from longest path to shortest, so that when matching redirect paths 657 | // we will match the longest matching path first. 658 | std::sort(split_paths.begin(), split_paths.end(), 659 | sort_string_key_lengths >); 660 | 661 | for (int i = 0; i < split_paths.size(); ++i) { 662 | const string& key = split_paths[i].first; 663 | char* data = pestrdup(split_paths[i].second.c_str(), 1); 664 | zend_hash_add(redirect_path_hash_table, 665 | key.c_str(), 666 | key.length() + 1, 667 | reinterpret_cast(&data), 668 | sizeof(char*), 669 | NULL); 670 | } 671 | } 672 | 673 | static void release_static_resources(TSRMLS_D) { 674 | if (user_ini_hash_table) { 675 | zend_hash_destroy(user_ini_hash_table); 676 | free(user_ini_hash_table); 677 | user_ini_hash_table = NULL; 678 | } 679 | if (disabled_function_names) { 680 | for (int i = 0; i < disabled_function_names->size(); ++i) { 681 | pefree(disabled_function_names->at(i), 1); 682 | } 683 | delete disabled_function_names; 684 | disabled_function_names = NULL; 685 | } 686 | if (overriden_ini_values) { 687 | for (int i = 0; i < overriden_ini_values->size(); ++i) { 688 | pefree(overriden_ini_values->at(i), 1); 689 | } 690 | delete overriden_ini_values; 691 | overriden_ini_values = NULL; 692 | } 693 | if (redirect_path_hash_table) { 694 | zend_hash_destroy(redirect_path_hash_table); 695 | free(redirect_path_hash_table); 696 | redirect_path_hash_table = NULL; 697 | } 698 | } 699 | 700 | static void update_ca_bundle_local_path(INIT_FUNC_ARGS) { 701 | const char* const cainfo_ini_name = "curl.cainfo"; 702 | const char* ini_value = zend_ini_string(const_cast(cainfo_ini_name), 703 | strlen(cainfo_ini_name) + 1, 704 | 0); 705 | 706 | if (ini_value != NULL) { 707 | if (ini_value[0] == '~' && ini_value[1] == '/') { 708 | const char* const application_basedir = 709 | appengine::PhpRuntimeSapi::GetApplicationBasedir(); 710 | string new_path = application_basedir + string(ini_value + 1); 711 | zend_update_ini_entry(cainfo_ini_name, new_path.c_str() TSRMLS_CC); 712 | } 713 | } 714 | } 715 | 716 | bool appengine::is_redirect_path(const char* path, char** new_path TSRMLS_DC) { 717 | HashPosition pos; 718 | 719 | MutexLock l(&redirect_path_ht_mutex); 720 | 721 | for (zend_hash_internal_pointer_reset_ex(redirect_path_hash_table, &pos); 722 | zend_hash_has_more_elements_ex(redirect_path_hash_table, &pos) == SUCCESS; 723 | zend_hash_move_forward_ex(redirect_path_hash_table, &pos)) { 724 | char* key; 725 | uint keylen; 726 | ulong idx; 727 | 728 | if (zend_hash_get_current_key_ex(redirect_path_hash_table, 729 | &key, 730 | &keylen, 731 | &idx, 732 | 0, 733 | &pos) == HASH_KEY_IS_STRING) { 734 | // TODO(slangley): Should be case insensitive? 735 | if (strncmp(key, path, keylen-1) == 0) { 736 | char** data; 737 | if (zend_hash_get_current_data_ex(redirect_path_hash_table, 738 | reinterpret_cast(&data), 739 | &pos) == SUCCESS) { 740 | // We have a match, take a copy and return it. 741 | string updated_path = path; 742 | string replacement_prefix = *data; 743 | updated_path.replace(0, keylen-1, replacement_prefix); 744 | updated_path.insert(0, kRedirectPrependString); 745 | *new_path = estrdup(updated_path.c_str()); 746 | return true; 747 | } 748 | } 749 | } 750 | } 751 | return false; 752 | } 753 | 754 | php_stream_wrapper* appengine::get_correct_stream_wrapper(const char* path, 755 | char** path_for_open, int options TSRMLS_DC) { 756 | if (!strncmp(path, kRedirectPrependString, strlen(kRedirectPrependString))) { 757 | const char* actual_path = path + strlen(kRedirectPrependString); 758 | if (strncmp(actual_path, kVfsPrefix, strlen(kVfsPrefix)) == 0) { 759 | if (!GAERT_G(vfs_initialized)) { 760 | GAERT_G(vfs_initialized) = initialize_virtual_filesystem(TSRMLS_C); 761 | } 762 | } 763 | return php_stream_locate_url_wrapper(actual_path, 764 | path_for_open, 765 | options TSRMLS_CC); 766 | } else { 767 | return php_stream_locate_url_wrapper(path, 768 | path_for_open, 769 | options TSRMLS_CC); 770 | } 771 | } 772 | 773 | 774 | bool initialize_virtual_filesystem(TSRMLS_D) { 775 | bool result = false; 776 | zval* z_function_name = NULL; 777 | zval* z_retval = NULL; 778 | 779 | MAKE_STD_ZVAL(z_function_name); 780 | MAKE_STD_ZVAL(z_retval); 781 | ZVAL_STRING(z_function_name, kVirtualFileSystemInitializeFunctionName, 0); 782 | 783 | // Call VirtualFileSystem::getInstance() and store the resulting object in 784 | // z_retval. 785 | if (call_user_function(EG(function_table), 786 | NULL, 787 | z_function_name, 788 | z_retval, 789 | 0, 790 | NULL TSRMLS_CC) == SUCCESS) { 791 | zval* z_method_retval = NULL; 792 | 793 | MAKE_STD_ZVAL(z_method_retval); 794 | ZVAL_STRING(z_function_name, kVirtualFileSystemIntiailizeMethodName, 0); 795 | // Call the initialize() method on the VirtualFileSystem, which is stored in 796 | // the return value of the previous call. 797 | if (call_user_function(NULL, 798 | &z_retval, 799 | z_function_name, 800 | z_method_retval, 801 | 0, 802 | NULL TSRMLS_CC) == SUCCESS) { 803 | result = true; 804 | if (z_method_retval) { 805 | zval_ptr_dtor(&z_method_retval); 806 | } 807 | } else { 808 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 809 | "Unable to initialize virtial filesystem calling %s()", 810 | kVirtualFileSystemIntiailizeMethodName); 811 | } 812 | } else { 813 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 814 | "Unable to call %s()", 815 | kVirtualFileSystemInitializeFunctionName); 816 | } 817 | if (z_retval) { 818 | zval_ptr_dtor(&z_retval); 819 | } 820 | return result; 821 | } 822 | 823 | 824 | #define GAE_INI_ENTRY(name, default) \ 825 | PHP_INI_ENTRY(name, default, PHP_INI_PERDIR, NULL) 826 | #define GAE_INI_ENTRY_SYSTEM(name, default) \ 827 | PHP_INI_ENTRY(name, default, PHP_INI_SYSTEM, NULL) 828 | #define GAE_STR_INI_ENTRY(name, default, access, var) \ 829 | STD_PHP_INI_ENTRY(name, default, access, OnUpdateString , var, \ 830 | zend_gae_runtime_module_globals, \ 831 | gae_runtime_module_globals) 832 | #define GAE_BOOL_INI_ENTRY(name, default, access, var) \ 833 | STD_PHP_INI_BOOLEAN(name, default, access, OnUpdateBool, var, \ 834 | zend_gae_runtime_module_globals, \ 835 | gae_runtime_module_globals) 836 | 837 | // Note: The way these macros expand, we cannot use const_cast(kName) 838 | // as it results in sizeof(char*) being used to determine the name size, which 839 | // is wrong. 840 | PHP_INI_BEGIN() 841 | GAE_INI_ENTRY("google_app_engine.enable_functions", "") 842 | GAE_INI_ENTRY("google_app_engine.allow_include_gs_buckets", "") 843 | GAE_INI_ENTRY_SYSTEM("google_app_engine.direct_file_upload", "0") 844 | GAE_INI_ENTRY_SYSTEM("google_app_engine.gcs_default_keyword", "0") 845 | GAE_INI_ENTRY_SYSTEM( 846 | "google_app_engine.enable_additional_cloud_storage_headers", "0") 847 | GAE_STR_INI_ENTRY("google_app_engine.redirect_paths", 848 | "", 849 | PHP_INI_SYSTEM, 850 | redirct_paths) 851 | GAE_BOOL_INI_ENTRY("google_app_engine.disable_readonly_filesystem", 852 | "0", 853 | PHP_INI_SYSTEM, 854 | disable_readonly_filesystem) 855 | GAE_BOOL_INI_ENTRY("google_app_engine.enable_curl_lite", 856 | "0", 857 | PHP_INI_SYSTEM, 858 | enable_curl_lite) 859 | GAE_BOOL_INI_ENTRY("google_app_engine.enable_gcs_stat_cache", 860 | "0", 861 | PHP_INI_SYSTEM, 862 | enable_gcs_stat_cache) 863 | PHP_INI_END() 864 | 865 | // Module initialization function. 866 | static PHP_MINIT_FUNCTION(gae_runtime_module) { 867 | ZEND_INIT_MODULE_GLOBALS(gae_runtime_module, 868 | gae_runtime_module_init_globals, 869 | NULL); 870 | REGISTER_INI_ENTRIES(); 871 | 872 | allocate_static_resources(INIT_FUNC_ARGS_PASSTHRU); 873 | load_user_ini(INIT_FUNC_ARGS_PASSTHRU); 874 | override_ini_values(INIT_FUNC_ARGS_PASSTHRU); 875 | delete_functions(INIT_FUNC_ARGS_PASSTHRU); 876 | unregister_streams(INIT_FUNC_ARGS_PASSTHRU); 877 | unregister_stream_xports(INIT_FUNC_ARGS_PASSTHRU); 878 | soft_disable_functions(INIT_FUNC_ARGS_PASSTHRU); 879 | enable_allowed_include_streams(INIT_FUNC_ARGS_PASSTHRU); 880 | disable_socket_functions(INIT_FUNC_ARGS_PASSTHRU); 881 | replace_builtin_functions(INIT_FUNC_ARGS_PASSTHRU); 882 | update_ca_bundle_local_path(INIT_FUNC_ARGS_PASSTHRU); 883 | if (FLAGS_php_allow_file_redirect) { 884 | load_redirect_paths(INIT_FUNC_ARGS_PASSTHRU); 885 | } 886 | 887 | 888 | // Register our php://input handler. 889 | php_unregister_url_stream_wrapper(const_cast("php") TSRMLS_CC); 890 | php_register_url_stream_wrapper(const_cast("php"), 891 | &php_input_stream_wrapper TSRMLS_CC); 892 | 893 | // Register our __redirect:// stream handler 894 | php_register_url_stream_wrapper( 895 | const_cast(kRedirectProtocol), 896 | &php_redirect_stream_wrapper TSRMLS_CC); 897 | 898 | if (FLAGS_php_enforce_filesystem_readonly && 899 | !GAERT_G(disable_readonly_filesystem)) { 900 | appengine::hook_readonly_filesystem_wrapper( 901 | &php_plain_files_wrapper TSRMLS_CC); 902 | } 903 | 904 | zend_module_entry* modules_to_register[] = { 905 | &fake_memcache_module_entry, 906 | &fake_memcached_module_entry, 907 | &urlfetch_stream_wrapper_module_entry, 908 | // Must be NULL terminated. 909 | NULL, 910 | }; 911 | 912 | // Can't use zend_next_free_module() directly here as it is not declared 913 | // as ZEND_API, and thus unavailable when building on Windows. 914 | int next_free_module = zend_hash_num_elements(&module_registry) + 1; 915 | for (int i=0; modules_to_register[i] != NULL; ++i) { 916 | modules_to_register[i]->module_number = next_free_module++; 917 | zend_register_module_ex(modules_to_register[i] TSRMLS_CC); 918 | } 919 | 920 | if (GAERT_G(enable_curl_lite)) { 921 | fake_curl_module_entry.module_number = next_free_module++; 922 | zend_register_module_ex(&fake_curl_module_entry TSRMLS_CC); 923 | } 924 | 925 | return SUCCESS; 926 | } 927 | 928 | // Module shutdown function 929 | static PHP_MSHUTDOWN_FUNCTION(gae_runtime_module) { 930 | php_unregister_url_stream_wrapper(const_cast("php") TSRMLS_CC); 931 | 932 | release_static_resources(TSRMLS_C); 933 | 934 | #ifdef ZTS 935 | ts_free_id(gae_runtime_module_globals_id); 936 | #endif 937 | 938 | UNREGISTER_INI_ENTRIES(); 939 | return SUCCESS; 940 | } 941 | 942 | // Request initialization function 943 | static PHP_RINIT_FUNCTION(gae_runtime_module) { 944 | if (user_ini_hash_table) { 945 | // php_ini_activate_config is not thread safe. 946 | MutexLock l(&request_init_mutex); 947 | php_ini_activate_config(user_ini_hash_table, 948 | PHP_INI_PERDIR, 949 | PHP_INI_STAGE_HTACCESS TSRMLS_CC); 950 | } 951 | 952 | return SUCCESS; 953 | } 954 | 955 | // Request shutdown function 956 | static PHP_RSHUTDOWN_FUNCTION(gae_runtime_module) { 957 | // For now, nothing to initialize. 958 | return SUCCESS; 959 | } 960 | 961 | // Module info function. 962 | static PHP_MINFO_FUNCTION(gae_runtime_module) { 963 | php_info_print_table_start(); 964 | php_info_print_table_header(2, "Google App Engine Runtime Module", "enabled"); 965 | php_info_print_table_end(); 966 | 967 | DISPLAY_INI_ENTRIES(); 968 | } 969 | 970 | // Make an API call to the appserver, using the callback that is supplied in 971 | // the SAPI request context. 972 | // 973 | // Arguments: 974 | // - package: string name of the package for the API call. 975 | // - call: string name of the call to make 976 | // - request: string request protobuf 977 | // - result_array: A PHP array type for storage of the results of the API call 978 | // to be returned to the called. 979 | // - callback: callback for when the call is completed. 980 | // - deadline: number of seconds to allow for the RPC 981 | // 982 | // Throws: 983 | // - InvalidArgumentException: If the supplied callback is not callable. 984 | PHP_FUNCTION(make_call) { 985 | char* package = NULL; 986 | int package_len = 0; 987 | char* call_name = NULL; 988 | int call_name_len = 0; 989 | char* request = NULL; 990 | int request_len = 0; 991 | zval* result_array = NULL; 992 | zval* callback = NULL; 993 | long deadline = -1; // NOLINT 994 | 995 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 996 | const_cast("sssa|z!l"), 997 | &package, &package_len, 998 | &call_name, &call_name_len, 999 | &request, &request_len, 1000 | &result_array, 1001 | &callback, 1002 | &deadline) == FAILURE) { 1003 | // TODO(slangley): Throw exception? 1004 | php_error(E_WARNING, "Unable to parse parameters."); 1005 | return; 1006 | } 1007 | 1008 | char* callback_name = NULL; 1009 | if (callback != NULL && 1010 | !zend_is_callable(callback, 0, &callback_name TSRMLS_CC)) { 1011 | zend_throw_exception_ex(spl_ce_InvalidArgumentException, 1012 | 0 TSRMLS_CC, 1013 | const_cast("%s is not a valid callback."), 1014 | callback_name); 1015 | if (callback_name) { 1016 | efree(callback_name); 1017 | } 1018 | return; 1019 | } 1020 | 1021 | if (callback_name) { 1022 | efree(callback_name); 1023 | } 1024 | 1025 | // TODO(slangley): Async requests from user PHP is not possible, due to the 1026 | // single threading memory model. For now, this request must always be sync. 1027 | string package_str(package, package_len); 1028 | string call_str(call_name, call_name_len); 1029 | string request_str(request, request_len); 1030 | PhpApiRpc rpc; 1031 | 1032 | PhpRpcUtils::MakeApiCall(&rpc, 1033 | package_str, 1034 | call_str, 1035 | request_str, 1036 | deadline TSRMLS_CC); 1037 | 1038 | add_assoc_long(result_array, appengine::kErrorCodeName, rpc.error()); 1039 | add_assoc_long(result_array, 1040 | appengine::kApplicationErrorCodeName, 1041 | rpc.app_error()); 1042 | add_assoc_string(result_array, 1043 | appengine::kApplicationErrorDetailName, 1044 | const_cast(rpc.error_detail().c_str()), 1045 | 1); 1046 | add_assoc_stringl(result_array, 1047 | appengine::kResultStringName, 1048 | const_cast(rpc.response_pb().c_str()), 1049 | rpc.response_pb().length(), 1050 | 1); 1051 | add_assoc_long(result_array, appengine::kCpuUsageName, rpc.cpu_usage()); 1052 | 1053 | 1054 | zval dummy_retval; 1055 | 1056 | if (callback != NULL) { 1057 | call_user_function(EG(function_table), 1058 | NULL, 1059 | callback, 1060 | &dummy_retval, 1061 | 0, 1062 | NULL TSRMLS_CC); 1063 | } 1064 | 1065 | RETURN_NULL(); 1066 | } 1067 | 1068 | // Type of zend_error_cb, which does not have a public definition 1069 | static void (*old_error_handler)(int, 1070 | const char*, 1071 | const uint, 1072 | const char*, 1073 | va_list); 1074 | 1075 | // Error handler installed during the lint_string function - this captures 1076 | // errors into an array without reporting them or altering that state of the 1077 | // interpreter. 1078 | static void lint_error_handler(int error_num, 1079 | const char* filename, 1080 | uint line_no, 1081 | const char* format, 1082 | va_list args) { 1083 | TSRMLS_FETCH(); 1084 | 1085 | zval* error_array; 1086 | MAKE_STD_ZVAL(error_array); 1087 | array_init(error_array); 1088 | add_assoc_long(error_array, "error_number", error_num); 1089 | add_assoc_string(error_array, "file_name", const_cast(filename), 1); 1090 | add_assoc_long(error_array, "line_number", line_no); 1091 | 1092 | char* buffer; 1093 | int buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, args); 1094 | add_assoc_stringl(error_array, "error_message", buffer, buffer_len, 0); 1095 | 1096 | add_next_index_zval(GAERT_G(recorded_errors_array), error_array); 1097 | } 1098 | 1099 | // lint a string containing PHP code. 1100 | // Based on the php_lint_script function. 1101 | // 1102 | // Arguments: 1103 | // string - the script code. 1104 | // string - the name of the file being linted 1105 | // 1106 | // Returns: 1107 | // An array of associative arrays of linter errors from parsing the string. 1108 | PHP_FUNCTION(lint_string) { 1109 | char* script_code = NULL; 1110 | int script_code_len = 0; 1111 | char* filename = NULL; 1112 | int filename_len = 0; 1113 | 1114 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1115 | const_cast("ss"), 1116 | &script_code, &script_code_len, 1117 | &filename, &filename_len) == FAILURE) { 1118 | php_error(E_WARNING, "Unable to parse parameters."); 1119 | RETURN_FALSE; 1120 | } 1121 | 1122 | // If the user is not using the return value then do nothing. 1123 | if (!return_value_used) { 1124 | php_error_docref(NULL TSRMLS_CC, E_NOTICE, 1125 | "lint_string called without processing output"); 1126 | RETURN_NULL(); 1127 | } 1128 | 1129 | // Initialize the return_value and then share the pointer with the module 1130 | // global state. We will write the error messages directly into the 1131 | // return_value. 1132 | array_init(return_value); 1133 | GAERT_G(recorded_errors_array) = return_value; 1134 | 1135 | int retval = FAILURE; 1136 | zend_op_array* op_array; 1137 | // Replace the error callback with our own handler, that just records the 1138 | // errors but does not modify the executor state. 1139 | old_error_handler = zend_error_cb; 1140 | zend_error_cb = lint_error_handler; 1141 | 1142 | zend_try { 1143 | zval pv; 1144 | Z_STRLEN(pv) = script_code_len; 1145 | Z_STRVAL(pv) = script_code; 1146 | Z_TYPE(pv) = IS_STRING; 1147 | 1148 | op_array = zend_compile_string(&pv, filename TSRMLS_CC); 1149 | 1150 | if (op_array) { 1151 | destroy_op_array(op_array TSRMLS_CC); 1152 | efree(op_array); 1153 | } 1154 | retval = SUCCESS; 1155 | } zend_end_try(); 1156 | 1157 | // Reinstall saved state. 1158 | zend_error_cb = old_error_handler; 1159 | GAERT_G(recorded_errors_array) = NULL; 1160 | 1161 | // If successful, we will return the array - otherwise we will return FALSE. 1162 | if (retval == FAILURE) { 1163 | zval_dtor(return_value); 1164 | RETURN_FALSE; 1165 | } 1166 | } 1167 | 1168 | // Implement tmpfile() using an in-memory stream. Note that the type returned 1169 | // here is rather than but that should be transparent 1170 | // to most applications. 1171 | PHP_FUNCTION(memory_tmpfile) { 1172 | if (zend_parse_parameters_none() == FAILURE) { 1173 | return; 1174 | } 1175 | php_stream_to_zval(php_stream_memory_create(TEMP_STREAM_DEFAULT), 1176 | return_value); 1177 | } 1178 | 1179 | // Return an associative array of the user defined stream redirect paths. 1180 | PHP_FUNCTION(get_stream_redirect_paths) { 1181 | array_init(return_value); 1182 | 1183 | MutexLock l(&redirect_path_ht_mutex); 1184 | HashPosition pos; 1185 | 1186 | for (zend_hash_internal_pointer_reset_ex(redirect_path_hash_table, &pos); 1187 | zend_hash_has_more_elements_ex(redirect_path_hash_table, &pos) == SUCCESS; 1188 | zend_hash_move_forward_ex(redirect_path_hash_table, &pos)) { 1189 | char* key; 1190 | uint keylen; 1191 | ulong idx; 1192 | 1193 | if (zend_hash_get_current_key_ex(redirect_path_hash_table, 1194 | &key, 1195 | &keylen, 1196 | &idx, 1197 | 0, 1198 | &pos) == HASH_KEY_IS_STRING) { 1199 | char** data; 1200 | if (zend_hash_get_current_data_ex(redirect_path_hash_table, 1201 | reinterpret_cast(&data), 1202 | &pos) == SUCCESS) { 1203 | add_assoc_string(return_value, key, *data, 1); 1204 | } 1205 | } 1206 | } 1207 | } 1208 | 1209 | // Add cross stream wrapper type support to rename() using copy-then-unlink. 1210 | // The remainder part of the function is copied directly from PHP's default 1211 | // implementation. 1212 | // 1213 | // Arguments: 1214 | // string - The old file name 1215 | // string - The new file name 1216 | // context - Optional stream context 1217 | // 1218 | // Returns: 1219 | // TRUE on success or FALSE on failure. 1220 | PHP_FUNCTION(cross_stream_wrapper_rename) { 1221 | char *old_name, *new_name; 1222 | int old_name_len, new_name_len; 1223 | zval *zcontext = NULL; 1224 | php_stream_wrapper *wrapper = NULL; 1225 | php_stream_context *context = NULL; 1226 | 1227 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1228 | "pp|r", 1229 | &old_name, 1230 | &old_name_len, 1231 | &new_name, 1232 | &new_name_len, 1233 | &zcontext) == FAILURE) { 1234 | RETURN_FALSE; 1235 | } 1236 | 1237 | // Determine redirect paths now so we can retrieve the correct stream wrappers 1238 | char* old_name_redirect = NULL; 1239 | if (appengine::is_redirect_path(old_name, &old_name_redirect TSRMLS_CC)) { 1240 | old_name = old_name_redirect; 1241 | } 1242 | char* new_name_redirect = NULL; 1243 | if (appengine::is_redirect_path(new_name, &new_name_redirect TSRMLS_CC)) { 1244 | new_name = new_name_redirect; 1245 | } 1246 | 1247 | wrapper = appengine::get_correct_stream_wrapper(old_name, 1248 | &old_name, 1249 | 0 TSRMLS_CC); 1250 | 1251 | if (!wrapper || !wrapper->wops) { 1252 | php_error_docref( 1253 | NULL TSRMLS_CC, E_WARNING, "Unable to locate stream wrapper"); 1254 | RETURN_FALSE; 1255 | } 1256 | 1257 | if (!wrapper->wops->rename) { 1258 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1259 | "%s wrapper does not support renaming", 1260 | wrapper->wops->label ? wrapper->wops->label : "Source"); 1261 | RETURN_FALSE; 1262 | } 1263 | 1264 | // This is the only part changed from the original implementation. Instead of 1265 | // giving up and printing an error message, use copy-then-unlink to rename 1266 | // across different types of wrapper. 1267 | if (wrapper != appengine::get_correct_stream_wrapper(new_name, 1268 | &new_name, 1269 | 0 TSRMLS_CC) && 1270 | wrapper->wops->unlink) { 1271 | if (php_copy_file_ctx(old_name, 1272 | new_name, 1273 | 0, 1274 | context TSRMLS_CC) != SUCCESS) { 1275 | RETURN_FALSE; 1276 | } 1277 | 1278 | RETURN_BOOL(wrapper->wops->unlink( 1279 | wrapper, old_name, REPORT_ERRORS, context TSRMLS_CC)); 1280 | } 1281 | 1282 | context = reinterpret_cast( 1283 | php_stream_context_from_zval(zcontext, 0)); 1284 | 1285 | RETURN_BOOL(wrapper->wops->rename( 1286 | wrapper, old_name, new_name, 0, context TSRMLS_CC)); 1287 | } 1288 | 1289 | // Provide undocumented function for removing a file from rfc1867_uploaded_files 1290 | // hash for use by the user-space implementation of move_uploaded_file(). 1291 | PHP_FUNCTION(__remove_uploaded_file) { 1292 | if (!SG(rfc1867_uploaded_files)) { 1293 | RETURN_FALSE; 1294 | } 1295 | 1296 | char* name = NULL; 1297 | int name_len = 0; 1298 | 1299 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1300 | "s", 1301 | &name, &name_len) == FAILURE) { 1302 | RETURN_FALSE; 1303 | } 1304 | 1305 | if (!zend_hash_exists(SG(rfc1867_uploaded_files), 1306 | name, 1307 | name_len + 1)) { 1308 | php_error(E_WARNING, "Not an uploaded file."); 1309 | RETURN_FALSE; 1310 | } 1311 | 1312 | zend_hash_del(SG(rfc1867_uploaded_files), name, name_len + 1); 1313 | RETURN_TRUE; 1314 | } 1315 | 1316 | PHP_FUNCTION(glob) { 1317 | char* pattern = NULL; 1318 | int pattern_len; 1319 | long flags = 0; 1320 | zval* z_function_name; 1321 | zval* z_args[2]; 1322 | zval* z_pattern; 1323 | zval* z_flags; 1324 | zval* z_retval = NULL; 1325 | 1326 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1327 | "p|l", 1328 | &pattern, 1329 | &pattern_len, 1330 | &flags) == FAILURE) { 1331 | return; 1332 | } 1333 | if (pattern_len >= MAXPATHLEN) { 1334 | php_error_docref( 1335 | NULL TSRMLS_CC, 1336 | E_WARNING, 1337 | "Pattern exceeds the maximum allowed length of %d characters", 1338 | MAXPATHLEN); 1339 | RETURN_FALSE; 1340 | } 1341 | 1342 | MAKE_STD_ZVAL(z_function_name); 1343 | MAKE_STD_ZVAL(z_pattern); 1344 | MAKE_STD_ZVAL(z_flags); 1345 | MAKE_STD_ZVAL(z_retval); 1346 | 1347 | ZVAL_STRING(z_function_name, kGlobFunctionName, 1); 1348 | ZVAL_STRINGL(z_pattern, pattern, pattern_len, 1); 1349 | ZVAL_LONG(z_flags, flags); 1350 | 1351 | 1352 | z_args[0] = z_pattern; 1353 | z_args[1] = z_flags; 1354 | 1355 | if (call_user_function(EG(function_table), 1356 | NULL, 1357 | z_function_name, 1358 | z_retval, 1359 | 2, 1360 | z_args TSRMLS_CC) == SUCCESS) { 1361 | if (z_retval) { 1362 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1363 | } 1364 | } else { 1365 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1366 | "Unable to call %s()", kGlobFunctionName); 1367 | } 1368 | 1369 | zval_ptr_dtor(&z_function_name); 1370 | zval_ptr_dtor(&z_pattern); 1371 | zval_ptr_dtor(&z_flags); 1372 | } 1373 | 1374 | // A wrapper for user-space implementation of tempnam(). 1375 | PHP_FUNCTION(userspace_tempnam) { 1376 | char *dir, *prefix; 1377 | int dir_len, prefix_len; 1378 | zval* z_function_name; 1379 | zval* z_args[2]; 1380 | zval* z_dir; 1381 | zval* z_prefix; 1382 | zval* z_retval = NULL; 1383 | 1384 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1385 | "ps", 1386 | &dir, 1387 | &dir_len, 1388 | &prefix, 1389 | &prefix_len) == FAILURE) { 1390 | return; 1391 | } 1392 | 1393 | MAKE_STD_ZVAL(z_function_name); 1394 | MAKE_STD_ZVAL(z_dir); 1395 | MAKE_STD_ZVAL(z_prefix); 1396 | MAKE_STD_ZVAL(z_retval); 1397 | 1398 | ZVAL_STRING(z_function_name, kTempnamFunctionName, 1); 1399 | ZVAL_STRINGL(z_dir, dir, dir_len, 1); 1400 | ZVAL_STRINGL(z_prefix, prefix, prefix_len, 1); 1401 | 1402 | z_args[0] = z_dir; 1403 | z_args[1] = z_prefix; 1404 | 1405 | if (call_user_function(EG(function_table), 1406 | NULL, 1407 | z_function_name, 1408 | z_retval, 1409 | 2, 1410 | z_args TSRMLS_CC) == SUCCESS) { 1411 | if (z_retval) { 1412 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1413 | } 1414 | } else { 1415 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1416 | "Unable to call %s()", kTempnamFunctionName); 1417 | } 1418 | 1419 | zval_ptr_dtor(&z_function_name); 1420 | zval_ptr_dtor(&z_dir); 1421 | zval_ptr_dtor(&z_prefix); 1422 | } 1423 | 1424 | // A wrapper for user-space implementation of sys_get_temp_dir(). 1425 | PHP_FUNCTION(userspace_sys_get_temp_dir) { 1426 | zval* z_function_name; 1427 | zval* z_retval = NULL; 1428 | 1429 | if (zend_parse_parameters_none() == FAILURE) { 1430 | return; 1431 | } 1432 | 1433 | MAKE_STD_ZVAL(z_function_name); 1434 | MAKE_STD_ZVAL(z_retval); 1435 | 1436 | ZVAL_STRING(z_function_name, kSysGetTempDirFunctionName, 1); 1437 | 1438 | if (call_user_function(EG(function_table), 1439 | NULL, 1440 | z_function_name, 1441 | z_retval, 1442 | 0, 1443 | NULL TSRMLS_CC) == SUCCESS) { 1444 | if (z_retval) { 1445 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1446 | } 1447 | } else { 1448 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1449 | "Unable to call %s()", kSysGetTempDirFunctionName); 1450 | } 1451 | 1452 | zval_ptr_dtor(&z_function_name); 1453 | } 1454 | 1455 | // A wrapper for user-space implementation of mail(). 1456 | PHP_FUNCTION(userspace_mail) { 1457 | char *to = NULL; 1458 | char *subject = NULL; 1459 | char *message = NULL; 1460 | char *headers = NULL; 1461 | char *extra_cmd = NULL; 1462 | int to_len = 0; 1463 | int subject_len = 0; 1464 | int message_len = 0; 1465 | int headers_len = 0; 1466 | int extra_cmd_len = 0; 1467 | zval* z_function_name; 1468 | zval* z_retval = NULL; 1469 | zval* z_args[5]; 1470 | 1471 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1472 | "sss|ss", 1473 | &to, 1474 | &to_len, 1475 | &subject, 1476 | &subject_len, 1477 | &message, 1478 | &message_len, 1479 | &headers, 1480 | &headers_len, 1481 | &extra_cmd, 1482 | &extra_cmd_len) == FAILURE) { 1483 | return; 1484 | } 1485 | 1486 | if (!GAERT_G(enable_mail_replacement)) { 1487 | zend_error(E_WARNING, "The function 'mail' is not implemented."); 1488 | RETURN_FALSE; 1489 | } 1490 | 1491 | MAKE_STD_ZVAL(z_function_name); 1492 | MAKE_STD_ZVAL(z_retval); 1493 | for (int i = 0; i < 5; i++) { 1494 | MAKE_STD_ZVAL(z_args[i]); 1495 | } 1496 | 1497 | ZVAL_STRINGL(z_args[0], to, to_len, 1); 1498 | ZVAL_STRINGL(z_args[1], subject, subject_len, 1); 1499 | ZVAL_STRINGL(z_args[2], message, message_len, 1); 1500 | 1501 | if (headers) { 1502 | ZVAL_STRINGL(z_args[3], headers, headers_len, 1); 1503 | } else { 1504 | ZVAL_NULL(z_args[3]); 1505 | } 1506 | 1507 | if (extra_cmd) { 1508 | ZVAL_STRINGL(z_args[4], extra_cmd, extra_cmd_len, 1); 1509 | } else { 1510 | ZVAL_NULL(z_args[4]); 1511 | } 1512 | 1513 | ZVAL_STRING(z_function_name, kMailFunctionName, 1); 1514 | 1515 | if (call_user_function(EG(function_table), 1516 | NULL, 1517 | z_function_name, 1518 | z_retval, 1519 | 5, 1520 | z_args TSRMLS_CC) == SUCCESS) { 1521 | if (z_retval) { 1522 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1523 | } 1524 | } else { 1525 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1526 | "Unable to call %s()", kMailFunctionName); 1527 | } 1528 | 1529 | zval_ptr_dtor(&z_function_name); 1530 | for (int i = 0; i < 5; i++) { 1531 | zval_ptr_dtor(&z_args[i]); 1532 | } 1533 | } 1534 | 1535 | PHP_FUNCTION(gcs_clearstatcache) { 1536 | zend_bool clear_realpath_cache = 0; 1537 | char *filename = NULL; 1538 | int filename_len = 0; 1539 | 1540 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1541 | "|bp", 1542 | &clear_realpath_cache, 1543 | &filename, 1544 | &filename_len) == FAILURE) { 1545 | return; 1546 | } 1547 | 1548 | zval* z_function_name; 1549 | zval* z_retval; 1550 | zval* z_args[1]; 1551 | MAKE_STD_ZVAL(z_function_name); 1552 | MAKE_STD_ZVAL(z_args[0]); 1553 | MAKE_STD_ZVAL(z_retval); 1554 | 1555 | ZVAL_STRING(z_function_name, kClearGcsStatCacheFunctionName, 1); 1556 | ZVAL_STRINGL(z_args[0], filename, filename_len, 0); 1557 | 1558 | int arg_count = filename != NULL ? 1 : 0; 1559 | 1560 | if (call_user_function(EG(function_table), 1561 | NULL, 1562 | z_function_name, 1563 | z_retval, 1564 | arg_count, 1565 | z_args TSRMLS_CC) != SUCCESS) { 1566 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1567 | "Unable to call %s()", kClearGcsStatCacheFunctionName); 1568 | } 1569 | FREE_ZVAL(z_args[0]); 1570 | FREE_ZVAL(z_function_name); 1571 | FREE_ZVAL(z_retval); 1572 | 1573 | php_clear_stat_cache(clear_realpath_cache, filename, filename_len TSRMLS_CC); 1574 | } 1575 | 1576 | // A wrapper for user-space implementation of gethostname(). 1577 | PHP_FUNCTION(userspace_gethostname) { 1578 | zval* z_function_name; 1579 | zval* z_retval; 1580 | 1581 | if (zend_parse_parameters_none() == FAILURE) { 1582 | return; 1583 | } 1584 | 1585 | MAKE_STD_ZVAL(z_function_name); 1586 | MAKE_STD_ZVAL(z_retval); 1587 | 1588 | ZVAL_STRING(z_function_name, kGetHostnameFunctionName, 1); 1589 | 1590 | if (call_user_function(EG(function_table), 1591 | NULL, 1592 | z_function_name, 1593 | z_retval, 1594 | 0, 1595 | NULL TSRMLS_CC) != SUCCESS) { 1596 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1597 | "Unable to call %s()", kGetHostnameFunctionName); 1598 | } else { 1599 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1600 | } 1601 | 1602 | zval_ptr_dtor(&z_function_name); 1603 | } 1604 | 1605 | // A wrapper for user-space implementation of move_uploaded_file(). 1606 | PHP_FUNCTION(userspace_move_uploaded_file) { 1607 | char *filename = NULL; 1608 | char *destination = NULL; 1609 | int filename_len = 0; 1610 | int destination_len = 0; 1611 | zval *context_options = NULL; 1612 | zval *z_function_name; 1613 | zval *z_retval = NULL; 1614 | zval *z_args[3]; 1615 | 1616 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1617 | "ss|a", 1618 | &filename, 1619 | &filename_len, 1620 | &destination, 1621 | &destination_len, 1622 | &context_options) == FAILURE) { 1623 | return; 1624 | } 1625 | 1626 | MAKE_STD_ZVAL(z_function_name); 1627 | MAKE_STD_ZVAL(z_retval); 1628 | for (int i = 0; i < 3; i++) { 1629 | MAKE_STD_ZVAL(z_args[i]); 1630 | } 1631 | 1632 | ZVAL_STRING(z_function_name, kMoveUploadedFileFunctionName, 1); 1633 | ZVAL_STRINGL(z_args[0], filename, filename_len, 1); 1634 | ZVAL_STRINGL(z_args[1], destination, destination_len, 1); 1635 | if (context_options) { 1636 | ZVAL_ZVAL(z_args[2], context_options, 1, 1); 1637 | } else { 1638 | ZVAL_NULL(z_args[2]); 1639 | } 1640 | 1641 | if (call_user_function(EG(function_table), 1642 | NULL, 1643 | z_function_name, 1644 | z_retval, 1645 | 3, 1646 | z_args TSRMLS_CC) == SUCCESS) { 1647 | if (z_retval) { 1648 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1649 | } 1650 | } else { 1651 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1652 | "Unable to call %s()", kMoveUploadedFileFunctionName); 1653 | } 1654 | 1655 | zval_ptr_dtor(&z_function_name); 1656 | for (int i = 0; i < 3; i++) { 1657 | zval_ptr_dtor(&z_args[i]); 1658 | } 1659 | } 1660 | 1661 | // A wrapper for user-space implementation of syslog(). 1662 | PHP_FUNCTION(userspace_syslog) { 1663 | long priority; 1664 | char *message; 1665 | int message_len; 1666 | zval *z_function_name; 1667 | zval *z_retval = NULL; 1668 | zval *z_args[2]; 1669 | 1670 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 1671 | "ls", 1672 | &priority, 1673 | &message, 1674 | &message_len) == FAILURE) { 1675 | return; 1676 | } 1677 | 1678 | MAKE_STD_ZVAL(z_function_name); 1679 | MAKE_STD_ZVAL(z_args[0]); 1680 | MAKE_STD_ZVAL(z_args[1]); 1681 | MAKE_STD_ZVAL(z_retval); 1682 | 1683 | ZVAL_STRING(z_function_name, kLogFunctionName, 1); 1684 | ZVAL_LONG(z_args[0], kSyslogPriorityMap[priority & kSyslogPriorityMask]); 1685 | ZVAL_STRINGL(z_args[1], message, message_len, 1); 1686 | 1687 | if (call_user_function(EG(function_table), 1688 | NULL, 1689 | z_function_name, 1690 | z_retval, 1691 | 2, 1692 | z_args TSRMLS_CC) != SUCCESS) { 1693 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 1694 | "Unable to call %s()", kLogFunctionName); 1695 | 1696 | if (z_retval) { 1697 | COPY_PZVAL_TO_ZVAL(*return_value, z_retval); 1698 | } 1699 | } 1700 | 1701 | zval_ptr_dtor(&z_function_name); 1702 | zval_ptr_dtor(&z_args[0]); 1703 | zval_ptr_dtor(&z_args[1]); 1704 | } 1705 | 1706 | // Arguments for make_call 1707 | ZEND_BEGIN_ARG_INFO_EX(make_call_arginfo, 0, 0, 4) 1708 | ZEND_ARG_INFO(0, pacakge) 1709 | ZEND_ARG_INFO(0, call) 1710 | ZEND_ARG_INFO(0, request_data) 1711 | ZEND_ARG_ARRAY_INFO(1, result_array, 0) 1712 | ZEND_ARG_INFO(0, callback) // Optional 1713 | ZEND_ARG_INFO(0, deadline) // Optional 1714 | ZEND_END_ARG_INFO() 1715 | 1716 | // Arguments for lint_string 1717 | ZEND_BEGIN_ARG_INFO_EX(lint_string_arginfo, 0, 0, 2) 1718 | ZEND_ARG_INFO(0, script_code) 1719 | ZEND_ARG_INFO(0, filename) 1720 | ZEND_END_ARG_INFO() 1721 | 1722 | ZEND_BEGIN_ARG_INFO_EX(memory_tmpfile_arginfo, 0, 0, 0) 1723 | ZEND_END_ARG_INFO() 1724 | 1725 | ZEND_BEGIN_ARG_INFO_EX(get_stream_redirect_paths_arginfo, 0, 0, 0) 1726 | ZEND_END_ARG_INFO() 1727 | 1728 | ZEND_BEGIN_ARG_INFO_EX(cross_stream_wrapper_rename_arginfo, 0, 0, 2) 1729 | ZEND_ARG_INFO(0, old_name) 1730 | ZEND_ARG_INFO(0, new_name) 1731 | ZEND_ARG_INFO(0, context) 1732 | ZEND_END_ARG_INFO() 1733 | 1734 | ZEND_BEGIN_ARG_INFO_EX(__remove_uploaded_file_arginfo, 0, 0, 1) 1735 | ZEND_ARG_INFO(0, name) 1736 | ZEND_END_ARG_INFO() 1737 | 1738 | ZEND_BEGIN_ARG_INFO_EX(glob_arginfo, 0, 0, 1) 1739 | ZEND_ARG_INFO(0, pattern) 1740 | ZEND_ARG_INFO(0, flags) 1741 | ZEND_END_ARG_INFO() 1742 | 1743 | ZEND_BEGIN_ARG_INFO_EX(userspace_tempnam_arginfo, 0, 0, 2) 1744 | ZEND_ARG_INFO(0, dir) 1745 | ZEND_ARG_INFO(0, prefix) 1746 | ZEND_END_ARG_INFO() 1747 | 1748 | ZEND_BEGIN_ARG_INFO_EX(userspace_sys_get_temp_dir_arginfo, 0, 0, 0) 1749 | ZEND_END_ARG_INFO() 1750 | 1751 | ZEND_BEGIN_ARG_INFO_EX(userspace_mail_arginfo, 0, 0, 3) 1752 | ZEND_ARG_INFO(0, to) 1753 | ZEND_ARG_INFO(0, subject) 1754 | ZEND_ARG_INFO(0, message) 1755 | ZEND_ARG_INFO(0, headers) // Optional 1756 | ZEND_ARG_INFO(0, etra_cmd) // Optional 1757 | ZEND_END_ARG_INFO() 1758 | 1759 | ZEND_BEGIN_ARG_INFO_EX(userspace_syslog_arginfo, 0, 0, 2) 1760 | ZEND_ARG_INFO(0, priority) 1761 | ZEND_ARG_INFO(0, message) 1762 | ZEND_END_ARG_INFO() 1763 | 1764 | ZEND_BEGIN_ARG_INFO_EX(gcs_clearstatcache_arginfo, 0, 0, 0) 1765 | ZEND_ARG_INFO(0, clear_realpath_cache) 1766 | ZEND_ARG_INFO(0, filename) 1767 | ZEND_END_ARG_INFO() 1768 | 1769 | ZEND_BEGIN_ARG_INFO_EX(userspace_gethostname_arginfo, 0, 0, 0) 1770 | ZEND_END_ARG_INFO() 1771 | 1772 | ZEND_BEGIN_ARG_INFO_EX(userspace_move_uploaded_file_arginfo, 0, 0, 2) 1773 | ZEND_ARG_INFO(0, filename) 1774 | ZEND_ARG_INFO(0, destination) 1775 | ZEND_ARG_INFO(0, context_options) // Optional 1776 | ZEND_END_ARG_INFO() 1777 | 1778 | // Define user visible functions 1779 | static const zend_function_entry gae_runtime_module_functions[] = { 1780 | PHP_FE(make_call, make_call_arginfo) 1781 | PHP_FE(lint_string, lint_string_arginfo) 1782 | PHP_FE(get_stream_redirect_paths, get_stream_redirect_paths_arginfo) 1783 | PHP_FE(__remove_uploaded_file, __remove_uploaded_file_arginfo) 1784 | PHP_FE_END 1785 | }; 1786 | 1787 | // Define built in functions we replace at startup 1788 | static const zend_function_entry runtime_builtin_replacement_functions[] = { 1789 | // TODO(marslan): Remove tmpfile replacement once PHP 5.4 is fully turned 1790 | // down, as the fix for b/22120224 is only applied to PHP 5.5 interpreter. 1791 | PHP_NAMED_FE(tmpfile, PHP_FN(memory_tmpfile), memory_tmpfile_arginfo) 1792 | PHP_NAMED_FE(mail, PHP_FN(userspace_mail), userspace_mail_arginfo) 1793 | PHP_NAMED_FE(gethostname, 1794 | PHP_FN(userspace_gethostname), 1795 | userspace_gethostname_arginfo) 1796 | PHP_NAMED_FE(move_uploaded_file, 1797 | PHP_FN(userspace_move_uploaded_file), 1798 | userspace_move_uploaded_file_arginfo) 1799 | PHP_FE_END 1800 | }; 1801 | 1802 | // Define replacement functions for cross stream wrapper type renaming 1803 | static const zend_function_entry rename_replacement_functions[] = { 1804 | PHP_NAMED_FE(rename, 1805 | PHP_FN(cross_stream_wrapper_rename), 1806 | cross_stream_wrapper_rename_arginfo) 1807 | PHP_FE_END 1808 | }; 1809 | 1810 | static const zend_function_entry glob_replacement_functions[] = { 1811 | PHP_NAMED_FE(glob, PHP_FN(glob), glob_arginfo) 1812 | PHP_FE_END 1813 | }; 1814 | 1815 | static const zend_function_entry tempnam_replacement_functions[] = { 1816 | PHP_NAMED_FE(tempnam, PHP_FN(userspace_tempnam), userspace_tempnam_arginfo) 1817 | PHP_NAMED_FE(sys_get_temp_dir, 1818 | PHP_FN(userspace_sys_get_temp_dir), 1819 | userspace_sys_get_temp_dir_arginfo) 1820 | PHP_FE_END 1821 | }; 1822 | 1823 | static const zend_function_entry clearstatcache_replacement_functions[] = { 1824 | PHP_NAMED_FE(clearstatcache, 1825 | PHP_FN(gcs_clearstatcache), 1826 | gcs_clearstatcache_arginfo) 1827 | PHP_FE_END 1828 | }; 1829 | 1830 | static const zend_function_entry syslog_replacement_functions[] = { 1831 | PHP_NAMED_FE(syslog, PHP_FN(userspace_syslog), userspace_syslog_arginfo) 1832 | PHP_FE_END 1833 | }; 1834 | 1835 | static void zend_replace_functions( 1836 | const zend_function_entry* functions TSRMLS_DC) { 1837 | const zend_function_entry* fe_ptr = functions; 1838 | while (fe_ptr->fname) { 1839 | // Remove the old entry 1840 | zend_hash_del(CG(function_table), fe_ptr->fname, strlen(fe_ptr->fname) + 1); 1841 | ++fe_ptr; 1842 | } 1843 | 1844 | zend_register_functions(NULL, 1845 | functions, 1846 | CG(function_table), 1847 | MODULE_PERSISTENT TSRMLS_CC); 1848 | } 1849 | 1850 | static void replace_builtin_functions(INIT_FUNC_ARGS) { 1851 | zend_replace_functions(runtime_builtin_replacement_functions TSRMLS_CC); 1852 | 1853 | if (FLAGS_php_enable_cross_stream_wrapper_rename) { 1854 | zend_replace_functions(rename_replacement_functions TSRMLS_CC); 1855 | } 1856 | 1857 | if (FLAGS_php_enable_glob_replacement) { 1858 | zend_replace_functions(glob_replacement_functions TSRMLS_CC); 1859 | } 1860 | 1861 | if (FLAGS_php_enable_tempnam) { 1862 | zend_replace_functions(tempnam_replacement_functions TSRMLS_CC); 1863 | } 1864 | 1865 | if (FLAGS_php_enable_gcs_stat_cache) { 1866 | zend_replace_functions(clearstatcache_replacement_functions TSRMLS_CC); 1867 | } 1868 | 1869 | if (FLAGS_php_enable_syslog_replacement) { 1870 | zend_replace_functions(syslog_replacement_functions TSRMLS_CC); 1871 | } 1872 | } 1873 | 1874 | // Module dependancies are used to ensure that the runtime module is initialized 1875 | // last, so that we can strip out functions, streams and transports 1876 | static const zend_module_dep gae_runtime_module_dep[] = { 1877 | ZEND_MOD_OPTIONAL("standard") 1878 | ZEND_MOD_OPTIONAL("session") 1879 | ZEND_MOD_OPTIONAL("openssl") 1880 | ZEND_MOD_OPTIONAL("curl") 1881 | ZEND_MOD_END 1882 | }; 1883 | 1884 | zend_module_entry gae_runtime_module_entry = { 1885 | STANDARD_MODULE_HEADER_EX, 1886 | NULL, 1887 | gae_runtime_module_dep, 1888 | const_cast("GAE Runtime Module"), 1889 | gae_runtime_module_functions, 1890 | PHP_MINIT(gae_runtime_module), 1891 | PHP_MSHUTDOWN(gae_runtime_module), 1892 | PHP_RINIT(gae_runtime_module), 1893 | PHP_RSHUTDOWN(gae_runtime_module), 1894 | PHP_MINFO(gae_runtime_module), 1895 | NO_VERSION_YET, 1896 | PHP_MODULE_GLOBALS(gae_runtime_module), 1897 | NULL, 1898 | NULL, 1899 | NULL, 1900 | STANDARD_MODULE_PROPERTIES_EX 1901 | }; 1902 | 1903 | #ifdef COMPILE_DL_GAE_RUNTIME_MODULE 1904 | ZEND_GET_MODULE(gae_runtime) 1905 | #endif 1906 | 1907 | zend_module_entry fake_memcache_module_entry = { 1908 | STANDARD_MODULE_HEADER_EX, 1909 | NULL, 1910 | NULL, // dependancies 1911 | const_cast("memcache"), 1912 | NULL, // functions 1913 | NULL, // MINIT 1914 | NULL, // MSHUTDOWN 1915 | NULL, // RINIT 1916 | NULL, // RSHUTDOWN 1917 | NULL, // MINFO 1918 | NO_VERSION_YET, 1919 | STANDARD_MODULE_PROPERTIES 1920 | }; 1921 | 1922 | zend_module_entry fake_memcached_module_entry = { 1923 | STANDARD_MODULE_HEADER_EX, 1924 | NULL, 1925 | NULL, // dependancies 1926 | const_cast("memcached"), 1927 | NULL, // functions 1928 | NULL, // MINIT 1929 | NULL, // MSHUTDOWN 1930 | NULL, // RINIT 1931 | NULL, // RSHUTDOWN 1932 | NULL, // MINFO 1933 | NO_VERSION_YET, 1934 | STANDARD_MODULE_PROPERTIES 1935 | }; 1936 | 1937 | zend_module_entry fake_curl_module_entry = { 1938 | STANDARD_MODULE_HEADER_EX, 1939 | NULL, 1940 | NULL, // dependancies 1941 | const_cast("curl"), 1942 | NULL, // functions 1943 | NULL, // MINIT 1944 | NULL, // MSHUTDOWN 1945 | NULL, // RINIT 1946 | NULL, // RSHUTDOWN 1947 | NULL, // MINFO 1948 | NO_VERSION_YET, 1949 | STANDARD_MODULE_PROPERTIES 1950 | }; 1951 | -------------------------------------------------------------------------------- /gae_runtime_module.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | 18 | #ifndef GAE_RUNTIME_MODULE_H_ 19 | #define GAE_RUNTIME_MODULE_H_ 20 | 21 | extern "C" { 22 | #ifdef __google_internal__ 23 | #include "php/main/php.h" 24 | #else 25 | #include "main/php.h" 26 | #endif 27 | } 28 | 29 | extern zend_module_entry gae_runtime_module_entry; 30 | 31 | // For each PECL extension that we implement in user PHP, we need to make sure 32 | // that the function call 'extension_loaded('extension_name')' still returns 33 | // true. To do that, we need to prepare fake zend_module_entry structures and 34 | // add them to the startup routines. 35 | 36 | extern zend_module_entry fake_memcache_module_entry; 37 | extern zend_module_entry fake_memcached_module_entry; 38 | extern zend_module_entry fake_curl_module_entry; 39 | 40 | // Module globals structure 41 | ZEND_BEGIN_MODULE_GLOBALS(gae_runtime_module) 42 | zval* recorded_errors_array; 43 | int disable_readonly_filesystem; 44 | int enable_curl_lite; 45 | bool enable_mail_replacement; 46 | int enable_gcs_stat_cache; 47 | const char* redirct_paths; 48 | bool vfs_initialized; 49 | ZEND_END_MODULE_GLOBALS(gae_runtime_module) 50 | 51 | namespace appengine { 52 | // Check if a path should be redirected, will return true if the path should 53 | // be redirected and will return the path for redirection in the new_path 54 | // variable. 55 | bool is_redirect_path(const char* path, char** new_path TSRMLS_DC); 56 | 57 | // Retrieve the stream wrapper for the supplied path, taking into account that 58 | // the path might be prepended by "redirect://" and need to be stripped. 59 | php_stream_wrapper* get_correct_stream_wrapper(const char* path, 60 | char** path_for_open, 61 | int options TSRMLS_DC); 62 | } // namespace appengine 63 | 64 | // The following method is defined with weak linkage in the PHP source so we can 65 | // define it in our extension and have it called only when our extension is 66 | // loaded. 67 | extern "C" 68 | int redirect_path_lookup(const char* path, char** new_path TSRMLS_DC); 69 | 70 | #endif // GAE_RUNTIME_MODULE_H_ 71 | -------------------------------------------------------------------------------- /gae_runtime_module_stub.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | 18 | #include "gae_runtime_module_stub.h" 19 | 20 | DEFINE_bool(enable_socket_api, true, ""); 21 | -------------------------------------------------------------------------------- /gae_runtime_module_stub.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | #ifndef GAE_RUNTIME_MODULE_STUB_H_ 17 | #define GAE_RUNTIME_MODULE_STUB_H_ 18 | 19 | #include // NOLINT(readability/streams) 20 | #include 21 | 22 | using std::string; 23 | 24 | namespace base { 25 | enum LinkerInitialized { LINKER_INITIALIZED }; 26 | } 27 | 28 | // Dummy Mutex class 29 | class Mutex { 30 | public: 31 | Mutex() {} 32 | Mutex(base::LinkerInitialized x) {} // NOLINT(runtime/explicit) 33 | ~Mutex() {} 34 | 35 | void Lock() {} 36 | void UnLock() {} 37 | }; 38 | 39 | class MutexLock { 40 | public: 41 | MutexLock(Mutex *mutex) {} // NOLINT 42 | }; 43 | 44 | #define DEFINE_string(name, val, txt) \ 45 | namespace fLS { \ 46 | std::string FLAGS_##name = val; \ 47 | } \ 48 | using fLS::FLAGS_##name; 49 | 50 | #define DEFINE_bool(name, val, txt) \ 51 | namespace fLB { \ 52 | bool FLAGS_##name = val; \ 53 | } \ 54 | using fLB::FLAGS_##name; 55 | 56 | #define DECLARE_bool(name) \ 57 | namespace fLB { \ 58 | extern bool FLAGS_##name; \ 59 | } \ 60 | using fLB::FLAGS_##name; 61 | 62 | #define CHECK(var) assert(var) 63 | 64 | #define VLOG(level) std::cerr 65 | 66 | #endif // GAE_RUNTIME_MODULE_STUB_H_ 67 | -------------------------------------------------------------------------------- /php_api_rpc.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #ifndef PHP_API_RPC_H_ 18 | #define PHP_API_RPC_H_ 19 | 20 | #include 21 | 22 | typedef long long int64; 23 | 24 | using std::string; 25 | 26 | namespace appengine { 27 | 28 | class RuntimeModule; 29 | 30 | class PhpApiRpc { 31 | public: 32 | enum Error { 33 | OK = 0, 34 | RPC_FAILED, 35 | CALL_NOT_FOUND, 36 | ARGUMENT_ERROR, 37 | DEADLINE_EXCEEDED, 38 | CANCELLED, 39 | APPLICATION_ERROR, 40 | OTHER_ERROR, 41 | OVER_QUOTA, 42 | REQUEST_TOO_LARGE, 43 | CAPABILITY_DISABLED, 44 | FEATURE_DISABLED, 45 | RESPONSE_TOO_LARGE 46 | }; 47 | 48 | PhpApiRpc() 49 | : error_(OK), 50 | cpu_usage_(0) { 51 | } 52 | 53 | Error error() const { 54 | return error_; 55 | } 56 | void set_error(Error error) { 57 | error_ = error; 58 | } 59 | 60 | const string& error_detail() const { 61 | return error_detail_; 62 | } 63 | void set_error_detail(const string& error_detail) { 64 | error_detail_ = error_detail; 65 | } 66 | 67 | int app_error() const { 68 | return app_error_; 69 | } 70 | void set_app_error(int app_error) { 71 | app_error_ = app_error; 72 | } 73 | 74 | int64 cpu_usage() const { 75 | return cpu_usage_; 76 | } 77 | void set_cpu_usage(int64 cpu_usage) { 78 | cpu_usage_ = cpu_usage; 79 | } 80 | 81 | const string& response_pb() const { 82 | return response_pb_; 83 | } 84 | void set_response_pb(const string& response_pb) { 85 | response_pb_ = response_pb; 86 | } 87 | 88 | private: 89 | string request_id_; 90 | string response_pb_; 91 | 92 | Error error_; 93 | string error_detail_; 94 | int app_error_; 95 | 96 | int64 cpu_usage_; 97 | }; 98 | 99 | } // namespace appengine 100 | 101 | #endif // PHP_API_RPC_H_ 102 | -------------------------------------------------------------------------------- /php_constants.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | 18 | #ifndef PHP_CONSTANTS_H_ 19 | #define PHP_CONSTANTS_H_ 20 | 21 | namespace appengine { 22 | // RPC values taken from APIRpc - used here so we don't need to pull in APIRpc 23 | // when testing using gmock. 24 | enum RPC_ERROR_CODES { 25 | RPC_OK = 0, 26 | RPC_FAILED, 27 | CALL_NOT_FOUND, 28 | ARGUMENT_ERROR, 29 | DEADLINE_EXCEEDED, 30 | CANCELLED, 31 | APPLICATION_ERROR, 32 | OTHER_ERROR, 33 | OVER_QUOTA, 34 | REQUEST_TOO_LARGE, 35 | CAPABILITY_DISABLED, 36 | FEATURE_DISABLED, 37 | RESPONSE_TOO_LARGE 38 | }; 39 | 40 | // The name of the php make_call hook 41 | static const char kMakeApiCallName[] = "make_call"; 42 | 43 | // The values returned from RPC calls in the hash table. 44 | static const char kErrorCodeName[] = "error"; 45 | static const char kApplicationErrorCodeName[] = "application_error"; 46 | static const char kApplicationErrorDetailName[] = "error_detail"; 47 | static const char kResultStringName[] = "result_string"; 48 | static const char kCpuUsageName[] = "cpu_usage_mcycles"; 49 | 50 | // Taken from //webutil/http/httpresponse.h 51 | enum ResponseCode { 52 | RC_REQUEST_OK = 200, 53 | RC_NOT_FOUND = 404, // Not found 54 | RC_ERROR = 500, // Internal server error 55 | }; 56 | 57 | } // namespace appengine 58 | 59 | #endif // PHP_CONSTANTS_H_ 60 | -------------------------------------------------------------------------------- /php_features_stub.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | // 18 | // This file contains hard-coded values for the feature-guarding flags for 19 | // the extension bundled with the SDK. 20 | 21 | namespace fLB { 22 | 23 | bool FLAGS_php_enable_direct_uploads = true; 24 | bool FLAGS_php_enable_additional_cloud_storage_headers = true; 25 | bool FLAGS_php_enable_tempnam = true; 26 | bool FLAGS_php_enable_cross_stream_wrapper_rename = true; 27 | bool FLAGS_php_enable_glob_replacement = true; 28 | bool FLAGS_php_enforce_filesystem_readonly = true; 29 | bool FLAGS_php_enable_mail_replacement = true; 30 | bool FLAGS_php_remove_glob_stream_wrapper = true; 31 | bool FLAGS_php_enable_gcs_stat_cache = true; 32 | bool FLAGS_php_enable_php_output_stream = true; 33 | bool FLAGS_php_unregister_unix_xport = false; 34 | bool FLAGS_php_enable_syslog_replacement = true; 35 | bool FLAGS_php_allow_file_redirect = false; 36 | bool FLAGS_php_enable_gcs_default_keyword = true; 37 | 38 | } // namespace fLB 39 | -------------------------------------------------------------------------------- /php_readonly_filesystem_wrapper.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #include "php_readonly_filesystem_wrapper.h" 18 | 19 | namespace appengine { 20 | 21 | // Keep a track of the original operations, in case we need them. Right now we 22 | // only re-use stream_opener 23 | static php_stream_wrapper_ops original_ops; 24 | 25 | static void show_php_error(const char* function_name TSRMLS_DC) { 26 | // E_WARNING as we don't want to terminate script execution. 27 | php_error_docref(NULL TSRMLS_CC, 28 | E_WARNING, 29 | "The local filesystem is readonly, %s failed", 30 | function_name); 31 | } 32 | 33 | static int disabled_rename(php_stream_wrapper* wrapper, 34 | char* url_from, 35 | char* url_to, 36 | int options, 37 | php_stream_context *context TSRMLS_DC) { 38 | // Rename never sets options to a non zero value, so we will always print this 39 | // warning. 40 | show_php_error("rename" TSRMLS_CC); 41 | errno = EROFS; 42 | return 0; 43 | } 44 | 45 | static int disabled_unlink(php_stream_wrapper* wrapper, 46 | char* url, 47 | int options, 48 | php_stream_context* context TSRMLS_DC) { 49 | if (options & REPORT_ERRORS) { 50 | show_php_error("unlink" TSRMLS_CC); 51 | } 52 | errno = EROFS; 53 | return 0; 54 | } 55 | 56 | static int disabled_mkdir(php_stream_wrapper* wrapper, 57 | char* url, 58 | int mode, 59 | int options, 60 | php_stream_context* context TSRMLS_DC) { 61 | if (options & REPORT_ERRORS) { 62 | show_php_error("mkdir" TSRMLS_CC); 63 | } 64 | errno = EROFS; 65 | return 0; 66 | } 67 | 68 | static php_stream* disabled_write_stream_opener(php_stream_wrapper* wrapper, 69 | char* path, char* mode, int options, char** opened_path, 70 | php_stream_context* context STREAMS_DC TSRMLS_DC) { 71 | 72 | if (mode[0] != 'r' || strchr(mode, '+') != NULL) { 73 | php_error_docref1(NULL TSRMLS_CC, 74 | path, 75 | E_WARNING, 76 | "The local filesystem is readonly, open failed"); 77 | errno = EROFS; 78 | return NULL; 79 | } 80 | 81 | // Defer to the original implementation. 82 | return original_ops.stream_opener(wrapper, 83 | path, 84 | mode, 85 | options, 86 | opened_path, 87 | context STREAMS_CC TSRMLS_CC); 88 | } 89 | 90 | static int disabled_rmdir(php_stream_wrapper* wrapper, 91 | char* url, 92 | int options, 93 | php_stream_context* context TSRMLS_DC) { 94 | if (options & REPORT_ERRORS) { 95 | show_php_error("rmdir" TSRMLS_CC); 96 | } 97 | errno = EROFS; 98 | return 0; 99 | } 100 | 101 | void hook_readonly_filesystem_wrapper(php_stream_wrapper* wrapper TSRMLS_DC) { 102 | php_stream_wrapper_ops* ops = wrapper->wops; 103 | // Only if we have not already hooked this. 104 | if (ops->stream_opener != disabled_write_stream_opener) { 105 | // Keep a copy of what was once there. 106 | ::memcpy(&original_ops, ops, sizeof(original_ops)); 107 | 108 | // Replace the functions we are flat out disabling. 109 | ops->stream_opener = disabled_write_stream_opener; 110 | ops->unlink = disabled_unlink; 111 | ops->rename = disabled_rename; 112 | ops->stream_mkdir = disabled_mkdir; 113 | ops->stream_rmdir = disabled_rmdir; 114 | } 115 | } 116 | 117 | } // namespace appengine 118 | -------------------------------------------------------------------------------- /php_readonly_filesystem_wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #ifndef PHP_READONLY_FILESYSTEM_WRAPPER_H_ 18 | #define PHP_READONLY_FILESYSTEM_WRAPPER_H_ 19 | 20 | extern "C" { 21 | #ifdef __google_internal__ 22 | #include "php/main/php.h" 23 | #include "php/main/php_streams.h" 24 | #else 25 | #include "main/php.h" 26 | #include "main/php_streams.h" 27 | #endif 28 | } // extern "C" 29 | 30 | namespace appengine { 31 | 32 | void hook_readonly_filesystem_wrapper(php_stream_wrapper* wrapper TSRMLS_DC); 33 | 34 | } // namespace appengine 35 | 36 | #endif // PHP_READONLY_FILESYSTEM_WRAPPER_H_ 37 | 38 | -------------------------------------------------------------------------------- /php_redirect_filesystem_wrapper.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #include "php_redirect_filesystem_wrapper.h" 18 | 19 | #include 20 | 21 | #include "gae_runtime_module.h" 22 | 23 | namespace appengine { 24 | 25 | static php_stream* redirect_stream_opener(php_stream_wrapper* orig_wrapper, 26 | char* filename, char* mode, int options, char** opened_path, 27 | php_stream_context* context STREAMS_DC TSRMLS_DC) { 28 | 29 | char* actual_path; 30 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper(filename, 31 | &actual_path, options TSRMLS_CC); 32 | if (wrapper) { 33 | return wrapper->wops->stream_opener(wrapper, actual_path, mode, options, 34 | opened_path, context STREAMS_CC TSRMLS_CC); 35 | } 36 | return NULL; 37 | } 38 | 39 | static int redirect_url_stat(php_stream_wrapper* orig_wrapper, char* url, 40 | int flags, php_stream_statbuf* ssb, php_stream_context* context TSRMLS_DC) { 41 | char* actual_path = NULL; 42 | // Clear the flag IGNORE_URL when looking up the correct wrapper, specifically 43 | // for the case when doing a file_exists() check. PHP will set the flag 44 | // PHP_STREAM_URL_STAT_QUIET which just happens to have the same value as 45 | // IGNORE_URL which causes php_stream_locate_url_wrapper to return 46 | // plain_files_wrapper. 47 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper(url, 48 | &actual_path, flags & ~IGNORE_URL TSRMLS_CC); 49 | if (wrapper) { 50 | return wrapper->wops->url_stat(wrapper, actual_path, flags, ssb, 51 | context TSRMLS_CC); 52 | } 53 | return 0; 54 | } 55 | 56 | static php_stream* redirect_dir_opener(php_stream_wrapper* orig_wrapper, 57 | char* filename, char* mode, int options, char** opened_path, 58 | php_stream_context* context STREAMS_DC TSRMLS_DC) { 59 | char* actual_path = NULL; 60 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper(filename, 61 | &actual_path, options TSRMLS_CC); 62 | if (wrapper) { 63 | return wrapper->wops->dir_opener(wrapper, actual_path, mode, options, 64 | opened_path, context STREAMS_CC TSRMLS_CC); 65 | } 66 | return NULL; 67 | } 68 | 69 | static int redirect_unlink(php_stream_wrapper* orig_wrapper, char* url, 70 | int options, php_stream_context* context TSRMLS_DC) { 71 | char* new_path; 72 | if (is_redirect_path(url, &new_path TSRMLS_CC)) { 73 | char* actual_path; 74 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper( 75 | new_path, &actual_path, options TSRMLS_CC); 76 | if (wrapper) { 77 | return wrapper->wops->unlink(wrapper, actual_path, options, 78 | context TSRMLS_CC); 79 | } 80 | } 81 | return 0; 82 | } 83 | 84 | static int redirect_stream_mkdir(php_stream_wrapper* orig_wrapper, char* url, 85 | int mode, int options, php_stream_context* context TSRMLS_DC) { 86 | char* new_path = NULL; 87 | if (is_redirect_path(url, &new_path TSRMLS_CC)) { 88 | char* actual_path = NULL; 89 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper( 90 | new_path, &actual_path, options TSRMLS_CC); 91 | if (wrapper) { 92 | return wrapper->wops->stream_mkdir(wrapper, actual_path, mode, options, 93 | context TSRMLS_CC); 94 | } 95 | } 96 | return 0; 97 | } 98 | 99 | static int redirect_stream_rmdir(php_stream_wrapper* orig_wrapper, char* url, 100 | int options, php_stream_context* context TSRMLS_DC) { 101 | char* new_path = NULL; 102 | if (is_redirect_path(url, &new_path TSRMLS_CC)) { 103 | char* actual_path = NULL; 104 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper( 105 | new_path, &actual_path, options TSRMLS_CC); 106 | if (wrapper) { 107 | return wrapper->wops->stream_rmdir(wrapper, actual_path, options, 108 | context TSRMLS_CC); 109 | } 110 | } 111 | return 0; 112 | } 113 | 114 | static int redirect_stream_metadata(php_stream_wrapper* orig_wrapper, char* url, 115 | int options, void* value, php_stream_context* context TSRMLS_DC) { 116 | char* redirect_path; 117 | if (is_redirect_path(url, &redirect_path TSRMLS_CC)) { 118 | char* actual_path; 119 | php_stream_wrapper* wrapper = appengine::get_correct_stream_wrapper( 120 | redirect_path, &actual_path, options TSRMLS_CC); 121 | if (wrapper) { 122 | return wrapper->wops->stream_metadata(wrapper, actual_path, options, 123 | value, context TSRMLS_CC); 124 | } 125 | } 126 | return 0; 127 | } 128 | 129 | static int redirect_stream_rename(php_stream_wrapper* wrapper, char* url_from, 130 | char* url_to, int options, php_stream_context *context TSRMLS_DC) { 131 | // Supported in the cross wrapper rename function. 132 | return 0; 133 | } 134 | 135 | static php_stream_wrapper_ops redirect_stdio_wops = { 136 | redirect_stream_opener, 137 | NULL, 138 | NULL, 139 | redirect_url_stat, 140 | redirect_dir_opener, 141 | "redirect", 142 | redirect_unlink, 143 | redirect_stream_rename, 144 | redirect_stream_mkdir, 145 | redirect_stream_rmdir, 146 | redirect_stream_metadata, 147 | }; 148 | 149 | php_stream_wrapper php_redirect_stream_wrapper = { 150 | &redirect_stdio_wops, 151 | NULL, 152 | 1, // is_url 153 | }; 154 | 155 | } // namespace appengine 156 | -------------------------------------------------------------------------------- /php_redirect_filesystem_wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #ifndef PHP_REDIRECT_FILESYSTEM_WRAPPER_H_ 18 | #define PHP_REDIRECT_FILESYSTEM_WRAPPER_H_ 19 | 20 | extern "C" { 21 | #ifdef __google_internal__ 22 | #include "php/main/php.h" 23 | #include "php/main/php_streams.h" 24 | #else 25 | #include "main/php.h" 26 | #include "main/php_streams.h" 27 | #endif 28 | } // extern "C" 29 | 30 | namespace appengine { 31 | 32 | extern php_stream_wrapper php_redirect_stream_wrapper; 33 | 34 | } // namespace appengine 35 | 36 | #endif // PHP_REDIRECT_FILESYSTEM_WRAPPER_H_ 37 | -------------------------------------------------------------------------------- /php_rpc_utils_stub.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | 18 | #include 19 | 20 | #include "php_rpc_utils_stub.h" 21 | #include "php_runtime_utils_stub.h" 22 | 23 | #include "remote_api.pb.h" 24 | 25 | extern "C" { 26 | #include "main/php.h" 27 | #include "ext/standard/php_fopen_wrappers.h" 28 | } 29 | 30 | using apphosting::ext::remote_api::Request; 31 | using apphosting::ext::remote_api::Response; 32 | 33 | namespace appengine { 34 | 35 | void PhpRpcUtils::MakeApiCall(PhpApiRpc* rpc, 36 | const string& package_name, 37 | const string& call_name, 38 | const string& request_pb, 39 | double deadline_sec TSRMLS_DC) { 40 | const char* api_host = getenv("REMOTE_API_HOST"); 41 | if (!api_host) { 42 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 43 | "Missing required REMOTE_API_HOST enviornmental variable."); 44 | return; 45 | } 46 | 47 | int api_port = 0; 48 | const char* api_port_env = getenv("REMOTE_API_PORT"); 49 | if (!api_port_env) { 50 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 51 | "Missing required REMOTE_API_PORT enviornmental variable."); 52 | return; 53 | } else { 54 | api_port = static_cast( 55 | strtol(api_port_env, NULL, 10)); // NOLINT(runtime/deprecated_fn) 56 | } 57 | 58 | if (api_port <= 0 || api_port > USHRT_MAX) { 59 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 60 | "Invalid REMOTE_API_PORT value %s", api_port_env); 61 | } 62 | 63 | const char* request_id = getenv("REMOTE_REQUEST_ID"); 64 | if (!request_id) { 65 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 66 | "Missing required REMOTE_REQUEST_ID enviornmental variable."); 67 | return; 68 | } 69 | 70 | php_stream_wrapper* wrapper = &php_stream_http_wrapper; 71 | php_stream_wrapper_ops* wops = wrapper->wops; 72 | 73 | // Wrap RPC in a Remote API Request proto. 74 | Request* remote_request = new Request(); 75 | remote_request->set_service_name(package_name); 76 | remote_request->set_method(call_name); 77 | remote_request->set_request(request_pb.c_str(), request_pb.length()); 78 | remote_request->set_request_id(request_id); 79 | string content = remote_request->SerializeAsString(); 80 | int content_len = remote_request->ByteSize(); 81 | 82 | // Setup stream context. 83 | php_stream_context* context = php_stream_context_alloc(TSRMLS_C); 84 | 85 | zval method_val; 86 | ZVAL_STRING(&method_val, "POST", 0); 87 | php_stream_context_set_option(context, wops->label, "method", &method_val); 88 | 89 | string header = StringPrintf("Content-type: application/octet-stream\r\n" 90 | "Content-length: %d\r\n", content_len); 91 | zval header_val; 92 | ZVAL_STRING(&header_val, header.c_str(), 0); 93 | php_stream_context_set_option(context, wops->label, "header", &header_val); 94 | 95 | 96 | zval content_val; 97 | ZVAL_STRINGL(&content_val, content.c_str(), content_len, 0); 98 | php_stream_context_set_option(context, wops->label, "content", &content_val); 99 | 100 | string url = StringPrintf("http://%s:%d/", api_host, api_port); 101 | char* opened_path; 102 | php_stream* stream = wops->stream_opener( 103 | wrapper, const_cast(url.c_str()), 104 | const_cast("rb"), REPORT_ERRORS, &opened_path, context 105 | STREAMS_CC TSRMLS_CC); 106 | if (!stream) { 107 | rpc->set_error(PhpApiRpc::RPC_FAILED); 108 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 109 | "Invalid response from API server."); 110 | php_stream_close(stream); 111 | return; 112 | } 113 | 114 | char* buffer; 115 | int len = php_stream_copy_to_mem(stream, &buffer, PHP_STREAM_COPY_ALL, 0); 116 | if (len < 0) { 117 | rpc->set_error(PhpApiRpc::RPC_FAILED); 118 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 119 | "Cannot read API server's response."); 120 | php_stream_close(stream); 121 | return; 122 | } 123 | 124 | Response *remote_response = new Response(); 125 | remote_response->ParseFromArray(buffer, len); 126 | efree(buffer); 127 | 128 | if (remote_response->has_application_error()) { 129 | rpc->set_error(PhpApiRpc::APPLICATION_ERROR); 130 | rpc->set_app_error(remote_response->application_error().code()); 131 | rpc->set_error_detail(remote_response->application_error().detail()); 132 | } else if (remote_response->has_exception() || 133 | remote_response->has_java_exception()) { 134 | rpc->set_error(PhpApiRpc::RPC_FAILED); 135 | php_error_docref(NULL TSRMLS_CC, E_ERROR, 136 | "Remote implementation for %s.%s failed", 137 | package_name.c_str(), call_name.c_str()); 138 | } else { 139 | rpc->set_error(PhpApiRpc::OK); 140 | rpc->set_response_pb(remote_response->response()); 141 | } 142 | 143 | php_stream_close(stream); 144 | } 145 | 146 | } // namespace appengine 147 | -------------------------------------------------------------------------------- /php_rpc_utils_stub.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | #ifndef PHP_RPC_UTILS_STUB_H_ 18 | #define PHP_RPC_UTILS_STUB_H_ 19 | 20 | #include 21 | 22 | #include "php_api_rpc.h" 23 | 24 | extern "C" { 25 | #ifdef __google_internal__ 26 | #include "php/main/php.h" 27 | #else 28 | #include "main/php.h" 29 | #endif 30 | } 31 | 32 | using std::string; 33 | 34 | 35 | namespace appengine { 36 | 37 | class PhpApiRpc; 38 | 39 | class PhpRpcUtils { 40 | public: 41 | static void MakeApiCall(PhpApiRpc* rpc, 42 | const string& package_name, 43 | const string& call_name, 44 | const string& request, 45 | double deadline_sec TSRMLS_DC); 46 | }; 47 | 48 | } // namespace appengine 49 | 50 | #endif // PHP_RPC_UTILS_STUB_H_ 51 | -------------------------------------------------------------------------------- /php_runtime_sapi_stub.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | 18 | #include "php_runtime_sapi_stub.h" 19 | 20 | #include 21 | 22 | #include "gae_runtime_module_stub.h" 23 | 24 | namespace appengine { 25 | 26 | const char* const PhpRuntimeSapi::GetApplicationBasedir() { 27 | return getenv("APPLICATION_ROOT"); 28 | } 29 | 30 | } // namespace appengine 31 | -------------------------------------------------------------------------------- /php_runtime_sapi_stub.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | #ifndef PHP_RUNTIME_SAPI_STUB_H_ 17 | #define PHP_RUNTIME_SAPI_STUB_H_ 18 | 19 | namespace appengine { 20 | 21 | class PhpRuntimeSapi { 22 | public: 23 | static const char* const GetApplicationBasedir(); 24 | }; 25 | 26 | } // namespace appengine 27 | 28 | #endif // PHP_RUNTIME_SAPI_STUB_H_ 29 | -------------------------------------------------------------------------------- /php_runtime_utils_stub.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: marslan@google.com (Mars Lan) 17 | 18 | #include "php_runtime_utils_stub.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | using std::string; 31 | using std::stringstream; 32 | 33 | 34 | #ifdef PHP_WIN32 35 | #define strcasecmp _stricmp 36 | #endif 37 | 38 | namespace appengine { 39 | 40 | void SplitString(const string& full, 41 | const char* delim, 42 | vector* result) { 43 | size_t pos = 0; 44 | size_t found = string::npos; 45 | int delim_len = strlen(delim); 46 | vector temp_vector; 47 | 48 | while ((found = full.find(delim, pos)) != string::npos) { 49 | const string ref = full.substr(pos, (found - pos)); 50 | temp_vector.push_back(ref); 51 | pos = found + delim_len; 52 | } 53 | 54 | temp_vector.push_back(full.substr(pos)); 55 | result->swap(temp_vector); 56 | } 57 | 58 | vector SplitStringWithMaxSplit(const string& full, 59 | const char* delim, 60 | unsigned int max_split) { 61 | vector result; 62 | appengine::SplitString(full, delim, &result); 63 | if (result.size() > max_split + 1) { 64 | vector trailing(result.begin() + max_split, result.end()); 65 | result[max_split] = appengine::JoinString(trailing, delim); 66 | result.resize(max_split + 1); 67 | } 68 | return result; 69 | } 70 | 71 | string JoinString(const vector& parts, const char* delim) { 72 | string merged; 73 | for (int i = 0; i < parts.size(); ++i) { 74 | if (i != 0) { 75 | merged.append(delim); 76 | } 77 | merged.append(parts[i]); 78 | } 79 | return merged; 80 | } 81 | 82 | void TrimWhitespaceASCII(const string& input, string* output) { 83 | const char* space_chars = " \t\n\v\f\r"; 84 | size_t left = input.find_first_not_of(space_chars); 85 | size_t right = input.find_last_not_of(space_chars); 86 | 87 | if (left == string::npos) { 88 | left = 0; 89 | } 90 | 91 | if (right == string::npos) { 92 | right = input.size(); 93 | } 94 | 95 | output->assign(input.substr(left, right - left + 1)); 96 | } 97 | 98 | bool StringCaseEqual(const string& str1, const string& str2) { 99 | return strcasecmp(str1.c_str(), str2.c_str()) == 0; 100 | } 101 | 102 | string StringPrintf(const char* format, ...) { 103 | int buf_size = 100; 104 | int len; 105 | char* buffer = new char[buf_size]; 106 | std::string str; 107 | va_list args; 108 | 109 | va_start(args, format); 110 | len = vsnprintf(buffer, buf_size, format, args); 111 | va_end(args); 112 | if (len < buf_size) { 113 | // Everything fits into the initial buffer. 114 | str.assign(buffer, len); 115 | } else if (len >= buf_size) { 116 | // Not enough space. Enlarge buffer and try again. 117 | buf_size = len + 1; 118 | delete[] buffer; 119 | buffer = new char[buf_size]; 120 | va_start(args, format); 121 | len = vsnprintf(buffer, buf_size, format, args); 122 | va_end(args); 123 | if (len > 0 && len < buf_size) { 124 | str.assign(buffer, len); 125 | } 126 | } 127 | 128 | delete[] buffer; 129 | 130 | // Will return empty string if there's any encoding error. 131 | return str; 132 | } 133 | 134 | string StrCat(const string &a, const string &b) { 135 | return a + b; 136 | } 137 | 138 | string StrCat(const string &a, int b) { 139 | return a + StringPrintf("%d", b); 140 | } 141 | 142 | string StrCat(const string &a, const string &b, const string &c) { 143 | return a + b + c; 144 | } 145 | 146 | static const char encodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop" 147 | "qrstuvwxyz0123456789+/"; 148 | static const char padCharacter = '='; 149 | 150 | void EncodeBase64(const string& input, string* output) { 151 | output->clear(); 152 | output->reserve(((input.size()/3) + (input.size() % 3 > 0)) * 4); 153 | unsigned long temp; 154 | string::const_iterator iter = input.begin(); 155 | size_t triplet_count = input.length() / 3; 156 | for (size_t i = 0; i < triplet_count; ++i) { 157 | temp = (*iter++) << 16 | (*iter++) << 8 | (*iter++); 158 | output->append(1, encodeLookup[(temp & 0x00FC0000) >> 18]); 159 | output->append(1, encodeLookup[(temp & 0x0003F000) >> 12]); 160 | output->append(1, encodeLookup[(temp & 0x00000FC0) >> 6]); 161 | output->append(1, encodeLookup[(temp & 0x0000003F)]); 162 | } 163 | if (input.size() % 3 == 1) { 164 | temp = (*iter++) << 16; 165 | output->append(1, encodeLookup[(temp & 0x00FC0000) >> 18]); 166 | output->append(1, encodeLookup[(temp & 0x0003F000) >> 12]); 167 | output->append(2, padCharacter); 168 | } else if (input.size() % 3 == 2) { 169 | temp = (*iter++) << 16 | (*iter++) << 8; 170 | output->append(1, encodeLookup[(temp & 0x00FC0000) >> 18]); 171 | output->append(1, encodeLookup[(temp & 0x0003F000) >> 12]); 172 | output->append(1, encodeLookup[(temp & 0x00000FC0) >> 6]); 173 | output->append(1, padCharacter); 174 | } 175 | } 176 | 177 | } // namespace appengine 178 | -------------------------------------------------------------------------------- /php_runtime_utils_stub.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | #ifndef PHP_RUNTIME_UTILS_STUB_H_ 17 | #define PHP_RUNTIME_UTILS_STUB_H_ 18 | 19 | #include 20 | #include 21 | 22 | using std::string; 23 | using std::vector; 24 | 25 | 26 | namespace appengine { 27 | 28 | void SplitString(const string& fill, 29 | const char* delim, 30 | vector* result); 31 | 32 | vector SplitStringWithMaxSplit(const string& full, 33 | const char* delim, 34 | unsigned int max_split); 35 | 36 | string JoinString(const vector& parts, const char* delim); 37 | 38 | void TrimWhitespaceASCII(const string& input, string* output); 39 | 40 | // Case insensitive equality comparison. 41 | bool StringCaseEqual(const string& str1, const string& str2); 42 | 43 | string StringPrintf(const char* format, ...); 44 | 45 | string StrCat(const string &a, const string &b); 46 | string StrCat(const string &a, int b); 47 | string StrCat(const string &a, const string &b, const string &c); 48 | 49 | void EncodeBase64(const string& input, string* output); 50 | 51 | } // namespace appengine 52 | 53 | #endif // PHP_RUNTIME_UTILS_STUB_H_ 54 | -------------------------------------------------------------------------------- /php_stream_wrapper.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | 18 | // This is a substitute stream wrapper for the standard php:// stream wrapper, 19 | // which has been removed in GAE due to the potential for security exploits in 20 | // the standard stream. 21 | // 22 | // We will be adding a limited number of php:// streams that are deemed useful 23 | // and not a potential security risk. 24 | // 25 | // Streams currently supported. 26 | // * php://input - For reading POST data sent to the app. 27 | // * php://output - For writing to the response that will be sent. 28 | // * php://memory - For working with memory based streams. 29 | // * php://temp - For working with temporary data (uses memory). 30 | 31 | #include "php_stream_wrapper.h" 32 | 33 | #include "gae_runtime_module_stub.h" 34 | 35 | extern "C" { 36 | #ifdef __google_internal__ 37 | #include "php/main/SAPI.h" 38 | #else 39 | #include "main/SAPI.h" 40 | #endif 41 | } 42 | 43 | DECLARE_bool(php_enable_php_output_stream); 44 | 45 | namespace appengine { 46 | 47 | static const char kProtocolSeparator[] = "://"; 48 | static const int kProtocolSeparatorLength = sizeof(kProtocolSeparator) - 1; 49 | static const char kProtocol[] = "php"; 50 | static const char kPostDataPathName[] = "input"; 51 | static const char kInvalidOpenModes[] = "awx+"; 52 | static const char kOutputDataPathName[] = "output"; 53 | 54 | // State that is associated with the stream. 55 | struct input_stream_data { 56 | input_stream_data() 57 | : read_position(0) {} 58 | 59 | void* operator new(size_t bytes) { 60 | return emalloc(bytes); 61 | } 62 | 63 | void operator delete(void* ptr) { 64 | efree(ptr); 65 | } 66 | 67 | void* operator new[](size_t bytes); 68 | void operator delete[](void* ptr); 69 | 70 | off_t read_position; 71 | }; 72 | 73 | static size_t php_stream_input_write(php_stream* stream, 74 | const char* buffer, 75 | size_t count TSRMLS_DC) { 76 | return -1; 77 | } 78 | 79 | static size_t php_stream_input_read(php_stream* stream, 80 | char* buffer, 81 | size_t count TSRMLS_DC) { 82 | CHECK(stream); 83 | CHECK(stream->abstract); 84 | CHECK(buffer); 85 | 86 | input_stream_data* data = 87 | reinterpret_cast(stream->abstract); 88 | size_t read_bytes = 0; 89 | 90 | if (!stream->eof) { 91 | // If always-populate-raw-post-data is on then we can memcpy from the 92 | // in memory copy of the POST data. 93 | if (SG(request_info).raw_post_data) { 94 | read_bytes = SG(request_info).raw_post_data_length - data->read_position; 95 | if (read_bytes <= count) { 96 | stream->eof = 1; 97 | } else { 98 | read_bytes = count; 99 | } 100 | if (read_bytes) { 101 | memcpy(buffer, 102 | SG(request_info).raw_post_data + data->read_position, 103 | read_bytes); 104 | } 105 | } else if (sapi_module.read_post) { 106 | // When reading from the sapi module, the sapi keeps track of the current 107 | // position in the stream. 108 | read_bytes = sapi_module.read_post(buffer, count TSRMLS_CC); 109 | if (read_bytes <= 0) { 110 | stream->eof = 1; 111 | read_bytes = 0; 112 | } 113 | SG(read_post_bytes) += read_bytes; 114 | } else { 115 | stream->eof = 1; 116 | } 117 | } 118 | 119 | data->read_position += read_bytes; 120 | return read_bytes; 121 | } 122 | 123 | static int php_stream_input_close(php_stream *stream, 124 | int close_handle TSRMLS_DC) { 125 | CHECK(stream); 126 | CHECK(stream->abstract); 127 | 128 | input_stream_data* data = 129 | reinterpret_cast(stream->abstract); 130 | delete data; 131 | return 0; 132 | } 133 | 134 | static int php_stream_input_flush(php_stream *stream TSRMLS_DC) { 135 | return -1; 136 | } 137 | 138 | static php_stream_ops php_stream_input_ops = { 139 | php_stream_input_write, 140 | php_stream_input_read, 141 | php_stream_input_close, 142 | php_stream_input_flush, 143 | "Input", 144 | NULL, // seek 145 | NULL, // cast 146 | NULL, // stat 147 | NULL // set_option 148 | }; 149 | 150 | static size_t php_stream_output_write(php_stream* stream, 151 | const char* buf, 152 | size_t count TSRMLS_DC) { 153 | php_output_write(buf, count TSRMLS_CC); 154 | return count; 155 | } 156 | 157 | static size_t php_stream_output_read(php_stream* stream, 158 | char* buf, 159 | size_t count TSRMLS_DC) { 160 | stream->eof = 1; 161 | return 0; 162 | } 163 | 164 | static int php_stream_output_close(php_stream* stream, 165 | int close_handle TSRMLS_DC) { 166 | return 0; 167 | } 168 | 169 | static php_stream_ops php_stream_output_ops = { 170 | php_stream_output_write, 171 | php_stream_output_read, 172 | php_stream_output_close, 173 | NULL, // flush 174 | "Output", 175 | NULL, // seek 176 | NULL, // cast 177 | NULL, // stat 178 | NULL // set_option 179 | }; 180 | 181 | static php_stream* input_stream_wrapper(php_stream_wrapper* wrapper, 182 | char* path, 183 | char* mode, 184 | int options, 185 | char** opened_path, 186 | php_stream_context* context 187 | STREAMS_DC TSRMLS_DC) { 188 | CHECK(wrapper); 189 | CHECK(path); 190 | CHECK(mode); 191 | 192 | string mode_str(mode); 193 | string path_str(path); 194 | 195 | size_t protocol_pos = path_str.find(kProtocolSeparator); 196 | string protocol; 197 | string data_path; 198 | 199 | if (protocol_pos != string::npos) { 200 | protocol = path_str.substr(0, protocol_pos); 201 | data_path = path_str.substr(protocol_pos + kProtocolSeparatorLength); 202 | } else { 203 | data_path = path_str; 204 | } 205 | 206 | if (!protocol.empty()) { 207 | if (strcasecmp(protocol.c_str(), kProtocol) != 0) { 208 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 209 | "Protocol name %s is invalid. Only %s is supported.", 210 | protocol.c_str(), kProtocol); 211 | return NULL; 212 | } 213 | } 214 | 215 | if (strcasecmp(data_path.c_str(), kPostDataPathName) == 0) { 216 | if (mode_str.find_first_of(kInvalidOpenModes) != string::npos) { 217 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 218 | "Invalid open mode %s", mode); 219 | return NULL; 220 | } 221 | 222 | input_stream_data* data = new input_stream_data(); 223 | return php_stream_alloc(&php_stream_input_ops, data, 0, mode); 224 | } else if (FLAGS_php_enable_php_output_stream && 225 | strcasecmp(data_path.c_str(), kOutputDataPathName) == 0) { 226 | // The original PHP does not bother checking the mode of php://output, just 227 | // sets it as "wb" 228 | return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb"); 229 | } else if ((strncasecmp(data_path.c_str(), "temp", 4) == 0) || 230 | (strcasecmp(data_path.c_str(), "memory") == 0)) { 231 | // The "temp" stream can specify an amount of memory past which the 232 | // implementation should use a temporary file e.g. 233 | // "php://temp/maxmemory:2097152". The App Engine implementation always 234 | // uses memory and ignores negative maximums (which are normally an error). 235 | int mode_rw = 0; 236 | if (strpbrk(mode, "wa+")) { 237 | mode_rw = TEMP_STREAM_DEFAULT; 238 | } else { 239 | mode_rw = TEMP_STREAM_READONLY; 240 | } 241 | return php_stream_memory_create(mode_rw); 242 | } 243 | 244 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 245 | "Unknown stream type %s%s%s.", 246 | kProtocol, kProtocolSeparator, data_path.c_str()); 247 | 248 | return NULL; 249 | } 250 | 251 | static php_stream_wrapper_ops php_stdio_wops = { 252 | input_stream_wrapper, 253 | NULL, // close 254 | NULL, // fstat 255 | NULL, // stat 256 | NULL, // opendir 257 | "PHP", 258 | NULL, // unlink 259 | NULL, // rename 260 | NULL, // mkdir 261 | NULL // rmdir 262 | }; 263 | 264 | php_stream_wrapper php_input_stream_wrapper = { 265 | &php_stdio_wops, 266 | NULL, 267 | 1, // is_url 268 | }; 269 | 270 | } // namespace appengine 271 | -------------------------------------------------------------------------------- /php_stream_wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // Author: slangley@google.com (Stuart Langley) 17 | #ifndef PHP_STREAM_WRAPPER_H_ 18 | #define PHP_STREAM_WRAPPER_H_ 19 | 20 | extern "C" { 21 | #ifdef __google_internal__ 22 | #include "php/main/php.h" 23 | #include "php/main/php_streams.h" 24 | #else 25 | #include "main/php.h" 26 | #include "main/php_streams.h" 27 | #endif 28 | } // extern "C" 29 | 30 | namespace appengine { 31 | 32 | extern php_stream_wrapper php_input_stream_wrapper; 33 | 34 | } // namespace appengine 35 | 36 | #endif // PHP_STREAM_WRAPPER_H_ 37 | 38 | -------------------------------------------------------------------------------- /remote_api.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2008 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // 17 | // Container types for App Engine's public rpc-over-http interface. 18 | // 19 | // These containers are used for at least three different purposes, which are 20 | // not always compatible: 21 | // 1. Interfacing remotely with the AppEngine APIs (from e.g. a user's desktop) 22 | // by going through a special handler on the app. See 23 | // https://developers.google.com/appengine/articles/remote_api 24 | // 2. In the devappserver to make API calls from the app to the local API 25 | // server. 26 | // 3. Issuing API calls from a VM runtime to a proxy endpoint on the 27 | // appserver. Also in the near-ish future, some API calls might go to a 28 | // local API server on the VM. 29 | // 30 | // Once gRPC takes shape, that protocol will likely replace most of this file. 31 | 32 | syntax = "proto2"; 33 | 34 | 35 | 36 | package apphosting.ext.remote_api; 37 | option java_package = "com.google.apphosting.utils.remoteapi"; 38 | option java_outer_classname = "RemoteApiPb"; 39 | option go_package = "remote_api"; 40 | 41 | message Request { 42 | required string service_name = 2; 43 | required string method = 3; 44 | // Contains the serialized service-specific protobuf 45 | required bytes request = 4; 46 | optional string request_id = 5; 47 | } 48 | 49 | message ApplicationError { 50 | required int32 code = 1; 51 | required string detail = 2; 52 | } 53 | 54 | // Transport-level RPC errors adapted from runtime.proto 55 | message RpcError { 56 | enum ErrorCode { 57 | UNKNOWN = 0; 58 | CALL_NOT_FOUND = 1; 59 | PARSE_ERROR = 2; 60 | SECURITY_VIOLATION = 3; 61 | OVER_QUOTA = 4; 62 | REQUEST_TOO_LARGE = 5; 63 | CAPABILITY_DISABLED = 6; 64 | FEATURE_DISABLED = 7; 65 | BAD_REQUEST = 8; 66 | RESPONSE_TOO_LARGE = 9; 67 | CANCELLED = 10; 68 | REPLAY_ERROR = 11; 69 | DEADLINE_EXCEEDED = 12; 70 | } 71 | required int32 code = 1; // Should contain an ErrorCode 72 | optional string detail = 2; 73 | } 74 | 75 | message Response { 76 | // Exactly one of the fields 'response', 'exception', 'java_exception' and 77 | // 'rpc_error' should be set. If 'exception' or 'java_exception' is set, 78 | // 'application_error' will also be set if the exception was generated due to 79 | // RPC::ApplicationError being set. 80 | // 81 | // The remote access feature described above (1) and the devappserver (2) 82 | // use 'exception' and 'java_exception' to specify error responses. 83 | // 'exception' is a pickled python exception object. 'java_exception' is a 84 | // serialized java exception object. Remote access (1) also uses 85 | // 'application_error' for datastore errors. 86 | // 87 | // For VM runtimes (3), transport-layer errors are passed in 'rpc_error' and 88 | // application-layer errors in 'application_error'. The 'exception' and 89 | // 'java_exception' fields are unused in this context. 90 | // 91 | // For cross-language maintainability, it is probably best to avoid 92 | // using 'exception' and 'java_exception' in new code. 93 | optional bytes response = 1; 94 | optional bytes exception = 2; 95 | optional ApplicationError application_error = 3; 96 | optional bytes java_exception = 4; 97 | optional RpcError rpc_error = 5; 98 | } 99 | 100 | option optimize_for = LITE_RUNTIME; 101 | -------------------------------------------------------------------------------- /urlfetch_service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2007 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // All Rights Reserved. 17 | // 18 | // 19 | // The URLFetch service downloads external HTTP URLs using Harpoon and some 20 | // internal URLs using HTTPOverRpc. 21 | // See: 22 | // https://cloud.google.com/appengine/docs/python/urlfetch/ 23 | 24 | syntax = "proto2"; 25 | 26 | // Some generic_services option(s) added automatically. 27 | 28 | option java_package = "com.google.appengine.api.urlfetch"; 29 | option java_outer_classname = "URLFetchServicePb"; 30 | 31 | package apphosting; 32 | 33 | message URLFetchServiceError { 34 | enum ErrorCode { 35 | OK = 0; 36 | INVALID_URL = 1; 37 | FETCH_ERROR = 2; 38 | UNSPECIFIED_ERROR = 3; 39 | RESPONSE_TOO_LARGE = 4; 40 | DEADLINE_EXCEEDED = 5; 41 | SSL_CERTIFICATE_ERROR = 6; 42 | DNS_ERROR = 7; 43 | CLOSED = 8; 44 | INTERNAL_TRANSIENT_ERROR = 9; 45 | TOO_MANY_REDIRECTS = 10; 46 | MALFORMED_REPLY = 11; 47 | CONNECTION_ERROR = 12; 48 | PAYLOAD_TOO_LARGE = 13; 49 | } 50 | } 51 | 52 | message URLFetchRequest { 53 | enum RequestMethod { 54 | GET = 1; 55 | POST = 2; 56 | HEAD = 3; 57 | PUT = 4; 58 | DELETE = 5; 59 | PATCH = 6; 60 | }; 61 | required RequestMethod Method = 1; 62 | required string Url = 2; 63 | repeated group Header = 3 { 64 | required string Key = 4; 65 | required string Value = 5; 66 | }; 67 | // Only specify a payload for POST, PUT and PATCH requests 68 | optional bytes Payload = 6 ; 69 | 70 | // If false, Fastnet will return the real HTTP redirect status code. 71 | // (this is required for e.g. OpenID RP implementations). By 72 | // like 5, but hides the redirect chain from the end user and any 73 | // eventual successful fetch results in a 200 OK. 74 | // 75 | // NOTE(user): For historical reasons this must be set to false to permit 76 | // the use of HttpOverRpc back to App Engine. b/22785370 77 | optional bool FollowRedirects = 7 [default=true]; 78 | 79 | // The timeout for the request in seconds. 80 | optional double Deadline = 8; 81 | 82 | // If true, Harpoon will drop the connection and fail the request if it 83 | // encounters an invalid certificate. 84 | // WARNING: Do not rely on the default value of this field for certificate 85 | // validation. There is an appserver flag that applies if the default value 86 | // is used (urlfetch_harpoon_failonsslcertificateerror_default) which is 87 | // currently (as of August 2015) set to false. If you want certificate 88 | // validation then always set the value of this field to true. b/1829826 89 | optional bool MustValidateServerCertificate = 9 [default=true]; 90 | } 91 | 92 | message URLFetchResponse { 93 | optional bytes Content = 1; 94 | required int32 StatusCode = 2; 95 | repeated group Header = 3 { 96 | // Keys are not guaranteed to be unique across all Headers belonging 97 | // to a single URLFetchResponse. There could be multiple Headers with 98 | // the same name. 99 | required string Key = 4; 100 | required string Value = 5; 101 | }; 102 | optional bool ContentWasTruncated = 6 [default=false]; 103 | optional int64 ExternalBytesSent = 7; 104 | optional int64 ExternalBytesReceived = 8; 105 | 106 | // The final URL retrieved, if any redirects were followed. Will not 107 | // be present if the retrieved URL matches the requested URL (or redirects 108 | // are not followed). 109 | optional string FinalUrl = 9; 110 | 111 | // Quota used by the recipient app in fulfilling an api request. 112 | optional int64 ApiCpuMilliseconds = 10 [default=0]; 113 | optional int64 ApiBytesSent = 11 [default=0]; 114 | optional int64 ApiBytesReceived = 12 [default=0]; 115 | } 116 | 117 | service URLFetchService { 118 | rpc Fetch(URLFetchRequest) returns (URLFetchResponse) {}; 119 | } 120 | option optimize_for = LITE_RUNTIME; 121 | -------------------------------------------------------------------------------- /urlfetch_stream_wrapper.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 17 | 18 | #include "urlfetch_stream_wrapper.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "php_constants.h" 29 | 30 | #include "gae_runtime_module_stub.h" 31 | #include "php_rpc_utils_stub.h" 32 | #include "php_runtime_utils_stub.h" 33 | #include "urlfetch_service.pb.h" 34 | 35 | extern "C" { 36 | #ifdef __google_internal__ 37 | #include "php/ext/standard/basic_functions.h" 38 | #include "php/ext/standard/php_smart_str.h" 39 | #include "php/ext/standard/php_standard.h" 40 | #include "php/main/php.h" 41 | #include "php/main/php_globals.h" 42 | #include "php/main/php_ini.h" 43 | #include "php/main/php_network.h" 44 | #include "php/main/php_streams.h" 45 | #include "php/main/SAPI.h" 46 | #else 47 | #include "ext/standard/basic_functions.h" 48 | #include "ext/standard/php_smart_str.h" 49 | #include "ext/standard/php_standard.h" 50 | #include "main/php.h" 51 | #include "main/php_globals.h" 52 | #include "main/php_ini.h" 53 | #include "main/php_network.h" 54 | #include "main/php_streams.h" 55 | #include "main/SAPI.h" 56 | #endif 57 | } 58 | 59 | using appengine::kErrorCodeName; 60 | using appengine::kMakeApiCallName; 61 | using appengine::kResultStringName; 62 | using appengine::PhpApiRpc; 63 | using apphosting::URLFetchRequest; 64 | using apphosting::URLFetchRequest_Header; 65 | using apphosting::URLFetchResponse; 66 | using apphosting::URLFetchResponse_Header; 67 | using apphosting::URLFetchServiceError; 68 | 69 | namespace appengine { 70 | 71 | // SSL context options that URL Fetch does not use. 72 | static const char* kSSLContextOptions[] = { 73 | "allow_self_signed", 74 | "cafile", 75 | "capath", 76 | "local_cert", 77 | "passphrase", 78 | "CN_match", 79 | "verify_depth", 80 | "ciphers", 81 | "capture_peer_cert", 82 | "capture_peer_cert_chain", 83 | "SNI_enabled", 84 | "SNI_server_name", 85 | NULL}; 86 | 87 | struct php_urlfetch_data { 88 | URLFetchResponse response; 89 | size_t buffer_size; 90 | off_t buffer_read_position; 91 | }; 92 | 93 | // Reads at most a specified amount of data from the response buffer into the 94 | // read buffer and returns the amount read. 95 | // 96 | // Arguments: 97 | // - stream: The stream from which the request was initated. 98 | // - buf: Reference to the read buffer to copy data to. 99 | // - count: The maximal amount to copy to the read buffer. 100 | // 101 | // Returns: The amount of data that was copied to the read buffer. 102 | static size_t php_urlfetch_read(php_stream* stream, 103 | char* buf, size_t count TSRMLS_DC) { 104 | CHECK(stream); 105 | CHECK(stream->abstract); 106 | 107 | php_urlfetch_data* response_data = 108 | reinterpret_cast(stream->abstract); 109 | 110 | const char* current_pos = response_data->response.content().c_str(); 111 | off_t read_offset = response_data->buffer_read_position; 112 | int remaining_in_response_buf = response_data->buffer_size - read_offset; 113 | 114 | if (count > remaining_in_response_buf) { 115 | count = remaining_in_response_buf; 116 | stream->eof = 1; 117 | } else { 118 | stream->eof = 0; 119 | } 120 | 121 | memcpy(buf, current_pos + read_offset, count); 122 | 123 | response_data->buffer_read_position += count; 124 | return count; 125 | } 126 | 127 | // Closes the supplied stream. Deconstructs and frees the response buffer and 128 | // associated values. 129 | // 130 | // Arguments: 131 | // - stream: The stream from which the request was initated. 132 | // - close_handle: The close handle. 133 | // 134 | // Returns: 0 if successful. 135 | static int php_urlfetch_close(php_stream* stream, int close_handle TSRMLS_DC) { 136 | CHECK(stream); 137 | CHECK(stream->abstract); 138 | php_urlfetch_data* response_data = 139 | reinterpret_cast(stream->abstract); 140 | response_data->~php_urlfetch_data(); 141 | efree(response_data); 142 | return 0; 143 | } 144 | 145 | // Given an URL Fetch HTTP Stream, offset and offset type (whence), move the 146 | // position holder in the response (background) buffer as well as setting 147 | // newoffset to update the read buffer. 148 | // 149 | // Arguments: 150 | // - stream: The stream from which the request was initiated. 151 | // - offset: How much to offset from the current position/start/end. 152 | // - whence: Whether to offset from the current position/start/end. 153 | // - newoffset: Reference to the offset to set in the read buffer. 154 | // 155 | // Returns: 0 if successfully changed position, -1 otherwise. 156 | static int php_urlfetch_seek(php_stream* stream, 157 | off_t offset, 158 | int whence, off_t* newoffset TSRMLS_DC) { 159 | CHECK(stream); 160 | CHECK(stream->abstract); 161 | 162 | php_urlfetch_data* response_data = 163 | reinterpret_cast(stream->abstract); 164 | 165 | size_t eof_position = response_data->buffer_size; 166 | off_t new_position; 167 | 168 | switch (whence) { 169 | case SEEK_CUR: 170 | // streams.c, _php_stream_seek translates SEEK_CUR to SEEK_SET. 171 | // We won't encounter SEEK_CUR here, so return -1. 172 | VLOG(2) << "Unexpected call to seek with SEEK_CUR, ignoring seek."; 173 | return -1; 174 | break; 175 | case SEEK_SET: 176 | new_position = offset; 177 | break; 178 | case SEEK_END: 179 | new_position = eof_position + offset; 180 | break; 181 | } 182 | 183 | if (new_position < 0 || new_position > eof_position) { 184 | return -1; 185 | } 186 | 187 | response_data->buffer_read_position = new_position; 188 | CHECK(newoffset); 189 | *newoffset = new_position; 190 | 191 | if (new_position + stream->chunk_size < eof_position) { 192 | stream->eof = 0; 193 | } else { 194 | stream->eof = 1; 195 | } 196 | 197 | return 0; 198 | } 199 | 200 | // Returns the message string associated with a http response status code. 201 | // TODO(user): Put this function somewhere common. 202 | // 203 | // Arguments: 204 | // - status_code: The status code to return a message for. 205 | // - status_code_message: Pointer to the string to print the message to. 206 | // 207 | // Returns: True if the message was successfully set. 208 | static bool urlfetch_http_status_message(int status_code, 209 | string* status_code_message) { 210 | switch (status_code) { 211 | case 100: 212 | *status_code_message = "Continue"; 213 | break; 214 | case 101: 215 | *status_code_message = "Switching Protocols"; 216 | break; 217 | case 200: 218 | *status_code_message = "OK"; 219 | break; 220 | case 201: 221 | *status_code_message = "Created"; 222 | break; 223 | case 202: 224 | *status_code_message = "Accepted"; 225 | break; 226 | case 203: 227 | *status_code_message = "Non-Authoritative Information"; 228 | break; 229 | case 204: 230 | *status_code_message = "No Content"; 231 | break; 232 | case 205: 233 | *status_code_message = "Reset Content"; 234 | break; 235 | case 206: 236 | *status_code_message = "Partial Content"; 237 | break; 238 | case 300: 239 | *status_code_message = "Multiple Choices"; 240 | break; 241 | case 301: 242 | *status_code_message = "Moved Permanently"; 243 | break; 244 | case 302: 245 | *status_code_message = "Moved Temporarily"; 246 | break; 247 | case 303: 248 | *status_code_message = "See Other"; 249 | break; 250 | case 304: 251 | *status_code_message = "Not Modified"; 252 | break; 253 | case 305: 254 | *status_code_message = "Use Proxy"; 255 | break; 256 | case 400: 257 | *status_code_message = "Bad Request"; 258 | break; 259 | case 401: 260 | *status_code_message = "Unauthorized"; 261 | break; 262 | case 402: 263 | *status_code_message = "Payment Required"; 264 | break; 265 | case 403: 266 | *status_code_message = "Forbidden"; 267 | break; 268 | case 404: 269 | *status_code_message = "Not Found"; 270 | break; 271 | case 405: 272 | *status_code_message = "Method Not Allowed"; 273 | break; 274 | case 406: 275 | *status_code_message = "Not Acceptable"; 276 | break; 277 | case 407: 278 | *status_code_message = "Proxy Authentication Required"; 279 | break; 280 | case 408: 281 | *status_code_message = "Request Time-out"; 282 | break; 283 | case 409: 284 | *status_code_message = "Conflict"; 285 | break; 286 | case 410: 287 | *status_code_message = "Gone"; 288 | break; 289 | case 411: 290 | *status_code_message = "Length Required"; 291 | break; 292 | case 412: 293 | *status_code_message = "Precondition Failed"; 294 | break; 295 | case 413: 296 | *status_code_message = "Request Entity Too Large"; 297 | break; 298 | case 414: 299 | *status_code_message = "Request-URI Too Large"; 300 | break; 301 | case 415: 302 | *status_code_message = "Unsupported Media Type"; 303 | break; 304 | case 428: 305 | *status_code_message = "Precondition Required"; 306 | break; 307 | case 429: 308 | *status_code_message = "Too Many Requests"; 309 | break; 310 | case 431: 311 | *status_code_message = "Request Header Fields Too Large"; 312 | break; 313 | case 500: 314 | *status_code_message = "Internal Server Error"; 315 | break; 316 | case 501: 317 | *status_code_message = "Not Implemented"; 318 | break; 319 | case 502: 320 | *status_code_message = "Bad Gateway"; 321 | break; 322 | case 503: 323 | *status_code_message = "Service Unavailable"; 324 | break; 325 | case 504: 326 | *status_code_message = "Gateway Time-out"; 327 | break; 328 | case 505: 329 | *status_code_message = "HTTP Version not supported"; 330 | break; 331 | case 511: 332 | *status_code_message = "Network Authentication Required"; 333 | break; 334 | default: 335 | VLOG(2) << "URL Fetch: Status not found. Code: " << status_code; 336 | return false; 337 | break; 338 | } 339 | return true; 340 | } 341 | 342 | // Takes an application error code and returns the associated error message. 343 | // 344 | // Arguments: 345 | // - error_code: The error code to return a message for. 346 | // 347 | // Returns: The message associated with the error code. 348 | static string urlfetch_application_error_message(const PhpApiRpc& rpc) { 349 | string message; 350 | switch (rpc.app_error()) { 351 | case URLFetchServiceError::INVALID_URL: 352 | message = "Invalid URL"; 353 | break; 354 | case URLFetchServiceError::FETCH_ERROR: 355 | message = "Fetch error"; 356 | break; 357 | case URLFetchServiceError::RESPONSE_TOO_LARGE: 358 | message = "Response too large"; 359 | break; 360 | case URLFetchServiceError::DEADLINE_EXCEEDED: 361 | message = "Request deadline exceeded"; 362 | break; 363 | case URLFetchServiceError::SSL_CERTIFICATE_ERROR: 364 | message = "SSL certificate error - certificate invalid or non-existent"; 365 | break; 366 | case URLFetchServiceError::DNS_ERROR: 367 | message = "DNS error"; 368 | break; 369 | case URLFetchServiceError::CLOSED: 370 | message = "Connection closed"; 371 | break; 372 | case URLFetchServiceError::INTERNAL_TRANSIENT_ERROR: 373 | message = "Internal transient error"; 374 | break; 375 | case URLFetchServiceError::TOO_MANY_REDIRECTS: 376 | message = "Too many redirects"; 377 | break; 378 | case URLFetchServiceError::MALFORMED_REPLY: 379 | message = "Malformed reply"; 380 | break; 381 | case URLFetchServiceError::CONNECTION_ERROR: 382 | message = "Connection error"; 383 | break; 384 | default: 385 | // Also catch-all for URLFetchServiceError::UNSPECIFIED_ERROR. 386 | message = StrCat("Unknown error - ", rpc.app_error()); 387 | break; 388 | } 389 | 390 | if (!rpc.error_detail().empty()) { 391 | message = StrCat(message, ", ", rpc.error_detail()); 392 | } 393 | return message; 394 | } 395 | 396 | // Takes a header row string (i.e. header_key: value) and inserts a new header 397 | // item into the supplied request object. 398 | // 399 | // Arguments: 400 | // - header_line: The header row to process. 401 | // - request: The request object to insert the header key/value pair into. 402 | // 403 | // Returns: True if the header pair processed was a user-agent header, false 404 | // otherwise. 405 | static bool urlfetch_header_row_process(const string& header_line, 406 | URLFetchRequest* request TSRMLS_DC) { 407 | bool header_user_agent = false; 408 | size_t pos = header_line.find_first_of(":"); 409 | 410 | if (pos != string::npos) { 411 | string header_key = header_line.substr(0, pos); 412 | string header_value = header_line.substr(pos+1, string::npos); 413 | 414 | TrimWhitespaceASCII(header_key, &header_key); 415 | TrimWhitespaceASCII(header_value, &header_value); 416 | 417 | if (header_key.empty()) { 418 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 419 | "HTTP stream input header contains ill-formatted header rows: %s", 420 | header_line.c_str()); 421 | } else { 422 | URLFetchRequest_Header* header = request->add_header(); 423 | header->set_key(header_key); 424 | header->set_value(header_value); 425 | } 426 | 427 | if (!header_user_agent && 428 | StringCaseEqual(header_key, "user-agent")) { 429 | header_user_agent = true; 430 | } 431 | } else { 432 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 433 | "HTTP stream input header contains ill-formatted header rows: %s", 434 | header_line.c_str()); 435 | } 436 | 437 | return header_user_agent; 438 | } 439 | 440 | // Processes a number of header rows supplied in a single string, adding each 441 | // header key/value pair to the supplied request object. 442 | // 443 | // Arguments: 444 | // - header: The string of header rows to process. 445 | // - request: The request object to insert header key/value pairs into. 446 | // 447 | // Returns: True if the header pair processed was a user-agent header, false 448 | // otherwise. 449 | static bool urlfetch_header_process(const string& header, 450 | URLFetchRequest* request TSRMLS_DC) { 451 | bool header_user_agent = false; 452 | vector headers; 453 | SplitString(header, "\n", &headers); 454 | 455 | for (int i = 0; i < headers.size(); ++i) { 456 | const string& header_line = headers.at(i); 457 | if (!header_line.empty()) { 458 | if (urlfetch_header_row_process(header_line, request TSRMLS_CC)) { 459 | header_user_agent = true; 460 | } 461 | } 462 | } 463 | return header_user_agent; 464 | } 465 | 466 | // Returns the request method associated with that string, or GET if no request 467 | // method can be determined (gives a warning). 468 | // 469 | // Arguments: 470 | // - method_string: A string representation of the method choice. 471 | // - wrapper: Contains the wrapper data for error logging. 472 | // - options: Contains wrapper opener options for error logging. 473 | // 474 | // Returns: The URLFetchRequest::RequestMethod associated with that string. 475 | static URLFetchRequest::RequestMethod urlfetch_method(string method_string, 476 | php_stream_wrapper* wrapper, int options TSRMLS_DC) { 477 | if (StringCaseEqual(method_string, "GET")) { 478 | return URLFetchRequest::GET; 479 | } else if (StringCaseEqual(method_string, "POST")) { 480 | return URLFetchRequest::POST; 481 | } else if (StringCaseEqual(method_string, "HEAD")) { 482 | return URLFetchRequest::HEAD; 483 | } else if (StringCaseEqual(method_string, "PUT")) { 484 | return URLFetchRequest::PUT; 485 | } else if (StringCaseEqual(method_string, "DELETE")) { 486 | return URLFetchRequest::DELETE; 487 | } else if (StringCaseEqual(method_string, "PATCH")) { 488 | return URLFetchRequest::PATCH; 489 | } 490 | php_error_docref(NULL TSRMLS_CC, E_WARNING, 491 | "Invalid method: %s. Must be of GET, POST, HEAD, PUT, DELETE or PATCH. " 492 | "Defaulting to GET.", 493 | method_string.c_str()); 494 | return URLFetchRequest::GET; 495 | } 496 | 497 | // Populates the $http_response_header array with the response headers from URL 498 | // Fetch. 499 | // 500 | // Arguments: 501 | // - response_data: Contains the response protobuf. 502 | // - wrapper: Contains the wrapper data for error logging. 503 | // - options: Contains wrapper opener options for error logging. 504 | // 505 | // Returns: A pointer to the response header ZEND array. 506 | static zval* urlfetch_populate_response_header( 507 | php_urlfetch_data* response_data, php_stream_wrapper* wrapper, 508 | int options TSRMLS_DC) { 509 | if (!EG(active_symbol_table)) { 510 | zend_rebuild_symbol_table(TSRMLS_C); 511 | } 512 | zval* ztmp; 513 | MAKE_STD_ZVAL(ztmp); 514 | array_init(ztmp); 515 | ZEND_SET_SYMBOL(EG(active_symbol_table), 516 | const_cast("http_response_header"), ztmp); 517 | 518 | zval** rh; 519 | zend_hash_find(EG(active_symbol_table), 520 | const_cast("http_response_header"), 521 | sizeof("http_response_header"), 522 | reinterpret_cast(&rh)); 523 | CHECK(rh); 524 | zval* response_header = *rh; 525 | 526 | int status_code = response_data->response.statuscode(); 527 | 528 | string status_code_message = ""; 529 | if (!urlfetch_http_status_message(status_code, &status_code_message)) { 530 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown status code."); 531 | } 532 | 533 | // We use a HTTP/1.1 compliant proxy. 534 | string response_row_buf = StringPrintf("HTTP/1.1 %d %s", 535 | status_code, 536 | status_code_message.c_str()); 537 | 538 | zval* http_response_default; 539 | MAKE_STD_ZVAL(http_response_default); 540 | ZVAL_STRINGL(http_response_default, 541 | response_row_buf.c_str(), response_row_buf.length(), 1); 542 | zend_hash_next_index_insert(Z_ARRVAL_P(response_header), 543 | &http_response_default, sizeof(zval*), NULL); 544 | 545 | // Insert the remaining headers. 546 | int num_header_rows = response_data->response.header_size(); 547 | string protocol_and_version = ""; 548 | for (int i = 0; i < num_header_rows; ++i) { 549 | const URLFetchResponse_Header& header_row = 550 | response_data->response.header(i); 551 | 552 | string tmp_line = StrCat(header_row.key(), ": ", header_row.value()); 553 | zval* http_response; 554 | MAKE_STD_ZVAL(http_response); 555 | ZVAL_STRINGL(http_response, tmp_line.c_str(), tmp_line.length(), 1); 556 | zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_response, 557 | sizeof(zval*), NULL); 558 | } 559 | 560 | return response_header; 561 | } 562 | 563 | static php_stream_ops http_stream_ops = { 564 | NULL, // write 565 | php_urlfetch_read, // read 566 | php_urlfetch_close, // close 567 | NULL, // flush 568 | "http via urlfetch", // label 569 | php_urlfetch_seek, // seek 570 | NULL, // cast 571 | NULL, // stat 572 | NULL // set_option 573 | }; 574 | 575 | // Called to open a new URL Fetch HTTP/HTTPS stream. Parses inputs, forms a URL 576 | // Fetch protobuf, makes the request, parse outputs, buffers the response data 577 | // and stores stream meta data as appropriate. 578 | // 579 | // Arguments: 580 | // - wrapper: The appropriate wrapper to use to process the data and make the 581 | // request. Used for ensuring errors and correctly logged. 582 | // - path: The URL for the request. Of the form http://host or https://host. 583 | // - mode: The mode with which to open the request. Either read- 'r' or read 584 | // binary- 'rb'. Write modes are invalid for HTTP streams. 585 | // - options: Flag concerning STREAM_USE_PATH and STREAM_REPORT_ERRORS. 586 | // - opened_path: The actual path that was opened. 587 | // - context: Context options to consider when making the request. 588 | // 589 | // Returns: A stream object containing the relevant data to perform stream 590 | // operations. 591 | static php_stream* urlfetch_stream_wrapper(php_stream_wrapper* wrapper, 592 | char* path, 593 | char* mode, 594 | int options, 595 | char** opened_path, 596 | php_stream_context* context STREAMS_DC TSRMLS_DC) { 597 | 598 | CHECK(wrapper); 599 | CHECK(path); 600 | CHECK(mode); 601 | 602 | zval** tmpzval = NULL; 603 | 604 | const string url = path; 605 | const string separator = "://"; 606 | 607 | size_t pos = url.find(separator); 608 | if (pos == string::npos) { 609 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 610 | "URL %s is not correctly formatted. Use http://hostname or " 611 | "https://hostname.", url.c_str()); 612 | return NULL; 613 | } 614 | const string url_scheme = url.substr(0, pos); 615 | const string full_url_without_scheme = url.substr(pos + separator.length()); 616 | 617 | if (!StringCaseEqual(url_scheme, "http") && 618 | !StringCaseEqual(url_scheme, "https")) { 619 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 620 | "Invalid wrapper scheme for this wrapper. Use http:// or https://."); 621 | return NULL; 622 | } else if (full_url_without_scheme.empty()) { 623 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 624 | "The URL %s is invalid. Use http://hostname or " 625 | "https://hostname.", url.c_str()); 626 | return NULL; 627 | } 628 | 629 | string open_mode = mode; 630 | if (open_mode.find_first_of("awx+") != string::npos) { 631 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 632 | "HTTP streams do not support writeable connections."); 633 | return NULL; 634 | } 635 | 636 | URLFetchRequest request; 637 | 638 | // Extract the authority from the url, so we can check for basic auth. 639 | string authority = full_url_without_scheme; 640 | pos = authority.find('/'); 641 | if (pos != string::npos) { 642 | authority = authority.substr(0, pos); 643 | } 644 | 645 | // Support for basic auth in the authority, in user:pass format 646 | // https://tools.ietf.org/html/rfc3986#section-3.2.1 647 | const string at_sign = "@"; 648 | pos = authority.find_first_of(at_sign); 649 | if (pos != string::npos) { 650 | string userinfo = full_url_without_scheme.substr(0, pos); 651 | // Update the URL to remove the user:pass 652 | string updated_path = StrCat(url_scheme, 653 | separator, 654 | full_url_without_scheme.substr( 655 | pos + at_sign.length())); 656 | request.set_url(updated_path); 657 | 658 | // Add a header for the Basic Authorization 659 | string base64_encoded; 660 | EncodeBase64(userinfo, &base64_encoded); 661 | URLFetchRequest_Header* header = request.add_header(); 662 | header->set_key("Authorization"); 663 | header->set_value(StrCat("Basic ", base64_encoded)); 664 | } else { 665 | request.set_url(path); 666 | } 667 | 668 | if (context && php_stream_context_get_option( 669 | context, "http", "timeout", &tmpzval) == SUCCESS) { 670 | SEPARATE_ZVAL(tmpzval); 671 | convert_to_double_ex(tmpzval); 672 | request.set_deadline(Z_DVAL_PP(tmpzval)); 673 | } else { 674 | double default_socket_timeout = 675 | static_cast(FG(default_socket_timeout)); 676 | if (default_socket_timeout > 0) { 677 | request.set_deadline(static_cast(FG(default_socket_timeout))); 678 | } 679 | } 680 | 681 | int redirect_max = 5; 682 | if (context && php_stream_context_get_option( 683 | context, "http", "max_redirects", &tmpzval) == SUCCESS) { 684 | SEPARATE_ZVAL(tmpzval); 685 | convert_to_long_ex(tmpzval); 686 | redirect_max = static_cast(Z_LVAL_PP(tmpzval)); 687 | } 688 | 689 | int follow_location = 1; 690 | if (context && php_stream_context_get_option( 691 | context, "http", "follow_location", &tmpzval) == SUCCESS) { 692 | SEPARATE_ZVAL(tmpzval); 693 | convert_to_long_ex(tmpzval); 694 | follow_location = Z_LVAL_PP(tmpzval); 695 | } 696 | 697 | bool follow_redirects = true; 698 | if (redirect_max <= 1 || follow_location == 0) { 699 | follow_redirects = false; 700 | } 701 | request.set_followredirects(follow_redirects); 702 | 703 | if (context && php_stream_context_get_option( 704 | context, "http", "method", &tmpzval) == SUCCESS) { 705 | if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval) > 0) { 706 | string method_string = Z_STRVAL_PP(tmpzval); 707 | URLFetchRequest::RequestMethod method = urlfetch_method(method_string, 708 | wrapper, options TSRMLS_CC); 709 | request.set_method(method); 710 | } else { 711 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 712 | "Invalid method. Must be a string."); 713 | return NULL; 714 | } 715 | } else { 716 | // The default context is GET if another context is not set. 717 | request.set_method(URLFetchRequest::GET); 718 | } 719 | 720 | // Precendence of user-agent: header, context option, ini file. 721 | bool header_user_agent = false; 722 | if (context && php_stream_context_get_option( 723 | context, "http", "header", &tmpzval) == SUCCESS) { 724 | char* tmp = NULL; 725 | if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) { 726 | tmp = Z_STRVAL_PP(tmpzval); 727 | } else { 728 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 729 | "Invalid headers. Must be a string."); 730 | return NULL; 731 | } 732 | 733 | if (tmp && strlen(tmp) > 0) { 734 | string header = tmp; 735 | if (header.find("\n") == string::npos) { 736 | header_user_agent = urlfetch_header_row_process(header, 737 | &request TSRMLS_CC); 738 | } else { 739 | header_user_agent = urlfetch_header_process(header, 740 | &request TSRMLS_CC); 741 | } 742 | } 743 | } 744 | 745 | // Precendence of remaining options: user-agent context option, ini file. 746 | if (!header_user_agent) { 747 | if (context && php_stream_context_get_option( 748 | context, "http", "user-agent", &tmpzval) == SUCCESS) { 749 | if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) { 750 | char* tmp = Z_STRVAL_PP(tmpzval); 751 | apphosting::URLFetchRequest_Header* header = request.add_header(); 752 | header->set_key("user-agent"); 753 | header->set_value(tmp); 754 | } else { 755 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 756 | "Invalid user-agent field. Must be a string"); 757 | return NULL; 758 | } 759 | } else { 760 | const char* user_agent = FG(user_agent); 761 | if (user_agent && *user_agent) { 762 | apphosting::URLFetchRequest_Header* header = request.add_header(); 763 | header->set_key("user-agent"); 764 | header->set_value(user_agent); 765 | } 766 | } 767 | } 768 | 769 | URLFetchRequest::RequestMethod request_method = 770 | (URLFetchRequest::RequestMethod) request.method(); 771 | if (request_method == URLFetchRequest::POST || 772 | request_method == URLFetchRequest::PUT || 773 | request_method == URLFetchRequest::PATCH) { 774 | if (context && php_stream_context_get_option( 775 | context, "http", "content", &tmpzval) == SUCCESS) { 776 | if (Z_TYPE_PP(tmpzval) == IS_STRING && Z_STRLEN_PP(tmpzval)) { 777 | string payload(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval)); 778 | request.set_payload(payload); 779 | } 780 | } 781 | } 782 | 783 | bool must_validate_server_certificate = true; 784 | if (url_scheme.compare("http") == 0) { 785 | must_validate_server_certificate = false; 786 | } 787 | if (context && php_stream_context_get_option( 788 | context, "ssl", "verify_peer", &tmpzval) == SUCCESS) { 789 | must_validate_server_certificate = Z_LVAL_PP(tmpzval); 790 | } 791 | request.set_mustvalidateservercertificate(must_validate_server_certificate); 792 | 793 | if (context && (url_scheme.compare("https") == 0)) { 794 | // Check for SSL context options that we don't support, print debug message. 795 | string ssl_context_errors; 796 | for (int i = 0; kSSLContextOptions[i] != NULL; ++i) { 797 | if (php_stream_context_get_option( 798 | context, "ssl", kSSLContextOptions[i], &tmpzval) == SUCCESS) { 799 | ssl_context_errors = StrCat(ssl_context_errors, 800 | ssl_context_errors.empty() ? "" : ", ", 801 | kSSLContextOptions[i]); 802 | } 803 | } 804 | 805 | if (!ssl_context_errors.empty()) { 806 | // Using php_stream_wrapper_log_error here leads to confusing error 807 | // messages for users if there is an error making the RPC call. Just 808 | // log this message directly instead. 809 | string message = StringPrintf( 810 | "Unsupported SSL context options are set. The following options are " 811 | "present, but have been ignored: %s", ssl_context_errors.c_str()); 812 | sapi_module.log_message(const_cast(message.c_str()) TSRMLS_CC); 813 | } 814 | } 815 | 816 | PhpApiRpc rpc; 817 | string request_string; 818 | string response_string; 819 | request.SerializeToString(&request_string); 820 | 821 | PhpRpcUtils::MakeApiCall(&rpc, "urlfetch", "Fetch", request_string, 822 | request.deadline() TSRMLS_CC); 823 | 824 | if (rpc.error() != PhpApiRpc::OK) { 825 | switch (rpc.error()) { 826 | case PhpApiRpc::RPC_FAILED: 827 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 828 | "RPC Error: Call failed."); 829 | break; 830 | case PhpApiRpc::CALL_NOT_FOUND: 831 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 832 | "RPC Error: Call not found."); 833 | break; 834 | case PhpApiRpc::APPLICATION_ERROR: 835 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 836 | "%s", urlfetch_application_error_message(rpc).c_str()); 837 | break; 838 | default: 839 | php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 840 | "RPC Error: Unknown error - %d.", rpc.error()); 841 | break; 842 | } 843 | return NULL; 844 | } 845 | 846 | void* place = ecalloc(1, sizeof(php_urlfetch_data)); 847 | php_urlfetch_data* response_data = new(place) php_urlfetch_data(); 848 | response_data->response.ParseFromString(rpc.response_pb()); 849 | 850 | zval* response_header = urlfetch_populate_response_header( 851 | response_data, wrapper, options TSRMLS_CC); 852 | 853 | response_data->buffer_read_position = 0; 854 | response_data->buffer_size = response_data->response.content().length(); 855 | 856 | php_stream* stream = 857 | php_stream_alloc(&http_stream_ops, response_data, 0, mode); 858 | 859 | if (response_data->buffer_size > 0) { 860 | stream->eof = 0; 861 | } else { 862 | // If the response has no content. 863 | stream->eof = 1; 864 | } 865 | 866 | MAKE_STD_ZVAL(stream->wrapperdata); 867 | MAKE_COPY_ZVAL(&response_header, stream->wrapperdata); 868 | 869 | return stream; 870 | } 871 | 872 | static php_stream_wrapper_ops urlfetch_stdio_wops = { 873 | urlfetch_stream_wrapper, // open/create 874 | NULL, // close 875 | NULL, // stream_stat 876 | NULL, // url_stat 877 | NULL, // dir_opener 878 | "http via urlfetch", // label 879 | NULL, // unlink 880 | NULL, // rename 881 | NULL, // stream_mkdir 882 | NULL // stream_rmdir 883 | }; 884 | 885 | php_stream_wrapper urlfetch_stream_http_wrapper = { 886 | &urlfetch_stdio_wops, // operations a wrapper can perform 887 | NULL, // context for the wrapper 888 | 1 // is_url, so that allow_url_fopen can be checked. 889 | }; 890 | 891 | static const zend_function_entry urlfetch_stream_wrapper_functions[] = { 892 | PHP_FE_END 893 | }; 894 | 895 | static const zend_module_dep urlfetch_stream_wrapper_dep[] = { 896 | ZEND_MOD_END 897 | }; 898 | 899 | static PHP_MINIT_FUNCTION(urlfetch_stream_wrapper) { 900 | // Remove whatever might already be there 901 | php_unregister_url_stream_wrapper(const_cast("http") TSRMLS_CC); 902 | php_unregister_url_stream_wrapper(const_cast("https") TSRMLS_CC); 903 | 904 | // Register our URL fetch stream wrapper 905 | php_register_url_stream_wrapper(const_cast("http"), 906 | &urlfetch_stream_http_wrapper TSRMLS_CC); 907 | php_register_url_stream_wrapper(const_cast("https"), 908 | &urlfetch_stream_http_wrapper TSRMLS_CC); 909 | 910 | return SUCCESS; 911 | } 912 | 913 | static PHP_MSHUTDOWN_FUNCTION(urlfetch_stream_wrapper) { 914 | php_unregister_url_stream_wrapper(const_cast("http") TSRMLS_CC); 915 | php_unregister_url_stream_wrapper(const_cast("https") TSRMLS_CC); 916 | return SUCCESS; 917 | } 918 | 919 | static PHP_RINIT_FUNCTION(urlfetch_stream_wrapper) { 920 | return SUCCESS; 921 | } 922 | 923 | static PHP_RSHUTDOWN_FUNCTION(urlfetch_stream_wrapper) { 924 | return SUCCESS; 925 | } 926 | 927 | zend_module_entry urlfetch_stream_wrapper_module_entry = { 928 | STANDARD_MODULE_HEADER_EX, 929 | NULL, 930 | urlfetch_stream_wrapper_dep, 931 | "urlfetch_stream_wrapper_plugin", 932 | urlfetch_stream_wrapper_functions, 933 | PHP_MINIT(urlfetch_stream_wrapper), 934 | PHP_MSHUTDOWN(urlfetch_stream_wrapper), 935 | PHP_RINIT(urlfetch_stream_wrapper), 936 | PHP_RSHUTDOWN(urlfetch_stream_wrapper), 937 | NULL, 938 | NO_VERSION_YET, 939 | STANDARD_MODULE_PROPERTIES 940 | }; 941 | 942 | } // namespace appengine 943 | -------------------------------------------------------------------------------- /urlfetch_stream_wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 17 | #ifndef URLFETCH_STREAM_WRAPPER_H_ 18 | #define URLFETCH_STREAM_WRAPPER_H_ 19 | 20 | extern "C" { 21 | #ifdef __google_internal__ 22 | #include "php/Zend/zend_modules.h" 23 | #else 24 | #include "Zend/zend_modules.h" 25 | #endif 26 | } 27 | 28 | namespace appengine { 29 | extern zend_module_entry urlfetch_stream_wrapper_module_entry; 30 | } // namespace appengine 31 | 32 | #endif // URLFETCH_STREAM_WRAPPER_H_ 33 | --------------------------------------------------------------------------------