From 10713450924846006076c3c3011c94f2a1cae7f1 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 18:19:06 +0000 Subject: [PATCH 01/11] commit patch 22402292 --- ext/dom/domimplementation.c | 5 + ext/dom/tests/bug79971_2.phpt | 20 + ext/libxml/libxml.c | 9 + ext/libxml/libxml.c.orig | 1353 +++++++++++++++++++++++++++ ext/simplexml/tests/bug79971_1.phpt | 27 + ext/simplexml/tests/bug79971_1.xml | 2 + 6 files changed, 1416 insertions(+) create mode 100644 ext/dom/tests/bug79971_2.phpt create mode 100644 ext/libxml/libxml.c.orig create mode 100644 ext/simplexml/tests/bug79971_1.phpt create mode 100644 ext/simplexml/tests/bug79971_1.xml diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index 05e99af39b9f2..a518f44c08024 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -112,6 +112,11 @@ PHP_METHOD(domimplementation, createDocumentType) pch2 = (xmlChar *) systemid; } + if (strstr(name, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + RETURN_FALSE; + } + uri = xmlParseURI(name); if (uri != NULL && uri->opaque != NULL) { localname = xmlStrdup((xmlChar *) uri->opaque); diff --git a/ext/dom/tests/bug79971_2.phpt b/ext/dom/tests/bug79971_2.phpt new file mode 100644 index 0000000000000..c4e6b1e4e0933 --- /dev/null +++ b/ext/dom/tests/bug79971_2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +createDocumentType("$uri%00foo")); +?> +--EXPECTF-- +Warning: DOMImplementation::createDocumentType(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index c024e1667025c..a4b0355b6b17e 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -303,6 +303,10 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char int isescaped=0; xmlURI *uri; + if (strstr(filename, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } uri = xmlParseURI(filename); if (uri && (uri->scheme == NULL || @@ -434,6 +438,11 @@ php_libxml_output_buffer_create_filename(const char *URI, if (URI == NULL) return(NULL); + if (strstr(URI, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } + puri = xmlParseURI(URI); if (puri != NULL) { if (puri->scheme != NULL) diff --git a/ext/libxml/libxml.c.orig b/ext/libxml/libxml.c.orig new file mode 100644 index 0000000000000..c024e1667025c --- /dev/null +++ b/ext/libxml/libxml.c.orig @@ -0,0 +1,1353 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Shane Caraveo | + | Wez Furlong | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "SAPI.h" + +#include "zend_variables.h" +#include "ext/standard/php_string.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" + +#if HAVE_LIBXML + +#include +#include +#include +#include +#include +#include +#ifdef LIBXML_SCHEMAS_ENABLED +#include +#include +#endif + +#include "php_libxml.h" + +#define PHP_LIBXML_ERROR 0 +#define PHP_LIBXML_CTX_ERROR 1 +#define PHP_LIBXML_CTX_WARNING 2 + +/* a true global for initialization */ +static int _php_libxml_initialized = 0; +static int _php_libxml_per_request_initialization = 1; +static xmlExternalEntityLoader _php_libxml_default_entity_loader; + +typedef struct _php_libxml_func_handler { + php_libxml_export_node export_func; +} php_libxml_func_handler; + +static HashTable php_libxml_exports; + +static ZEND_DECLARE_MODULE_GLOBALS(libxml) +static PHP_GINIT_FUNCTION(libxml); + +static PHP_FUNCTION(libxml_set_streams_context); +static PHP_FUNCTION(libxml_use_internal_errors); +static PHP_FUNCTION(libxml_get_last_error); +static PHP_FUNCTION(libxml_clear_errors); +static PHP_FUNCTION(libxml_get_errors); +static PHP_FUNCTION(libxml_set_external_entity_loader); +static PHP_FUNCTION(libxml_disable_entity_loader); + +static zend_class_entry *libxmlerror_class_entry; + +/* {{{ dynamically loadable module stuff */ +#ifdef COMPILE_DL_LIBXML +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(libxml) +#endif /* COMPILE_DL_LIBXML */ +/* }}} */ + +/* {{{ function prototypes */ +static PHP_MINIT_FUNCTION(libxml); +static PHP_RINIT_FUNCTION(libxml); +static PHP_RSHUTDOWN_FUNCTION(libxml); +static PHP_MSHUTDOWN_FUNCTION(libxml); +static PHP_MINFO_FUNCTION(libxml); +static int php_libxml_post_deactivate(void); + +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_libxml_set_streams_context, 0) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_use_internal_errors, 0, 0, 0) + ZEND_ARG_INFO(0, use_errors) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_last_error, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_clear_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_disable_entity_loader, 0, 0, 0) + ZEND_ARG_INFO(0, disable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_set_external_entity_loader, 0, 0, 1) + ZEND_ARG_INFO(0, resolver_function) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ extension definition structures */ +static const zend_function_entry libxml_functions[] = { + PHP_FE(libxml_set_streams_context, arginfo_libxml_set_streams_context) + PHP_FE(libxml_use_internal_errors, arginfo_libxml_use_internal_errors) + PHP_FE(libxml_get_last_error, arginfo_libxml_get_last_error) + PHP_FE(libxml_clear_errors, arginfo_libxml_clear_errors) + PHP_FE(libxml_get_errors, arginfo_libxml_get_errors) + PHP_FE(libxml_disable_entity_loader, arginfo_libxml_disable_entity_loader) + PHP_FE(libxml_set_external_entity_loader, arginfo_libxml_set_external_entity_loader) + PHP_FE_END +}; + +zend_module_entry libxml_module_entry = { + STANDARD_MODULE_HEADER, + "libxml", /* extension name */ + libxml_functions, /* extension function list */ + PHP_MINIT(libxml), /* extension-wide startup function */ + PHP_MSHUTDOWN(libxml), /* extension-wide shutdown function */ + PHP_RINIT(libxml), /* per-request startup function */ + PHP_RSHUTDOWN(libxml), /* per-request shutdown function */ + PHP_MINFO(libxml), /* information function */ + PHP_LIBXML_VERSION, + PHP_MODULE_GLOBALS(libxml), /* globals descriptor */ + PHP_GINIT(libxml), /* globals ctor */ + NULL, /* globals dtor */ + php_libxml_post_deactivate, /* post deactivate */ + STANDARD_MODULE_PROPERTIES_EX +}; + +/* }}} */ + +/* {{{ internal functions for interoperability */ +static int php_libxml_clear_object(php_libxml_node_object *object) +{ + if (object->properties) { + object->properties = NULL; + } + php_libxml_decrement_node_ptr(object); + return php_libxml_decrement_doc_ref(object); +} + +static int php_libxml_unregister_node(xmlNodePtr nodep) +{ + php_libxml_node_object *wrapper; + + php_libxml_node_ptr *nodeptr = nodep->_private; + + if (nodeptr != NULL) { + wrapper = nodeptr->_private; + if (wrapper) { + php_libxml_clear_object(wrapper); + } else { + if (nodeptr->node != NULL && nodeptr->node->type != XML_DOCUMENT_NODE) { + nodeptr->node->_private = NULL; + } + nodeptr->node = NULL; + } + } + + return -1; +} + +static void php_libxml_node_free(xmlNodePtr node) +{ + if(node) { + if (node->_private != NULL) { + ((php_libxml_node_ptr *) node->_private)->node = NULL; + } + switch (node->type) { + case XML_ATTRIBUTE_NODE: + xmlFreeProp((xmlAttrPtr) node); + break; + case XML_ENTITY_DECL: + case XML_ELEMENT_DECL: + case XML_ATTRIBUTE_DECL: + break; + case XML_NOTATION_NODE: + /* These require special handling */ + if (node->name != NULL) { + xmlFree((char *) node->name); + } + if (((xmlEntityPtr) node)->ExternalID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->ExternalID); + } + if (((xmlEntityPtr) node)->SystemID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->SystemID); + } + xmlFree(node); + break; + case XML_NAMESPACE_DECL: + if (node->ns) { + xmlFreeNs(node->ns); + node->ns = NULL; + } + node->type = XML_ELEMENT_NODE; + default: + xmlFreeNode(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) +{ + xmlNodePtr curnode; + + if (node != NULL) { + curnode = node; + while (curnode != NULL) { + node = curnode; + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_NOTATION_NODE: + case XML_ENTITY_DECL: + break; + case XML_ENTITY_REF_NODE: + php_libxml_node_free_list((xmlNodePtr) node->properties); + break; + case XML_ATTRIBUTE_NODE: + if ((node->doc != NULL) && (((xmlAttrPtr) node)->atype == XML_ATTRIBUTE_ID)) { + xmlRemoveID(node->doc, (xmlAttrPtr) node); + } + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + php_libxml_node_free_list(node->children); + break; + default: + php_libxml_node_free_list(node->children); + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + + curnode = node->next; + xmlUnlinkNode(node); + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } + } +} + +/* }}} */ + +/* {{{ startup, shutdown and info functions */ +static PHP_GINIT_FUNCTION(libxml) +{ +#if defined(COMPILE_DL_LIBXML) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + ZVAL_UNDEF(&libxml_globals->stream_context); + libxml_globals->error_buffer.s = NULL; + libxml_globals->error_list = NULL; + ZVAL_UNDEF(&libxml_globals->entity_loader.object); + libxml_globals->entity_loader.fci.size = 0; + libxml_globals->entity_loader_disabled = 0; +} + +static void _php_libxml_destroy_fci(zend_fcall_info *fci, zval *object) +{ + if (fci->size > 0) { + zval_ptr_dtor(&fci->function_name); + fci->size = 0; + } + if (!Z_ISUNDEF_P(object)) { + zval_ptr_dtor(object); + ZVAL_UNDEF(object); + } +} + +/* Channel libxml file io layer through the PHP streams subsystem. + * This allows use of ftps:// and https:// urls */ + +static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char *mode, const int read_only) +{ + php_stream_statbuf ssbuf; + php_stream_context *context = NULL; + php_stream_wrapper *wrapper = NULL; + char *resolved_path; + const char *path_to_open = NULL; + void *ret_val = NULL; + int isescaped=0; + xmlURI *uri; + + + uri = xmlParseURI(filename); + if (uri && (uri->scheme == NULL || + (xmlStrncmp(BAD_CAST uri->scheme, BAD_CAST "file", 4) == 0))) { + resolved_path = xmlURIUnescapeString(filename, 0, NULL); + isescaped = 1; +#if LIBXML_VERSION >= 20902 && defined(PHP_WIN32) + /* Libxml 2.9.2 prefixes local paths with file:/ instead of file://, + thus the php stream wrapper will fail on a valid case. For this + reason the prefix is rather better cut off. */ + { + size_t pre_len = sizeof("file:/") - 1; + + if (strncasecmp(resolved_path, "file:/", pre_len) == 0 + && '/' != resolved_path[pre_len]) { + xmlChar *tmp = xmlStrdup(resolved_path + pre_len); + xmlFree(resolved_path); + resolved_path = tmp; + } + } +#endif + } else { + resolved_path = (char *)filename; + } + + if (uri) { + xmlFreeURI(uri); + } + + if (resolved_path == NULL) { + return NULL; + } + + /* logic copied from _php_stream_stat, but we only want to fail + if the wrapper supports stat, otherwise, figure it out from + the open. This logic is only to support hiding warnings + that the streams layer puts out at times, but for libxml we + may try to open files that don't exist, but it is not a failure + in xml processing (eg. DTD files) */ + wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, 0); + if (wrapper && read_only && wrapper->wops->url_stat) { + if (wrapper->wops->url_stat(wrapper, path_to_open, PHP_STREAM_URL_STAT_QUIET, &ssbuf, NULL) == -1) { + if (isescaped) { + xmlFree(resolved_path); + } + return NULL; + } + } + + context = php_stream_context_from_zval(Z_ISUNDEF(LIBXML(stream_context))? NULL : &LIBXML(stream_context), 0); + + ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, REPORT_ERRORS, NULL, context); + if (ret_val) { + /* Prevent from closing this by fclose() */ + ((php_stream*)ret_val)->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + } + if (isescaped) { + xmlFree(resolved_path); + } + return ret_val; +} + +static void *php_libxml_streams_IO_open_read_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "rb", 1); +} + +static void *php_libxml_streams_IO_open_write_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "wb", 0); +} + +static int php_libxml_streams_IO_read(void *context, char *buffer, int len) +{ + return php_stream_read((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_write(void *context, const char *buffer, int len) +{ + return php_stream_write((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_close(void *context) +{ + return php_stream_close((php_stream*)context); +} + +static xmlParserInputBufferPtr +php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) +{ + xmlParserInputBufferPtr ret; + void *context = NULL; + + if (LIBXML(entity_loader_disabled)) { + return NULL; + } + + if (URI == NULL) + return(NULL); + + context = php_libxml_streams_IO_open_read_wrapper(URI); + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Input buffer front-end. */ + ret = xmlAllocParserInputBuffer(enc); + if (ret != NULL) { + ret->context = context; + ret->readcallback = php_libxml_streams_IO_read; + ret->closecallback = php_libxml_streams_IO_close; + } else + php_libxml_streams_IO_close(context); + + return(ret); +} + +static xmlOutputBufferPtr +php_libxml_output_buffer_create_filename(const char *URI, + xmlCharEncodingHandlerPtr encoder, + int compression ATTRIBUTE_UNUSED) +{ + xmlOutputBufferPtr ret; + xmlURIPtr puri; + void *context = NULL; + char *unescaped = NULL; + + if (URI == NULL) + return(NULL); + + puri = xmlParseURI(URI); + if (puri != NULL) { + if (puri->scheme != NULL) + unescaped = xmlURIUnescapeString(URI, 0, NULL); + xmlFreeURI(puri); + } + + if (unescaped != NULL) { + context = php_libxml_streams_IO_open_write_wrapper(unescaped); + xmlFree(unescaped); + } + + /* try with a non-escaped URI this may be a strange filename */ + if (context == NULL) { + context = php_libxml_streams_IO_open_write_wrapper(URI); + } + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Output buffer front-end. */ + ret = xmlAllocOutputBuffer(encoder); + if (ret != NULL) { + ret->context = context; + ret->writecallback = php_libxml_streams_IO_write; + ret->closecallback = php_libxml_streams_IO_close; + } + + return(ret); +} + +static int _php_libxml_free_error(xmlErrorPtr error) +{ + /* This will free the libxml alloc'd memory */ + xmlResetError(error); + return 1; +} + +static void _php_list_set_error_structure(xmlErrorPtr error, const char *msg) +{ + xmlError error_copy; + int ret; + + + memset(&error_copy, 0, sizeof(xmlError)); + + if (error) { + ret = xmlCopyError(error, &error_copy); + } else { + error_copy.domain = 0; + error_copy.code = XML_ERR_INTERNAL_ERROR; + error_copy.level = XML_ERR_ERROR; + error_copy.line = 0; + error_copy.node = NULL; + error_copy.int1 = 0; + error_copy.int2 = 0; + error_copy.ctxt = NULL; + error_copy.message = (char*)xmlStrdup((xmlChar*)msg); + error_copy.file = NULL; + error_copy.str1 = NULL; + error_copy.str2 = NULL; + error_copy.str3 = NULL; + ret = 0; + } + + if (ret == 0) { + zend_llist_add_element(LIBXML(error_list), &error_copy); + } +} + +static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) +{ + xmlParserCtxtPtr parser; + + parser = (xmlParserCtxtPtr) ctx; + + if (parser != NULL && parser->input != NULL) { + if (parser->input->filename) { + php_error_docref(NULL, level, "%s in %s, line: %d", msg, parser->input->filename, parser->input->line); + } else { + php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); + } + } +} + +void php_libxml_issue_error(int level, const char *msg) +{ + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, msg); + } else { + php_error_docref(NULL, level, "%s", msg); + } +} + +static void php_libxml_internal_error_handler(int error_type, void *ctx, const char **msg, va_list ap) +{ + char *buf; + int len, len_iter, output = 0; + + + len = vspprintf(&buf, 0, *msg, ap); + len_iter = len; + + /* remove any trailing \n */ + while (len_iter && buf[--len_iter] == '\n') { + buf[len_iter] = '\0'; + output = 1; + } + + smart_str_appendl(&LIBXML(error_buffer), buf, len); + + efree(buf); + + if (output == 1) { + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, ZSTR_VAL(LIBXML(error_buffer).s)); + } else { + switch (error_type) { + case PHP_LIBXML_CTX_ERROR: + php_libxml_ctx_error_level(E_WARNING, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + case PHP_LIBXML_CTX_WARNING: + php_libxml_ctx_error_level(E_NOTICE, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + default: + php_error_docref(NULL, E_WARNING, "%s", ZSTR_VAL(LIBXML(error_buffer).s)); + } + } + smart_str_free(&LIBXML(error_buffer)); + } +} + +static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + xmlParserInputPtr ret = NULL; + const char *resource = NULL; + zval *ctxzv, retval; + zval params[3]; + int status; + zend_fcall_info *fci; + + fci = &LIBXML(entity_loader).fci; + + if (fci->size == 0) { + /* no custom user-land callback set up; delegate to original loader */ + return _php_libxml_default_entity_loader(URL, ID, context); + } + + if (ID != NULL) { + ZVAL_STRING(¶ms[0], ID); + } else { + ZVAL_NULL(¶ms[0]); + } + if (URL != NULL) { + ZVAL_STRING(¶ms[1], URL); + } else { + ZVAL_NULL(¶ms[1]); + } + ctxzv = ¶ms[2]; + array_init_size(ctxzv, 4); + +#define ADD_NULL_OR_STRING_KEY(memb) \ + if (context->memb == NULL) { \ + add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \ + } else { \ + add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \ + (char *)context->memb); \ + } + + ADD_NULL_OR_STRING_KEY(directory) + ADD_NULL_OR_STRING_KEY(intSubName) + ADD_NULL_OR_STRING_KEY(extSubURI) + ADD_NULL_OR_STRING_KEY(extSubSystem) + +#undef ADD_NULL_OR_STRING_KEY + + fci->retval = &retval; + fci->params = params; + fci->param_count = sizeof(params)/sizeof(*params); + fci->no_separation = 1; + + status = zend_call_function(fci, &LIBXML(entity_loader).fcc); + if (status != SUCCESS || Z_ISUNDEF(retval)) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed", + Z_STRVAL(fci->function_name)); + } else { + /* + retval_ptr = *fci->retval_ptr_ptr; + if (retval_ptr == NULL) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed; " + "probably it has thrown an exception", + fci->function_name); + } else */ if (Z_TYPE(retval) == IS_STRING) { +is_string: + resource = Z_STRVAL(retval); + } else if (Z_TYPE(retval) == IS_RESOURCE) { + php_stream *stream; + php_stream_from_zval_no_verify(stream, &retval); + if (stream == NULL) { + php_libxml_ctx_error(context, + "The user entity loader callback '%s' has returned a " + "resource, but it is not a stream", + Z_STRVAL(fci->function_name)); + } else { + /* TODO: allow storing the encoding in the stream context? */ + xmlCharEncoding enc = XML_CHAR_ENCODING_NONE; + xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc); + if (pib == NULL) { + php_libxml_ctx_error(context, "Could not allocate parser " + "input buffer"); + } else { + /* make stream not being closed when the zval is freed */ + GC_ADDREF(stream->res); + pib->context = stream; + pib->readcallback = php_libxml_streams_IO_read; + pib->closecallback = php_libxml_streams_IO_close; + + ret = xmlNewIOInputStream(context, pib, enc); + if (ret == NULL) { + xmlFreeParserInputBuffer(pib); + } + } + } + } else if (Z_TYPE(retval) != IS_NULL) { + /* retval not string nor resource nor null; convert to string */ + if (try_convert_to_string(&retval)) { + goto is_string; + } + } /* else is null; don't try anything */ + } + + if (ret == NULL) { + if (resource == NULL) { + if (ID == NULL) { + ID = "NULL"; + } + php_libxml_ctx_error(context, + "Failed to load external entity \"%s\"\n", ID); + } else { + /* we got the resource in the form of a string; open it */ + ret = xmlNewInputFromFile(context, resource); + } + } + + zval_ptr_dtor(¶ms[0]); + zval_ptr_dtor(¶ms[1]); + zval_ptr_dtor(¶ms[2]); + zval_ptr_dtor(&retval); + return ret; +} + +static xmlParserInputPtr _php_libxml_pre_ext_ent_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + + /* Check whether we're running in a PHP context, since the entity loader + * we've defined is an application level (true global) setting. + * If we are, we also want to check whether we've finished activating + * the modules (RINIT phase). Using our external entity loader during a + * RINIT should not be problem per se (though during MINIT it is, because + * we don't even have a resource list by then), but then whether one + * extension would be using the custom external entity loader or not + * could depend on extension loading order + * (if _php_libxml_per_request_initialization */ + if (xmlGenericError == php_libxml_error_handler && PG(modules_activated)) { + return _php_libxml_external_entity_loader(URL, ID, context); + } else { + return _php_libxml_default_entity_loader(URL, ID, context); + } +} + +PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_ERROR, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_WARNING, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error) +{ + _php_list_set_error_structure(error, NULL); + + return; +} + +PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_ERROR, ctx, &msg, args); + va_end(args); +} + +static void php_libxml_exports_dtor(zval *zv) +{ + free(Z_PTR_P(zv)); +} + +PHP_LIBXML_API void php_libxml_initialize(void) +{ + if (!_php_libxml_initialized) { + /* we should be the only one's to ever init!! */ + ZEND_IGNORE_LEAKS_BEGIN(); + xmlInitParser(); + ZEND_IGNORE_LEAKS_END(); + + _php_libxml_default_entity_loader = xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(_php_libxml_pre_ext_ent_loader); + + zend_hash_init(&php_libxml_exports, 0, NULL, php_libxml_exports_dtor, 1); + + _php_libxml_initialized = 1; + } +} + +PHP_LIBXML_API void php_libxml_shutdown(void) +{ + if (_php_libxml_initialized) { +#if defined(LIBXML_SCHEMAS_ENABLED) + xmlRelaxNGCleanupTypes(); +#endif + /* xmlCleanupParser(); */ + zend_hash_destroy(&php_libxml_exports); + + xmlSetExternalEntityLoader(_php_libxml_default_entity_loader); + _php_libxml_initialized = 0; + } +} + +PHP_LIBXML_API void php_libxml_switch_context(zval *context, zval *oldcontext) +{ + if (oldcontext) { + ZVAL_COPY_VALUE(oldcontext, &LIBXML(stream_context)); + } + if (context) { + ZVAL_COPY_VALUE(&LIBXML(stream_context), context); + } +} + +static PHP_MINIT_FUNCTION(libxml) +{ + zend_class_entry ce; + + php_libxml_initialize(); + + REGISTER_LONG_CONSTANT("LIBXML_VERSION", LIBXML_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_DOTTED_VERSION", LIBXML_DOTTED_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_LOADED_VERSION", (char *)xmlParserVersion, CONST_CS | CONST_PERSISTENT); + + /* For use with loading xml */ + REGISTER_LONG_CONSTANT("LIBXML_NOENT", XML_PARSE_NOENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDLOAD", XML_PARSE_DTDLOAD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDATTR", XML_PARSE_DTDATTR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDVALID", XML_PARSE_DTDVALID, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOERROR", XML_PARSE_NOERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOWARNING", XML_PARSE_NOWARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOBLANKS", XML_PARSE_NOBLANKS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_XINCLUDE", XML_PARSE_XINCLUDE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NSCLEAN", XML_PARSE_NSCLEAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOCDATA", XML_PARSE_NOCDATA, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NONET", XML_PARSE_NONET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_PEDANTIC", XML_PARSE_PEDANTIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_COMPACT", XML_PARSE_COMPACT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOXMLDECL", XML_SAVE_NO_DECL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_PARSEHUGE", XML_PARSE_HUGE, CONST_CS | CONST_PERSISTENT); +#if LIBXML_VERSION >= 20900 + REGISTER_LONG_CONSTANT("LIBXML_BIGLINES", XML_PARSE_BIG_LINES, CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("LIBXML_NOEMPTYTAG", LIBXML_SAVE_NOEMPTYTAG, CONST_CS | CONST_PERSISTENT); + + /* Schema validation options */ +#if defined(LIBXML_SCHEMAS_ENABLED) + REGISTER_LONG_CONSTANT("LIBXML_SCHEMA_CREATE", XML_SCHEMA_VAL_VC_I_CREATE, CONST_CS | CONST_PERSISTENT); +#endif + + /* Additional constants for use with loading html */ +#if LIBXML_VERSION >= 20707 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NOIMPLIED", HTML_PARSE_NOIMPLIED, CONST_CS | CONST_PERSISTENT); +#endif + +#if LIBXML_VERSION >= 20708 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NODEFDTD", HTML_PARSE_NODEFDTD, CONST_CS | CONST_PERSISTENT); +#endif + + /* Error levels */ + REGISTER_LONG_CONSTANT("LIBXML_ERR_NONE", XML_ERR_NONE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_WARNING", XML_ERR_WARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_ERROR", XML_ERR_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_FATAL", XML_ERR_FATAL, CONST_CS | CONST_PERSISTENT); + + INIT_CLASS_ENTRY(ce, "LibXMLError", NULL); + libxmlerror_class_entry = zend_register_internal_class(&ce); + + if (sapi_module.name) { + static const char * const supported_sapis[] = { + "cgi-fcgi", + "litespeed", + NULL + }; + const char * const *sapi_name; + + for (sapi_name = supported_sapis; *sapi_name; sapi_name++) { + if (strcmp(sapi_module.name, *sapi_name) == 0) { + _php_libxml_per_request_initialization = 0; + break; + } + } + } + + if (!_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + } + + return SUCCESS; +} + + +static PHP_RINIT_FUNCTION(libxml) +{ + if (_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + } + + /* Enable the entity loader by default. This ensures that + * other threads/requests that might have disabled the loader + * do not affect the current request. + */ + LIBXML(entity_loader_disabled) = 0; + + return SUCCESS; +} + +static PHP_RSHUTDOWN_FUNCTION(libxml) +{ + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(libxml) +{ + if (!_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + php_libxml_shutdown(); + + return SUCCESS; +} + +static int php_libxml_post_deactivate(void) +{ + /* reset libxml generic error handling */ + if (_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + xmlSetStructuredErrorFunc(NULL, NULL); + + /* the steam_context resource will be released by resource list destructor */ + ZVAL_UNDEF(&LIBXML(stream_context)); + smart_str_free(&LIBXML(error_buffer)); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + xmlResetLastError(); + + return SUCCESS; +} + + +static PHP_MINFO_FUNCTION(libxml) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "libXML support", "active"); + php_info_print_table_row(2, "libXML Compiled Version", LIBXML_DOTTED_VERSION); + php_info_print_table_row(2, "libXML Loaded Version", (char *)xmlParserVersion); + php_info_print_table_row(2, "libXML streams", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ proto void libxml_set_streams_context(resource streams_context) + Set the streams context for the next libxml document load or write */ +static PHP_FUNCTION(libxml_set_streams_context) +{ + zval *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_RESOURCE(arg) + ZEND_PARSE_PARAMETERS_END(); + + if (!Z_ISUNDEF(LIBXML(stream_context))) { + zval_ptr_dtor(&LIBXML(stream_context)); + ZVAL_UNDEF(&LIBXML(stream_context)); + } + ZVAL_COPY(&LIBXML(stream_context), arg); +} +/* }}} */ + +/* {{{ proto bool libxml_use_internal_errors([boolean use_errors]) + Disable libxml errors and allow user to fetch error information as needed */ +static PHP_FUNCTION(libxml_use_internal_errors) +{ + xmlStructuredErrorFunc current_handler; + zend_bool use_errors=0, retval; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(use_errors) + ZEND_PARSE_PARAMETERS_END(); + + current_handler = xmlStructuredError; + if (current_handler && current_handler == php_libxml_structured_error_handler) { + retval = 1; + } else { + retval = 0; + } + + if (ZEND_NUM_ARGS() == 0) { + RETURN_BOOL(retval); + } + + if (use_errors == 0) { + xmlSetStructuredErrorFunc(NULL, NULL); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + } else { + xmlSetStructuredErrorFunc(NULL, php_libxml_structured_error_handler); + if (LIBXML(error_list) == NULL) { + LIBXML(error_list) = (zend_llist *) emalloc(sizeof(zend_llist)); + zend_llist_init(LIBXML(error_list), sizeof(xmlError), (llist_dtor_func_t) _php_libxml_free_error, 0); + } + } + RETURN_BOOL(retval); +} +/* }}} */ + +/* {{{ proto object libxml_get_last_error() + Retrieve last error from libxml */ +static PHP_FUNCTION(libxml_get_last_error) +{ + xmlErrorPtr error; + + error = xmlGetLastError(); + + if (error) { + object_init_ex(return_value, libxmlerror_class_entry); + add_property_long(return_value, "level", error->level); + add_property_long(return_value, "code", error->code); + add_property_long(return_value, "column", error->int2); + if (error->message) { + add_property_string(return_value, "message", error->message); + } else { + add_property_stringl(return_value, "message", "", 0); + } + if (error->file) { + add_property_string(return_value, "file", error->file); + } else { + add_property_stringl(return_value, "file", "", 0); + } + add_property_long(return_value, "line", error->line); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object libxml_get_errors() + Retrieve array of errors */ +static PHP_FUNCTION(libxml_get_errors) +{ + + xmlErrorPtr error; + + if (LIBXML(error_list)) { + + array_init(return_value); + error = zend_llist_get_first(LIBXML(error_list)); + + while (error != NULL) { + zval z_error; + + object_init_ex(&z_error, libxmlerror_class_entry); + add_property_long_ex(&z_error, "level", sizeof("level") - 1, error->level); + add_property_long_ex(&z_error, "code", sizeof("code") - 1, error->code); + add_property_long_ex(&z_error, "column", sizeof("column") - 1, error->int2 ); + if (error->message) { + add_property_string_ex(&z_error, "message", sizeof("message") - 1, error->message); + } else { + add_property_stringl_ex(&z_error, "message", sizeof("message") - 1, "", 0); + } + if (error->file) { + add_property_string_ex(&z_error, "file", sizeof("file") - 1, error->file); + } else { + add_property_stringl_ex(&z_error, "file", sizeof("file") - 1, "", 0); + } + add_property_long_ex(&z_error, "line", sizeof("line") - 1, error->line); + add_next_index_zval(return_value, &z_error); + + error = zend_llist_get_next(LIBXML(error_list)); + } + } else { + RETURN_EMPTY_ARRAY(); + } +} +/* }}} */ + +/* {{{ proto void libxml_clear_errors() + Clear last error from libxml */ +static PHP_FUNCTION(libxml_clear_errors) +{ + xmlResetLastError(); + if (LIBXML(error_list)) { + zend_llist_clean(LIBXML(error_list)); + } +} +/* }}} */ + +PHP_LIBXML_API zend_bool php_libxml_disable_entity_loader(zend_bool disable) /* {{{ */ +{ + zend_bool old = LIBXML(entity_loader_disabled); + + LIBXML(entity_loader_disabled) = disable; + return old; +} /* }}} */ + +/* {{{ proto bool libxml_disable_entity_loader([boolean disable]) + Disable/Enable ability to load external entities */ +static PHP_FUNCTION(libxml_disable_entity_loader) +{ + zend_bool disable = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(disable) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(php_libxml_disable_entity_loader(disable)); +} +/* }}} */ + +/* {{{ proto void libxml_set_external_entity_loader(callback resolver_function) + Changes the default external entity loader */ +static PHP_FUNCTION(libxml_set_external_entity_loader) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC_EX(fci, fcc, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + if (fci.size > 0) { /* argument not null */ + LIBXML(entity_loader).fci = fci; + Z_ADDREF(fci.function_name); + if (fci.object != NULL) { + ZVAL_OBJ(&LIBXML(entity_loader).object, fci.object); + Z_ADDREF(LIBXML(entity_loader).object); + } + LIBXML(entity_loader).fcc = fcc; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Common functions shared by extensions */ +int php_libxml_xmlCheckUTF8(const unsigned char *s) +{ + int i; + unsigned char c; + + for (i = 0; (c = s[i++]);) { + if ((c & 0x80) == 0) { + } else if ((c & 0xe0) == 0xc0) { + if ((s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf0) == 0xe0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf8) == 0xf0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else { + return 0; + } + } + return 1; +} + +zval *php_libxml_register_export(zend_class_entry *ce, php_libxml_export_node export_function) +{ + php_libxml_func_handler export_hnd; + + /* Initialize in case this module hasn't been loaded yet */ + php_libxml_initialize(); + export_hnd.export_func = export_function; + + return zend_hash_add_mem(&php_libxml_exports, ce->name, &export_hnd, sizeof(export_hnd)); +} + +PHP_LIBXML_API xmlNodePtr php_libxml_import_node(zval *object) +{ + zend_class_entry *ce = NULL; + xmlNodePtr node = NULL; + php_libxml_func_handler *export_hnd; + + if (Z_TYPE_P(object) == IS_OBJECT) { + ce = Z_OBJCE_P(object); + while (ce->parent != NULL) { + ce = ce->parent; + } + if ((export_hnd = zend_hash_find_ptr(&php_libxml_exports, ce->name))) { + node = export_hnd->export_func(object); + } + } + return node; +} + +PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data) +{ + int ret_refcount = -1; + + if (object != NULL && node != NULL) { + if (object->node != NULL) { + if (object->node->node == node) { + return object->node->refcount; + } else { + php_libxml_decrement_node_ptr(object); + } + } + if (node->_private != NULL) { + object->node = node->_private; + ret_refcount = ++object->node->refcount; + /* Only dom uses _private */ + if (object->node->_private == NULL) { + object->node->_private = private_data; + } + } else { + ret_refcount = 1; + object->node = emalloc(sizeof(php_libxml_node_ptr)); + object->node->node = node; + object->node->refcount = 1; + object->node->_private = private_data; + node->_private = object->node; + } + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object) +{ + int ret_refcount = -1; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + ret_refcount = --obj_node->refcount; + if (ret_refcount == 0) { + if (obj_node->node != NULL) { + obj_node->node->_private = NULL; + } + efree(obj_node); + } + object->node = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp) +{ + int ret_refcount = -1; + + if (object->document != NULL) { + object->document->refcount++; + ret_refcount = object->document->refcount; + } else if (docp != NULL) { + ret_refcount = 1; + object->document = emalloc(sizeof(php_libxml_ref_obj)); + object->document->ptr = docp; + object->document->refcount = ret_refcount; + object->document->doc_props = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object) +{ + int ret_refcount = -1; + + if (object != NULL && object->document != NULL) { + ret_refcount = --object->document->refcount; + if (ret_refcount == 0) { + if (object->document->ptr != NULL) { + xmlFreeDoc((xmlDoc *) object->document->ptr); + } + if (object->document->doc_props != NULL) { + if (object->document->doc_props->classmap) { + zend_hash_destroy(object->document->doc_props->classmap); + FREE_HASHTABLE(object->document->doc_props->classmap); + } + efree(object->document->doc_props); + } + efree(object->document); + } + object->document = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API void php_libxml_node_free_resource(xmlNodePtr node) +{ + if (!node) { + return; + } + + switch (node->type) { + case XML_DOCUMENT_NODE: + case XML_HTML_DOCUMENT_NODE: + break; + default: + if (node->parent == NULL || node->type == XML_NAMESPACE_DECL) { + php_libxml_node_free_list((xmlNodePtr) node->children); + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_ENTITY_DECL: + case XML_ATTRIBUTE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + break; + default: + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } else { + php_libxml_unregister_node(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_decrement_resource(php_libxml_node_object *object) +{ + int ret_refcount = -1; + xmlNodePtr nodep; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + nodep = object->node->node; + ret_refcount = php_libxml_decrement_node_ptr(object); + if (ret_refcount == 0) { + php_libxml_node_free_resource(nodep); + } else { + if (obj_node && object == obj_node->_private) { + obj_node->_private = NULL; + } + } + } + if (object != NULL && object->document != NULL) { + /* Safe to call as if the resource were freed then doc pointer is NULL */ + php_libxml_decrement_doc_ref(object); + } +} +/* }}} */ + +#if defined(PHP_WIN32) && defined(COMPILE_DL_LIBXML) +PHP_LIBXML_API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return xmlDllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +#endif diff --git a/ext/simplexml/tests/bug79971_1.phpt b/ext/simplexml/tests/bug79971_1.phpt new file mode 100644 index 0000000000000..197776d82d38d --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +asXML("$uri.out%00foo")); +?> +--EXPECTF-- +Warning: simplexml_load_file(): URI must not contain percent-encoded NUL bytes in %s on line %d + +Warning: simplexml_load_file(): I/O warning : failed to load external entity "%s/bug79971_1.xml%00foo" in %s on line %d +bool(false) + +Warning: SimpleXMLElement::asXML(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/simplexml/tests/bug79971_1.xml b/ext/simplexml/tests/bug79971_1.xml new file mode 100644 index 0000000000000..912bb76d9d7e2 --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.xml @@ -0,0 +1,2 @@ + + From dece8604547460c36d96dfa4949c97cd87bff4bb Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 18:19:08 +0000 Subject: [PATCH 02/11] commit patch 24281011 --- ext/openssl/openssl.c | 10 +- ext/openssl/openssl.c.orig | 6992 ++++++++++++++++++++ ext/openssl/tests/cipher_tests.inc | 21 + ext/openssl/tests/openssl_decrypt_ccm.phpt | 22 +- ext/openssl/tests/openssl_encrypt_ccm.phpt | 26 +- 5 files changed, 7049 insertions(+), 22 deletions(-) create mode 100644 ext/openssl/openssl.c.orig diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 04cb9b0f23130..fdad2c3bc68b5 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -6521,11 +6521,6 @@ static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_requir { char *iv_new; - /* Best case scenario, user behaved */ - if (*piv_len == iv_required_len) { - return SUCCESS; - } - if (mode->is_aead) { if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); @@ -6534,6 +6529,11 @@ static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_requir return SUCCESS; } + /* Best case scenario, user behaved */ + if (*piv_len == iv_required_len) { + return SUCCESS; + } + iv_new = ecalloc(1, iv_required_len + 1); if (*piv_len == 0) { diff --git a/ext/openssl/openssl.c.orig b/ext/openssl/openssl.c.orig new file mode 100644 index 0000000000000..04cb9b0f23130 --- /dev/null +++ b/ext/openssl/openssl.c.orig @@ -0,0 +1,6992 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Stig Venaas | + | Wez Furlong | + | Sascha Kettler | + | Pierre-Alain Joye | + | Marc Delling (PKCS12 functions) | + | Jakub Zelenka | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "php_openssl.h" +#include "zend_exceptions.h" + +/* PHP Includes */ +#include "ext/standard/file.h" +#include "ext/standard/info.h" +#include "ext/standard/php_fopen_wrappers.h" +#include "ext/standard/md5.h" +#include "ext/standard/base64.h" +#ifdef PHP_WIN32 +# include "win32/winutil.h" +#endif + +/* OpenSSL includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Common */ +#include + +#if (defined(PHP_WIN32) && defined(_MSC_VER) && _MSC_VER >= 1900) +#define timezone _timezone /* timezone is called _timezone in LibC */ +#endif + +#define MIN_KEY_LENGTH 384 + +#define OPENSSL_ALGO_SHA1 1 +#define OPENSSL_ALGO_MD5 2 +#define OPENSSL_ALGO_MD4 3 +#ifdef HAVE_OPENSSL_MD2_H +#define OPENSSL_ALGO_MD2 4 +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 +#define OPENSSL_ALGO_DSS1 5 +#endif +#define OPENSSL_ALGO_SHA224 6 +#define OPENSSL_ALGO_SHA256 7 +#define OPENSSL_ALGO_SHA384 8 +#define OPENSSL_ALGO_SHA512 9 +#define OPENSSL_ALGO_RMD160 10 +#define DEBUG_SMIME 0 + +#if !defined(OPENSSL_NO_EC) && defined(EVP_PKEY_EC) +#define HAVE_EVP_PKEY_EC 1 +#endif + +ZEND_DECLARE_MODULE_GLOBALS(openssl) + +/* FIXME: Use the openssl constants instead of + * enum. It is now impossible to match real values + * against php constants. Also sorry to break the + * enum principles here, BC... + */ +enum php_openssl_key_type { + OPENSSL_KEYTYPE_RSA, + OPENSSL_KEYTYPE_DSA, + OPENSSL_KEYTYPE_DH, + OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA, +#ifdef HAVE_EVP_PKEY_EC + OPENSSL_KEYTYPE_EC = OPENSSL_KEYTYPE_DH +1 +#endif +}; + +enum php_openssl_cipher_type { + PHP_OPENSSL_CIPHER_RC2_40, + PHP_OPENSSL_CIPHER_RC2_128, + PHP_OPENSSL_CIPHER_RC2_64, + PHP_OPENSSL_CIPHER_DES, + PHP_OPENSSL_CIPHER_3DES, + PHP_OPENSSL_CIPHER_AES_128_CBC, + PHP_OPENSSL_CIPHER_AES_192_CBC, + PHP_OPENSSL_CIPHER_AES_256_CBC, + + PHP_OPENSSL_CIPHER_DEFAULT = PHP_OPENSSL_CIPHER_RC2_40 +}; + +PHP_FUNCTION(openssl_get_md_methods); +PHP_FUNCTION(openssl_get_cipher_methods); +#ifdef HAVE_EVP_PKEY_EC +PHP_FUNCTION(openssl_get_curve_names); +#endif + +PHP_FUNCTION(openssl_digest); +PHP_FUNCTION(openssl_encrypt); +PHP_FUNCTION(openssl_decrypt); +PHP_FUNCTION(openssl_cipher_iv_length); + +PHP_FUNCTION(openssl_dh_compute_key); +PHP_FUNCTION(openssl_pkey_derive); +PHP_FUNCTION(openssl_random_pseudo_bytes); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export, 0, 0, 2) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_fingerprint, 0, 0, 1) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, raw_output) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_check_private_key, 0) + ZEND_ARG_INFO(0, cert) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_verify, 0) + ZEND_ARG_INFO(0, cert) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_parse, 0, 0, 1) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, shortname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_checkpurpose, 0, 0, 2) + ZEND_ARG_INFO(0, x509cert) + ZEND_ARG_INFO(0, purpose) + ZEND_ARG_INFO(0, cainfo) /* array */ + ZEND_ARG_INFO(0, untrustedfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_read, 0) + ZEND_ARG_INFO(0, cert) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_free, 0) + ZEND_ARG_INFO(0, x509) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs12_export_to_file, 0, 0, 4) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, pass) + ZEND_ARG_INFO(0, args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs12_export, 0, 0, 4) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, pass) + ZEND_ARG_INFO(0, args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkcs12_read, 0) + ZEND_ARG_INFO(0, PKCS12) + ZEND_ARG_INFO(1, certs) /* array */ + ZEND_ARG_INFO(0, pass) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_export, 0, 0, 2) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_sign, 0, 0, 4) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, days) + ZEND_ARG_INFO(0, config_args) /* array */ + ZEND_ARG_INFO(0, serial) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_new, 0, 0, 2) + ZEND_ARG_INFO(0, dn) /* array */ + ZEND_ARG_INFO(1, privkey) + ZEND_ARG_INFO(0, configargs) + ZEND_ARG_INFO(0, extraattribs) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_get_subject, 0, 0, 1) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, use_shortnames) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_get_public_key, 0, 0, 1) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, use_shortnames) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_new, 0, 0, 0) + ZEND_ARG_INFO(0, configargs) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, passphrase) + ZEND_ARG_INFO(0, config_args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_export, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, passphrase) + ZEND_ARG_INFO(0, config_args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_get_public, 0) + ZEND_ARG_INFO(0, cert) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_free, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_get_private, 0, 0, 1) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, passphrase) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_get_details, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pbkdf2, 0, 0, 4) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, salt) + ZEND_ARG_INFO(0, key_length) + ZEND_ARG_INFO(0, iterations) + ZEND_ARG_INFO(0, digest_algorithm) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_verify, 0, 0, 2) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, signerscerts) + ZEND_ARG_INFO(0, cainfo) /* array */ + ZEND_ARG_INFO(0, extracerts) + ZEND_ARG_INFO(0, content) + ZEND_ARG_INFO(0, pk7) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_encrypt, 0, 0, 4) + ZEND_ARG_INFO(0, infile) + ZEND_ARG_INFO(0, outfile) + ZEND_ARG_INFO(0, recipcerts) + ZEND_ARG_INFO(0, headers) /* array */ + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, cipher) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_sign, 0, 0, 5) + ZEND_ARG_INFO(0, infile) + ZEND_ARG_INFO(0, outfile) + ZEND_ARG_INFO(0, signcert) + ZEND_ARG_INFO(0, signkey) + ZEND_ARG_INFO(0, headers) /* array */ + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, extracertsfilename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, infilename) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, recipcert) + ZEND_ARG_INFO(0, recipkey) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_read, 0, 0, 2) + ZEND_ARG_INFO(0, infilename) + ZEND_ARG_INFO(1, certs) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_private_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_private_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_public_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_public_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_error_string, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_sign, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, signature) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_verify, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, signature) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_seal, 0, 0, 4) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, sealdata) + ZEND_ARG_INFO(1, ekeys) /* array */ + ZEND_ARG_INFO(0, pubkeys) /* array */ + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(1, iv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_open, 0, 0, 4) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, opendata) + ZEND_ARG_INFO(0, ekey) + ZEND_ARG_INFO(0, privkey) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, iv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_md_methods, 0, 0, 0) + ZEND_ARG_INFO(0, aliases) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_cipher_methods, 0, 0, 0) + ZEND_ARG_INFO(0, aliases) +ZEND_END_ARG_INFO() + +#ifdef HAVE_EVP_PKEY_EC +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_curve_names, 0, 0, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_digest, 0, 0, 2) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, raw_output) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, iv) + ZEND_ARG_INFO(1, tag) + ZEND_ARG_INFO(0, aad) + ZEND_ARG_INFO(0, tag_length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, iv) + ZEND_ARG_INFO(0, tag) + ZEND_ARG_INFO(0, aad) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_cipher_iv_length, 0) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_dh_compute_key, 0) + ZEND_ARG_INFO(0, pub_key) + ZEND_ARG_INFO(0, dh_key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_derive, 0, 0, 2) + ZEND_ARG_INFO(0, peer_pub_key) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, keylen) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_random_pseudo_bytes, 0, 0, 1) + ZEND_ARG_INFO(0, length) + ZEND_ARG_INFO(1, result_is_strong) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_spki_new, 0, 0, 2) + ZEND_ARG_INFO(0, privkey) + ZEND_ARG_INFO(0, challenge) + ZEND_ARG_INFO(0, algo) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_verify, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export_challenge, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_get_cert_locations, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ openssl_functions[] + */ +static const zend_function_entry openssl_functions[] = { + PHP_FE(openssl_get_cert_locations, arginfo_openssl_get_cert_locations) + +/* spki functions */ + PHP_FE(openssl_spki_new, arginfo_openssl_spki_new) + PHP_FE(openssl_spki_verify, arginfo_openssl_spki_verify) + PHP_FE(openssl_spki_export, arginfo_openssl_spki_export) + PHP_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge) + +/* public/private key functions */ + PHP_FE(openssl_pkey_free, arginfo_openssl_pkey_free) + PHP_FE(openssl_pkey_new, arginfo_openssl_pkey_new) + PHP_FE(openssl_pkey_export, arginfo_openssl_pkey_export) + PHP_FE(openssl_pkey_export_to_file, arginfo_openssl_pkey_export_to_file) + PHP_FE(openssl_pkey_get_private, arginfo_openssl_pkey_get_private) + PHP_FE(openssl_pkey_get_public, arginfo_openssl_pkey_get_public) + PHP_FE(openssl_pkey_get_details, arginfo_openssl_pkey_get_details) + + PHP_FALIAS(openssl_free_key, openssl_pkey_free, arginfo_openssl_pkey_free) + PHP_FALIAS(openssl_get_privatekey, openssl_pkey_get_private, arginfo_openssl_pkey_get_private) + PHP_FALIAS(openssl_get_publickey, openssl_pkey_get_public, arginfo_openssl_pkey_get_public) + +/* x.509 cert funcs */ + PHP_FE(openssl_x509_read, arginfo_openssl_x509_read) + PHP_FE(openssl_x509_free, arginfo_openssl_x509_free) + PHP_FE(openssl_x509_parse, arginfo_openssl_x509_parse) + PHP_FE(openssl_x509_checkpurpose, arginfo_openssl_x509_checkpurpose) + PHP_FE(openssl_x509_check_private_key, arginfo_openssl_x509_check_private_key) + PHP_FE(openssl_x509_verify, arginfo_openssl_x509_verify) + PHP_FE(openssl_x509_export, arginfo_openssl_x509_export) + PHP_FE(openssl_x509_fingerprint, arginfo_openssl_x509_fingerprint) + PHP_FE(openssl_x509_export_to_file, arginfo_openssl_x509_export_to_file) + +/* PKCS12 funcs */ + PHP_FE(openssl_pkcs12_export, arginfo_openssl_pkcs12_export) + PHP_FE(openssl_pkcs12_export_to_file, arginfo_openssl_pkcs12_export_to_file) + PHP_FE(openssl_pkcs12_read, arginfo_openssl_pkcs12_read) + +/* CSR funcs */ + PHP_FE(openssl_csr_new, arginfo_openssl_csr_new) + PHP_FE(openssl_csr_export, arginfo_openssl_csr_export) + PHP_FE(openssl_csr_export_to_file, arginfo_openssl_csr_export_to_file) + PHP_FE(openssl_csr_sign, arginfo_openssl_csr_sign) + PHP_FE(openssl_csr_get_subject, arginfo_openssl_csr_get_subject) + PHP_FE(openssl_csr_get_public_key, arginfo_openssl_csr_get_public_key) + + PHP_FE(openssl_digest, arginfo_openssl_digest) + PHP_FE(openssl_encrypt, arginfo_openssl_encrypt) + PHP_FE(openssl_decrypt, arginfo_openssl_decrypt) + PHP_FE(openssl_cipher_iv_length, arginfo_openssl_cipher_iv_length) + PHP_FE(openssl_sign, arginfo_openssl_sign) + PHP_FE(openssl_verify, arginfo_openssl_verify) + PHP_FE(openssl_seal, arginfo_openssl_seal) + PHP_FE(openssl_open, arginfo_openssl_open) + + PHP_FE(openssl_pbkdf2, arginfo_openssl_pbkdf2) + +/* for S/MIME handling */ + PHP_FE(openssl_pkcs7_verify, arginfo_openssl_pkcs7_verify) + PHP_FE(openssl_pkcs7_decrypt, arginfo_openssl_pkcs7_decrypt) + PHP_FE(openssl_pkcs7_sign, arginfo_openssl_pkcs7_sign) + PHP_FE(openssl_pkcs7_encrypt, arginfo_openssl_pkcs7_encrypt) + PHP_FE(openssl_pkcs7_read, arginfo_openssl_pkcs7_read) + + PHP_FE(openssl_private_encrypt, arginfo_openssl_private_encrypt) + PHP_FE(openssl_private_decrypt, arginfo_openssl_private_decrypt) + PHP_FE(openssl_public_encrypt, arginfo_openssl_public_encrypt) + PHP_FE(openssl_public_decrypt, arginfo_openssl_public_decrypt) + + PHP_FE(openssl_get_md_methods, arginfo_openssl_get_md_methods) + PHP_FE(openssl_get_cipher_methods, arginfo_openssl_get_cipher_methods) +#ifdef HAVE_EVP_PKEY_EC + PHP_FE(openssl_get_curve_names, arginfo_openssl_get_curve_names) +#endif + + PHP_FE(openssl_dh_compute_key, arginfo_openssl_dh_compute_key) + PHP_FE(openssl_pkey_derive, arginfo_openssl_pkey_derive) + + PHP_FE(openssl_random_pseudo_bytes, arginfo_openssl_random_pseudo_bytes) + PHP_FE(openssl_error_string, arginfo_openssl_error_string) + PHP_FE_END +}; +/* }}} */ + +/* {{{ openssl_module_entry + */ +zend_module_entry openssl_module_entry = { + STANDARD_MODULE_HEADER, + "openssl", + openssl_functions, + PHP_MINIT(openssl), + PHP_MSHUTDOWN(openssl), + NULL, + NULL, + PHP_MINFO(openssl), + PHP_OPENSSL_VERSION, + PHP_MODULE_GLOBALS(openssl), + PHP_GINIT(openssl), + PHP_GSHUTDOWN(openssl), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_OPENSSL +ZEND_GET_MODULE(openssl) +#endif + +/* {{{ OpenSSL compatibility functions and macros */ +#if PHP_OPENSSL_API_VERSION < 0x10100 +#define EVP_PKEY_get0_RSA(_pkey) _pkey->pkey.rsa +#define EVP_PKEY_get0_DH(_pkey) _pkey->pkey.dh +#define EVP_PKEY_get0_DSA(_pkey) _pkey->pkey.dsa +#define EVP_PKEY_get0_EC_KEY(_pkey) _pkey->pkey.ec + +static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + r->n = n; + r->e = e; + r->d = d; + + return 1; +} + +static int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) +{ + r->p = p; + r->q = q; + + return 1; +} + +static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) +{ + r->dmp1 = dmp1; + r->dmq1 = dmq1; + r->iqmp = iqmp; + + return 1; +} + +static void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + *n = r->n; + *e = r->e; + *d = r->d; +} + +static void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) +{ + *p = r->p; + *q = r->q; +} + +static void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) +{ + *dmp1 = r->dmp1; + *dmq1 = r->dmq1; + *iqmp = r->iqmp; +} + +static void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = dh->p; + *q = dh->q; + *g = dh->g; +} + +static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + dh->p = p; + dh->q = q; + dh->g = g; + + return 1; +} + +static void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + *pub_key = dh->pub_key; + *priv_key = dh->priv_key; +} + +static int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) +{ + dh->pub_key = pub_key; + dh->priv_key = priv_key; + + return 1; +} + +static void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = d->p; + *q = d->q; + *g = d->g; +} + +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + d->p = p; + d->q = q; + d->g = g; + + return 1; +} + +static void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + *pub_key = d->pub_key; + *priv_key = d->priv_key; +} + +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) +{ + d->pub_key = pub_key; + d->priv_key = priv_key; + + return 1; +} + +static const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) +{ + return M_ASN1_STRING_data(asn1); +} + +#if PHP_OPENSSL_API_VERSION < 0x10002 + +static int X509_get_signature_nid(const X509 *x) +{ + return OBJ_obj2nid(x->sig_alg->algorithm); +} + +#endif + +#define OpenSSL_version SSLeay_version +#define OPENSSL_VERSION SSLEAY_VERSION +#define X509_getm_notBefore X509_get_notBefore +#define X509_getm_notAfter X509_get_notAfter +#define EVP_CIPHER_CTX_reset EVP_CIPHER_CTX_cleanup + +#endif +/* }}} */ + +/* number conversion flags checks */ +#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION(_cond, _name) \ + do { \ + if (_cond) { \ + php_error_docref(NULL, E_WARNING, #_name" is too long"); \ + RETURN_FALSE; \ + } \ + } while(0) +/* number conversion flags checks */ +#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NORET(_cond, _name) \ + do { \ + if (_cond) { \ + php_error_docref(NULL, E_WARNING, #_name" is too long"); \ + return NULL; \ + } \ + } while(0) +/* check if size_t can be safely casted to int */ +#define PHP_OPENSSL_CHECK_SIZE_T_TO_INT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_INT_OVFL(_var), _name) +/* check if size_t can be safely casted to int */ +#define PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NORET(ZEND_SIZE_T_INT_OVFL(_var), _name) +/* check if size_t can be safely casted to unsigned int */ +#define PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_UINT_OVFL(_var), _name) +/* check if long can be safely casted to int */ +#define PHP_OPENSSL_CHECK_LONG_TO_INT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_LONG_EXCEEDS_INT(_var), _name) +/* check if long can be safely casted to int */ +#define PHP_OPENSSL_CHECK_LONG_TO_INT_NORET(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NORET(ZEND_LONG_EXCEEDS_INT(_var), _name) + +/* {{{ php_openssl_store_errors */ +void php_openssl_store_errors() +{ + struct php_openssl_errors *errors; + int error_code = ERR_get_error(); + + if (!error_code) { + return; + } + + if (!OPENSSL_G(errors)) { + OPENSSL_G(errors) = pecalloc(1, sizeof(struct php_openssl_errors), 1); + } + + errors = OPENSSL_G(errors); + + do { + errors->top = (errors->top + 1) % ERR_NUM_ERRORS; + if (errors->top == errors->bottom) { + errors->bottom = (errors->bottom + 1) % ERR_NUM_ERRORS; + } + errors->buffer[errors->top] = error_code; + } while ((error_code = ERR_get_error())); + +} +/* }}} */ + +static int le_key; +static int le_x509; +static int le_csr; +static int ssl_stream_data_index; + +int php_openssl_get_x509_list_id(void) /* {{{ */ +{ + return le_x509; +} +/* }}} */ + +/* {{{ resource destructors */ +static void php_openssl_pkey_free(zend_resource *rsrc) +{ + EVP_PKEY *pkey = (EVP_PKEY *)rsrc->ptr; + + assert(pkey != NULL); + + EVP_PKEY_free(pkey); +} + +static void php_openssl_x509_free(zend_resource *rsrc) +{ + X509 *x509 = (X509 *)rsrc->ptr; + X509_free(x509); +} + +static void php_openssl_csr_free(zend_resource *rsrc) +{ + X509_REQ * csr = (X509_REQ*)rsrc->ptr; + X509_REQ_free(csr); +} +/* }}} */ + +/* {{{ openssl open_basedir check */ +inline static int php_openssl_open_base_dir_chk(char *filename) +{ + if (php_check_open_basedir(filename)) { + return -1; + } + + return 0; +} +/* }}} */ + +php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl) +{ + return (php_stream*)SSL_get_ex_data(ssl, ssl_stream_data_index); +} + +int php_openssl_get_ssl_stream_data_index() +{ + return ssl_stream_data_index; +} + +/* openssl -> PHP "bridging" */ +/* true global; readonly after module startup */ +static char default_ssl_conf_filename[MAXPATHLEN]; + +struct php_x509_request { /* {{{ */ + LHASH_OF(CONF_VALUE) * global_config; /* Global SSL config */ + LHASH_OF(CONF_VALUE) * req_config; /* SSL config for this request */ + const EVP_MD * md_alg; + const EVP_MD * digest; + char * section_name, + * config_filename, + * digest_name, + * extensions_section, + * request_extensions_section; + int priv_key_bits; + int priv_key_type; + + int priv_key_encrypt; + +#ifdef HAVE_EVP_PKEY_EC + int curve_name; +#endif + + EVP_PKEY * priv_key; + + const EVP_CIPHER * priv_key_encrypt_cipher; +}; +/* }}} */ + +static X509 * php_openssl_x509_from_zval(zval * val, int makeresource, zend_resource **resourceval); +static EVP_PKEY * php_openssl_evp_from_zval( + zval * val, int public_key, char *passphrase, size_t passphrase_len, + int makeresource, zend_resource **resourceval); +static int php_openssl_is_private_key(EVP_PKEY* pkey); +static X509_STORE * php_openssl_setup_verify(zval * calist); +static STACK_OF(X509) * php_openssl_load_all_certs_from_file(char *certfile); +static X509_REQ * php_openssl_csr_from_zval(zval * val, int makeresource, zend_resource ** resourceval); +static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req); + +static void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname) /* {{{ */ +{ + zval *data; + zval subitem, tmp; + int i; + char *sname; + int nid; + X509_NAME_ENTRY * ne; + ASN1_STRING * str = NULL; + ASN1_OBJECT * obj; + + if (key != NULL) { + array_init(&subitem); + } else { + ZVAL_COPY_VALUE(&subitem, val); + } + + for (i = 0; i < X509_NAME_entry_count(name); i++) { + const unsigned char *to_add = NULL; + int to_add_len = 0; + unsigned char *to_add_buf = NULL; + + ne = X509_NAME_get_entry(name, i); + obj = X509_NAME_ENTRY_get_object(ne); + nid = OBJ_obj2nid(obj); + + if (shortname) { + sname = (char *) OBJ_nid2sn(nid); + } else { + sname = (char *) OBJ_nid2ln(nid); + } + + str = X509_NAME_ENTRY_get_data(ne); + if (ASN1_STRING_type(str) != V_ASN1_UTF8STRING) { + /* ASN1_STRING_to_UTF8(3): The converted data is copied into a newly allocated buffer */ + to_add_len = ASN1_STRING_to_UTF8(&to_add_buf, str); + to_add = to_add_buf; + } else { + /* ASN1_STRING_get0_data(3): Since this is an internal pointer it should not be freed or modified in any way */ + to_add = ASN1_STRING_get0_data(str); + to_add_len = ASN1_STRING_length(str); + } + + if (to_add_len != -1) { + if ((data = zend_hash_str_find(Z_ARRVAL(subitem), sname, strlen(sname))) != NULL) { + if (Z_TYPE_P(data) == IS_ARRAY) { + add_next_index_stringl(data, (const char *)to_add, to_add_len); + } else if (Z_TYPE_P(data) == IS_STRING) { + array_init(&tmp); + add_next_index_str(&tmp, zend_string_copy(Z_STR_P(data))); + add_next_index_stringl(&tmp, (const char *)to_add, to_add_len); + zend_hash_str_update(Z_ARRVAL(subitem), sname, strlen(sname), &tmp); + } + } else { + /* it might be better to expand it and pass zval from ZVAL_STRING + * to zend_symtable_str_update so we do not silently drop const + * but we need a test to cover this part first */ + add_assoc_stringl(&subitem, sname, (char *)to_add, to_add_len); + } + } else { + php_openssl_store_errors(); + } + + if (to_add_buf != NULL) { + OPENSSL_free(to_add_buf); + } + } + + if (key != NULL) { + zend_hash_str_update(Z_ARRVAL_P(val), key, strlen(key), &subitem); + } +} +/* }}} */ + +static void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str) /* {{{ */ +{ + add_assoc_stringl(val, key, (char *)str->data, str->length); +} +/* }}} */ + +static time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr) /* {{{ */ +{ +/* + This is how the time string is formatted: + + snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100, + ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec); +*/ + + time_t ret; + struct tm thetime; + char * strbuf; + char * thestr; + long gmadjust = 0; + size_t timestr_len; + + if (ASN1_STRING_type(timestr) != V_ASN1_UTCTIME && ASN1_STRING_type(timestr) != V_ASN1_GENERALIZEDTIME) { + php_error_docref(NULL, E_WARNING, "illegal ASN1 data type for timestamp"); + return (time_t)-1; + } + + timestr_len = (size_t)ASN1_STRING_length(timestr); + + if (timestr_len != strlen((const char *)ASN1_STRING_get0_data(timestr))) { + php_error_docref(NULL, E_WARNING, "illegal length in timestamp"); + return (time_t)-1; + } + + if (timestr_len < 13 && timestr_len != 11) { + php_error_docref(NULL, E_WARNING, "unable to parse time string %s correctly", timestr->data); + return (time_t)-1; + } + + if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME && timestr_len < 15) { + php_error_docref(NULL, E_WARNING, "unable to parse time string %s correctly", timestr->data); + return (time_t)-1; + } + + strbuf = estrdup((const char *)ASN1_STRING_get0_data(timestr)); + + memset(&thetime, 0, sizeof(thetime)); + + /* we work backwards so that we can use atoi more easily */ + + thestr = strbuf + timestr_len - 3; + + if (timestr_len == 11) { + thetime.tm_sec = 0; + } else { + thetime.tm_sec = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + } + thetime.tm_min = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_hour = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_mday = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_mon = atoi(thestr)-1; + + *thestr = '\0'; + if( ASN1_STRING_type(timestr) == V_ASN1_UTCTIME ) { + thestr -= 2; + thetime.tm_year = atoi(thestr); + + if (thetime.tm_year < 68) { + thetime.tm_year += 100; + } + } else if( ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME ) { + thestr -= 4; + thetime.tm_year = atoi(thestr) - 1900; + } + + + thetime.tm_isdst = -1; + ret = mktime(&thetime); + +#if HAVE_STRUCT_TM_TM_GMTOFF + gmadjust = thetime.tm_gmtoff; +#else + /* + ** If correcting for daylight savings time, we set the adjustment to + ** the value of timezone - 3600 seconds. Otherwise, we need to overcorrect and + ** set the adjustment to the main timezone + 3600 seconds. + */ + gmadjust = -(thetime.tm_isdst ? (long)timezone - 3600 : (long)timezone); +#endif + ret += gmadjust; + + efree(strbuf); + + return ret; +} +/* }}} */ + +static inline int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, LHASH_OF(CONF_VALUE) * config) /* {{{ */ +{ + X509V3_CTX ctx; + + X509V3_set_ctx_test(&ctx); + X509V3_set_conf_lhash(&ctx, config); + if (!X509V3_EXT_add_conf(config, &ctx, (char *)section, NULL)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error loading %s section %s of %s", + section_label, + section, + config_filename); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +static int php_openssl_add_oid_section(struct php_x509_request * req) /* {{{ */ +{ + char * str; + STACK_OF(CONF_VALUE) * sktmp; + CONF_VALUE * cnf; + int i; + + str = CONF_get_string(req->req_config, NULL, "oid_section"); + if (str == NULL) { + php_openssl_store_errors(); + return SUCCESS; + } + sktmp = CONF_get_section(req->req_config, str); + if (sktmp == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "problem loading oid section %s", str); + return FAILURE; + } + for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) { + cnf = sk_CONF_VALUE_value(sktmp, i); + if (OBJ_sn2nid(cnf->name) == NID_undef && OBJ_ln2nid(cnf->name) == NID_undef && + OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "problem creating object %s=%s", cnf->name, cnf->value); + return FAILURE; + } + } + return SUCCESS; +} +/* }}} */ + +#define PHP_SSL_REQ_INIT(req) memset(req, 0, sizeof(*req)) +#define PHP_SSL_REQ_DISPOSE(req) php_openssl_dispose_config(req) +#define PHP_SSL_REQ_PARSE(req, zval) php_openssl_parse_config(req, zval) + +#define PHP_SSL_CONFIG_SYNTAX_CHECK(var) if (req->var && php_openssl_config_check_syntax(#var, \ + req->config_filename, req->var, req->req_config) == FAILURE) return FAILURE + +#define SET_OPTIONAL_STRING_ARG(key, varname, defval) \ + do { \ + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_STRING) { \ + varname = Z_STRVAL_P(item); \ + } else { \ + varname = defval; \ + if (varname == NULL) { \ + php_openssl_store_errors(); \ + } \ + } \ + } while(0) + +#define SET_OPTIONAL_LONG_ARG(key, varname, defval) \ + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_LONG) \ + varname = (int)Z_LVAL_P(item); \ + else \ + varname = defval + +static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo); + +/* {{{ strip line endings from spkac */ +static int php_openssl_spki_cleanup(const char *src, char *dest) +{ + int removed = 0; + + while (*src) { + if (*src != '\n' && *src != '\r') { + *dest++ = *src; + } else { + ++removed; + } + ++src; + } + *dest = 0; + return removed; +} +/* }}} */ + +static int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args) /* {{{ */ +{ + char * str; + zval * item; + + SET_OPTIONAL_STRING_ARG("config", req->config_filename, default_ssl_conf_filename); + SET_OPTIONAL_STRING_ARG("config_section_name", req->section_name, "req"); + req->global_config = CONF_load(NULL, default_ssl_conf_filename, NULL); + if (req->global_config == NULL) { + php_openssl_store_errors(); + } + req->req_config = CONF_load(NULL, req->config_filename, NULL); + if (req->req_config == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + + /* read in the oids */ + str = CONF_get_string(req->req_config, NULL, "oid_file"); + if (str == NULL) { + php_openssl_store_errors(); + } else if (!php_openssl_open_base_dir_chk(str)) { + BIO *oid_bio = BIO_new_file(str, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (oid_bio) { + OBJ_create_objects(oid_bio); + BIO_free(oid_bio); + php_openssl_store_errors(); + } + } + if (php_openssl_add_oid_section(req) == FAILURE) { + return FAILURE; + } + SET_OPTIONAL_STRING_ARG("digest_alg", req->digest_name, + CONF_get_string(req->req_config, req->section_name, "default_md")); + SET_OPTIONAL_STRING_ARG("x509_extensions", req->extensions_section, + CONF_get_string(req->req_config, req->section_name, "x509_extensions")); + SET_OPTIONAL_STRING_ARG("req_extensions", req->request_extensions_section, + CONF_get_string(req->req_config, req->section_name, "req_extensions")); + SET_OPTIONAL_LONG_ARG("private_key_bits", req->priv_key_bits, + CONF_get_number(req->req_config, req->section_name, "default_bits")); + + SET_OPTIONAL_LONG_ARG("private_key_type", req->priv_key_type, OPENSSL_KEYTYPE_DEFAULT); + + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key", sizeof("encrypt_key")-1)) != NULL) { + req->priv_key_encrypt = Z_TYPE_P(item) == IS_TRUE ? 1 : 0; + } else { + str = CONF_get_string(req->req_config, req->section_name, "encrypt_rsa_key"); + if (str == NULL) { + str = CONF_get_string(req->req_config, req->section_name, "encrypt_key"); + /* it is sure that there are some errrors as str was NULL for encrypt_rsa_key */ + php_openssl_store_errors(); + } + if (str != NULL && strcmp(str, "no") == 0) { + req->priv_key_encrypt = 0; + } else { + req->priv_key_encrypt = 1; + } + } + + if (req->priv_key_encrypt && + optional_args && + (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key_cipher", sizeof("encrypt_key_cipher")-1)) != NULL && + Z_TYPE_P(item) == IS_LONG + ) { + zend_long cipher_algo = Z_LVAL_P(item); + const EVP_CIPHER* cipher = php_openssl_get_evp_cipher_from_algo(cipher_algo); + if (cipher == NULL) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm for private key."); + return FAILURE; + } else { + req->priv_key_encrypt_cipher = cipher; + } + } else { + req->priv_key_encrypt_cipher = NULL; + } + + /* digest alg */ + if (req->digest_name == NULL) { + req->digest_name = CONF_get_string(req->req_config, req->section_name, "default_md"); + } + if (req->digest_name != NULL) { + req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name); + } else { + php_openssl_store_errors(); + } + if (req->md_alg == NULL) { + req->md_alg = req->digest = EVP_sha1(); + php_openssl_store_errors(); + } + + PHP_SSL_CONFIG_SYNTAX_CHECK(extensions_section); +#ifdef HAVE_EVP_PKEY_EC + /* set the ec group curve name */ + req->curve_name = NID_undef; + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "curve_name", sizeof("curve_name")-1)) != NULL + && Z_TYPE_P(item) == IS_STRING) { + req->curve_name = OBJ_sn2nid(Z_STRVAL_P(item)); + if (req->curve_name == NID_undef) { + php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(item)); + return FAILURE; + } + } +#endif + + /* set the string mask */ + str = CONF_get_string(req->req_config, req->section_name, "string_mask"); + if (str == NULL) { + php_openssl_store_errors(); + } else if (!ASN1_STRING_set_default_mask_asc(str)) { + php_error_docref(NULL, E_WARNING, "Invalid global string mask setting %s", str); + return FAILURE; + } + + PHP_SSL_CONFIG_SYNTAX_CHECK(request_extensions_section); + + return SUCCESS; +} +/* }}} */ + +static void php_openssl_dispose_config(struct php_x509_request * req) /* {{{ */ +{ + if (req->priv_key) { + EVP_PKEY_free(req->priv_key); + req->priv_key = NULL; + } + if (req->global_config) { + CONF_free(req->global_config); + req->global_config = NULL; + } + if (req->req_config) { + CONF_free(req->req_config); + req->req_config = NULL; + } +} +/* }}} */ + +#if defined(PHP_WIN32) || PHP_OPENSSL_API_VERSION >= 0x10100 +#define PHP_OPENSSL_RAND_ADD_TIME() ((void) 0) +#else +#define PHP_OPENSSL_RAND_ADD_TIME() php_openssl_rand_add_timeval() + +static inline void php_openssl_rand_add_timeval() /* {{{ */ +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + RAND_add(&tv, sizeof(tv), 0.0); +} +/* }}} */ + +#endif + +static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *seeded) /* {{{ */ +{ + char buffer[MAXPATHLEN]; + + *egdsocket = 0; + *seeded = 0; + + if (file == NULL) { + file = RAND_file_name(buffer, sizeof(buffer)); +#ifdef HAVE_RAND_EGD + } else if (RAND_egd(file) > 0) { + /* if the given filename is an EGD socket, don't + * write anything back to it */ + *egdsocket = 1; + return SUCCESS; +#endif + } + if (file == NULL || !RAND_load_file(file, -1)) { + if (RAND_status() == 0) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "unable to load random state; not enough random data!"); + return FAILURE; + } + return FAILURE; + } + *seeded = 1; + return SUCCESS; +} +/* }}} */ + +static int php_openssl_write_rand_file(const char * file, int egdsocket, int seeded) /* {{{ */ +{ + char buffer[MAXPATHLEN]; + + + if (egdsocket || !seeded) { + /* if we did not manage to read the seed file, we should not write + * a low-entropy seed file back */ + return FAILURE; + } + if (file == NULL) { + file = RAND_file_name(buffer, sizeof(buffer)); + } + PHP_OPENSSL_RAND_ADD_TIME(); + if (file == NULL || !RAND_write_file(file)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "unable to write random state"); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +static EVP_MD * php_openssl_get_evp_md_from_algo(zend_long algo) { /* {{{ */ + EVP_MD *mdtype; + + switch (algo) { + case OPENSSL_ALGO_SHA1: + mdtype = (EVP_MD *) EVP_sha1(); + break; + case OPENSSL_ALGO_MD5: + mdtype = (EVP_MD *) EVP_md5(); + break; + case OPENSSL_ALGO_MD4: + mdtype = (EVP_MD *) EVP_md4(); + break; +#ifdef HAVE_OPENSSL_MD2_H + case OPENSSL_ALGO_MD2: + mdtype = (EVP_MD *) EVP_md2(); + break; +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 + case OPENSSL_ALGO_DSS1: + mdtype = (EVP_MD *) EVP_dss1(); + break; +#endif + case OPENSSL_ALGO_SHA224: + mdtype = (EVP_MD *) EVP_sha224(); + break; + case OPENSSL_ALGO_SHA256: + mdtype = (EVP_MD *) EVP_sha256(); + break; + case OPENSSL_ALGO_SHA384: + mdtype = (EVP_MD *) EVP_sha384(); + break; + case OPENSSL_ALGO_SHA512: + mdtype = (EVP_MD *) EVP_sha512(); + break; + case OPENSSL_ALGO_RMD160: + mdtype = (EVP_MD *) EVP_ripemd160(); + break; + default: + return NULL; + break; + } + return mdtype; +} +/* }}} */ + +static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo) { /* {{{ */ + switch (algo) { +#ifndef OPENSSL_NO_RC2 + case PHP_OPENSSL_CIPHER_RC2_40: + return EVP_rc2_40_cbc(); + break; + case PHP_OPENSSL_CIPHER_RC2_64: + return EVP_rc2_64_cbc(); + break; + case PHP_OPENSSL_CIPHER_RC2_128: + return EVP_rc2_cbc(); + break; +#endif + +#ifndef OPENSSL_NO_DES + case PHP_OPENSSL_CIPHER_DES: + return EVP_des_cbc(); + break; + case PHP_OPENSSL_CIPHER_3DES: + return EVP_des_ede3_cbc(); + break; +#endif + +#ifndef OPENSSL_NO_AES + case PHP_OPENSSL_CIPHER_AES_128_CBC: + return EVP_aes_128_cbc(); + break; + case PHP_OPENSSL_CIPHER_AES_192_CBC: + return EVP_aes_192_cbc(); + break; + case PHP_OPENSSL_CIPHER_AES_256_CBC: + return EVP_aes_256_cbc(); + break; +#endif + + + default: + return NULL; + break; + } +} +/* }}} */ + +/* {{{ INI Settings */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("openssl.cafile", NULL, PHP_INI_PERDIR, NULL) + PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_PERDIR, NULL) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(openssl) +{ + char * config_filename; + + le_key = zend_register_list_destructors_ex(php_openssl_pkey_free, NULL, "OpenSSL key", module_number); + le_x509 = zend_register_list_destructors_ex(php_openssl_x509_free, NULL, "OpenSSL X.509", module_number); + le_csr = zend_register_list_destructors_ex(php_openssl_csr_free, NULL, "OpenSSL X.509 CSR", module_number); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER) + OPENSSL_config(NULL); + SSL_library_init(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + OpenSSL_add_all_algorithms(); + +#if !defined(OPENSSL_NO_AES) && defined(EVP_CIPH_CCM_MODE) && OPENSSL_VERSION_NUMBER < 0x100020000 + EVP_add_cipher(EVP_aes_128_ccm()); + EVP_add_cipher(EVP_aes_192_ccm()); + EVP_add_cipher(EVP_aes_256_ccm()); +#endif + + SSL_load_error_strings(); +#else + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL); +#endif + + /* register a resource id number with OpenSSL so that we can map SSL -> stream structures in + * OpenSSL callbacks */ + ssl_stream_data_index = SSL_get_ex_new_index(0, "PHP stream index", NULL, NULL, NULL); + + REGISTER_STRING_CONSTANT("OPENSSL_VERSION_TEXT", OPENSSL_VERSION_TEXT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_VERSION_NUMBER", OPENSSL_VERSION_NUMBER, CONST_CS|CONST_PERSISTENT); + + /* purposes for cert purpose checking */ + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_CLIENT", X509_PURPOSE_SSL_CLIENT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_SERVER", X509_PURPOSE_SSL_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_NS_SSL_SERVER", X509_PURPOSE_NS_SSL_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_SIGN", X509_PURPOSE_SMIME_SIGN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_ENCRYPT", X509_PURPOSE_SMIME_ENCRYPT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_CRL_SIGN", X509_PURPOSE_CRL_SIGN, CONST_CS|CONST_PERSISTENT); +#ifdef X509_PURPOSE_ANY + REGISTER_LONG_CONSTANT("X509_PURPOSE_ANY", X509_PURPOSE_ANY, CONST_CS|CONST_PERSISTENT); +#endif + + /* signature algorithm constants */ + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA1", OPENSSL_ALGO_SHA1, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD5", OPENSSL_ALGO_MD5, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD4", OPENSSL_ALGO_MD4, CONST_CS|CONST_PERSISTENT); +#ifdef HAVE_OPENSSL_MD2_H + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD2", OPENSSL_ALGO_MD2, CONST_CS|CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_DSS1", OPENSSL_ALGO_DSS1, CONST_CS|CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA224", OPENSSL_ALGO_SHA224, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA256", OPENSSL_ALGO_SHA256, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA384", OPENSSL_ALGO_SHA384, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA512", OPENSSL_ALGO_SHA512, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_RMD160", OPENSSL_ALGO_RMD160, CONST_CS|CONST_PERSISTENT); + + /* flags for S/MIME */ + REGISTER_LONG_CONSTANT("PKCS7_DETACHED", PKCS7_DETACHED, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_TEXT", PKCS7_TEXT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOINTERN", PKCS7_NOINTERN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOVERIFY", PKCS7_NOVERIFY, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOCHAIN", PKCS7_NOCHAIN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOCERTS", PKCS7_NOCERTS, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOATTR", PKCS7_NOATTR, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_BINARY", PKCS7_BINARY, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOSIGS", PKCS7_NOSIGS, CONST_CS|CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_PADDING", RSA_PKCS1_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_SSLV23_PADDING", RSA_SSLV23_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_NO_PADDING", RSA_NO_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING, CONST_CS|CONST_PERSISTENT); + + /* Informational stream wrapper constants */ + REGISTER_STRING_CONSTANT("OPENSSL_DEFAULT_STREAM_CIPHERS", OPENSSL_DEFAULT_STREAM_CIPHERS, CONST_CS|CONST_PERSISTENT); + + /* Ciphers */ +#ifndef OPENSSL_NO_RC2 + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_40", PHP_OPENSSL_CIPHER_RC2_40, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_128", PHP_OPENSSL_CIPHER_RC2_128, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_64", PHP_OPENSSL_CIPHER_RC2_64, CONST_CS|CONST_PERSISTENT); +#endif +#ifndef OPENSSL_NO_DES + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_DES", PHP_OPENSSL_CIPHER_DES, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_3DES", PHP_OPENSSL_CIPHER_3DES, CONST_CS|CONST_PERSISTENT); +#endif +#ifndef OPENSSL_NO_AES + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_128_CBC", PHP_OPENSSL_CIPHER_AES_128_CBC, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_192_CBC", PHP_OPENSSL_CIPHER_AES_192_CBC, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_256_CBC", PHP_OPENSSL_CIPHER_AES_256_CBC, CONST_CS|CONST_PERSISTENT); +#endif + + /* Values for key types */ + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_RSA", OPENSSL_KEYTYPE_RSA, CONST_CS|CONST_PERSISTENT); +#ifndef NO_DSA + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DSA", OPENSSL_KEYTYPE_DSA, CONST_CS|CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DH", OPENSSL_KEYTYPE_DH, CONST_CS|CONST_PERSISTENT); +#ifdef HAVE_EVP_PKEY_EC + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_EC", OPENSSL_KEYTYPE_EC, CONST_CS|CONST_PERSISTENT); +#endif + + REGISTER_LONG_CONSTANT("OPENSSL_RAW_DATA", OPENSSL_RAW_DATA, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ZERO_PADDING", OPENSSL_ZERO_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_DONT_ZERO_PAD_KEY", OPENSSL_DONT_ZERO_PAD_KEY, CONST_CS|CONST_PERSISTENT); + +#ifndef OPENSSL_NO_TLSEXT + /* SNI support included */ + REGISTER_LONG_CONSTANT("OPENSSL_TLSEXT_SERVER_NAME", 1, CONST_CS|CONST_PERSISTENT); +#endif + + /* Determine default SSL configuration file */ + config_filename = getenv("OPENSSL_CONF"); + if (config_filename == NULL) { + config_filename = getenv("SSLEAY_CONF"); + } + + /* default to 'openssl.cnf' if no environment variable is set */ + if (config_filename == NULL) { + snprintf(default_ssl_conf_filename, sizeof(default_ssl_conf_filename), "%s/%s", + X509_get_default_cert_area(), + "openssl.cnf"); + } else { + strlcpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename)); + } + + php_stream_xport_register("ssl", php_openssl_ssl_socket_factory); +#ifndef OPENSSL_NO_SSL3 + php_stream_xport_register("sslv3", php_openssl_ssl_socket_factory); +#endif + php_stream_xport_register("tls", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.0", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.1", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.2", php_openssl_ssl_socket_factory); +#if OPENSSL_VERSION_NUMBER >= 0x10101000 + php_stream_xport_register("tlsv1.3", php_openssl_ssl_socket_factory); +#endif + + /* override the default tcp socket provider */ + php_stream_xport_register("tcp", php_openssl_ssl_socket_factory); + + php_register_url_stream_wrapper("https", &php_stream_http_wrapper); + php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper); + + REGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION +*/ +PHP_GINIT_FUNCTION(openssl) +{ +#if defined(COMPILE_DL_OPENSSL) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + openssl_globals->errors = NULL; +} +/* }}} */ + +/* {{{ PHP_GSHUTDOWN_FUNCTION +*/ +PHP_GSHUTDOWN_FUNCTION(openssl) +{ + if (openssl_globals->errors) { + pefree(openssl_globals->errors, 1); + } +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(openssl) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "OpenSSL support", "enabled"); + php_info_print_table_row(2, "OpenSSL Library Version", OpenSSL_version(OPENSSL_VERSION)); + php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT); + php_info_print_table_row(2, "Openssl default config", default_ssl_conf_filename); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(openssl) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER) + EVP_cleanup(); + + /* prevent accessing locking callback from unloaded extension */ + CRYPTO_set_locking_callback(NULL); + /* free allocated error strings */ + ERR_free_strings(); + CONF_modules_free(); +#endif + + php_unregister_url_stream_wrapper("https"); + php_unregister_url_stream_wrapper("ftps"); + + php_stream_xport_unregister("ssl"); +#ifndef OPENSSL_NO_SSL3 + php_stream_xport_unregister("sslv3"); +#endif + php_stream_xport_unregister("tls"); + php_stream_xport_unregister("tlsv1.0"); + php_stream_xport_unregister("tlsv1.1"); + php_stream_xport_unregister("tlsv1.2"); +#if OPENSSL_VERSION_NUMBER >= 0x10101000 + php_stream_xport_unregister("tlsv1.3"); +#endif + + /* reinstate the default tcp handler */ + php_stream_xport_register("tcp", php_stream_generic_socket_factory); + + UNREGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ + +/* {{{ x509 cert functions */ + +/* {{{ proto array openssl_get_cert_locations(void) + Retrieve an array mapping available certificate locations */ +PHP_FUNCTION(openssl_get_cert_locations) +{ + array_init(return_value); + + add_assoc_string(return_value, "default_cert_file", (char *) X509_get_default_cert_file()); + add_assoc_string(return_value, "default_cert_file_env", (char *) X509_get_default_cert_file_env()); + add_assoc_string(return_value, "default_cert_dir", (char *) X509_get_default_cert_dir()); + add_assoc_string(return_value, "default_cert_dir_env", (char *) X509_get_default_cert_dir_env()); + add_assoc_string(return_value, "default_private_dir", (char *) X509_get_default_private_dir()); + add_assoc_string(return_value, "default_default_cert_area", (char *) X509_get_default_cert_area()); + add_assoc_string(return_value, "ini_cafile", + zend_ini_string("openssl.cafile", sizeof("openssl.cafile")-1, 0)); + add_assoc_string(return_value, "ini_capath", + zend_ini_string("openssl.capath", sizeof("openssl.capath")-1, 0)); +} +/* }}} */ + + +/* {{{ php_openssl_x509_from_zval + Given a zval, coerce it into an X509 object. + The zval can be: + . X509 resource created using openssl_read_x509() + . if it starts with file:// then it will be interpreted as the path to that cert + . it will be interpreted as the cert data + If you supply makeresource, the result will be registered as an x509 resource and + it's value returned in makeresource. +*/ +static X509 * php_openssl_x509_from_zval(zval * val, int makeresource, zend_resource **resourceval) +{ + X509 *cert = NULL; + BIO *in; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_RESOURCE) { + /* is it an x509 resource ? */ + void * what; + zend_resource *res = Z_RES_P(val); + + what = zend_fetch_resource(res, "OpenSSL X.509", le_x509); + if (!what) { + return NULL; + } + if (resourceval) { + *resourceval = res; + if (makeresource) { + Z_ADDREF_P(val); + } + } + return (X509*)what; + } + + if (!(Z_TYPE_P(val) == IS_STRING || Z_TYPE_P(val) == IS_OBJECT)) { + return NULL; + } + + /* force it to be a string and check if it refers to a file */ + if (!try_convert_to_string(val)) { + return NULL; + } + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + + if (php_openssl_open_base_dir_chk(Z_STRVAL_P(val) + (sizeof("file://") - 1))) { + return NULL; + } + + in = BIO_new_file(Z_STRVAL_P(val) + (sizeof("file://") - 1), PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } + cert = PEM_read_bio_X509(in, NULL, NULL, NULL); + + } else { + + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } +#ifdef TYPEDEF_D2I_OF + cert = (X509 *) PEM_ASN1_read_bio((d2i_of_void *)d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL); +#else + cert = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL); +#endif + } + + if (!BIO_free(in)) { + php_openssl_store_errors(); + } + + if (cert == NULL) { + php_openssl_store_errors(); + return NULL; + } + + if (makeresource && resourceval) { + *resourceval = zend_register_resource(cert, le_x509); + } + return cert; +} + +/* }}} */ + +/* {{{ proto bool openssl_x509_export_to_file(mixed x509, string outfilename [, bool notext = true]) + Exports a CERT to file or a var */ +PHP_FUNCTION(openssl_x509_export_to_file) +{ + X509 * cert; + zval * zcert; + zend_bool notext = 1; + BIO * bio_out; + char * filename; + size_t filename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zp|b", &zcert, &filename, &filename_len, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + + if (php_openssl_open_base_dir_chk(filename)) { + return; + } + + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out) { + if (!notext && !X509_print(bio_out, cert)) { + php_openssl_store_errors(); + } + if (!PEM_write_bio_X509(bio_out, cert)) { + php_openssl_store_errors(); + } + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } + + if (!BIO_free(bio_out)) { + php_openssl_store_errors(); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_new(mixed zpkey, string challenge [, mixed method]) + Creates new private key (or uses existing) and creates a new spki cert + outputting results to var */ +PHP_FUNCTION(openssl_spki_new) +{ + size_t challenge_len; + char * challenge = NULL, * spkstr = NULL; + zend_string * s = NULL; + zend_resource *keyresource = NULL; + const char *spkac = "SPKAC="; + zend_long algo = OPENSSL_ALGO_MD5; + + zval *method = NULL; + zval * zpkey = NULL; + EVP_PKEY * pkey = NULL; + NETSCAPE_SPKI *spki=NULL; + const EVP_MD *mdtype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|z", &zpkey, &challenge, &challenge_len, &method) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(challenge_len, challenge); + pkey = php_openssl_evp_from_zval(zpkey, 0, challenge, challenge_len, 1, &keyresource); + + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to use supplied private key"); + goto cleanup; + } + + if (method != NULL) { + if (Z_TYPE_P(method) == IS_LONG) { + algo = Z_LVAL_P(method); + } else { + php_error_docref(NULL, E_WARNING, "Algorithm must be of supported type"); + goto cleanup; + } + } + mdtype = php_openssl_get_evp_md_from_algo(algo); + + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + goto cleanup; + } + + if ((spki = NETSCAPE_SPKI_new()) == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to create new SPKAC"); + goto cleanup; + } + + if (challenge) { + if (!ASN1_STRING_set(spki->spkac->challenge, challenge, (int)challenge_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to set challenge data"); + goto cleanup; + } + } + + if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to embed public key"); + goto cleanup; + } + + if (!NETSCAPE_SPKI_sign(spki, pkey, mdtype)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to sign with specified algorithm"); + goto cleanup; + } + + spkstr = NETSCAPE_SPKI_b64_encode(spki); + if (!spkstr){ + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to encode SPKAC"); + goto cleanup; + } + + s = zend_string_alloc(strlen(spkac) + strlen(spkstr), 0); + sprintf(ZSTR_VAL(s), "%s%s", spkac, spkstr); + ZSTR_LEN(s) = strlen(ZSTR_VAL(s)); + OPENSSL_free(spkstr); + + RETVAL_STR(s); + goto cleanup; + +cleanup: + + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (keyresource == NULL && pkey != NULL) { + EVP_PKEY_free(pkey); + } + + if (s && ZSTR_LEN(s) <= 0) { + RETVAL_FALSE; + } + + if (keyresource == NULL && s != NULL) { + zend_string_release_ex(s, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_spki_verify(string spki) + Verifies spki returns boolean */ +PHP_FUNCTION(openssl_spki_verify) +{ + size_t spkstr_len; + int i = 0, spkstr_cleaned_len = 0; + char *spkstr, * spkstr_cleaned = NULL; + + EVP_PKEY *pkey = NULL; + NETSCAPE_SPKI *spki = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode supplied SPKAC"); + goto cleanup; + } + + pkey = X509_PUBKEY_get(spki->spkac->pubkey); + if (pkey == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; + } + + i = NETSCAPE_SPKI_verify(spki, pkey); + goto cleanup; + +cleanup: + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + + if (i > 0) { + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_export(string spki) + Exports public key from existing spki to var */ +PHP_FUNCTION(openssl_spki_export) +{ + size_t spkstr_len; + char *spkstr, * spkstr_cleaned = NULL, * s = NULL; + int spkstr_cleaned_len; + + EVP_PKEY *pkey = NULL; + NETSCAPE_SPKI *spki = NULL; + BIO *out = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode supplied SPKAC"); + goto cleanup; + } + + pkey = X509_PUBKEY_get(spki->spkac->pubkey); + if (pkey == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; + } + + out = BIO_new(BIO_s_mem()); + if (out && PEM_write_bio_PUBKEY(out, pkey)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(out, &bio_buf); + RETVAL_STRINGL((char *)bio_buf->data, bio_buf->length); + } else { + php_openssl_store_errors(); + } + goto cleanup; + +cleanup: + + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (out != NULL) { + BIO_free_all(out); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + if (s != NULL) { + efree(s); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_export_challenge(string spki) + Exports spkac challenge from existing spki to var */ +PHP_FUNCTION(openssl_spki_export_challenge) +{ + size_t spkstr_len; + char *spkstr, * spkstr_cleaned = NULL; + int spkstr_cleaned_len; + + NETSCAPE_SPKI *spki = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode SPKAC"); + goto cleanup; + } + + RETVAL_STRING((const char *)ASN1_STRING_get0_data(spki->spkac->challenge)); + goto cleanup; + +cleanup: + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + if (spki) { + NETSCAPE_SPKI_free(spki); + } +} +/* }}} */ + +/* {{{ proto bool openssl_x509_export(mixed x509, string &out [, bool notext = true]) + Exports a CERT to file or a var */ +PHP_FUNCTION(openssl_x509_export) +{ + X509 * cert; + zval * zcert, *zout; + zend_bool notext = 1; + BIO * bio_out; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|b", &zcert, &zout, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + + bio_out = BIO_new(BIO_s_mem()); + if (!bio_out) { + php_openssl_store_errors(); + goto cleanup; + } + if (!notext && !X509_print(bio_out, cert)) { + php_openssl_store_errors(); + } + if (PEM_write_bio_X509(bio_out, cert)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(bio_out, &bio_buf); + ZEND_TRY_ASSIGN_REF_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + BIO_free(bio_out); + +cleanup: + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw) +{ + unsigned char md[EVP_MAX_MD_SIZE]; + const EVP_MD *mdtype; + unsigned int n; + zend_string *ret; + + if (!(mdtype = EVP_get_digestbyname(method))) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + return NULL; + } else if (!X509_digest(peer, mdtype, md, &n)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "Could not generate signature"); + return NULL; + } + + if (raw) { + ret = zend_string_init((char*)md, n, 0); + } else { + ret = zend_string_alloc(n * 2, 0); + make_digest_ex(ZSTR_VAL(ret), md, n); + ZSTR_VAL(ret)[n * 2] = '\0'; + } + + return ret; +} + +PHP_FUNCTION(openssl_x509_fingerprint) +{ + X509 *cert; + zval *zcert; + zend_bool raw_output = 0; + char *method = "sha1"; + size_t method_len; + zend_string *fingerprint; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|sb", &zcert, &method, &method_len, &raw_output) == FAILURE) { + return; + } + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + RETURN_FALSE; + } + + fingerprint = php_openssl_x509_fingerprint(cert, method, raw_output); + if (fingerprint) { + RETVAL_STR(fingerprint); + } else { + RETVAL_FALSE; + } + + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} + +/* {{{ proto bool openssl_x509_check_private_key(mixed cert, mixed key) + Checks if a private key corresponds to a CERT */ +PHP_FUNCTION(openssl_x509_check_private_key) +{ + zval * zcert, *zkey; + X509 * cert = NULL; + EVP_PKEY * key = NULL; + zend_resource *keyresource = NULL; + + RETVAL_FALSE; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &zcert, &zkey) == FAILURE) { + return; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + RETURN_FALSE; + } + key = php_openssl_evp_from_zval(zkey, 0, "", 0, 1, &keyresource); + if (key) { + RETVAL_BOOL(X509_check_private_key(cert, key)); + } + + if (keyresource == NULL && key) { + EVP_PKEY_free(key); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto int openssl_x509_verify(mixed cert, mixed key) + Verifies the signature of certificate cert using public key key */ +PHP_FUNCTION(openssl_x509_verify) +{ + zval * zcert, *zkey; + X509 * cert = NULL; + EVP_PKEY * key = NULL; + zend_resource *keyresource = NULL; + int err = -1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &zcert, &zkey) == FAILURE) { + return; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + RETURN_LONG(err); + } + key = php_openssl_evp_from_zval(zkey, 1, NULL, 0, 0, &keyresource); + if (key == NULL) { + X509_free(cert); + RETURN_LONG(err); + } + + err = X509_verify(cert, key); + + if (err < 0) { + php_openssl_store_errors(); + } + + if (keyresource == NULL && key) { + EVP_PKEY_free(key); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } + + RETURN_LONG(err); +} +/* }}} */ + +/* Special handling of subjectAltName, see CVE-2013-4073 + * Christian Heimes + */ + +static int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) +{ + GENERAL_NAMES *names; + const X509V3_EXT_METHOD *method = NULL; + ASN1_OCTET_STRING *extension_data; + long i, length, num; + const unsigned char *p; + + method = X509V3_EXT_get(extension); + if (method == NULL) { + return -1; + } + + extension_data = X509_EXTENSION_get_data(extension); + p = extension_data->data; + length = extension_data->length; + if (method->it) { + names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, &p, length, + ASN1_ITEM_ptr(method->it))); + } else { + names = (GENERAL_NAMES*) (method->d2i(NULL, &p, length)); + } + if (names == NULL) { + php_openssl_store_errors(); + return -1; + } + + num = sk_GENERAL_NAME_num(names); + for (i = 0; i < num; i++) { + GENERAL_NAME *name; + ASN1_STRING *as; + name = sk_GENERAL_NAME_value(names, i); + switch (name->type) { + case GEN_EMAIL: + BIO_puts(bio, "email:"); + as = name->d.rfc822Name; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + case GEN_DNS: + BIO_puts(bio, "DNS:"); + as = name->d.dNSName; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + case GEN_URI: + BIO_puts(bio, "URI:"); + as = name->d.uniformResourceIdentifier; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + default: + /* use builtin print for GEN_OTHERNAME, GEN_X400, + * GEN_EDIPARTY, GEN_DIRNAME, GEN_IPADD and GEN_RID + */ + GENERAL_NAME_print(bio, name); + } + /* trailing ', ' except for last element */ + if (i < (num - 1)) { + BIO_puts(bio, ", "); + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return 0; +} + +/* {{{ proto array openssl_x509_parse(mixed x509 [, bool shortnames=true]) + Returns an array of the fields/values of the CERT */ +PHP_FUNCTION(openssl_x509_parse) +{ + zval * zcert; + X509 * cert = NULL; + int i, sig_nid; + zend_bool useshortnames = 1; + char * tmpstr; + zval subitem; + X509_EXTENSION *extension; + X509_NAME *subject_name; + char *cert_name; + char *extname; + BIO *bio_out; + BUF_MEM *bio_buf; + ASN1_INTEGER *asn1_serial; + BIGNUM *bn_serial; + char *str_serial; + char *hex_serial; + char buf[256]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcert, &useshortnames) == FAILURE) { + return; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + RETURN_FALSE; + } + array_init(return_value); + + subject_name = X509_get_subject_name(cert); + cert_name = X509_NAME_oneline(subject_name, NULL, 0); + add_assoc_string(return_value, "name", cert_name); + OPENSSL_free(cert_name); + + php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames); + /* hash as used in CA directories to lookup cert by subject name */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%08lx", X509_subject_name_hash(cert)); + add_assoc_string(return_value, "hash", buf); + } + + php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames); + add_assoc_long(return_value, "version", X509_get_version(cert)); + + asn1_serial = X509_get_serialNumber(cert); + + bn_serial = ASN1_INTEGER_to_BN(asn1_serial, NULL); + /* Can return NULL on error or memory allocation failure */ + if (!bn_serial) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + hex_serial = BN_bn2hex(bn_serial); + BN_free(bn_serial); + /* Can return NULL on error or memory allocation failure */ + if (!hex_serial) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + str_serial = i2s_ASN1_INTEGER(NULL, asn1_serial); + add_assoc_string(return_value, "serialNumber", str_serial); + OPENSSL_free(str_serial); + + /* Return the hex representation of the serial number, as defined by OpenSSL */ + add_assoc_string(return_value, "serialNumberHex", hex_serial); + OPENSSL_free(hex_serial); + + php_openssl_add_assoc_asn1_string(return_value, "validFrom", X509_getm_notBefore(cert)); + php_openssl_add_assoc_asn1_string(return_value, "validTo", X509_getm_notAfter(cert)); + + add_assoc_long(return_value, "validFrom_time_t", php_openssl_asn1_time_to_time_t(X509_getm_notBefore(cert))); + add_assoc_long(return_value, "validTo_time_t", php_openssl_asn1_time_to_time_t(X509_getm_notAfter(cert))); + + tmpstr = (char *)X509_alias_get0(cert, NULL); + if (tmpstr) { + add_assoc_string(return_value, "alias", tmpstr); + } + + sig_nid = X509_get_signature_nid(cert); + add_assoc_string(return_value, "signatureTypeSN", (char*)OBJ_nid2sn(sig_nid)); + add_assoc_string(return_value, "signatureTypeLN", (char*)OBJ_nid2ln(sig_nid)); + add_assoc_long(return_value, "signatureTypeNID", sig_nid); + array_init(&subitem); + + /* NOTE: the purposes are added as integer keys - the keys match up to the X509_PURPOSE_SSL_XXX defines + in x509v3.h */ + for (i = 0; i < X509_PURPOSE_get_count(); i++) { + int id, purpset; + char * pname; + X509_PURPOSE * purp; + zval subsub; + + array_init(&subsub); + + purp = X509_PURPOSE_get0(i); + id = X509_PURPOSE_get_id(purp); + + purpset = X509_check_purpose(cert, id, 0); + add_index_bool(&subsub, 0, purpset); + + purpset = X509_check_purpose(cert, id, 1); + add_index_bool(&subsub, 1, purpset); + + pname = useshortnames ? X509_PURPOSE_get0_sname(purp) : X509_PURPOSE_get0_name(purp); + add_index_string(&subsub, 2, pname); + + /* NOTE: if purpset > 1 then it's a warning - we should mention it ? */ + + add_index_zval(&subitem, id, &subsub); + } + add_assoc_zval(return_value, "purposes", &subitem); + + array_init(&subitem); + + + for (i = 0; i < X509_get_ext_count(cert); i++) { + int nid; + extension = X509_get_ext(cert, i); + nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension)); + if (nid != NID_undef) { + extname = (char *)OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(extension))); + } else { + OBJ_obj2txt(buf, sizeof(buf)-1, X509_EXTENSION_get_object(extension), 1); + extname = buf; + } + bio_out = BIO_new(BIO_s_mem()); + if (bio_out == NULL) { + php_openssl_store_errors(); + RETURN_FALSE; + } + if (nid == NID_subject_alt_name) { + if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + zend_array_destroy(Z_ARR_P(return_value)); + BIO_free(bio_out); + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } + RETURN_FALSE; + } + } + else if (X509V3_EXT_print(bio_out, extension, 0, 0)) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + php_openssl_add_assoc_asn1_string(&subitem, extname, X509_EXTENSION_get_data(extension)); + } + BIO_free(bio_out); + } + add_assoc_zval(return_value, "extensions", &subitem); + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ php_openssl_load_all_certs_from_file */ +static STACK_OF(X509) *php_openssl_load_all_certs_from_file(char *certfile) +{ + STACK_OF(X509_INFO) *sk=NULL; + STACK_OF(X509) *stack=NULL, *ret=NULL; + BIO *in=NULL; + X509_INFO *xi; + + if(!(stack = sk_X509_new_null())) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "memory allocation failure"); + goto end; + } + + if (php_openssl_open_base_dir_chk(certfile)) { + sk_X509_free(stack); + goto end; + } + + if (!(in=BIO_new_file(certfile, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)))) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening the file, %s", certfile); + sk_X509_free(stack); + goto end; + } + + /* This loads from a file, a stack of x509/crl/pkey sets */ + if (!(sk=PEM_X509_INFO_read_bio(in, NULL, NULL, NULL))) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error reading the file, %s", certfile); + sk_X509_free(stack); + goto end; + } + + /* scan over it and pull out the certs */ + while (sk_X509_INFO_num(sk)) { + xi=sk_X509_INFO_shift(sk); + if (xi->x509 != NULL) { + sk_X509_push(stack,xi->x509); + xi->x509=NULL; + } + X509_INFO_free(xi); + } + if (!sk_X509_num(stack)) { + php_error_docref(NULL, E_WARNING, "no certificates in file, %s", certfile); + sk_X509_free(stack); + goto end; + } + ret = stack; +end: + BIO_free(in); + sk_X509_INFO_free(sk); + + return ret; +} +/* }}} */ + +/* {{{ check_cert */ +static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose) +{ + int ret=0; + X509_STORE_CTX *csc; + + csc = X509_STORE_CTX_new(); + if (csc == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "memory allocation failure"); + return 0; + } + if (!X509_STORE_CTX_init(csc, ctx, x, untrustedchain)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "cert store initialization failed"); + return 0; + } + if (purpose >= 0 && !X509_STORE_CTX_set_purpose(csc, purpose)) { + php_openssl_store_errors(); + } + ret = X509_verify_cert(csc); + if (ret < 0) { + php_openssl_store_errors(); + } + X509_STORE_CTX_free(csc); + + return ret; +} +/* }}} */ + +/* {{{ proto int openssl_x509_checkpurpose(mixed x509cert, int purpose, array cainfo [, string untrustedfile]) + Checks the CERT to see if it can be used for the purpose in purpose. cainfo holds information about trusted CAs */ +PHP_FUNCTION(openssl_x509_checkpurpose) +{ + zval * zcert, * zcainfo = NULL; + X509_STORE * cainfo = NULL; + X509 * cert = NULL; + STACK_OF(X509) * untrustedchain = NULL; + zend_long purpose; + char * untrusted = NULL; + size_t untrusted_len = 0; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl|a!s", &zcert, &purpose, &zcainfo, &untrusted, &untrusted_len) == FAILURE) { + return; + } + + RETVAL_LONG(-1); + + if (untrusted) { + untrustedchain = php_openssl_load_all_certs_from_file(untrusted); + if (untrustedchain == NULL) { + goto clean_exit; + } + } + + cainfo = php_openssl_setup_verify(zcainfo); + if (cainfo == NULL) { + goto clean_exit; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + goto clean_exit; + } + + ret = check_cert(cainfo, cert, untrustedchain, (int)purpose); + if (ret != 0 && ret != 1) { + RETVAL_LONG(ret); + } else { + RETVAL_BOOL(ret); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +clean_exit: + if (cainfo) { + X509_STORE_free(cainfo); + } + if (untrustedchain) { + sk_X509_pop_free(untrustedchain, X509_free); + } +} +/* }}} */ + +/* {{{ php_openssl_setup_verify + * calist is an array containing file and directory names. create a + * certificate store and add those certs to it for use in verification. +*/ +static X509_STORE *php_openssl_setup_verify(zval *calist) +{ + X509_STORE *store; + X509_LOOKUP * dir_lookup, * file_lookup; + int ndirs = 0, nfiles = 0; + zval * item; + zend_stat_t sb; + + store = X509_STORE_new(); + + if (store == NULL) { + php_openssl_store_errors(); + return NULL; + } + + if (calist && (Z_TYPE_P(calist) == IS_ARRAY)) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(calist), item) { + zend_string *str = zval_try_get_string(item); + if (UNEXPECTED(!str)) { + return NULL; + } + + if (VCWD_STAT(ZSTR_VAL(str), &sb) == -1) { + php_error_docref(NULL, E_WARNING, "unable to stat %s", ZSTR_VAL(str)); + zend_string_release(str); + continue; + } + + if ((sb.st_mode & S_IFREG) == S_IFREG) { + file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, ZSTR_VAL(str), X509_FILETYPE_PEM)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error loading file %s", ZSTR_VAL(str)); + } else { + nfiles++; + } + file_lookup = NULL; + } else { + dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, ZSTR_VAL(str), X509_FILETYPE_PEM)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error loading directory %s", ZSTR_VAL(str)); + } else { + ndirs++; + } + dir_lookup = NULL; + } + zend_string_release(str); + } ZEND_HASH_FOREACH_END(); + } + if (nfiles == 0) { + file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT)) { + php_openssl_store_errors(); + } + } + if (ndirs == 0) { + dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT)) { + php_openssl_store_errors(); + } + } + return store; +} +/* }}} */ + +/* {{{ proto resource openssl_x509_read(mixed cert) + Reads X.509 certificates */ +PHP_FUNCTION(openssl_x509_read) +{ + zval *cert; + X509 *x509; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &cert) == FAILURE) { + return; + } + x509 = php_openssl_x509_from_zval(cert, 1, &res); + ZVAL_RES(return_value, res); + + if (x509 == NULL) { + php_error_docref(NULL, E_WARNING, "supplied parameter cannot be coerced into an X509 certificate!"); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void openssl_x509_free(resource x509) + Frees X.509 certificates */ +PHP_FUNCTION(openssl_x509_free) +{ + zval *x509; + X509 *cert; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &x509) == FAILURE) { + return; + } + if ((cert = (X509 *)zend_fetch_resource(Z_RES_P(x509), "OpenSSL X.509", le_x509)) == NULL) { + RETURN_FALSE; + } + zend_list_close(Z_RES_P(x509)); +} +/* }}} */ + +/* }}} */ + +/* Pop all X509 from Stack and free them, free the stack afterwards */ +static void php_sk_X509_free(STACK_OF(X509) * sk) /* {{{ */ +{ + for (;;) { + X509* x = sk_X509_pop(sk); + if (!x) break; + X509_free(x); + } + sk_X509_free(sk); +} +/* }}} */ + +static STACK_OF(X509) * php_array_to_X509_sk(zval * zcerts) /* {{{ */ +{ + zval * zcertval; + STACK_OF(X509) * sk = NULL; + X509 * cert; + zend_resource *certresource; + + sk = sk_X509_new_null(); + + /* get certs */ + if (Z_TYPE_P(zcerts) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zcerts), zcertval) { + cert = php_openssl_x509_from_zval(zcertval, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + cert = X509_dup(cert); + + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + } + sk_X509_push(sk, cert); + } ZEND_HASH_FOREACH_END(); + } else { + /* a single certificate */ + cert = php_openssl_x509_from_zval(zcerts, 0, &certresource); + + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(sk, cert); + } + +clean_exit: + return sk; +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_export_to_file(mixed x509, string filename, mixed priv_key, string pass[, array args]) + Creates and exports a PKCS to file */ +PHP_FUNCTION(openssl_pkcs12_export_to_file) +{ + X509 * cert = NULL; + BIO * bio_out = NULL; + PKCS12 * p12 = NULL; + char * filename; + char * friendly_name = NULL; + size_t filename_len; + char * pass; + size_t pass_len; + zval *zcert = NULL, *zpkey = NULL, *args = NULL; + EVP_PKEY *priv_key = NULL; + zend_resource *keyresource; + zval * item; + STACK_OF(X509) *ca = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zpzs|a", &zcert, &filename, &filename_len, &zpkey, &pass, &pass_len, &args) == FAILURE) + return; + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (!X509_check_private_key(cert, priv_key)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "private key does not correspond to cert"); + goto cleanup; + } + if (php_openssl_open_base_dir_chk(filename)) { + goto cleanup; + } + + /* parse extra config from args array, promote this to an extra function */ + if (args && + (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + Z_TYPE_P(item) == IS_STRING + ) { + friendly_name = Z_STRVAL_P(item); + } + /* certpbe (default RC2-40) + keypbe (default 3DES) + friendly_caname + */ + + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + ca = php_array_to_X509_sk(item); + } + /* end parse extra config */ + + /*PKCS12 *PKCS12_create(char *pass, char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ca, + int nid_key, int nid_cert, int iter, int mac_iter, int keytype);*/ + + p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0); + if (p12 != NULL) { + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out != NULL) { + + i2d_PKCS12_bio(bio_out, p12); + BIO_free(bio_out); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + + PKCS12_free(p12); + } else { + php_openssl_store_errors(); + } + + php_sk_X509_free(ca); + +cleanup: + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_export(mixed x509, string &out, mixed priv_key, string pass[, array args]) + Creates and exports a PKCS12 to a var */ +PHP_FUNCTION(openssl_pkcs12_export) +{ + X509 * cert = NULL; + BIO * bio_out; + PKCS12 * p12 = NULL; + zval * zcert = NULL, *zout = NULL, *zpkey, *args = NULL; + EVP_PKEY *priv_key = NULL; + zend_resource *keyresource; + char * pass; + size_t pass_len; + char * friendly_name = NULL; + zval * item; + STACK_OF(X509) *ca = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzzs|a", &zcert, &zout, &zpkey, &pass, &pass_len, &args) == FAILURE) + return; + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (!X509_check_private_key(cert, priv_key)) { + php_error_docref(NULL, E_WARNING, "private key does not correspond to cert"); + goto cleanup; + } + + /* parse extra config from args array, promote this to an extra function */ + if (args && + (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + Z_TYPE_P(item) == IS_STRING + ) { + friendly_name = Z_STRVAL_P(item); + } + + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + ca = php_array_to_X509_sk(item); + } + /* end parse extra config */ + + p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0); + + if (p12 != NULL) { + bio_out = BIO_new(BIO_s_mem()); + if (i2d_PKCS12_bio(bio_out, p12)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(bio_out, &bio_buf); + ZEND_TRY_ASSIGN_REF_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + BIO_free(bio_out); + PKCS12_free(p12); + } else { + php_openssl_store_errors(); + } + php_sk_X509_free(ca); + +cleanup: + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_read(string PKCS12, array &certs, string pass) + Parses a PKCS12 to an array */ +PHP_FUNCTION(openssl_pkcs12_read) +{ + zval *zout = NULL, zextracerts, zcert, zpkey; + char *pass, *zp12; + size_t pass_len, zp12_len; + PKCS12 * p12 = NULL; + EVP_PKEY * pkey = NULL; + X509 * cert = NULL; + STACK_OF(X509) * ca = NULL; + BIO * bio_in = NULL; + int i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szs", &zp12, &zp12_len, &zout, &pass, &pass_len) == FAILURE) + return; + + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(zp12_len, pkcs12); + + bio_in = BIO_new(BIO_s_mem()); + + if (0 >= BIO_write(bio_in, zp12, (int)zp12_len)) { + php_openssl_store_errors(); + goto cleanup; + } + + if (d2i_PKCS12_bio(bio_in, &p12) && PKCS12_parse(p12, pass, &pkey, &cert, &ca)) { + BIO * bio_out; + int cert_num; + + zout = zend_try_array_init(zout); + if (!zout) { + goto cleanup; + } + + if (cert) { + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(bio_out, cert)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_assoc_zval(zout, "cert", &zcert); + } else { + php_openssl_store_errors(); + } + BIO_free(bio_out); + } + + if (pkey) { + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_PrivateKey(bio_out, pkey, NULL, NULL, 0, 0, NULL)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zpkey, bio_buf->data, bio_buf->length); + add_assoc_zval(zout, "pkey", &zpkey); + } else { + php_openssl_store_errors(); + } + BIO_free(bio_out); + } + + cert_num = sk_X509_num(ca); + if (ca && cert_num) { + array_init(&zextracerts); + + for (i = 0; i < cert_num; i++) { + zval zextracert; + X509* aCA = sk_X509_pop(ca); + if (!aCA) break; + + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(bio_out, aCA)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zextracert, bio_buf->data, bio_buf->length); + add_index_zval(&zextracerts, i, &zextracert); + } + + X509_free(aCA); + BIO_free(bio_out); + } + + sk_X509_free(ca); + add_assoc_zval(zout, "extracerts", &zextracerts); + } + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + cleanup: + if (bio_in) { + BIO_free(bio_in); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + if (cert) { + X509_free(cert); + } + if (p12) { + PKCS12_free(p12); + } +} +/* }}} */ + +/* {{{ x509 CSR functions */ + +/* {{{ php_openssl_make_REQ */ +static int php_openssl_make_REQ(struct php_x509_request * req, X509_REQ * csr, zval * dn, zval * attribs) +{ + STACK_OF(CONF_VALUE) * dn_sk, *attr_sk = NULL; + char * str, *dn_sect, *attr_sect; + + dn_sect = CONF_get_string(req->req_config, req->section_name, "distinguished_name"); + if (dn_sect == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + dn_sk = CONF_get_section(req->req_config, dn_sect); + if (dn_sk == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + attr_sect = CONF_get_string(req->req_config, req->section_name, "attributes"); + if (attr_sect == NULL) { + php_openssl_store_errors(); + attr_sk = NULL; + } else { + attr_sk = CONF_get_section(req->req_config, attr_sect); + if (attr_sk == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + } + /* setup the version number: version 1 */ + if (X509_REQ_set_version(csr, 0L)) { + int i, nid; + char * type; + CONF_VALUE * v; + X509_NAME * subj; + zval * item; + zend_string * strindex = NULL; + + subj = X509_REQ_get_subject_name(csr); + /* apply values from the dn hash */ + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(dn), strindex, item) { + if (strindex) { + int nid = OBJ_txt2nid(ZSTR_VAL(strindex)); + if (nid != NID_undef) { + zend_string *str_item = zval_try_get_string(item); + if (UNEXPECTED(!str_item)) { + return FAILURE; + } + if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_UTF8, + (unsigned char*)ZSTR_VAL(str_item), -1, -1, 0)) + { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, + "dn: add_entry_by_NID %d -> %s (failed; check error" + " queue and value of string_mask OpenSSL option " + "if illegal characters are reported)", + nid, ZSTR_VAL(str_item)); + zend_string_release(str_item); + return FAILURE; + } + zend_string_release(str_item); + } else { + php_error_docref(NULL, E_WARNING, "dn: %s is not a recognized name", ZSTR_VAL(strindex)); + } + } + } ZEND_HASH_FOREACH_END(); + + /* Finally apply defaults from config file */ + for(i = 0; i < sk_CONF_VALUE_num(dn_sk); i++) { + size_t len; + char buffer[200 + 1]; /*200 + \0 !*/ + + v = sk_CONF_VALUE_value(dn_sk, i); + type = v->name; + + len = strlen(type); + if (len < sizeof("_default")) { + continue; + } + len -= sizeof("_default") - 1; + if (strcmp("_default", type + len) != 0) { + continue; + } + if (len > 200) { + len = 200; + } + memcpy(buffer, type, len); + buffer[len] = '\0'; + type = buffer; + + /* Skip past any leading X. X: X, etc to allow for multiple + * instances */ + for (str = type; *str; str++) { + if (*str == ':' || *str == ',' || *str == '.') { + str++; + if (*str) { + type = str; + } + break; + } + } + /* if it is already set, skip this */ + nid = OBJ_txt2nid(type); + if (X509_NAME_get_index_by_NID(subj, nid, -1) >= 0) { + continue; + } + if (!X509_NAME_add_entry_by_txt(subj, type, MBSTRING_UTF8, (unsigned char*)v->value, -1, -1, 0)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "add_entry_by_txt %s -> %s (failed)", type, v->value); + return FAILURE; + } + if (!X509_NAME_entry_count(subj)) { + php_error_docref(NULL, E_WARNING, "no objects specified in config file"); + return FAILURE; + } + } + if (attribs) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(attribs), strindex, item) { + int nid; + + if (NULL == strindex) { + php_error_docref(NULL, E_WARNING, "dn: numeric fild names are not supported"); + continue; + } + + nid = OBJ_txt2nid(ZSTR_VAL(strindex)); + if (nid != NID_undef) { + zend_string *str_item = zval_try_get_string(item); + if (UNEXPECTED(!str_item)) { + return FAILURE; + } + if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_UTF8, (unsigned char*)ZSTR_VAL(str_item), -1, -1, 0)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "attribs: add_entry_by_NID %d -> %s (failed)", nid, ZSTR_VAL(str_item)); + zend_string_release(str_item); + return FAILURE; + } + zend_string_release(str_item); + } else { + php_error_docref(NULL, E_WARNING, "dn: %s is not a recognized name", ZSTR_VAL(strindex)); + } + } ZEND_HASH_FOREACH_END(); + for (i = 0; i < sk_CONF_VALUE_num(attr_sk); i++) { + v = sk_CONF_VALUE_value(attr_sk, i); + /* if it is already set, skip this */ + nid = OBJ_txt2nid(v->name); + if (X509_REQ_get_attr_by_NID(csr, nid, -1) >= 0) { + continue; + } + if (!X509_REQ_add1_attr_by_txt(csr, v->name, MBSTRING_UTF8, (unsigned char*)v->value, -1)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, + "add1_attr_by_txt %s -> %s (failed; check error queue " + "and value of string_mask OpenSSL option if illegal " + "characters are reported)", + v->name, v->value); + return FAILURE; + } + } + } + } else { + php_openssl_store_errors(); + } + + if (!X509_REQ_set_pubkey(csr, req->priv_key)) { + php_openssl_store_errors(); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ php_openssl_csr_from_zval */ +static X509_REQ * php_openssl_csr_from_zval(zval * val, int makeresource, zend_resource **resourceval) +{ + X509_REQ * csr = NULL; + char * filename = NULL; + BIO * in; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_RESOURCE) { + void * what; + zend_resource *res = Z_RES_P(val); + + what = zend_fetch_resource(res, "OpenSSL X.509 CSR", le_csr); + if (what) { + if (resourceval) { + *resourceval = res; + if (makeresource) { + Z_ADDREF_P(val); + } + } + return (X509_REQ*)what; + } + return NULL; + } else if (Z_TYPE_P(val) != IS_STRING) { + return NULL; + } + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + filename = Z_STRVAL_P(val) + (sizeof("file://") - 1); + } + if (filename) { + if (php_openssl_open_base_dir_chk(filename)) { + return NULL; + } + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } + + csr = PEM_read_bio_X509_REQ(in, NULL,NULL,NULL); + if (csr == NULL) { + php_openssl_store_errors(); + } + + BIO_free(in); + + return csr; +} +/* }}} */ + +/* {{{ proto bool openssl_csr_export_to_file(resource csr, string outfilename [, bool notext=true]) + Exports a CSR to file */ +PHP_FUNCTION(openssl_csr_export_to_file) +{ + X509_REQ * csr; + zval * zcsr = NULL; + zend_bool notext = 1; + char * filename = NULL; + size_t filename_len; + BIO * bio_out; + zend_resource *csr_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rp|b", &zcsr, &filename, &filename_len, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + + if (php_openssl_open_base_dir_chk(filename)) { + return; + } + + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out != NULL) { + if (!notext && !X509_REQ_print(bio_out, csr)) { + php_openssl_store_errors(); + } + if (!PEM_write_bio_X509_REQ(bio_out, csr)) { + php_error_docref(NULL, E_WARNING, "error writing PEM to file %s", filename); + php_openssl_store_errors(); + } else { + RETVAL_TRUE; + } + BIO_free(bio_out); + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + + if (csr_resource == NULL && csr != NULL) { + X509_REQ_free(csr); + } +} +/* }}} */ + +/* {{{ proto bool openssl_csr_export(resource csr, string &out [, bool notext=true]) + Exports a CSR to file or a var */ +PHP_FUNCTION(openssl_csr_export) +{ + X509_REQ * csr; + zval * zcsr = NULL, *zout=NULL; + zend_bool notext = 1; + BIO * bio_out; + zend_resource *csr_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz|b", &zcsr, &zout, ¬ext) == FAILURE) { + return; + } + + RETVAL_FALSE; + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + + /* export to a var */ + + bio_out = BIO_new(BIO_s_mem()); + if (!notext && !X509_REQ_print(bio_out, csr)) { + php_openssl_store_errors(); + } + + if (PEM_write_bio_X509_REQ(bio_out, csr)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(bio_out, &bio_buf); + ZEND_TRY_ASSIGN_REF_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (csr_resource == NULL && csr) { + X509_REQ_free(csr); + } + BIO_free(bio_out); +} +/* }}} */ + +/* {{{ proto resource openssl_csr_sign(mixed csr, mixed x509, mixed priv_key, int days [, array config_args [, int serial]]) + Signs a cert with another CERT */ +PHP_FUNCTION(openssl_csr_sign) +{ + zval * zcert = NULL, *zcsr, *zpkey, *args = NULL; + zend_long num_days; + zend_long serial = Z_L(0); + X509 * cert = NULL, *new_cert = NULL; + X509_REQ * csr; + EVP_PKEY * key = NULL, *priv_key = NULL; + zend_resource *csr_resource, *certresource = NULL, *keyresource = NULL; + int i; + struct php_x509_request req; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz!zl|a!l", &zcsr, &zcert, &zpkey, &num_days, &args, &serial) == FAILURE) + return; + + RETVAL_FALSE; + PHP_SSL_REQ_INIT(&req); + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + if (zcert) { + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 2"); + goto cleanup; + } + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (cert && !X509_check_private_key(cert, priv_key)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "private key does not correspond to signing cert"); + goto cleanup; + } + + if (PHP_SSL_REQ_PARSE(&req, args) == FAILURE) { + goto cleanup; + } + /* Check that the request matches the signature */ + key = X509_REQ_get_pubkey(csr); + if (key == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error unpacking public key"); + goto cleanup; + } + i = X509_REQ_verify(csr, key); + + if (i < 0) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Signature verification problems"); + goto cleanup; + } + else if (i == 0) { + php_error_docref(NULL, E_WARNING, "Signature did not match the certificate request"); + goto cleanup; + } + + /* Now we can get on with it */ + + new_cert = X509_new(); + if (new_cert == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "No memory"); + goto cleanup; + } + /* Version 3 cert */ + if (!X509_set_version(new_cert, 2)) { + goto cleanup; + } + + + ASN1_INTEGER_set(X509_get_serialNumber(new_cert), (long)serial); + + X509_set_subject_name(new_cert, X509_REQ_get_subject_name(csr)); + + if (cert == NULL) { + cert = new_cert; + } + if (!X509_set_issuer_name(new_cert, X509_get_subject_name(cert))) { + php_openssl_store_errors(); + goto cleanup; + } + X509_gmtime_adj(X509_getm_notBefore(new_cert), 0); + X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*(long)num_days); + i = X509_set_pubkey(new_cert, key); + if (!i) { + php_openssl_store_errors(); + goto cleanup; + } + if (req.extensions_section) { + X509V3_CTX ctx; + + X509V3_set_ctx(&ctx, cert, new_cert, csr, NULL, 0); + X509V3_set_conf_lhash(&ctx, req.req_config); + if (!X509V3_EXT_add_conf(req.req_config, &ctx, req.extensions_section, new_cert)) { + php_openssl_store_errors(); + goto cleanup; + } + } + + /* Now sign it */ + if (!X509_sign(new_cert, priv_key, req.digest)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "failed to sign it"); + goto cleanup; + } + + /* Succeeded; lets return the cert */ + ZVAL_RES(return_value, zend_register_resource(new_cert, le_x509)); + new_cert = NULL; + +cleanup: + + if (cert == new_cert) { + cert = NULL; + } + PHP_SSL_REQ_DISPOSE(&req); + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + if (key) { + EVP_PKEY_free(key); + } + if (csr_resource == NULL && csr) { + X509_REQ_free(csr); + } + if (zcert && certresource == NULL && cert) { + X509_free(cert); + } + if (new_cert) { + X509_free(new_cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_csr_new(array dn, resource &privkey [, array configargs [, array extraattribs]]) + Generates a privkey and CSR */ +PHP_FUNCTION(openssl_csr_new) +{ + struct php_x509_request req; + zval * args = NULL, * dn, *attribs = NULL; + zval * out_pkey; + X509_REQ * csr = NULL; + int we_made_the_key = 1; + zend_resource *key_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "az|a!a!", &dn, &out_pkey, &args, &attribs) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + zval *out_pkey_val = out_pkey; + ZVAL_DEREF(out_pkey_val); + + /* Generate or use a private key */ + if (Z_TYPE_P(out_pkey_val) != IS_NULL) { + req.priv_key = php_openssl_evp_from_zval(out_pkey_val, 0, NULL, 0, 0, &key_resource); + if (req.priv_key != NULL) { + we_made_the_key = 0; + } + } + if (req.priv_key == NULL) { + php_openssl_generate_private_key(&req); + } + if (req.priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to generate a private key"); + } else { + csr = X509_REQ_new(); + if (csr) { + if (php_openssl_make_REQ(&req, csr, dn, attribs) == SUCCESS) { + X509V3_CTX ext_ctx; + + X509V3_set_ctx(&ext_ctx, NULL, NULL, csr, NULL, 0); + X509V3_set_conf_lhash(&ext_ctx, req.req_config); + + /* Add extensions */ + if (req.request_extensions_section && !X509V3_EXT_REQ_add_conf(req.req_config, + &ext_ctx, req.request_extensions_section, csr)) + { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error loading extension section %s", req.request_extensions_section); + } else { + RETVAL_TRUE; + + if (X509_REQ_sign(csr, req.priv_key, req.digest)) { + ZVAL_RES(return_value, zend_register_resource(csr, le_csr)); + csr = NULL; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error signing request"); + } + + if (we_made_the_key) { + /* and a resource for the private key */ + ZEND_TRY_ASSIGN_REF_RES(out_pkey, zend_register_resource(req.priv_key, le_key)); + req.priv_key = NULL; /* make sure the cleanup code doesn't zap it! */ + } else if (key_resource != NULL) { + req.priv_key = NULL; /* make sure the cleanup code doesn't zap it! */ + } + } + } + else { + if (!we_made_the_key) { + /* if we have not made the key we are not supposed to zap it by calling dispose! */ + req.priv_key = NULL; + } + } + } else { + php_openssl_store_errors(); + } + + } + } + if (csr) { + X509_REQ_free(csr); + } + PHP_SSL_REQ_DISPOSE(&req); +} +/* }}} */ + +/* {{{ proto mixed openssl_csr_get_subject(mixed csr) + Returns the subject of a CERT or FALSE on error */ +PHP_FUNCTION(openssl_csr_get_subject) +{ + zval * zcsr; + zend_bool use_shortnames = 1; + zend_resource *csr_resource; + X509_NAME * subject; + X509_REQ * csr; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcsr, &use_shortnames) == FAILURE) { + return; + } + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + + if (csr == NULL) { + RETURN_FALSE; + } + + subject = X509_REQ_get_subject_name(csr); + + array_init(return_value); + php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames); + + if (!csr_resource) { + X509_REQ_free(csr); + } +} +/* }}} */ + +/* {{{ proto mixed openssl_csr_get_public_key(mixed csr) + Returns the subject of a CERT or FALSE on error */ +PHP_FUNCTION(openssl_csr_get_public_key) +{ + zval * zcsr; + zend_bool use_shortnames = 1; + zend_resource *csr_resource; + + X509_REQ *orig_csr, *csr; + EVP_PKEY *tpubkey; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcsr, &use_shortnames) == FAILURE) { + return; + } + + orig_csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + + if (orig_csr == NULL) { + RETURN_FALSE; + } + +#if PHP_OPENSSL_API_VERSION >= 0x10100 + /* Due to changes in OpenSSL 1.1 related to locking when decoding CSR, + * the pub key is not changed after assigning. It means if we pass + * a private key, it will be returned including the private part. + * If we duplicate it, then we get just the public part which is + * the same behavior as for OpenSSL 1.0 */ + csr = X509_REQ_dup(orig_csr); +#else + csr = orig_csr; +#endif + + /* Retrieve the public key from the CSR */ + tpubkey = X509_REQ_get_pubkey(csr); + + if (csr != orig_csr) { + /* We need to free the duplicated CSR */ + X509_REQ_free(csr); + } + + if (!csr_resource) { + /* We also need to free the original CSR if it was freshly created */ + X509_REQ_free(orig_csr); + } + + if (tpubkey == NULL) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(tpubkey, le_key)); +} +/* }}} */ + +/* }}} */ + +/* {{{ EVP Public/Private key functions */ + +struct php_openssl_pem_password { + char *key; + int len; +}; + +/* {{{ php_openssl_pem_password_cb */ +static int php_openssl_pem_password_cb(char *buf, int size, int rwflag, void *userdata) +{ + struct php_openssl_pem_password *password = userdata; + + if (password == NULL || password->key == NULL) { + return -1; + } + + size = (password->len > size) ? size : password->len; + memcpy(buf, password->key, size); + + return size; +} +/* }}} */ + +/* {{{ php_openssl_evp_from_zval + Given a zval, coerce it into a EVP_PKEY object. + It can be: + 1. private key resource from openssl_get_privatekey() + 2. X509 resource -> public key will be extracted from it + 3. if it starts with file:// interpreted as path to key file + 4. interpreted as the data from the cert/key file and interpreted in same way as openssl_get_privatekey() + 5. an array(0 => [items 2..4], 1 => passphrase) + 6. if val is a string (possibly starting with file:///) and it is not an X509 certificate, then interpret as public key + NOTE: If you are requesting a private key but have not specified a passphrase, you should use an + empty string rather than NULL for the passphrase - NULL causes a passphrase prompt to be emitted in + the Apache error log! +*/ +static EVP_PKEY * php_openssl_evp_from_zval( + zval * val, int public_key, char *passphrase, size_t passphrase_len, + int makeresource, zend_resource **resourceval) +{ + EVP_PKEY * key = NULL; + X509 * cert = NULL; + int free_cert = 0; + zend_resource *cert_res = NULL; + char * filename = NULL; + zval tmp; + + ZVAL_NULL(&tmp); + +#define TMP_CLEAN \ + if (Z_TYPE(tmp) == IS_STRING) {\ + zval_ptr_dtor_str(&tmp); \ + } \ + return NULL; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_ARRAY) { + zval * zphrase; + + /* get passphrase */ + + if ((zphrase = zend_hash_index_find(Z_ARRVAL_P(val), 1)) == NULL) { + php_error_docref(NULL, E_WARNING, "key array must be of the form array(0 => key, 1 => phrase)"); + return NULL; + } + + if (Z_TYPE_P(zphrase) == IS_STRING) { + passphrase = Z_STRVAL_P(zphrase); + passphrase_len = Z_STRLEN_P(zphrase); + } else { + ZVAL_COPY(&tmp, zphrase); + if (!try_convert_to_string(&tmp)) { + return NULL; + } + + passphrase = Z_STRVAL(tmp); + passphrase_len = Z_STRLEN(tmp); + } + + /* now set val to be the key param and continue */ + if ((val = zend_hash_index_find(Z_ARRVAL_P(val), 0)) == NULL) { + php_error_docref(NULL, E_WARNING, "key array must be of the form array(0 => key, 1 => phrase)"); + TMP_CLEAN; + } + } + + if (Z_TYPE_P(val) == IS_RESOURCE) { + void * what; + zend_resource * res = Z_RES_P(val); + + what = zend_fetch_resource2(res, "OpenSSL X.509/key", le_x509, le_key); + if (!what) { + TMP_CLEAN; + } + if (resourceval) { + *resourceval = res; + Z_ADDREF_P(val); + } + if (res->type == le_x509) { + /* extract key from cert, depending on public_key param */ + cert = (X509*)what; + free_cert = 0; + } else if (res->type == le_key) { + int is_priv; + + is_priv = php_openssl_is_private_key((EVP_PKEY*)what); + + /* check whether it is actually a private key if requested */ + if (!public_key && !is_priv) { + php_error_docref(NULL, E_WARNING, "supplied key param is a public key"); + TMP_CLEAN; + } + + if (public_key && is_priv) { + php_error_docref(NULL, E_WARNING, "Don't know how to get public key from this private key"); + TMP_CLEAN; + } else { + if (Z_TYPE(tmp) == IS_STRING) { + zval_ptr_dtor_str(&tmp); + } + /* got the key - return it */ + return (EVP_PKEY*)what; + } + } else { + /* other types could be used here - eg: file pointers and read in the data from them */ + TMP_CLEAN; + } + } else { + /* force it to be a string and check if it refers to a file */ + /* passing non string values leaks, object uses toString, it returns NULL + * See bug38255.phpt + */ + if (!(Z_TYPE_P(val) == IS_STRING || Z_TYPE_P(val) == IS_OBJECT)) { + TMP_CLEAN; + } + if (!try_convert_to_string(val)) { + TMP_CLEAN; + } + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + filename = Z_STRVAL_P(val) + (sizeof("file://") - 1); + if (php_openssl_open_base_dir_chk(filename)) { + TMP_CLEAN; + } + } + /* it's an X509 file/cert of some kind, and we need to extract the data from that */ + if (public_key) { + cert = php_openssl_x509_from_zval(val, 0, &cert_res); + free_cert = (cert_res == NULL); + /* actual extraction done later */ + if (!cert) { + /* not a X509 certificate, try to retrieve public key */ + BIO* in; + if (filename) { + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + if (in == NULL) { + php_openssl_store_errors(); + TMP_CLEAN; + } + key = PEM_read_bio_PUBKEY(in, NULL,NULL, NULL); + BIO_free(in); + } + } else { + /* we want the private key */ + BIO *in; + + if (filename) { + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + + if (in == NULL) { + TMP_CLEAN; + } + if (passphrase == NULL) { + key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); + } else { + struct php_openssl_pem_password password; + password.key = passphrase; + password.len = passphrase_len; + key = PEM_read_bio_PrivateKey(in, NULL, php_openssl_pem_password_cb, &password); + } + BIO_free(in); + } + } + + if (key == NULL) { + php_openssl_store_errors(); + + if (public_key && cert) { + /* extract public key from X509 cert */ + key = (EVP_PKEY *) X509_get_pubkey(cert); + if (key == NULL) { + php_openssl_store_errors(); + } + } + } + + if (free_cert && cert) { + X509_free(cert); + } + if (key && makeresource && resourceval) { + *resourceval = zend_register_resource(key, le_key); + } + if (Z_TYPE(tmp) == IS_STRING) { + zval_ptr_dtor_str(&tmp); + } + return key; +} +/* }}} */ + +/* {{{ php_openssl_generate_private_key */ +static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req) +{ + char * randfile = NULL; + int egdsocket, seeded; + EVP_PKEY * return_val = NULL; + + if (req->priv_key_bits < MIN_KEY_LENGTH) { + php_error_docref(NULL, E_WARNING, "private key length is too short; it needs to be at least %d bits, not %d", + MIN_KEY_LENGTH, req->priv_key_bits); + return NULL; + } + + randfile = CONF_get_string(req->req_config, req->section_name, "RANDFILE"); + if (randfile == NULL) { + php_openssl_store_errors(); + } + php_openssl_load_rand_file(randfile, &egdsocket, &seeded); + + if ((req->priv_key = EVP_PKEY_new()) != NULL) { + switch(req->priv_key_type) { + case OPENSSL_KEYTYPE_RSA: + { + RSA* rsaparam; +#if OPENSSL_VERSION_NUMBER < 0x10002000L + /* OpenSSL 1.0.2 deprecates RSA_generate_key */ + PHP_OPENSSL_RAND_ADD_TIME(); + rsaparam = (RSA*)RSA_generate_key(req->priv_key_bits, RSA_F4, NULL, NULL); +#else + { + BIGNUM *bne = (BIGNUM *)BN_new(); + if (BN_set_word(bne, RSA_F4) != 1) { + BN_free(bne); + php_error_docref(NULL, E_WARNING, "failed setting exponent"); + return NULL; + } + rsaparam = RSA_new(); + PHP_OPENSSL_RAND_ADD_TIME(); + if (rsaparam == NULL || !RSA_generate_key_ex(rsaparam, req->priv_key_bits, bne, NULL)) { + php_openssl_store_errors(); + } + BN_free(bne); + } +#endif + if (rsaparam && EVP_PKEY_assign_RSA(req->priv_key, rsaparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } + break; +#if !defined(NO_DSA) + case OPENSSL_KEYTYPE_DSA: + PHP_OPENSSL_RAND_ADD_TIME(); + { + DSA *dsaparam = DSA_new(); + if (dsaparam && DSA_generate_parameters_ex(dsaparam, req->priv_key_bits, NULL, 0, NULL, NULL, NULL)) { + DSA_set_method(dsaparam, DSA_get_default_method()); + if (DSA_generate_key(dsaparam)) { + if (EVP_PKEY_assign_DSA(req->priv_key, dsaparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + DSA_free(dsaparam); + } + } else { + php_openssl_store_errors(); + } + } + break; +#endif +#if !defined(NO_DH) + case OPENSSL_KEYTYPE_DH: + PHP_OPENSSL_RAND_ADD_TIME(); + { + int codes = 0; + DH *dhparam = DH_new(); + if (dhparam && DH_generate_parameters_ex(dhparam, req->priv_key_bits, 2, NULL)) { + DH_set_method(dhparam, DH_get_default_method()); + if (DH_check(dhparam, &codes) && codes == 0 && DH_generate_key(dhparam)) { + if (EVP_PKEY_assign_DH(req->priv_key, dhparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + DH_free(dhparam); + } + } else { + php_openssl_store_errors(); + } + } + break; +#endif +#ifdef HAVE_EVP_PKEY_EC + case OPENSSL_KEYTYPE_EC: + { + EC_KEY *eckey; + if (req->curve_name == NID_undef) { + php_error_docref(NULL, E_WARNING, "Missing configuration value: 'curve_name' not set"); + return NULL; + } + eckey = EC_KEY_new_by_curve_name(req->curve_name); + if (eckey) { + EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); + if (EC_KEY_generate_key(eckey) && + EVP_PKEY_assign_EC_KEY(req->priv_key, eckey)) { + return_val = req->priv_key; + } else { + EC_KEY_free(eckey); + } + } + } + break; +#endif + default: + php_error_docref(NULL, E_WARNING, "Unsupported private key type"); + } + } else { + php_openssl_store_errors(); + } + + php_openssl_write_rand_file(randfile, egdsocket, seeded); + + if (return_val == NULL) { + EVP_PKEY_free(req->priv_key); + req->priv_key = NULL; + return NULL; + } + + return return_val; +} +/* }}} */ + +/* {{{ php_openssl_is_private_key + Check whether the supplied key is a private key by checking if the secret prime factors are set */ +static int php_openssl_is_private_key(EVP_PKEY* pkey) +{ + assert(pkey != NULL); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + { + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + if (rsa != NULL) { + const BIGNUM *p, *q; + + RSA_get0_factors(rsa, &p, &q); + if (p == NULL || q == NULL) { + return 0; + } + } + } + break; + case EVP_PKEY_DSA: + case EVP_PKEY_DSA1: + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: + { + DSA *dsa = EVP_PKEY_get0_DSA(pkey); + if (dsa != NULL) { + const BIGNUM *p, *q, *g, *pub_key, *priv_key; + + DSA_get0_pqg(dsa, &p, &q, &g); + if (p == NULL || q == NULL) { + return 0; + } + + DSA_get0_key(dsa, &pub_key, &priv_key); + if (priv_key == NULL) { + return 0; + } + } + } + break; + case EVP_PKEY_DH: + { + DH *dh = EVP_PKEY_get0_DH(pkey); + if (dh != NULL) { + const BIGNUM *p, *q, *g, *pub_key, *priv_key; + + DH_get0_pqg(dh, &p, &q, &g); + if (p == NULL) { + return 0; + } + + DH_get0_key(dh, &pub_key, &priv_key); + if (priv_key == NULL) { + return 0; + } + } + } + break; +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + { + EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (ec != NULL && NULL == EC_KEY_get0_private_key(ec)) { + return 0; + } + } + break; +#endif + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + break; + } + return 1; +} +/* }}} */ + +#define OPENSSL_GET_BN(_array, _bn, _name) do { \ + if (_bn != NULL) { \ + int len = BN_num_bytes(_bn); \ + zend_string *str = zend_string_alloc(len, 0); \ + BN_bn2bin(_bn, (unsigned char*)ZSTR_VAL(str)); \ + ZSTR_VAL(str)[len] = 0; \ + add_assoc_str(&_array, #_name, str); \ + } \ + } while (0); + +#define OPENSSL_PKEY_GET_BN(_type, _name) OPENSSL_GET_BN(_type, _name, _name) + +#define OPENSSL_PKEY_SET_BN(_data, _name) do { \ + zval *bn; \ + if ((bn = zend_hash_str_find(Z_ARRVAL_P(_data), #_name, sizeof(#_name)-1)) != NULL && \ + Z_TYPE_P(bn) == IS_STRING) { \ + _name = BN_bin2bn( \ + (unsigned char*)Z_STRVAL_P(bn), \ + (int)Z_STRLEN_P(bn), NULL); \ + } else { \ + _name = NULL; \ + } \ + } while (0); + +/* {{{ php_openssl_pkey_init_rsa */ +static zend_bool php_openssl_pkey_init_and_assign_rsa(EVP_PKEY *pkey, RSA *rsa, zval *data) +{ + BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; + + OPENSSL_PKEY_SET_BN(data, n); + OPENSSL_PKEY_SET_BN(data, e); + OPENSSL_PKEY_SET_BN(data, d); + if (!n || !d || !RSA_set0_key(rsa, n, e, d)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + if ((p || q) && !RSA_set0_factors(rsa, p, q)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, dmp1); + OPENSSL_PKEY_SET_BN(data, dmq1); + OPENSSL_PKEY_SET_BN(data, iqmp); + if ((dmp1 || dmq1 || iqmp) && !RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)) { + return 0; + } + + if (!EVP_PKEY_assign_RSA(pkey, rsa)) { + php_openssl_store_errors(); + return 0; + } + + return 1; +} + +/* {{{ php_openssl_pkey_init_dsa */ +static zend_bool php_openssl_pkey_init_dsa(DSA *dsa, zval *data) +{ + BIGNUM *p, *q, *g, *priv_key, *pub_key; + const BIGNUM *priv_key_const, *pub_key_const; + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + OPENSSL_PKEY_SET_BN(data, g); + if (!p || !q || !g || !DSA_set0_pqg(dsa, p, q, g)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, pub_key); + OPENSSL_PKEY_SET_BN(data, priv_key); + if (pub_key) { + return DSA_set0_key(dsa, pub_key, priv_key); + } + + /* generate key */ + PHP_OPENSSL_RAND_ADD_TIME(); + if (!DSA_generate_key(dsa)) { + php_openssl_store_errors(); + return 0; + } + + /* if BN_mod_exp return -1, then DSA_generate_key succeed for failed key + * so we need to double check that public key is created */ + DSA_get0_key(dsa, &pub_key_const, &priv_key_const); + if (!pub_key_const || BN_is_zero(pub_key_const)) { + return 0; + } + /* all good */ + return 1; +} +/* }}} */ + +/* {{{ php_openssl_dh_pub_from_priv */ +static BIGNUM *php_openssl_dh_pub_from_priv(BIGNUM *priv_key, BIGNUM *g, BIGNUM *p) +{ + BIGNUM *pub_key, *priv_key_const_time; + BN_CTX *ctx; + + pub_key = BN_new(); + if (pub_key == NULL) { + php_openssl_store_errors(); + return NULL; + } + + priv_key_const_time = BN_new(); + if (priv_key_const_time == NULL) { + BN_free(pub_key); + php_openssl_store_errors(); + return NULL; + } + ctx = BN_CTX_new(); + if (ctx == NULL) { + BN_free(pub_key); + BN_free(priv_key_const_time); + php_openssl_store_errors(); + return NULL; + } + + BN_with_flags(priv_key_const_time, priv_key, BN_FLG_CONSTTIME); + + if (!BN_mod_exp_mont(pub_key, g, priv_key_const_time, p, ctx, NULL)) { + BN_free(pub_key); + php_openssl_store_errors(); + pub_key = NULL; + } + + BN_free(priv_key_const_time); + BN_CTX_free(ctx); + + return pub_key; +} +/* }}} */ + +/* {{{ php_openssl_pkey_init_dh */ +static zend_bool php_openssl_pkey_init_dh(DH *dh, zval *data) +{ + BIGNUM *p, *q, *g, *priv_key, *pub_key; + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + OPENSSL_PKEY_SET_BN(data, g); + if (!p || !g || !DH_set0_pqg(dh, p, q, g)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, priv_key); + OPENSSL_PKEY_SET_BN(data, pub_key); + if (pub_key) { + return DH_set0_key(dh, pub_key, priv_key); + } + if (priv_key) { + pub_key = php_openssl_dh_pub_from_priv(priv_key, g, p); + if (pub_key == NULL) { + return 0; + } + return DH_set0_key(dh, pub_key, priv_key); + } + + /* generate key */ + PHP_OPENSSL_RAND_ADD_TIME(); + if (!DH_generate_key(dh)) { + php_openssl_store_errors(); + return 0; + } + /* all good */ + return 1; +} +/* }}} */ + +/* {{{ proto resource openssl_pkey_new([array configargs]) + Generates a new private key */ +PHP_FUNCTION(openssl_pkey_new) +{ + struct php_x509_request req; + zval * args = NULL; + zval *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + if (args && Z_TYPE_P(args) == IS_ARRAY) { + EVP_PKEY *pkey; + + if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "rsa", sizeof("rsa")-1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + RSA *rsa = RSA_new(); + if (rsa) { + if (php_openssl_pkey_init_and_assign_rsa(pkey, rsa, data)) { + RETURN_RES(zend_register_resource(pkey, le_key)); + } + RSA_free(rsa); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dsa", sizeof("dsa") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + DSA *dsa = DSA_new(); + if (dsa) { + if (php_openssl_pkey_init_dsa(dsa, data)) { + if (EVP_PKEY_assign_DSA(pkey, dsa)) { + RETURN_RES(zend_register_resource(pkey, le_key)); + } else { + php_openssl_store_errors(); + } + } + DSA_free(dsa); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dh", sizeof("dh") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + DH *dh = DH_new(); + if (dh) { + if (php_openssl_pkey_init_dh(dh, data)) { + if (EVP_PKEY_assign_DH(pkey, dh)) { + ZVAL_COPY_VALUE(return_value, zend_list_insert(pkey, le_key)); + return; + } else { + php_openssl_store_errors(); + } + } + DH_free(dh); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; +#ifdef HAVE_EVP_PKEY_EC + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ec", sizeof("ec") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + EC_KEY *eckey = NULL; + EC_GROUP *group = NULL; + EC_POINT *pnt = NULL; + BIGNUM *d = NULL; + pkey = EVP_PKEY_new(); + if (pkey) { + eckey = EC_KEY_new(); + if (eckey) { + EC_GROUP *group = NULL; + zval *bn; + zval *x; + zval *y; + + if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1)) != NULL && + Z_TYPE_P(bn) == IS_STRING) { + int nid = OBJ_sn2nid(Z_STRVAL_P(bn)); + if (nid != NID_undef) { + group = EC_GROUP_new_by_curve_name(nid); + if (!group) { + php_openssl_store_errors(); + goto clean_exit; + } + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + if (!EC_KEY_set_group(eckey, group)) { + php_openssl_store_errors(); + goto clean_exit; + } + } + } + + if (group == NULL) { + php_error_docref(NULL, E_WARNING, "Unknown curve_name"); + goto clean_exit; + } + + // The public key 'pnt' can be calculated from 'd' or is defined by 'x' and 'y' + if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "d", sizeof("d") - 1)) != NULL && + Z_TYPE_P(bn) == IS_STRING) { + d = BN_bin2bn((unsigned char*) Z_STRVAL_P(bn), Z_STRLEN_P(bn), NULL); + if (!EC_KEY_set_private_key(eckey, d)) { + php_openssl_store_errors(); + goto clean_exit; + } + // Calculate the public key by multiplying the Point Q with the public key + // P = d * Q + pnt = EC_POINT_new(group); + if (!pnt || !EC_POINT_mul(group, pnt, d, NULL, NULL, NULL)) { + php_openssl_store_errors(); + goto clean_exit; + } + + BN_free(d); + } else if ((x = zend_hash_str_find(Z_ARRVAL_P(data), "x", sizeof("x") - 1)) != NULL && + Z_TYPE_P(x) == IS_STRING && + (y = zend_hash_str_find(Z_ARRVAL_P(data), "y", sizeof("y") - 1)) != NULL && + Z_TYPE_P(y) == IS_STRING) { + pnt = EC_POINT_new(group); + if (pnt == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + if (!EC_POINT_set_affine_coordinates_GFp( + group, pnt, BN_bin2bn((unsigned char*) Z_STRVAL_P(x), Z_STRLEN_P(x), NULL), + BN_bin2bn((unsigned char*) Z_STRVAL_P(y), Z_STRLEN_P(y), NULL), NULL)) { + php_openssl_store_errors(); + goto clean_exit; + } + } + + if (pnt != NULL) { + if (!EC_KEY_set_public_key(eckey, pnt)) { + php_openssl_store_errors(); + goto clean_exit; + } + EC_POINT_free(pnt); + pnt = NULL; + } + + if (!EC_KEY_check_key(eckey)) { + PHP_OPENSSL_RAND_ADD_TIME(); + EC_KEY_generate_key(eckey); + php_openssl_store_errors(); + } + if (EC_KEY_check_key(eckey) && EVP_PKEY_assign_EC_KEY(pkey, eckey)) { + EC_GROUP_free(group); + RETURN_RES(zend_register_resource(pkey, le_key)); + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + } +clean_exit: + if (d != NULL) { + BN_free(d); + } + if (pnt != NULL) { + EC_POINT_free(pnt); + } + if (group != NULL) { + EC_GROUP_free(group); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + RETURN_FALSE; +#endif + } + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + if (php_openssl_generate_private_key(&req)) { + /* pass back a key resource */ + RETVAL_RES(zend_register_resource(req.priv_key, le_key)); + /* make sure the cleanup code doesn't zap it! */ + req.priv_key = NULL; + } + } + PHP_SSL_REQ_DISPOSE(&req); +} +/* }}} */ + +/* {{{ proto bool openssl_pkey_export_to_file(mixed key, string outfilename [, string passphrase, array config_args) + Gets an exportable representation of a key into a file */ +PHP_FUNCTION(openssl_pkey_export_to_file) +{ + struct php_x509_request req; + zval * zpkey, * args = NULL; + char * passphrase = NULL; + size_t passphrase_len = 0; + char * filename = NULL; + size_t filename_len = 0; + zend_resource *key_resource = NULL; + int pem_write = 0; + EVP_PKEY * key; + BIO * bio_out = NULL; + const EVP_CIPHER * cipher; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zp|s!a!", &zpkey, &filename, &filename_len, &passphrase, &passphrase_len, &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + key = php_openssl_evp_from_zval(zpkey, 0, passphrase, passphrase_len, 0, &key_resource); + + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get key from parameter 1"); + RETURN_FALSE; + } + + if (php_openssl_open_base_dir_chk(filename)) { + RETURN_FALSE; + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + if (passphrase && req.priv_key_encrypt) { + if (req.priv_key_encrypt_cipher) { + cipher = req.priv_key_encrypt_cipher; + } else { + cipher = (EVP_CIPHER *) EVP_des_ede3_cbc(); + } + } else { + cipher = NULL; + } + + switch (EVP_PKEY_base_id(key)) { +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey( + bio_out, EVP_PKEY_get0_EC_KEY(key), cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; +#endif + default: + pem_write = PEM_write_bio_PrivateKey( + bio_out, key, cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; + } + + if (pem_write) { + /* Success! + * If returning the output as a string, do so now */ + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + } + +clean_exit: + PHP_SSL_REQ_DISPOSE(&req); + + if (key_resource == NULL && key) { + EVP_PKEY_free(key); + } + if (bio_out) { + BIO_free(bio_out); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkey_export(mixed key, &mixed out [, string passphrase [, array config_args]]) + Gets an exportable representation of a key into a string or file */ +PHP_FUNCTION(openssl_pkey_export) +{ + struct php_x509_request req; + zval * zpkey, * args = NULL, *out; + char * passphrase = NULL; size_t passphrase_len = 0; + int pem_write = 0; + zend_resource *key_resource = NULL; + EVP_PKEY * key; + BIO * bio_out = NULL; + const EVP_CIPHER * cipher; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|s!a!", &zpkey, &out, &passphrase, &passphrase_len, &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + key = php_openssl_evp_from_zval(zpkey, 0, passphrase, passphrase_len, 0, &key_resource); + + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get key from parameter 1"); + RETURN_FALSE; + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + bio_out = BIO_new(BIO_s_mem()); + + if (passphrase && req.priv_key_encrypt) { + if (req.priv_key_encrypt_cipher) { + cipher = req.priv_key_encrypt_cipher; + } else { + cipher = (EVP_CIPHER *) EVP_des_ede3_cbc(); + } + } else { + cipher = NULL; + } + + switch (EVP_PKEY_base_id(key)) { +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey( + bio_out, EVP_PKEY_get0_EC_KEY(key), cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; +#endif + default: + pem_write = PEM_write_bio_PrivateKey( + bio_out, key, cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; + } + + if (pem_write) { + /* Success! + * If returning the output as a string, do so now */ + + char * bio_mem_ptr; + long bio_mem_len; + RETVAL_TRUE; + + bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr); + ZEND_TRY_ASSIGN_REF_STRINGL(out, bio_mem_ptr, bio_mem_len); + } else { + php_openssl_store_errors(); + } + } + PHP_SSL_REQ_DISPOSE(&req); + + if (key_resource == NULL && key) { + EVP_PKEY_free(key); + } + if (bio_out) { + BIO_free(bio_out); + } +} +/* }}} */ + +/* {{{ proto int openssl_pkey_get_public(mixed cert) + Gets public key from X.509 certificate */ +PHP_FUNCTION(openssl_pkey_get_public) +{ + zval *cert; + EVP_PKEY *pkey; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &cert) == FAILURE) { + return; + } + pkey = php_openssl_evp_from_zval(cert, 1, NULL, 0, 1, &res); + if (pkey == NULL) { + RETURN_FALSE; + } + ZVAL_RES(return_value, res); +} +/* }}} */ + +/* {{{ proto void openssl_pkey_free(int key) + Frees a key */ +PHP_FUNCTION(openssl_pkey_free) +{ + zval *key; + EVP_PKEY *pkey; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + zend_list_close(Z_RES_P(key)); +} +/* }}} */ + +/* {{{ proto int openssl_pkey_get_private(string key [, string passphrase]) + Gets private keys */ +PHP_FUNCTION(openssl_pkey_get_private) +{ + zval *cert; + EVP_PKEY *pkey; + char * passphrase = ""; + size_t passphrase_len = sizeof("")-1; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &cert, &passphrase, &passphrase_len) == FAILURE) { + return; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + pkey = php_openssl_evp_from_zval(cert, 0, passphrase, passphrase_len, 1, &res); + + if (pkey == NULL) { + RETURN_FALSE; + } + ZVAL_RES(return_value, res); +} + +/* }}} */ + +/* {{{ proto resource openssl_pkey_get_details(resource key) + returns an array with the key details (bits, pkey, type)*/ +PHP_FUNCTION(openssl_pkey_get_details) +{ + zval *key; + EVP_PKEY *pkey; + BIO *out; + unsigned int pbio_len; + char *pbio; + zend_long ktype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + out = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_PUBKEY(out, pkey)) { + BIO_free(out); + php_openssl_store_errors(); + RETURN_FALSE; + } + pbio_len = BIO_get_mem_data(out, &pbio); + + array_init(return_value); + add_assoc_long(return_value, "bits", EVP_PKEY_bits(pkey)); + add_assoc_stringl(return_value, "key", pbio, pbio_len); + /*TODO: Use the real values once the openssl constants are used + * See the enum at the top of this file + */ + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + { + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + ktype = OPENSSL_KEYTYPE_RSA; + + if (rsa != NULL) { + zval z_rsa; + const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; + + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + + array_init(&z_rsa); + OPENSSL_PKEY_GET_BN(z_rsa, n); + OPENSSL_PKEY_GET_BN(z_rsa, e); + OPENSSL_PKEY_GET_BN(z_rsa, d); + OPENSSL_PKEY_GET_BN(z_rsa, p); + OPENSSL_PKEY_GET_BN(z_rsa, q); + OPENSSL_PKEY_GET_BN(z_rsa, dmp1); + OPENSSL_PKEY_GET_BN(z_rsa, dmq1); + OPENSSL_PKEY_GET_BN(z_rsa, iqmp); + add_assoc_zval(return_value, "rsa", &z_rsa); + } + } + break; + case EVP_PKEY_DSA: + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: + { + DSA *dsa = EVP_PKEY_get0_DSA(pkey); + ktype = OPENSSL_KEYTYPE_DSA; + + if (dsa != NULL) { + zval z_dsa; + const BIGNUM *p, *q, *g, *priv_key, *pub_key; + + DSA_get0_pqg(dsa, &p, &q, &g); + DSA_get0_key(dsa, &pub_key, &priv_key); + + array_init(&z_dsa); + OPENSSL_PKEY_GET_BN(z_dsa, p); + OPENSSL_PKEY_GET_BN(z_dsa, q); + OPENSSL_PKEY_GET_BN(z_dsa, g); + OPENSSL_PKEY_GET_BN(z_dsa, priv_key); + OPENSSL_PKEY_GET_BN(z_dsa, pub_key); + add_assoc_zval(return_value, "dsa", &z_dsa); + } + } + break; + case EVP_PKEY_DH: + { + DH *dh = EVP_PKEY_get0_DH(pkey); + ktype = OPENSSL_KEYTYPE_DH; + + if (dh != NULL) { + zval z_dh; + const BIGNUM *p, *q, *g, *priv_key, *pub_key; + + DH_get0_pqg(dh, &p, &q, &g); + DH_get0_key(dh, &pub_key, &priv_key); + + array_init(&z_dh); + OPENSSL_PKEY_GET_BN(z_dh, p); + OPENSSL_PKEY_GET_BN(z_dh, g); + OPENSSL_PKEY_GET_BN(z_dh, priv_key); + OPENSSL_PKEY_GET_BN(z_dh, pub_key); + add_assoc_zval(return_value, "dh", &z_dh); + } + } + break; +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + ktype = OPENSSL_KEYTYPE_EC; + if (EVP_PKEY_get0_EC_KEY(pkey) != NULL) { + zval ec; + const EC_GROUP *ec_group; + const EC_POINT *pub; + int nid; + char *crv_sn; + ASN1_OBJECT *obj; + // openssl recommends a buffer length of 80 + char oir_buf[80]; + const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + const BIGNUM *d; + + ec_group = EC_KEY_get0_group(ec_key); + + // Curve nid (numerical identifier) used for ASN1 mapping + nid = EC_GROUP_get_curve_name(ec_group); + if (nid == NID_undef) { + break; + } + array_init(&ec); + + // Short object name + crv_sn = (char*) OBJ_nid2sn(nid); + if (crv_sn != NULL) { + add_assoc_string(&ec, "curve_name", crv_sn); + } + + obj = OBJ_nid2obj(nid); + if (obj != NULL) { + int oir_len = OBJ_obj2txt(oir_buf, sizeof(oir_buf), obj, 1); + add_assoc_stringl(&ec, "curve_oid", (char*) oir_buf, oir_len); + ASN1_OBJECT_free(obj); + } + + pub = EC_KEY_get0_public_key(ec_key); + + if (EC_POINT_get_affine_coordinates_GFp(ec_group, pub, x, y, NULL)) { + OPENSSL_GET_BN(ec, x, x); + OPENSSL_GET_BN(ec, y, y); + } else { + php_openssl_store_errors(); + } + + if ((d = EC_KEY_get0_private_key(EVP_PKEY_get0_EC_KEY(pkey))) != NULL) { + OPENSSL_GET_BN(ec, d, d); + } + + add_assoc_zval(return_value, "ec", &ec); + + BN_free(x); + BN_free(y); + } + break; +#endif + default: + ktype = -1; + break; + } + add_assoc_long(return_value, "type", ktype); + + BIO_free(out); +} +/* }}} */ + +/* {{{ proto string openssl_dh_compute_key(string pub_key, resource dh_key) + Computes shared secret for public value of remote DH key and local DH key */ +PHP_FUNCTION(openssl_dh_compute_key) +{ + zval *key; + char *pub_str; + size_t pub_len; + DH *dh; + EVP_PKEY *pkey; + BIGNUM *pub; + zend_string *data; + int len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sr", &pub_str, &pub_len, &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) { + RETURN_FALSE; + } + dh = EVP_PKEY_get0_DH(pkey); + if (dh == NULL) { + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(pub_len, pub_key); + pub = BN_bin2bn((unsigned char*)pub_str, (int)pub_len, NULL); + + data = zend_string_alloc(DH_size(dh), 0); + len = DH_compute_key((unsigned char*)ZSTR_VAL(data), pub, dh); + + if (len >= 0) { + ZSTR_LEN(data) = len; + ZSTR_VAL(data)[len] = 0; + RETVAL_NEW_STR(data); + } else { + php_openssl_store_errors(); + zend_string_release_ex(data, 0); + RETVAL_FALSE; + } + + BN_free(pub); +} +/* }}} */ + +/* {{{ proto string openssl_pkey_derive(peer_pub_key, priv_key, int keylen=NULL) + Computes shared secret for public value of remote and local DH or ECDH key */ +PHP_FUNCTION(openssl_pkey_derive) +{ + zval *priv_key; + zval *peer_pub_key; + EVP_PKEY *pkey; + EVP_PKEY *peer_key; + size_t key_size; + zend_long key_len = 0; + zend_string *result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|l", &peer_pub_key, &priv_key, &key_len) == FAILURE) { + RETURN_FALSE; + } + if (key_len < 0) { + php_error_docref(NULL, E_WARNING, "keylen < 0, assuming NULL"); + } + key_size = key_len; + if ((pkey = php_openssl_evp_from_zval(priv_key, 0, "", 0, 0, NULL)) == NULL + || (peer_key = php_openssl_evp_from_zval(peer_pub_key, 1, NULL, 0, 0, NULL)) == NULL) { + RETURN_FALSE; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RETURN_FALSE; + } + if (EVP_PKEY_derive_init(ctx) > 0 + && EVP_PKEY_derive_set_peer(ctx, peer_key) > 0 + && (key_size > 0 || EVP_PKEY_derive(ctx, NULL, &key_size) > 0) + && (result = zend_string_alloc(key_size, 0)) != NULL) { + if (EVP_PKEY_derive(ctx, (unsigned char*)ZSTR_VAL(result), &key_size) > 0) { + ZSTR_LEN(result) = key_size; + ZSTR_VAL(result)[key_size] = 0; + RETVAL_NEW_STR(result); + } else { + php_openssl_store_errors(); + zend_string_release_ex(result, 0); + RETVAL_FALSE; + } + } else { + RETVAL_FALSE; + } + EVP_PKEY_CTX_free(ctx); +} +/* }}} */ + + +/* {{{ proto string openssl_pbkdf2(string password, string salt, int key_length, int iterations [, string digest_method = "sha1"]) + Generates a PKCS5 v2 PBKDF2 string, defaults to sha1 */ +PHP_FUNCTION(openssl_pbkdf2) +{ + zend_long key_length = 0, iterations = 0; + char *password; + size_t password_len; + char *salt; + size_t salt_len; + char *method; + size_t method_len = 0; + zend_string *out_buffer; + + const EVP_MD *digest; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssll|s", + &password, &password_len, + &salt, &salt_len, + &key_length, &iterations, + &method, &method_len) == FAILURE) { + return; + } + + if (key_length <= 0) { + RETURN_FALSE; + } + + if (method_len) { + digest = EVP_get_digestbyname(method); + } else { + digest = EVP_sha1(); + } + + if (!digest) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_LONG_TO_INT(key_length, key); + PHP_OPENSSL_CHECK_LONG_TO_INT(iterations, iterations); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(salt_len, salt); + + out_buffer = zend_string_alloc(key_length, 0); + + if (PKCS5_PBKDF2_HMAC(password, (int)password_len, (unsigned char *)salt, (int)salt_len, (int)iterations, digest, (int)key_length, (unsigned char*)ZSTR_VAL(out_buffer)) == 1) { + ZSTR_VAL(out_buffer)[key_length] = 0; + RETURN_NEW_STR(out_buffer); + } else { + php_openssl_store_errors(); + zend_string_release_ex(out_buffer, 0); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ PKCS7 S/MIME functions */ + +/* {{{ proto bool openssl_pkcs7_verify(string filename, int flags [, string signerscerts [, array cainfo [, string extracerts [, string content [, string pk7]]]]]) + Verifys that the data block is intact, the signer is who they say they are, and returns the CERTs of the signers */ +PHP_FUNCTION(openssl_pkcs7_verify) +{ + X509_STORE * store = NULL; + zval * cainfo = NULL; + STACK_OF(X509) *signers= NULL; + STACK_OF(X509) *others = NULL; + PKCS7 * p7 = NULL; + BIO * in = NULL, * datain = NULL, * dataout = NULL, * p7bout = NULL; + zend_long flags = 0; + char * filename; + size_t filename_len; + char * extracerts = NULL; + size_t extracerts_len = 0; + char * signersfilename = NULL; + size_t signersfilename_len = 0; + char * datafilename = NULL; + size_t datafilename_len = 0; + char * p7bfilename = NULL; + size_t p7bfilename_len = 0; + + RETVAL_LONG(-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pl|pappp", &filename, &filename_len, + &flags, &signersfilename, &signersfilename_len, &cainfo, + &extracerts, &extracerts_len, &datafilename, &datafilename_len, &p7bfilename, &p7bfilename_len) == FAILURE) { + return; + } + + if (extracerts) { + others = php_openssl_load_all_certs_from_file(extracerts); + if (others == NULL) { + goto clean_exit; + } + } + + flags = flags & ~PKCS7_DETACHED; + + store = php_openssl_setup_verify(cainfo); + + if (!store) { + goto clean_exit; + } + if (php_openssl_open_base_dir_chk(filename)) { + goto clean_exit; + } + + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (in == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + p7 = SMIME_read_PKCS7(in, &datain); + if (p7 == NULL) { +#if DEBUG_SMIME + zend_printf("SMIME_read_PKCS7 failed\n"); +#endif + php_openssl_store_errors(); + goto clean_exit; + } + + if (datafilename) { + + if (php_openssl_open_base_dir_chk(datafilename)) { + goto clean_exit; + } + + dataout = BIO_new_file(datafilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (dataout == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + + if (p7bfilename) { + + if (php_openssl_open_base_dir_chk(p7bfilename)) { + goto clean_exit; + } + + p7bout = BIO_new_file(p7bfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (p7bout == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } +#if DEBUG_SMIME + zend_printf("Calling PKCS7 verify\n"); +#endif + + if (PKCS7_verify(p7, others, store, datain, dataout, (int)flags)) { + + RETVAL_TRUE; + + if (signersfilename) { + BIO *certout; + + if (php_openssl_open_base_dir_chk(signersfilename)) { + goto clean_exit; + } + + certout = BIO_new_file(signersfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (certout) { + int i; + signers = PKCS7_get0_signers(p7, NULL, (int)flags); + if (signers != NULL) { + + for (i = 0; i < sk_X509_num(signers); i++) { + if (!PEM_write_bio_X509(certout, sk_X509_value(signers, i))) { + php_openssl_store_errors(); + RETVAL_LONG(-1); + php_error_docref(NULL, E_WARNING, "failed to write signer %d", i); + } + } + + sk_X509_free(signers); + } else { + RETVAL_LONG(-1); + php_openssl_store_errors(); + } + + BIO_free(certout); + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "signature OK, but cannot open %s for writing", signersfilename); + RETVAL_LONG(-1); + } + + if (p7bout) { + PEM_write_bio_PKCS7(p7bout, p7); + } + } + } else { + php_openssl_store_errors(); + RETVAL_FALSE; + } +clean_exit: + if (p7bout) { + BIO_free(p7bout); + } + X509_STORE_free(store); + BIO_free(datain); + BIO_free(in); + BIO_free(dataout); + PKCS7_free(p7); + sk_X509_pop_free(others, X509_free); +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_encrypt(string infile, string outfile, mixed recipcerts, array headers [, int flags [, int cipher]]) + Encrypts the message in the file named infile with the certificates in recipcerts and output the result to the file named outfile */ +PHP_FUNCTION(openssl_pkcs7_encrypt) +{ + zval * zrecipcerts, * zheaders = NULL; + STACK_OF(X509) * recipcerts = NULL; + BIO * infile = NULL, * outfile = NULL; + zend_long flags = 0; + PKCS7 * p7 = NULL; + zval * zcertval; + X509 * cert; + const EVP_CIPHER *cipher = NULL; + zend_long cipherid = PHP_OPENSSL_CIPHER_DEFAULT; + zend_string * strindex; + char * infilename = NULL; + size_t infilename_len; + char * outfilename = NULL; + size_t outfilename_len; + + RETVAL_FALSE; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppza!|ll", &infilename, &infilename_len, + &outfilename, &outfilename_len, &zrecipcerts, &zheaders, &flags, &cipherid) == FAILURE) + return; + + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + return; + } + + infile = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (infile == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + outfile = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(flags)); + if (outfile == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + recipcerts = sk_X509_new_null(); + + /* get certs */ + if (Z_TYPE_P(zrecipcerts) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zrecipcerts), zcertval) { + zend_resource *certresource; + + cert = php_openssl_x509_from_zval(zcertval, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + /* we shouldn't free this particular cert, as it is a resource. + make a copy and push that on the stack instead */ + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(recipcerts, cert); + } ZEND_HASH_FOREACH_END(); + } else { + /* a single certificate */ + zend_resource *certresource; + + cert = php_openssl_x509_from_zval(zrecipcerts, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + /* we shouldn't free this particular cert, as it is a resource. + make a copy and push that on the stack instead */ + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(recipcerts, cert); + } + + /* sanity check the cipher */ + cipher = php_openssl_get_evp_cipher_from_algo(cipherid); + if (cipher == NULL) { + /* shouldn't happen */ + php_error_docref(NULL, E_WARNING, "Failed to get cipher"); + goto clean_exit; + } + + p7 = PKCS7_encrypt(recipcerts, infile, (EVP_CIPHER*)cipher, (int)flags); + + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + /* tack on extra headers */ + if (zheaders) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), strindex, zcertval) { + zend_string *str = zval_try_get_string(zcertval); + if (UNEXPECTED(!str)) { + goto clean_exit; + } + if (strindex) { + BIO_printf(outfile, "%s: %s\n", ZSTR_VAL(strindex), ZSTR_VAL(str)); + } else { + BIO_printf(outfile, "%s\n", ZSTR_VAL(str)); + } + zend_string_release(str); + } ZEND_HASH_FOREACH_END(); + } + + (void)BIO_reset(infile); + + /* write the encrypted data */ + if (!SMIME_write_PKCS7(outfile, p7, infile, (int)flags)) { + php_openssl_store_errors(); + goto clean_exit; + } + + RETVAL_TRUE; + +clean_exit: + PKCS7_free(p7); + BIO_free(infile); + BIO_free(outfile); + if (recipcerts) { + sk_X509_pop_free(recipcerts, X509_free); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_read(string P7B, array &certs) + Exports the PKCS7 file to an array of PEM certificates */ +PHP_FUNCTION(openssl_pkcs7_read) +{ + zval * zout = NULL, zcert; + char *p7b; + size_t p7b_len; + STACK_OF(X509) *certs = NULL; + STACK_OF(X509_CRL) *crls = NULL; + BIO * bio_in = NULL, * bio_out = NULL; + PKCS7 * p7 = NULL; + int i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &p7b, &p7b_len, + &zout) == FAILURE) { + return; + } + + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(p7b_len, p7b); + + bio_in = BIO_new(BIO_s_mem()); + if (bio_in == NULL) { + goto clean_exit; + } + + if (0 >= BIO_write(bio_in, p7b, (int)p7b_len)) { + php_openssl_store_errors(); + goto clean_exit; + } + + p7 = PEM_read_bio_PKCS7(bio_in, NULL, NULL, NULL); + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_signed: + if (p7->d.sign != NULL) { + certs = p7->d.sign->cert; + crls = p7->d.sign->crl; + } + break; + case NID_pkcs7_signedAndEnveloped: + if (p7->d.signed_and_enveloped != NULL) { + certs = p7->d.signed_and_enveloped->cert; + crls = p7->d.signed_and_enveloped->crl; + } + break; + default: + break; + } + + zout = zend_try_array_init(zout); + if (!zout) { + goto clean_exit; + } + + if (certs != NULL) { + for (i = 0; i < sk_X509_num(certs); i++) { + X509* ca = sk_X509_value(certs, i); + + bio_out = BIO_new(BIO_s_mem()); + if (bio_out && PEM_write_bio_X509(bio_out, ca)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_index_zval(zout, i, &zcert); + BIO_free(bio_out); + } + } + } + + if (crls != NULL) { + for (i = 0; i < sk_X509_CRL_num(crls); i++) { + X509_CRL* crl = sk_X509_CRL_value(crls, i); + + bio_out = BIO_new(BIO_s_mem()); + if (bio_out && PEM_write_bio_X509_CRL(bio_out, crl)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_index_zval(zout, i, &zcert); + BIO_free(bio_out); + } + } + } + + RETVAL_TRUE; + +clean_exit: + if (bio_in != NULL) { + BIO_free(bio_in); + } + + if (p7 != NULL) { + PKCS7_free(p7); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert, mixed signkey, array headers [, int flags [, string extracertsfilename]]) + Signs the MIME message in the file named infile with signcert/signkey and output the result to file name outfile. headers lists plain text headers to exclude from the signed portion of the message, and should include to, from and subject as a minimum */ + +PHP_FUNCTION(openssl_pkcs7_sign) +{ + zval * zcert, * zprivkey, * zheaders; + zval * hval; + X509 * cert = NULL; + EVP_PKEY * privkey = NULL; + zend_long flags = PKCS7_DETACHED; + PKCS7 * p7 = NULL; + BIO * infile = NULL, * outfile = NULL; + STACK_OF(X509) *others = NULL; + zend_resource *certresource = NULL, *keyresource = NULL; + zend_string * strindex; + char * infilename; + size_t infilename_len; + char * outfilename; + size_t outfilename_len; + char * extracertsfilename = NULL; + size_t extracertsfilename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppzza!|lp!", + &infilename, &infilename_len, &outfilename, &outfilename_len, + &zcert, &zprivkey, &zheaders, &flags, &extracertsfilename, + &extracertsfilename_len) == FAILURE) { + return; + } + + RETVAL_FALSE; + + if (extracertsfilename) { + others = php_openssl_load_all_certs_from_file(extracertsfilename); + if (others == NULL) { + goto clean_exit; + } + } + + privkey = php_openssl_evp_from_zval(zprivkey, 0, "", 0, 0, &keyresource); + if (privkey == NULL) { + php_error_docref(NULL, E_WARNING, "error getting private key"); + goto clean_exit; + } + + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "error getting cert"); + goto clean_exit; + } + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + goto clean_exit; + } + + infile = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (infile == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening input file %s!", infilename); + goto clean_exit; + } + + outfile = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (outfile == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening output file %s!", outfilename); + goto clean_exit; + } + + p7 = PKCS7_sign(cert, privkey, others, infile, (int)flags); + if (p7 == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error creating PKCS7 structure!"); + goto clean_exit; + } + + (void)BIO_reset(infile); + + /* tack on extra headers */ + if (zheaders) { + int ret; + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), strindex, hval) { + zend_string *str = zval_try_get_string(hval); + if (UNEXPECTED(!str)) { + goto clean_exit; + } + if (strindex) { + ret = BIO_printf(outfile, "%s: %s\n", ZSTR_VAL(strindex), ZSTR_VAL(str)); + } else { + ret = BIO_printf(outfile, "%s\n", ZSTR_VAL(str)); + } + zend_string_release(str); + if (ret < 0) { + php_openssl_store_errors(); + } + } ZEND_HASH_FOREACH_END(); + } + /* write the signed data */ + if (!SMIME_write_PKCS7(outfile, p7, infile, (int)flags)) { + php_openssl_store_errors(); + goto clean_exit; + } + + RETVAL_TRUE; + +clean_exit: + PKCS7_free(p7); + BIO_free(infile); + BIO_free(outfile); + if (others) { + sk_X509_pop_free(others, X509_free); + } + if (privkey && keyresource == NULL) { + EVP_PKEY_free(privkey); + } + if (cert && certresource == NULL) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed recipcert [, mixed recipkey]) + Decrypts the S/MIME message in the file name infilename and output the results to the file name outfilename. recipcert is a CERT for one of the recipients. recipkey specifies the private key matching recipcert, if recipcert does not include the key */ + +PHP_FUNCTION(openssl_pkcs7_decrypt) +{ + zval * recipcert, * recipkey = NULL; + X509 * cert = NULL; + EVP_PKEY * key = NULL; + zend_resource *certresval, *keyresval; + BIO * in = NULL, * out = NULL, * datain = NULL; + PKCS7 * p7 = NULL; + char * infilename; + size_t infilename_len; + char * outfilename; + size_t outfilename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppz|z", &infilename, &infilename_len, + &outfilename, &outfilename_len, &recipcert, &recipkey) == FAILURE) { + return; + } + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(recipcert, 0, &certresval); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "unable to coerce parameter 3 to x509 cert"); + goto clean_exit; + } + + key = php_openssl_evp_from_zval(recipkey ? recipkey : recipcert, 0, "", 0, 0, &keyresval); + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "unable to get private key"); + goto clean_exit; + } + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + goto clean_exit; + } + + in = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (in == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + out = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (out == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + p7 = SMIME_read_PKCS7(in, &datain); + + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + if (PKCS7_decrypt(p7, key, cert, out, PKCS7_DETACHED)) { + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } +clean_exit: + PKCS7_free(p7); + BIO_free(datain); + BIO_free(in); + BIO_free(out); + if (cert && certresval == NULL) { + X509_free(cert); + } + if (key && keyresval == NULL) { + EVP_PKEY_free(key); + } +} +/* }}} */ + +/* }}} */ + +/* {{{ proto bool openssl_private_encrypt(string data, string &crypted, mixed key [, int padding]) + Encrypts data with private key */ +PHP_FUNCTION(openssl_private_encrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + int successful = 0; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + zend_long padding = RSA_PKCS1_PADDING; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key param is not a valid private key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = zend_string_alloc(cryptedlen, 0); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + successful = (RSA_private_encrypt((int)data_len, + (unsigned char *)data, + (unsigned char *)ZSTR_VAL(cryptedbuf), + EVP_PKEY_get0_RSA(pkey), + (int)padding) == cryptedlen); + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + } + + if (successful) { + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZEND_TRY_ASSIGN_REF_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto bool openssl_private_decrypt(string data, string &decrypted, mixed key [, int padding]) + Decrypts data with private key */ +PHP_FUNCTION(openssl_private_decrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + unsigned char *crypttemp; + int successful = 0; + zend_long padding = RSA_PKCS1_PADDING; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid private key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + cryptedlen = RSA_private_decrypt((int)data_len, + (unsigned char *)data, + crypttemp, + EVP_PKEY_get0_RSA(pkey), + (int)padding); + if (cryptedlen != -1) { + cryptedbuf = zend_string_alloc(cryptedlen, 0); + memcpy(ZSTR_VAL(cryptedbuf), crypttemp, cryptedlen); + successful = 1; + } + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + } + + efree(crypttemp); + + if (successful) { + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZEND_TRY_ASSIGN_REF_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_public_encrypt(string data, string &crypted, mixed key [, int padding]) + Encrypts data with public key */ +PHP_FUNCTION(openssl_public_encrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf; + int successful = 0; + zend_resource *keyresource = NULL; + zend_long padding = RSA_PKCS1_PADDING; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) + return; + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid public key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = zend_string_alloc(cryptedlen, 0); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + successful = (RSA_public_encrypt((int)data_len, + (unsigned char *)data, + (unsigned char *)ZSTR_VAL(cryptedbuf), + EVP_PKEY_get0_RSA(pkey), + (int)padding) == cryptedlen); + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + + } + + if (successful) { + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZEND_TRY_ASSIGN_REF_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_public_decrypt(string data, string &crypted, resource key [, int padding]) + Decrypts data with public key */ +PHP_FUNCTION(openssl_public_decrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + unsigned char *crypttemp; + int successful = 0; + zend_resource *keyresource = NULL; + zend_long padding = RSA_PKCS1_PADDING; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid public key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + cryptedlen = RSA_public_decrypt((int)data_len, + (unsigned char *)data, + crypttemp, + EVP_PKEY_get0_RSA(pkey), + (int)padding); + if (cryptedlen != -1) { + cryptedbuf = zend_string_alloc(cryptedlen, 0); + memcpy(ZSTR_VAL(cryptedbuf), crypttemp, cryptedlen); + successful = 1; + } + break; + + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + + } + + efree(crypttemp); + + if (successful) { + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZEND_TRY_ASSIGN_REF_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto mixed openssl_error_string(void) + Returns a description of the last error, and alters the index of the error messages. Returns false when the are no more messages */ +PHP_FUNCTION(openssl_error_string) +{ + char buf[256]; + unsigned long val; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + php_openssl_store_errors(); + + if (OPENSSL_G(errors) == NULL || OPENSSL_G(errors)->top == OPENSSL_G(errors)->bottom) { + RETURN_FALSE; + } + + OPENSSL_G(errors)->bottom = (OPENSSL_G(errors)->bottom + 1) % ERR_NUM_ERRORS; + val = OPENSSL_G(errors)->buffer[OPENSSL_G(errors)->bottom]; + + if (val) { + ERR_error_string_n(val, buf, 256); + RETURN_STRING(buf); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool openssl_sign(string data, &string signature, mixed key[, mixed method]) + Signs data */ +PHP_FUNCTION(openssl_sign) +{ + zval *key, *signature; + EVP_PKEY *pkey; + unsigned int siglen; + zend_string *sigbuf; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + EVP_MD_CTX *md_ctx; + zval *method = NULL; + zend_long signature_algo = OPENSSL_ALGO_SHA1; + const EVP_MD *mdtype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|z", &data, &data_len, &signature, &key, &method) == FAILURE) { + return; + } + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "supplied key param cannot be coerced into a private key"); + RETURN_FALSE; + } + + if (method == NULL || Z_TYPE_P(method) == IS_LONG) { + if (method != NULL) { + signature_algo = Z_LVAL_P(method); + } + mdtype = php_openssl_get_evp_md_from_algo(signature_algo); + } else if (Z_TYPE_P(method) == IS_STRING) { + mdtype = EVP_get_digestbyname(Z_STRVAL_P(method)); + } else { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + + siglen = EVP_PKEY_size(pkey); + sigbuf = zend_string_alloc(siglen, 0); + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx != NULL && + EVP_SignInit(md_ctx, mdtype) && + EVP_SignUpdate(md_ctx, data, data_len) && + EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, pkey)) { + ZSTR_VAL(sigbuf)[siglen] = '\0'; + ZSTR_LEN(sigbuf) = siglen; + ZEND_TRY_ASSIGN_REF_NEW_STR(signature, sigbuf); + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + efree(sigbuf); + RETVAL_FALSE; + } + EVP_MD_CTX_destroy(md_ctx); + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto int openssl_verify(string data, string signature, mixed key[, mixed method]) + Verifys data */ +PHP_FUNCTION(openssl_verify) +{ + zval *key; + EVP_PKEY *pkey; + int err = 0; + EVP_MD_CTX *md_ctx; + const EVP_MD *mdtype; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + char * signature; + size_t signature_len; + zval *method = NULL; + zend_long signature_algo = OPENSSL_ALGO_SHA1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz|z", &data, &data_len, &signature, &signature_len, &key, &method) == FAILURE) { + return; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(signature_len, signature); + + if (method == NULL || Z_TYPE_P(method) == IS_LONG) { + if (method != NULL) { + signature_algo = Z_LVAL_P(method); + } + mdtype = php_openssl_get_evp_md_from_algo(signature_algo); + } else if (Z_TYPE_P(method) == IS_STRING) { + mdtype = EVP_get_digestbyname(Z_STRVAL_P(method)); + } else { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "supplied key param cannot be coerced into a public key"); + RETURN_FALSE; + } + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL || + !EVP_VerifyInit (md_ctx, mdtype) || + !EVP_VerifyUpdate (md_ctx, data, data_len) || + (err = EVP_VerifyFinal(md_ctx, (unsigned char *)signature, (unsigned int)signature_len, pkey)) < 0) { + php_openssl_store_errors(); + } + EVP_MD_CTX_destroy(md_ctx); + + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + RETURN_LONG(err); +} +/* }}} */ + +/* {{{ proto int openssl_seal(string data, &string sealdata, &array ekeys, array pubkeys [, string method [, &string iv]])) + Seals data */ +PHP_FUNCTION(openssl_seal) +{ + zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL; + HashTable *pubkeysht; + EVP_PKEY **pkeys; + zend_resource ** key_resources; /* so we know what to cleanup */ + int i, len1, len2, *eksl, nkeys, iv_len; + unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks; + char * data; + size_t data_len; + char *method =NULL; + size_t method_len = 0; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szza|sz", &data, &data_len, + &sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) { + return; + } + pubkeysht = Z_ARRVAL_P(pubkeys); + nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0; + if (!nkeys) { + php_error_docref(NULL, E_WARNING, "Fourth argument to openssl_seal() must be a non-empty array"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + if (method) { + cipher = EVP_get_cipherbyname(method); + if (!cipher) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + } else { + cipher = EVP_rc4(); + } + + iv_len = EVP_CIPHER_iv_length(cipher); + if (!iv && iv_len > 0) { + php_error_docref(NULL, E_WARNING, + "Cipher algorithm requires an IV to be supplied as a sixth parameter"); + RETURN_FALSE; + } + + pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0); + eksl = safe_emalloc(nkeys, sizeof(*eksl), 0); + eks = safe_emalloc(nkeys, sizeof(*eks), 0); + memset(eks, 0, sizeof(*eks) * nkeys); + key_resources = safe_emalloc(nkeys, sizeof(zend_resource*), 0); + memset(key_resources, 0, sizeof(zend_resource*) * nkeys); + memset(pkeys, 0, sizeof(*pkeys) * nkeys); + + /* get the public keys we are using to seal this data */ + i = 0; + ZEND_HASH_FOREACH_VAL(pubkeysht, pubkey) { + pkeys[i] = php_openssl_evp_from_zval(pubkey, 1, NULL, 0, 0, &key_resources[i]); + if (pkeys[i] == NULL) { + php_error_docref(NULL, E_WARNING, "not a public key (%dth member of pubkeys)", i+1); + RETVAL_FALSE; + goto clean_exit; + } + eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1); + i++; + } ZEND_HASH_FOREACH_END(); + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) { + EVP_CIPHER_CTX_free(ctx); + php_openssl_store_errors(); + RETVAL_FALSE; + goto clean_exit; + } + + /* allocate one byte extra to make room for \0 */ + buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx)); + EVP_CIPHER_CTX_reset(ctx); + + if (EVP_SealInit(ctx, cipher, eks, eksl, &iv_buf[0], pkeys, nkeys) <= 0 || + !EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) || + !EVP_SealFinal(ctx, buf + len1, &len2)) { + efree(buf); + EVP_CIPHER_CTX_free(ctx); + php_openssl_store_errors(); + RETVAL_FALSE; + goto clean_exit; + } + + if (len1 + len2 > 0) { + ZEND_TRY_ASSIGN_REF_NEW_STR(sealdata, zend_string_init((char*)buf, len1 + len2, 0)); + efree(buf); + + ekeys = zend_try_array_init(ekeys); + if (!ekeys) { + EVP_CIPHER_CTX_free(ctx); + goto clean_exit; + } + + for (i=0; i 0) { + if (!iv) { + php_error_docref(NULL, E_WARNING, + "Cipher algorithm requires an IV to be supplied as a sixth parameter"); + RETURN_FALSE; + } + if ((size_t)cipher_iv_len != iv_len) { + php_error_docref(NULL, E_WARNING, "IV length is invalid"); + RETURN_FALSE; + } + iv_buf = (unsigned char *)iv; + } else { + iv_buf = NULL; + } + + buf = emalloc(data_len + 1); + + ctx = EVP_CIPHER_CTX_new(); + if (ctx != NULL && EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey) && + EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) && + EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) { + buf[len1 + len2] = '\0'; + ZEND_TRY_ASSIGN_REF_NEW_STR(opendata, zend_string_init((char*)buf, len1 + len2, 0)); + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + RETVAL_FALSE; + } + + efree(buf); + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + EVP_CIPHER_CTX_free(ctx); +} +/* }}} */ + +static void php_openssl_add_method_or_alias(const OBJ_NAME *name, void *arg) /* {{{ */ +{ + add_next_index_string((zval*)arg, (char*)name->name); +} +/* }}} */ + +static void php_openssl_add_method(const OBJ_NAME *name, void *arg) /* {{{ */ +{ + if (name->alias == 0) { + add_next_index_string((zval*)arg, (char*)name->name); + } +} +/* }}} */ + +/* {{{ proto array openssl_get_md_methods([bool aliases = false]) + Return array of available digest methods */ +PHP_FUNCTION(openssl_get_md_methods) +{ + zend_bool aliases = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &aliases) == FAILURE) { + return; + } + array_init(return_value); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, + aliases ? php_openssl_add_method_or_alias: php_openssl_add_method, + return_value); +} +/* }}} */ + +/* {{{ proto array openssl_get_cipher_methods([bool aliases = false]) + Return array of available cipher methods */ +PHP_FUNCTION(openssl_get_cipher_methods) +{ + zend_bool aliases = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &aliases) == FAILURE) { + return; + } + array_init(return_value); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH, + aliases ? php_openssl_add_method_or_alias: php_openssl_add_method, + return_value); +} +/* }}} */ + +/* {{{ proto array openssl_get_curve_names() + Return array of available elliptic curves */ +#ifdef HAVE_EVP_PKEY_EC +PHP_FUNCTION(openssl_get_curve_names) +{ + EC_builtin_curve *curves = NULL; + const char *sname; + size_t i; + size_t len = EC_get_builtin_curves(NULL, 0); + + curves = emalloc(sizeof(EC_builtin_curve) * len); + if (!EC_get_builtin_curves(curves, len)) { + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < len; i++) { + sname = OBJ_nid2sn(curves[i].nid); + if (sname != NULL) { + add_next_index_string(return_value, sname); + } + } + efree(curves); +} +#endif +/* }}} */ + +/* {{{ proto string openssl_digest(string data, string method [, bool raw_output=false]) + Computes digest hash value for given data using given method, returns raw or binhex encoded string */ +PHP_FUNCTION(openssl_digest) +{ + zend_bool raw_output = 0; + char *data, *method; + size_t data_len, method_len; + const EVP_MD *mdtype; + EVP_MD_CTX *md_ctx; + unsigned int siglen; + zend_string *sigbuf; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &data, &data_len, &method, &method_len, &raw_output) == FAILURE) { + return; + } + mdtype = EVP_get_digestbyname(method); + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + RETURN_FALSE; + } + + siglen = EVP_MD_size(mdtype); + sigbuf = zend_string_alloc(siglen, 0); + + md_ctx = EVP_MD_CTX_create(); + if (EVP_DigestInit(md_ctx, mdtype) && + EVP_DigestUpdate(md_ctx, (unsigned char *)data, data_len) && + EVP_DigestFinal (md_ctx, (unsigned char *)ZSTR_VAL(sigbuf), &siglen)) { + if (raw_output) { + ZSTR_VAL(sigbuf)[siglen] = '\0'; + ZSTR_LEN(sigbuf) = siglen; + RETVAL_STR(sigbuf); + } else { + int digest_str_len = siglen * 2; + zend_string *digest_str = zend_string_alloc(digest_str_len, 0); + + make_digest_ex(ZSTR_VAL(digest_str), (unsigned char*)ZSTR_VAL(sigbuf), siglen); + ZSTR_VAL(digest_str)[digest_str_len] = '\0'; + zend_string_release_ex(sigbuf, 0); + RETVAL_NEW_STR(digest_str); + } + } else { + php_openssl_store_errors(); + zend_string_release_ex(sigbuf, 0); + RETVAL_FALSE; + } + + EVP_MD_CTX_destroy(md_ctx); +} +/* }}} */ + +/* Cipher mode info */ +struct php_openssl_cipher_mode { + zend_bool is_aead; + zend_bool is_single_run_aead; + int aead_get_tag_flag; + int aead_set_tag_flag; + int aead_ivlen_flag; +}; + +static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type) /* {{{ */ +{ + switch (EVP_CIPHER_mode(cipher_type)) { +#ifdef EVP_CIPH_GCM_MODE + case EVP_CIPH_GCM_MODE: + mode->is_aead = 1; + mode->is_single_run_aead = 0; + mode->aead_get_tag_flag = EVP_CTRL_GCM_GET_TAG; + mode->aead_set_tag_flag = EVP_CTRL_GCM_SET_TAG; + mode->aead_ivlen_flag = EVP_CTRL_GCM_SET_IVLEN; + break; +#endif +#ifdef EVP_CIPH_CCM_MODE + case EVP_CIPH_CCM_MODE: + mode->is_aead = 1; + mode->is_single_run_aead = 1; + mode->aead_get_tag_flag = EVP_CTRL_CCM_GET_TAG; + mode->aead_set_tag_flag = EVP_CTRL_CCM_SET_TAG; + mode->aead_ivlen_flag = EVP_CTRL_CCM_SET_IVLEN; + break; +#endif + default: + memset(mode, 0, sizeof(struct php_openssl_cipher_mode)); + } +} +/* }}} */ + +static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_required_len, + zend_bool *free_iv, EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode) /* {{{ */ +{ + char *iv_new; + + /* Best case scenario, user behaved */ + if (*piv_len == iv_required_len) { + return SUCCESS; + } + + if (mode->is_aead) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); + return FAILURE; + } + return SUCCESS; + } + + iv_new = ecalloc(1, iv_required_len + 1); + + if (*piv_len == 0) { + /* BC behavior */ + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + + } + + if (*piv_len < iv_required_len) { + php_error_docref(NULL, E_WARNING, + "IV passed is only %zd bytes long, cipher expects an IV of precisely %zd bytes, padding with \\0", + *piv_len, iv_required_len); + memcpy(iv_new, *piv, *piv_len); + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, + "IV passed is %zd bytes long which is longer than the %zd expected by selected cipher, truncating", + *piv_len, iv_required_len); + memcpy(iv_new, *piv, iv_required_len); + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + +} +/* }}} */ + +static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, + EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode, + char **ppassword, size_t *ppassword_len, zend_bool *free_password, + char **piv, size_t *piv_len, zend_bool *free_iv, + char *tag, int tag_len, zend_long options, int enc) /* {{{ */ +{ + unsigned char *key; + int key_len, password_len; + size_t max_iv_len; + + *free_password = 0; + + max_iv_len = EVP_CIPHER_iv_length(cipher_type); + if (enc && *piv_len == 0 && max_iv_len > 0 && !mode->is_aead) { + php_error_docref(NULL, E_WARNING, + "Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); + } + + if (!EVP_CipherInit_ex(cipher_ctx, cipher_type, NULL, NULL, NULL, enc)) { + php_openssl_store_errors(); + return FAILURE; + } + if (php_openssl_validate_iv(piv, piv_len, max_iv_len, free_iv, cipher_ctx, mode) == FAILURE) { + return FAILURE; + } + if (mode->is_single_run_aead && enc) { + if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); + return FAILURE; + } + } else if (!enc && tag && tag_len > 0) { + if (!mode->is_aead) { + php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher method does not support AEAD"); + } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); + return FAILURE; + } + } + /* check and set key */ + password_len = (int) *ppassword_len; + key_len = EVP_CIPHER_key_length(cipher_type); + if (key_len > password_len) { + if ((OPENSSL_DONT_ZERO_PAD_KEY & options) && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Key length cannot be set for the cipher method"); + return FAILURE; + } + key = emalloc(key_len); + memset(key, 0, key_len); + memcpy(key, *ppassword, password_len); + *ppassword = (char *) key; + *ppassword_len = key_len; + *free_password = 1; + } else { + if (password_len > key_len && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) { + php_openssl_store_errors(); + } + key = (unsigned char*)*ppassword; + } + + if (!EVP_CipherInit_ex(cipher_ctx, NULL, NULL, key, (unsigned char *)*piv, enc)) { + php_openssl_store_errors(); + return FAILURE; + } + if (options & OPENSSL_ZERO_PADDING) { + EVP_CIPHER_CTX_set_padding(cipher_ctx, 0); + } + + return SUCCESS; +} +/* }}} */ + +static int php_openssl_cipher_update(const EVP_CIPHER *cipher_type, + EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode, + zend_string **poutbuf, int *poutlen, char *data, size_t data_len, + char *aad, size_t aad_len, int enc) /* {{{ */ +{ + int i = 0; + + if (mode->is_single_run_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, NULL, (int)data_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Setting of data length failed"); + return FAILURE; + } + + if (mode->is_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, (unsigned char *)aad, (int)aad_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Setting of additional application data failed"); + return FAILURE; + } + + *poutbuf = zend_string_alloc((int)data_len + EVP_CIPHER_block_size(cipher_type), 0); + + if (!EVP_CipherUpdate(cipher_ctx, (unsigned char*)ZSTR_VAL(*poutbuf), + &i, (unsigned char *)data, (int)data_len)) { + /* we don't show warning when we fail but if we ever do, then it should look like this: + if (mode->is_single_run_aead && !enc) { + php_error_docref(NULL, E_WARNING, "Tag verifycation failed"); + } else { + php_error_docref(NULL, E_WARNING, enc ? "Encryption failed" : "Decryption failed"); + } + */ + php_openssl_store_errors(); + zend_string_release_ex(*poutbuf, 0); + return FAILURE; + } + + *poutlen = i; + + return SUCCESS; +} +/* }}} */ + + +PHP_OPENSSL_API zend_string* php_openssl_encrypt(char *data, size_t data_len, char *method, size_t method_len, char *password, size_t password_len, zend_long options, char *iv, size_t iv_len, zval *tag, zend_long tag_len, char *aad, size_t aad_len) +{ + const EVP_CIPHER *cipher_type; + EVP_CIPHER_CTX *cipher_ctx; + struct php_openssl_cipher_mode mode; + int i = 0, outlen; + zend_bool free_iv = 0, free_password = 0; + zend_string *outbuf = NULL; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(data_len, data); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(aad_len, aad); + PHP_OPENSSL_CHECK_LONG_TO_INT_NORET(tag_len, tag_len); + + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + return NULL; + } + + cipher_ctx = EVP_CIPHER_CTX_new(); + if (!cipher_ctx) { + php_error_docref(NULL, E_WARNING, "Failed to create cipher context"); + return NULL; + } + + php_openssl_load_cipher_mode(&mode, cipher_type); + + if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode, + &password, &password_len, &free_password, + &iv, &iv_len, &free_iv, NULL, tag_len, options, 1) == FAILURE || + php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen, + data, data_len, aad, aad_len, 1) == FAILURE) { + outbuf = NULL; + } else if (EVP_EncryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) { + outlen += i; + if (options & OPENSSL_RAW_DATA) { + ZSTR_VAL(outbuf)[outlen] = '\0'; + ZSTR_LEN(outbuf) = outlen; + } else { + zend_string *base64_str; + + base64_str = php_base64_encode((unsigned char*)ZSTR_VAL(outbuf), outlen); + zend_string_release_ex(outbuf, 0); + outbuf = base64_str; + } + if (mode.is_aead && tag) { + zend_string *tag_str = zend_string_alloc(tag_len, 0); + + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + ZSTR_VAL(tag_str)[tag_len] = '\0'; + ZSTR_LEN(tag_str) = tag_len; + ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str); + } else { + php_error_docref(NULL, E_WARNING, "Retrieving verification tag failed"); + zend_string_release_ex(tag_str, 0); + zend_string_release_ex(outbuf, 0); + outbuf = NULL; + } + } else if (tag) { + ZEND_TRY_ASSIGN_REF_NULL(tag); + php_error_docref(NULL, E_WARNING, + "The authenticated tag cannot be provided for cipher that doesn not support AEAD"); + } else if (mode.is_aead) { + php_error_docref(NULL, E_WARNING, "A tag should be provided when using AEAD mode"); + zend_string_release_ex(outbuf, 0); + outbuf = NULL; + } + } else { + php_openssl_store_errors(); + zend_string_release_ex(outbuf, 0); + outbuf = NULL; + } + + if (free_password) { + efree(password); + } + if (free_iv) { + efree(iv); + } + EVP_CIPHER_CTX_reset(cipher_ctx); + EVP_CIPHER_CTX_free(cipher_ctx); + return outbuf; +} + +/* {{{ proto string openssl_encrypt(string data, string method, string password [, int options=0 [, string $iv=''[, string &$tag = ''[, string $aad = ''[, int $tag_length = 16]]]]]) + Encrypts given data with given method and key, returns raw or base64 encoded string */ +PHP_FUNCTION(openssl_encrypt) +{ + zend_long options = 0, tag_len = 16; + char *data, *method, *password, *iv = "", *aad = ""; + size_t data_len, method_len, password_len, iv_len = 0, aad_len = 0; + zend_string *ret; + zval *tag = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lszsl", &data, &data_len, &method, &method_len, + &password, &password_len, &options, &iv, &iv_len, &tag, &aad, &aad_len, &tag_len) == FAILURE) { + return; + } + + if ((ret = php_openssl_encrypt(data, data_len, method, method_len, password, password_len, options, iv, iv_len, tag, tag_len, aad, aad_len))) { + RETVAL_STR(ret); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +PHP_OPENSSL_API zend_string* php_openssl_decrypt(char *data, size_t data_len, char *method, size_t method_len, char *password, size_t password_len, zend_long options, char *iv, size_t iv_len, char *tag, zend_long tag_len, char *aad, size_t aad_len) +{ + const EVP_CIPHER *cipher_type; + EVP_CIPHER_CTX *cipher_ctx; + struct php_openssl_cipher_mode mode; + int i = 0, outlen; + zend_string *base64_str = NULL; + zend_bool free_iv = 0, free_password = 0; + zend_string *outbuf = NULL; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(data_len, data); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(aad_len, aad); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NORET(tag_len, tag); + + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + return NULL; + } + + cipher_ctx = EVP_CIPHER_CTX_new(); + if (!cipher_ctx) { + php_error_docref(NULL, E_WARNING, "Failed to create cipher context"); + return NULL; + } + + php_openssl_load_cipher_mode(&mode, cipher_type); + + if (!(options & OPENSSL_RAW_DATA)) { + base64_str = php_base64_decode((unsigned char*)data, data_len); + if (!base64_str) { + php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input"); + EVP_CIPHER_CTX_free(cipher_ctx); + return NULL; + } + data_len = ZSTR_LEN(base64_str); + data = ZSTR_VAL(base64_str); + } + + if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode, + &password, &password_len, &free_password, + &iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE || + php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen, + data, data_len, aad, aad_len, 0) == FAILURE) { + outbuf = NULL; + } else if (mode.is_single_run_aead || + EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) { + outlen += i; + ZSTR_VAL(outbuf)[outlen] = '\0'; + ZSTR_LEN(outbuf) = outlen; + } else { + php_openssl_store_errors(); + zend_string_release_ex(outbuf, 0); + outbuf = NULL; + } + + if (free_password) { + efree(password); + } + if (free_iv) { + efree(iv); + } + if (base64_str) { + zend_string_release_ex(base64_str, 0); + } + EVP_CIPHER_CTX_reset(cipher_ctx); + EVP_CIPHER_CTX_free(cipher_ctx); + return outbuf; +} + +/* {{{ proto string openssl_decrypt(string data, string method, string password [, int options=0 [, string $iv = ''[, string $tag = ''[, string $aad = '']]]]) + Takes raw or base64 encoded string and decrypts it using given method and key */ +PHP_FUNCTION(openssl_decrypt) +{ + zend_long options = 0; + char *data, *method, *password, *iv = "", *tag = NULL, *aad = ""; + size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0; + zend_string *ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsss", &data, &data_len, &method, &method_len, + &password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) { + return; + } + + if (!method_len) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + if ((ret = php_openssl_decrypt(data, data_len, method, method_len, password, password_len, options, iv, iv_len, tag, tag_len, aad, aad_len))) { + RETVAL_STR(ret); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +PHP_OPENSSL_API zend_long php_openssl_cipher_iv_length(char *method) +{ + const EVP_CIPHER *cipher_type; + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + return -1; + } + + return EVP_CIPHER_iv_length(cipher_type); +} + +/* {{{ proto int openssl_cipher_iv_length(string $method) */ +PHP_FUNCTION(openssl_cipher_iv_length) +{ + char *method; + size_t method_len; + zend_long ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &method, &method_len) == FAILURE) { + return; + } + + if (!method_len) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + if ((ret = php_openssl_cipher_iv_length(method)) == -1) { + RETURN_FALSE; + } + + RETURN_LONG(ret); +} +/* }}} */ + + +PHP_OPENSSL_API zend_string* php_openssl_random_pseudo_bytes(zend_long buffer_length) +{ + zend_string *buffer = NULL; + if (buffer_length <= 0 +#ifndef PHP_WIN32 + || ZEND_LONG_INT_OVFL(buffer_length) +#endif + ) { + zend_throw_exception(zend_ce_error, "Length must be greater than 0", 0); + return NULL; + } + buffer = zend_string_alloc(buffer_length, 0); + +#ifdef PHP_WIN32 + /* random/urandom equivalent on Windows */ + if (php_win32_get_random_bytes((unsigned char*)(buffer)->val, (size_t) buffer_length) == FAILURE){ + zend_string_release_ex(buffer, 0); + zend_throw_exception(zend_ce_exception, "Error reading from source device", 0); + return NULL; + } +#else + + PHP_OPENSSL_CHECK_LONG_TO_INT_NORET(buffer_length, length); + PHP_OPENSSL_RAND_ADD_TIME(); + /* FIXME loop if requested size > INT_MAX */ + if (RAND_bytes((unsigned char*)ZSTR_VAL(buffer), (int)buffer_length) <= 0) { + zend_string_release_ex(buffer, 0); + zend_throw_exception(zend_ce_exception, "Error reading from source device", 0); + return NULL; + } else { + php_openssl_store_errors(); + } +#endif + return buffer; +} + +/* {{{ proto string openssl_random_pseudo_bytes(int length [, &bool returned_strong_result]) + Returns a string of the length specified filled with random pseudo bytes */ +PHP_FUNCTION(openssl_random_pseudo_bytes) +{ + zend_string *buffer = NULL; + zend_long buffer_length; + zval *zstrong_result_returned = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z", &buffer_length, &zstrong_result_returned) == FAILURE) { + return; + } + + if (zstrong_result_returned) { + ZEND_TRY_ASSIGN_REF_FALSE(zstrong_result_returned); + } + + if ((buffer = php_openssl_random_pseudo_bytes(buffer_length))) { + ZSTR_VAL(buffer)[buffer_length] = 0; + RETVAL_NEW_STR(buffer); + } + + if (zstrong_result_returned) { + ZEND_TRY_ASSIGN_REF_TRUE(zstrong_result_returned); + } +} +/* }}} */ diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc index b1e46b411e54e..779bfa8515cbd 100644 --- a/ext/openssl/tests/cipher_tests.inc +++ b/ext/openssl/tests/cipher_tests.inc @@ -1,5 +1,26 @@ array( + array( + 'key' => '404142434445464748494a4b4c4d4e4f', + 'iv' => '1011121314151617', + 'aad' => '000102030405060708090a0b0c0d0e0f', + 'tag' => '1fc64fbfaccd', + 'pt' => '202122232425262728292a2b2c2d2e2f', + 'ct' => 'd2a1f0e051ea5f62081a7792073d593d', + ), + array( + 'key' => '404142434445464748494a4b4c4d4e4f', + 'iv' => '101112131415161718191a1b', + 'aad' => '000102030405060708090a0b0c0d0e0f' . + '10111213', + 'tag' => '484392fbc1b09951', + 'pt' => '202122232425262728292a2b2c2d2e2f' . + '3031323334353637', + 'ct' => 'e3b201a9f5b71a7a9b1ceaeccd97e70b' . + '6176aad9a4428aa5', + ), + ), 'aes-256-ccm' => array( array( 'key' => '1bde3251d41a8b5ea013c195ae128b21' . diff --git a/ext/openssl/tests/openssl_decrypt_ccm.phpt b/ext/openssl/tests/openssl_decrypt_ccm.phpt index a5f01b87cea8b..08ef5bb7b7c43 100644 --- a/ext/openssl/tests/openssl_decrypt_ccm.phpt +++ b/ext/openssl/tests/openssl_decrypt_ccm.phpt @@ -10,14 +10,16 @@ if (!in_array('aes-256-ccm', openssl_get_cipher_methods())) --FILE-- $test) { - echo "TEST $idx\n"; - $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, - $test['iv'], $test['tag'], $test['aad']); - var_dump($test['pt'] === $pt); +foreach ($methods as $method) { + $tests = openssl_get_cipher_tests($method); + foreach ($tests as $idx => $test) { + echo "$method - TEST $idx\n"; + $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $test['tag'], $test['aad']); + var_dump($test['pt'] === $pt); + } } // no IV @@ -32,7 +34,11 @@ var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, ?> --EXPECTF-- -TEST 0 +aes-128-ccm - TEST 0 +bool(true) +aes-128-ccm - TEST 1 +bool(true) +aes-256-ccm - TEST 0 bool(true) Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d diff --git a/ext/openssl/tests/openssl_encrypt_ccm.phpt b/ext/openssl/tests/openssl_encrypt_ccm.phpt index fb5dbbc849d06..8c4c41f81870c 100644 --- a/ext/openssl/tests/openssl_encrypt_ccm.phpt +++ b/ext/openssl/tests/openssl_encrypt_ccm.phpt @@ -10,15 +10,17 @@ if (!in_array('aes-256-ccm', openssl_get_cipher_methods())) --FILE-- $test) { - echo "TEST $idx\n"; - $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA, - $test['iv'], $tag, $test['aad'], strlen($test['tag'])); - var_dump($test['ct'] === $ct); - var_dump($test['tag'] === $tag); +foreach ($methods as $method) { + $tests = openssl_get_cipher_tests($method); + foreach ($tests as $idx => $test) { + echo "$method - TEST $idx\n"; + $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $tag, $test['aad'], strlen($test['tag'])); + var_dump($test['ct'] === $ct); + var_dump($test['tag'] === $tag); + } } // Empty IV error @@ -32,7 +34,13 @@ var_dump(strlen($tag)); var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 16), $tag, '', 1024)); ?> --EXPECTF-- -TEST 0 +aes-128-ccm - TEST 0 +bool(true) +bool(true) +aes-128-ccm - TEST 1 +bool(true) +bool(true) +aes-256-ccm - TEST 0 bool(true) bool(true) From 8fd609912ba8b9ae1cfc70b9d1cbd58364a9ac71 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 18:19:10 +0000 Subject: [PATCH 03/11] commit patch 23921650 --- NEWS | 3 + NEWS.orig | 1962 ++++++++++++++++++ ext/soap/php_sdl.c | 26 +- ext/soap/php_sdl.c.orig | 3672 ++++++++++++++++++++++++++++++++++ ext/soap/php_xml.c | 4 +- ext/soap/php_xml.c.orig | 325 +++ ext/soap/tests/bug80672.phpt | 15 + ext/soap/tests/bug80672.xml | 6 + 8 files changed, 5999 insertions(+), 14 deletions(-) create mode 100644 NEWS.orig create mode 100644 ext/soap/php_sdl.c.orig create mode 100644 ext/soap/php_xml.c.orig create mode 100644 ext/soap/tests/bug80672.phpt create mode 100644 ext/soap/tests/bug80672.xml diff --git a/NEWS b/NEWS index f12b03661b720..0c97be34dd59d 100644 --- a/NEWS +++ b/NEWS @@ -1750,6 +1750,9 @@ PHP NEWS . Fixed bug #75453 (Incorrect reflection for ibase_[p]connect). (villfa) . Fixed bug #76443 (php+php_interbase.dll crash on module_shutdown). (Kalle) +- SOAP: + . Fixed bug #80672 (Null Dereference in SoapClient). (CVE-2021-21702) (cmb, Stas) + - intl: . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..f12b03661b720 --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,1962 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +11 Jun 2020, PHP 7.4.7 + +- Core: + . Fixed bug #79599 (coredump in set_error_handler). (Laruence) + . Fixed bug #79566 (Private SHM is not private on Windows). (cmb) + . Fixed bug #79489 (.user.ini does not inherit). (cmb) + . Fixed bug #79600 (Regression in 7.4.6 when yielding an array based + generator). (Nikita) + . Fixed bug #79657 ("yield from" hangs when invalid value encountered). + (Nikita) + +- FFI: + . Fixed bug #79571 (FFI: var_dumping unions may segfault). (cmb) + +- GD: + . Fixed bug #79615 (Wrong GIF header written in GD GIFEncode). (sageptr, cmb) + +- Opcache: + . Fixed bug #79588 (Boolean opcache settings ignore on/off values). (cmb) + . Fixed bug #79548 (Preloading segfault with inherited method using static + variable). (Nikita) + . Fixed bug #79603 (RTD collision with opcache). (Nikita) + +- Standard: + . Fixed bug #79561 (dns_get_record() fails with DNS_ALL). (cmb) + +14 May 2020, PHP 7.4.6 + +- Core: + . Fixed bug #79536 (zend_clear_exception prevent exception's destructor to be + called). (Laruence) + . Fixed bug #78434 (Generator yields no items after valid() call). (Nikita) + . Fixed bug #79477 (casting object into array creates references). (Nikita) + . Fixed bug #79514 (Memory leaks while including unexistent file). (cmb, + Nikita) + +- DOM: + . Fixed bug #78221 (DOMNode::normalize() doesn't remove empty text nodes). + (cmb) + +- EXIF: + . Fixed bug #79336 (ext/exif/tests/bug79046.phpt fails on Big endian arch). + (Nikita) + +- FCGI: + . Fixed bug #79491 (Search for .user.ini extends up to root dir). (cmb) + +- MBString: + . Fixed bug #79441 (Segfault in mb_chr() if internal encoding is unsupported). + (Girgias) + +- OpenSSL: + . Fixed bug #79497 (stream_socket_client() throws an unknown error sometimes + with <1s timeout). (Joe Cai) + +- PCRE: + . Upgraded to PCRE2 10.34. (cmb) + +- Phar: + . Fixed bug #79503 (Memory leak on duplicate metadata). (cmb) + +- SimpleXML: + . Fixed bug #79528 (Different object of the same xml between 7.4.5 and + 7.4.4). (cmb) + +- SPL: + . Fixed bug #69264 (__debugInfo() ignored while extending SPL classes). (cmb) + . Fixed bug #67369 (ArrayObject serialization drops the iterator class). + (Alex Dowad) + +- Standard: + . Fixed bug #79468 (SIGSEGV when closing stream handle with a stream filter + appended). (dinosaur) + . Fixed bug #79447 (Serializing uninitialized typed properties with __sleep + should not throw). (nicolas-grekas) + +16 Apr 2020, PHP 7.4.5 + +- Core: + . Fixed bug #79364 (When copy empty array, next key is unspecified). (cmb) + . Fixed bug #78210 (Invalid pointer address). (cmb, Nikita) + +- CURL: + . Fixed bug #79199 (curl_copy_handle() memory leak). (cmb) + +- Date: + . Fixed bug #79396 (DateTime hour incorrect during DST jump forward). (Nate + Brunette) + . Fixed bug #74940 (DateTimeZone loose comparison always true). (cmb) + +- FPM: + . Implement request #77062 (Allow numeric [UG]ID in FPM listen.{owner,group}) + (Andre Nathan) + +- Iconv: + . Fixed bug #79200 (Some iconv functions cut Windows-1258). (cmb) + +- OPcache: + . Fixed bug #79412 (Opcache chokes and uses 100% CPU on specific script). + (Dmitry) + +- Session: + . Fixed bug #79413 (session_create_id() fails for active sessions). (cmb) + +- Shmop: + . Fixed bug #79427 (Integer Overflow in shmop_open()). (cmb) + +- SimpleXML: + . Fixed bug #61597 (SXE properties may lack attributes and content). (cmb) + +- SOAP: + . Fixed bug #79357 (SOAP request segfaults when any request parameter is + missing). (Nikita) + +- Spl: + . Fixed bug #75673 (SplStack::unserialize() behavior). (cmb) + . Fixed bug #79393 (Null coalescing operator failing with SplFixedArray). + (cmb) + +- Standard: + . Fixed bug #79330 (shell_exec() silently truncates after a null byte). (stas) + . Fixed bug #79465 (OOB Read in urldecode()). (CVE-2020-7067) (stas) + . Fixed bug #79410 (system() swallows last chunk if it is exactly 4095 bytes + without newline). (Christian Schneider) + +- Zip: + . Fixed Bug #79296 (ZipArchive::open fails on empty file). (Remi) + . Fixed bug #79424 (php_zip_glob uses gl_pathc after call to globfree). + (Max Rees) + +19 Mar 2020, PHP 7.4.4 + +- Core: + . Fixed bug #79244 (php crashes during parsing INI file). (Laruence) + . Fixed bug #63206 (restore_error_handler does not restore previous errors + mask). (Mark Plomer) + +- COM: + . Fixed bug #66322 (COMPersistHelper::SaveToFile can save to wrong location). + (cmb) + . Fixed bug #79242 (COM error constants don't match com_exception codes on + x86). (cmb) + . Fixed bug #79247 (Garbage collecting variant objects segfaults). (cmb) + . Fixed bug #79248 (Traversing empty VT_ARRAY throws com_exception). (cmb) + . Fixed bug #79299 (com_print_typeinfo prints duplicate variables). (Litiano + Moura) + . Fixed bug #79332 (php_istreams are never freed). (cmb) + . Fixed bug #79333 (com_print_typeinfo() leaks memory). (cmb) + +- CURL: + . Fixed bug #79019 (Copied cURL handles upload empty file). (cmb) + . Fixed bug #79013 (Content-Length missing when posting a curlFile with + curl). (cmb) + +- DOM: + . Fixed bug #77569: (Write Access Violation in DomImplementation). (Nikita, + cmb) + . Fixed bug #79271 (DOMDocumentType::$childNodes is NULL). (cmb) + +- Enchant: + . Fixed bug #79311 (enchant_dict_suggest() fails on big endian architecture). + (cmb) + +- EXIF: + . Fixed bug #79282 (Use-of-uninitialized-value in exif). (CVE-2020-7064) + (Nikita) + +- Fileinfo: + . Fixed bug #79283 (Segfault in libmagic patch contains a buffer + overflow). (cmb) + +- FPM: + . Fixed bug #77653 (operator displayed instead of the real error message). + (Jakub Zelenka) + . Fixed bug #79014 (PHP-FPM & Primary script unknown). (Jakub Zelenka) + +- MBstring: + . Fixed bug #79371 (mb_strtolower (UTF-32LE): stack-buffer-overflow at + php_unicode_tolower_full). (CVE-2020-7065) (cmb) + +- MySQLi: + . Fixed bug #64032 (mysqli reports different client_version). (cmb) + +- MySQLnd: + . Implemented FR #79275 (Support auth_plugin_caching_sha2_password on + Windows). (cmb) + +- Opcache: + . Fixed bug #79252 (preloading causes php-fpm to segfault during exit). + (Nikita) + +- PCRE: + . Fixed bug #79188 (Memory corruption in preg_replace/preg_replace_callback + and unicode). (Nikita) + . Fixed bug #79241 (Segmentation fault on preg_match()). (Nikita) + . Fixed bug #79257 (Duplicate named groups (?J) prefer last alternative even + if not matched). (Nikita) + +- PDO_ODBC: + . Fixed bug #79038 (PDOStatement::nextRowset() leaks column values). (cmb) + +- Reflection: + . Fixed bug #79062 (Property with heredoc default value returns false for + getDocComment). (Nikita) + +- SQLite3: + . Fixed bug #79294 (::columnType() may fail after SQLite3Stmt::reset()). (cmb) + +- Standard: + . Fixed bug #79329 (get_headers() silently truncates after a null byte). + (CVE-2020-7066) (cmb) + . Fixed bug #79254 (getenv() w/o arguments not showing changes). (cmb) + . Fixed bug #79265 (Improper injection of Host header when using fopen for + http requests). (Miguel Xavier Penha Neto) + +- Zip: + . Fixed bug #79315 (ZipArchive::addFile doesn't honor start/length + parameters). (Remi) + +20 Feb 2020, PHP 7.4.3 + +- Core: + . Fixed bug #79146 (cscript can fail to run on some systems). (clarodeus) + . Fixed bug #79155 (Property nullability lost when using multiple property + definition). (Nikita) + . Fixed bug #78323 (Code 0 is returned on invalid options). (Ivan Mikheykin) + . Fixed bug #78989 (Delayed variance check involving trait segfaults). + (Nikita) + . Fixed bug #79174 (cookie values with spaces fail to round-trip). (cmb) + . Fixed bug #76047 (Use-after-free when accessing already destructed + backtrace arguments). (Nikita) + +- CURL: + . Fixed bug #79078 (Hypothetical use-after-free in curl_multi_add_handle()). + (cmb) + +- FFI: + . Fixed bug #79096 (FFI Struct Segfault). (cmb) + +- IMAP: + . Fixed bug #79112 (IMAP extension can't find OpenSSL libraries at configure + time). (Nikita) + +- Intl: + . Fixed bug #79212 (NumberFormatter::format() may detect wrong type). (cmb) + +- Libxml: + . Fixed bug #79191 (Error in SoapClient ctor disables DOMDocument::save()). + (Nikita, cmb) + +- MBString: + . Fixed bug #79149 (SEGV in mb_convert_encoding with non-string encodings). + (cmb) + +- MySQLi: + . Fixed bug #78666 (Properties may emit a warning on var_dump()). (kocsismate) + +- MySQLnd: + . Fixed bug #79084 (mysqlnd may fetch wrong column indexes with MYSQLI_BOTH). + (cmb) + . Fixed bug #79011 (MySQL caching_sha2_password Access denied for password + with more than 20 chars). (Nikita) + +- Opcache: + . Fixed bug #79114 (Eval class during preload causes class to be only half + available). (Laruence) + . Fixed bug #79128 (Preloading segfaults if preload_user is used). (Nikita) + . Fixed bug #79193 (Incorrect type inference for self::$field =& $field). + (Nikita) + +- OpenSSL: + . Fixed bug #79145 (openssl memory leak). (cmb, Nikita) + +- Phar: + . Fixed bug #79082 (Files added to tar with Phar::buildFromIterator have + all-access permissions). (CVE-2020-7063) (stas) + . Fixed bug #79171 (heap-buffer-overflow in phar_extract_file). + (CVE-2020-7061) (cmb) + . Fixed bug #76584 (PharFileInfo::decompress not working). (cmb) + +- Reflection: + . Fixed bug #79115 (ReflectionClass::isCloneable call reflected class + __destruct). (Nikita) + +- Session: + . Fixed bug #79221 (Null Pointer Dereference in PHP Session Upload Progress). + (CVE-2020-7062) (stas) + +- Standard: + . Fixed bug #78902 (Memory leak when using stream_filter_append). (liudaixiao) + . Fixed bug #78969 (PASSWORD_DEFAULT should match PASSWORD_BCRYPT instead of being null). (kocsismate) + +- Testing: + . Fixed bug #78090 (bug45161.phpt takes forever to finish). (cmb) + +- XSL: + . Fixed bug #70078 (XSL callbacks with nodes as parameter leak memory). (cmb) + +- Zip: + . Add ZipArchive::CM_LZMA2 and ZipArchive::CM_XZ constants (since libzip 1.6.0). (Remi) + . Add ZipArchive::RDONLY (since libzip 1.0.0). (Remi) + . Add ZipArchive::ER_* missing constants. (Remi) + . Add ZipArchive::LIBZIP_VERSION constant. (Remi) + . Fixed bug #73119 (Wrong return for ZipArchive::addEmptyDir Method). (Remi) + +23 Jan 2020, PHP 7.4.2 + +- Core: + . Preloading support on Windows has been disabled. (Nikita) + . Fixed bug #79022 (class_exists returns True for classes that are not ready + to be used). (Laruence) + . Fixed bug #78929 (plus signs in cookie values are converted to spaces). + (Alexey Kachalin) + . Fixed bug #78973 (Destructor during CV freeing causes segfault if opline + never saved). (Nikita) + . Fixed bug #78776 (Abstract method implementation from trait does not check + "static"). (Nikita) + . Fixed bug #78999 (Cycle leak when using function result as temporary). + (Dmitry) + . Fixed bug #79008 (General performance regression with PHP 7.4 on Windows). + (cmb) + . Fixed bug #79002 (Serializing uninitialized typed properties with __sleep + makes unserialize throw). (Nikita) + +- CURL: + . Fixed bug #79033 (Curl timeout error with specific url and post). (cmb) + . Fixed bug #79063 (curl openssl does not respect PKG_CONFIG_PATH). (Nikita) + +- Date: + . Fixed bug #79015 (undefined-behavior in php_date.c). (cmb) + +- DBA: + . Fixed bug #78808 ([LMDB] MDB_MAP_FULL: Environment mapsize limit reached). + (cmb) + +- Exif: + . Fixed bug #79046 (NaN to int cast undefined behavior in exif). (Nikita) + +- Fileinfo: + . Fixed bug #74170 (locale information change after mime_content_type). + (Sergei Turchanov) + +- GD: + . Fixed bug #79067 (gdTransformAffineCopy() may use unitialized values). (cmb) + . Fixed bug #79068 (gdTransformAffineCopy() changes interpolation method). + (cmb) + +- Libxml: + . Fixed bug #79029 (Use After Free's in XMLReader / XMLWriter). (Laruence) + +- OPcache: + . Fixed bug #78961 (erroneous optimization of re-assigned $GLOBALS). (Dmitry) + . Fixed bug #78950 (Preloading trait method with static variables). (Nikita) + . Fixed bug #78903 (Conflict in RTD key for closures results in crash). + (Nikita) + . Fixed bug #78986 (Opcache segfaults when inheriting ctor from immutable + into mutable class). (Nikita) + . Fixed bug #79040 (Warning Opcode handlers are unusable due to ASLR). (cmb) + . Fixed bug #79055 (Typed property become unknown with OPcache file cache). + (Nikita) + +- Pcntl: + . Fixed bug #78402 (Converting null to string in error message is bad DX). + (SATŌ Kentarō) + +- PDO_PgSQL: + . Fixed bug #78983 (pdo_pgsql config.w32 cannot find libpq-fe.h). (SATŌ + Kentarō) + . Fixed bug #78980 (pgsqlGetNotify() overlooks dead connection). (SATŌ + Kentarō) + . Fixed bug #78982 (pdo_pgsql returns dead persistent connection). (SATŌ + Kentarō) + +- Session: + . Fixed bug #79031 (Session unserialization problem). (Nikita) + +- Shmop: + . Fixed bug #78538 (shmop memory leak). (cmb) + +- Sqlite3: + . Fixed bug #79056 (sqlite does not respect PKG_CONFIG_PATH during + compilation). (Nikita) + +- Spl: + . Fixed bug #78976 (SplFileObject::fputcsv returns -1 on failure). (cmb) + +- Standard: + . Fixed bug #79000 (Non-blocking socket stream reports EAGAIN as error). + (Nikita) + . Fixed bug #54298 (Using empty additional_headers adding extraneous CRLF). + (cmb) + +18 Dec 2019, PHP 7.4.1 + +- Core: + . Fixed bug #78810 (RW fetches do not throw "uninitialized property" + exception). (Nikita) + . Fixed bug #78868 (Calling __autoload() with incorrect EG(fake_scope) value). + (Antony Dovgal, Dmitry) + . Fixed bug #78296 (is_file fails to detect file). (cmb) + . Fixed bug #78883 (fgets(STDIN) fails on Windows). (cmb) + . Fixed bug #78898 (call_user_func(['parent', ...]) fails while other + succeed). (Nikita) + . Fixed bug #78904 (Uninitialized property triggers __get()). (Nikita) + . Fixed bug #78926 (Segmentation fault on Symfony cache:clear). (Nikita) + +- GD: + . Fixed bug #78849 (GD build broken with -D SIGNED_COMPARE_SLOW). (cmb) + . Fixed bug #78923 (Artifacts when convoluting image with transparency). + (wilson chen) + +- FPM: + . Fixed bug #76601 (Partially working php-fpm ater incomplete reload). + (Maksim Nikulin) + . Fixed bug #78889 (php-fpm service fails to start). (Jakub Zelenka) + . Fixed bug #78916 (php-fpm 7.4.0 don't send mail via mail()). + (Jakub Zelenka) + +- Intl: + . Implemented FR #78912 (INTL Support for accounting format). (cmb) + +- Mysqlnd: + . Fixed bug #78823 (ZLIB_LIBS not added to EXTRA_LIBS). (Arjen de Korte) + +- OPcache: + . Fixed $x = (bool)$x; with opcache (should emit undeclared variable notice). + (Tyson Andre) + . Fixed bug #78935 (Preloading removes classes that have dependencies). + (Nikita, Dmitry) + +- PCRE: + . Fixed bug #78853 (preg_match() may return integer > 1). (cmb) + +- Reflection: + . Fixed bug #78895 (Reflection detects abstract non-static class as abstract + static. IS_IMPLICIT_ABSTRACT is not longer used). (Dmitry) + +- Standard: + . Fixed bug #77638 (var_export'ing certain class instances segfaults). (cmb) + . Fixed bug #78840 (imploding $GLOBALS crashes). (cmb) + . Fixed bug #78833 (Integer overflow in pack causes out-of-bound access). + (cmb) + . Fixed bug #78814 (strip_tags allows / in tag name => whitelist bypass). + (cmb) + +28 Nov 2019, PHP 7.4.0 + +- Core: + . Implemented RFC: Deprecate curly brace syntax for accessing array elements + and string offsets. + https://wiki.php.net/rfc/deprecate_curly_braces_array_access (Andrey Gromov) + . Implemented RFC: Deprecations for PHP 7.4. + https://wiki.php.net/rfc/deprecations_php_7_4 (Kalle, Nikita) + . Fixed bug #52752 (Crash when lexing). (Nikita) + . Fixed bug #60677 (CGI doesn't properly validate shebang line contains #!). + (Nikita) + . Fixed bug #71030 (Self-assignment in list() may have inconsistent behavior). + (Nikita) + . Fixed bug #72530 (Use After Free in GC with Certain Destructors). (Nikita) + . Fixed bug #75921 (Inconsistent: No warning in some cases when stdObj is + created on the fly). (David Walker) + . Implemented FR #76148 (Add array_key_exists() to the list of specially + compiled functions). (Majkl578) + . Fixed bug #76430 (__METHOD__ inconsistent outside of method). + (Ryan McCullagh, Nikita) + . Fixed bug #76451 (Aliases during inheritance type checks affected by + opcache). (Nikita) + . Implemented FR #77230 (Support custom CFLAGS and LDFLAGS from environment). + (cmb) + . Fixed bug #77345 (Stack Overflow caused by circular reference in garbage + collection). (Alexandru Patranescu, Nikita, Dmitry) + . Fixed bug #77812 (Interactive mode does not support PHP 7.3-style heredoc). + (cmb, Nikita) + . Fixed bug #77877 (call_user_func() passes $this to static methods). + (Dmitry) + . Fixed bug #78066 (PHP eats the first byte of a program that comes from + process substitution). (Nikita) + . Fixed bug #78151 (Segfault caused by indirect expressions in PHP 7.4a1). + (Nikita) + . Fixed bug #78154 (SEND_VAR_NO_REF does not always send reference). (Nikita) + . Fixed bug #78182 (Segmentation fault during by-reference property + assignment). (Nikita) + . Fixed bug #78212 (Segfault in built-in webserver). (cmb) + . Fixed bug #78220 (Can't access OneDrive folder). (cmb, ab) + . Fixed bug #78226 (Unexpected __set behavior with typed properties). (Nikita) + . Fixed bug #78239 (Deprecation notice during string conversion converted to + exception hangs). (Nikita) + . Fixed bug #78335 (Static properties/variables containing cycles report as + leak). (Nikita) + . Fixed bug #78340 (Include of stream wrapper not reading whole file). + (Nikita) + . Fixed bug #78344 (Segmentation fault on zend_check_protected). (Nikita) + . Fixed bug #78356 (Array returned from ArrayAccess is incorrectly unpacked + as argument). (Nikita) + . Fixed bug #78379 (Cast to object confuses GC, causes crash). (Dmitry) + . Fixed bug #78386 (fstat mode has unexpected value on PHP 7.4). (cmb) + . Fixed bug #78396 (Second file_put_contents in Shutdown hangs script). + (Nikita) + . Fixed bug #78406 (Broken file includes with user-defined stream filters). + (Nikita) + . Fixed bug #78438 (Corruption when __unserializing deeply nested structures). + (cmb, Nikita) + . Fixed bug #78441 (Parse error due to heredoc identifier followed by digit). + (cmb) + . Fixed bug #78454 (Consecutive numeric separators cause OOM error). + (Theodore Brown) + . Fixed bug #78460 (PEAR installation failure). (Peter Kokot, L. Declercq) + . Fixed bug #78531 (Crash when using undefined variable as object). (Dmitry) + . Fixed bug #78535 (auto_detect_line_endings value not parsed as bool). + (bugreportuser) + . Fixed bug #78604 (token_get_all() does not properly tokenize FOOstat modifies $dbc->affected_rows). + (Derick) + . Fixed bug #76809 (SSL settings aren't respected when persistent connections + are used). (fabiomsouto) + . Fixed bug #78179 (MariaDB server version incorrectly detected). (cmb) + . Fixed bug #78213 (Empty row pocket). (cmb) + +- MySQLnd: + . Fixed connect_attr issues and added the _server_host connection attribute. + (Qianqian Bu) + . Fixed bug #60594 (mysqlnd exposes 160 lines of stats in phpinfo). (PeeHaa) + +- ODBC: + . Fixed bug #78473 (odbc_close() closes arbitrary resources). (cmb) + +- Opcache: + . Implemented preloading RFC: https://wiki.php.net/rfc/preload. (Dmitry) + . Add opcache.preload_user INI directive. (Dmitry) + . Added new INI directive opcache.cache_id (Windows only). (cmb) + . Fixed bug #78106 (Path resolution fails if opcache disabled during request). + (Nikita) + . Fixed bug #78175 (Preloading segfaults at preload time and at runtime). + (Dmitry) + . Fixed bug #78202 (Opcache stats for cache hits are capped at 32bit NUM). + (cmb) + . Fixed bug #78271 (Invalid result of if-else). (Nikita) + . Fixed bug #78341 (Failure to detect smart branch in DFA pass). (Nikita) + . Fixed bug #78376 (Incorrect preloading of constant static properties). + (Dmitry) + . Fixed bug #78429 (opcache_compile_file(__FILE__); segfaults). (cmb) + . Fixed bug #78512 (Cannot make preload work). (Dmitry) + . Fixed bug #78514 (Preloading segfaults with inherited typed property). + (Nikita) + . Fixed bug #78654 (Incorrectly computed opcache checksum on files with + non-ascii characters). (mhagstrand) + +- OpenSSL: + . Added TLS 1.3 support to streams including new tlsv1.3 stream. + (Codarren Velvindron, Jakub Zelenka) + . Added openssl_x509_verify function. (Ben Scholzen) + . openssl_random_pseudo_bytes() now throws in error conditions. + (Sammy Kaye Powers) + . Changed the default config path (Windows only). (cmb) + . Fixed bug #78231 (Segmentation fault upon stream_socket_accept of exported + socket-to-stream). (Nikita) + . Fixed bug #78391 (Assertion failure in openssl_random_pseudo_bytes). + (Nikita) + . Fixed bug #78775 (TLS issues from HTTP request affecting other encrypted + connections). (Nikita) + +- Pcntl: + . Fixed bug #77335 (PHP is preventing SIGALRM from specifying SA_RESTART). + (Nikita) + +- PCRE: + . Implemented FR #77094 (Support flags in preg_replace_callback). (Nikita) + . Fixed bug #72685 (Repeated UTF-8 validation of same string in UTF-8 mode). + (Nikita) + . Fixed bug #73948 (Preg_match_all should return NULLs on trailing optional + capture groups). + . Fixed bug #78338 (Array cross-border reading in PCRE). (cmb) + . Fixed bug #78349 (Bundled pcre2 library missing LICENCE file). (Peter Kokot) + +- PDO: + . Implemented FR #71885 (Allow escaping question mark placeholders). + https://wiki.php.net/rfc/pdo_escape_placeholders (Matteo) + . Fixed bug #77849 (Disable cloning of PDO handle/connection objects). + (camporter) + . Implemented FR #78033 (PDO - support username & password specified in + DSN). (sjon) + +- PDO_Firebird: + . Implemented FR #65690 (PDO_Firebird should also support dialect 1). + (Simonov Denis) + . Implemented FR #77863 (PDO firebird support type Boolean in input + parameters). (Simonov Denis) + +- PDO_MySQL: + . Fixed bug #41997 (SP call yields additional empty result set). (cmb) + . Fixed bug #78623 (Regression caused by "SP call yields additional empty + result set"). (cmb) + +- PDO_OCI: + . Support Oracle Database tracing attributes ACTION, MODULE, + CLIENT_INFO, and CLIENT_IDENTIFIER. (Cameron Porter) + . Implemented FR #76908 (PDO_OCI getColumnMeta() not implemented). + (Valentin Collet, Chris Jones, Remi) + +- PDO_SQLite: + . Implemented sqlite_stmt_readonly in PDO_SQLite. (BohwaZ) + . Raised requirements to SQLite 3.5.0. (cmb) + . Fixed bug #78192 (SegFault when reuse statement after schema has changed). + (Vincent Quatrevieux) + . Fixed bug #78348 (Remove -lrt from pdo_sqlite.so). (Peter Kokot) + +- Phar: + . Fixed bug #77919 (Potential UAF in Phar RSHUTDOWN). (cmb) + +- phpdbg: + . Fixed bug #76596 (phpdbg support for display_errors=stderr). (kabel) + . Fixed bug #76801 (too many open files). (alekitto) + . Fixed bug #77800 (phpdbg segfaults on listing some conditional breakpoints). + (krakjoe) + . Fixed bug #77805 (phpdbg build fails when readline is shared). (krakjoe) + +- Recode: + . Unbundled the recode extension. (cmb) + +- Reflection: + . Fixed bug #76737 (Unserialized reflection objects are broken, they + shouldn't be serializable). (Nikita) + . Fixed bug #78263 (\ReflectionReference::fromArrayElement() returns null + while item is a reference). (Nikita) + . Fixed bug #78410 (Cannot "manually" unserialize class that is final and + extends an internal one). (Nikita) + . Fixed bug #78697 (ReflectionClass::implementsInterface - inaccurate error + message with traits). (villfa) + . Fixed bug #78774 (ReflectionNamedType on Typed Properties Crash). (Nikita) + +- Session: + . Fixed bug #78624 (session_gc return value for user defined session + handlers). (bshaffer) + +- SimpleXML: + . Implemented FR #65215 (SimpleXMLElement could register as implementing + Countable). (LeSuisse) + . Fixed bug #75245 (Don't set content of elements with only whitespaces). + (eriklundin) + +- Sockets: + . Fixed bug #67619 (Validate length on socket_write). (thiagooak) + . Fixed bug #78665 (Multicasting may leak memory). (cmb) + +- sodium: + . Fixed bug #77646 (sign_detached() strings not terminated). (Frank) + . Fixed bug #78510 (Partially uninitialized buffer returned by + sodium_crypto_generichash_init()). (Frank Denis, cmb) + . Fixed bug #78516 (password_hash(): Memory cost is not in allowed range). + (cmb, Nikita) + +- SPL: + . Fixed bug #77518 (SeekableIterator::seek() should accept 'int' typehint as + documented). (Nikita) + . Fixed bug #78409 (Segfault when creating instance of ArrayIterator without + constructor). (Nikita) + . Fixed bug #78436 (Missing addref in SplPriorityQueue EXTR_BOTH mode). + (Nikita) + . Fixed bug #78456 (Segfault when serializing SplDoublyLinkedList). (Nikita) + +- SQLite3: + . Unbundled libsqlite. (cmb) + . Raised requirements to SQLite 3.7.4. (cmb) + . Forbid (un)serialization of SQLite3, SQLite3Stmt and SQLite3Result. (cmb) + . Added support for the SQLite @name notation. (cmb, BohwaZ) + . Added SQLite3Stmt::getSQL() to retrieve the SQL of the statement. (Bohwaz) + . Implement FR ##70950 (Make SQLite3 Online Backup API available). (BohwaZ) + +- Standard: + . Implemented password hashing registry RFC: + https://wiki.php.net/rfc/password_registry. (Sara) + . Implemented RFC where password_hash() has argon2i(d) implementations from + ext/sodium when PHP is built without libargon: + https://wiki.php.net/rfc/sodium.argon.hash (Sara) + . Implemented FR #38301 (field enclosure behavior in fputcsv). (cmb) + . Implemented FR #51496 (fgetcsv should take empty string as an escape). (cmb) + . Fixed bug #73535 (php_sockop_write() returns 0 on error, can be used to + trigger Denial of Service). (Nikita) + . Fixed bug #74764 (Bindto IPv6 works with file_get_contents but fails with + stream_socket_client). (Ville Hukkamäki) + . Fixed bug #76859 (stream_get_line skips data if used with data-generating + filter). (kkopachev) + . Implemented FR #77377 (No way to handle CTRL+C in Windows). (Anatol) + . Fixed bug #77930 (stream_copy_to_stream should use mmap more often). + (Nikita) + . Implemented FR #78177 (Make proc_open accept command array). (Nikita) + . Fixed bug #78208 (password_needs_rehash() with an unknown algo should always + return true). (Sara) + . Fixed bug #78241 (touch() does not handle dates after 2038 in PHP 64-bit). (cmb) + . Fixed bug #78282 (atime and mtime mismatch). (cmb) + . Fixed bug #78326 (improper memory deallocation on stream_get_contents() + with fixed length buffer). (Albert Casademont) + . Fixed bug #78346 (strip_tags no longer handling nested php tags). (cmb) + . Fixed bug #78506 (Error in a php_user_filter::filter() is not reported). + (Nikita) + . Fixed bug #78549 (Stack overflow due to nested serialized input). (Nikita) + . Fixed bug #78759 (array_search in $GLOBALS). (Nikita) + +- Testing: + . Fixed bug #78684 (PCRE bug72463_2 test is sending emails on Linux). (cmb) + +- Tidy: + . Added TIDY_TAG_* constants for HTML5 elements. (cmb) + . Fixed bug #76736 (wrong reflection for tidy_get_head, tidy_get_html, + tidy_get_root, and tidy_getopt) (tandre) + +- WDDX: + . Deprecated and unbundled the WDDX extension. (cmb) + +- Zip: + . Fixed bug #78641 (addGlob can modify given remove_path value). (cmb) + +21 Nov 2019, PHP 7.3.12 + +- Core: + . Fixed bug #78658 (Memory corruption using Closure::bindTo). (Nikita) + . Fixed bug #78656 (Parse errors classified as highest log-level). (Erik + Lundin) + . Fixed bug #78752 (Segfault if GC triggered while generator stack frame is + being destroyed). (Nikita) + . Fixed bug #78689 (Closure::fromCallable() doesn't handle + [Closure, '__invoke']). (Nikita) + +- COM: + . Fixed bug #78694 (Appending to a variant array causes segfault). (cmb) + +- Date: + . Fixed bug #70153 (\DateInterval incorrectly unserialized). (Maksim Iakunin) + . Fixed bug #78751 (Serialising DatePeriod converts DateTimeImmutable). (cmb) + +- Iconv: + . Fixed bug #78642 (Wrong libiconv version displayed). (gedas at martynas, + cmb). + +- OpCache: + . Fixed bug #78654 (Incorrectly computed opcache checksum on files with + non-ascii characters). (mhagstrand) + . Fixed bug #78747 (OpCache corrupts custom extension result). (Nikita) + +- OpenSSL: + . Fixed bug #78775 (TLS issues from HTTP request affecting other encrypted + connections). (Nikita) + +- Reflection: + . Fixed bug #78697 (ReflectionClass::ImplementsInterface - inaccurate error + message with traits). (villfa) + +- Sockets: + . Fixed bug #78665 (Multicasting may leak memory). (cmb) + +24 Oct 2019, PHP 7.3.11 + +- Core: + . Fixed bug #78535 (auto_detect_line_endings value not parsed as bool). + (bugreportuser) + . Fixed bug #78620 (Out of memory error). (cmb, Nikita) + +- Exif : + . Fixed bug #78442 ('Illegal component' on exif_read_data since PHP7) + (Kalle) + +- FPM: + . Fixed bug #78599 (env_path_info underflow in fpm_main.c can lead to RCE). + (CVE-2019-11043) (Jakub Zelenka) + . Fixed bug #78413 (request_terminate_timeout does not take effect after + fastcgi_finish_request). (Sergei Turchanov) + +- MBString: + . Fixed bug #78633 (Heap buffer overflow (read) in mb_eregi). (cmb) + . Fixed bug #78579 (mb_decode_numericentity: args number inconsistency). + (cmb) + . Fixed bug #78609 (mb_check_encoding() no longer supports stringable + objects). (cmb) + +- MySQLi: + . Fixed bug #76809 (SSL settings aren't respected when persistent connections + are used). (fabiomsouto) + +- Mysqlnd: + . Fixed bug #78525 (Memory leak in pdo when reusing native prepared + statements). (Nikita) + +- PCRE: + . Fixed bug #78272 (calling preg_match() before pcntl_fork() will freeze + child process). (Nikita) + +- PDO_MySQL: + . Fixed bug #78623 (Regression caused by "SP call yields additional empty + result set"). (cmb) + +- Session: + . Fixed bug #78624 (session_gc return value for user defined session + handlers). (bshaffer) + +- Standard: + . Fixed bug #76342 (file_get_contents waits twice specified timeout). + (Thomas Calvet) + . Fixed bug #78612 (strtr leaks memory when integer keys are used and the + subject string shorter). (Nikita) + . Fixed bug #76859 (stream_get_line skips data if used with data-generating + filter). (kkopachev) + +- Zip: + . Fixed bug #78641 (addGlob can modify given remove_path value). (cmb) + +26 Sep 2019, PHP 7.3.10 + +- Core: + . Fixed bug #78220 (Can't access OneDrive folder). (cmb, ab) + . Fixed bug #77922 (Double release of doc comment on inherited shadow + property). (Nikita) + . Fixed bug #78441 (Parse error due to heredoc identifier followed by digit). + (cmb) + . Fixed bug #77812 (Interactive mode does not support PHP 7.3-style heredoc). + (cmb, Nikita) + +- FastCGI: + . Fixed bug #78469 (FastCGI on_accept hook is not called when using named + pipes on Windows). (Sergei Turchanov) + +- FPM: + . Fixed bug #78334 (fpm log prefix message includes wrong stdout/stderr + notation). (Tsuyoshi Sadakata) + +- Intl: + . Ensure IDNA2003 rules are used with idn_to_ascii() and idn_to_utf8() + when requested. (Sara) + +- MBString: + . Fixed bug #78559 (Heap buffer overflow in mb_eregi). (cmb) + +- MySQLnd: + . Fixed connect_attr issues and added the _server_host connection attribute. + (Qianqian Bu) + +- ODBC: + . Fixed bug #78473 (odbc_close() closes arbitrary resources). (cmb) + +- PDO_MySQL: + . Fixed bug #41997 (SP call yields additional empty result set). (cmb) + +- sodium: + . Fixed bug #78510 (Partially uninitialized buffer returned by + sodium_crypto_generichash_init()). (Frank Denis, cmb) + +29 Aug 2019, PHP 7.3.9 + +- Core: + . Fixed bug #78363 (Buffer overflow in zendparse). (Nikita) + . Fixed bug #78379 (Cast to object confuses GC, causes crash). (Dmitry) + . Fixed bug #78412 (Generator incorrectly reports non-releasable $this as GC + child). (Nikita) + +- Curl: + . Fixed bug #77946 (Bad cURL resources returned by curl_multi_info_read()). + (Abyr Valg) + +- Exif: + . Fixed bug #78333 (Exif crash (bus error) due to wrong alignment and + invalid cast). (Nikita) + +- FPM: + . Fixed bug #77185 (Use-after-free in FPM master event handling). + (Maksim Nikulin) + +- Iconv: + . Fixed bug #78342 (Bus error in configure test for iconv //IGNORE). (Rainer + Jung) + +- LiteSpeed: + . Updated to LiteSpeed SAPI V7.5 (Fixed clean shutdown). (George Wang) + +- MBString: + . Fixed bug #78380 (Oniguruma 6.9.3 fixes CVEs). (CVE-2019-13224) (Stas) + +- MySQLnd: + . Fixed bug #78179 (MariaDB server version incorrectly detected). (cmb) + . Fixed bug #78213 (Empty row pocket). (cmb) + +- Opcache: + . Fixed bug #77191 (Assertion failure in dce_live_ranges() when silencing is + used). (Nikita) + +- Standard: + . Fixed bug #69100 (Bus error from stream_copy_to_stream (file -> SSL stream) + with invalid length). (Nikita) + . Fixed bug #78282 (atime and mtime mismatch). (cmb) + . Fixed bug #78326 (improper memory deallocation on stream_get_contents() + with fixed length buffer). (Albert Casademont) + . Fixed bug #78346 (strip_tags no longer handling nested php tags). (cmb) + +01 Aug 2019, PHP 7.3.8 + +- Core: + . Added syslog.filter=raw option. (Erik Lundin) + . Fixed bug #78212 (Segfault in built-in webserver). (cmb) + +- Date: + . Fixed bug #69044 (discrepency between time and microtime). (krakjoe) + . Updated timelib to 2018.02. (Derick) + +- EXIF: + . Fixed bug #78256 (heap-buffer-overflow on exif_process_user_comment). + (CVE-2019-11042) (Stas) + . Fixed bug #78222 (heap-buffer-overflow on exif_scan_thumbnail). + (CVE-2019-11041) (Stas) + +- FTP: + . Fixed bug #78039 (FTP with SSL memory leak). (Nikita) + +- Libxml: + . Fixed bug #78279 (libxml_disable_entity_loader settings is shared between + requests (cgi-fcgi)). (Nikita) + +- LiteSpeed: + . Updated to LiteSpeed SAPI V7.4.3 (increased response header count limit from + 100 to 1000, added crash handler to cleanly shutdown PHP request, added + CloudLinux mod_lsapi mode). (George Wang) + . Fixed bug #76058 (After "POST data can't be buffered", using php://input + makes huge tmp files). (George Wang) + +- Openssl: + . Fixed bug #78231 (Segmentation fault upon stream_socket_accept of exported + socket-to-stream). (Nikita) + +- Opcache: + . Fixed bug #78189 (file cache strips last character of uname hash). (cmb) + . Fixed bug #78202 (Opcache stats for cache hits are capped at 32bit NUM). + (cmb) + . Fixed bug #78271 (Invalid result of if-else). (Nikita) + . Fixed bug #78291 (opcache_get_configuration doesn't list all directives). + (Andrew Collington) + . Fixed bug #78341 (Failure to detect smart branch in DFA pass). (Nikita) + +- PCRE: + . Fixed bug #78197 (PCRE2 version check in configure fails for "##.##-xxx" + version strings). (pgnet, Peter Kokot) + . Fixed bug #78338 (Array cross-border reading in PCRE). (cmb) + +- PDO_Sqlite: + . Fixed bug #78192 (SegFault when reuse statement after schema has changed). + (Vincent Quatrevieux) + +- Phar: + . Fixed bug #77919 (Potential UAF in Phar RSHUTDOWN). (cmb) + +- Phpdbg: + . Fixed bug #78297 (Include unexistent file memory leak). (Nikita) + +- SQLite: + . Upgraded to SQLite 3.28.0. (cmb) + +- Standard: + . Fixed bug #78241 (touch() does not handle dates after 2038 in PHP 64-bit). (cmb) + . Fixed bug #78269 (password_hash uses weak options for argon2). (Remi) + +04 Jul 2019, PHP 7.3.7 + +- Core: + . Fixed bug #76980 (Interface gets skipped if autoloader throws an exception). + (Nikita) + +- DOM: + . Fixed bug #78025 (segfault when accessing properties of DOMDocumentType). + (cmb) + +- MySQLi: + . Fixed bug #77956 (When mysqli.allow_local_infile = Off, use a meaningful + error message). (Sjon Hortensius) + . Fixed bug #38546 (bindParam incorrect processing of bool types). + (camporter) + +- MySQLnd: + . Fixed bug #77955 (Random segmentation fault in mysqlnd from php-fpm). + (Nikita) + +- Opcache: + . Fixed bug #78015 (Incorrect evaluation of expressions involving partials + arrays in SCCP). (Nikita) + . Fixed bug #78106 (Path resolution fails if opcache disabled during request). + (Nikita) + +- OpenSSL: + . Fixed bug #78079 (openssl_encrypt_ccm.phpt fails with OpenSSL 1.1.1c). + (Jakub Zelenka) + +- phpdbg: + . Fixed bug #78050 (SegFault phpdbg + opcache on include file twice). + (Nikita) + +- Sockets: + . Fixed bug #78038 (Socket_select fails when resource array contains + references). (Nikita) + +- Sodium: + . Fixed bug #78114 (segfault when calling sodium_* functions from eval). (cmb) + +- Standard: + . Fixed bug #77135 (Extract with EXTR_SKIP should skip $this). + (Craig Duncan, Dmitry) + . Fixed bug #77937 (preg_match failed). (cmb, Anatol) + +- Zip: + . Fixed bug #76345 (zip.h not found). (Michael Maroszek) + +30 May 2019, PHP 7.3.6 + +- cURL: + . Implemented FR #72189 (Add missing CURL_VERSION_* constants). (Javier + Spagnoletti) + +- Date: + . Fixed bug #77909 (DatePeriod::__construct() with invalid recurrence count + value). (Ignace Nyamagana Butera) + +- EXIF: + . Fixed bug #77988 (heap-buffer-overflow on php_jpg_get16). + (CVE-2019-11040) (Stas) + +- FPM: + . Fixed bug #77934 (php-fpm kill -USR2 not working). (Jakub Zelenka) + . Fixed bug #77921 (static.php.net doesn't work anymore). (Peter Kokot) + +- GD: + . Fixed bug #77943 (imageantialias($image, false); does not work). (cmb) + . Fixed bug #77973 (Uninitialized read in gdImageCreateFromXbm). + (CVE-2019-11038) (cmb) + +- Iconv: + . Fixed bug #78069 (Out-of-bounds read in iconv.c:_php_iconv_mime_decode() + due to integer overflow). (CVE-2019-11039). (maris dot adam) + +- JSON: + . Fixed bug #77843 (Use after free with json serializer). (Nikita) + +- Opcache: + . Fixed possible crashes, because of inconsistent PCRE cache and opcache + SHM reset. (Alexey Kalinin, Dmitry) + +- PDO_MySQL: + . Fixed bug #77944 (Wrong meta pdo_type for bigint on LLP64). (cmb) + +- Reflection: + . Fixed bug #75186 (Inconsistent reflection of Closure:::__invoke()). (Nikita) + +- Session: + . Fixed bug #77911 (Wrong warning for session.sid_bits_per_character). (cmb) + +- SOAP: + . Fixed bug #77945 (Segmentation fault when constructing SoapClient with + WSDL_CACHE_BOTH). (Nikita) + +- SPL: + . Fixed bug #77024 (SplFileObject::__toString() may return array). (Craig + Duncan) + +- SQLite: + . Fixed bug #77967 (Bypassing open_basedir restrictions via file uris). (Stas) + +- Standard: + . Fixed bug #77931 (Warning for array_map mentions wrong type). (Nikita) + . Fixed bug #78003 (strip_tags output change since PHP 7.3). (cmb) + +02 May 2019, PHP 7.3.5 + +- Core: + . Fixed bug #77903 (ArrayIterator stops iterating after offsetSet call). + (Nikita) + +- CLI: + . Fixed bug #77794 (Incorrect Date header format in built-in server). + (kelunik) + +- EXIF + . Fixed bug #77950 (Heap-buffer-overflow in _estrndup via exif_process_IFD_TAG). + (CVE-2019-11036) (Stas) + +- Interbase: + . Fixed bug #72175 (Impossibility of creating multiple connections to + Interbase with php 7.x). (Nikita) + +- Intl: + . Fixed bug #77895 (IntlDateFormatter::create fails in strict mode if $locale + = null). (Nikita) + +- LDAP: + . Fixed bug #77869 (Core dump when using server controls) (mcmic) + +- Mail + . Fixed bug #77821 (Potential heap corruption in TSendMail()). (cmb) + +- mbstring: + . Implemented FR #72777 (Implement regex stack limits for mbregex functions). + (Yasuo Ohgaki, Stas) + +- MySQLi: + . Fixed bug #77773 (Unbuffered queries leak memory - MySQLi / mysqlnd). + (Nikita) + +- PCRE: + . Fixed bug #77827 (preg_match does not ignore \r in regex flags). (requinix, + cmb) + +- PDO: + . Fixed bug #77849 (Disable cloning of PDO handle/connection objects). + (camporter) + +- phpdbg: + . Fixed bug #76801 (too many open files). (alekitto) + . Fixed bug #77800 (phpdbg segfaults on listing some conditional breakpoints). + (krakjoe) + . Fixed bug #77805 (phpdbg build fails when readline is shared). (krakjoe) + +- Reflection: + . Fixed bug #77772 (ReflectionClass::getMethods(null) doesn't work). (Nikita) + . Fixed bug #77882 (Different behavior: always calls destructor). (Nikita) + +- Standard: + . Fixed bug #77793 (Segmentation fault in extract() when overwriting + reference with itself). (Nikita) + . Fixed bug #77844 (Crash due to null pointer in parse_ini_string with + INI_SCANNER_TYPED). (Nikita) + . Fixed bug #77853 (Inconsistent substr_compare behaviour with empty + haystack). (Nikita) + +04 Apr 2019, PHP 7.3.4 + +- Core: + . Fixed bug #77738 (Nullptr deref in zend_compile_expr). (Laruence) + . Fixed bug #77660 (Segmentation fault on break 2147483648). (Laruence) + . Fixed bug #77652 (Anonymous classes can lose their interface information). + (Nikita) + . Fixed bug #77345 (Stack Overflow caused by circular reference in garbage + collection). (Alexandru Patranescu, Nikita, Dmitry) + . Fixed bug #76956 (Wrong value for 'syslog.filter' documented in php.ini). + (cmb) + +- Apache2Handler: + . Fixed bug #77648 (BOM in sapi/apache2handler/php_functions.c). (cmb) + +- Bcmath: + . Fixed bug #77742 (bcpow() implementation related to gcc compiler + optimization). (Nikita) + +- CLI Server: + . Fixed bug #77722 (Incorrect IP set to $_SERVER['REMOTE_ADDR'] on the + localhost). (Nikita) + +- COM: + . Fixed bug #77578 (Crash when php unload). (cmb) + +- EXIF: + . Fixed bug #77753 (Heap-buffer-overflow in php_ifd_get32s). (CVE-2019-11034) + (Stas) + . Fixed bug #77831 (Heap-buffer-overflow in exif_iif_add_value). + (CVE-2019-11035) (Stas) + +- FPM: + . Fixed bug #77677 (FPM fails to build on AIX due to missing WCOREDUMP). + (Kevin Adler) + +- GD: + . Fixed bug #77700 (Writing truecolor images as GIF ignores interlace flag). + (cmb) + +- MySQLi: + . Fixed bug #77597 (mysqli_fetch_field hangs scripts). (Nikita) + +- Opcache: + . Fixed bug #77743 (Incorrect pi node insertion for jmpznz with identical + successors). (Nikita) + +- PCRE: + . Fixed bug #76127 (preg_split does not raise an error on invalid UTF-8). + (Nikita) + +- Phar: + . Fixed bug #77697 (Crash on Big_Endian platform). (Laruence) + +- phpdbg: + . Fixed bug #77767 (phpdbg break cmd aliases listed in help do not match + actual aliases). (Miriam Lauter) + +- sodium: + . Fixed bug #77646 (sign_detached() strings not terminated). (Frank) + +- SQLite3: + . Added sqlite3.defensive INI directive. (BohwaZ) + +- Standard: + . Fixed bug #77664 (Segmentation fault when using undefined constant in + custom wrapper). (Laruence) + . Fixed bug #77669 (Crash in extract() when overwriting extracted array). + (Nikita) + . Fixed bug #76717 (var_export() does not create a parsable value for + PHP_INT_MIN). (Nikita) + . Fixed bug #77765 (FTP stream wrapper should set the directory as + executable). (Vlad Temian) + +07 Mar 2019, PHP 7.3.3 + +- Core: + . Fixed bug #77589 (Core dump using parse_ini_string with numeric sections). + (Laruence) + . Fixed bug #77329 (Buffer Overflow via overly long Error Messages). + (Dmitry) + . Fixed bug #77494 (Disabling class causes segfault on member access). + (Dmitry) + . Fixed bug #77498 (Custom extension Segmentation fault when declare static + property). (Nikita) + . Fixed bug #77530 (PHP crashes when parsing `(2)::class`). (Ekin) + . Fixed bug #77546 (iptcembed broken function). (gdegoulet) + . Fixed bug #77630 (rename() across the device may allow unwanted access + during processing). (Stas) + +- COM: + . Fixed bug #77621 (Already defined constants are not properly reported). + (cmb) + . Fixed bug #77626 (Persistence confusion in php_com_import_typelib()). (cmb) + +- EXIF: + . Fixed bug #77509 (Uninitialized read in exif_process_IFD_in_TIFF). (Stas) + . Fixed bug #77540 (Invalid Read on exif_process_SOFn). (Stas) + . Fixed bug #77563 (Uninitialized read in exif_process_IFD_in_MAKERNOTE). (Stas) + . Fixed bug #77659 (Uninitialized read in exif_process_IFD_in_MAKERNOTE). (Stas) + +- Mbstring: + . Fixed bug #77514 (mb_ereg_replace() with trailing backslash adds null byte). + (Nikita) + +- MySQL + . Disabled LOCAL INFILE by default, can be enabled using php.ini directive + mysqli.allow_local_infile for mysqli, or PDO::MYSQL_ATTR_LOCAL_INFILE + attribute for pdo_mysql. (Darek Slusarczyk) + +- OpenSSL: + . Fixed bug #77390 (feof might hang on TLS streams in case of fragmented TLS + records). (Abyl Valg, Jakub Zelenka) + +- PDO_OCI: + . Support Oracle Database tracing attributes ACTION, MODULE, + CLIENT_INFO, and CLIENT_IDENTIFIER. (Cameron Porter) + +- PHAR: + . Fixed bug #77396 (Null Pointer Dereference in phar_create_or_parse_filename). + (bishop) + . Fixed bug #77586 (phar_tar_writeheaders_int() buffer overflow). (bishop) + +- phpdbg: + . Fixed bug #76596 (phpdbg support for display_errors=stderr). (kabel) + +- SPL: + . Fixed bug #51068 (DirectoryIterator glob:// don't support current path + relative queries). (Ahmed Abdou) + . Fixed bug #77431 (openFile() silently truncates after a null byte). (cmb) + +- Standard: + . Fixed bug #77552 (Unintialized php_stream_statbuf in stat functions). + (John Stevenson) + . Fixed bug #77612 (setcookie() sets incorrect SameSite header if all of its + options filled). (Nikita) + +07 Feb 2019, PHP 7.3.2 + +- Core: + . Fixed bug #77369 (memcpy with negative length via crafted DNS response). (Stas) + . Fixed bug #77387 (Recursion detection broken when printing GLOBALS). + (Laruence) + . Fixed bug #77376 ("undefined function" message no longer includes + namespace). (Laruence) + . Fixed bug #77357 (base64_encode / base64_decode doest not work on nested + VM). (Nikita) + . Fixed bug #77339 (__callStatic may get incorrect arguments). (Dmitry) + . Fixed bug #77317 (__DIR__, __FILE__, realpath() reveal physical path for + subst virtual drive). (Anatol) + . Fixed bug #77263 (Segfault when using 2 RecursiveFilterIterator). (Dmitry) + . Fixed bug #77447 (PHP 7.3 built with ASAN crashes in + zend_cpu_supports_avx2). (Nikita) + . Fixed bug #77484 (Zend engine crashes when calling realpath in invalid + working dir). (Anatol) + +- Curl: + . Fixed bug #76675 (Segfault with H2 server push). (Pedro Magalhães) + +- Fileinfo: + . Fixed bug #77346 (webm files incorrectly detected as + application/octet-stream). (Anatol) + +- FPM: + . Fixed bug #77430 (php-fpm crashes with Main process exited, code=dumped, + status=11/SEGV). (Jakub Zelenka) + +- GD: + . Fixed bug #73281 (imagescale(…, IMG_BILINEAR_FIXED) can cause black border). + (cmb) + . Fixed bug #73614 (gdImageFilledArc() doesn't properly draw pies). (cmb) + . Fixed bug #77272 (imagescale() may return image resource on failure). (cmb) + . Fixed bug #77391 (1bpp BMPs may fail to be loaded). (Romain Déoux, cmb) + . Fixed bug #77479 (imagewbmp() segfaults with very large images). (cmb) + +- ldap: + . Fixed bug #77440 (ldap_bind using ldaps or ldap_start_tls()=exception in + libcrypto-1_1-x64.dll). (Anatol) + +- Mbstring: + . Fixed bug #77428 (mb_ereg_replace() doesn't replace a substitution + variable). (Nikita) + . Fixed bug #77454 (mb_scrub() silently truncates after a null byte). + (64796c6e69 at gmail dot com) + +- MySQLnd: + . Fixed bug #77308 (Unbuffered queries memory leak). (Dmitry) + . Fixed bug #75684 (In mysqlnd_ext_plugin.h the plugin methods family has + no external visibility). (Anatol) + +- Opcache: + . Fixed bug #77266 (Assertion failed in dce_live_ranges). (Laruence) + . Fixed bug #77257 (value of variable assigned in a switch() construct gets + lost). (Nikita) + . Fixed bug #77434 (php-fpm workers are segfaulting in zend_gc_addre). + (Nikita) + . Fixed bug #77361 (configure fails on 64-bit AIX when opcache enabled). + (Kevin Adler) + . Fixed bug #77287 (Opcache literal compaction is incompatible with EXT + opcodes). (Nikita) + +- PCRE: + . Fixed bug #77338 (get_browser with empty string). (Nikita) + +- PDO: + . Fixed bug #77273 (array_walk_recursive corrupts value types leading to PDO + failure). (Nikita) + +- PDO MySQL: + . Fixed bug #77289 (PDO MySQL segfaults with persistent connection). + (Lauri Kenttä) + +- SOAP: + . Fixed bug #77410 (Segmentation Fault when executing method with an empty + parameter). (Nikita) + +- Sockets: + . Fixed bug #76839 (socket_recvfrom may return an invalid 'from' address + on MacOS). (Michael Meyer) + +- SPL: + . Fixed bug #77298 (segfault occurs when add property to unserialized empty + ArrayObject). (jhdxr) + +- Standard: + . Fixed bug #77395 (segfault about array_multisort). (Laruence) + . Fixed bug #77439 (parse_str segfaults when inserting item into existing + array). (Nikita) + +10 Jan 2019, PHP 7.3.1 + +- Core: + . Fixed bug #76654 (Build failure on Mac OS X on 32-bit Intel). (Ryandesign) + . Fixed bug #71041 (zend_signal_startup() needs ZEND_API). + (Valentin V. Bartenev) + . Fixed bug #76046 (PHP generates "FE_FREE" opcode on the wrong line). + (Nikita) + . Fixed bug #77291 (magic methods inherited from a trait may be ignored). + (cmb) + +- CURL: + . Fixed bug #77264 (curl_getinfo returning microseconds, not seconds). + (Pierrick) + +- COM: + . Fixed bug #77177 (Serializing or unserializing COM objects crashes). (cmb) + +- Exif: + . Fixed bug #77184 (Unsigned rational numbers are written out as signed + rationals). (Colin Basnett) + +- GD: + . Fixed bug #77195 (Incorrect error handling of imagecreatefromjpeg()). (cmb) + . Fixed bug #77198 (auto cropping has insufficient precision). (cmb) + . Fixed bug #77200 (imagecropauto(…, GD_CROP_SIDES) crops left but not right). + (cmb) + . Fixed bug #77269 (efree() on uninitialized Heap data in imagescale leads to + use-after-free). (cmb) + . Fixed bug #77270 (imagecolormatch Out Of Bounds Write on Heap). (cmb) + +- MBString: + . Fixed bug #77367 (Negative size parameter in mb_split). (Stas) + . Fixed bug #77370 (Buffer overflow on mb regex functions - fetch_token). + (Stas) + . Fixed bug #77371 (heap buffer overflow in mb regex functions + - compile_string_node). (Stas) + . Fixed bug #77381 (heap buffer overflow in multibyte match_at). (Stas) + . Fixed bug #77382 (heap buffer overflow due to incorrect length in + expand_case_fold_string). (Stas) + . Fixed bug #77385 (buffer overflow in fetch_token). (Stas) + . Fixed bug #77394 (Buffer overflow in multibyte case folding - unicode). + (Stas) + . Fixed bug #77418 (Heap overflow in utf32be_mbc_to_code). (Stas) + +- OCI8: + . Fixed bug #76804 (oci_pconnect with OCI_CRED_EXT not working). (KoenigsKind) + . Added oci_set_call_timeout() for call timeouts. + . Added oci_set_db_operation() for the DBOP end-to-end-tracing attribute. + +- Opcache: + . Fixed bug #77215 (CFG assertion failure on multiple finalizing switch + frees in one block). (Nikita) + . Fixed bug #77275 (OPcache optimization problem for ArrayAccess->offsetGet). + (Nikita) + +- PCRE: + . Fixed bug #77193 (Infinite loop in preg_replace_callback). (Anatol) + +- PDO: + . Handle invalid index passed to PDOStatement::fetchColumn() as error. (Sergei + Morozov) + +- Phar: + . Fixed bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext). (Stas) + +- Soap: + . Fixed bug #77088 (Segfault when using SoapClient with null options). + (Laruence) + +- Sockets: + . Fixed bug #77136 (Unsupported IPV6_RECVPKTINFO constants on macOS). + (Mizunashi Mana) + +- Sodium: + . Fixed bug #77297 (SodiumException segfaults on PHP 7.3). (Nikita, Scott) + +- SPL: + . Fixed bug #77359 (spl_autoload causes segfault). (Lauri Kenttä) + . Fixed bug #77360 (class_uses causes segfault). (Lauri Kenttä) + +- SQLite3: + . Fixed bug #77051 (Issue with re-binding on SQLite3). (BohwaZ) + +- Xmlrpc: + . Fixed bug #77242 (heap out of bounds read in xmlrpc_decode()). (cmb) + . Fixed bug #77380 (Global out of bounds read in xmlrpc base64 code). (Stas) + +06 Dec 2018, PHP 7.3.0 + +- Core: + . Improved PHP GC. (Dmitry, Nikita) + . Redesigned the old ext_skel program written in PHP, run: + 'php ext_skel.php' for all options. This means there are no dependencies, + thus making it work on Windows out of the box. (Kalle) + . Removed support for BeOS. (Kalle) + . Add PHP_VERSION to phpinfo() . (github/MattJeevas) + . Add net_get_interfaces(). (Sara, Joe, Anatol) + . Added gc_status(). (Benjamin Eberlei) + . Implemented flexible heredoc and nowdoc syntax, per + RFC https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes. + (Thomas Punt) + . Added support for references in list() and array destructuring, per + RFC https://wiki.php.net/rfc/list_reference_assignment. + (David Walker) + . Improved effectiveness of ZEND_SECURE_ZERO for NetBSD and systems + without native similar feature. (devnexen) + . Added syslog.facility and syslog.ident INI entries for customizing syslog + logging. (Philip Prindeville) + . Fixed bug #75683 (Memory leak in zend_register_functions() in ZTS mode). + (Dmitry) + . Fixed bug #75031 (support append mode in temp/memory streams). (adsr) + . Fixed bug #74860 (Uncaught exceptions not being formatted properly when + error_log set to "syslog"). (Philip Prindeville) + . Fixed bug #75220 (Segfault when calling is_callable on parent). + (andrewnester) + . Fixed bug #69954 (broken links and unused config items in distributed ini + files). (petk) + . Fixed bug #74922 (Composed class has fatal error with duplicate, equal const + properties). (pmmaga) + . Fixed bug #63911 (identical trait methods raise errors during composition). + (pmmaga) + . Fixed bug #75677 (Clang ignores fastcall calling convention on variadic + function). (Li-Wen Hsu) + . Fixed bug #54043 (Remove inconsitency of internal exceptions and user + defined exceptions). (Nikita) + . Fixed bug #53033 (Mathematical operations convert objects to integers). + (Nikita) + . Fixed bug #73108 (Internal class cast handler uses integer instead of + float). (Nikita) + . Fixed bug #75765 (Fatal error instead of Error exception when base class is + not found). (Timur Ibragimov) + . Fixed bug #76198 (Wording: "iterable" is not a scalar type). (Levi Morrison) + . Fixed bug #76137 (config.guess/config.sub do not recognize RISC-V). (cmb) + . Fixed bug #76427 (Segfault in zend_objects_store_put). (Laruence) + . Fixed bug #76422 (ftruncate fails on files > 2GB). (Anatol) + . Fixed bug #76509 (Inherited static properties can be desynchronized from + their parent by ref). (Nikita) + . Fixed bug #76439 (Changed behaviour in unclosed HereDoc). (Nikita, tpunt) + . Fixed bug #63217 (Constant numeric strings become integers when used as + ArrayAccess offset). (Rudi Theunissen, Dmitry) + . Fixed bug #33502 (Some nullary functions don't check the number of + arguments). (cmb) + . Fixed bug #76392 (Error relocating sapi/cli/php: unsupported relocation + type 37). (Peter Kokot) + . The declaration and use of case-insensitive constants has been deprecated. + (Nikita) + . Added syslog.filter INI entry for syslog filtering. (Philip Prindeville) + . Fixed bug #76667 (Segfault with divide-assign op and __get + __set). + (Laruence) + . Fixed bug #76030 (RE2C_FLAGS rarely honoured) (Cristian Rodríguez) + . Fixed broken zend_read_static_property (Laruence) + . Fixed bug #76773 (Traits used on the parent are ignored for child classes). + (daverandom) + . Fixed bug #76767 (‘asm’ operand has impossible constraints in zend_operators.h). + (ondrej) + . Fixed bug #76752 (Crash in ZEND_COALESCE_SPEC_TMP_HANDLER - assertion in + _get_zval_ptr_tmp failed). (Laruence) + . Fixed bug #76820 (Z_COPYABLE invalid definition). (mvdwerve, cmb) + . Fixed bug #76510 (file_exists() stopped working for phar://). (cmb) + . Fixed bug #76869 (Incorrect bypassing protected method accessibilty check). + (Dmitry) + . Fixed bug #72635 (Undefined class used by class constant in constexpr + generates fatal error). (Nikita) + . Fixed bug #76947 (file_put_contents() blocks the directory of the file + (__DIR__)). (Anatol) + . Fixed bug #76979 (define() error message does not mention resources as + valid values). (Michael Moravec) + . Fixed bug #76825 (Undefined symbols ___cpuid_count). (Laruence, cmb) + . Fixed bug #77110 (undefined symbol zend_string_equal_val in C++ build). + (Remi) + +- BCMath: + . Implemented FR #67855 (No way to get current scale in use). (Chris Wright, + cmb) + . Fixed bug #66364 (BCMath bcmul ignores scale parameter). (cmb) + . Fixed bug #75164 (split_bc_num() is pointless). (cmb) + . Fixed bug #75169 (BCMath errors/warnings bypass PHP's error handling). (cmb) + +- CLI: + . Fixed bug #44217 (Output after stdout/stderr closed cause immediate exit + with status 0). (Robert Lu) + . Fixed bug #77111 (php-win.exe corrupts unicode symbols from cli + parameters). (Anatol) + +- cURL: + . Expose curl constants from curl 7.50 to 7.61. (Pierrick) + . Fixed bug #74125 (Fixed finding CURL on systems with multiarch support). + (cebe) + +- Date: + . Implemented FR #74668: Add DateTime::createFromImmutable() method. + (majkl578, Rican7) + . Fixed bug #75222 (DateInterval microseconds property always 0). (jhdxr) + . Fixed bug #68406 (calling var_dump on a DateTimeZone object modifies it). + (jhdxr) + . Fixed bug #76131 (mismatch arginfo for date_create). (carusogabriel) + . Updated timelib to 2018.01RC1 to address several bugs: + . Fixed bug #75577 (DateTime::createFromFormat does not accept 'v' format + specifier). (Derick) + . Fixed bug #75642 (Wrap around behaviour for microseconds is not working). + (Derick) + +- DBA: + . Fixed bug #75264 (compiler warnings emitted). (petk) + +- DOM: + . Fixed bug #76285 (DOMDocument::formatOutput attribute sometimes ignored). + (Andrew Nester, Laruence, Anatol) + +- Fileinfo: + . Fixed bug #77095 (slowness regression in 7.2/7.3 (compared to 7.1)). + (Anatol) + +- Filter: + . Added the 'add_slashes' sanitization mode (FILTER_SANITIZE_ADD_SLASHES). + (Kalle) + +- FPM: + . Added fpm_get_status function. (Till Backhaus) + . Fixed bug #62596 (getallheaders() missing with PHP-FPM). (Remi) + . Fixed bug #69031 (Long messages into stdout/stderr are truncated + incorrectly) - added new log related FPM configuration options: + log_limit, log_buffering and decorate_workers_output. (Jakub Zelenka) + +- ftp: + . Fixed bug #77151 (ftp_close(): SSL_read on shutdown). (Remi) + +- GD: + . Added support for WebP in imagecreatefromstring(). (Andreas Treichel, cmb) + +- GMP: + . Export internal structures and accessor helpers for GMP object. (Sara) + . Added gmp_binomial(n, k). (Nikita) + . Added gmp_lcm(a, b). (Nikita) + . Added gmp_perfect_power(a). (Nikita) + . Added gmp_kronecker(a, b). (Nikita) + +- iconv: + . Fixed bug #53891 (iconv_mime_encode() fails to Q-encode UTF-8 string). (cmb) + . Fixed bug #77147 (Fixing 60494 ignored ICONV_MIME_DECODE_CONTINUE_ON_ERROR). + (cmb) + +- IMAP: + . Fixed bug #77020 (null pointer dereference in imap_mail). (cmb) + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + +- Interbase: + . Fixed bug #75453 (Incorrect reflection for ibase_[p]connect). (villfa) + . Fixed bug #76443 (php+php_interbase.dll crash on module_shutdown). (Kalle) + + +- intl: + . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead + of destination). (andrewnester) + . Fixed bug #76829 (Incorrect validation of domain on idn_to_utf8() + function). (Anatol) + +- JSON: + . Added JSON_THROW_ON_ERROR flag. (Andrea) + +- LDAP: + . Added ldap_exop_refresh helper for EXOP REFRESH operation with dds overlay. + (Come) + . Added full support for sending and parsing ldap controls. (Come) + . Fixed bug #49876 (Fix LDAP path lookup on 64-bit distros). (dzuelke) + +- libxml2: + . Fixed bug #75871 (use pkg-config where available). (pmmaga) + +- litespeed: + . Fixed bug #75248 (Binary directory doesn't get created when building + only litespeed SAPI). (petk) + . Fixed bug #75251 (Missing program prefix and suffix). (petk) + +- MBstring: + . Updated to Oniguruma 6.9.0. (cmb) + . Fixed bug #65544 (mb title case conversion-first word in quotation isn't + capitalized). (Nikita) + . Fixed bug #71298 (MB_CASE_TITLE misbehaves with curled apostrophe/quote). + (Nikita) + . Fixed bug #73528 (Crash in zif_mb_send_mail). (Nikita) + . Fixed bug #74929 (mbstring functions version 7.1.1 are slow compared to 5.3 + on Windows). (Nikita) + . Fixed bug #76319 (mb_strtolower with invalid UTF-8 causes segmentation + fault). (Nikita) + . Fixed bug #76574 (use of undeclared identifiers INT_MAX and LONG_MAX). (cmb) + . Fixed bug #76594 (Bus Error due to unaligned access in zend_ini.c + OnUpdateLong). (cmb, Nikita) + . Fixed bug #76706 (mbstring.http_output_conv_mimetypes is ignored). (cmb) + . Fixed bug #76958 (Broken UTF7-IMAP conversion). (Nikita) + . Fixed bug #77025 (mb_strpos throws Unknown encoding or conversion error). + (Nikita) + . Fixed bug #77165 (mb_check_encoding crashes when argument given an empty + array). (Nikita) + +- Mysqlnd: + . Fixed bug #76386 (Prepared Statement formatter truncates fractional seconds + from date/time column). (Victor Csiky) + +- ODBC: + . Removed support for ODBCRouter. (Kalle) + . Removed support for Birdstep. (Kalle) + . Fixed bug #77079 (odbc_fetch_object has incorrect type signature). + (Jon Allen) + +- Opcache: + . Fixed bug #76466 (Loop variable confusion). (Dmitry, Laruence, Nikita) + . Fixed bug #76463 (var has array key type but not value type). (Laruence) + . Fixed bug #76446 (zend_variables.c:73: zend_string_destroy: Assertion + `!(zval_gc_flags((str)->gc)). (Nikita, Laruence) + . Fixed bug #76711 (OPcache enabled triggers false-positive "Illegal string + offset"). (Dmitry) + . Fixed bug #77058 (Type inference in opcache causes side effects). (Nikita) + . Fixed bug #77092 (array_diff_key() - segmentation fault). (Nikita) + +- OpenSSL: + . Added openssl_pkey_derive function. (Jim Zubov) + . Add min_proto_version and max_proto_version ssl stream options as well as + related constants for possible TLS protocol values. (Jakub Zelenka) + +- PCRE: + . Implemented https://wiki.php.net/rfc/pcre2-migration. (Anatol, Dmitry) + . Upgrade PCRE2 to 10.32. (Anatol) + . Fixed bug #75355 (preg_quote() does not quote # control character). + (Michael Moravec) + . Fixed bug #76512 (\w no longer includes unicode characters). (cmb) + . Fixed bug #76514 (Regression in preg_match makes it fail with + PREG_JIT_STACKLIMIT_ERROR). (Anatol) + . Fixed bug #76909 (preg_match difference between 7.3 and < 7.3). (Anatol) + +- PDO_DBlib: + . Implemented FR #69592 (allow 0-column rowsets to be skipped automatically). + (fandrieu) + . Expose TDS version as \PDO::DBLIB_ATTR_TDS_VERSION attribute on \PDO + instance. (fandrieu) + . Treat DATETIME2 columns like DATETIME. (fandrieu) + . Fixed bug #74243 (allow locales.conf to drive datetime format). (fandrieu) + +- PDO_Firebird: + . Fixed bug #74462 (PDO_Firebird returns only NULLs for results with boolean + for FIREBIRD >= 3.0). (Dorin Marcoci) + +- PDO_OCI: + . Fixed bug #74631 (PDO_PCO with PHP-FPM: OCI environment initialized + before PHP-FPM sets it up). (Ingmar Runge) + +- PDO SQLite + . Add support for additional open flags + +- pgsql: + . Added new error constants for pg_result_error(): PGSQL_DIAG_SCHEMA_NAME, + PGSQL_DIAG_TABLE_NAME, PGSQL_DIAG_COLUMN_NAME, PGSQL_DIAG_DATATYPE_NAME, + PGSQL_DIAG_CONSTRAINT_NAME and PGSQL_DIAG_SEVERITY_NONLOCALIZED. (Kalle) + . Fixed bug #77047 (pg_convert has a broken regex for the 'TIME WITHOUT + TIMEZONE' data type). (Andy Gajetzki) + +- phar: + . Fixed bug #74991 (include_path has a 4096 char limit in some cases). + (bwbroersma) + . Fixed bug #65414 (deal with leading slash when adding files correctly). + (bishopb) + +- readline: + . Added completion_append_character and completion_suppress_append options + to readline_info() if linked against libreadline. (krageon) + +- Session: + . Fixed bug #74941 (session fails to start after having headers sent). + (morozov) + +- SimpleXML: + . Fixed bug #54973 (SimpleXML casts integers wrong). (Nikita) + . Fixed bug #76712 (Assignment of empty string creates extraneous text node). + (cmb) + +- Sockets: + . Fixed bug #67619 (Validate length on socket_write). (thiagooak) + +- SOAP: + . Fixed bug #75464 (Wrong reflection on SoapClient::__setSoapHeaders). + (villfa) + . Fixed bug #70469 (SoapClient generates E_ERROR even if exceptions=1 is + used). (Anton Artamonov) + . Fixed bug #50675 (SoapClient can't handle object references correctly). + (Cameron Porter) + . Fixed bug #76348 (WSDL_CACHE_MEMORY causes Segmentation fault). (cmb) + . Fixed bug #77141 (Signedness issue in SOAP when precision=-1). (cmb) + +- SPL: + . Fixed bug #74977 (Appending AppendIterator leads to segfault). + (Andrew Nester) + . Fixed bug #75173 (incorrect behavior of AppendIterator::append in foreach + loop). (jhdxr) + . Fixed bug #74372 (autoloading file with syntax error uses next autoloader, + may hide parse error). (Nikita) + . Fixed bug #75878 (RecursiveTreeIterator::setPostfix has wrong signature). + (cmb) + . Fixed bug #74519 (strange behavior of AppendIterator). (jhdxr) + . Fixed bug #76131 (mismatch arginfo for splarray constructor). + (carusogabriel) + +- SQLite3: + . Updated bundled libsqlite to 3.24.0. (cmb) + +- Standard: + . Added is_countable() function. (Gabriel Caruso) + . Added support for the SameSite cookie directive, including an alternative + signature for setcookie(), setrawcookie() and session_set_cookie_params(). + (Frederik Bosch, pmmaga) + . Remove superfluous warnings from inet_ntop()/inet_pton(). (daverandom) + . Fixed bug #75916 (DNS_CAA record results contain garbage). (Mike, + Philip Sharp) + . Fixed unserialize(), to disable creation of unsupported data structures + through manually crafted strings. (Dmitry) + . Fixed bug #75409 (accept EFAULT in addition to ENOSYS as indicator + that getrandom() is missing). (sarciszewski) + . Fixed bug #74719 (fopen() should accept NULL as context). (Alexander Holman) + . Fixed bug #69948 (path/domain are not sanitized in setcookie). (cmb) + . Fixed bug #75996 (incorrect url in header for mt_rand). (tatarbj) + . Added hrtime() function, to get high resolution time. (welting) + . Fixed bug #48016 (stdClass::__setState is not defined although var_export() + uses it). (Andrea) + . Fixed bug #76136 (stream_socket_get_name should enclose IPv6 in brackets). + (seliver) + . Fixed bug #76688 (Disallow excessive parameters after options array). + (pmmaga) + . Fixed bug #76713 (Segmentation fault caused by property corruption). + (Laruence) + . Fixed bug #76755 (setcookie does not accept "double" type for expire time). + (Laruence) + . Fixed bug #76674 (improve array_* failure messages exposing what was passed + instead of an array). (carusogabriel) + . Fixed bug #76803 (ftruncate changes file pointer). (Anatol) + . Fixed bug #76818 (Memory corruption and segfault). (Remi) + . Fixed bug #77081 (ftruncate() changes seek pointer in c mode). (cmb, Anatol) + +- Testing: + . Implemented FR #62055 (Make run-tests.php support --CGI-- sections). (cmb) + +- Tidy: + . Support using tidyp instead of tidy. (devnexen) + . Fixed bug #74707 (Tidy has incorrect ReflectionFunction param counts for + functions taking tidy). (Gabriel Caruso) + . Fixed arginfo for tidy::__construct(). (Tyson Andre) + +- Tokenizer: + . Fixed bug #76437 (token_get_all with TOKEN_PARSE flag fails to recognise + close tag). (Laruence) + . Fixed bug #75218 (Change remaining uncatchable fatal errors for parsing + into ParseError). (Nikita) + . Fixed bug #76538 (token_get_all with TOKEN_PARSE flag fails to recognise + close tag with newline). (Nikita) + . Fixed bug #76991 (Incorrect tokenization of multiple invalid flexible + heredoc strings). (Nikita) + +- XML: + . Fixed bug #71592 (External entity processing never fails). (cmb) + +- Zlib: + . Added zlib/level context option for compress.zlib wrapper. (Sara) diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index 82fbd3bb20a76..d4dd8f090a01e 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -315,6 +315,8 @@ void sdl_restore_uri_credentials(sdlCtx *ctx) ctx->context = NULL; } +#define SAFE_STR(a) ((a)?a:"") + static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) { sdlPtr tmpsdl = ctx->sdl; @@ -376,7 +378,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { load_schema(ctx, trav2); } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -437,7 +439,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) soap_error0(E_ERROR, "Parsing WSDL: <service> has no name attribute"); } } else if (!node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -547,7 +549,7 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -649,7 +651,7 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -681,14 +683,14 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) sdlParamPtr param; if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", SAFE_STR(trav->name)); } if (node_is_equal(trav,"documentation")) { trav = trav->next; continue; } if (!node_is_equal(trav,"part")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } part = trav; param = emalloc(sizeof(sdlParam)); @@ -697,7 +699,7 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) name = get_attribute(part->properties, "name"); if (name == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: No name associated with <part> '%s'", message->name); + soap_error1(E_ERROR, "Parsing WSDL: No name associated with <part> '%s'", SAFE_STR(message->name)); } param->paramName = estrdup((char*)name->children->content); @@ -768,7 +770,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav,"port")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } port = trav; @@ -807,7 +809,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -909,7 +911,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav2,"operation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } operation = trav2; @@ -928,7 +930,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) !node_is_equal(trav3,"output") && !node_is_equal(trav3,"fault") && !node_is_equal(trav3,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav3->name)); } trav3 = trav3->next; } @@ -1106,7 +1108,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } diff --git a/ext/soap/php_sdl.c.orig b/ext/soap/php_sdl.c.orig new file mode 100644 index 0000000000000..82fbd3bb20a76 --- /dev/null +++ b/ext/soap/php_sdl.c.orig @@ -0,0 +1,3672 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Brad Lafountain <rodif_bl@yahoo.com> | + | Shane Caraveo <shane@caraveo.com> | + | Dmitry Stogov <dmitry@php.net> | + +----------------------------------------------------------------------+ +*/ + +#include "php_soap.h" +#include "ext/libxml/php_libxml.h" +#include "libxml/uri.h" + +#include "ext/standard/md5.h" +#include "zend_virtual_cwd.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +static void delete_fault(zval *zv); +static void delete_fault_persistent(zval *zv); +static void delete_binding(zval *zv); +static void delete_binding_persistent(zval *zv); +static void delete_function(zval *zv); +static void delete_function_persistent(zval *zv); +static void delete_parameter(zval *zv); +static void delete_parameter_persistent(zval *zv); +static void delete_header(zval *header); +static void delete_header_int(sdlSoapBindingFunctionHeaderPtr hdr); +static void delete_header_persistent(zval *zv); +static void delete_document(zval *zv); + +encodePtr get_encoder_from_prefix(sdlPtr sdl, xmlNodePtr node, const xmlChar *type) +{ + encodePtr enc = NULL; + xmlNsPtr nsptr; + char *ns, *cptype; + + parse_namespace(type, &cptype, &ns); + nsptr = xmlSearchNs(node->doc, node, BAD_CAST(ns)); + if (nsptr != NULL) { + enc = get_encoder(sdl, (char*)nsptr->href, cptype); + if (enc == NULL) { + enc = get_encoder_ex(sdl, cptype, strlen(cptype)); + } + } else { + enc = get_encoder_ex(sdl, (char*)type, xmlStrlen(type)); + } + efree(cptype); + if (ns) {efree(ns);} + return enc; +} + +static sdlTypePtr get_element(sdlPtr sdl, xmlNodePtr node, const xmlChar *type) +{ + sdlTypePtr ret = NULL; + + if (sdl->elements) { + xmlNsPtr nsptr; + char *ns, *cptype; + sdlTypePtr sdl_type; + + parse_namespace(type, &cptype, &ns); + nsptr = xmlSearchNs(node->doc, node, BAD_CAST(ns)); + if (nsptr != NULL) { + int ns_len = xmlStrlen(nsptr->href); + int type_len = strlen(cptype); + int len = ns_len + type_len + 1; + char *nscat = emalloc(len + 1); + + memcpy(nscat, nsptr->href, ns_len); + nscat[ns_len] = ':'; + memcpy(nscat+ns_len+1, cptype, type_len); + nscat[len] = '\0'; + + if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, nscat, len)) != NULL) { + ret = sdl_type; + } else if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, (char*)type, type_len)) != NULL) { + ret = sdl_type; + } + efree(nscat); + } else { + if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, (char*)type, xmlStrlen(type))) != NULL) { + ret = sdl_type; + } + } + + efree(cptype); + if (ns) {efree(ns);} + } + return ret; +} + +encodePtr get_encoder(sdlPtr sdl, const char *ns, const char *type) +{ + encodePtr enc = NULL; + char *nscat; + int ns_len = ns ? strlen(ns) : 0; + int type_len = strlen(type); + int len = ns_len + type_len + 1; + + nscat = emalloc(len + 1); + if (ns) { + memcpy(nscat, ns, ns_len); + } + nscat[ns_len] = ':'; + memcpy(nscat+ns_len+1, type, type_len); + nscat[len] = '\0'; + + enc = get_encoder_ex(sdl, nscat, len); + + if (enc == NULL && + ((ns_len == sizeof(SOAP_1_1_ENC_NAMESPACE)-1 && + memcmp(ns, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)-1) == 0) || + (ns_len == sizeof(SOAP_1_2_ENC_NAMESPACE)-1 && + memcmp(ns, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)-1) == 0))) { + char *enc_nscat; + int enc_ns_len; + int enc_len; + + enc_ns_len = sizeof(XSD_NAMESPACE)-1; + enc_len = enc_ns_len + type_len + 1; + enc_nscat = emalloc(enc_len + 1); + memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); + enc_nscat[enc_ns_len] = ':'; + memcpy(enc_nscat+enc_ns_len+1, type, type_len); + enc_nscat[enc_len] = '\0'; + + enc = get_encoder_ex(NULL, enc_nscat, enc_len); + efree(enc_nscat); + if (enc && sdl) { + encodePtr new_enc = pemalloc(sizeof(encode), sdl->is_persistent); + memcpy(new_enc, enc, sizeof(encode)); + if (sdl->is_persistent) { + new_enc->details.ns = zend_strndup(ns, ns_len); + new_enc->details.type_str = strdup(new_enc->details.type_str); + } else { + new_enc->details.ns = estrndup(ns, ns_len); + new_enc->details.type_str = estrdup(new_enc->details.type_str); + } + if (sdl->encoders == NULL) { + sdl->encoders = pemalloc(sizeof(HashTable), sdl->is_persistent); + zend_hash_init(sdl->encoders, 0, NULL, delete_encoder, sdl->is_persistent); + } + zend_hash_str_update_ptr(sdl->encoders, nscat, len, new_enc); + enc = new_enc; + } + } + efree(nscat); + return enc; +} + +encodePtr get_encoder_ex(sdlPtr sdl, const char *nscat, int len) +{ + encodePtr enc; + + if ((enc = zend_hash_str_find_ptr(&SOAP_GLOBAL(defEnc), (char*)nscat, len)) != NULL) { + return enc; + } else if (sdl && sdl->encoders && (enc = zend_hash_str_find_ptr(sdl->encoders, (char*)nscat, len)) != NULL) { + return enc; + } + return NULL; +} + +sdlBindingPtr get_binding_from_type(sdlPtr sdl, sdlBindingType type) +{ + sdlBindingPtr binding; + + if (sdl == NULL) { + return NULL; + } + + ZEND_HASH_FOREACH_PTR(sdl->bindings, binding) { + if (binding->bindingType == type) { + return binding; + } + } ZEND_HASH_FOREACH_END(); + return NULL; +} + +sdlBindingPtr get_binding_from_name(sdlPtr sdl, char *name, char *ns) +{ + sdlBindingPtr binding; + smart_str key = {0}; + + smart_str_appends(&key, ns); + smart_str_appendc(&key, ':'); + smart_str_appends(&key, name); + smart_str_0(&key); + + binding = zend_hash_find_ptr(sdl->bindings, key.s); + + smart_str_free(&key); + return binding; +} + +static int is_wsdl_element(xmlNodePtr node) +{ + if (node->ns && strcmp((char*)node->ns->href, WSDL_NAMESPACE) != 0) { + xmlAttrPtr attr; + if ((attr = get_attribute_ex(node->properties, "required", WSDL_NAMESPACE)) != NULL && + attr->children && attr->children->content && + (strcmp((char*)attr->children->content, "1") == 0 || + strcmp((char*)attr->children->content, "true") == 0)) { + soap_error1(E_ERROR, "Parsing WSDL: Unknown required WSDL extension '%s'", node->ns->href); + } + return 0; + } + return 1; +} + +void sdl_set_uri_credentials(sdlCtx *ctx, char *uri) +{ + char *s; + size_t l1, l2; + zval context; + zval *header = NULL; + + /* check if we load xsd from the same server */ + s = strstr(ctx->sdl->source, "://"); + if (!s) return; + s = strchr(s+3, '/'); + l1 = s ? (size_t)(s - ctx->sdl->source) : strlen(ctx->sdl->source); + s = strstr((char*)uri, "://"); + if (!s) return; + s = strchr(s+3, '/'); + l2 = s ? (size_t)(s - (char*)uri) : strlen((char*)uri); + if (l1 != l2) { + /* check for http://...:80/ */ + if (l1 > 11 && + ctx->sdl->source[4] == ':' && + ctx->sdl->source[l1-3] == ':' && + ctx->sdl->source[l1-2] == '8' && + ctx->sdl->source[l1-1] == '0') { + l1 -= 3; + } + if (l2 > 11 && + uri[4] == ':' && + uri[l2-3] == ':' && + uri[l2-2] == '8' && + uri[l2-1] == '0') { + l2 -= 3; + } + /* check for https://...:443/ */ + if (l1 > 13 && + ctx->sdl->source[4] == 's' && + ctx->sdl->source[l1-4] == ':' && + ctx->sdl->source[l1-3] == '4' && + ctx->sdl->source[l1-2] == '4' && + ctx->sdl->source[l1-1] == '3') { + l1 -= 4; + } + if (l2 > 13 && + uri[4] == 's' && + uri[l2-4] == ':' && + uri[l2-3] == '4' && + uri[l2-2] == '4' && + uri[l2-1] == '3') { + l2 -= 4; + } + } + if (l1 != l2 || memcmp(ctx->sdl->source, uri, l1) != 0) { + /* another server. clear authentication credentals */ + php_libxml_switch_context(NULL, &context); + php_libxml_switch_context(&context, NULL); + if (Z_TYPE(context) != IS_UNDEF) { + zval *context_ptr = &context; + ctx->context = php_stream_context_from_zval(context_ptr, 1); + + if (ctx->context && + (header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL) { + s = strstr(Z_STRVAL_P(header), "Authorization: Basic"); + if (s && (s == Z_STRVAL_P(header) || *(s-1) == '\n' || *(s-1) == '\r')) { + char *rest = strstr(s, "\r\n"); + if (rest) { + zval new_header; + + rest += 2; + ZVAL_NEW_STR(&new_header, zend_string_alloc(Z_STRLEN_P(header) - (rest - s), 0)); + memcpy(Z_STRVAL(new_header), Z_STRVAL_P(header), s - Z_STRVAL_P(header)); + memcpy(Z_STRVAL(new_header) + (s - Z_STRVAL_P(header)), rest, Z_STRLEN_P(header) - (rest - Z_STRVAL_P(header)) + 1); + ZVAL_COPY(&ctx->old_header, header); + php_stream_context_set_option(ctx->context, "http", "header", &new_header); + zval_ptr_dtor(&new_header); + } + } + } + } + } +} + +void sdl_restore_uri_credentials(sdlCtx *ctx) +{ + if (Z_TYPE(ctx->old_header) != IS_UNDEF) { + php_stream_context_set_option(ctx->context, "http", "header", &ctx->old_header); + zval_ptr_dtor(&ctx->old_header); + ZVAL_UNDEF(&ctx->old_header); + } + ctx->context = NULL; +} + +static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) +{ + sdlPtr tmpsdl = ctx->sdl; + xmlDocPtr wsdl; + xmlNodePtr root, definitions, trav; + xmlAttrPtr targetNamespace; + + if (zend_hash_str_exists(&ctx->docs, struri, strlen(struri))) { + return; + } + + sdl_set_uri_credentials(ctx, struri); + wsdl = soap_xmlParseFile(struri); + sdl_restore_uri_credentials(ctx); + + if (!wsdl) { + xmlErrorPtr xmlErrorPtr = xmlGetLastError(); + + if (xmlErrorPtr) { + soap_error2(E_ERROR, "Parsing WSDL: Couldn't load from '%s' : %s", struri, xmlErrorPtr->message); + } else { + soap_error1(E_ERROR, "Parsing WSDL: Couldn't load from '%s'", struri); + } + } + + zend_hash_str_add_ptr(&ctx->docs, struri, strlen(struri), wsdl); + + root = wsdl->children; + definitions = get_node_ex(root, "definitions", WSDL_NAMESPACE); + if (!definitions) { + if (include) { + xmlNodePtr schema = get_node_ex(root, "schema", XSD_NAMESPACE); + if (schema) { + load_schema(ctx, schema); + return; + } + } + soap_error1(E_ERROR, "Parsing WSDL: Couldn't find <definitions> in '%s'", struri); + } + + if (!include) { + targetNamespace = get_attribute(definitions->properties, "targetNamespace"); + if (targetNamespace) { + tmpsdl->target_ns = estrdup((char*)targetNamespace->children->content); + } + } + + trav = definitions->children; + while (trav != NULL) { + if (!is_wsdl_element(trav)) { + trav = trav->next; + continue; + } + if (node_is_equal(trav,"types")) { + /* TODO: Only one "types" is allowed */ + xmlNodePtr trav2 = trav->children; + + while (trav2 != NULL) { + if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { + load_schema(ctx, trav2); + } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + trav2 = trav2->next; + } + } else if (node_is_equal(trav,"import")) { + /* TODO: namespace ??? */ + xmlAttrPtr tmp = get_attribute(trav->properties, "location"); + if (tmp) { + xmlChar *uri; + xmlChar *base = xmlNodeGetBase(trav->doc, trav); + + if (base == NULL) { + uri = xmlBuildURI(tmp->children->content, trav->doc->URL); + } else { + uri = xmlBuildURI(tmp->children->content, base); + xmlFree(base); + } + load_wsdl_ex(this_ptr, (char*)uri, ctx, 1); + xmlFree(uri); + } + + } else if (node_is_equal(trav,"message")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->messages, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: <message> '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: <message> has no name attribute"); + } + + } else if (node_is_equal(trav,"portType")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->portTypes, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: <portType> '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: <portType> has no name attribute"); + } + + } else if (node_is_equal(trav,"binding")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->bindings, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: <binding> '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: <binding> has no name attribute"); + } + + } else if (node_is_equal(trav,"service")) { + xmlAttrPtr name = get_attribute(trav->properties, "name"); + if (name && name->children && name->children->content) { + if (zend_hash_str_add_ptr(&ctx->services, (char*)name->children->content, xmlStrlen(name->children->content), trav) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: <service> '%s' already defined", name->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: <service> has no name attribute"); + } + } else if (!node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } +} + +static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xmlNodePtr header, char* wsdl_soap_namespace, int fault) +{ + xmlAttrPtr tmp; + xmlNodePtr message, part; + char *ctype; + sdlSoapBindingFunctionHeaderPtr h; + + tmp = get_attribute(header->properties, "message"); + if (!tmp) { + soap_error0(E_ERROR, "Parsing WSDL: Missing message attribute for <header>"); + } + + ctype = strrchr((char*)tmp->children->content,':'); + if (ctype == NULL) { + ctype = (char*)tmp->children->content; + } else { + ++ctype; + } + if ((message = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing <message> with name '%s'", tmp->children->content); + } + + tmp = get_attribute(header->properties, "part"); + if (!tmp) { + soap_error0(E_ERROR, "Parsing WSDL: Missing part attribute for <header>"); + } + part = get_node_with_attribute_ex(message->children, "part", WSDL_NAMESPACE, "name", (char*)tmp->children->content, NULL); + if (!part) { + soap_error1(E_ERROR, "Parsing WSDL: Missing part '%s' in <message>", tmp->children->content); + } + + h = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(h, 0, sizeof(sdlSoapBindingFunctionHeader)); + h->name = estrdup((char*)tmp->children->content); + + tmp = get_attribute(header->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "encoded", sizeof("encoded"))) { + h->use = SOAP_ENCODED; + } else { + h->use = SOAP_LITERAL; + } + + tmp = get_attribute(header->properties, "namespace"); + if (tmp) { + h->ns = estrdup((char*)tmp->children->content); + } + + if (h->use == SOAP_ENCODED) { + tmp = get_attribute(header->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + h->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + h->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + + tmp = get_attribute(part->properties, "type"); + if (tmp != NULL) { + h->encode = get_encoder_from_prefix(ctx->sdl, part, tmp->children->content); + } else { + tmp = get_attribute(part->properties, "element"); + if (tmp != NULL) { + h->element = get_element(ctx->sdl, part, tmp->children->content); + if (h->element) { + h->encode = h->element->encode; + if (!h->ns && h->element->namens) { + h->ns = estrdup(h->element->namens); + } + if (h->element->name) { + efree(h->name); + h->name = estrdup(h->element->name); + } + } + } + } + if (!fault) { + xmlNodePtr trav = header->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "headerfault", wsdl_soap_namespace)) { + sdlSoapBindingFunctionHeaderPtr hf = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, 1); + smart_str key = {0}; + + if (h->headerfaults == NULL) { + h->headerfaults = emalloc(sizeof(HashTable)); + zend_hash_init(h->headerfaults, 0, NULL, delete_header, 0); + } + + if (hf->ns) { + smart_str_appends(&key,hf->ns); + smart_str_appendc(&key,':'); + } + smart_str_appends(&key,hf->name); + smart_str_0(&key); + if (zend_hash_add_ptr(h->headerfaults, key.s, hf) == NULL) { + delete_header_int(hf); + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } + } + return h; +} + +static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap_namespace, sdlSoapBindingFunctionBody *binding, HashTable* params) +{ + xmlNodePtr body, trav; + xmlAttrPtr tmp; + + trav = node->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "body", wsdl_soap_namespace)) { + body = trav; + + tmp = get_attribute(body->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "literal", sizeof("literal"))) { + binding->use = SOAP_LITERAL; + } else { + binding->use = SOAP_ENCODED; + } + + tmp = get_attribute(body->properties, "namespace"); + if (tmp) { + binding->ns = estrdup((char*)tmp->children->content); + } + + tmp = get_attribute(body->properties, "parts"); + if (tmp) { + HashTable ht; + char *parts = (char*)tmp->children->content; + + /* Delete all parts those are not in the "parts" attribute */ + zend_hash_init(&ht, 0, NULL, delete_parameter, 0); + while (*parts) { + sdlParamPtr param; + int found = 0; + char *end; + + while (*parts == ' ') ++parts; + if (*parts == '\0') break; + end = strchr(parts, ' '); + if (end) *end = '\0'; + ZEND_HASH_FOREACH_PTR(params, param) { + if (param->paramName && + strcmp(parts, param->paramName) == 0) { + sdlParamPtr x_param; + x_param = emalloc(sizeof(sdlParam)); + *x_param = *param; + param->paramName = NULL; + zend_hash_next_index_insert_ptr(&ht, x_param); + found = 1; + break; + } + } ZEND_HASH_FOREACH_END(); + if (!found) { + soap_error1(E_ERROR, "Parsing WSDL: Missing part '%s' in <message>", parts); + } + parts += strlen(parts); + if (end) *end = ' '; + } + zend_hash_destroy(params); + *params = ht; + } + + if (binding->use == SOAP_ENCODED) { + tmp = get_attribute(body->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + } else if (node_is_equal_ex(trav, "header", wsdl_soap_namespace)) { + sdlSoapBindingFunctionHeaderPtr h = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, 0); + smart_str key = {0}; + + if (binding->headers == NULL) { + binding->headers = emalloc(sizeof(HashTable)); + zend_hash_init(binding->headers, 0, NULL, delete_header, 0); + } + + if (h->ns) { + smart_str_appends(&key,h->ns); + smart_str_appendc(&key,':'); + } + smart_str_appends(&key,h->name); + smart_str_0(&key); + if (zend_hash_add_ptr(binding->headers, key.s, h) == NULL) { + delete_header_int(h); + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } +} + +static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) +{ + xmlNodePtr trav, part, message = NULL, tmp; + HashTable* parameters = NULL; + char *ctype; + + ctype = strrchr((char*)message_name,':'); + if (ctype == NULL) { + ctype = (char*)message_name; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing <message> with name '%s'", message_name); + } + message = tmp; + + parameters = emalloc(sizeof(HashTable)); + zend_hash_init(parameters, 0, NULL, delete_parameter, 0); + + trav = message->children; + while (trav != NULL) { + xmlAttrPtr element, type, name; + sdlParamPtr param; + + if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); + } + if (node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"part")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + part = trav; + param = emalloc(sizeof(sdlParam)); + memset(param,0,sizeof(sdlParam)); + param->order = 0; + + name = get_attribute(part->properties, "name"); + if (name == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: No name associated with <part> '%s'", message->name); + } + + param->paramName = estrdup((char*)name->children->content); + + type = get_attribute(part->properties, "type"); + if (type != NULL) { + param->encode = get_encoder_from_prefix(ctx->sdl, part, type->children->content); + } else { + element = get_attribute(part->properties, "element"); + if (element != NULL) { + param->element = get_element(ctx->sdl, part, element->children->content); + if (param->element) { + param->encode = param->element->encode; + } + } + } + + zend_hash_next_index_insert_ptr(parameters, param); + + trav = trav->next; + } + return parameters; +} + +static sdlPtr load_wsdl(zval *this_ptr, char *struri) +{ + sdlCtx ctx; + int i,n; + + memset(&ctx,0,sizeof(ctx)); + ctx.sdl = emalloc(sizeof(sdl)); + memset(ctx.sdl, 0, sizeof(sdl)); + ctx.sdl->source = estrdup(struri); + zend_hash_init(&ctx.sdl->functions, 0, NULL, delete_function, 0); + + zend_hash_init(&ctx.docs, 0, NULL, delete_document, 0); + zend_hash_init(&ctx.messages, 0, NULL, NULL, 0); + zend_hash_init(&ctx.bindings, 0, NULL, NULL, 0); + zend_hash_init(&ctx.portTypes, 0, NULL, NULL, 0); + zend_hash_init(&ctx.services, 0, NULL, NULL, 0); + + load_wsdl_ex(this_ptr, struri, &ctx, 0); + zend_try { + + schema_pass2(&ctx); + + n = zend_hash_num_elements(&ctx.services); + if (n > 0) { + zend_hash_internal_pointer_reset(&ctx.services); + for (i = 0; i < n; i++) { + xmlNodePtr service, tmp; + xmlNodePtr trav, port; + int has_soap_port = 0; + + service = tmp = zend_hash_get_current_data_ptr(&ctx.services); + + trav = service->children; + while (trav != NULL) { + xmlAttrPtr type, name, bindingAttr, location; + xmlNodePtr portType, operation; + xmlNodePtr address, binding, trav2; + char *ctype; + sdlBindingPtr tmpbinding; + char *wsdl_soap_namespace = NULL; + + if (!is_wsdl_element(trav) || node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"port")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + + port = trav; + + tmpbinding = emalloc(sizeof(sdlBinding)); + memset(tmpbinding, 0, sizeof(sdlBinding)); + + bindingAttr = get_attribute(port->properties, "binding"); + if (bindingAttr == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: No binding associated with <port>"); + } + + /* find address and figure out binding type */ + address = NULL; + trav2 = port->children; + while (trav2 != NULL) { + if (node_is_equal(trav2,"address") && trav2->ns) { + if (!strncmp((char*)trav2->ns->href, WSDL_SOAP11_NAMESPACE, sizeof(WSDL_SOAP11_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = WSDL_SOAP11_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_SOAP12_NAMESPACE, sizeof(WSDL_SOAP12_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = WSDL_SOAP12_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, RPC_SOAP12_NAMESPACE, sizeof(RPC_SOAP12_NAMESPACE))) { + address = trav2; + wsdl_soap_namespace = RPC_SOAP12_NAMESPACE; + tmpbinding->bindingType = BINDING_SOAP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_HTTP11_NAMESPACE, sizeof(WSDL_HTTP11_NAMESPACE))) { + address = trav2; + tmpbinding->bindingType = BINDING_HTTP; + } else if (!strncmp((char*)trav2->ns->href, WSDL_HTTP12_NAMESPACE, sizeof(WSDL_HTTP12_NAMESPACE))) { + address = trav2; + tmpbinding->bindingType = BINDING_HTTP; + } + } + if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + trav2 = trav2->next; + } + if (!address || tmpbinding->bindingType == BINDING_HTTP) { + if (has_soap_port || trav->next || i < n-1) { + efree(tmpbinding); + trav = trav->next; + continue; + } else if (!address) { + soap_error0(E_ERROR, "Parsing WSDL: No address associated with <port>"); + } + } + has_soap_port = 1; + + location = get_attribute(address->properties, "location"); + if (!location) { + soap_error0(E_ERROR, "Parsing WSDL: No location associated with <port>"); + } + + tmpbinding->location = estrdup((char*)location->children->content); + + ctype = strrchr((char*)bindingAttr->children->content,':'); + if (ctype == NULL) { + ctype = (char*)bindingAttr->children->content; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx.bindings, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: No <binding> element with name '%s'", ctype); + } + binding = tmp; + + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapBindingNode; + xmlAttrPtr tmp; + + soapBinding = emalloc(sizeof(sdlSoapBinding)); + memset(soapBinding, 0, sizeof(sdlSoapBinding)); + soapBinding->style = SOAP_DOCUMENT; + + soapBindingNode = get_node_ex(binding->children, "binding", wsdl_soap_namespace); + if (soapBindingNode) { + tmp = get_attribute(soapBindingNode->properties, "style"); + if (tmp && !strncmp((char*)tmp->children->content, "rpc", sizeof("rpc"))) { + soapBinding->style = SOAP_RPC; + } + + tmp = get_attribute(soapBindingNode->properties, "transport"); + if (tmp) { + if (strncmp((char*)tmp->children->content, WSDL_HTTP_TRANSPORT, sizeof(WSDL_HTTP_TRANSPORT)) == 0) { + soapBinding->transport = SOAP_TRANSPORT_HTTP; + } else { + /* try the next binding */ + efree(soapBinding); + efree(tmpbinding->location); + efree(tmpbinding); + trav = trav->next; + continue; + } + } + } + tmpbinding->bindingAttributes = (void *)soapBinding; + } + + name = get_attribute(binding->properties, "name"); + if (name == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for <binding>"); + } + tmpbinding->name = estrdup((char*)name->children->content); + + type = get_attribute(binding->properties, "type"); + if (type == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'type' attribute for <binding>"); + } + + ctype = strrchr((char*)type->children->content,':'); + if (ctype == NULL) { + ctype = (char*)type->children->content; + } else { + ++ctype; + } + if ((tmp = zend_hash_str_find_ptr(&ctx.portTypes, ctype, strlen(ctype))) == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing <portType> with name '%s'", name->children->content); + } + portType = tmp; + + trav2 = binding->children; + while (trav2 != NULL) { + sdlFunctionPtr function; + xmlNodePtr input, output, fault, portTypeOperation, trav3; + xmlAttrPtr op_name, paramOrder; + + if ((tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav2, "binding", wsdl_soap_namespace)) || + !is_wsdl_element(trav2) || + node_is_equal(trav2,"documentation")) { + trav2 = trav2->next; + continue; + } + if (!node_is_equal(trav2,"operation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + } + + operation = trav2; + + op_name = get_attribute(operation->properties, "name"); + if (op_name == NULL) { + soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for <operation>"); + } + + trav3 = operation->children; + while (trav3 != NULL) { + if (tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav3, "operation", wsdl_soap_namespace)) { + } else if (is_wsdl_element(trav3) && + !node_is_equal(trav3,"input") && + !node_is_equal(trav3,"output") && + !node_is_equal(trav3,"fault") && + !node_is_equal(trav3,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); + } + trav3 = trav3->next; + } + + portTypeOperation = get_node_with_attribute_ex(portType->children, "operation", WSDL_NAMESPACE, "name", (char*)op_name->children->content, NULL); + if (portTypeOperation == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing <portType>/<operation> with name '%s'", op_name->children->content); + } + + function = emalloc(sizeof(sdlFunction)); + memset(function, 0, sizeof(sdlFunction)); + function->functionName = estrdup((char*)op_name->children->content); + + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunctionBinding; + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapOperation; + xmlAttrPtr tmp; + + soapFunctionBinding = emalloc(sizeof(sdlSoapBindingFunction)); + memset(soapFunctionBinding, 0, sizeof(sdlSoapBindingFunction)); + soapBinding = (sdlSoapBindingPtr)tmpbinding->bindingAttributes; + soapFunctionBinding->style = soapBinding->style; + + soapOperation = get_node_ex(operation->children, "operation", wsdl_soap_namespace); + if (soapOperation) { + tmp = get_attribute(soapOperation->properties, "soapAction"); + if (tmp) { + soapFunctionBinding->soapAction = estrdup((char*)tmp->children->content); + } + + tmp = get_attribute(soapOperation->properties, "style"); + if (tmp) { + if (!strncmp((char*)tmp->children->content, "rpc", sizeof("rpc"))) { + soapFunctionBinding->style = SOAP_RPC; + } else { + soapFunctionBinding->style = SOAP_DOCUMENT; + } + } else { + soapFunctionBinding->style = soapBinding->style; + } + } + + function->bindingAttributes = (void *)soapFunctionBinding; + } + + input = get_node_ex(portTypeOperation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + xmlAttrPtr message; + + message = get_attribute(input->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for <input> of '%s'", op_name->children->content); + } + function->requestParameters = wsdl_message(&ctx, message->children->content); + +/* FIXME + xmlAttrPtr name = get_attribute(input->properties, "name"); + if (name != NULL) { + function->requestName = estrdup(name->children->content); + } else { +*/ + { + function->requestName = estrdup(function->functionName); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + input = get_node_ex(operation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + wsdl_soap_binding_body(&ctx, input, wsdl_soap_namespace, &soapFunctionBinding->input, function->requestParameters); + } + } + } + + output = get_node_ex(portTypeOperation->children, "output", WSDL_NAMESPACE); + if (output != NULL) { + xmlAttrPtr message; + + message = get_attribute(output->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for <output> of '%s'", op_name->children->content); + } + function->responseParameters = wsdl_message(&ctx, message->children->content); + +/* FIXME + xmlAttrPtr name = get_attribute(output->properties, "name"); + if (name != NULL) { + function->responseName = estrdup(name->children->content); + } else if (input == NULL) { + function->responseName = estrdup(function->functionName); + } else { +*/ + { + int len = strlen(function->functionName); + function->responseName = emalloc(len + sizeof("Response")); + memcpy(function->responseName, function->functionName, len); + memcpy(function->responseName+len, "Response", sizeof("Response")); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + output = get_node_ex(operation->children, "output", WSDL_NAMESPACE); + if (output != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + wsdl_soap_binding_body(&ctx, output, wsdl_soap_namespace, &soapFunctionBinding->output, function->responseParameters); + } + } + } + + paramOrder = get_attribute(portTypeOperation->properties, "parameterOrder"); + if (paramOrder) { + /* FIXME: */ + } + + fault = portTypeOperation->children; + while (fault != NULL) { + if (node_is_equal_ex(fault, "fault", WSDL_NAMESPACE)) { + xmlAttrPtr message, name; + sdlFaultPtr f; + + name = get_attribute(fault->properties, "name"); + if (name == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for <fault> of '%s'", op_name->children->content); + } + message = get_attribute(fault->properties, "message"); + if (message == NULL) { + soap_error1(E_ERROR, "Parsing WSDL: Missing name for <output> of '%s'", op_name->children->content); + } + + f = emalloc(sizeof(sdlFault)); + memset(f, 0, sizeof(sdlFault)); + + f->name = estrdup((char*)name->children->content); + f->details = wsdl_message(&ctx, message->children->content); + if (f->details == NULL || zend_hash_num_elements(f->details) > 1) { + soap_error1(E_ERROR, "Parsing WSDL: The fault message '%s' must have a single part", message->children->content); + } + + if (tmpbinding->bindingType == BINDING_SOAP) { + xmlNodePtr soap_fault = get_node_with_attribute_ex(operation->children, "fault", WSDL_NAMESPACE, "name", f->name, NULL); + if (soap_fault != NULL) { + xmlNodePtr trav = soap_fault->children; + while (trav != NULL) { + if (node_is_equal_ex(trav, "fault", wsdl_soap_namespace)) { + xmlAttrPtr tmp; + sdlSoapBindingFunctionFaultPtr binding; + + binding = f->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); + memset(f->bindingAttributes, 0, sizeof(sdlSoapBindingFunctionFault)); + + tmp = get_attribute(trav->properties, "use"); + if (tmp && !strncmp((char*)tmp->children->content, "encoded", sizeof("encoded"))) { + binding->use = SOAP_ENCODED; + } else { + binding->use = SOAP_LITERAL; + } + + tmp = get_attribute(trav->properties, "namespace"); + if (tmp) { + binding->ns = estrdup((char*)tmp->children->content); + } + + if (binding->use == SOAP_ENCODED) { + tmp = get_attribute(trav->properties, "encodingStyle"); + if (tmp) { + if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + binding->encodingStyle = SOAP_ENCODING_1_2; + } else { + soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + } + } + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + } + trav = trav->next; + } + } + } + if (function->faults == NULL) { + function->faults = emalloc(sizeof(HashTable)); + zend_hash_init(function->faults, 0, NULL, delete_fault, 0); + } + if (zend_hash_str_add_ptr(function->faults, f->name, strlen(f->name), f) == NULL) { + soap_error2(E_ERROR, "Parsing WSDL: <fault> with name '%s' already defined in '%s'", f->name, op_name->children->content); + } + } + fault = fault->next; + } + + function->binding = tmpbinding; + + { + char *tmp = estrdup(function->functionName); + int len = strlen(tmp); + + if (zend_hash_str_add_ptr(&ctx.sdl->functions, php_strtolower(tmp, len), len, function) == NULL) { + zend_hash_next_index_insert_ptr(&ctx.sdl->functions, function); + } + efree(tmp); + if (function->requestName != NULL && strcmp(function->requestName,function->functionName) != 0) { + if (ctx.sdl->requests == NULL) { + ctx.sdl->requests = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.sdl->requests, 0, NULL, NULL, 0); + } + tmp = estrdup(function->requestName); + len = strlen(tmp); + zend_hash_str_add_ptr(ctx.sdl->requests, php_strtolower(tmp, len), len, function); + efree(tmp); + } + } + trav2 = trav2->next; + } + + if (!ctx.sdl->bindings) { + ctx.sdl->bindings = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.sdl->bindings, 0, NULL, delete_binding, 0); + } + + if (!zend_hash_str_add_ptr(ctx.sdl->bindings, tmpbinding->name, strlen(tmpbinding->name), tmpbinding)) { + zend_hash_next_index_insert_ptr(ctx.sdl->bindings, tmpbinding); + } + trav= trav->next; + } + + zend_hash_move_forward(&ctx.services); + } + } else { + soap_error0(E_ERROR, "Parsing WSDL: Couldn't bind to service"); + } + + if (ctx.sdl->bindings == NULL || ctx.sdl->bindings->nNumOfElements == 0) { + soap_error0(E_ERROR, "Parsing WSDL: Could not find any usable binding services in WSDL."); + } + + } zend_catch { + /* Avoid persistent memory leak. */ + zend_hash_destroy(&ctx.docs); + zend_bailout(); + } zend_end_try(); + + zend_hash_destroy(&ctx.messages); + zend_hash_destroy(&ctx.bindings); + zend_hash_destroy(&ctx.portTypes); + zend_hash_destroy(&ctx.services); + zend_hash_destroy(&ctx.docs); + + return ctx.sdl; +} + +#define WSDL_CACHE_VERSION 0x10 + +#define WSDL_CACHE_GET(ret,type,buf) memcpy(&ret,*buf,sizeof(type)); *buf += sizeof(type); +#define WSDL_CACHE_GET_INT(ret,buf) ret = ((unsigned char)(*buf)[0])|((unsigned char)(*buf)[1]<<8)|((unsigned char)(*buf)[2]<<16)|((unsigned)(*buf)[3]<<24); *buf += 4; +#define WSDL_CACHE_GET_1(ret,type,buf) ret = (type)(**buf); (*buf)++; +#define WSDL_CACHE_GET_N(ret,n,buf) memcpy(ret,*buf,n); *buf += n; +#define WSDL_CACHE_SKIP(n,buf) *buf += n; + +#define WSDL_CACHE_PUT_INT(val,buf) smart_str_appendc(buf,(char)(val & 0xff)); \ + smart_str_appendc(buf,(char)((val >> 8) & 0xff)); \ + smart_str_appendc(buf,(char)((val >> 16) & 0xff)); \ + smart_str_appendc(buf,(char)((val >> 24) & 0xff)); +#define WSDL_CACHE_PUT_1(val,buf) smart_str_appendc(buf,val); +#define WSDL_CACHE_PUT_N(val,n,buf) smart_str_appendl(buf,(char*)val,n); + +#define WSDL_NO_STRING_MARKER 0x7fffffff + +static char* sdl_deserialize_string(char **in) +{ + char *s; + int len; + + WSDL_CACHE_GET_INT(len, in); + if (len == WSDL_NO_STRING_MARKER) { + return NULL; + } else { + s = emalloc(len+1); + WSDL_CACHE_GET_N(s, len, in); + s[len] = '\0'; + return s; + } +} + +static void sdl_deserialize_key(HashTable* ht, void* data, char **in) +{ + int len; + + WSDL_CACHE_GET_INT(len, in); + if (len == WSDL_NO_STRING_MARKER) { + zend_hash_next_index_insert_ptr(ht, data); + } else { + zend_hash_str_add_ptr(ht, *in, len, data); + WSDL_CACHE_SKIP(len, in); + } +} + +static void sdl_deserialize_attribute(sdlAttributePtr attr, encodePtr *encoders, char **in) +{ + int i; + + attr->name = sdl_deserialize_string(in); + attr->namens = sdl_deserialize_string(in); + attr->ref = sdl_deserialize_string(in); + attr->def = sdl_deserialize_string(in); + attr->fixed = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(attr->form, sdlForm, in); + WSDL_CACHE_GET_1(attr->use, sdlUse, in); + WSDL_CACHE_GET_INT(i, in); + attr->encode = encoders[i]; + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + attr->extraAttributes = emalloc(sizeof(HashTable)); + zend_hash_init(attr->extraAttributes, i, NULL, delete_extra_attribute, 0); + while (i > 0) { + sdlExtraAttributePtr x = emalloc(sizeof(sdlExtraAttribute)); + sdl_deserialize_key(attr->extraAttributes, x, in); + x->ns = sdl_deserialize_string(in); + x->val = sdl_deserialize_string(in); + --i; + } + } +} + +static sdlRestrictionIntPtr sdl_deserialize_resriction_int(char **in) +{ + if (**in == 1) { + sdlRestrictionIntPtr x = emalloc(sizeof(sdlRestrictionInt)); + WSDL_CACHE_SKIP(1, in); + WSDL_CACHE_GET_INT(x->value, in); + WSDL_CACHE_GET_1(x->fixed, char, in); + return x; + } else { + WSDL_CACHE_SKIP(1, in); + return NULL; + } +} + +static sdlRestrictionCharPtr sdl_deserialize_resriction_char(char **in) +{ + if (**in == 1) { + sdlRestrictionCharPtr x = emalloc(sizeof(sdlRestrictionChar)); + WSDL_CACHE_SKIP(1, in); + x->value = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(x->fixed, char, in); + return x; + } else { + WSDL_CACHE_SKIP(1, in); + return NULL; + } +} + +static sdlContentModelPtr sdl_deserialize_model(sdlTypePtr *types, sdlTypePtr *elements, char **in) +{ + int i; + sdlContentModelPtr model = emalloc(sizeof(sdlContentModel)); + + WSDL_CACHE_GET_1(model->kind, sdlContentKind, in); + WSDL_CACHE_GET_INT(model->min_occurs, in); + WSDL_CACHE_GET_INT(model->max_occurs, in); + switch (model->kind) { + case XSD_CONTENT_ELEMENT: + WSDL_CACHE_GET_INT(i, in); + model->u.element = elements[i]; + break; + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: + WSDL_CACHE_GET_INT(i, in); + model->u.content = emalloc(sizeof(HashTable)); + zend_hash_init(model->u.content, i, NULL, delete_model, 0); + while (i > 0) { + sdlContentModelPtr x = sdl_deserialize_model(types, elements, in); + zend_hash_next_index_insert_ptr(model->u.content, x); + i--; + } + break; + case XSD_CONTENT_GROUP_REF: + model->u.group_ref = sdl_deserialize_string(in); + break; + case XSD_CONTENT_GROUP: + WSDL_CACHE_GET_INT(i, in); + model->u.group = types[i]; + break; + default: + break; + } + return model; +} + +static void sdl_deserialize_type(sdlTypePtr type, sdlTypePtr *types, encodePtr *encoders, char **in) +{ + int i; + sdlTypePtr *elements = NULL; + + WSDL_CACHE_GET_1(type->kind, sdlTypeKind, in); + type->name = sdl_deserialize_string(in); + type->namens = sdl_deserialize_string(in); + type->def = sdl_deserialize_string(in); + type->fixed = sdl_deserialize_string(in); + type->ref = sdl_deserialize_string(in); + WSDL_CACHE_GET_1(type->nillable, char, in); + WSDL_CACHE_GET_1(type->form, sdlForm, in); + + WSDL_CACHE_GET_INT(i, in); + type->encode = encoders[i]; + + if (**in == 1) { + WSDL_CACHE_SKIP(1, in); + type->restrictions = emalloc(sizeof(sdlRestrictions)); + /*memset(type->restrictions, 0, sizeof(sdlRestrictions));*/ + type->restrictions->minExclusive = sdl_deserialize_resriction_int(in); + type->restrictions->minInclusive = sdl_deserialize_resriction_int(in); + type->restrictions->maxExclusive = sdl_deserialize_resriction_int(in); + type->restrictions->maxInclusive = sdl_deserialize_resriction_int(in); + type->restrictions->totalDigits = sdl_deserialize_resriction_int(in); + type->restrictions->fractionDigits = sdl_deserialize_resriction_int(in); + type->restrictions->length = sdl_deserialize_resriction_int(in); + type->restrictions->minLength = sdl_deserialize_resriction_int(in); + type->restrictions->maxLength = sdl_deserialize_resriction_int(in); + type->restrictions->whiteSpace = sdl_deserialize_resriction_char(in); + type->restrictions->pattern = sdl_deserialize_resriction_char(in); + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + type->restrictions->enumeration = emalloc(sizeof(HashTable)); + zend_hash_init(type->restrictions->enumeration, i, NULL, delete_restriction_var_char, 0); + while (i > 0) { + sdlRestrictionCharPtr x = sdl_deserialize_resriction_char(in); + sdl_deserialize_key(type->restrictions->enumeration, x, in); + --i; + } + } else { + type->restrictions->enumeration = NULL; + } + } else { + WSDL_CACHE_SKIP(1, in); + } + + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + elements = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); + elements[0] = NULL; + type->elements = emalloc(sizeof(HashTable)); + zend_hash_init(type->elements, i, NULL, delete_type, 0); + while (i > 0) { + sdlTypePtr t = emalloc(sizeof(sdlType)); + memset(t, 0, sizeof(sdlType)); + sdl_deserialize_key(type->elements, t, in); + sdl_deserialize_type(t, types, encoders, in); + elements[i] = t; + --i; + } + } + + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + type->attributes = emalloc(sizeof(HashTable)); + zend_hash_init(type->attributes, i, NULL, delete_attribute, 0); + while (i > 0) { + sdlAttributePtr attr = emalloc(sizeof(sdlAttribute)); + memset(attr, 0, sizeof(sdlAttribute)); + sdl_deserialize_key(type->attributes, attr, in); + sdl_deserialize_attribute(attr, encoders, in); + --i; + } + } + + if (**in != 0) { + WSDL_CACHE_SKIP(1, in); + type->model = sdl_deserialize_model(types, elements, in); + } else { + WSDL_CACHE_SKIP(1, in); + } + if (elements != NULL) { + efree(elements); + } +} + +static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) +{ + int i; + + WSDL_CACHE_GET_INT(enc->details.type, in); + enc->details.type_str = sdl_deserialize_string(in); + enc->details.ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(i, in); + enc->details.sdl_type = types[i]; + enc->to_xml = sdl_guess_convert_xml; + enc->to_zval = sdl_guess_convert_zval; + + if (enc->details.sdl_type == NULL) { + int ns_len = strlen(enc->details.ns); + int type_len = strlen(enc->details.type_str); + + if (((ns_len == sizeof(SOAP_1_1_ENC_NAMESPACE)-1 && + memcmp(enc->details.ns, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)-1) == 0) || + (ns_len == sizeof(SOAP_1_2_ENC_NAMESPACE)-1 && + memcmp(enc->details.ns, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)-1) == 0))) { + char *enc_nscat; + int enc_ns_len; + int enc_len; + encodePtr real_enc; + + enc_ns_len = sizeof(XSD_NAMESPACE)-1; + enc_len = enc_ns_len + type_len + 1; + enc_nscat = emalloc(enc_len + 1); + memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); + enc_nscat[enc_ns_len] = ':'; + memcpy(enc_nscat+enc_ns_len+1, enc->details.type_str, type_len); + enc_nscat[enc_len] = '\0'; + + real_enc = get_encoder_ex(NULL, enc_nscat, enc_len); + efree(enc_nscat); + if (real_enc) { + enc->to_zval = real_enc->to_zval; + enc->to_xml = real_enc->to_xml; + } + } + } +} + +static void sdl_deserialize_soap_body(sdlSoapBindingFunctionBodyPtr body, encodePtr *encoders, sdlTypePtr *types, char **in) +{ + int i, j, n; + + WSDL_CACHE_GET_1(body->use, sdlEncodingUse, in); + if (body->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(body->encodingStyle, sdlRpcEncodingStyle, in); + } else { + body->encodingStyle = SOAP_ENCODING_DEFAULT; + } + body->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(i, in); + if (i > 0) { + body->headers = emalloc(sizeof(HashTable)); + zend_hash_init(body->headers, i, NULL, delete_header, 0); + while (i > 0) { + sdlSoapBindingFunctionHeaderPtr tmp = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(tmp, 0, sizeof(sdlSoapBindingFunctionHeader)); + sdl_deserialize_key(body->headers, tmp, in); + WSDL_CACHE_GET_1(tmp->use, sdlEncodingUse, in); + if (tmp->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(tmp->encodingStyle, sdlRpcEncodingStyle, in); + } else { + tmp->encodingStyle = SOAP_ENCODING_DEFAULT; + } + tmp->name = sdl_deserialize_string(in); + tmp->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(n, in); + tmp->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + tmp->element = types[n]; + --i; + WSDL_CACHE_GET_INT(j, in); + if (j > 0) { + tmp->headerfaults = emalloc(sizeof(HashTable)); + zend_hash_init(tmp->headerfaults, i, NULL, delete_header, 0); + while (j > 0) { + sdlSoapBindingFunctionHeaderPtr tmp2 = emalloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(tmp2, 0, sizeof(sdlSoapBindingFunctionHeader)); + sdl_deserialize_key(tmp->headerfaults, tmp2, in); + WSDL_CACHE_GET_1(tmp2->use, sdlEncodingUse, in); + if (tmp2->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(tmp2->encodingStyle, sdlRpcEncodingStyle, in); + } else { + tmp2->encodingStyle = SOAP_ENCODING_DEFAULT; + } + tmp2->name = sdl_deserialize_string(in); + tmp2->ns = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(n, in); + tmp2->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + tmp2->element = types[n]; + --j; + } + } + } + } +} + +static HashTable* sdl_deserialize_parameters(encodePtr *encoders, sdlTypePtr *types, char **in) +{ + int i, n; + HashTable *ht; + + WSDL_CACHE_GET_INT(i, in); + if (i == 0) {return NULL;} + ht = emalloc(sizeof(HashTable)); + zend_hash_init(ht, i, NULL, delete_parameter, 0); + while (i > 0) { + sdlParamPtr param = emalloc(sizeof(sdlParam)); + sdl_deserialize_key(ht, param, in); + param->paramName = sdl_deserialize_string(in); + WSDL_CACHE_GET_INT(param->order, in); + WSDL_CACHE_GET_INT(n, in); + param->encode = encoders[n]; + WSDL_CACHE_GET_INT(n, in); + param->element = types[n]; + --i; + } + return ht; +} + +static sdlPtr get_sdl_from_cache(const char *fn, const char *uri, time_t t, time_t *cached) +{ + sdlPtr sdl; + time_t old_t; + int i, num_groups, num_types, num_elements, num_encoders, num_bindings, num_func; + sdlFunctionPtr *functions = NULL; + sdlBindingPtr *bindings; + sdlTypePtr *types; + encodePtr *encoders; + const encode *enc; + + int f; + struct stat st; + char *in, *buf; + + f = open(fn, O_RDONLY|O_BINARY); + if (f < 0) { + return NULL; + } + if (fstat(f, &st) != 0) { + close(f); + return NULL; + } + buf = in = emalloc(st.st_size); + if (read(f, in, st.st_size) != st.st_size) { + close(f); + efree(in); + return NULL; + } + close(f); + + if (strncmp(in,"wsdl",4) != 0 || in[4] != WSDL_CACHE_VERSION || in[5] != '\0') { + unlink(fn); + efree(buf); + return NULL; + } + in += 6; + + WSDL_CACHE_GET(old_t, time_t, &in); + if (old_t < t) { + unlink(fn); + efree(buf); + return NULL; + } + *cached = old_t; + + WSDL_CACHE_GET_INT(i, &in); + if (i == 0 && strncmp(in, uri, i) != 0) { + unlink(fn); + efree(buf); + return NULL; + } + WSDL_CACHE_SKIP(i, &in); + + sdl = emalloc(sizeof(*sdl)); + memset(sdl, 0, sizeof(*sdl)); + + sdl->source = sdl_deserialize_string(&in); + sdl->target_ns = sdl_deserialize_string(&in); + + WSDL_CACHE_GET_INT(num_groups, &in); + WSDL_CACHE_GET_INT(num_types, &in); + WSDL_CACHE_GET_INT(num_elements, &in); + WSDL_CACHE_GET_INT(num_encoders, &in); + + i = num_groups+num_types+num_elements; + types = safe_emalloc((i+1), sizeof(sdlTypePtr), 0); + types[0] = NULL; + while (i > 0) { + types[i] = emalloc(sizeof(sdlType)); + memset(types[i], 0, sizeof(sdlType)); + i--; + } + + i = num_encoders; + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + i++; enc++; + } + encoders = safe_emalloc((i+1), sizeof(encodePtr), 0); + i = num_encoders; + encoders[0] = NULL; + while (i > 0) { + encoders[i] = emalloc(sizeof(encode)); + memset(encoders[i], 0, sizeof(encode)); + i--; + } + i = num_encoders; + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + encoders[++i] = (encodePtr)enc++; + } + + i = 1; + if (num_groups > 0) { + sdl->groups = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->groups, num_groups, NULL, delete_type, 0); + while (i < num_groups+1) { + sdl_deserialize_key(sdl->groups, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + if (num_types > 0) { + sdl->types = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->types, num_types, NULL, delete_type, 0); + while (i < num_groups+num_types+1) { + sdl_deserialize_key(sdl->types, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + if (num_elements > 0) { + sdl->elements = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->elements, num_elements, NULL, delete_type, 0); + while (i < num_groups+num_types+num_elements+1) { + sdl_deserialize_key(sdl->elements, types[i], &in); + sdl_deserialize_type(types[i], types, encoders, &in); + i++; + } + } + + i = 1; + if (num_encoders > 0) { + sdl->encoders = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->encoders, num_encoders, NULL, delete_encoder, 0); + while (i < num_encoders+1) { + sdl_deserialize_key(sdl->encoders, encoders[i], &in); + sdl_deserialize_encoder(encoders[i], types, &in); + i++; + } + } + + /* deserialize bindings */ + WSDL_CACHE_GET_INT(num_bindings, &in); + bindings = safe_emalloc(num_bindings, sizeof(sdlBindingPtr), 0); + if (num_bindings > 0) { + sdl->bindings = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->bindings, num_bindings, NULL, delete_binding, 0); + for (i = 0; i < num_bindings; i++) { + sdlBindingPtr binding = emalloc(sizeof(sdlBinding)); + memset(binding, 0, sizeof(sdlBinding)); + sdl_deserialize_key(sdl->bindings, binding, &in); + binding->name = sdl_deserialize_string(&in); + binding->location = sdl_deserialize_string(&in); + WSDL_CACHE_GET_1(binding->bindingType,sdlBindingType,&in); + if (binding->bindingType == BINDING_SOAP && *in != 0) { + sdlSoapBindingPtr soap_binding = binding->bindingAttributes = emalloc(sizeof(sdlSoapBinding)); + WSDL_CACHE_GET_1(soap_binding->style,sdlEncodingStyle,&in); + WSDL_CACHE_GET_1(soap_binding->transport,sdlTransport,&in); + } else { + WSDL_CACHE_SKIP(1,&in); + } + bindings[i] = binding; + } + } + + /* deserialize functions */ + WSDL_CACHE_GET_INT(num_func, &in); + zend_hash_init(&sdl->functions, num_func, NULL, delete_function, 0); + if (num_func > 0) { + functions = safe_emalloc(num_func, sizeof(sdlFunctionPtr), 0); + for (i = 0; i < num_func; i++) { + int binding_num, num_faults; + sdlFunctionPtr func = emalloc(sizeof(sdlFunction)); + sdl_deserialize_key(&sdl->functions, func, &in); + func->functionName = sdl_deserialize_string(&in); + func->requestName = sdl_deserialize_string(&in); + func->responseName = sdl_deserialize_string(&in); + + WSDL_CACHE_GET_INT(binding_num, &in); + if (binding_num == 0) { + func->binding = NULL; + } else { + func->binding = bindings[binding_num-1]; + } + if (func->binding && func->binding->bindingType == BINDING_SOAP && *in != 0) { + sdlSoapBindingFunctionPtr binding = func->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunction)); + memset(binding, 0, sizeof(sdlSoapBindingFunction)); + WSDL_CACHE_GET_1(binding->style,sdlEncodingStyle,&in); + binding->soapAction = sdl_deserialize_string(&in); + sdl_deserialize_soap_body(&binding->input, encoders, types, &in); + sdl_deserialize_soap_body(&binding->output, encoders, types, &in); + } else { + WSDL_CACHE_SKIP(1, &in); + func->bindingAttributes = NULL; + } + + func->requestParameters = sdl_deserialize_parameters(encoders, types, &in); + func->responseParameters = sdl_deserialize_parameters(encoders, types, &in); + + WSDL_CACHE_GET_INT(num_faults, &in); + if (num_faults > 0) { + int j; + + func->faults = emalloc(sizeof(HashTable)); + zend_hash_init(func->faults, num_faults, NULL, delete_fault, 0); + + for (j = 0; j < num_faults; j++) { + sdlFaultPtr fault = emalloc(sizeof(sdlFault)); + + sdl_deserialize_key(func->faults, fault, &in); + fault->name =sdl_deserialize_string(&in); + fault->details =sdl_deserialize_parameters(encoders, types, &in); + if (*in != 0) { + sdlSoapBindingFunctionFaultPtr binding = fault->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); + memset(binding, 0, sizeof(sdlSoapBindingFunctionFault)); + WSDL_CACHE_GET_1(binding->use,sdlEncodingUse,&in); + if (binding->use == SOAP_ENCODED) { + WSDL_CACHE_GET_1(binding->encodingStyle, sdlRpcEncodingStyle, &in); + } else { + binding->encodingStyle = SOAP_ENCODING_DEFAULT; + } + binding->ns = sdl_deserialize_string(&in); + } else { + WSDL_CACHE_SKIP(1, &in); + fault->bindingAttributes = NULL; + } + } + } else { + func->faults = NULL; + } + functions[i] = func; + } + } + + /* deserialize requests */ + WSDL_CACHE_GET_INT(i, &in); + if (i > 0) { + sdl->requests = emalloc(sizeof(HashTable)); + zend_hash_init(sdl->requests, i, NULL, NULL, 0); + while (i > 0) { + int function_num; + + WSDL_CACHE_GET_INT(function_num, &in); + sdl_deserialize_key(sdl->requests, functions[function_num-1], &in); + i--; + } + } + + if (functions) { + efree(functions); + } + efree(bindings); + efree(encoders); + efree(types); + efree(buf); + return sdl; +} + +static void sdl_serialize_string(const char *str, smart_str *out) +{ + if (str) { + int i = strlen(str); + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + WSDL_CACHE_PUT_N(str, i, out); + } + } else { + WSDL_CACHE_PUT_INT(WSDL_NO_STRING_MARKER, out); + } +} + +// TODO: refactor it +static void sdl_serialize_key(zend_string *key, smart_str *out) +{ + if (key) { + WSDL_CACHE_PUT_INT(ZSTR_LEN(key), out); + WSDL_CACHE_PUT_N(ZSTR_VAL(key), ZSTR_LEN(key), out); + } else { + WSDL_CACHE_PUT_INT(WSDL_NO_STRING_MARKER, out); + } +} + +static void sdl_serialize_encoder_ref(encodePtr enc, HashTable *tmp_encoders, smart_str *out) { + if (enc) { + zval *encoder_num; + if ((encoder_num = zend_hash_str_find(tmp_encoders, (char*)&enc, sizeof(enc))) != 0) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(encoder_num), out); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + } else { + WSDL_CACHE_PUT_INT(0, out); + } +} + +static void sdl_serialize_type_ref(sdlTypePtr type, HashTable *tmp_types, smart_str *out) { + if (type) { + zval *type_num; + if ((type_num = zend_hash_str_find(tmp_types, (char*)&type, sizeof(type))) != NULL) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(type_num), out); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + } else { + WSDL_CACHE_PUT_INT(0,out); + } +} + +static void sdl_serialize_attribute(sdlAttributePtr attr, HashTable *tmp_encoders, smart_str *out) +{ + int i; + + sdl_serialize_string(attr->name, out); + sdl_serialize_string(attr->namens, out); + sdl_serialize_string(attr->ref, out); + sdl_serialize_string(attr->def, out); + sdl_serialize_string(attr->fixed, out); + WSDL_CACHE_PUT_1(attr->form, out); + WSDL_CACHE_PUT_1(attr->use, out); + sdl_serialize_encoder_ref(attr->encode, tmp_encoders, out); + if (attr->extraAttributes) { + i = zend_hash_num_elements(attr->extraAttributes); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlExtraAttributePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(attr->extraAttributes, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->ns, out); + sdl_serialize_string(tmp->val, out); + } ZEND_HASH_FOREACH_END(); + } +} + +static void sdl_serialize_model(sdlContentModelPtr model, HashTable *tmp_types, HashTable *tmp_elements, smart_str *out) +{ + WSDL_CACHE_PUT_1(model->kind, out); + WSDL_CACHE_PUT_INT(model->min_occurs, out); + WSDL_CACHE_PUT_INT(model->max_occurs, out); + switch (model->kind) { + case XSD_CONTENT_ELEMENT: + sdl_serialize_type_ref(model->u.element, tmp_elements, out); + break; + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: { + sdlContentModelPtr tmp; + int i = zend_hash_num_elements(model->u.content); + + WSDL_CACHE_PUT_INT(i, out); + ZEND_HASH_FOREACH_PTR(model->u.content, tmp) { + sdl_serialize_model(tmp, tmp_types, tmp_elements, out); + } ZEND_HASH_FOREACH_END(); + } + break; + case XSD_CONTENT_GROUP_REF: + sdl_serialize_string(model->u.group_ref,out); + break; + case XSD_CONTENT_GROUP: + sdl_serialize_type_ref(model->u.group, tmp_types, out); + break; + default: + break; + } +} + +static void sdl_serialize_resriction_int(sdlRestrictionIntPtr x, smart_str *out) +{ + if (x) { + WSDL_CACHE_PUT_1(1, out); + WSDL_CACHE_PUT_INT(x->value, out); + WSDL_CACHE_PUT_1(x->fixed, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } +} + +static void sdl_serialize_resriction_char(sdlRestrictionCharPtr x, smart_str *out) +{ + if (x) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_string(x->value, out); + WSDL_CACHE_PUT_1(x->fixed, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } +} + +static void sdl_serialize_type(sdlTypePtr type, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i; + HashTable *tmp_elements = NULL; + + WSDL_CACHE_PUT_1(type->kind, out); + sdl_serialize_string(type->name, out); + sdl_serialize_string(type->namens, out); + sdl_serialize_string(type->def, out); + sdl_serialize_string(type->fixed, out); + sdl_serialize_string(type->ref, out); + WSDL_CACHE_PUT_1(type->nillable, out); + WSDL_CACHE_PUT_1(type->form, out); + sdl_serialize_encoder_ref(type->encode, tmp_encoders, out); + + if (type->restrictions) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_resriction_int(type->restrictions->minExclusive,out); + sdl_serialize_resriction_int(type->restrictions->minInclusive,out); + sdl_serialize_resriction_int(type->restrictions->maxExclusive,out); + sdl_serialize_resriction_int(type->restrictions->maxInclusive,out); + sdl_serialize_resriction_int(type->restrictions->totalDigits,out); + sdl_serialize_resriction_int(type->restrictions->fractionDigits,out); + sdl_serialize_resriction_int(type->restrictions->length,out); + sdl_serialize_resriction_int(type->restrictions->minLength,out); + sdl_serialize_resriction_int(type->restrictions->maxLength,out); + sdl_serialize_resriction_char(type->restrictions->whiteSpace,out); + sdl_serialize_resriction_char(type->restrictions->pattern,out); + if (type->restrictions->enumeration) { + i = zend_hash_num_elements(type->restrictions->enumeration); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlRestrictionCharPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->restrictions->enumeration, key, tmp) { + sdl_serialize_resriction_char(tmp, out); + sdl_serialize_key(key, out); + } ZEND_HASH_FOREACH_END(); + } + } else { + WSDL_CACHE_PUT_1(0, out); + } + if (type->elements) { + i = zend_hash_num_elements(type->elements); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zend_string *key; + zval zv; + + tmp_elements = emalloc(sizeof(HashTable)); + zend_hash_init(tmp_elements, i, NULL, NULL, 0); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->elements, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, tmp_encoders, tmp_types, out); + ZVAL_LONG(&zv, i); + zend_hash_str_add(tmp_elements, (char*)&tmp, sizeof(tmp), &zv); + i--; + } ZEND_HASH_FOREACH_END(); + } + + if (type->attributes) { + i = zend_hash_num_elements(type->attributes); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlAttributePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->attributes, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_attribute(tmp, tmp_encoders, out); + } ZEND_HASH_FOREACH_END(); + } + if (type->model) { + WSDL_CACHE_PUT_1(1, out); + sdl_serialize_model(type->model, tmp_types, tmp_elements, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } + if (tmp_elements != NULL) { + zend_hash_destroy(tmp_elements); + efree(tmp_elements); + } +} + +static void sdl_serialize_encoder(encodePtr enc, HashTable *tmp_types, smart_str *out) +{ + WSDL_CACHE_PUT_INT(enc->details.type, out); + sdl_serialize_string(enc->details.type_str, out); + sdl_serialize_string(enc->details.ns, out); + sdl_serialize_type_ref(enc->details.sdl_type, tmp_types, out); +} + +static void sdl_serialize_parameters(HashTable *ht, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i; + + if (ht) { + i = zend_hash_num_elements(ht); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlParamPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(ht, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->paramName, out); + WSDL_CACHE_PUT_INT(tmp->order, out); + sdl_serialize_encoder_ref(tmp->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp->element, tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } +} + +static void sdl_serialize_soap_body(sdlSoapBindingFunctionBodyPtr body, HashTable *tmp_encoders, HashTable *tmp_types, smart_str *out) +{ + int i, j; + + WSDL_CACHE_PUT_1(body->use, out); + if (body->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(body->encodingStyle, out); + } + sdl_serialize_string(body->ns, out); + if (body->headers) { + i = zend_hash_num_elements(body->headers); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlSoapBindingFunctionHeaderPtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(body->headers, key, tmp) { + sdl_serialize_key(key, out); + WSDL_CACHE_PUT_1(tmp->use, out); + if (tmp->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(tmp->encodingStyle, out); + } + sdl_serialize_string(tmp->name, out); + sdl_serialize_string(tmp->ns, out); + sdl_serialize_encoder_ref(tmp->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp->element, tmp_types, out); + if (tmp->headerfaults) { + j = zend_hash_num_elements(tmp->headerfaults); + } else { + j = 0; + } + WSDL_CACHE_PUT_INT(j, out); + if (j > 0) { + sdlSoapBindingFunctionHeaderPtr tmp2; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(body->headers, key, tmp2) { + sdl_serialize_key(key, out); + WSDL_CACHE_PUT_1(tmp2->use, out); + if (tmp2->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(tmp2->encodingStyle, out); + } + sdl_serialize_string(tmp2->name, out); + sdl_serialize_string(tmp2->ns, out); + sdl_serialize_encoder_ref(tmp2->encode, tmp_encoders, out); + sdl_serialize_type_ref(tmp2->element, tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void add_sdl_to_cache(const char *fn, const char *uri, time_t t, sdlPtr sdl) +{ + smart_str buf = {0}; + smart_str *out = &buf; + int i; + int type_num = 1; + int encoder_num = 1; + int f; + const encode *enc; + HashTable tmp_types; + HashTable tmp_encoders; + HashTable tmp_bindings; + HashTable tmp_functions; + +#ifdef ZEND_WIN32 + f = open(fn,O_CREAT|O_WRONLY|O_EXCL|O_BINARY,S_IREAD|S_IWRITE); +#else + f = open(fn,O_CREAT|O_WRONLY|O_EXCL|O_BINARY,S_IREAD|S_IWRITE); +#endif + if (f < 0) {return;} + + zend_hash_init(&tmp_types, 0, NULL, NULL, 0); + zend_hash_init(&tmp_encoders, 0, NULL, NULL, 0); + zend_hash_init(&tmp_bindings, 0, NULL, NULL, 0); + zend_hash_init(&tmp_functions, 0, NULL, NULL, 0); + + WSDL_CACHE_PUT_N("wsdl", 4, out); + WSDL_CACHE_PUT_1(WSDL_CACHE_VERSION,out); + WSDL_CACHE_PUT_1(0,out); + WSDL_CACHE_PUT_N(&t, sizeof(t), out); + + sdl_serialize_string(uri, out); + sdl_serialize_string(sdl->source, out); + sdl_serialize_string(sdl->target_ns, out); + + if (sdl->groups) { + i = zend_hash_num_elements(sdl->groups); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->groups, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + i = zend_hash_num_elements(sdl->types); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->types, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + i = zend_hash_num_elements(sdl->elements); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlTypePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->elements, tmp) { + ZVAL_LONG(&zv, type_num); + zend_hash_str_add(&tmp_types, (char*)&tmp, sizeof(tmp), &zv); + ++type_num; + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + i = zend_hash_num_elements(sdl->encoders); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + encodePtr tmp; + zval zv; + + ZEND_HASH_FOREACH_PTR(sdl->encoders, tmp) { + ZVAL_LONG(&zv, encoder_num); + zend_hash_str_add(&tmp_encoders, (char*)&tmp, sizeof(tmp), &zv); + ++encoder_num; + } ZEND_HASH_FOREACH_END(); + } + enc = defaultEncoding; + while (enc->details.type != END_KNOWN_TYPES) { + zval zv; + + ZVAL_LONG(&zv, encoder_num); + zend_hash_str_add(&tmp_encoders, (char*)&enc, sizeof(encodePtr), &zv); + enc++; + ++encoder_num; + } + + if (sdl->groups) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->groups, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->types, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + sdlTypePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->elements, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_type(tmp, &tmp_encoders, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + encodePtr tmp; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->encoders, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_encoder(tmp, &tmp_types, out); + } ZEND_HASH_FOREACH_END(); + } + + /* serialize bindings */ + if (sdl->bindings) { + i = zend_hash_num_elements(sdl->bindings); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlBindingPtr tmp; + int binding_num = 1; + zval zv; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->bindings, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->name, out); + sdl_serialize_string(tmp->location, out); + WSDL_CACHE_PUT_1(tmp->bindingType,out); + if (tmp->bindingType == BINDING_SOAP && tmp->bindingAttributes != NULL) { + sdlSoapBindingPtr binding = (sdlSoapBindingPtr)tmp->bindingAttributes; + WSDL_CACHE_PUT_1(binding->style, out); + WSDL_CACHE_PUT_1(binding->transport, out); + } else { + WSDL_CACHE_PUT_1(0,out); + } + + ZVAL_LONG(&zv, binding_num); + zend_hash_str_add(&tmp_bindings, (char*)&tmp, sizeof(tmp), &zv); + binding_num++; + } ZEND_HASH_FOREACH_END(); + } + + /* serialize functions */ + i = zend_hash_num_elements(&sdl->functions); + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlFunctionPtr tmp; + zval *binding_num, zv; + int function_num = 1; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&sdl->functions, key, tmp) { + sdl_serialize_key(key, out); + sdl_serialize_string(tmp->functionName, out); + sdl_serialize_string(tmp->requestName, out); + sdl_serialize_string(tmp->responseName, out); + + if (tmp->binding) { + binding_num = zend_hash_str_find(&tmp_bindings,(char*)&tmp->binding, sizeof(tmp->binding)); + if (binding_num) { + WSDL_CACHE_PUT_INT(Z_LVAL_P(binding_num), out); + if (Z_LVAL_P(binding_num) >= 0) { + if (tmp->binding->bindingType == BINDING_SOAP && tmp->bindingAttributes != NULL) { + sdlSoapBindingFunctionPtr binding = (sdlSoapBindingFunctionPtr)tmp->bindingAttributes; + WSDL_CACHE_PUT_1(binding->style, out); + sdl_serialize_string(binding->soapAction, out); + sdl_serialize_soap_body(&binding->input, &tmp_encoders, &tmp_types, out); + sdl_serialize_soap_body(&binding->output, &tmp_encoders, &tmp_types, out); + } else { + WSDL_CACHE_PUT_1(0,out); + } + } + } + } + sdl_serialize_parameters(tmp->requestParameters, &tmp_encoders, &tmp_types, out); + sdl_serialize_parameters(tmp->responseParameters, &tmp_encoders, &tmp_types, out); + + if (tmp->faults) { + sdlFaultPtr fault; + zend_string *key; + + WSDL_CACHE_PUT_INT(zend_hash_num_elements(tmp->faults), out); + + ZEND_HASH_FOREACH_STR_KEY_PTR(tmp->faults, key, fault) { + sdl_serialize_key(key, out); + sdl_serialize_string(fault->name, out); + sdl_serialize_parameters(fault->details, &tmp_encoders, &tmp_types, out); + if (tmp->binding->bindingType == BINDING_SOAP && fault->bindingAttributes != NULL) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + WSDL_CACHE_PUT_1(binding->use, out); + if (binding->use == SOAP_ENCODED) { + WSDL_CACHE_PUT_1(binding->encodingStyle, out); + } + sdl_serialize_string(binding->ns, out); + } else { + WSDL_CACHE_PUT_1(0, out); + } + } ZEND_HASH_FOREACH_END(); + } else { + WSDL_CACHE_PUT_INT(0, out); + } + + ZVAL_LONG(&zv, function_num); + zend_hash_str_add(&tmp_functions, (char*)&tmp, sizeof(tmp), &zv); + function_num++; + } ZEND_HASH_FOREACH_END(); + } + + /* serialize requests */ + if (sdl->requests) { + i = zend_hash_num_elements(sdl->requests); + } else { + i = 0; + } + WSDL_CACHE_PUT_INT(i, out); + if (i > 0) { + sdlFunctionPtr tmp; + zval *function_num; + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->requests, key, tmp) { + function_num = zend_hash_str_find(&tmp_functions, (char*)&tmp, sizeof(tmp)); + WSDL_CACHE_PUT_INT(Z_LVAL_P(function_num), out); + sdl_serialize_key(key, out); + } ZEND_HASH_FOREACH_END(); + } + + php_ignore_value(write(f, ZSTR_VAL(buf.s), ZSTR_LEN(buf.s))); + close(f); + smart_str_free(&buf); + zend_hash_destroy(&tmp_functions); + zend_hash_destroy(&tmp_bindings); + zend_hash_destroy(&tmp_encoders); + zend_hash_destroy(&tmp_types); +} + + +static void make_persistent_restriction_int(void *data) +{ + sdlRestrictionIntPtr *rest = (sdlRestrictionIntPtr *)data; + sdlRestrictionIntPtr prest = NULL; + + prest = malloc(sizeof(sdlRestrictionInt)); + *prest = **rest; + *rest = prest; +} + + +static void make_persistent_restriction_char_int(sdlRestrictionCharPtr *rest) +{ + sdlRestrictionCharPtr prest = NULL; + + prest = malloc(sizeof(sdlRestrictionChar)); + memset(prest, 0, sizeof(sdlRestrictionChar)); + prest->value = strdup((*rest)->value); + prest->fixed = (*rest)->fixed; + *rest = prest; +} + + +static void make_persistent_sdl_type_ref(sdlTypePtr *type, HashTable *ptr_map, HashTable *bp_types) +{ + sdlTypePtr tmp; + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char *)type, sizeof(sdlTypePtr))) != NULL) { + *type = tmp; + } else { + zend_hash_next_index_insert_ptr(bp_types, *type); + } +} + + +static void make_persistent_sdl_encoder_ref(encodePtr *enc, HashTable *ptr_map, HashTable *bp_encoders) +{ + encodePtr tmp; + + /* do not process defaultEncoding's here */ + if ((*enc) >= defaultEncoding && (*enc) < defaultEncoding + numDefaultEncodings) { + return; + } + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char *)enc, sizeof(encodePtr))) != NULL) { + *enc = tmp; + } else { + zend_hash_next_index_insert_ptr(bp_encoders, enc); + } +} + + +static HashTable* make_persistent_sdl_function_headers(HashTable *headers, HashTable *ptr_map) +{ + HashTable *pheaders; + sdlSoapBindingFunctionHeaderPtr tmp, pheader; + encodePtr penc; + sdlTypePtr ptype; + zend_string *key; + + pheaders = malloc(sizeof(HashTable)); + zend_hash_init(pheaders, zend_hash_num_elements(headers), NULL, delete_header_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(headers, key, tmp) { + pheader = malloc(sizeof(sdlSoapBindingFunctionHeader)); + memset(pheader, 0, sizeof(sdlSoapBindingFunctionHeader)); + *pheader = *tmp; + + if (pheader->name) { + pheader->name = strdup(pheader->name); + } + if (pheader->ns) { + pheader->ns = strdup(pheader->ns); + } + + if (pheader->encode && pheader->encode->details.sdl_type) { + if ((penc = zend_hash_str_find_ptr(ptr_map, (char*)&pheader->encode, sizeof(encodePtr))) == NULL) { + assert(0); + } + pheader->encode = penc; + } + if (pheader->element) { + if ((ptype = zend_hash_str_find_ptr(ptr_map, (char*)&pheader->element, sizeof(sdlTypePtr))) == NULL) { + assert(0); + } + pheader->element = ptype; + } + + if (pheader->headerfaults) { + pheader->headerfaults = make_persistent_sdl_function_headers(pheader->headerfaults, ptr_map); + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pheaders, ZSTR_VAL(key), ZSTR_LEN(key), pheader); + } else { + zend_hash_next_index_insert_ptr(pheaders, pheader); + } + } ZEND_HASH_FOREACH_END(); + + return pheaders; +} + + +static void make_persistent_sdl_soap_body(sdlSoapBindingFunctionBodyPtr body, HashTable *ptr_map) +{ + if (body->ns) { + body->ns = strdup(body->ns); + } + + if (body->headers) { + body->headers = make_persistent_sdl_function_headers(body->headers, ptr_map); + } +} + + +static HashTable* make_persistent_sdl_parameters(HashTable *params, HashTable *ptr_map) +{ + HashTable *pparams; + sdlParamPtr tmp, pparam; + sdlTypePtr ptype; + encodePtr penc; + zend_string *key; + + pparams = malloc(sizeof(HashTable)); + zend_hash_init(pparams, zend_hash_num_elements(params), NULL, delete_parameter_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(params, key, tmp) { + pparam = malloc(sizeof(sdlParam)); + memset(pparam, 0, sizeof(sdlParam)); + *pparam = *tmp; + + if (pparam->paramName) { + pparam->paramName = strdup(pparam->paramName); + } + + if (pparam->encode && pparam->encode->details.sdl_type) { + if ((penc = zend_hash_str_find_ptr(ptr_map, (char*)&pparam->encode, sizeof(encodePtr))) == NULL) { + assert(0); + } + pparam->encode = penc; + } + if (pparam->element) { + if ((ptype = zend_hash_str_find_ptr(ptr_map, (char*)&pparam->element, sizeof(sdlTypePtr))) == NULL) { + assert(0); + } + pparam->element = ptype; + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pparams, ZSTR_VAL(key), ZSTR_LEN(key), pparam); + } else { + zend_hash_next_index_insert_ptr(pparams, pparam); + } + } ZEND_HASH_FOREACH_END(); + + return pparams; +} + +static HashTable* make_persistent_sdl_function_faults(sdlFunctionPtr func, HashTable *faults, HashTable *ptr_map) +{ + HashTable *pfaults; + sdlFaultPtr tmp, pfault; + zend_string *key; + + pfaults = malloc(sizeof(HashTable)); + zend_hash_init(pfaults, zend_hash_num_elements(faults), NULL, delete_fault_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(faults, key, tmp) { + pfault = malloc(sizeof(sdlFault)); + memset(pfault, 0, sizeof(sdlFault)); + *pfault = *tmp; + + if (pfault->name) { + pfault->name = strdup(pfault->name); + } + if (pfault->details) { + pfault->details = make_persistent_sdl_parameters(pfault->details, ptr_map); + } + + if (func->binding->bindingType == BINDING_SOAP && pfault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBindingFunctionFault)); + memset(soap_binding, 0, sizeof(sdlSoapBindingFunctionFault)); + *soap_binding = *(sdlSoapBindingFunctionFaultPtr)pfault->bindingAttributes; + if (soap_binding->ns) { + soap_binding->ns = strdup(soap_binding->ns); + } + pfault->bindingAttributes = soap_binding; + } + + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pfaults, ZSTR_VAL(key), ZSTR_LEN(key), pfault); + } else { + zend_hash_next_index_insert_ptr(pfaults, pfault); + } + + } ZEND_HASH_FOREACH_END(); + + return pfaults; +} + + +static sdlAttributePtr make_persistent_sdl_attribute(sdlAttributePtr attr, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + sdlAttributePtr pattr; + zend_string *key; + + pattr = malloc(sizeof(sdlAttribute)); + memset(pattr, 0, sizeof(sdlAttribute)); + + *pattr = *attr; + + if (pattr->name) { + pattr->name = strdup(pattr->name); + } + if (pattr->namens) { + pattr->namens = strdup(pattr->namens); + } + if (pattr->ref) { + pattr->ref = strdup(pattr->ref); + } + if (pattr->def) { + pattr->def = strdup(pattr->def); + } + if (pattr->fixed) { + pattr->fixed = strdup(pattr->fixed); + } + + /* we do not want to process defaultEncoding's here */ + if (pattr->encode) { + make_persistent_sdl_encoder_ref(&pattr->encode, ptr_map, bp_encoders); + } + + if (pattr->extraAttributes) { + sdlExtraAttributePtr tmp, pextra; + + pattr->extraAttributes = malloc(sizeof(HashTable)); + zend_hash_init(pattr->extraAttributes, zend_hash_num_elements(attr->extraAttributes), NULL, delete_extra_attribute_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(attr->extraAttributes, key, tmp) { + if (key) { + pextra = malloc(sizeof(sdlExtraAttribute)); + memset(pextra, 0, sizeof(sdlExtraAttribute)); + + if (tmp->ns) { + pextra->ns = strdup(tmp->ns); + } + if (tmp->val) { + pextra->val = strdup(tmp->val); + } + + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(pattr->extraAttributes, ZSTR_VAL(key), ZSTR_LEN(key), pextra); + } + } ZEND_HASH_FOREACH_END(); + } + + return pattr; +} + + +static sdlContentModelPtr make_persistent_sdl_model(sdlContentModelPtr model, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + sdlContentModelPtr pmodel; + sdlContentModelPtr tmp, pcontent; + + pmodel = malloc(sizeof(sdlContentModel)); + memset(pmodel, 0, sizeof(sdlContentModel)); + *pmodel = *model; + + switch (pmodel->kind) { + case XSD_CONTENT_ELEMENT: + if (pmodel->u.element) { + make_persistent_sdl_type_ref(&pmodel->u.element, ptr_map, bp_types); + } + break; + + case XSD_CONTENT_SEQUENCE: + case XSD_CONTENT_ALL: + case XSD_CONTENT_CHOICE: + pmodel->u.content = malloc(sizeof(HashTable)); + zend_hash_init(pmodel->u.content, zend_hash_num_elements(model->u.content), NULL, delete_model_persistent, 1); + + ZEND_HASH_FOREACH_PTR(model->u.content, tmp) { + pcontent = make_persistent_sdl_model(tmp, ptr_map, bp_types, bp_encoders); + zend_hash_next_index_insert_ptr(pmodel->u.content, pcontent); + } ZEND_HASH_FOREACH_END(); + break; + + case XSD_CONTENT_GROUP_REF: + if (pmodel->u.group_ref) { + pmodel->u.group_ref = strdup(pmodel->u.group_ref); + } + break; + + case XSD_CONTENT_GROUP: + if (pmodel->u.group) { + make_persistent_sdl_type_ref(&pmodel->u.group, ptr_map, bp_types); + } + break; + + default: + break; + } + + return pmodel; +} + + +static sdlTypePtr make_persistent_sdl_type(sdlTypePtr type, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + zend_string *key; + sdlTypePtr ptype = NULL; + + ptype = malloc(sizeof(sdlType)); + memset(ptype, 0, sizeof(sdlType)); + + *ptype = *type; + + if (ptype->name) { + ptype->name = strdup(ptype->name); + } + if (ptype->namens) { + ptype->namens = strdup(ptype->namens); + } + if (ptype->def) { + ptype->def = strdup(ptype->def); + } + if (ptype->fixed) { + ptype->fixed = strdup(ptype->fixed); + } + if (ptype->ref) { + ptype->ref = strdup(ptype->ref); + } + + /* we do not want to process defaultEncoding's here */ + if (ptype->encode) { + make_persistent_sdl_encoder_ref(&ptype->encode, ptr_map, bp_encoders); + } + + if (ptype->restrictions) { + ptype->restrictions = malloc(sizeof(sdlRestrictions)); + memset(ptype->restrictions, 0, sizeof(sdlRestrictions)); + *ptype->restrictions = *type->restrictions; + + if (ptype->restrictions->minExclusive) { + make_persistent_restriction_int(&ptype->restrictions->minExclusive); + } + if (ptype->restrictions->maxExclusive) { + make_persistent_restriction_int(&ptype->restrictions->maxExclusive); + } + if (ptype->restrictions->minInclusive) { + make_persistent_restriction_int(&ptype->restrictions->minInclusive); + } + if (ptype->restrictions->maxInclusive) { + make_persistent_restriction_int(&ptype->restrictions->maxInclusive); + } + if (ptype->restrictions->totalDigits) { + make_persistent_restriction_int(&ptype->restrictions->totalDigits); + } + if (ptype->restrictions->fractionDigits) { + make_persistent_restriction_int(&ptype->restrictions->fractionDigits); + } + if (ptype->restrictions->length) { + make_persistent_restriction_int(&ptype->restrictions->length); + } + if (ptype->restrictions->minLength) { + make_persistent_restriction_int(&ptype->restrictions->minLength); + } + if (ptype->restrictions->maxLength) { + make_persistent_restriction_int(&ptype->restrictions->maxLength); + } + if (ptype->restrictions->whiteSpace) { + make_persistent_restriction_char_int(&ptype->restrictions->whiteSpace); + } + if (ptype->restrictions->pattern) { + make_persistent_restriction_char_int(&ptype->restrictions->pattern); + } + + if (type->restrictions->enumeration) { + sdlRestrictionCharPtr tmp, penum; + ptype->restrictions->enumeration = malloc(sizeof(HashTable)); + zend_hash_init(ptype->restrictions->enumeration, zend_hash_num_elements(type->restrictions->enumeration), NULL, delete_restriction_var_char_persistent, 1); + ZEND_HASH_FOREACH_STR_KEY_PTR(type->restrictions->enumeration, key, tmp) { + penum = tmp; + make_persistent_restriction_char_int(&penum); + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(ptype->restrictions->enumeration, ZSTR_VAL(key), ZSTR_LEN(key), penum); + } ZEND_HASH_FOREACH_END(); + } + } + + if (ptype->elements) { + sdlTypePtr tmp, pelem; + + ptype->elements = malloc(sizeof(HashTable)); + zend_hash_init(ptype->elements, zend_hash_num_elements(type->elements), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->elements, key, tmp) { + pelem = make_persistent_sdl_type(tmp, ptr_map, bp_types, bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(ptype->elements, ZSTR_VAL(key), ZSTR_LEN(key), pelem); + } else { + zend_hash_next_index_insert_ptr(ptype->elements, pelem); + } + zend_hash_str_add_ptr(ptr_map, (char*)&tmp, sizeof(tmp), pelem); + } ZEND_HASH_FOREACH_END(); + } + + if (ptype->attributes) { + sdlAttributePtr tmp, pattr; + + ptype->attributes = malloc(sizeof(HashTable)); + zend_hash_init(ptype->attributes, zend_hash_num_elements(type->attributes), NULL, delete_attribute_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(type->attributes, key, tmp) { + pattr = make_persistent_sdl_attribute(tmp, ptr_map, bp_types, bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(ptype->attributes, ZSTR_VAL(key), ZSTR_LEN(key), pattr); + } else { + zend_hash_next_index_insert_ptr(ptype->attributes, pattr); + } + } ZEND_HASH_FOREACH_END(); + } + + if (type->model) { + ptype->model = make_persistent_sdl_model(ptype->model, ptr_map, bp_types, bp_encoders); + } + + return ptype; +} + +static encodePtr make_persistent_sdl_encoder(encodePtr enc, HashTable *ptr_map, HashTable *bp_types, HashTable *bp_encoders) +{ + encodePtr penc = NULL; + + penc = malloc(sizeof(encode)); + memset(penc, 0, sizeof(encode)); + + *penc = *enc; + + if (penc->details.type_str) { + penc->details.type_str = strdup(penc->details.type_str); + } + if (penc->details.ns) { + penc->details.ns = strdup(penc->details.ns); + } + + if (penc->details.sdl_type) { + make_persistent_sdl_type_ref(&penc->details.sdl_type, ptr_map, bp_types); + } + + return penc; +} + +static sdlBindingPtr make_persistent_sdl_binding(sdlBindingPtr bind, HashTable *ptr_map) +{ + sdlBindingPtr pbind = NULL; + + pbind = malloc(sizeof(sdlBinding)); + memset(pbind, 0, sizeof(sdlBinding)); + + *pbind = *bind; + + if (pbind->name) { + pbind->name = strdup(pbind->name); + } + if (pbind->location) { + pbind->location = strdup(pbind->location); + } + + if (pbind->bindingType == BINDING_SOAP && pbind->bindingAttributes) { + sdlSoapBindingPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBinding)); + memset(soap_binding, 0, sizeof(sdlSoapBinding)); + *soap_binding = *(sdlSoapBindingPtr)pbind->bindingAttributes; + pbind->bindingAttributes = soap_binding; + } + + return pbind; +} + +static sdlFunctionPtr make_persistent_sdl_function(sdlFunctionPtr func, HashTable *ptr_map) +{ + sdlFunctionPtr pfunc = NULL; + + pfunc = malloc(sizeof(sdlFunction)); + memset(pfunc, 0, sizeof(sdlFunction)); + + *pfunc = *func; + + if (pfunc->functionName) { + pfunc->functionName = strdup(pfunc->functionName); + } + if (pfunc->requestName) { + pfunc->requestName = strdup(pfunc->requestName); + } + if (pfunc->responseName) { + pfunc->responseName = strdup(pfunc->responseName); + } + + if (pfunc->binding) { + sdlBindingPtr tmp; + + if ((tmp = zend_hash_str_find_ptr(ptr_map, (char*)&pfunc->binding, sizeof(pfunc->binding))) == NULL) { + assert(0); + } + pfunc->binding = tmp; + + if (pfunc->binding->bindingType == BINDING_SOAP && pfunc->bindingAttributes) { + sdlSoapBindingFunctionPtr soap_binding; + + soap_binding = malloc(sizeof(sdlSoapBindingFunction)); + memset(soap_binding, 0, sizeof(sdlSoapBindingFunction)); + *soap_binding = *(sdlSoapBindingFunctionPtr)pfunc->bindingAttributes; + if (soap_binding->soapAction) { + soap_binding->soapAction = strdup(soap_binding->soapAction); + } + make_persistent_sdl_soap_body(&soap_binding->input, ptr_map); + make_persistent_sdl_soap_body(&soap_binding->output, ptr_map); + pfunc->bindingAttributes = soap_binding; + } + + if (pfunc->requestParameters) { + pfunc->requestParameters = make_persistent_sdl_parameters(pfunc->requestParameters, ptr_map); + } + if (pfunc->responseParameters) { + pfunc->responseParameters = make_persistent_sdl_parameters(pfunc->responseParameters, ptr_map); + } + if (pfunc->faults) { + pfunc->faults = make_persistent_sdl_function_faults(pfunc, pfunc->faults, ptr_map); + } + } + + return pfunc; +} + +static sdlPtr make_persistent_sdl(sdlPtr sdl) +{ + sdlPtr psdl = NULL; + HashTable ptr_map; + HashTable bp_types, bp_encoders; + zend_string *key; + + zend_hash_init(&bp_types, 0, NULL, NULL, 0); + zend_hash_init(&bp_encoders, 0, NULL, NULL, 0); + zend_hash_init(&ptr_map, 0, NULL, NULL, 0); + + psdl = malloc(sizeof(*sdl)); + memset(psdl, 0, sizeof(*sdl)); + + if (sdl->source) { + psdl->source = strdup(sdl->source); + } + if (sdl->target_ns) { + psdl->target_ns = strdup(sdl->target_ns); + } + + if (sdl->groups) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->groups = malloc(sizeof(HashTable)); + zend_hash_init(psdl->groups, zend_hash_num_elements(sdl->groups), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->groups, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->groups, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->groups, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->types) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->types = malloc(sizeof(HashTable)); + zend_hash_init(psdl->types, zend_hash_num_elements(sdl->types), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->types, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->types, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->types, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->elements) { + sdlTypePtr tmp; + sdlTypePtr ptype; + + psdl->elements = malloc(sizeof(HashTable)); + zend_hash_init(psdl->elements, zend_hash_num_elements(sdl->elements), NULL, delete_type_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->elements, key, tmp) { + ptype = make_persistent_sdl_type(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->elements, ZSTR_VAL(key), ZSTR_LEN(key), ptype); + } else { + zend_hash_next_index_insert_ptr(psdl->elements, ptype); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), ptype); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->encoders) { + encodePtr tmp; + encodePtr penc; + + psdl->encoders = malloc(sizeof(HashTable)); + zend_hash_init(psdl->encoders, zend_hash_num_elements(sdl->encoders), NULL, delete_encoder_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->encoders, key, tmp) { + penc = make_persistent_sdl_encoder(tmp, &ptr_map, &bp_types, &bp_encoders); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->encoders, ZSTR_VAL(key), ZSTR_LEN(key), penc); + } else { + zend_hash_next_index_insert_ptr(psdl->encoders, penc); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), penc); + } ZEND_HASH_FOREACH_END(); + } + + /* do backpatching here */ + if (zend_hash_num_elements(&bp_types)) { + sdlTypePtr *tmp, ptype = NULL; + + ZEND_HASH_FOREACH_PTR(&bp_types, tmp) { + if ((ptype = zend_hash_str_find_ptr(&ptr_map, (char*)tmp, sizeof(*tmp))) == NULL) { + assert(0); + } + *tmp = ptype; + } ZEND_HASH_FOREACH_END(); + } + if (zend_hash_num_elements(&bp_encoders)) { + encodePtr *tmp, penc = NULL; + + ZEND_HASH_FOREACH_PTR(&bp_encoders, tmp) { + if ((penc = zend_hash_str_find_ptr(&ptr_map, (char*)tmp, sizeof(*tmp))) == NULL) { + assert(0); + } + *tmp = penc; + } ZEND_HASH_FOREACH_END(); + } + + + if (sdl->bindings) { + sdlBindingPtr tmp; + sdlBindingPtr pbind; + + psdl->bindings = malloc(sizeof(HashTable)); + zend_hash_init(psdl->bindings, zend_hash_num_elements(sdl->bindings), NULL, delete_binding_persistent, 1); + + ZEND_HASH_FOREACH_STR_KEY_PTR(sdl->bindings, key, tmp) { + pbind = make_persistent_sdl_binding(tmp, &ptr_map); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->bindings, ZSTR_VAL(key), ZSTR_LEN(key), pbind); + } else { + zend_hash_next_index_insert_ptr(psdl->bindings, pbind); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), pbind); + } ZEND_HASH_FOREACH_END(); + } + + zend_hash_init(&psdl->functions, zend_hash_num_elements(&sdl->functions), NULL, delete_function_persistent, 1); + if (zend_hash_num_elements(&sdl->functions)) { + sdlFunctionPtr tmp; + sdlFunctionPtr pfunc; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&sdl->functions, key, tmp) { + pfunc = make_persistent_sdl_function(tmp, &ptr_map); + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(&psdl->functions, ZSTR_VAL(key), ZSTR_LEN(key), pfunc); + } else { + zend_hash_next_index_insert_ptr(&psdl->functions, pfunc); + } + zend_hash_str_add_ptr(&ptr_map, (char*)&tmp, sizeof(tmp), pfunc); + } ZEND_HASH_FOREACH_END(); + } + + if (sdl->requests) { + zval *zv; + sdlFunctionPtr tmp; + sdlFunctionPtr preq; + + psdl->requests = malloc(sizeof(HashTable)); + zend_hash_init(psdl->requests, zend_hash_num_elements(sdl->requests), NULL, NULL, 1); + + ZEND_HASH_FOREACH_STR_KEY_VAL(sdl->requests, key, zv) { + tmp = Z_PTR_P(zv); + if ((preq = zend_hash_str_find_ptr(&ptr_map, (char*)&tmp, sizeof(tmp))) == NULL) { + assert(0); + } + Z_PTR_P(zv) = preq; + if (key) { + /* We have to duplicate key emalloc->malloc */ + zend_hash_str_add_ptr(psdl->requests, ZSTR_VAL(key), ZSTR_LEN(key), preq); + } + } ZEND_HASH_FOREACH_END(); + } + + zend_hash_destroy(&ptr_map); + zend_hash_destroy(&bp_encoders); + zend_hash_destroy(&bp_types); + + return psdl; +} + +typedef struct _sdl_cache_bucket { + sdlPtr sdl; + time_t time; +} sdl_cache_bucket; + +static void delete_psdl_int(sdl_cache_bucket *p) +{ + sdlPtr tmp = p->sdl; + + zend_hash_destroy(&tmp->functions); + if (tmp->source) { + free(tmp->source); + } + if (tmp->target_ns) { + free(tmp->target_ns); + } + if (tmp->elements) { + zend_hash_destroy(tmp->elements); + free(tmp->elements); + } + if (tmp->encoders) { + zend_hash_destroy(tmp->encoders); + free(tmp->encoders); + } + if (tmp->types) { + zend_hash_destroy(tmp->types); + free(tmp->types); + } + if (tmp->groups) { + zend_hash_destroy(tmp->groups); + free(tmp->groups); + } + if (tmp->bindings) { + zend_hash_destroy(tmp->bindings); + free(tmp->bindings); + } + if (tmp->requests) { + zend_hash_destroy(tmp->requests); + free(tmp->requests); + } + free(tmp); +} + +static void delete_psdl(zval *zv) +{ + delete_psdl_int(Z_PTR_P(zv)); + free(Z_PTR_P(zv)); +} + +sdlPtr get_sdl(zval *this_ptr, char *uri, zend_long cache_wsdl) +{ + char fn[MAXPATHLEN]; + sdlPtr sdl = NULL; + char* old_error_code = SOAP_GLOBAL(error_code); + size_t uri_len = 0; + php_stream_context *context=NULL; + zval *tmp, *proxy_host, *proxy_port, orig_context, new_context; + smart_str headers = {0}; + char* key = NULL; + time_t t = time(0); + zend_bool has_proxy_authorization = 0; + zend_bool has_authorization = 0; + + ZVAL_UNDEF(&orig_context); + ZVAL_UNDEF(&new_context); + if (strchr(uri,':') != NULL || IS_ABSOLUTE_PATH(uri, uri_len)) { + uri_len = strlen(uri); + } else if (VCWD_REALPATH(uri, fn) == NULL) { + cache_wsdl = WSDL_CACHE_NONE; + } else { + uri = fn; + uri_len = strlen(uri); + } + + if ((cache_wsdl & WSDL_CACHE_MEMORY) && SOAP_GLOBAL(mem_cache)) { + sdl_cache_bucket *p; + + if (NULL != (p = zend_hash_str_find_ptr(SOAP_GLOBAL(mem_cache), uri, uri_len))) { + if (p->time < t - SOAP_GLOBAL(cache_ttl)) { + /* in-memory cache entry is expired */ + zend_hash_str_del(&EG(persistent_list), uri, uri_len); + } else { + return p->sdl; + } + } + } + + if ((cache_wsdl & WSDL_CACHE_DISK) && (uri_len < MAXPATHLEN)) { + time_t t = time(0); + char md5str[33]; + PHP_MD5_CTX context; + unsigned char digest[16]; + int len = strlen(SOAP_GLOBAL(cache_dir)); + time_t cached; + char *user = php_get_current_user(); + int user_len = user ? strlen(user) + 1 : 0; + + md5str[0] = '\0'; + PHP_MD5Init(&context); + PHP_MD5Update(&context, (unsigned char*)uri, uri_len); + PHP_MD5Final(digest, &context); + make_digest(md5str, digest); + key = emalloc(len+sizeof("/wsdl-")-1+user_len+2+sizeof(md5str)); + memcpy(key,SOAP_GLOBAL(cache_dir),len); + memcpy(key+len,"/wsdl-",sizeof("/wsdl-")-1); + len += sizeof("/wsdl-")-1; + if (user_len) { + memcpy(key+len, user, user_len-1); + len += user_len-1; + key[len++] = '-'; + } + if (WSDL_CACHE_VERSION <= 0x9f) { + key[len++] = (WSDL_CACHE_VERSION >> 8) + '0'; + } else { + key[len++] = (WSDL_CACHE_VERSION >> 8) - 10 + 'a'; + } + if ((WSDL_CACHE_VERSION & 0xf) <= 0x9) { + key[len++] = (WSDL_CACHE_VERSION & 0xf) + '0'; + } else { + key[len++] = (WSDL_CACHE_VERSION & 0xf) - 10 + 'a'; + } + memcpy(key+len,md5str,sizeof(md5str)); + + if ((sdl = get_sdl_from_cache(key, uri, t-SOAP_GLOBAL(cache_ttl), &cached)) != NULL) { + t = cached; + efree(key); + goto cache_in_memory; + } + } + + if (NULL != (tmp = zend_hash_str_find(Z_OBJPROP_P(this_ptr), + "_stream_context", sizeof("_stream_context")-1))) { + context = php_stream_context_from_zval(tmp, 0); + } else { + context = php_stream_context_alloc(); + } + + if ((tmp = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_user_agent", sizeof("_user_agent")-1)) != NULL && + Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp) > 0) { + smart_str_appends(&headers, "User-Agent: "); + smart_str_appends(&headers, Z_STRVAL_P(tmp)); + smart_str_appends(&headers, "\r\n"); + } + + if ((proxy_host = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_proxy_host", sizeof("_proxy_host")-1)) != NULL && + Z_TYPE_P(proxy_host) == IS_STRING && + (proxy_port = zend_hash_str_find(Z_OBJPROP_P(this_ptr), "_proxy_port", sizeof("_proxy_port")-1)) != NULL && + Z_TYPE_P(proxy_port) == IS_LONG) { + zval str_proxy; + smart_str proxy = {0}; + smart_str_appends(&proxy,"tcp://"); + smart_str_appends(&proxy,Z_STRVAL_P(proxy_host)); + smart_str_appends(&proxy,":"); + smart_str_append_long(&proxy,Z_LVAL_P(proxy_port)); + smart_str_0(&proxy); + ZVAL_NEW_STR(&str_proxy, proxy.s); + + if (!context) { + context = php_stream_context_alloc(); + } + php_stream_context_set_option(context, "http", "proxy", &str_proxy); + zval_ptr_dtor(&str_proxy); + + if (uri_len < sizeof("https://")-1 || + strncasecmp(uri, "https://", sizeof("https://")-1) != 0) { + ZVAL_TRUE(&str_proxy); + php_stream_context_set_option(context, "http", "request_fulluri", &str_proxy); + } + + has_proxy_authorization = proxy_authentication(this_ptr, &headers); + } + + has_authorization = basic_authentication(this_ptr, &headers); + + /* Use HTTP/1.1 with "Connection: close" by default */ + if ((tmp = php_stream_context_get_option(context, "http", "protocol_version")) == NULL) { + zval http_version; + + ZVAL_DOUBLE(&http_version, 1.1); + php_stream_context_set_option(context, "http", "protocol_version", &http_version); + smart_str_appendl(&headers, "Connection: close\r\n", sizeof("Connection: close\r\n")-1); + } + + if (headers.s && ZSTR_LEN(headers.s) > 0) { + zval str_headers; + + if (!context) { + context = php_stream_context_alloc(); + } else { + http_context_headers(context, has_authorization, has_proxy_authorization, 0, &headers); + } + + smart_str_0(&headers); + ZVAL_NEW_STR(&str_headers, headers.s); + php_stream_context_set_option(context, "http", "header", &str_headers); + zval_ptr_dtor(&str_headers); + } + + if (context) { + php_stream_context_to_zval(context, &new_context); + php_libxml_switch_context(&new_context, &orig_context); + } + + SOAP_GLOBAL(error_code) = "WSDL"; + + sdl = load_wsdl(this_ptr, uri); + if (sdl) { + sdl->is_persistent = 0; + } + + SOAP_GLOBAL(error_code) = old_error_code; + + if (context) { + php_libxml_switch_context(&orig_context, NULL); + zval_ptr_dtor(&new_context); + } + + if ((cache_wsdl & WSDL_CACHE_DISK) && key) { + if (sdl) { + add_sdl_to_cache(key, uri, t, sdl); + } + efree(key); + } + +cache_in_memory: + if (cache_wsdl & WSDL_CACHE_MEMORY) { + if (sdl) { + sdlPtr psdl; + sdl_cache_bucket p; + + if (SOAP_GLOBAL(mem_cache) == NULL) { + SOAP_GLOBAL(mem_cache) = malloc(sizeof(HashTable)); + zend_hash_init(SOAP_GLOBAL(mem_cache), 0, NULL, delete_psdl, 1); + } else if (SOAP_GLOBAL(cache_limit) > 0 && + SOAP_GLOBAL(cache_limit) <= (zend_long)zend_hash_num_elements(SOAP_GLOBAL(mem_cache))) { + /* in-memory cache overflow */ + sdl_cache_bucket *q; + time_t latest = t; + zend_string *latest_key = NULL, *key; + + ZEND_HASH_FOREACH_STR_KEY_PTR(SOAP_GLOBAL(mem_cache), key, q) { + if (q->time < latest) { + latest = q->time; + latest_key = key; + } + } ZEND_HASH_FOREACH_END(); + if (latest_key) { + zend_hash_del(SOAP_GLOBAL(mem_cache), latest_key); + } else { + return sdl; + } + } + + psdl = make_persistent_sdl(sdl); + psdl->is_persistent = 1; + p.time = t; + p.sdl = psdl; + + zend_hash_str_update_mem(SOAP_GLOBAL(mem_cache), uri, + uri_len, &p, sizeof(sdl_cache_bucket)); + /* remove non-persitent sdl structure */ + delete_sdl_impl(sdl); + /* and replace it with persistent one */ + sdl = psdl; + } + } + + return sdl; +} + +/* Deletes */ +void delete_sdl_impl(void *handle) +{ + sdlPtr tmp = (sdlPtr)handle; + + zend_hash_destroy(&tmp->functions); + if (tmp->source) { + efree(tmp->source); + } + if (tmp->target_ns) { + efree(tmp->target_ns); + } + if (tmp->elements) { + zend_hash_destroy(tmp->elements); + efree(tmp->elements); + } + if (tmp->encoders) { + zend_hash_destroy(tmp->encoders); + efree(tmp->encoders); + } + if (tmp->types) { + zend_hash_destroy(tmp->types); + efree(tmp->types); + } + if (tmp->groups) { + zend_hash_destroy(tmp->groups); + efree(tmp->groups); + } + if (tmp->bindings) { + zend_hash_destroy(tmp->bindings); + efree(tmp->bindings); + } + if (tmp->requests) { + zend_hash_destroy(tmp->requests); + efree(tmp->requests); + } + efree(tmp); +} + +void delete_sdl(void *handle) +{ + sdlPtr tmp = (sdlPtr)handle; + + if (!tmp->is_persistent) { + delete_sdl_impl(tmp); + } +} + +static void delete_binding(zval *zv) +{ + sdlBindingPtr binding = Z_PTR_P(zv); + + if (binding->location) { + efree(binding->location); + } + if (binding->name) { + efree(binding->name); + } + + if (binding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBind = binding->bindingAttributes; + if (soapBind) { + efree(soapBind); + } + } + efree(binding); +} + +static void delete_binding_persistent(zval *zv) +{ + sdlBindingPtr binding = Z_PTR_P(zv); + + if (binding->location) { + free(binding->location); + } + if (binding->name) { + free(binding->name); + } + + if (binding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBind = binding->bindingAttributes; + if (soapBind) { + free(soapBind); + } + } + free(binding); +} + +static void delete_sdl_soap_binding_function_body(sdlSoapBindingFunctionBody body) +{ + if (body.ns) { + efree(body.ns); + } + if (body.headers) { + zend_hash_destroy(body.headers); + efree(body.headers); + } +} + +static void delete_sdl_soap_binding_function_body_persistent(sdlSoapBindingFunctionBody body) +{ + if (body.ns) { + free(body.ns); + } + if (body.headers) { + zend_hash_destroy(body.headers); + free(body.headers); + } +} + +static void delete_function(zval *zv) +{ + sdlFunctionPtr function = Z_PTR_P(zv); + + if (function->functionName) { + efree(function->functionName); + } + if (function->requestName) { + efree(function->requestName); + } + if (function->responseName) { + efree(function->responseName); + } + if (function->requestParameters) { + zend_hash_destroy(function->requestParameters); + efree(function->requestParameters); + } + if (function->responseParameters) { + zend_hash_destroy(function->responseParameters); + efree(function->responseParameters); + } + if (function->faults) { + zend_hash_destroy(function->faults); + efree(function->faults); + } + + if (function->bindingAttributes && + function->binding && function->binding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunction = function->bindingAttributes; + if (soapFunction->soapAction) { + efree(soapFunction->soapAction); + } + delete_sdl_soap_binding_function_body(soapFunction->input); + delete_sdl_soap_binding_function_body(soapFunction->output); + efree(soapFunction); + } + efree(function); +} + +static void delete_function_persistent(zval *zv) +{ + sdlFunctionPtr function = Z_PTR_P(zv); + + if (function->functionName) { + free(function->functionName); + } + if (function->requestName) { + free(function->requestName); + } + if (function->responseName) { + free(function->responseName); + } + if (function->requestParameters) { + zend_hash_destroy(function->requestParameters); + free(function->requestParameters); + } + if (function->responseParameters) { + zend_hash_destroy(function->responseParameters); + free(function->responseParameters); + } + if (function->faults) { + zend_hash_destroy(function->faults); + free(function->faults); + } + + if (function->bindingAttributes && + function->binding && function->binding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunction = function->bindingAttributes; + if (soapFunction->soapAction) { + free(soapFunction->soapAction); + } + delete_sdl_soap_binding_function_body_persistent(soapFunction->input); + delete_sdl_soap_binding_function_body_persistent(soapFunction->output); + free(soapFunction); + } + free(function); +} + +static void delete_parameter(zval *zv) +{ + sdlParamPtr param = Z_PTR_P(zv); + if (param->paramName) { + efree(param->paramName); + } + efree(param); +} + +static void delete_parameter_persistent(zval *zv) +{ + sdlParamPtr param = Z_PTR_P(zv); + if (param->paramName) { + free(param->paramName); + } + free(param); +} + +static void delete_header_int(sdlSoapBindingFunctionHeaderPtr hdr) +{ + if (hdr->name) { + efree(hdr->name); + } + if (hdr->ns) { + efree(hdr->ns); + } + if (hdr->headerfaults) { + zend_hash_destroy(hdr->headerfaults); + efree(hdr->headerfaults); + } + efree(hdr); +} + +static void delete_header(zval *zv) +{ + delete_header_int(Z_PTR_P(zv)); +} + +static void delete_header_persistent(zval *zv) +{ + sdlSoapBindingFunctionHeaderPtr hdr = Z_PTR_P(zv); + if (hdr->name) { + free(hdr->name); + } + if (hdr->ns) { + free(hdr->ns); + } + if (hdr->headerfaults) { + zend_hash_destroy(hdr->headerfaults); + free(hdr->headerfaults); + } + free(hdr); +} + +static void delete_fault(zval *zv) +{ + sdlFaultPtr fault = Z_PTR_P(zv); + if (fault->name) { + efree(fault->name); + } + if (fault->details) { + zend_hash_destroy(fault->details); + efree(fault->details); + } + if (fault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + + if (binding->ns) { + efree(binding->ns); + } + efree(fault->bindingAttributes); + } + efree(fault); +} + +static void delete_fault_persistent(zval *zv) +{ + sdlFaultPtr fault = Z_PTR_P(zv); + if (fault->name) { + free(fault->name); + } + if (fault->details) { + zend_hash_destroy(fault->details); + free(fault->details); + } + if (fault->bindingAttributes) { + sdlSoapBindingFunctionFaultPtr binding = (sdlSoapBindingFunctionFaultPtr)fault->bindingAttributes; + + if (binding->ns) { + free(binding->ns); + } + free(fault->bindingAttributes); + } + free(fault); +} + +static void delete_document(zval *zv) +{ + xmlDocPtr doc = Z_PTR_P(zv); + xmlFreeDoc(doc); +} diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index b60603017957b..18a266179b7d2 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -199,7 +199,7 @@ xmlNsPtr node_find_ns(xmlNodePtr node) int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = attr_find_ns(node); if (nsPtr) { @@ -215,7 +215,7 @@ int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = node_find_ns(node); if (nsPtr) { diff --git a/ext/soap/php_xml.c.orig b/ext/soap/php_xml.c.orig new file mode 100644 index 0000000000000..b60603017957b --- /dev/null +++ b/ext/soap/php_xml.c.orig @@ -0,0 +1,325 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Brad Lafountain <rodif_bl@yahoo.com> | + | Shane Caraveo <shane@caraveo.com> | + | Dmitry Stogov <dmitry@php.net> | + +----------------------------------------------------------------------+ +*/ + +#include "php_soap.h" +#include "ext/libxml/php_libxml.h" +#include "libxml/parser.h" +#include "libxml/parserInternals.h" + +/* Channel libxml file io layer through the PHP streams subsystem. + * This allows use of ftps:// and https:// urls */ + +static int is_blank(const xmlChar* str) +{ + while (*str != '\0') { + if (*str != ' ' && *str != 0x9 && *str != 0xa && *str != 0xd) { + return 0; + } + str++; + } + return 1; +} + +/* removes all empty text, comments and other insignoficant nodes */ +static void cleanup_xml_node(xmlNodePtr node) +{ + xmlNodePtr trav; + xmlNodePtr del = NULL; + + trav = node->children; + while (trav != NULL) { + if (del != NULL) { + xmlUnlinkNode(del); + xmlFreeNode(del); + del = NULL; + } + if (trav->type == XML_TEXT_NODE) { + if (is_blank(trav->content)) { + del = trav; + } + } else if ((trav->type != XML_ELEMENT_NODE) && + (trav->type != XML_CDATA_SECTION_NODE)) { + del = trav; + } else if (trav->children != NULL) { + cleanup_xml_node(trav); + } + trav = trav->next; + } + if (del != NULL) { + xmlUnlinkNode(del); + xmlFreeNode(del); + } +} + +static void soap_ignorableWhitespace(void *ctx, const xmlChar *ch, int len) +{ +} + +static void soap_Comment(void *ctx, const xmlChar *value) +{ +} + +xmlDocPtr soap_xmlParseFile(const char *filename) +{ + xmlParserCtxtPtr ctxt = NULL; + xmlDocPtr ret; + zend_bool old_allow_url_fopen; + +/* + xmlInitParser(); +*/ + + old_allow_url_fopen = PG(allow_url_fopen); + PG(allow_url_fopen) = 1; + ctxt = xmlCreateFileParserCtxt(filename); + PG(allow_url_fopen) = old_allow_url_fopen; + if (ctxt) { + zend_bool old; + + ctxt->keepBlanks = 0; + ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; + ctxt->sax->comment = soap_Comment; + ctxt->sax->warning = NULL; + ctxt->sax->error = NULL; + /*ctxt->sax->fatalError = NULL;*/ + ctxt->options |= XML_PARSE_HUGE; + old = php_libxml_disable_entity_loader(1); + xmlParseDocument(ctxt); + php_libxml_disable_entity_loader(old); + if (ctxt->wellFormed) { + ret = ctxt->myDoc; + if (ret->URL == NULL && ctxt->directory != NULL) { + ret->URL = xmlCharStrdup(ctxt->directory); + } + } else { + ret = NULL; + xmlFreeDoc(ctxt->myDoc); + ctxt->myDoc = NULL; + } + xmlFreeParserCtxt(ctxt); + } else { + ret = NULL; + } + +/* + xmlCleanupParser(); +*/ + + if (ret) { + cleanup_xml_node((xmlNodePtr)ret); + } + return ret; +} + +xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size) +{ + xmlParserCtxtPtr ctxt = NULL; + xmlDocPtr ret; + + +/* + xmlInitParser(); +*/ + ctxt = xmlCreateMemoryParserCtxt(buf, buf_size); + if (ctxt) { + zend_bool old; + + ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; + ctxt->sax->comment = soap_Comment; + ctxt->sax->warning = NULL; + ctxt->sax->error = NULL; + /*ctxt->sax->fatalError = NULL;*/ + ctxt->options |= XML_PARSE_HUGE; + old = php_libxml_disable_entity_loader(1); + xmlParseDocument(ctxt); + php_libxml_disable_entity_loader(old); + if (ctxt->wellFormed) { + ret = ctxt->myDoc; + if (ret->URL == NULL && ctxt->directory != NULL) { + ret->URL = xmlCharStrdup(ctxt->directory); + } + } else { + ret = NULL; + xmlFreeDoc(ctxt->myDoc); + ctxt->myDoc = NULL; + } + xmlFreeParserCtxt(ctxt); + } else { + ret = NULL; + } + +/* + xmlCleanupParser(); +*/ + +/* + if (ret) { + cleanup_xml_node((xmlNodePtr)ret); + } +*/ + return ret; +} + +xmlNsPtr attr_find_ns(xmlAttrPtr node) +{ + if (node->ns) { + return node->ns; + } else if (node->parent->ns) { + return node->parent->ns; + } else { + return xmlSearchNs(node->doc, node->parent, NULL); + } +} + +xmlNsPtr node_find_ns(xmlNodePtr node) +{ + if (node->ns) { + return node->ns; + } else { + return xmlSearchNs(node->doc, node, NULL); + } +} + +int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) +{ + if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (ns) { + xmlNsPtr nsPtr = attr_find_ns(node); + if (nsPtr) { + return (strcmp((char*)nsPtr->href, ns) == 0); + } else { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + +int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) +{ + if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (ns) { + xmlNsPtr nsPtr = node_find_ns(node); + if (nsPtr) { + return (strcmp((char*)nsPtr->href, ns) == 0); + } else { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + + +xmlAttrPtr get_attribute_ex(xmlAttrPtr node, char *name, char *ns) +{ + while (node!=NULL) { + if (attr_is_equal_ex(node, name, ns)) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_ex(xmlNodePtr node, char *name, char *ns) +{ + while (node!=NULL) { + if (node_is_equal_ex(node, name, ns)) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_recurisve_ex(xmlNodePtr node, char *name, char *ns) +{ + while (node != NULL) { + if (node_is_equal_ex(node, name, ns)) { + return node; + } else if (node->children != NULL) { + xmlNodePtr tmp = get_node_recurisve_ex(node->children, name, ns); + if (tmp) { + return tmp; + } + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_with_attribute_ex(xmlNodePtr node, char *name, char *name_ns, char *attribute, char *value, char *attr_ns) +{ + xmlAttrPtr attr; + + while (node != NULL) { + if (name != NULL) { + node = get_node_ex(node, name, name_ns); + if (node==NULL) { + return NULL; + } + } + + attr = get_attribute_ex(node->properties, attribute, attr_ns); + if (attr != NULL && strcmp((char*)attr->children->content, value) == 0) { + return node; + } + node = node->next; + } + return NULL; +} + +xmlNodePtr get_node_with_attribute_recursive_ex(xmlNodePtr node, char *name, char *name_ns, char *attribute, char *value, char *attr_ns) +{ + while (node != NULL) { + if (node_is_equal_ex(node, name, name_ns)) { + xmlAttrPtr attr = get_attribute_ex(node->properties, attribute, attr_ns); + if (attr != NULL && strcmp((char*)attr->children->content, value) == 0) { + return node; + } + } + if (node->children != NULL) { + xmlNodePtr tmp = get_node_with_attribute_recursive_ex(node->children, name, name_ns, attribute, value, attr_ns); + if (tmp) { + return tmp; + } + } + node = node->next; + } + return NULL; +} + +int parse_namespace(const xmlChar *inval, char **value, char **namespace) +{ + char *found = strrchr((char*)inval, ':'); + + if (found != NULL && found != (char*)inval) { + (*namespace) = estrndup((char*)inval, found - (char*)inval); + (*value) = estrdup(++found); + } else { + (*value) = estrdup((char*)inval); + (*namespace) = NULL; + } + + return FALSE; +} diff --git a/ext/soap/tests/bug80672.phpt b/ext/soap/tests/bug80672.phpt new file mode 100644 index 0000000000000..71e2b1d84105e --- /dev/null +++ b/ext/soap/tests/bug80672.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #80672 Null Dereference in SoapClient +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +try { + $client = new SoapClient(__DIR__ . "/bug80672.xml"); + $query = $soap->query(array('sXML' => 'something')); +} catch(SoapFault $e) { + print $e->getMessage(); +} +?> +--EXPECTF-- +SOAP-ERROR: Parsing WSDL: Unexpected WSDL element <> \ No newline at end of file diff --git a/ext/soap/tests/bug80672.xml b/ext/soap/tests/bug80672.xml new file mode 100644 index 0000000000000..0fa185bf1ebba --- /dev/null +++ b/ext/soap/tests/bug80672.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<soap:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/"> +<![CDATA[test]]> +</soap:definitions> From 655dfa202f3db3dbe3ea2c744e712aec13277b45 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:12 +0000 Subject: [PATCH 04/11] commit patch 17480184 --- ext/pgsql/pgsql.c | 6 +- ext/pgsql/pgsql.c.orig | 7204 +++++++++++++++++++++++++++++++++ ext/pgsql/tests/bug81720.phpt | 27 + 3 files changed, 7234 insertions(+), 3 deletions(-) create mode 100644 ext/pgsql/pgsql.c.orig create mode 100644 ext/pgsql/tests/bug81720.phpt diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 9f51256ac5fb8..84f1e86e87694 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -1993,7 +1993,7 @@ PHP_FUNCTION(pg_query_params) if (Z_TYPE(tmp_val) != IS_STRING) { php_error_docref(NULL, E_WARNING,"Error converting parameter"); zval_ptr_dtor(&tmp_val); - _php_pgsql_free_params(params, num_params); + _php_pgsql_free_params(params, i); RETURN_FALSE; } params[i] = estrndup(Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); @@ -5174,8 +5174,8 @@ PHP_FUNCTION(pg_send_execute) params[i] = NULL; } else { zend_string *tmp_str = zval_try_get_string(tmp); - if (UNEXPECTED(!tmp)) { - _php_pgsql_free_params(params, num_params); + if (UNEXPECTED(!tmp_str)) { + _php_pgsql_free_params(params, i); return; } params[i] = estrndup(ZSTR_VAL(tmp_str), ZSTR_LEN(tmp_str)); diff --git a/ext/pgsql/pgsql.c.orig b/ext/pgsql/pgsql.c.orig new file mode 100644 index 0000000000000..9f51256ac5fb8 --- /dev/null +++ b/ext/pgsql/pgsql.c.orig @@ -0,0 +1,7204 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski <zeev@php.net> | + | Jouni Ahto <jouni.ahto@exdec.fi> | + | Yasuo Ohgaki <yohgaki@php.net> | + | Youichi Iwakiri <yiwakiri@st.rim.or.jp> (pg_copy_*) | + | Chris Kings-Lynne <chriskl@php.net> (v3 protocol) | + +----------------------------------------------------------------------+ + */ + +#include <stdlib.h> + +#define PHP_PGSQL_PRIVATE 1 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define SMART_STR_PREALLOC 512 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" +#include "zend_smart_str.h" +#include "ext/pcre/php_pcre.h" +#ifdef PHP_WIN32 +# include "win32/time.h" +#endif +#include "php_pgsql.h" +#include "php_globals.h" +#include "zend_exceptions.h" + +#if HAVE_PGSQL + +#ifndef InvalidOid +#define InvalidOid ((Oid) 0) +#endif + +#define PGSQL_ASSOC 1<<0 +#define PGSQL_NUM 1<<1 +#define PGSQL_BOTH (PGSQL_ASSOC|PGSQL_NUM) + +#define PGSQL_NOTICE_LAST 1 /* Get the last notice */ +#define PGSQL_NOTICE_ALL 2 /* Get all notices */ +#define PGSQL_NOTICE_CLEAR 3 /* Remove notices */ + +#define PGSQL_STATUS_LONG 1 +#define PGSQL_STATUS_STRING 2 + +#define PGSQL_MAX_LENGTH_OF_LONG 30 +#define PGSQL_MAX_LENGTH_OF_DOUBLE 60 + +#if ZEND_LONG_MAX < UINT_MAX +#define PGSQL_RETURN_OID(oid) do { \ + if (oid > ZEND_LONG_MAX) { \ + smart_str s = {0}; \ + smart_str_append_unsigned(&s, oid); \ + smart_str_0(&s); \ + RETURN_NEW_STR(s.s); \ + } \ + RETURN_LONG((zend_long)oid); \ +} while(0) +#else +#define PGSQL_RETURN_OID(oid) RETURN_LONG((zend_long)oid) +#endif + +#if HAVE_PQSETNONBLOCKING +#define PQ_SETNONBLOCKING(pg_link, flag) PQsetnonblocking(pg_link, flag) +#else +#define PQ_SETNONBLOCKING(pg_link, flag) 0 +#endif + +#define CHECK_DEFAULT_LINK(x) if ((x) == NULL) { php_error_docref(NULL, E_WARNING, "No PostgreSQL link opened yet"); RETURN_FALSE; } +#define FETCH_DEFAULT_LINK() PGG(default_link) + +#ifndef HAVE_PQFREEMEM +#define PQfreemem free +#endif + +ZEND_DECLARE_MODULE_GLOBALS(pgsql) +static PHP_GINIT_FUNCTION(pgsql); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connect, 0, 0, 1) + ZEND_ARG_INFO(0, connection_string) + ZEND_ARG_INFO(0, connect_type) + ZEND_ARG_INFO(0, host) + ZEND_ARG_INFO(0, port) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, tty) + ZEND_ARG_INFO(0, database) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_pconnect, 0, 0, 1) + ZEND_ARG_INFO(0, connection_string) + ZEND_ARG_INFO(0, host) + ZEND_ARG_INFO(0, port) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, tty) + ZEND_ARG_INFO(0, database) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connect_poll, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +#if HAVE_PQPARAMETERSTATUS +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_parameter_status, 0, 0, 1) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, param_name) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_close, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_dbname, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_last_error, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_options, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_port, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_tty, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_host, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_version, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_ping, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_query, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO() + +#if HAVE_PQEXECPARAMS +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_query_params, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, query) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQPREPARE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_prepare, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, stmtname) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQEXECPREPARED +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_execute, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, stmtname) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_num_rows, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_num_fields, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() + +#if HAVE_PQCMDTUPLES +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_affected_rows, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_last_notice, 0, 0, 1) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, option) +ZEND_END_ARG_INFO() + +#ifdef HAVE_PQFTABLE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_table, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_number) + ZEND_ARG_INFO(0, oid_only) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_name, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_size, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_type, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_type_oid, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_num, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, field_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_result, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row_number) + ZEND_ARG_INFO(0, field_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_row, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) + ZEND_ARG_INFO(0, result_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_assoc, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_array, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) + ZEND_ARG_INFO(0, result_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_object, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) + ZEND_ARG_INFO(0, class_name) + ZEND_ARG_INFO(0, l) + ZEND_ARG_INFO(0, ctor_params) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_all, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, result_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_all_columns, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, column_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_result_seek, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_prtlen, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) + ZEND_ARG_INFO(0, field_name_or_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_is_null, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, row) + ZEND_ARG_INFO(0, field_name_or_number) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_free_result, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_last_oid, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_trace, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_untrace, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_create, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, large_object_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_unlink, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, large_object_oid) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_open, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, large_object_oid) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_close, 0, 0, 1) + ZEND_ARG_INFO(0, large_object) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_read, 0, 0, 1) + ZEND_ARG_INFO(0, large_object) + ZEND_ARG_INFO(0, len) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_write, 0, 0, 2) + ZEND_ARG_INFO(0, large_object) + ZEND_ARG_INFO(0, buf) + ZEND_ARG_INFO(0, len) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_read_all, 0, 0, 1) + ZEND_ARG_INFO(0, large_object) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_import, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, large_object_oid) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_export, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, objoid) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_seek, 0, 0, 2) + ZEND_ARG_INFO(0, large_object) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, whence) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_tell, 0, 0, 1) + ZEND_ARG_INFO(0, large_object) +ZEND_END_ARG_INFO() + +#if HAVE_PG_LO_TRUNCATE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_lo_truncate, 0, 0, 1) + ZEND_ARG_INFO(0, large_object) + ZEND_ARG_INFO(0, size) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQSETERRORVERBOSITY +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_set_error_verbosity, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, verbosity) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQCLIENTENCODING +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_set_client_encoding, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, encoding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_client_encoding, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_end_copy, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_put_line, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_copy_to, 0, 0, 2) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, table_name) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, null_as) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_copy_from, 0, 0, 3) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, table_name) + ZEND_ARG_INFO(0, rows) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, null_as) +ZEND_END_ARG_INFO() + +#if HAVE_PQESCAPE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_escape_string, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_escape_bytea, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_unescape_bytea, 0, 0, 1) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQESCAPE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_escape_literal, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_escape_identifier, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_result_error, 0, 0, 1) + ZEND_ARG_INFO(0, result) +ZEND_END_ARG_INFO() + +#if HAVE_PQRESULTERRORFIELD +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_result_error_field, 0, 0, 2) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, fieldcode) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connection_status, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +#if HAVE_PGTRANSACTIONSTATUS +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_transaction_status, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connection_reset, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_cancel_query, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connection_busy, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_query, 0, 0, 2) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO() + +#if HAVE_PQSENDQUERYPARAMS +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_query_params, 0, 0, 3) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, query) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQSENDPREPARE +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_prepare, 0, 0, 3) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, stmtname) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO() +#endif + +#if HAVE_PQSENDQUERYPREPARED +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_execute, 0, 0, 3) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, stmtname) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_get_result, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_result_status, 0, 0, 1) + ZEND_ARG_INFO(0, result) + ZEND_ARG_INFO(0, result_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_get_notify, 0, 0, 0) + ZEND_ARG_INFO(0, connection) + ZEND_ARG_INFO(0, e) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_get_pid, 0, 0, 0) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_socket, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_consume_input, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_flush, 0, 0, 1) + ZEND_ARG_INFO(0, connection) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_meta_data, 0, 0, 2) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_convert, 0, 0, 3) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) + ZEND_ARG_INFO(0, values) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_insert, 0, 0, 3) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) + ZEND_ARG_INFO(0, values) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_update, 0, 0, 4) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, ids) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_delete, 0, 0, 3) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) + ZEND_ARG_INFO(0, ids) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_select, 0, 0, 3) + ZEND_ARG_INFO(0, db) + ZEND_ARG_INFO(0, table) + ZEND_ARG_INFO(0, ids) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, result_type) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ pgsql_functions[] + */ +static const zend_function_entry pgsql_functions[] = { + /* connection functions */ + PHP_FE(pg_connect, arginfo_pg_connect) + PHP_FE(pg_pconnect, arginfo_pg_pconnect) + PHP_FE(pg_connect_poll, arginfo_pg_connect_poll) + PHP_FE(pg_close, arginfo_pg_close) + PHP_FE(pg_connection_status, arginfo_pg_connection_status) + PHP_FE(pg_connection_busy, arginfo_pg_connection_busy) + PHP_FE(pg_connection_reset, arginfo_pg_connection_reset) + PHP_FE(pg_host, arginfo_pg_host) + PHP_FE(pg_dbname, arginfo_pg_dbname) + PHP_FE(pg_port, arginfo_pg_port) + PHP_FE(pg_tty, arginfo_pg_tty) + PHP_FE(pg_options, arginfo_pg_options) + PHP_FE(pg_version, arginfo_pg_version) + PHP_FE(pg_ping, arginfo_pg_ping) +#if HAVE_PQPARAMETERSTATUS + PHP_FE(pg_parameter_status, arginfo_pg_parameter_status) +#endif +#if HAVE_PGTRANSACTIONSTATUS + PHP_FE(pg_transaction_status, arginfo_pg_transaction_status) +#endif + /* query functions */ + PHP_FE(pg_query, arginfo_pg_query) +#if HAVE_PQEXECPARAMS + PHP_FE(pg_query_params, arginfo_pg_query_params) +#endif +#if HAVE_PQPREPARE + PHP_FE(pg_prepare, arginfo_pg_prepare) +#endif +#if HAVE_PQEXECPREPARED + PHP_FE(pg_execute, arginfo_pg_execute) +#endif + PHP_FE(pg_send_query, arginfo_pg_send_query) +#if HAVE_PQSENDQUERYPARAMS + PHP_FE(pg_send_query_params, arginfo_pg_send_query_params) +#endif +#if HAVE_PQSENDPREPARE + PHP_FE(pg_send_prepare, arginfo_pg_send_prepare) +#endif +#if HAVE_PQSENDQUERYPREPARED + PHP_FE(pg_send_execute, arginfo_pg_send_execute) +#endif + PHP_FE(pg_cancel_query, arginfo_pg_cancel_query) + /* result functions */ + PHP_FE(pg_fetch_result, arginfo_pg_fetch_result) + PHP_FE(pg_fetch_row, arginfo_pg_fetch_row) + PHP_FE(pg_fetch_assoc, arginfo_pg_fetch_assoc) + PHP_FE(pg_fetch_array, arginfo_pg_fetch_array) + PHP_FE(pg_fetch_object, arginfo_pg_fetch_object) + PHP_FE(pg_fetch_all, arginfo_pg_fetch_all) + PHP_FE(pg_fetch_all_columns, arginfo_pg_fetch_all_columns) +#if HAVE_PQCMDTUPLES + PHP_FE(pg_affected_rows,arginfo_pg_affected_rows) +#endif + PHP_FE(pg_get_result, arginfo_pg_get_result) + PHP_FE(pg_result_seek, arginfo_pg_result_seek) + PHP_FE(pg_result_status,arginfo_pg_result_status) + PHP_FE(pg_free_result, arginfo_pg_free_result) + PHP_FE(pg_last_oid, arginfo_pg_last_oid) + PHP_FE(pg_num_rows, arginfo_pg_num_rows) + PHP_FE(pg_num_fields, arginfo_pg_num_fields) + PHP_FE(pg_field_name, arginfo_pg_field_name) + PHP_FE(pg_field_num, arginfo_pg_field_num) + PHP_FE(pg_field_size, arginfo_pg_field_size) + PHP_FE(pg_field_type, arginfo_pg_field_type) + PHP_FE(pg_field_type_oid, arginfo_pg_field_type_oid) + PHP_FE(pg_field_prtlen, arginfo_pg_field_prtlen) + PHP_FE(pg_field_is_null,arginfo_pg_field_is_null) +#ifdef HAVE_PQFTABLE + PHP_FE(pg_field_table, arginfo_pg_field_table) +#endif + /* async message function */ + PHP_FE(pg_get_notify, arginfo_pg_get_notify) + PHP_FE(pg_socket, arginfo_pg_socket) + PHP_FE(pg_consume_input,arginfo_pg_consume_input) + PHP_FE(pg_flush, arginfo_pg_flush) + PHP_FE(pg_get_pid, arginfo_pg_get_pid) + /* error message functions */ + PHP_FE(pg_result_error, arginfo_pg_result_error) +#if HAVE_PQRESULTERRORFIELD + PHP_FE(pg_result_error_field, arginfo_pg_result_error_field) +#endif + PHP_FE(pg_last_error, arginfo_pg_last_error) + PHP_FE(pg_last_notice, arginfo_pg_last_notice) + /* copy functions */ + PHP_FE(pg_put_line, arginfo_pg_put_line) + PHP_FE(pg_end_copy, arginfo_pg_end_copy) + PHP_FE(pg_copy_to, arginfo_pg_copy_to) + PHP_FE(pg_copy_from, arginfo_pg_copy_from) + /* debug functions */ + PHP_FE(pg_trace, arginfo_pg_trace) + PHP_FE(pg_untrace, arginfo_pg_untrace) + /* large object functions */ + PHP_FE(pg_lo_create, arginfo_pg_lo_create) + PHP_FE(pg_lo_unlink, arginfo_pg_lo_unlink) + PHP_FE(pg_lo_open, arginfo_pg_lo_open) + PHP_FE(pg_lo_close, arginfo_pg_lo_close) + PHP_FE(pg_lo_read, arginfo_pg_lo_read) + PHP_FE(pg_lo_write, arginfo_pg_lo_write) + PHP_FE(pg_lo_read_all, arginfo_pg_lo_read_all) + PHP_FE(pg_lo_import, arginfo_pg_lo_import) + PHP_FE(pg_lo_export, arginfo_pg_lo_export) + PHP_FE(pg_lo_seek, arginfo_pg_lo_seek) + PHP_FE(pg_lo_tell, arginfo_pg_lo_tell) +#if HAVE_PG_LO_TRUNCATE + PHP_FE(pg_lo_truncate, arginfo_pg_lo_truncate) +#endif + /* utility functions */ +#if HAVE_PQESCAPE + PHP_FE(pg_escape_string, arginfo_pg_escape_string) + PHP_FE(pg_escape_bytea, arginfo_pg_escape_bytea) + PHP_FE(pg_unescape_bytea, arginfo_pg_unescape_bytea) + PHP_FE(pg_escape_literal, arginfo_pg_escape_literal) + PHP_FE(pg_escape_identifier, arginfo_pg_escape_identifier) +#endif +#if HAVE_PQSETERRORVERBOSITY + PHP_FE(pg_set_error_verbosity, arginfo_pg_set_error_verbosity) +#endif +#if HAVE_PQCLIENTENCODING + PHP_FE(pg_client_encoding, arginfo_pg_client_encoding) + PHP_FE(pg_set_client_encoding, arginfo_pg_set_client_encoding) +#endif + /* misc function */ + PHP_FE(pg_meta_data, arginfo_pg_meta_data) + PHP_FE(pg_convert, arginfo_pg_convert) + PHP_FE(pg_insert, arginfo_pg_insert) + PHP_FE(pg_update, arginfo_pg_update) + PHP_FE(pg_delete, arginfo_pg_delete) + PHP_FE(pg_select, arginfo_pg_select) + /* aliases for downwards compatibility */ + PHP_FALIAS(pg_exec, pg_query, arginfo_pg_query) + PHP_FALIAS(pg_getlastoid, pg_last_oid, arginfo_pg_last_oid) +#if HAVE_PQCMDTUPLES + PHP_FALIAS(pg_cmdtuples, pg_affected_rows, arginfo_pg_affected_rows) +#endif + PHP_FALIAS(pg_errormessage, pg_last_error, arginfo_pg_last_error) + PHP_FALIAS(pg_numrows, pg_num_rows, arginfo_pg_num_rows) + PHP_FALIAS(pg_numfields, pg_num_fields, arginfo_pg_num_fields) + PHP_FALIAS(pg_fieldname, pg_field_name, arginfo_pg_field_name) + PHP_FALIAS(pg_fieldsize, pg_field_size, arginfo_pg_field_size) + PHP_FALIAS(pg_fieldtype, pg_field_type, arginfo_pg_field_type) + PHP_FALIAS(pg_fieldnum, pg_field_num, arginfo_pg_field_num) + PHP_FALIAS(pg_fieldprtlen, pg_field_prtlen, arginfo_pg_field_prtlen) + PHP_FALIAS(pg_fieldisnull, pg_field_is_null, arginfo_pg_field_is_null) + PHP_FALIAS(pg_freeresult, pg_free_result, arginfo_pg_free_result) + PHP_FALIAS(pg_result, pg_fetch_result, arginfo_pg_get_result) + PHP_FALIAS(pg_loreadall, pg_lo_read_all, arginfo_pg_lo_read_all) + PHP_FALIAS(pg_locreate, pg_lo_create, arginfo_pg_lo_create) + PHP_FALIAS(pg_lounlink, pg_lo_unlink, arginfo_pg_lo_unlink) + PHP_FALIAS(pg_loopen, pg_lo_open, arginfo_pg_lo_open) + PHP_FALIAS(pg_loclose, pg_lo_close, arginfo_pg_lo_close) + PHP_FALIAS(pg_loread, pg_lo_read, arginfo_pg_lo_read) + PHP_FALIAS(pg_lowrite, pg_lo_write, arginfo_pg_lo_write) + PHP_FALIAS(pg_loimport, pg_lo_import, arginfo_pg_lo_import) + PHP_FALIAS(pg_loexport, pg_lo_export, arginfo_pg_lo_export) +#if HAVE_PQCLIENTENCODING + PHP_FALIAS(pg_clientencoding, pg_client_encoding, arginfo_pg_client_encoding) + PHP_FALIAS(pg_setclientencoding, pg_set_client_encoding, arginfo_pg_set_client_encoding) +#endif + PHP_FE_END +}; +/* }}} */ + +/* {{{ pgsql_module_entry + */ +zend_module_entry pgsql_module_entry = { + STANDARD_MODULE_HEADER, + "pgsql", + pgsql_functions, + PHP_MINIT(pgsql), + PHP_MSHUTDOWN(pgsql), + PHP_RINIT(pgsql), + PHP_RSHUTDOWN(pgsql), + PHP_MINFO(pgsql), + PHP_PGSQL_VERSION, + PHP_MODULE_GLOBALS(pgsql), + PHP_GINIT(pgsql), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_PGSQL +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(pgsql) +#endif + +static int le_link, le_plink, le_result, le_lofp, le_string; + +/* Compatibility definitions */ + +#ifndef HAVE_PGSQL_WITH_MULTIBYTE_SUPPORT +#define pg_encoding_to_char(x) "SQL_ASCII" +#endif + +#if !HAVE_PQESCAPE_CONN +#define PQescapeStringConn(conn, to, from, len, error) PQescapeString(to, from, len) +#endif + +#if HAVE_PQESCAPELITERAL +#define PGSQLescapeLiteral(conn, str, len) PQescapeLiteral(conn, str, len) +#define PGSQLescapeIdentifier(conn, str, len) PQescapeIdentifier(conn, str, len) +#define PGSQLfree(a) PQfreemem(a) +#else +#define PGSQLescapeLiteral(conn, str, len) php_pgsql_PQescapeInternal(conn, str, len, 1, 0) +#define PGSQLescapeLiteral2(conn, str, len) php_pgsql_PQescapeInternal(conn, str, len, 1, 1) +#define PGSQLescapeIdentifier(conn, str, len) php_pgsql_PQescapeInternal(conn, str, len, 0, 0) +#define PGSQLfree(a) efree(a) + +/* emulate libpq's PQescapeInternal() 9.0 or later */ +static char *php_pgsql_PQescapeInternal(PGconn *conn, const char *str, size_t len, int escape_literal, int safe) /* {{{ */ +{ + char *result, *rp, *s; + + if (!conn) { + return NULL; + } + + /* allocate enough memory */ + rp = result = (char *)safe_emalloc(len, 2, 5); /* leading " E" needs extra 2 bytes + quote_chars on both end for 2 bytes + NULL */ + + if (escape_literal) { + if (safe) { + size_t new_len; + char *tmp = (char *)safe_emalloc(len, 2, 1); + *rp++ = '\''; + /* PQescapeString does not escape \, but it handles multibyte chars safely. + This escape is incompatible with PQescapeLiteral. */ + new_len = PQescapeStringConn(conn, tmp, str, len, NULL); + strncpy(rp, tmp, new_len); + efree(tmp); + rp += new_len; + } else { + char *encoding; + size_t tmp_len; + /* This is compatible with PQescapeLiteral, but it cannot handle multbyte chars + such as SJIS, BIG5. Raise warning and return NULL by checking + client_encoding. */ + encoding = (char *) pg_encoding_to_char(PQclientEncoding(conn)); + if (!strncmp(encoding, "SJIS", sizeof("SJIS")-1) || + !strncmp(encoding, "SHIFT_JIS_2004", sizeof("SHIFT_JIS_2004")-1) || + !strncmp(encoding, "BIG5", sizeof("BIG5")-1) || + !strncmp(encoding, "GB18030", sizeof("GB18030")-1) || + !strncmp(encoding, "GBK", sizeof("GBK")-1) || + !strncmp(encoding, "JOHAB", sizeof("JOHAB")-1) || + !strncmp(encoding, "UHC", sizeof("UHC")-1) ) { + + php_error_docref(NULL, E_WARNING, "Unsafe encoding is used. Do not use '%s' encoding or use PostgreSQL 9.0 or later libpq.", encoding); + } + /* check backslashes */ + tmp_len = strspn(str, "\\"); + if (tmp_len != len) { + /* add " E" for escaping slashes */ + *rp++ = ' '; + *rp++ = 'E'; + } + *rp++ = '\''; + for (s = (char *)str; s - str < len; ++s) { + if (*s == '\'' || *s == '\\') { + *rp++ = *s; + *rp++ = *s; + } else { + *rp++ = *s; + } + } + } + *rp++ = '\''; + } else { + /* Identifier escape. */ + *rp++ = '"'; + for (s = (char *)str; s - str < len; ++s) { + if (*s == '"') { + *rp++ = '"'; + *rp++ = '"'; + } else { + *rp++ = *s; + } + } + *rp++ = '"'; + } + *rp = '\0'; + + return result; +} +/* }}} */ +#endif + +/* {{{ _php_pgsql_trim_message */ +static char * _php_pgsql_trim_message(const char *message, size_t *len) +{ + register size_t i = strlen(message); + + if (i>2 && (message[i-2] == '\r' || message[i-2] == '\n') && message[i-1] == '.') { + --i; + } + while (i>1 && (message[i-1] == '\r' || message[i-1] == '\n')) { + --i; + } + if (len) { + *len = i; + } + return estrndup(message, i); +} +/* }}} */ + +/* {{{ _php_pgsql_trim_result */ +static inline char * _php_pgsql_trim_result(PGconn * pgsql, char **buf) +{ + return *buf = _php_pgsql_trim_message(PQerrorMessage(pgsql), NULL); +} +/* }}} */ + +#define PQErrorMessageTrim(pgsql, buf) _php_pgsql_trim_result(pgsql, buf) + +#define PHP_PQ_ERROR(text, pgsql) { \ + char *msgbuf = _php_pgsql_trim_message(PQerrorMessage(pgsql), NULL); \ + php_error_docref(NULL, E_WARNING, text, msgbuf); \ + efree(msgbuf); \ +} \ + +/* {{{ php_pgsql_set_default_link + */ +static void php_pgsql_set_default_link(zend_resource *res) +{ + GC_ADDREF(res); + + if (PGG(default_link) != NULL) { + zend_list_delete(PGG(default_link)); + } + + PGG(default_link) = res; +} +/* }}} */ + +/* {{{ _close_pgsql_link + */ +static void _close_pgsql_link(zend_resource *rsrc) +{ + PGconn *link = (PGconn *)rsrc->ptr; + PGresult *res; + zval *hash; + + while ((res = PQgetResult(link))) { + PQclear(res); + } + PQfinish(link); + PGG(num_links)--; + + /* Remove connection hash for this link */ + hash = zend_hash_index_find(&PGG(hashes), (uintptr_t) link); + if (hash) { + zend_hash_index_del(&PGG(hashes), (uintptr_t) link); + zend_hash_del(&EG(regular_list), Z_STR_P(hash)); + } +} +/* }}} */ + +/* {{{ _close_pgsql_plink + */ +static void _close_pgsql_plink(zend_resource *rsrc) +{ + PGconn *link = (PGconn *)rsrc->ptr; + PGresult *res; + + while ((res = PQgetResult(link))) { + PQclear(res); + } + PQfinish(link); + PGG(num_persistent)--; + PGG(num_links)--; +} +/* }}} */ + +/* {{{ _php_pgsql_notice_handler + */ +static void _php_pgsql_notice_handler(void *resource_id, const char *message) +{ + zval *notices; + zval tmp; + char *trimed_message; + size_t trimed_message_len; + + if (! PGG(ignore_notices)) { + notices = zend_hash_index_find(&PGG(notices), (zend_ulong)resource_id); + if (!notices) { + array_init(&tmp); + notices = &tmp; + zend_hash_index_update(&PGG(notices), (zend_ulong)resource_id, notices); + } + trimed_message = _php_pgsql_trim_message(message, &trimed_message_len); + if (PGG(log_notices)) { + php_error_docref(NULL, E_NOTICE, "%s", trimed_message); + } + add_next_index_stringl(notices, trimed_message, trimed_message_len); + efree(trimed_message); + } +} +/* }}} */ + +/* {{{ _rollback_transactions + */ +static int _rollback_transactions(zval *el) +{ + PGconn *link; + PGresult *res; + zend_resource *rsrc = Z_RES_P(el); + + if (rsrc->type != le_plink) + return 0; + + link = (PGconn *) rsrc->ptr; + + if (PQ_SETNONBLOCKING(link, 0)) { + php_error_docref("ref.pgsql", E_NOTICE, "Cannot set connection to blocking mode"); + return -1; + } + + while ((res = PQgetResult(link))) { + PQclear(res); + } +#if HAVE_PGTRANSACTIONSTATUS && HAVE_PQPROTOCOLVERSION + if ((PQprotocolVersion(link) >= 3 && PQtransactionStatus(link) != PQTRANS_IDLE) || PQprotocolVersion(link) < 3) +#endif + { + int orig = PGG(ignore_notices); + PGG(ignore_notices) = 1; +#if HAVE_PGTRANSACTIONSTATUS && HAVE_PQPROTOCOLVERSION + res = PQexec(link,"ROLLBACK;"); +#else + res = PQexec(link,"BEGIN;"); + PQclear(res); + res = PQexec(link,"ROLLBACK;"); +#endif + PQclear(res); + PGG(ignore_notices) = orig; + } + + return 0; +} +/* }}} */ + +/* {{{ _free_ptr + */ +static void _free_ptr(zend_resource *rsrc) +{ + pgLofp *lofp = (pgLofp *)rsrc->ptr; + efree(lofp); +} +/* }}} */ + +/* {{{ _free_result + */ +static void _free_result(zend_resource *rsrc) +{ + pgsql_result_handle *pg_result = (pgsql_result_handle *)rsrc->ptr; + + PQclear(pg_result->result); + efree(pg_result); +} +/* }}} */ + +static int _php_pgsql_detect_identifier_escape(const char *identifier, size_t len) /* {{{ */ +{ + /* Handle edge case. Cannot be a escaped string */ + if (len <= 2) { + return FAILURE; + } + /* Detect double quotes */ + if (identifier[0] == '"' && identifier[len-1] == '"') { + size_t i; + + /* Detect wrong format of " inside of escaped string */ + for (i = 1; i < len-1; i++) { + if (identifier[i] == '"' && (identifier[++i] != '"' || i == len-1)) { + return FAILURE; + } + } + } else { + return FAILURE; + } + /* Escaped properly */ + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() +STD_PHP_INI_BOOLEAN( "pgsql.allow_persistent", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_persistent, zend_pgsql_globals, pgsql_globals) +STD_PHP_INI_ENTRY_EX("pgsql.max_persistent", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_persistent, zend_pgsql_globals, pgsql_globals, display_link_numbers) +STD_PHP_INI_ENTRY_EX("pgsql.max_links", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_links, zend_pgsql_globals, pgsql_globals, display_link_numbers) +STD_PHP_INI_BOOLEAN( "pgsql.auto_reset_persistent", "0", PHP_INI_SYSTEM, OnUpdateBool, auto_reset_persistent, zend_pgsql_globals, pgsql_globals) +STD_PHP_INI_BOOLEAN( "pgsql.ignore_notice", "0", PHP_INI_ALL, OnUpdateBool, ignore_notices, zend_pgsql_globals, pgsql_globals) +STD_PHP_INI_BOOLEAN( "pgsql.log_notice", "0", PHP_INI_ALL, OnUpdateBool, log_notices, zend_pgsql_globals, pgsql_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(pgsql) +{ +#if defined(COMPILE_DL_PGSQL) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + memset(pgsql_globals, 0, sizeof(zend_pgsql_globals)); + /* Initialize notice message hash at MINIT only */ + zend_hash_init_ex(&pgsql_globals->notices, 0, NULL, ZVAL_PTR_DTOR, 1, 0); + zend_hash_init_ex(&pgsql_globals->hashes, 0, NULL, ZVAL_PTR_DTOR, 1, 0); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(pgsql) +{ + REGISTER_INI_ENTRIES(); + + le_link = zend_register_list_destructors_ex(_close_pgsql_link, NULL, "pgsql link", module_number); + le_plink = zend_register_list_destructors_ex(NULL, _close_pgsql_plink, "pgsql link persistent", module_number); + le_result = zend_register_list_destructors_ex(_free_result, NULL, "pgsql result", module_number); + le_lofp = zend_register_list_destructors_ex(_free_ptr, NULL, "pgsql large object", module_number); + le_string = zend_register_list_destructors_ex(_free_ptr, NULL, "pgsql string", module_number); +#if HAVE_PG_CONFIG_H + /* PG_VERSION - libpq version */ + REGISTER_STRING_CONSTANT("PGSQL_LIBPQ_VERSION", PG_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("PGSQL_LIBPQ_VERSION_STR", PG_VERSION_STR, CONST_CS | CONST_PERSISTENT); +#endif + /* For connection option */ + REGISTER_LONG_CONSTANT("PGSQL_CONNECT_FORCE_NEW", PGSQL_CONNECT_FORCE_NEW, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECT_ASYNC", PGSQL_CONNECT_ASYNC, CONST_CS | CONST_PERSISTENT); + /* For pg_fetch_array() */ + REGISTER_LONG_CONSTANT("PGSQL_ASSOC", PGSQL_ASSOC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_NUM", PGSQL_NUM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_BOTH", PGSQL_BOTH, CONST_CS | CONST_PERSISTENT); + /* For pg_last_notice() */ + REGISTER_LONG_CONSTANT("PGSQL_NOTICE_LAST", PGSQL_NOTICE_LAST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_NOTICE_ALL", PGSQL_NOTICE_ALL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_NOTICE_CLEAR", PGSQL_NOTICE_CLEAR, CONST_CS | CONST_PERSISTENT); + /* For pg_connection_status() */ + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_BAD", CONNECTION_BAD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_OK", CONNECTION_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_STARTED", CONNECTION_STARTED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_MADE", CONNECTION_MADE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_AWAITING_RESPONSE", CONNECTION_AWAITING_RESPONSE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_AUTH_OK", CONNECTION_AUTH_OK, CONST_CS | CONST_PERSISTENT); +#ifdef CONNECTION_SSL_STARTUP + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_SSL_STARTUP", CONNECTION_SSL_STARTUP, CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("PGSQL_CONNECTION_SETENV", CONNECTION_SETENV, CONST_CS | CONST_PERSISTENT); + /* For pg_connect_poll() */ + REGISTER_LONG_CONSTANT("PGSQL_POLLING_FAILED", PGRES_POLLING_FAILED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_POLLING_READING", PGRES_POLLING_READING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_POLLING_WRITING", PGRES_POLLING_WRITING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_POLLING_OK", PGRES_POLLING_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_POLLING_ACTIVE", PGRES_POLLING_ACTIVE, CONST_CS | CONST_PERSISTENT); +#if HAVE_PGTRANSACTIONSTATUS + /* For pg_transaction_status() */ + REGISTER_LONG_CONSTANT("PGSQL_TRANSACTION_IDLE", PQTRANS_IDLE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_TRANSACTION_ACTIVE", PQTRANS_ACTIVE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_TRANSACTION_INTRANS", PQTRANS_INTRANS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_TRANSACTION_INERROR", PQTRANS_INERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_TRANSACTION_UNKNOWN", PQTRANS_UNKNOWN, CONST_CS | CONST_PERSISTENT); +#endif +#if HAVE_PQSETERRORVERBOSITY + /* For pg_set_error_verbosity() */ + REGISTER_LONG_CONSTANT("PGSQL_ERRORS_TERSE", PQERRORS_TERSE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_ERRORS_DEFAULT", PQERRORS_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_ERRORS_VERBOSE", PQERRORS_VERBOSE, CONST_CS | CONST_PERSISTENT); +#endif + /* For lo_seek() */ + REGISTER_LONG_CONSTANT("PGSQL_SEEK_SET", SEEK_SET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_SEEK_CUR", SEEK_CUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_SEEK_END", SEEK_END, CONST_CS | CONST_PERSISTENT); + /* For pg_result_status() return value type */ + REGISTER_LONG_CONSTANT("PGSQL_STATUS_LONG", PGSQL_STATUS_LONG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_STATUS_STRING", PGSQL_STATUS_STRING, CONST_CS | CONST_PERSISTENT); + /* For pg_result_status() return value */ + REGISTER_LONG_CONSTANT("PGSQL_EMPTY_QUERY", PGRES_EMPTY_QUERY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_COMMAND_OK", PGRES_COMMAND_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_TUPLES_OK", PGRES_TUPLES_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_COPY_OUT", PGRES_COPY_OUT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_COPY_IN", PGRES_COPY_IN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_BAD_RESPONSE", PGRES_BAD_RESPONSE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_NONFATAL_ERROR", PGRES_NONFATAL_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_FATAL_ERROR", PGRES_FATAL_ERROR, CONST_CS | CONST_PERSISTENT); +#if HAVE_PQRESULTERRORFIELD + /* For pg_result_error_field() field codes */ + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SEVERITY", PG_DIAG_SEVERITY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SQLSTATE", PG_DIAG_SQLSTATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_MESSAGE_PRIMARY", PG_DIAG_MESSAGE_PRIMARY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_MESSAGE_DETAIL", PG_DIAG_MESSAGE_DETAIL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_MESSAGE_HINT", PG_DIAG_MESSAGE_HINT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_STATEMENT_POSITION", PG_DIAG_STATEMENT_POSITION, CONST_CS | CONST_PERSISTENT); +#ifdef PG_DIAG_INTERNAL_POSITION + REGISTER_LONG_CONSTANT("PGSQL_DIAG_INTERNAL_POSITION", PG_DIAG_INTERNAL_POSITION, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_INTERNAL_QUERY + REGISTER_LONG_CONSTANT("PGSQL_DIAG_INTERNAL_QUERY", PG_DIAG_INTERNAL_QUERY, CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("PGSQL_DIAG_CONTEXT", PG_DIAG_CONTEXT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SOURCE_FILE", PG_DIAG_SOURCE_FILE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SOURCE_LINE", PG_DIAG_SOURCE_LINE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SOURCE_FUNCTION", PG_DIAG_SOURCE_FUNCTION, CONST_CS | CONST_PERSISTENT); +#ifdef PG_DIAG_SCHEMA_NAME + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SCHEMA_NAME", PG_DIAG_SCHEMA_NAME, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_TABLE_NAME + REGISTER_LONG_CONSTANT("PGSQL_DIAG_TABLE_NAME", PG_DIAG_TABLE_NAME, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_COLUMN_NAME + REGISTER_LONG_CONSTANT("PGSQL_DIAG_COLUMN_NAME", PG_DIAG_COLUMN_NAME, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_DATATYPE_NAME + REGISTER_LONG_CONSTANT("PGSQL_DIAG_DATATYPE_NAME", PG_DIAG_DATATYPE_NAME, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_CONSTRAINT_NAME + REGISTER_LONG_CONSTANT("PGSQL_DIAG_CONSTRAINT_NAME", PG_DIAG_CONSTRAINT_NAME, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef PG_DIAG_SEVERITY_NONLOCALIZED + REGISTER_LONG_CONSTANT("PGSQL_DIAG_SEVERITY_NONLOCALIZED", PG_DIAG_SEVERITY_NONLOCALIZED, CONST_CS | CONST_PERSISTENT); +#endif +#endif + /* pg_convert options */ + REGISTER_LONG_CONSTANT("PGSQL_CONV_IGNORE_DEFAULT", PGSQL_CONV_IGNORE_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONV_FORCE_NULL", PGSQL_CONV_FORCE_NULL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_CONV_IGNORE_NOT_NULL", PGSQL_CONV_IGNORE_NOT_NULL, CONST_CS | CONST_PERSISTENT); + /* pg_insert/update/delete/select options */ + REGISTER_LONG_CONSTANT("PGSQL_DML_ESCAPE", PGSQL_DML_ESCAPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DML_NO_CONV", PGSQL_DML_NO_CONV, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DML_EXEC", PGSQL_DML_EXEC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DML_ASYNC", PGSQL_DML_ASYNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PGSQL_DML_STRING", PGSQL_DML_STRING, CONST_CS | CONST_PERSISTENT); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(pgsql) +{ + UNREGISTER_INI_ENTRIES(); + zend_hash_destroy(&PGG(notices)); + zend_hash_destroy(&PGG(hashes)); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(pgsql) +{ + PGG(default_link) = NULL; + PGG(num_links) = PGG(num_persistent); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(pgsql) +{ + /* clean up notice messages */ + zend_hash_clean(&PGG(notices)); + zend_hash_clean(&PGG(hashes)); + /* clean up persistent connection */ + zend_hash_apply(&EG(persistent_list), (apply_func_t) _rollback_transactions); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(pgsql) +{ + char buf[256]; + + php_info_print_table_start(); + php_info_print_table_header(2, "PostgreSQL Support", "enabled"); +#if HAVE_PG_CONFIG_H + php_info_print_table_row(2, "PostgreSQL(libpq) Version", PG_VERSION); + php_info_print_table_row(2, "PostgreSQL(libpq) ", PG_VERSION_STR); +#ifdef HAVE_PGSQL_WITH_MULTIBYTE_SUPPORT + php_info_print_table_row(2, "Multibyte character support", "enabled"); +#else + php_info_print_table_row(2, "Multibyte character support", "disabled"); +#endif +#if defined(USE_SSL) || defined(USE_OPENSSL) + php_info_print_table_row(2, "SSL support", "enabled"); +#else + php_info_print_table_row(2, "SSL support", "disabled"); +#endif +#endif /* HAVE_PG_CONFIG_H */ + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, PGG(num_persistent)); + php_info_print_table_row(2, "Active Persistent Links", buf); + snprintf(buf, sizeof(buf), ZEND_LONG_FMT, PGG(num_links)); + php_info_print_table_row(2, "Active Links", buf); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ php_pgsql_do_connect + */ +static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) +{ + char *host=NULL,*port=NULL,*options=NULL,*tty=NULL,*dbname=NULL,*connstring=NULL; + PGconn *pgsql; + smart_str str = {0}; + zval *args; + uint32_t i; + int connect_type = 0; + PGresult *pg_result; + + args = (zval *)safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval), 0); + if (ZEND_NUM_ARGS() < 1 || ZEND_NUM_ARGS() > 5 + || zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) { + efree(args); + WRONG_PARAM_COUNT; + } + + smart_str_appends(&str, "pgsql"); + + for (i = 0; i < ZEND_NUM_ARGS(); i++) { + /* make sure that the PGSQL_CONNECT_FORCE_NEW bit is not part of the hash so that subsequent connections + * can re-use this connection. Bug #39979 + */ + if (i == 1 && ZEND_NUM_ARGS() == 2 && Z_TYPE(args[i]) == IS_LONG) { + if (Z_LVAL(args[1]) == PGSQL_CONNECT_FORCE_NEW) { + continue; + } else if (Z_LVAL(args[1]) & PGSQL_CONNECT_FORCE_NEW) { + smart_str_append_long(&str, Z_LVAL(args[1]) ^ PGSQL_CONNECT_FORCE_NEW); + } + } + ZVAL_STR(&args[i], zval_get_string(&args[i])); + smart_str_appendc(&str, '_'); + smart_str_appendl(&str, Z_STRVAL(args[i]), Z_STRLEN(args[i])); + } + + /* Exception thrown during a string conversion. */ + if (EG(exception)) { + goto cleanup; + } + + smart_str_0(&str); + + if (ZEND_NUM_ARGS() == 1) { /* new style, using connection string */ + connstring = Z_STRVAL(args[0]); + } else if (ZEND_NUM_ARGS() == 2 ) { /* Safe to add conntype_option, since 2 args was illegal */ + connstring = Z_STRVAL(args[0]); + connect_type = (int)zval_get_long(&args[1]); + } else { + host = Z_STRVAL(args[0]); + port = Z_STRVAL(args[1]); + dbname = Z_STRVAL(args[ZEND_NUM_ARGS()-1]); + + switch (ZEND_NUM_ARGS()) { + case 5: + tty = Z_STRVAL(args[3]); + /* fall through */ + case 4: + options = Z_STRVAL(args[2]); + break; + } + } + + if (persistent && PGG(allow_persistent)) { + zend_resource *le; + + /* try to find if we already have this link in our persistent list */ + if ((le = zend_hash_find_ptr(&EG(persistent_list), str.s)) == NULL) { /* we don't */ + if (PGG(max_links) != -1 && PGG(num_links) >= PGG(max_links)) { + php_error_docref(NULL, E_WARNING, + "Cannot create new link. Too many open links (" ZEND_LONG_FMT ")", PGG(num_links)); + goto err; + } + if (PGG(max_persistent) != -1 && PGG(num_persistent) >= PGG(max_persistent)) { + php_error_docref(NULL, E_WARNING, + "Cannot create new link. Too many open persistent links (" ZEND_LONG_FMT ")", PGG(num_persistent)); + goto err; + } + + /* create the link */ + if (connstring) { + pgsql = PQconnectdb(connstring); + } else { + pgsql = PQsetdb(host, port, options, tty, dbname); + } + if (pgsql == NULL || PQstatus(pgsql) == CONNECTION_BAD) { + PHP_PQ_ERROR("Unable to connect to PostgreSQL server: %s", pgsql) + if (pgsql) { + PQfinish(pgsql); + } + goto err; + } + + /* hash it up */ + if (zend_register_persistent_resource(ZSTR_VAL(str.s), ZSTR_LEN(str.s), pgsql, le_plink) == NULL) { + goto err; + } + PGG(num_links)++; + PGG(num_persistent)++; + } else { /* we do */ + if (le->type != le_plink) { + goto err; + } + /* ensure that the link did not die */ + if (PGG(auto_reset_persistent) & 1) { + /* need to send & get something from backend to + make sure we catch CONNECTION_BAD every time */ + PGresult *pg_result; + pg_result = PQexec(le->ptr, "select 1"); + PQclear(pg_result); + } + if (PQstatus(le->ptr) == CONNECTION_BAD) { /* the link died */ + if (le->ptr == NULL) { + if (connstring) { + le->ptr = PQconnectdb(connstring); + } else { + le->ptr = PQsetdb(host,port,options,tty,dbname); + } + } + else { + PQreset(le->ptr); + } + if (le->ptr == NULL || PQstatus(le->ptr) == CONNECTION_BAD) { + php_error_docref(NULL, E_WARNING,"PostgreSQL link lost, unable to reconnect"); + zend_hash_del(&EG(persistent_list), str.s); + goto err; + } + } + pgsql = (PGconn *) le->ptr; +#if HAVE_PQPROTOCOLVERSION && HAVE_PQPARAMETERSTATUS + if (PQprotocolVersion(pgsql) >= 3 && atof(PQparameterStatus(pgsql, "server_version")) >= 7.2) { +#else + if (atof(PG_VERSION) >= 7.2) { +#endif + pg_result = PQexec(pgsql, "RESET ALL;"); + PQclear(pg_result); + } + } + RETVAL_RES(zend_register_resource(pgsql, le_plink)); + } else { /* Non persistent connection */ + zend_resource *index_ptr, new_index_ptr; + + /* first we check the hash for the hashed_details key. if it exists, + * it should point us to the right offset where the actual pgsql link sits. + * if it doesn't, open a new pgsql link, add it to the resource list, + * and add a pointer to it with hashed_details as the key. + */ + if (!(connect_type & PGSQL_CONNECT_FORCE_NEW) + && (index_ptr = zend_hash_find_ptr(&EG(regular_list), str.s)) != NULL) { + zend_resource *link; + + if (index_ptr->type != le_index_ptr) { + goto err; + } + + link = (zend_resource *)index_ptr->ptr; + ZEND_ASSERT(link->ptr && (link->type == le_link || link->type == le_plink)); + php_pgsql_set_default_link(link); + GC_ADDREF(link); + RETVAL_RES(link); + goto cleanup; + } + if (PGG(max_links) != -1 && PGG(num_links) >= PGG(max_links)) { + php_error_docref(NULL, E_WARNING, "Cannot create new link. Too many open links (" ZEND_LONG_FMT ")", PGG(num_links)); + goto err; + } + + /* Non-blocking connect */ + if (connect_type & PGSQL_CONNECT_ASYNC) { + if (connstring) { + pgsql = PQconnectStart(connstring); + if (pgsql==NULL || PQstatus(pgsql)==CONNECTION_BAD) { + PHP_PQ_ERROR("Unable to connect to PostgreSQL server: %s", pgsql); + if (pgsql) { + PQfinish(pgsql); + } + goto err; + } + } else { + php_error_docref(NULL, E_WARNING, "Connection string required for async connections"); + goto err; + } + } else { + if (connstring) { + pgsql = PQconnectdb(connstring); + } else { + pgsql = PQsetdb(host,port,options,tty,dbname); + } + if (pgsql==NULL || PQstatus(pgsql)==CONNECTION_BAD) { + PHP_PQ_ERROR("Unable to connect to PostgreSQL server: %s", pgsql); + if (pgsql) { + PQfinish(pgsql); + } + goto err; + } + } + + /* add it to the list */ + RETVAL_RES(zend_register_resource(pgsql, le_link)); + + /* add it to the hash */ + new_index_ptr.ptr = (void *) Z_RES_P(return_value); + new_index_ptr.type = le_index_ptr; + zend_hash_update_mem(&EG(regular_list), str.s, (void *) &new_index_ptr, sizeof(zend_resource)); + + /* Keep track of link => hash mapping, so we can remove the hash entry from regular_list + * when the connection is closed. This uses the address of the connection rather than the + * zend_resource, because the resource destructor is passed a stack copy of the resource + * structure. */ + { + zval tmp; + ZVAL_STR_COPY(&tmp, str.s); + zend_hash_index_update(&PGG(hashes), (uintptr_t) pgsql, &tmp); + } + PGG(num_links)++; + } + /* set notice processor */ + if (! PGG(ignore_notices) && Z_TYPE_P(return_value) == IS_RESOURCE) { + PQsetNoticeProcessor(pgsql, _php_pgsql_notice_handler, (void*)(zend_uintptr_t)Z_RES_HANDLE_P(return_value)); + } + php_pgsql_set_default_link(Z_RES_P(return_value)); + +cleanup: + for (i = 0; i < ZEND_NUM_ARGS(); i++) { + zval_ptr_dtor(&args[i]); + } + efree(args); + smart_str_free(&str); + return; + +err: + for (i = 0; i < ZEND_NUM_ARGS(); i++) { + zval_ptr_dtor(&args[i]); + } + efree(args); + smart_str_free(&str); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto resource pg_connect(string connection_string[, int connect_type] | [string host, string port [, string options [, string tty,]]] string database) + Open a PostgreSQL connection */ +PHP_FUNCTION(pg_connect) +{ + php_pgsql_do_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU,0); +} +/* }}} */ + +/* {{{ proto resource pg_connect_poll(resource connection) + Poll the status of an in-progress async PostgreSQL connection attempt*/ +PHP_FUNCTION(pg_connect_poll) +{ + zval *pgsql_link; + PGconn *pgsql; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_link) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + ret = PQconnectPoll(pgsql); + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ proto resource pg_pconnect(string connection_string | [string host, string port [, string options [, string tty,]]] string database) + Open a persistent PostgreSQL connection */ +PHP_FUNCTION(pg_pconnect) +{ + php_pgsql_do_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU,1); +} +/* }}} */ + +/* {{{ proto bool pg_close([resource connection]) + Close a PostgreSQL connection */ +PHP_FUNCTION(pg_close) +{ + zval *pgsql_link = NULL; + zend_resource *link; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|r", &pgsql_link) == FAILURE) { + return; + } + + if (!pgsql_link) { + link = PGG(default_link); + CHECK_DEFAULT_LINK(link); + zend_list_delete(link); + PGG(default_link) = NULL; + RETURN_TRUE; + } + + link = Z_RES_P(pgsql_link); + if (zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink) == NULL) { + RETURN_FALSE; + } + + if (link == PGG(default_link)) { + zend_list_delete(link); + PGG(default_link) = NULL; + } + zend_list_close(link); + + RETURN_TRUE; +} +/* }}} */ + +#define PHP_PG_DBNAME 1 +#define PHP_PG_ERROR_MESSAGE 2 +#define PHP_PG_OPTIONS 3 +#define PHP_PG_PORT 4 +#define PHP_PG_TTY 5 +#define PHP_PG_HOST 6 +#define PHP_PG_VERSION 7 + +/* {{{ php_pgsql_get_link_info + */ +static void php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAMETERS, int entry_type) +{ + zend_resource *link; + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + char *msgbuf; + char *result; + + if (zend_parse_parameters(argc, "|r", &pgsql_link) == FAILURE) { + return; + } + + if (argc == 0) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + switch(entry_type) { + case PHP_PG_DBNAME: + result = PQdb(pgsql); + break; + case PHP_PG_ERROR_MESSAGE: + result = PQErrorMessageTrim(pgsql, &msgbuf); + RETVAL_STRING(result); + efree(result); + return; + case PHP_PG_OPTIONS: + result = PQoptions(pgsql); + break; + case PHP_PG_PORT: + result = PQport(pgsql); + break; + case PHP_PG_TTY: + result = PQtty(pgsql); + break; + case PHP_PG_HOST: + result = PQhost(pgsql); + break; + case PHP_PG_VERSION: + array_init(return_value); + add_assoc_string(return_value, "client", PG_VERSION); +#if HAVE_PQPROTOCOLVERSION + add_assoc_long(return_value, "protocol", PQprotocolVersion(pgsql)); +#if HAVE_PQPARAMETERSTATUS + if (PQprotocolVersion(pgsql) >= 3) { + /* 8.0 or grater supports protorol version 3 */ + char *tmp; + add_assoc_string(return_value, "server", (char*)PQparameterStatus(pgsql, "server_version")); + +#define PHP_PQ_COPY_PARAM(_x) tmp = (char*)PQparameterStatus(pgsql, _x); \ + if(tmp) add_assoc_string(return_value, _x, tmp); \ + else add_assoc_null(return_value, _x); + + PHP_PQ_COPY_PARAM("server_encoding"); + PHP_PQ_COPY_PARAM("client_encoding"); + PHP_PQ_COPY_PARAM("is_superuser"); + PHP_PQ_COPY_PARAM("session_authorization"); + PHP_PQ_COPY_PARAM("DateStyle"); + PHP_PQ_COPY_PARAM("IntervalStyle"); + PHP_PQ_COPY_PARAM("TimeZone"); + PHP_PQ_COPY_PARAM("integer_datetimes"); + PHP_PQ_COPY_PARAM("standard_conforming_strings"); + PHP_PQ_COPY_PARAM("application_name"); + } +#endif +#endif + return; + default: + RETURN_FALSE; + } + if (result) { + RETURN_STRING(result); + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto string pg_dbname([resource connection]) + Get the database name */ +PHP_FUNCTION(pg_dbname) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_DBNAME); +} +/* }}} */ + +/* {{{ proto string pg_last_error([resource connection]) + Get the error message string */ +PHP_FUNCTION(pg_last_error) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_ERROR_MESSAGE); +} +/* }}} */ + +/* {{{ proto string pg_options([resource connection]) + Get the options associated with the connection */ +PHP_FUNCTION(pg_options) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_OPTIONS); +} +/* }}} */ + +/* {{{ proto int pg_port([resource connection]) + Return the port number associated with the connection */ +PHP_FUNCTION(pg_port) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_PORT); +} +/* }}} */ + +/* {{{ proto string pg_tty([resource connection]) + Return the tty name associated with the connection */ +PHP_FUNCTION(pg_tty) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_TTY); +} +/* }}} */ + +/* {{{ proto string pg_host([resource connection]) + Returns the host name associated with the connection */ +PHP_FUNCTION(pg_host) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_HOST); +} +/* }}} */ + +/* {{{ proto array pg_version([resource connection]) + Returns an array with client, protocol and server version (when available) */ +PHP_FUNCTION(pg_version) +{ + php_pgsql_get_link_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_VERSION); +} +/* }}} */ + +#if HAVE_PQPARAMETERSTATUS +/* {{{ proto string|false pg_parameter_status([resource connection,] string param_name) + Returns the value of a server parameter */ +PHP_FUNCTION(pg_parameter_status) +{ + zval *pgsql_link = NULL; + zend_resource *link; + PGconn *pgsql; + char *param; + size_t len; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "rs", &pgsql_link, ¶m, &len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", ¶m, &len) == SUCCESS) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + RETURN_FALSE; + } + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + param = (char*)PQparameterStatus(pgsql, param); + if (param) { + RETURN_STRING(param); + } else { + RETURN_FALSE; + } +} +/* }}} */ +#endif + +/* {{{ proto bool pg_ping([resource connection]) + Ping database. If connection is bad, try to reconnect. */ +PHP_FUNCTION(pg_ping) +{ + zval *pgsql_link; + PGconn *pgsql; + PGresult *res; + zend_resource *link; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", &pgsql_link) == SUCCESS) { + link = Z_RES_P(pgsql_link); + } else { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + /* ping connection */ + res = PQexec(pgsql, "SELECT 1;"); + PQclear(res); + + /* check status. */ + if (PQstatus(pgsql) == CONNECTION_OK) + RETURN_TRUE; + + /* reset connection if it's broken */ + PQreset(pgsql); + if (PQstatus(pgsql) == CONNECTION_OK) { + RETURN_TRUE; + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto resource pg_query([resource connection,] string query) + Execute a query */ +PHP_FUNCTION(pg_query) +{ + zval *pgsql_link = NULL; + char *query; + int argc = ZEND_NUM_ARGS(); + size_t query_len; + int leftover = 0; + zend_resource *link; + PGconn *pgsql; + PGresult *pgsql_result; + ExecStatusType status; + + if (argc == 1) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &query, &query_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &pgsql_link, &query, &query_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); + RETURN_FALSE; + } + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + leftover = 1; + } + if (leftover) { + php_error_docref(NULL, E_NOTICE, "Found results on this connection. Use pg_get_result() to get these results first"); + } + pgsql_result = PQexec(pgsql, query); + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQclear(pgsql_result); + PQreset(pgsql); + pgsql_result = PQexec(pgsql, query); + } + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + switch (status) { + case PGRES_EMPTY_QUERY: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + PHP_PQ_ERROR("Query failed: %s", pgsql); + PQclear(pgsql_result); + RETURN_FALSE; + break; + case PGRES_COMMAND_OK: /* successful command that did not return rows */ + default: + if (pgsql_result) { + pgsql_result_handle *pg_result = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pg_result->conn = pgsql; + pg_result->result = pgsql_result; + pg_result->row = 0; + RETURN_RES(zend_register_resource(pg_result, le_result)); + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + break; + } +} +/* }}} */ + +#if HAVE_PQEXECPARAMS || HAVE_PQEXECPREPARED || HAVE_PQSENDQUERYPARAMS || HAVE_PQSENDQUERYPREPARED +/* {{{ _php_pgsql_free_params */ +static void _php_pgsql_free_params(char **params, int num_params) +{ + if (num_params > 0) { + int i; + for (i = 0; i < num_params; i++) { + if (params[i]) { + efree(params[i]); + } + } + efree(params); + } +} +/* }}} */ +#endif + +#if HAVE_PQEXECPARAMS +/* {{{ proto resource pg_query_params([resource connection,] string query, array params) + Execute a query */ +PHP_FUNCTION(pg_query_params) +{ + zval *pgsql_link = NULL; + zval *pv_param_arr, *tmp; + char *query; + size_t query_len; + int argc = ZEND_NUM_ARGS(); + int leftover = 0; + int num_params = 0; + char **params = NULL; + zend_resource *link; + PGconn *pgsql; + PGresult *pgsql_result; + ExecStatusType status; + pgsql_result_handle *pg_result; + + if (argc == 2) { + if (zend_parse_parameters(argc, "sa", &query, &query_len, &pv_param_arr) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rsa", &pgsql_link, &query, &query_len, &pv_param_arr) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); + RETURN_FALSE; + } + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + leftover = 1; + } + if (leftover) { + php_error_docref(NULL, E_NOTICE, "Found results on this connection. Use pg_get_result() to get these results first"); + } + + num_params = zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)); + if (num_params > 0) { + int i = 0; + params = (char **)safe_emalloc(sizeof(char *), num_params, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { + ZVAL_DEREF(tmp); + if (Z_TYPE_P(tmp) == IS_NULL) { + params[i] = NULL; + } else { + zval tmp_val; + + ZVAL_COPY(&tmp_val, tmp); + convert_to_cstring(&tmp_val); + if (Z_TYPE(tmp_val) != IS_STRING) { + php_error_docref(NULL, E_WARNING,"Error converting parameter"); + zval_ptr_dtor(&tmp_val); + _php_pgsql_free_params(params, num_params); + RETURN_FALSE; + } + params[i] = estrndup(Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); + zval_ptr_dtor(&tmp_val); + } + i++; + } ZEND_HASH_FOREACH_END(); + } + + pgsql_result = PQexecParams(pgsql, query, num_params, + NULL, (const char * const *)params, NULL, NULL, 0); + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQclear(pgsql_result); + PQreset(pgsql); + pgsql_result = PQexecParams(pgsql, query, num_params, + NULL, (const char * const *)params, NULL, NULL, 0); + } + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + _php_pgsql_free_params(params, num_params); + + switch (status) { + case PGRES_EMPTY_QUERY: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + PHP_PQ_ERROR("Query failed: %s", pgsql); + PQclear(pgsql_result); + RETURN_FALSE; + break; + case PGRES_COMMAND_OK: /* successful command that did not return rows */ + default: + if (pgsql_result) { + pg_result = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pg_result->conn = pgsql; + pg_result->result = pgsql_result; + pg_result->row = 0; + RETURN_RES(zend_register_resource(pg_result, le_result)); + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + break; + } +} +/* }}} */ +#endif + +#if HAVE_PQPREPARE +/* {{{ proto resource pg_prepare([resource connection,] string stmtname, string query) + Prepare a query for future execution */ +PHP_FUNCTION(pg_prepare) +{ + zval *pgsql_link = NULL; + char *query, *stmtname; + size_t query_len, stmtname_len; + int argc = ZEND_NUM_ARGS(); + int leftover = 0; + PGconn *pgsql; + zend_resource *link; + PGresult *pgsql_result; + ExecStatusType status; + pgsql_result_handle *pg_result; + + if (argc == 2) { + if (zend_parse_parameters(argc, "ss", &stmtname, &stmtname_len, &query, &query_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rss", &pgsql_link, &stmtname, &stmtname_len, &query, &query_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); + RETURN_FALSE; + } + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + leftover = 1; + } + if (leftover) { + php_error_docref(NULL, E_NOTICE, "Found results on this connection. Use pg_get_result() to get these results first"); + } + pgsql_result = PQprepare(pgsql, stmtname, query, 0, NULL); + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQclear(pgsql_result); + PQreset(pgsql); + pgsql_result = PQprepare(pgsql, stmtname, query, 0, NULL); + } + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + switch (status) { + case PGRES_EMPTY_QUERY: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + PHP_PQ_ERROR("Query failed: %s", pgsql); + PQclear(pgsql_result); + RETURN_FALSE; + break; + case PGRES_COMMAND_OK: /* successful command that did not return rows */ + default: + if (pgsql_result) { + pg_result = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pg_result->conn = pgsql; + pg_result->result = pgsql_result; + pg_result->row = 0; + RETURN_RES(zend_register_resource(pg_result, le_result)); + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + break; + } +} +/* }}} */ +#endif + +#if HAVE_PQEXECPREPARED +/* {{{ proto resource pg_execute([resource connection,] string stmtname, array params) + Execute a prepared query */ +PHP_FUNCTION(pg_execute) +{ + zval *pgsql_link = NULL; + zval *pv_param_arr, *tmp; + char *stmtname; + size_t stmtname_len; + int argc = ZEND_NUM_ARGS(); + int leftover = 0; + int num_params = 0; + char **params = NULL; + PGconn *pgsql; + zend_resource *link; + PGresult *pgsql_result; + ExecStatusType status; + pgsql_result_handle *pg_result; + + if (argc == 2) { + if (zend_parse_parameters(argc, "sa", &stmtname, &stmtname_len, &pv_param_arr)==FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rsa", &pgsql_link, &stmtname, &stmtname_len, &pv_param_arr) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); + RETURN_FALSE; + } + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + leftover = 1; + } + if (leftover) { + php_error_docref(NULL, E_NOTICE, "Found results on this connection. Use pg_get_result() to get these results first"); + } + + num_params = zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)); + if (num_params > 0) { + int i = 0; + params = (char **)safe_emalloc(sizeof(char *), num_params, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { + + if (Z_TYPE_P(tmp) == IS_NULL) { + params[i] = NULL; + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(tmp, &tmp_str); + + params[i] = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + } + + i++; + } ZEND_HASH_FOREACH_END(); + } + + pgsql_result = PQexecPrepared(pgsql, stmtname, num_params, + (const char * const *)params, NULL, NULL, 0); + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQclear(pgsql_result); + PQreset(pgsql); + pgsql_result = PQexecPrepared(pgsql, stmtname, num_params, + (const char * const *)params, NULL, NULL, 0); + } + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + _php_pgsql_free_params(params, num_params); + + switch (status) { + case PGRES_EMPTY_QUERY: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + PHP_PQ_ERROR("Query failed: %s", pgsql); + PQclear(pgsql_result); + RETURN_FALSE; + break; + case PGRES_COMMAND_OK: /* successful command that did not return rows */ + default: + if (pgsql_result) { + pg_result = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pg_result->conn = pgsql; + pg_result->result = pgsql_result; + pg_result->row = 0; + RETURN_RES(zend_register_resource(pg_result, le_result)); + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + break; + } +} +/* }}} */ +#endif + +#define PHP_PG_NUM_ROWS 1 +#define PHP_PG_NUM_FIELDS 2 +#define PHP_PG_CMD_TUPLES 3 + +/* {{{ php_pgsql_get_result_info + */ +static void php_pgsql_get_result_info(INTERNAL_FUNCTION_PARAMETERS, int entry_type) +{ + zval *result; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &result) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + + switch (entry_type) { + case PHP_PG_NUM_ROWS: + RETVAL_LONG(PQntuples(pgsql_result)); + break; + case PHP_PG_NUM_FIELDS: + RETVAL_LONG(PQnfields(pgsql_result)); + break; + case PHP_PG_CMD_TUPLES: +#if HAVE_PQCMDTUPLES + RETVAL_LONG(atoi(PQcmdTuples(pgsql_result))); +#else + php_error_docref(NULL, E_WARNING, "Not supported under this build"); + RETVAL_LONG(0); +#endif + break; + default: + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int pg_num_rows(resource result) + Return the number of rows in the result */ +PHP_FUNCTION(pg_num_rows) +{ + php_pgsql_get_result_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_NUM_ROWS); +} +/* }}} */ + +/* {{{ proto int pg_num_fields(resource result) + Return the number of fields in the result */ +PHP_FUNCTION(pg_num_fields) +{ + php_pgsql_get_result_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_NUM_FIELDS); +} +/* }}} */ + +#if HAVE_PQCMDTUPLES +/* {{{ proto int pg_affected_rows(resource result) + Returns the number of affected tuples */ +PHP_FUNCTION(pg_affected_rows) +{ + php_pgsql_get_result_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_CMD_TUPLES); +} +/* }}} */ +#endif + +/* {{{ proto mixed pg_last_notice(resource connection [, int option]) + Returns the last notice set by the backend */ +PHP_FUNCTION(pg_last_notice) +{ + zval *pgsql_link = NULL; + zval *notice, *notices; + PGconn *pg_link; + zend_long option = PGSQL_NOTICE_LAST; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &pgsql_link, &option) == FAILURE) { + return; + } + + /* Just to check if user passed valid resoruce */ + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + notices = zend_hash_index_find(&PGG(notices), (zend_ulong)Z_RES_HANDLE_P(pgsql_link)); + switch (option) { + case PGSQL_NOTICE_LAST: + if (notices) { + zend_hash_internal_pointer_end(Z_ARRVAL_P(notices)); + if ((notice = zend_hash_get_current_data(Z_ARRVAL_P(notices))) == NULL) { + RETURN_EMPTY_STRING(); + } + RETURN_ZVAL(notice, 1, 0); + } else { + RETURN_EMPTY_STRING(); + } + break; + case PGSQL_NOTICE_ALL: + if (notices) { + RETURN_ZVAL(notices, 1, 0); + } else { + array_init(return_value); + return; + } + break; + case PGSQL_NOTICE_CLEAR: + if (notices) { + zend_hash_clean(&PGG(notices)); + } + RETURN_TRUE; + break; + default: + php_error_docref(NULL, E_WARNING, + "Invalid option specified (" ZEND_LONG_FMT ")", option); + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ get_field_name + */ +static char *get_field_name(PGconn *pgsql, Oid oid, HashTable *list) +{ + smart_str str = {0}; + zend_resource *field_type; + char *ret=NULL; + + /* try to lookup the type in the resource list */ + smart_str_appends(&str, "pgsql_oid_"); + smart_str_append_unsigned(&str, oid); + smart_str_0(&str); + + if ((field_type = zend_hash_find_ptr(list, str.s)) != NULL) { + ret = estrdup((char *)field_type->ptr); + } else { /* hash all oid's */ + int i, num_rows; + int oid_offset,name_offset; + char *tmp_oid, *end_ptr, *tmp_name; + zend_resource new_oid_entry; + PGresult *result; + + if ((result = PQexec(pgsql, "select oid,typname from pg_type")) == NULL || PQresultStatus(result) != PGRES_TUPLES_OK) { + if (result) { + PQclear(result); + } + smart_str_free(&str); + return estrndup("", sizeof("") - 1); + } + num_rows = PQntuples(result); + oid_offset = PQfnumber(result,"oid"); + name_offset = PQfnumber(result,"typname"); + + for (i=0; i<num_rows; i++) { + if ((tmp_oid = PQgetvalue(result,i,oid_offset))==NULL) { + continue; + } + + smart_str_free(&str); + smart_str_appends(&str, "pgsql_oid_"); + smart_str_appends(&str, tmp_oid); + smart_str_0(&str); + + if ((tmp_name = PQgetvalue(result,i,name_offset))==NULL) { + continue; + } + new_oid_entry.type = le_string; + new_oid_entry.ptr = estrdup(tmp_name); + zend_hash_update_mem(list, str.s, (void *) &new_oid_entry, sizeof(zend_resource)); + if (!ret && strtoul(tmp_oid, &end_ptr, 10)==oid) { + ret = estrdup(tmp_name); + } + } + PQclear(result); + } + + smart_str_free(&str); + return ret; +} +/* }}} */ + +#ifdef HAVE_PQFTABLE +/* {{{ proto mixed pg_field_table(resource result, int field_number[, bool oid_only]) + Returns the name of the table field belongs to, or table's oid if oid_only is true */ +PHP_FUNCTION(pg_field_table) +{ + zval *result; + pgsql_result_handle *pg_result; + zend_long fnum = -1; + zend_bool return_oid = 0; + Oid oid; + smart_str hash_key = {0}; + char *table_name; + zend_resource *field_table; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|b", &result, &fnum, &return_oid) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + if (fnum < 0 || fnum >= PQnfields(pg_result->result)) { + php_error_docref(NULL, E_WARNING, "Bad field offset specified"); + RETURN_FALSE; + } + + oid = PQftable(pg_result->result, (int)fnum); + + if (InvalidOid == oid) { + RETURN_FALSE; + } + + if (return_oid) { +#if UINT_MAX > ZEND_LONG_MAX /* Oid is unsigned int, we don't need this code, where LONG is wider */ + if (oid > ZEND_LONG_MAX) { + smart_str oidstr = {0}; + smart_str_append_unsigned(&oidstr, oid); + smart_str_0(&oidstr); + RETURN_NEW_STR(oidstr.s); + } else +#endif + RETURN_LONG((zend_long)oid); + } + + /* try to lookup the table name in the resource list */ + smart_str_appends(&hash_key, "pgsql_table_oid_"); + smart_str_append_unsigned(&hash_key, oid); + smart_str_0(&hash_key); + + if ((field_table = zend_hash_find_ptr(&EG(regular_list), hash_key.s)) != NULL) { + smart_str_free(&hash_key); + RETURN_STRING((char *)field_table->ptr); + } else { /* Not found, lookup by querying PostgreSQL system tables */ + PGresult *tmp_res; + smart_str querystr = {0}; + zend_resource new_field_table; + + smart_str_appends(&querystr, "select relname from pg_class where oid="); + smart_str_append_unsigned(&querystr, oid); + smart_str_0(&querystr); + + if ((tmp_res = PQexec(pg_result->conn, ZSTR_VAL(querystr.s))) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + smart_str_free(&querystr); + smart_str_free(&hash_key); + RETURN_FALSE; + } + + smart_str_free(&querystr); + + if ((table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + smart_str_free(&hash_key); + RETURN_FALSE; + } + + new_field_table.type = le_string; + new_field_table.ptr = estrdup(table_name); + zend_hash_update_mem(&EG(regular_list), hash_key.s, (void *)&new_field_table, sizeof(zend_resource)); + + smart_str_free(&hash_key); + PQclear(tmp_res); + RETURN_STRING(table_name); + } + +} +/* }}} */ +#endif + +#define PHP_PG_FIELD_NAME 1 +#define PHP_PG_FIELD_SIZE 2 +#define PHP_PG_FIELD_TYPE 3 +#define PHP_PG_FIELD_TYPE_OID 4 + +/* {{{ php_pgsql_get_field_info + */ +static void php_pgsql_get_field_info(INTERNAL_FUNCTION_PARAMETERS, int entry_type) +{ + zval *result; + zend_long field; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + Oid oid; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &result, &field) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + + pgsql_result = pg_result->result; + + if (field < 0 || field >= PQnfields(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Bad field offset specified"); + RETURN_FALSE; + } + + switch (entry_type) { + case PHP_PG_FIELD_NAME: + RETURN_STRING(PQfname(pgsql_result, (int)field)); + break; + case PHP_PG_FIELD_SIZE: + RETURN_LONG(PQfsize(pgsql_result, (int)field)); + break; + case PHP_PG_FIELD_TYPE: { + char *name = get_field_name(pg_result->conn, PQftype(pgsql_result, (int)field), &EG(regular_list)); + RETVAL_STRING(name); + efree(name); + } + break; + case PHP_PG_FIELD_TYPE_OID: + + oid = PQftype(pgsql_result, (int)field); +#if UINT_MAX > ZEND_LONG_MAX + if (oid > ZEND_LONG_MAX) { + smart_str s = {0}; + smart_str_append_unsigned(&s, oid); + smart_str_0(&s); + RETURN_NEW_STR(s.s); + } else +#endif + { + RETURN_LONG((zend_long)oid); + } + break; + default: + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string pg_field_name(resource result, int field_number) + Returns the name of the field */ +PHP_FUNCTION(pg_field_name) +{ + php_pgsql_get_field_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_FIELD_NAME); +} +/* }}} */ + +/* {{{ proto int pg_field_size(resource result, int field_number) + Returns the internal size of the field */ +PHP_FUNCTION(pg_field_size) +{ + php_pgsql_get_field_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_FIELD_SIZE); +} +/* }}} */ + +/* {{{ proto string pg_field_type(resource result, int field_number) + Returns the type name for the given field */ +PHP_FUNCTION(pg_field_type) +{ + php_pgsql_get_field_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_FIELD_TYPE); +} +/* }}} */ + +/* {{{ proto string pg_field_type_oid(resource result, int field_number) + Returns the type oid for the given field */ +PHP_FUNCTION(pg_field_type_oid) +{ + php_pgsql_get_field_info(INTERNAL_FUNCTION_PARAM_PASSTHRU,PHP_PG_FIELD_TYPE_OID); +} +/* }}} */ + +/* {{{ proto int pg_field_num(resource result, string field_name) + Returns the field number of the named field */ +PHP_FUNCTION(pg_field_num) +{ + zval *result; + char *field; + size_t field_len; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &result, &field, &field_len) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + + RETURN_LONG(PQfnumber(pgsql_result, field)); +} +/* }}} */ + +/* {{{ proto mixed pg_fetch_result(resource result, [int row_number,] mixed field_name) + Returns values from a result identifier */ +PHP_FUNCTION(pg_fetch_result) +{ + zval *result, *field=NULL; + zend_long row; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + int field_offset, pgsql_row, argc = ZEND_NUM_ARGS(); + + if (argc == 2) { + if (zend_parse_parameters(argc, "rz", &result, &field) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(argc, "rlz", &result, &row, &field) == FAILURE) { + return; + } + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + if (argc == 2) { + if (pg_result->row < 0) { + pg_result->row = 0; + } + pgsql_row = pg_result->row; + if (pgsql_row >= PQntuples(pgsql_result)) { + RETURN_FALSE; + } + pg_result->row++; + } else { + if (row < 0 || row >= PQntuples(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Unable to jump to row " ZEND_LONG_FMT " on PostgreSQL result index " ZEND_LONG_FMT, + row, Z_LVAL_P(result)); + RETURN_FALSE; + } + pgsql_row = (int)row; + } + switch (Z_TYPE_P(field)) { + case IS_STRING: + field_offset = PQfnumber(pgsql_result, Z_STRVAL_P(field)); + if (field_offset < 0 || field_offset >= PQnfields(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Bad column offset specified"); + RETURN_FALSE; + } + break; + default: + convert_to_long_ex(field); + if (Z_LVAL_P(field) < 0 || Z_LVAL_P(field) >= PQnfields(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Bad column offset specified"); + RETURN_FALSE; + } + field_offset = (int)Z_LVAL_P(field); + break; + } + + if (PQgetisnull(pgsql_result, pgsql_row, field_offset)) { + RETVAL_NULL(); + } else { + RETVAL_STRINGL(PQgetvalue(pgsql_result, pgsql_row, field_offset), + PQgetlength(pgsql_result, pgsql_row, field_offset)); + } +} +/* }}} */ + +/* {{{ void php_pgsql_fetch_hash */ +static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_type, int into_object) +{ + zval *result, *zrow = NULL; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + int i, num_fields, pgsql_row, use_row; + zend_long row = -1; + char *field_name; + zval *ctor_params = NULL; + zend_class_entry *ce = NULL; + + if (into_object) { + zend_string *class_name = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z!Sz", &result, &zrow, &class_name, &ctor_params) == FAILURE) { + return; + } + if (!class_name) { + ce = zend_standard_class_def; + } else { + ce = zend_fetch_class(class_name, ZEND_FETCH_CLASS_AUTO); + } + if (!ce) { + php_error_docref(NULL, E_WARNING, "Could not find class '%s'", ZSTR_VAL(class_name)); + return; + } + result_type = PGSQL_ASSOC; + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z!l", &result, &zrow, &result_type) == FAILURE) { + return; + } + } + if (zrow == NULL) { + row = -1; + } else { + convert_to_long(zrow); + row = Z_LVAL_P(zrow); + if (row < 0) { + php_error_docref(NULL, E_WARNING, "The row parameter must be greater or equal to zero"); + RETURN_FALSE; + } + } + use_row = ZEND_NUM_ARGS() > 1 && row != -1; + + if (!(result_type & PGSQL_BOTH)) { + php_error_docref(NULL, E_WARNING, "Invalid result type"); + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + + if (use_row) { + if (row < 0 || row >= PQntuples(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Unable to jump to row " ZEND_LONG_FMT " on PostgreSQL result index " ZEND_LONG_FMT, + row, Z_LVAL_P(result)); + RETURN_FALSE; + } + pgsql_row = (int)row; + pg_result->row = pgsql_row; + } else { + /* If 2nd param is NULL, use internal row counter to access next row */ + pgsql_row = pg_result->row; + if (pgsql_row < 0 || pgsql_row >= PQntuples(pgsql_result)) { + RETURN_FALSE; + } + pg_result->row++; + } + + array_init(return_value); + for (i = 0, num_fields = PQnfields(pgsql_result); i < num_fields; i++) { + if (PQgetisnull(pgsql_result, pgsql_row, i)) { + if (result_type & PGSQL_NUM) { + add_index_null(return_value, i); + } + if (result_type & PGSQL_ASSOC) { + field_name = PQfname(pgsql_result, i); + add_assoc_null(return_value, field_name); + } + } else { + char *element = PQgetvalue(pgsql_result, pgsql_row, i); + if (element) { + const size_t element_len = strlen(element); + + if (result_type & PGSQL_NUM) { + add_index_stringl(return_value, i, element, element_len); + } + + if (result_type & PGSQL_ASSOC) { + field_name = PQfname(pgsql_result, i); + add_assoc_stringl(return_value, field_name, element, element_len); + } + } + } + } + + if (into_object) { + zval dataset; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zval retval; + + ZVAL_COPY_VALUE(&dataset, return_value); + object_init_ex(return_value, ce); + if (!ce->default_properties_count && !ce->__set) { + Z_OBJ_P(return_value)->properties = Z_ARR(dataset); + } else { + zend_merge_properties(return_value, Z_ARRVAL(dataset)); + zval_ptr_dtor(&dataset); + } + + if (ce->constructor) { + fci.size = sizeof(fci); + ZVAL_UNDEF(&fci.function_name); + fci.object = Z_OBJ_P(return_value); + fci.retval = &retval; + fci.params = NULL; + fci.param_count = 0; + fci.no_separation = 1; + + if (ctor_params && Z_TYPE_P(ctor_params) != IS_NULL) { + if (zend_fcall_info_args(&fci, ctor_params) == FAILURE) { + /* Two problems why we throw exceptions here: PHP is typeless + * and hence passing one argument that's not an array could be + * by mistake and the other way round is possible, too. The + * single value is an array. Also we'd have to make that one + * argument passed by reference. + */ + zend_throw_exception(zend_ce_exception, "Parameter ctor_params must be an array", 0); + return; + } + } + + fcc.function_handler = ce->constructor; + fcc.called_scope = Z_OBJCE_P(return_value); + fcc.object = Z_OBJ_P(return_value); + + if (zend_call_function(&fci, &fcc) == FAILURE) { + zend_throw_exception_ex(zend_ce_exception, 0, "Could not execute %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(ce->constructor->common.function_name)); + } else { + zval_ptr_dtor(&retval); + } + if (fci.params) { + efree(fci.params); + } + } else if (ctor_params) { + zend_throw_exception_ex(zend_ce_exception, 0, "Class %s does not have a constructor hence you cannot use ctor_params", ZSTR_VAL(ce->name)); + } + } +} +/* }}} */ + +/* {{{ proto array pg_fetch_row(resource result [, int row [, int result_type]]) + Get a row as an enumerated array */ +PHP_FUNCTION(pg_fetch_row) +{ + php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_NUM, 0); +} +/* }}} */ + +/* {{{ proto array pg_fetch_assoc(resource result [, int row]) + Fetch a row as an assoc array */ +PHP_FUNCTION(pg_fetch_assoc) +{ + /* pg_fetch_assoc() is added from PHP 4.3.0. It should raise error, when + there is 3rd parameter */ + if (ZEND_NUM_ARGS() > 2) + WRONG_PARAM_COUNT; + php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 0); +} +/* }}} */ + +/* {{{ proto array pg_fetch_array(resource result [, int row [, int result_type]]) + Fetch a row as an array */ +PHP_FUNCTION(pg_fetch_array) +{ + php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_BOTH, 0); +} +/* }}} */ + +/* {{{ proto object pg_fetch_object(resource result [, int row [, string class_name [, NULL|array ctor_params]]]) + Fetch a row as an object */ +PHP_FUNCTION(pg_fetch_object) +{ + /* pg_fetch_object() allowed result_type used to be. 3rd parameter + must be allowed for compatibility */ + php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 1); +} +/* }}} */ + +/* {{{ proto array pg_fetch_all(resource result [, int result_type]) + Fetch all rows into array */ +PHP_FUNCTION(pg_fetch_all) +{ + zval *result; + long result_type = PGSQL_ASSOC; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &result, &result_type) == FAILURE) { + return; + } + + if (!(result_type & PGSQL_BOTH)) { + php_error_docref(NULL, E_WARNING, "Invalid result type"); + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + array_init(return_value); + if (php_pgsql_result2array(pgsql_result, return_value, result_type) == FAILURE) { + zend_array_destroy(Z_ARR_P(return_value)); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array pg_fetch_all_columns(resource result [, int column_number]) + Fetch all rows into array */ +PHP_FUNCTION(pg_fetch_all_columns) +{ + zval *result; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + zend_long colno=0; + int pg_numrows, pg_row; + size_t num_fields; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &result, &colno) == FAILURE) { + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + + num_fields = PQnfields(pgsql_result); + if (colno >= (zend_long)num_fields || colno < 0) { + php_error_docref(NULL, E_WARNING, "Invalid column number '" ZEND_LONG_FMT "'", colno); + RETURN_FALSE; + } + + array_init(return_value); + + if ((pg_numrows = PQntuples(pgsql_result)) <= 0) { + return; + } + + for (pg_row = 0; pg_row < pg_numrows; pg_row++) { + if (PQgetisnull(pgsql_result, pg_row, (int)colno)) { + add_next_index_null(return_value); + } else { + add_next_index_string(return_value, PQgetvalue(pgsql_result, pg_row, (int)colno)); + } + } +} +/* }}} */ + +/* {{{ proto bool pg_result_seek(resource result, int offset) + Set internal row offset */ +PHP_FUNCTION(pg_result_seek) +{ + zval *result; + zend_long row; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &result, &row) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + if (row < 0 || row >= PQntuples(pg_result->result)) { + RETURN_FALSE; + } + + /* seek to offset */ + pg_result->row = (int)row; + RETURN_TRUE; +} +/* }}} */ + +#define PHP_PG_DATA_LENGTH 1 +#define PHP_PG_DATA_ISNULL 2 + +/* {{{ php_pgsql_data_info + */ +static void php_pgsql_data_info(INTERNAL_FUNCTION_PARAMETERS, int entry_type) +{ + zval *result, *field; + zend_long row; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + int field_offset, pgsql_row, argc = ZEND_NUM_ARGS(); + + if (argc == 2) { + if (zend_parse_parameters(argc, "rz", &result, &field) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(argc, "rlz", &result, &row, &field) == FAILURE) { + return; + } + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + if (argc == 2) { + if (pg_result->row < 0) { + pg_result->row = 0; + } + pgsql_row = pg_result->row; + if (pgsql_row < 0 || pgsql_row >= PQntuples(pgsql_result)) { + RETURN_FALSE; + } + } else { + if (row < 0 || row >= PQntuples(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Unable to jump to row " ZEND_LONG_FMT " on PostgreSQL result index " ZEND_LONG_FMT, + row, Z_LVAL_P(result)); + RETURN_FALSE; + } + pgsql_row = (int)row; + } + + switch (Z_TYPE_P(field)) { + case IS_STRING: + field_offset = PQfnumber(pgsql_result, Z_STRVAL_P(field)); + if (field_offset < 0 || field_offset >= PQnfields(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Bad column offset specified"); + RETURN_FALSE; + } + break; + default: + convert_to_long_ex(field); + if (Z_LVAL_P(field) < 0 || Z_LVAL_P(field) >= PQnfields(pgsql_result)) { + php_error_docref(NULL, E_WARNING, "Bad column offset specified"); + RETURN_FALSE; + } + field_offset = (int)Z_LVAL_P(field); + break; + } + + switch (entry_type) { + case PHP_PG_DATA_LENGTH: + RETVAL_LONG(PQgetlength(pgsql_result, pgsql_row, field_offset)); + break; + case PHP_PG_DATA_ISNULL: + RETVAL_LONG(PQgetisnull(pgsql_result, pgsql_row, field_offset)); + break; + } +} +/* }}} */ + +/* {{{ proto int pg_field_prtlen(resource result, [int row,] mixed field_name_or_number) + Returns the printed length */ +PHP_FUNCTION(pg_field_prtlen) +{ + php_pgsql_data_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_PG_DATA_LENGTH); +} +/* }}} */ + +/* {{{ proto int pg_field_is_null(resource result, [int row,] mixed field_name_or_number) + Test if a field is NULL */ +PHP_FUNCTION(pg_field_is_null) +{ + php_pgsql_data_info(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_PG_DATA_ISNULL); +} +/* }}} */ + +/* {{{ proto bool pg_free_result(resource result) + Free result memory */ +PHP_FUNCTION(pg_free_result) +{ + zval *result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &result) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + zend_list_close(Z_RES_P(result)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string pg_last_oid(resource result) + Returns the last object identifier */ +PHP_FUNCTION(pg_last_oid) +{ + zval *result; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; +#ifdef HAVE_PQOIDVALUE + Oid oid; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &result) == FAILURE) { + return; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; +#ifdef HAVE_PQOIDVALUE + oid = PQoidValue(pgsql_result); + if (oid == InvalidOid) { + RETURN_FALSE; + } + PGSQL_RETURN_OID(oid); +#else + Z_STRVAL_P(return_value) = (char *) PQoidStatus(pgsql_result); + if (Z_STRVAL_P(return_value)) { + RETURN_STRING(Z_STRVAL_P(return_value)); + } + RETURN_EMPTY_STRING(); +#endif +} +/* }}} */ + +/* {{{ proto bool pg_trace(string filename [, string mode [, resource connection]]) + Enable tracing a PostgreSQL connection */ +PHP_FUNCTION(pg_trace) +{ + char *z_filename, *mode = "w"; + size_t z_filename_len, mode_len; + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + FILE *fp = NULL; + php_stream *stream; + zend_resource *link; + + if (zend_parse_parameters(argc, "p|sr", &z_filename, &z_filename_len, &mode, &mode_len, &pgsql_link) == FAILURE) { + return; + } + + if (argc < 3) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + stream = php_stream_open_wrapper(z_filename, mode, REPORT_ERRORS, NULL); + + if (!stream) { + RETURN_FALSE; + } + + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS)) { + php_stream_close(stream); + RETURN_FALSE; + } + php_stream_auto_cleanup(stream); + PQtrace(pgsql, fp); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool pg_untrace([resource connection]) + Disable tracing of a PostgreSQL connection */ +PHP_FUNCTION(pg_untrace) +{ + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + zend_resource *link; + + if (zend_parse_parameters(argc, "|r", &pgsql_link) == FAILURE) { + return; + } + + if (argc == 0) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + PQuntrace(pgsql); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto mixed pg_lo_create([resource connection],[mixed large_object_oid]) + Create a large object */ +PHP_FUNCTION(pg_lo_create) +{ + zval *pgsql_link = NULL, *oid = NULL; + PGconn *pgsql; + Oid pgsql_oid, wanted_oid = InvalidOid; + int argc = ZEND_NUM_ARGS(); + zend_resource *link; + + if (zend_parse_parameters(argc, "|zz", &pgsql_link, &oid) == FAILURE) { + return; + } + + if ((argc == 1) && (Z_TYPE_P(pgsql_link) != IS_RESOURCE)) { + oid = pgsql_link; + pgsql_link = NULL; + } + + if (pgsql_link == NULL) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else if ((Z_TYPE_P(pgsql_link) == IS_RESOURCE)) { + link = Z_RES_P(pgsql_link); + } else { + link = NULL; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (oid) { +#ifndef HAVE_PG_LO_CREATE + php_error_docref(NULL, E_NOTICE, "Passing OID value is not supported. Upgrade your PostgreSQL"); +#else + switch (Z_TYPE_P(oid)) { + case IS_STRING: + { + char *end_ptr; + wanted_oid = (Oid)strtoul(Z_STRVAL_P(oid), &end_ptr, 10); + if ((Z_STRVAL_P(oid)+Z_STRLEN_P(oid)) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + } + break; + case IS_LONG: + if (Z_LVAL_P(oid) < (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + wanted_oid = (Oid)Z_LVAL_P(oid); + break; + default: + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + if ((pgsql_oid = lo_create(pgsql, wanted_oid)) == InvalidOid) { + php_error_docref(NULL, E_WARNING, "Unable to create PostgreSQL large object"); + RETURN_FALSE; + } + + PGSQL_RETURN_OID(pgsql_oid); +#endif + } + + if ((pgsql_oid = lo_creat(pgsql, INV_READ|INV_WRITE)) == InvalidOid) { + php_error_docref(NULL, E_WARNING, "Unable to create PostgreSQL large object"); + RETURN_FALSE; + } + + PGSQL_RETURN_OID(pgsql_oid); +} +/* }}} */ + +/* {{{ proto bool pg_lo_unlink([resource connection,] string large_object_oid) + Delete a large object */ +PHP_FUNCTION(pg_lo_unlink) +{ + zval *pgsql_link = NULL; + zend_long oid_long; + char *oid_string, *end_ptr; + size_t oid_strlen; + PGconn *pgsql; + Oid oid; + zend_resource *link; + int argc = ZEND_NUM_ARGS(); + + /* accept string type since Oid type is unsigned int */ + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rs", &pgsql_link, &oid_string, &oid_strlen) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rl", &pgsql_link, &oid_long) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "s", &oid_string, &oid_strlen) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "l", &oid_long) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID is specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else { + php_error_docref(NULL, E_WARNING, "Requires 1 or 2 arguments"); + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (lo_unlink(pgsql, oid) == -1) { + php_error_docref(NULL, E_WARNING, "Unable to delete PostgreSQL large object %u", oid); + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource pg_lo_open([resource connection,] int large_object_oid, string mode) + Open a large object and return fd */ +PHP_FUNCTION(pg_lo_open) +{ + zval *pgsql_link = NULL; + zend_long oid_long; + char *oid_string, *end_ptr, *mode_string; + size_t oid_strlen, mode_strlen; + PGconn *pgsql; + Oid oid; + int pgsql_mode=0, pgsql_lofd; + int create = 0; + pgLofp *pgsql_lofp; + int argc = ZEND_NUM_ARGS(); + zend_resource *link; + + /* accept string type since Oid is unsigned int */ + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rss", &pgsql_link, &oid_string, &oid_strlen, &mode_string, &mode_strlen) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rls", &pgsql_link, &oid_long, &mode_string, &mode_strlen) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "ss", &oid_string, &oid_strlen, &mode_string, &mode_strlen) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "ls", &oid_long, &mode_string, &mode_strlen) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else { + php_error_docref(NULL, E_WARNING, "Requires 1 or 2 arguments"); + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + /* r/w/+ is little bit more PHP-like than INV_READ/INV_WRITE and a lot of + faster to type. Unfortunately, doesn't behave the same way as fopen()... + (Jouni) + */ + + if (strchr(mode_string, 'r') == mode_string) { + pgsql_mode |= INV_READ; + if (strchr(mode_string, '+') == mode_string+1) { + pgsql_mode |= INV_WRITE; + } + } + if (strchr(mode_string, 'w') == mode_string) { + pgsql_mode |= INV_WRITE; + create = 1; + if (strchr(mode_string, '+') == mode_string+1) { + pgsql_mode |= INV_READ; + } + } + + pgsql_lofp = (pgLofp *) emalloc(sizeof(pgLofp)); + + if ((pgsql_lofd = lo_open(pgsql, oid, pgsql_mode)) == -1) { + if (create) { + if ((oid = lo_creat(pgsql, INV_READ|INV_WRITE)) == 0) { + efree(pgsql_lofp); + php_error_docref(NULL, E_WARNING, "Unable to create PostgreSQL large object"); + RETURN_FALSE; + } else { + if ((pgsql_lofd = lo_open(pgsql, oid, pgsql_mode)) == -1) { + if (lo_unlink(pgsql, oid) == -1) { + efree(pgsql_lofp); + php_error_docref(NULL, E_WARNING, "Something is really messed up! Your database is badly corrupted in a way NOT related to PHP"); + RETURN_FALSE; + } + efree(pgsql_lofp); + php_error_docref(NULL, E_WARNING, "Unable to open PostgreSQL large object"); + RETURN_FALSE; + } else { + pgsql_lofp->conn = pgsql; + pgsql_lofp->lofd = pgsql_lofd; + RETURN_RES(zend_register_resource(pgsql_lofp, le_lofp)); + } + } + } else { + efree(pgsql_lofp); + php_error_docref(NULL, E_WARNING, "Unable to open PostgreSQL large object"); + RETURN_FALSE; + } + } else { + pgsql_lofp->conn = pgsql; + pgsql_lofp->lofd = pgsql_lofd; + RETURN_RES(zend_register_resource(pgsql_lofp, le_lofp)); + } +} +/* }}} */ + +/* {{{ proto bool pg_lo_close(resource large_object) + Close a large object */ +PHP_FUNCTION(pg_lo_close) +{ + zval *pgsql_lofp; + pgLofp *pgsql; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_lofp) == FAILURE) { + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_lofp), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + + if (lo_close((PGconn *)pgsql->conn, pgsql->lofd) < 0) { + php_error_docref(NULL, E_WARNING, "Unable to close PostgreSQL large object descriptor %d", pgsql->lofd); + RETVAL_FALSE; + } else { + RETVAL_TRUE; + } + + zend_list_close(Z_RES_P(pgsql_lofp)); + return; +} +/* }}} */ + +#define PGSQL_LO_READ_BUF_SIZE 8192 + +/* {{{ proto string pg_lo_read(resource large_object [, int len]) + Read a large object */ +PHP_FUNCTION(pg_lo_read) +{ + zval *pgsql_id; + zend_long len; + size_t buf_len = PGSQL_LO_READ_BUF_SIZE; + int nbytes, argc = ZEND_NUM_ARGS(); + zend_string *buf; + pgLofp *pgsql; + + if (zend_parse_parameters(argc, "r|l", &pgsql_id, &len) == FAILURE) { + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + buf_len = len < 0 ? 0 : len; + } + + buf = zend_string_alloc(buf_len, 0); + if ((nbytes = lo_read((PGconn *)pgsql->conn, pgsql->lofd, ZSTR_VAL(buf), ZSTR_LEN(buf)))<0) { + zend_string_efree(buf); + RETURN_FALSE; + } + + ZSTR_LEN(buf) = nbytes; + ZSTR_VAL(buf)[ZSTR_LEN(buf)] = '\0'; + RETURN_NEW_STR(buf); +} +/* }}} */ + +/* {{{ proto int pg_lo_write(resource large_object, string buf [, int len]) + Write a large object */ +PHP_FUNCTION(pg_lo_write) +{ + zval *pgsql_id; + char *str; + zend_long z_len; + size_t str_len, nbytes; + size_t len; + pgLofp *pgsql; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rs|l", &pgsql_id, &str, &str_len, &z_len) == FAILURE) { + return; + } + + if (argc > 2) { + if (z_len > (zend_long)str_len) { + php_error_docref(NULL, E_WARNING, "Cannot write more than buffer size %zu. Tried to write " ZEND_LONG_FMT, str_len, z_len); + RETURN_FALSE; + } + if (z_len < 0) { + php_error_docref(NULL, E_WARNING, "Buffer size must be larger than 0, but " ZEND_LONG_FMT " was specified", z_len); + RETURN_FALSE; + } + len = z_len; + } + else { + len = str_len; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + + if ((nbytes = lo_write((PGconn *)pgsql->conn, pgsql->lofd, str, len)) == (size_t)-1) { + RETURN_FALSE; + } + + RETURN_LONG(nbytes); +} +/* }}} */ + +/* {{{ proto int pg_lo_read_all(resource large_object) + Read a large object and send straight to browser */ +PHP_FUNCTION(pg_lo_read_all) +{ + zval *pgsql_id; + int tbytes; + volatile int nbytes; + char buf[PGSQL_LO_READ_BUF_SIZE]; + pgLofp *pgsql; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_id) == FAILURE) { + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + + tbytes = 0; + while ((nbytes = lo_read((PGconn *)pgsql->conn, pgsql->lofd, buf, PGSQL_LO_READ_BUF_SIZE))>0) { + PHPWRITE(buf, nbytes); + tbytes += nbytes; + } + RETURN_LONG(tbytes); +} +/* }}} */ + +/* {{{ proto int pg_lo_import([resource connection, ] string filename [, mixed oid]) + Import large object direct from filesystem */ +PHP_FUNCTION(pg_lo_import) +{ + zval *pgsql_link = NULL, *oid = NULL; + char *file_in; + size_t name_len; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + Oid returned_oid; + zend_resource *link; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rp|z", &pgsql_link, &file_in, &name_len, &oid) == SUCCESS) { + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "p|z", &file_in, &name_len, &oid) == SUCCESS) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + /* old calling convention, deprecated since PHP 4.2 */ + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "pr", &file_in, &name_len, &pgsql_link ) == SUCCESS) { + php_error_docref(NULL, E_NOTICE, "Old API is used"); + link = Z_RES_P(pgsql_link); + } + else { + WRONG_PARAM_COUNT; + } + + if (php_check_open_basedir(file_in)) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (oid) { +#ifndef HAVE_PG_LO_IMPORT_WITH_OID + php_error_docref(NULL, E_NOTICE, "OID value passing not supported"); +#else + Oid wanted_oid; + switch (Z_TYPE_P(oid)) { + case IS_STRING: + { + char *end_ptr; + wanted_oid = (Oid)strtoul(Z_STRVAL_P(oid), &end_ptr, 10); + if ((Z_STRVAL_P(oid)+Z_STRLEN_P(oid)) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + } + break; + case IS_LONG: + if (Z_LVAL_P(oid) < (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + wanted_oid = (Oid)Z_LVAL_P(oid); + break; + default: + php_error_docref(NULL, E_NOTICE, "invalid OID value passed"); + RETURN_FALSE; + } + + returned_oid = lo_import_with_oid(pgsql, file_in, wanted_oid); + + if (returned_oid == InvalidOid) { + RETURN_FALSE; + } + + PGSQL_RETURN_OID(returned_oid); +#endif + } + + returned_oid = lo_import(pgsql, file_in); + + if (returned_oid == InvalidOid) { + RETURN_FALSE; + } + PGSQL_RETURN_OID(returned_oid); +} +/* }}} */ + +/* {{{ proto bool pg_lo_export([resource connection, ] int objoid, string filename) + Export large object direct to filesystem */ +PHP_FUNCTION(pg_lo_export) +{ + zval *pgsql_link = NULL; + char *file_out, *oid_string, *end_ptr; + size_t oid_strlen; + size_t name_len; + zend_long oid_long; + Oid oid; + PGconn *pgsql; + int argc = ZEND_NUM_ARGS(); + zend_resource *link; + + /* allow string to handle large OID value correctly */ + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rlp", &pgsql_link, &oid_long, &file_out, &name_len) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "rss", &pgsql_link, &oid_string, &oid_strlen, &file_out, &name_len) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "lp", &oid_long, &file_out, &name_len) == SUCCESS) { + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "sp", &oid_string, &oid_strlen, &file_out, &name_len) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "spr", &oid_string, &oid_strlen, &file_out, &name_len, &pgsql_link) == SUCCESS) { + oid = (Oid)strtoul(oid_string, &end_ptr, 10); + if ((oid_string+oid_strlen) != end_ptr) { + /* wrong integer format */ + php_error_docref(NULL, E_NOTICE, "Wrong OID value passed"); + RETURN_FALSE; + } + link = Z_RES_P(pgsql_link); + } + else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, argc, + "lpr", &oid_long, &file_out, &name_len, &pgsql_link) == SUCCESS) { + php_error_docref(NULL, E_NOTICE, "Old API is used"); + if (oid_long <= (zend_long)InvalidOid) { + php_error_docref(NULL, E_NOTICE, "Invalid OID specified"); + RETURN_FALSE; + } + oid = (Oid)oid_long; + link = Z_RES_P(pgsql_link); + } + else { + php_error_docref(NULL, E_WARNING, "Requires 2 or 3 arguments"); + RETURN_FALSE; + } + + if (php_check_open_basedir(file_out)) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (lo_export(pgsql, oid, file_out) == -1) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool pg_lo_seek(resource large_object, int offset [, int whence]) + Seeks position of large object */ +PHP_FUNCTION(pg_lo_seek) +{ + zval *pgsql_id = NULL; + zend_long result, offset = 0, whence = SEEK_CUR; + pgLofp *pgsql; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &pgsql_id, &offset, &whence) == FAILURE) { + return; + } + if (whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END) { + php_error_docref(NULL, E_WARNING, "Invalid whence parameter"); + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + +#if HAVE_PG_LO64 + if (PQserverVersion((PGconn *)pgsql->conn) >= 90300) { + result = lo_lseek64((PGconn *)pgsql->conn, pgsql->lofd, offset, (int)whence); + } else { + result = lo_lseek((PGconn *)pgsql->conn, pgsql->lofd, (int)offset, (int)whence); + } +#else + result = lo_lseek((PGconn *)pgsql->conn, pgsql->lofd, offset, whence); +#endif + if (result > -1) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int pg_lo_tell(resource large_object) + Returns current position of large object */ +PHP_FUNCTION(pg_lo_tell) +{ + zval *pgsql_id = NULL; + zend_long offset = 0; + pgLofp *pgsql; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r", &pgsql_id) == FAILURE) { + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + +#if HAVE_PG_LO64 + if (PQserverVersion((PGconn *)pgsql->conn) >= 90300) { + offset = lo_tell64((PGconn *)pgsql->conn, pgsql->lofd); + } else { + offset = lo_tell((PGconn *)pgsql->conn, pgsql->lofd); + } +#else + offset = lo_tell((PGconn *)pgsql->conn, pgsql->lofd); +#endif + RETURN_LONG(offset); +} +/* }}} */ + +#if HAVE_PG_LO_TRUNCATE +/* {{{ proto bool pg_lo_truncate(resource large_object, int size) + Truncate large object to size */ +PHP_FUNCTION(pg_lo_truncate) +{ + zval *pgsql_id = NULL; + size_t size; + pgLofp *pgsql; + int argc = ZEND_NUM_ARGS(); + int result; + + if (zend_parse_parameters(argc, "rl", &pgsql_id, &size) == FAILURE) { + return; + } + + if ((pgsql = (pgLofp *)zend_fetch_resource(Z_RES_P(pgsql_id), "PostgreSQL large object", le_lofp)) == NULL) { + RETURN_FALSE; + } + +#if HAVE_PG_LO64 + if (PQserverVersion((PGconn *)pgsql->conn) >= 90300) { + result = lo_truncate64((PGconn *)pgsql->conn, pgsql->lofd, size); + } else { + result = lo_truncate((PGconn *)pgsql->conn, pgsql->lofd, size); + } +#else + result = lo_truncate((PGconn *)pgsql->conn, pgsql->lofd, size); +#endif + if (!result) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ +#endif + +#if HAVE_PQSETERRORVERBOSITY +/* {{{ proto int pg_set_error_verbosity([resource connection,] int verbosity) + Set error verbosity */ +PHP_FUNCTION(pg_set_error_verbosity) +{ + zval *pgsql_link = NULL; + zend_long verbosity; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + zend_resource *link; + + if (argc == 1) { + if (zend_parse_parameters(argc, "l", &verbosity) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rl", &pgsql_link, &verbosity) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (verbosity & (PQERRORS_TERSE|PQERRORS_DEFAULT|PQERRORS_VERBOSE)) { + RETURN_LONG(PQsetErrorVerbosity(pgsql, verbosity)); + } else { + RETURN_FALSE; + } +} +/* }}} */ +#endif + +#ifdef HAVE_PQCLIENTENCODING +/* {{{ proto int pg_set_client_encoding([resource connection,] string encoding) + Set client encoding */ +PHP_FUNCTION(pg_set_client_encoding) +{ + char *encoding; + size_t encoding_len; + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + zend_resource *link; + + if (argc == 1) { + if (zend_parse_parameters(argc, "s", &encoding, &encoding_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rs", &pgsql_link, &encoding, &encoding_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(PQsetClientEncoding(pgsql, encoding)); +} +/* }}} */ + +/* {{{ proto string pg_client_encoding([resource connection]) + Get the current client encoding */ +PHP_FUNCTION(pg_client_encoding) +{ + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + zend_resource *link; + + if (zend_parse_parameters(argc, "|r", &pgsql_link) == FAILURE) { + return; + } + + if (argc == 0) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + /* Just do the same as found in PostgreSQL sources... */ + + RETURN_STRING((char *) pg_encoding_to_char(PQclientEncoding(pgsql))); +} +/* }}} */ +#endif + +#if !HAVE_PQGETCOPYDATA +#define COPYBUFSIZ 8192 +#endif + +/* {{{ proto bool pg_end_copy([resource connection]) + Sync with backend. Completes the Copy command */ +PHP_FUNCTION(pg_end_copy) +{ + zval *pgsql_link = NULL; + int argc = ZEND_NUM_ARGS(); + PGconn *pgsql; + int result = 0; + zend_resource *link; + + if (zend_parse_parameters(argc, "|r", &pgsql_link) == FAILURE) { + return; + } + + if (argc == 0) { + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + result = PQendcopy(pgsql); + + if (result!=0) { + PHP_PQ_ERROR("Query failed: %s", pgsql); + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool pg_put_line([resource connection,] string query) + Send null-terminated string to backend server*/ +PHP_FUNCTION(pg_put_line) +{ + char *query; + zval *pgsql_link = NULL; + size_t query_len; + PGconn *pgsql; + zend_resource *link; + int result = 0, argc = ZEND_NUM_ARGS(); + + if (argc == 1) { + if (zend_parse_parameters(argc, "s", &query, &query_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + } else { + if (zend_parse_parameters(argc, "rs", &pgsql_link, &query, &query_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + result = PQputline(pgsql, query); + if (result==EOF) { + PHP_PQ_ERROR("Query failed: %s", pgsql); + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array pg_copy_to(resource connection, string table_name [, string delimiter [, string null_as]]) + Copy table to array */ +PHP_FUNCTION(pg_copy_to) +{ + zval *pgsql_link; + char *table_name, *pg_delim = NULL, *pg_null_as = NULL; + size_t table_name_len, pg_delim_len, pg_null_as_len, free_pg_null = 0; + char *query; + PGconn *pgsql; + PGresult *pgsql_result; + ExecStatusType status; + char *csv = (char *)NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rs|ss", + &pgsql_link, &table_name, &table_name_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (!pg_delim) { + pg_delim = "\t"; + } + if (!pg_null_as) { + pg_null_as = estrdup("\\\\N"); + free_pg_null = 1; + } + + spprintf(&query, 0, "COPY %s TO STDOUT DELIMITER E'%c' NULL AS E'%s'", table_name, *pg_delim, pg_null_as); + + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(pgsql, query); + if (free_pg_null) { + efree(pg_null_as); + } + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + switch (status) { + case PGRES_COPY_OUT: + if (pgsql_result) { + int copydone = 0; +#if !HAVE_PQGETCOPYDATA + char copybuf[COPYBUFSIZ]; +#endif + + PQclear(pgsql_result); + array_init(return_value); +#if HAVE_PQGETCOPYDATA + while (!copydone) + { + int ret = PQgetCopyData(pgsql, &csv, 0); + switch (ret) { + case -1: + copydone = 1; + break; + case 0: + case -2: + PHP_PQ_ERROR("getline failed: %s", pgsql); + RETURN_FALSE; + break; + default: + add_next_index_string(return_value, csv); + PQfreemem(csv); + break; + } + } +#else + while (!copydone) + { + if ((ret = PQgetline(pgsql, copybuf, COPYBUFSIZ))) { + PHP_PQ_ERROR("getline failed: %s", pgsql); + RETURN_FALSE; + } + + if (copybuf[0] == '\\' && + copybuf[1] == '.' && + copybuf[2] == '\0') + { + copydone = 1; + } + else + { + if (csv == (char *)NULL) { + csv = estrdup(copybuf); + } else { + csv = (char *)erealloc(csv, strlen(csv) + sizeof(char)*(COPYBUFSIZ+1)); + strcat(csv, copybuf); + } + + switch (ret) + { + case EOF: + copydone = 1; + case 0: + add_next_index_string(return_value, csv); + efree(csv); + csv = (char *)NULL; + break; + case 1: + break; + } + } + } + if (PQendcopy(pgsql)) { + PHP_PQ_ERROR("endcopy failed: %s", pgsql); + RETURN_FALSE; + } +#endif + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + } + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + break; + default: + PQclear(pgsql_result); + PHP_PQ_ERROR("Copy command failed: %s", pgsql); + RETURN_FALSE; + break; + } +} +/* }}} */ + +/* {{{ proto bool pg_copy_from(resource connection, string table_name , array rows [, string delimiter [, string null_as]]) + Copy table from array */ +PHP_FUNCTION(pg_copy_from) +{ + zval *pgsql_link = NULL, *pg_rows; + zval *value; + char *table_name, *pg_delim = NULL, *pg_null_as = NULL; + size_t table_name_len, pg_delim_len, pg_null_as_len; + int pg_null_as_free = 0; + char *query; + PGconn *pgsql; + PGresult *pgsql_result; + ExecStatusType status; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rsa|ss", + &pgsql_link, &table_name, &table_name_len, &pg_rows, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (!pg_delim) { + pg_delim = "\t"; + } + if (!pg_null_as) { + pg_null_as = estrdup("\\\\N"); + pg_null_as_free = 1; + } + + spprintf(&query, 0, "COPY %s FROM STDIN DELIMITER E'%c' NULL AS E'%s'", table_name, *pg_delim, pg_null_as); + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(pgsql, query); + + if (pg_null_as_free) { + efree(pg_null_as); + } + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(pgsql); + } + + switch (status) { + case PGRES_COPY_IN: + if (pgsql_result) { + int command_failed = 0; + PQclear(pgsql_result); +#if HAVE_PQPUTCOPYDATA + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) { + zend_string *tmp = zval_try_get_string(value); + if (UNEXPECTED(!tmp)) { + return; + } + query = (char *)emalloc(ZSTR_LEN(tmp) + 2); + strlcpy(query, ZSTR_VAL(tmp), ZSTR_LEN(tmp) + 2); + if (ZSTR_LEN(tmp) > 0 && *(query + ZSTR_LEN(tmp) - 1) != '\n') { + strlcat(query, "\n", ZSTR_LEN(tmp) + 2); + } + if (PQputCopyData(pgsql, query, (int)strlen(query)) != 1) { + efree(query); + zend_string_release(tmp); + PHP_PQ_ERROR("copy failed: %s", pgsql); + RETURN_FALSE; + } + efree(query); + zend_string_release(tmp); + } ZEND_HASH_FOREACH_END(); + + if (PQputCopyEnd(pgsql, NULL) != 1) { + PHP_PQ_ERROR("putcopyend failed: %s", pgsql); + RETURN_FALSE; + } +#else + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) { + zend_string *tmp = zval_try_get_string(value); + if (UNEXPECTED(!tmp)) { + return; + } + query = (char *)emalloc(ZSTR_LEN(tmp) + 2); + strlcpy(query, ZSTR_LVAL(tmp), ZSTR_LEN(tmp) + 2); + if (ZSTR_LEN(tmp) > 0 && *(query + ZSTR_LEN(tmp) - 1) != '\n') { + strlcat(query, "\n", ZSTR_LEN(tmp) + 2); + } + if (PQputline(pgsql, query)==EOF) { + efree(query); + zend_string_release(tmp); + PHP_PQ_ERROR("copy failed: %s", pgsql); + RETURN_FALSE; + } + efree(query); + zend_string_release(tmp); + } ZEND_HASH_FOREACH_END(); + + if (PQputline(pgsql, "\\.\n") == EOF) { + PHP_PQ_ERROR("putline failed: %s", pgsql); + RETURN_FALSE; + } + if (PQendcopy(pgsql)) { + PHP_PQ_ERROR("endcopy failed: %s", pgsql); + RETURN_FALSE; + } +#endif + while ((pgsql_result = PQgetResult(pgsql))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + PHP_PQ_ERROR("Copy command failed: %s", pgsql); + command_failed = 1; + } + PQclear(pgsql_result); + } + if (command_failed) { + RETURN_FALSE; + } + } else { + PQclear(pgsql_result); + RETURN_FALSE; + } + RETURN_TRUE; + break; + default: + PQclear(pgsql_result); + PHP_PQ_ERROR("Copy command failed: %s", pgsql); + RETURN_FALSE; + break; + } +} +/* }}} */ + +#ifdef HAVE_PQESCAPE +/* {{{ proto string pg_escape_string([resource connection,] string data) + Escape string for text/char type */ +PHP_FUNCTION(pg_escape_string) +{ + zend_string *from = NULL, *to = NULL; + zval *pgsql_link; + zend_resource *link; +#ifdef HAVE_PQESCAPE_CONN + PGconn *pgsql; +#endif + + switch (ZEND_NUM_ARGS()) { + case 1: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &from) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + break; + default: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &pgsql_link, &from) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + break; + } + + to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0); +#ifdef HAVE_PQESCAPE_CONN + if (link) { + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL); + } else +#endif + { + ZSTR_LEN(to) = PQescapeString(ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from)); + } + + to = zend_string_truncate(to, ZSTR_LEN(to), 0); + RETURN_NEW_STR(to); +} +/* }}} */ + +/* {{{ proto string pg_escape_bytea([resource connection,] string data) + Escape binary for bytea type */ +PHP_FUNCTION(pg_escape_bytea) +{ + char *from = NULL, *to = NULL; + size_t to_len; + size_t from_len; +#ifdef HAVE_PQESCAPE_BYTEA_CONN + PGconn *pgsql; +#endif + zval *pgsql_link; + zend_resource *link; + + switch (ZEND_NUM_ARGS()) { + case 1: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &from, &from_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + break; + default: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &pgsql_link, &from, &from_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + break; + } + +#ifdef HAVE_PQESCAPE_BYTEA_CONN + if (link) { + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + to = (char *)PQescapeByteaConn(pgsql, (unsigned char *)from, (size_t)from_len, &to_len); + } else +#endif + to = (char *)PQescapeBytea((unsigned char*)from, from_len, &to_len); + + RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */ + PQfreemem(to); +} +/* }}} */ + +#if !HAVE_PQUNESCAPEBYTEA +/* PQunescapeBytea() from PostgreSQL 7.3 to provide bytea unescape feature to 7.2 users. + Renamed to php_pgsql_unescape_bytea() */ +/* + * PQunescapeBytea - converts the null terminated string representation + * of a bytea, strtext, into binary, filling a buffer. It returns a + * pointer to the buffer which is NULL on error, and the size of the + * buffer in retbuflen. The pointer may subsequently be used as an + * argument to the function free(3). It is the reverse of PQescapeBytea. + * + * The following transformations are reversed: + * '\0' == ASCII 0 == \000 + * '\'' == ASCII 39 == \' + * '\\' == ASCII 92 == \\ + * + * States: + * 0 normal 0->1->2->3->4 + * 1 \ 1->5 + * 2 \0 1->6 + * 3 \00 + * 4 \000 + * 5 \' + * 6 \\ + */ +static unsigned char * php_pgsql_unescape_bytea(unsigned char *strtext, size_t *retbuflen) /* {{{ */ +{ + size_t buflen; + unsigned char *buffer, + *sp, + *bp; + unsigned int state = 0; + + if (strtext == NULL) + return NULL; + buflen = strlen(strtext); /* will shrink, also we discover if + * strtext */ + buffer = (unsigned char *) emalloc(buflen); /* isn't NULL terminated */ + for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++) + { + switch (state) + { + case 0: + if (*sp == '\\') + state = 1; + *bp = *sp; + break; + case 1: + if (*sp == '\'') /* state=5 */ + { /* replace \' with 39 */ + bp--; + *bp = '\''; + buflen--; + state = 0; + } + else if (*sp == '\\') /* state=6 */ + { /* replace \\ with 92 */ + bp--; + *bp = '\\'; + buflen--; + state = 0; + } + else + { + if (isdigit(*sp)) + state = 2; + else + state = 0; + *bp = *sp; + } + break; + case 2: + if (isdigit(*sp)) + state = 3; + else + state = 0; + *bp = *sp; + break; + case 3: + if (isdigit(*sp)) /* state=4 */ + { + unsigned char *start, *end, buf[4]; /* 000 + '\0' */ + + bp -= 3; + memcpy(buf, sp-2, 3); + buf[3] = '\0'; + start = buf; + *bp = (unsigned char)strtoul(start, (char **)&end, 8); + buflen -= 3; + state = 0; + } + else + { + *bp = *sp; + state = 0; + } + break; + } + } + buffer = erealloc(buffer, buflen+1); + buffer[buflen] = '\0'; + + *retbuflen = buflen; + return buffer; +} +/* }}} */ +#endif + +/* {{{ proto string pg_unescape_bytea(string data) + Unescape binary for bytea type */ +PHP_FUNCTION(pg_unescape_bytea) +{ + char *from = NULL, *to = NULL, *tmp = NULL; + size_t to_len; + size_t from_len; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &from, &from_len) == FAILURE) { + return; + } + +#if HAVE_PQUNESCAPEBYTEA + tmp = (char *)PQunescapeBytea((unsigned char*)from, &to_len); + to = estrndup(tmp, to_len); + PQfreemem(tmp); +#else + to = (char *)php_pgsql_unescape_bytea((unsigned char*)from, &to_len); +#endif + if (!to) { + php_error_docref(NULL, E_WARNING,"Invalid parameter"); + RETURN_FALSE; + } + RETVAL_STRINGL(to, to_len); + efree(to); +} +/* }}} */ +#endif + +#ifdef HAVE_PQESCAPE +static void php_pgsql_escape_internal(INTERNAL_FUNCTION_PARAMETERS, int escape_literal) /* {{{ */ { + char *from = NULL; + zval *pgsql_link = NULL; + PGconn *pgsql; + size_t from_len; + char *tmp; + zend_resource *link; + + switch (ZEND_NUM_ARGS()) { + case 1: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &from, &from_len) == FAILURE) { + return; + } + link = FETCH_DEFAULT_LINK(); + CHECK_DEFAULT_LINK(link); + break; + + default: + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &pgsql_link, &from, &from_len) == FAILURE) { + return; + } + link = Z_RES_P(pgsql_link); + break; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (pgsql == NULL) { + php_error_docref(NULL, E_WARNING,"Cannot get pgsql link"); + RETURN_FALSE; + } + + if (escape_literal) { + tmp = PGSQLescapeLiteral(pgsql, from, (size_t)from_len); + } else { + tmp = PGSQLescapeIdentifier(pgsql, from, (size_t)from_len); + } + if (!tmp) { + php_error_docref(NULL, E_WARNING,"Failed to escape"); + RETURN_FALSE; + } + + RETVAL_STRING(tmp); + PGSQLfree(tmp); +} +/* }}} */ + +/* {{{ proto string pg_escape_literal([resource connection,] string data) + Escape parameter as string literal (i.e. parameter) */ +PHP_FUNCTION(pg_escape_literal) +{ + php_pgsql_escape_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string pg_escape_identifier([resource connection,] string data) + Escape identifier (i.e. table name, field name) */ +PHP_FUNCTION(pg_escape_identifier) +{ + php_pgsql_escape_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ +#endif + +/* {{{ proto string pg_result_error(resource result) + Get error message associated with result */ +PHP_FUNCTION(pg_result_error) +{ + zval *result; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + char *err = NULL; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &result) == FAILURE) { + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + if (!pgsql_result) { + RETURN_FALSE; + } + err = (char *)PQresultErrorMessage(pgsql_result); + RETURN_STRING(err); +} +/* }}} */ + +#if HAVE_PQRESULTERRORFIELD +/* {{{ proto string pg_result_error_field(resource result, int fieldcode) + Get error message field associated with result */ +PHP_FUNCTION(pg_result_error_field) +{ + zval *result; + zend_long fieldcode; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + char *field = NULL; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "rl", + &result, &fieldcode) == FAILURE) { + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + if (!pgsql_result) { + RETURN_FALSE; + } + if (fieldcode & (PG_DIAG_SEVERITY|PG_DIAG_SQLSTATE|PG_DIAG_MESSAGE_PRIMARY|PG_DIAG_MESSAGE_DETAIL + |PG_DIAG_MESSAGE_HINT|PG_DIAG_STATEMENT_POSITION +#if PG_DIAG_INTERNAL_POSITION + |PG_DIAG_INTERNAL_POSITION +#endif +#if PG_DIAG_INTERNAL_QUERY + |PG_DIAG_INTERNAL_QUERY +#endif + |PG_DIAG_CONTEXT|PG_DIAG_SOURCE_FILE|PG_DIAG_SOURCE_LINE + |PG_DIAG_SOURCE_FUNCTION)) { + field = (char *)PQresultErrorField(pgsql_result, (int)fieldcode); + if (field == NULL) { + RETURN_NULL(); + } else { + RETURN_STRING(field); + } + } else { + RETURN_FALSE; + } +} +/* }}} */ +#endif + +/* {{{ proto int pg_connection_status(resource connection) + Get connection status */ +PHP_FUNCTION(pg_connection_status) +{ + zval *pgsql_link = NULL; + PGconn *pgsql; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(PQstatus(pgsql)); +} + +/* }}} */ + +#if HAVE_PGTRANSACTIONSTATUS +/* {{{ proto int pg_transaction_status(resource connection) + Get transaction status */ +PHP_FUNCTION(pg_transaction_status) +{ + zval *pgsql_link = NULL; + PGconn *pgsql; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(PQtransactionStatus(pgsql)); +} +#endif + +/* }}} */ + +/* {{{ proto bool pg_connection_reset(resource connection) + Reset connection (reconnect) */ +PHP_FUNCTION(pg_connection_reset) +{ + zval *pgsql_link; + PGconn *pgsql; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + PQreset(pgsql); + if (PQstatus(pgsql) == CONNECTION_BAD) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +#define PHP_PG_ASYNC_IS_BUSY 1 +#define PHP_PG_ASYNC_REQUEST_CANCEL 2 + +/* {{{ php_pgsql_flush_query + */ +static int php_pgsql_flush_query(PGconn *pgsql) +{ + PGresult *res; + int leftover = 0; + + if (PQ_SETNONBLOCKING(pgsql, 1)) { + php_error_docref(NULL, E_NOTICE,"Cannot set connection to nonblocking mode"); + return -1; + } + while ((res = PQgetResult(pgsql))) { + PQclear(res); + leftover++; + } + PQ_SETNONBLOCKING(pgsql, 0); + return leftover; +} +/* }}} */ + +/* {{{ php_pgsql_do_async + */ +static void php_pgsql_do_async(INTERNAL_FUNCTION_PARAMETERS, int entry_type) +{ + zval *pgsql_link; + PGconn *pgsql; + PGresult *pgsql_result; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (PQ_SETNONBLOCKING(pgsql, 1)) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + switch(entry_type) { + case PHP_PG_ASYNC_IS_BUSY: + PQconsumeInput(pgsql); + RETVAL_LONG(PQisBusy(pgsql)); + break; + case PHP_PG_ASYNC_REQUEST_CANCEL: + RETVAL_LONG(PQrequestCancel(pgsql)); + while ((pgsql_result = PQgetResult(pgsql))) { + PQclear(pgsql_result); + } + break; + default: + php_error_docref(NULL, E_ERROR, "PostgreSQL module error, please report this error"); + break; + } + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to blocking mode"); + } + convert_to_boolean_ex(return_value); +} +/* }}} */ + +/* {{{ proto bool pg_cancel_query(resource connection) + Cancel request */ +PHP_FUNCTION(pg_cancel_query) +{ + php_pgsql_do_async(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_PG_ASYNC_REQUEST_CANCEL); +} +/* }}} */ + +/* {{{ proto bool pg_connection_busy(resource connection) + Get connection is busy or not */ +PHP_FUNCTION(pg_connection_busy) +{ + php_pgsql_do_async(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_PG_ASYNC_IS_BUSY); +} +/* }}} */ + +static int _php_pgsql_link_has_results(PGconn *pgsql) /* {{{ */ +{ + PGresult *result; + while ((result = PQgetResult(pgsql))) { + PQclear(result); + return 1; + } + return 0; +} +/* }}} */ + +/* {{{ proto bool pg_send_query(resource connection, string query) + Send asynchronous query */ +PHP_FUNCTION(pg_send_query) +{ + zval *pgsql_link; + char *query; + size_t len; + PGconn *pgsql; + int is_non_blocking; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &pgsql_link, &query, &len) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + is_non_blocking = PQisnonblocking(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 1) == -1) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + + if (_php_pgsql_link_has_results(pgsql)) { + php_error_docref(NULL, E_NOTICE, + "There are results on this connection. Call pg_get_result() until it returns FALSE"); + } + + if (is_non_blocking) { + if (!PQsendQuery(pgsql, query)) { + RETURN_FALSE; + } + ret = PQflush(pgsql); + } else { + if (!PQsendQuery(pgsql, query)) { + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQreset(pgsql); + } + if (!PQsendQuery(pgsql, query)) { + RETURN_FALSE; + } + } + + /* Wait to finish sending buffer */ + while ((ret = PQflush(pgsql))) { + if (ret == -1) { + php_error_docref(NULL, E_NOTICE, "Could not empty PostgreSQL send buffer"); + break; + } + usleep(10000); + } + + if (PQ_SETNONBLOCKING(pgsql, 0)) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to blocking mode"); + } + } + + if (ret == 0) { + RETURN_TRUE; + } else if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(0); + } +} +/* }}} */ + +#if HAVE_PQSENDQUERYPARAMS +/* {{{ proto bool pg_send_query_params(resource connection, string query, array params) + Send asynchronous parameterized query */ +PHP_FUNCTION(pg_send_query_params) +{ + zval *pgsql_link, *pv_param_arr, *tmp; + int num_params = 0; + char **params = NULL; + char *query; + size_t query_len; + PGconn *pgsql; + int is_non_blocking; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rsa", &pgsql_link, &query, &query_len, &pv_param_arr) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + is_non_blocking = PQisnonblocking(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 1) == -1) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + + if (_php_pgsql_link_has_results(pgsql)) { + php_error_docref(NULL, E_NOTICE, + "There are results on this connection. Call pg_get_result() until it returns FALSE"); + } + + num_params = zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)); + if (num_params > 0) { + int i = 0; + params = (char **)safe_emalloc(sizeof(char *), num_params, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { + + if (Z_TYPE_P(tmp) == IS_NULL) { + params[i] = NULL; + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(tmp, &tmp_str); + + params[i] = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + } + + i++; + } ZEND_HASH_FOREACH_END(); + } + + if (PQsendQueryParams(pgsql, query, num_params, NULL, (const char * const *)params, NULL, NULL, 0)) { + _php_pgsql_free_params(params, num_params); + } else if (is_non_blocking) { + _php_pgsql_free_params(params, num_params); + RETURN_FALSE; + } else { + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQreset(pgsql); + } + if (!PQsendQueryParams(pgsql, query, num_params, NULL, (const char * const *)params, NULL, NULL, 0)) { + _php_pgsql_free_params(params, num_params); + RETURN_FALSE; + } + } + + if (is_non_blocking) { + ret = PQflush(pgsql); + } else { + /* Wait to finish sending buffer */ + while ((ret = PQflush(pgsql))) { + if (ret == -1) { + php_error_docref(NULL, E_NOTICE, "Could not empty PostgreSQL send buffer"); + break; + } + usleep(10000); + } + + if (PQ_SETNONBLOCKING(pgsql, 0) != 0) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to blocking mode"); + } + } + + if (ret == 0) { + RETURN_TRUE; + } else if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(0); + } +} +/* }}} */ +#endif + +#if HAVE_PQSENDPREPARE +/* {{{ proto bool pg_send_prepare(resource connection, string stmtname, string query) + Asynchronously prepare a query for future execution */ +PHP_FUNCTION(pg_send_prepare) +{ + zval *pgsql_link; + char *query, *stmtname; + size_t stmtname_len, query_len; + PGconn *pgsql; + int is_non_blocking; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rss", &pgsql_link, &stmtname, &stmtname_len, &query, &query_len) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + is_non_blocking = PQisnonblocking(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 1) == -1) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + + if (_php_pgsql_link_has_results(pgsql)) { + php_error_docref(NULL, E_NOTICE, + "There are results on this connection. Call pg_get_result() until it returns FALSE"); + } + + if (!PQsendPrepare(pgsql, stmtname, query, 0, NULL)) { + if (is_non_blocking) { + RETURN_FALSE; + } else { + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQreset(pgsql); + } + if (!PQsendPrepare(pgsql, stmtname, query, 0, NULL)) { + RETURN_FALSE; + } + } + } + + if (is_non_blocking) { + ret = PQflush(pgsql); + } else { + /* Wait to finish sending buffer */ + while ((ret = PQflush(pgsql))) { + if (ret == -1) { + php_error_docref(NULL, E_NOTICE, "Could not empty PostgreSQL send buffer"); + break; + } + usleep(10000); + } + if (PQ_SETNONBLOCKING(pgsql, 0) != 0) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to blocking mode"); + } + } + + if (ret == 0) { + RETURN_TRUE; + } else if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(0); + } +} +/* }}} */ +#endif + +#if HAVE_PQSENDQUERYPREPARED +/* {{{ proto bool pg_send_execute(resource connection, string stmtname, array params) + Executes prevriously prepared stmtname asynchronously */ +PHP_FUNCTION(pg_send_execute) +{ + zval *pgsql_link; + zval *pv_param_arr, *tmp; + int num_params = 0; + char **params = NULL; + char *stmtname; + size_t stmtname_len; + PGconn *pgsql; + int is_non_blocking; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rsa", &pgsql_link, &stmtname, &stmtname_len, &pv_param_arr) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + is_non_blocking = PQisnonblocking(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 1) == -1) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + + if (_php_pgsql_link_has_results(pgsql)) { + php_error_docref(NULL, E_NOTICE, + "There are results on this connection. Call pg_get_result() until it returns FALSE"); + } + + num_params = zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)); + if (num_params > 0) { + int i = 0; + params = (char **)safe_emalloc(sizeof(char *), num_params, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { + + if (Z_TYPE_P(tmp) == IS_NULL) { + params[i] = NULL; + } else { + zend_string *tmp_str = zval_try_get_string(tmp); + if (UNEXPECTED(!tmp)) { + _php_pgsql_free_params(params, num_params); + return; + } + params[i] = estrndup(ZSTR_VAL(tmp_str), ZSTR_LEN(tmp_str)); + zend_string_release(tmp_str); + } + + i++; + } ZEND_HASH_FOREACH_END(); + } + + if (PQsendQueryPrepared(pgsql, stmtname, num_params, (const char * const *)params, NULL, NULL, 0)) { + _php_pgsql_free_params(params, num_params); + } else if (is_non_blocking) { + _php_pgsql_free_params(params, num_params); + RETURN_FALSE; + } else { + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { + PQreset(pgsql); + } + if (!PQsendQueryPrepared(pgsql, stmtname, num_params, (const char * const *)params, NULL, NULL, 0)) { + _php_pgsql_free_params(params, num_params); + RETURN_FALSE; + } + } + + if (is_non_blocking) { + ret = PQflush(pgsql); + } else { + /* Wait to finish sending buffer */ + while ((ret = PQflush(pgsql))) { + if (ret == -1) { + php_error_docref(NULL, E_NOTICE, "Could not empty PostgreSQL send buffer"); + break; + } + usleep(10000); + } + if (PQ_SETNONBLOCKING(pgsql, 0) != 0) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to blocking mode"); + } + } + + if (ret == 0) { + RETURN_TRUE; + } else if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(0); + } +} +/* }}} */ +#endif + +/* {{{ proto resource pg_get_result(resource connection) + Get asynchronous query result */ +PHP_FUNCTION(pg_get_result) +{ + zval *pgsql_link; + PGconn *pgsql; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = PQgetResult(pgsql); + if (!pgsql_result) { + /* no result */ + RETURN_FALSE; + } + pg_result = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pg_result->conn = pgsql; + pg_result->result = pgsql_result; + pg_result->row = 0; + RETURN_RES(zend_register_resource(pg_result, le_result)); +} +/* }}} */ + +/* {{{ proto mixed pg_result_status(resource result[, int result_type]) + Get status of query result */ +PHP_FUNCTION(pg_result_status) +{ + zval *result; + zend_long result_type = PGSQL_STATUS_LONG; + ExecStatusType status; + PGresult *pgsql_result; + pgsql_result_handle *pg_result; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r|l", + &result, &result_type) == FAILURE) { + RETURN_FALSE; + } + + if ((pg_result = (pgsql_result_handle *)zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { + RETURN_FALSE; + } + + pgsql_result = pg_result->result; + if (result_type == PGSQL_STATUS_LONG) { + status = PQresultStatus(pgsql_result); + RETURN_LONG((int)status); + } + else if (result_type == PGSQL_STATUS_STRING) { + RETURN_STRING(PQcmdStatus(pgsql_result)); + } + else { + php_error_docref(NULL, E_WARNING, "Optional 2nd parameter should be PGSQL_STATUS_LONG or PGSQL_STATUS_STRING"); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto mixed pg_get_notify([resource connection[, int result_type]]) + Get asynchronous notification */ +PHP_FUNCTION(pg_get_notify) +{ + zval *pgsql_link; + zend_long result_type = PGSQL_ASSOC; + PGconn *pgsql; + PGnotify *pgsql_notify; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r|l", + &pgsql_link, &result_type) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (!(result_type & PGSQL_BOTH)) { + php_error_docref(NULL, E_WARNING, "Invalid result type"); + RETURN_FALSE; + } + + PQconsumeInput(pgsql); + pgsql_notify = PQnotifies(pgsql); + if (!pgsql_notify) { + /* no notify message */ + RETURN_FALSE; + } + array_init(return_value); + if (result_type & PGSQL_NUM) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); +#if HAVE_PQPROTOCOLVERSION && HAVE_PQPARAMETERSTATUS + if (PQprotocolVersion(pgsql) >= 3 && atof(PQparameterStatus(pgsql, "server_version")) >= 9.0) { +#else + if (atof(PG_VERSION) >= 9.0) { +#endif +#if HAVE_PQPARAMETERSTATUS + add_index_string(return_value, 2, pgsql_notify->extra); +#endif + } + } + if (result_type & PGSQL_ASSOC) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); +#if HAVE_PQPROTOCOLVERSION && HAVE_PQPARAMETERSTATUS + if (PQprotocolVersion(pgsql) >= 3 && atof(PQparameterStatus(pgsql, "server_version")) >= 9.0) { +#else + if (atof(PG_VERSION) >= 9.0) { +#endif +#if HAVE_PQPARAMETERSTATUS + add_assoc_string(return_value, "payload", pgsql_notify->extra); +#endif + } + } + PQfreemem(pgsql_notify); +} +/* }}} */ + +/* {{{ proto int pg_get_pid([resource connection) + Get backend(server) pid */ +PHP_FUNCTION(pg_get_pid) +{ + zval *pgsql_link; + PGconn *pgsql; + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r", + &pgsql_link) == FAILURE) { + RETURN_FALSE; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(PQbackendPID(pgsql)); +} +/* }}} */ + +static ssize_t php_pgsql_fd_write(php_stream *stream, const char *buf, size_t count) /* {{{ */ +{ + return -1; +} +/* }}} */ + +static ssize_t php_pgsql_fd_read(php_stream *stream, char *buf, size_t count) /* {{{ */ +{ + return -1; +} +/* }}} */ + +static int php_pgsql_fd_close(php_stream *stream, int close_handle) /* {{{ */ +{ + return EOF; +} +/* }}} */ + +static int php_pgsql_fd_flush(php_stream *stream) /* {{{ */ +{ + return FAILURE; +} +/* }}} */ + +static int php_pgsql_fd_set_option(php_stream *stream, int option, int value, void *ptrparam) /* {{{ */ +{ + PGconn *pgsql = (PGconn *) stream->abstract; + switch (option) { + case PHP_STREAM_OPTION_BLOCKING: + return PQ_SETNONBLOCKING(pgsql, value); + default: + return FAILURE; + } +} +/* }}} */ + +static int php_pgsql_fd_cast(php_stream *stream, int cast_as, void **ret) /* {{{ */ +{ + PGconn *pgsql = (PGconn *) stream->abstract; + + switch (cast_as) { + case PHP_STREAM_AS_FD_FOR_SELECT: + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (ret) { + int fd_number = PQsocket(pgsql); + if (fd_number == -1) { + return FAILURE; + } + + *(php_socket_t *)ret = fd_number; + return SUCCESS; + } + default: + return FAILURE; + } +} +/* }}} */ + +/* {{{ proto resource pg_socket(resource connection) + Get a read-only handle to the socket underlying the pgsql connection */ +PHP_FUNCTION(pg_socket) +{ + zval *pgsql_link; + php_stream *stream; + PGconn *pgsql; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_link) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + stream = php_stream_alloc(&php_stream_pgsql_fd_ops, pgsql, NULL, "r"); + + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool pg_consume_input(resource connection) + Reads input on the connection */ +PHP_FUNCTION(pg_consume_input) +{ + zval *pgsql_link; + PGconn *pgsql; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_link) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(PQconsumeInput(pgsql)); +} +/* }}} */ + +/* {{{ proto mixed pg_flush(resource connection) + Flush outbound query data on the connection */ +PHP_FUNCTION(pg_flush) +{ + zval *pgsql_link; + PGconn *pgsql; + int ret; + int is_non_blocking; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &pgsql_link) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + is_non_blocking = PQisnonblocking(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 1) == -1) { + php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); + RETURN_FALSE; + } + + ret = PQflush(pgsql); + + if (is_non_blocking == 0 && PQ_SETNONBLOCKING(pgsql, 0) == -1) { + php_error_docref(NULL, E_NOTICE, "Failed resetting connection to blocking mode"); + } + + switch (ret) { + case 0: RETURN_TRUE; break; + case 1: RETURN_LONG(0); break; + default: RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ php_pgsql_meta_data + * TODO: Add meta_data cache for better performance + */ +PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, zval *meta, zend_bool extended) +{ + PGresult *pg_result; + char *src, *tmp_name, *tmp_name2 = NULL; + char *escaped; + smart_str querystr = {0}; + size_t new_len; + int i, num_rows; + zval elem; + + if (!*table_name) { + php_error_docref(NULL, E_WARNING, "The table name must be specified"); + return FAILURE; + } + + src = estrdup(table_name); + tmp_name = php_strtok_r(src, ".", &tmp_name2); + if (!tmp_name) { + efree(src); + php_error_docref(NULL, E_WARNING, "The table name must be specified"); + return FAILURE; + } + if (!tmp_name2 || !*tmp_name2) { + /* Default schema */ + tmp_name2 = tmp_name; + tmp_name = "public"; + } + + if (extended) { + smart_str_appends(&querystr, + "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotNULL, a.atthasdef, a.attndims, t.typtype, " + "d.description " + "FROM pg_class as c " + " JOIN pg_attribute a ON (a.attrelid = c.oid) " + " JOIN pg_type t ON (a.atttypid = t.oid) " + " JOIN pg_namespace n ON (c.relnamespace = n.oid) " + " LEFT JOIN pg_description d ON (d.objoid=a.attrelid AND d.objsubid=a.attnum AND c.oid=d.objoid) " + "WHERE a.attnum > 0 AND c.relname = '"); + } else { + smart_str_appends(&querystr, + "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotnull, a.atthasdef, a.attndims, t.typtype " + "FROM pg_class as c " + " JOIN pg_attribute a ON (a.attrelid = c.oid) " + " JOIN pg_type t ON (a.atttypid = t.oid) " + " JOIN pg_namespace n ON (c.relnamespace = n.oid) " + "WHERE a.attnum > 0 AND c.relname = '"); + } + escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL); + if (new_len) { + smart_str_appendl(&querystr, escaped, new_len); + } + efree(escaped); + + smart_str_appends(&querystr, "' AND n.nspname = '"); + escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL); + if (new_len) { + smart_str_appendl(&querystr, escaped, new_len); + } + efree(escaped); + + smart_str_appends(&querystr, "' ORDER BY a.attnum;"); + smart_str_0(&querystr); + efree(src); + + pg_result = PQexec(pg_link, ZSTR_VAL(querystr.s)); + if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) { + php_error_docref(NULL, E_WARNING, "Table '%s' doesn't exists", table_name); + smart_str_free(&querystr); + PQclear(pg_result); + return FAILURE; + } + smart_str_free(&querystr); + + for (i = 0; i < num_rows; i++) { + char *name; + array_init(&elem); + /* pg_attribute.attnum */ + add_assoc_long_ex(&elem, "num", sizeof("num") - 1, atoi(PQgetvalue(pg_result, i, 1))); + /* pg_type.typname */ + add_assoc_string_ex(&elem, "type", sizeof("type") - 1, PQgetvalue(pg_result, i, 2)); + /* pg_attribute.attlen */ + add_assoc_long_ex(&elem, "len", sizeof("len") - 1, atoi(PQgetvalue(pg_result,i,3))); + /* pg_attribute.attnonull */ + add_assoc_bool_ex(&elem, "not null", sizeof("not null") - 1, !strcmp(PQgetvalue(pg_result, i, 4), "t")); + /* pg_attribute.atthasdef */ + add_assoc_bool_ex(&elem, "has default", sizeof("has default") - 1, !strcmp(PQgetvalue(pg_result,i,5), "t")); + /* pg_attribute.attndims */ + add_assoc_long_ex(&elem, "array dims", sizeof("array dims") - 1, atoi(PQgetvalue(pg_result, i, 6))); + /* pg_type.typtype */ + add_assoc_bool_ex(&elem, "is enum", sizeof("is enum") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "e")); + if (extended) { + /* pg_type.typtype */ + add_assoc_bool_ex(&elem, "is base", sizeof("is base") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "b")); + add_assoc_bool_ex(&elem, "is composite", sizeof("is composite") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "c")); + add_assoc_bool_ex(&elem, "is pesudo", sizeof("is pesudo") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "p")); + /* pg_description.description */ + add_assoc_string_ex(&elem, "description", sizeof("description") - 1, PQgetvalue(pg_result, i, 8)); + } + /* pg_attribute.attname */ + name = PQgetvalue(pg_result,i,0); + add_assoc_zval(meta, name, &elem); + } + PQclear(pg_result); + + return SUCCESS; +} + +/* }}} */ + +/* {{{ proto array pg_meta_data(resource db, string table [, bool extended]) + Get meta_data */ +PHP_FUNCTION(pg_meta_data) +{ + zval *pgsql_link; + char *table_name; + size_t table_name_len; + zend_bool extended=0; + PGconn *pgsql; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|b", + &pgsql_link, &table_name, &table_name_len, &extended) == FAILURE) { + return; + } + + if ((pgsql = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + if (php_pgsql_meta_data(pgsql, table_name, return_value, extended) == FAILURE) { + zend_array_destroy(Z_ARR_P(return_value)); /* destroy array */ + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ php_pgsql_get_data_type + */ +static php_pgsql_data_type php_pgsql_get_data_type(const char *type_name, size_t len) +{ + /* This is stupid way to do. I'll fix it when I decied how to support + user defined types. (Yasuo) */ + + /* boolean */ + if (!strcmp(type_name, "bool")|| !strcmp(type_name, "boolean")) + return PG_BOOL; + /* object id */ + if (!strcmp(type_name, "oid")) + return PG_OID; + /* integer */ + if (!strcmp(type_name, "int2") || !strcmp(type_name, "smallint")) + return PG_INT2; + if (!strcmp(type_name, "int4") || !strcmp(type_name, "integer")) + return PG_INT4; + if (!strcmp(type_name, "int8") || !strcmp(type_name, "bigint")) + return PG_INT8; + /* real and other */ + if (!strcmp(type_name, "float4") || !strcmp(type_name, "real")) + return PG_FLOAT4; + if (!strcmp(type_name, "float8") || !strcmp(type_name, "double precision")) + return PG_FLOAT8; + if (!strcmp(type_name, "numeric")) + return PG_NUMERIC; + if (!strcmp(type_name, "money")) + return PG_MONEY; + /* character */ + if (!strcmp(type_name, "text")) + return PG_TEXT; + if (!strcmp(type_name, "bpchar") || !strcmp(type_name, "character")) + return PG_CHAR; + if (!strcmp(type_name, "varchar") || !strcmp(type_name, "character varying")) + return PG_VARCHAR; + /* time and interval */ + if (!strcmp(type_name, "abstime")) + return PG_UNIX_TIME; + if (!strcmp(type_name, "reltime")) + return PG_UNIX_TIME_INTERVAL; + if (!strcmp(type_name, "tinterval")) + return PG_UNIX_TIME_INTERVAL; + if (!strcmp(type_name, "date")) + return PG_DATE; + if (!strcmp(type_name, "time")) + return PG_TIME; + if (!strcmp(type_name, "time with time zone") || !strcmp(type_name, "timetz")) + return PG_TIME_WITH_TIMEZONE; + if (!strcmp(type_name, "timestamp without time zone") || !strcmp(type_name, "timestamp")) + return PG_TIMESTAMP; + if (!strcmp(type_name, "timestamp with time zone") || !strcmp(type_name, "timestamptz")) + return PG_TIMESTAMP_WITH_TIMEZONE; + if (!strcmp(type_name, "interval")) + return PG_INTERVAL; + /* binary */ + if (!strcmp(type_name, "bytea")) + return PG_BYTEA; + /* network */ + if (!strcmp(type_name, "cidr")) + return PG_CIDR; + if (!strcmp(type_name, "inet")) + return PG_INET; + if (!strcmp(type_name, "macaddr")) + return PG_MACADDR; + /* bit */ + if (!strcmp(type_name, "bit")) + return PG_BIT; + if (!strcmp(type_name, "bit varying")) + return PG_VARBIT; + /* geometric */ + if (!strcmp(type_name, "line")) + return PG_LINE; + if (!strcmp(type_name, "lseg")) + return PG_LSEG; + if (!strcmp(type_name, "box")) + return PG_BOX; + if (!strcmp(type_name, "path")) + return PG_PATH; + if (!strcmp(type_name, "point")) + return PG_POINT; + if (!strcmp(type_name, "polygon")) + return PG_POLYGON; + if (!strcmp(type_name, "circle")) + return PG_CIRCLE; + + return PG_UNKNOWN; +} +/* }}} */ + +/* {{{ php_pgsql_convert_match + * test field value with regular expression specified. + */ +static int php_pgsql_convert_match(const char *str, size_t str_len, const char *regex , size_t regex_len, int icase) +{ + pcre2_code *re; + PCRE2_SIZE err_offset; + int res, errnumber; + uint32_t options = PCRE2_NO_AUTO_CAPTURE; + size_t i; + pcre2_match_data *match_data; + + /* Check invalid chars for POSIX regex */ + for (i = 0; i < str_len; i++) { + if (str[i] == '\n' || + str[i] == '\r' || + str[i] == '\0' ) { + return FAILURE; + } + } + + if (icase) { + options |= PCRE2_CASELESS; + } + + re = pcre2_compile((PCRE2_SPTR)regex, regex_len, options, &errnumber, &err_offset, php_pcre_cctx()); + if (NULL == re) { + PCRE2_UCHAR err_msg[128]; + pcre2_get_error_message(errnumber, err_msg, sizeof(err_msg)); + php_error_docref(NULL, E_WARNING, "Cannot compile regex: '%s'", err_msg); + return FAILURE; + } + + match_data = php_pcre_create_match_data(0, re); + if (NULL == match_data) { + pcre2_code_free(re); + php_error_docref(NULL, E_WARNING, "Cannot allocate match data"); + return FAILURE; + } + res = pcre2_match(re, (PCRE2_SPTR)str, str_len, 0, 0, match_data, php_pcre_mctx()); + php_pcre_free_match_data(match_data); + pcre2_code_free(re); + + if (res == PCRE2_ERROR_NOMATCH) { + return FAILURE; + } else if (res < 0) { + php_error_docref(NULL, E_WARNING, "Cannot exec regex"); + return FAILURE; + } + return SUCCESS; +} + +/* }}} */ + +/* {{{ php_pgsql_add_quote + * add quotes around string. + */ +static int php_pgsql_add_quotes(zval *src, zend_bool should_free) +{ + smart_str str = {0}; + + assert(Z_TYPE_P(src) == IS_STRING); + assert(should_free == 1 || should_free == 0); + + smart_str_appendc(&str, 'E'); + smart_str_appendc(&str, '\''); + smart_str_appendl(&str, Z_STRVAL_P(src), Z_STRLEN_P(src)); + smart_str_appendc(&str, '\''); + smart_str_0(&str); + + if (should_free) { + zval_ptr_dtor(src); + } + ZVAL_NEW_STR(src, str.s); + + return SUCCESS; +} +/* }}} */ + +#define PGSQL_CONV_CHECK_IGNORE() \ + if (!err && Z_TYPE(new_val) == IS_STRING && !strcmp(Z_STRVAL(new_val), "NULL")) { \ + /* if new_value is string "NULL" and field has default value, remove element to use default value */ \ + if (!(opt & PGSQL_CONV_IGNORE_DEFAULT) && Z_TYPE_P(has_default) == IS_TRUE) { \ + zval_ptr_dtor(&new_val); \ + skip_field = 1; \ + } \ + /* raise error if it's not null and cannot be ignored */ \ + else if (!(opt & PGSQL_CONV_IGNORE_NOT_NULL) && Z_TYPE_P(not_null) == IS_TRUE) { \ + php_error_docref(NULL, E_NOTICE, "Detected NULL for 'NOT NULL' field '%s'", ZSTR_VAL(field)); \ + err = 1; \ + } \ + } + +/* {{{ php_pgsql_convert + * check and convert array values (fieldname=>value pair) for sql + */ +PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, const zval *values, zval *result, zend_ulong opt) +{ + zend_string *field = NULL; + zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val; + int err = 0, skip_field; + php_pgsql_data_type data_type; + + assert(pg_link != NULL); + assert(Z_TYPE_P(values) == IS_ARRAY); + assert(Z_TYPE_P(result) == IS_ARRAY); + assert(!(opt & ~PGSQL_CONV_OPTS)); + + if (!table_name) { + return FAILURE; + } + + array_init(&meta); +/* table_name is escaped by php_pgsql_meta_data */ + if (php_pgsql_meta_data(pg_link, table_name, &meta, 0) == FAILURE) { + zval_ptr_dtor(&meta); + return FAILURE; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(values), field, val) { + skip_field = 0; + ZVAL_NULL(&new_val); + + if (!err && field == NULL) { + php_error_docref(NULL, E_WARNING, "Accepts only string key for values"); + err = 1; + } + + if (!err && (def = zend_hash_find(Z_ARRVAL(meta), field)) == NULL) { + php_error_docref(NULL, E_NOTICE, "Invalid field name (%s) in values", ZSTR_VAL(field)); + err = 1; + } + if (!err && (type = zend_hash_str_find(Z_ARRVAL_P(def), "type", sizeof("type") - 1)) == NULL) { + php_error_docref(NULL, E_NOTICE, "Detected broken meta data. Missing 'type'"); + err = 1; + } + if (!err && (not_null = zend_hash_str_find(Z_ARRVAL_P(def), "not null", sizeof("not null") - 1)) == NULL) { + php_error_docref(NULL, E_NOTICE, "Detected broken meta data. Missing 'not null'"); + err = 1; + } + if (!err && (has_default = zend_hash_str_find(Z_ARRVAL_P(def), "has default", sizeof("has default") - 1)) == NULL) { + php_error_docref(NULL, E_NOTICE, "Detected broken meta data. Missing 'has default'"); + err = 1; + } + if (!err && (is_enum = zend_hash_str_find(Z_ARRVAL_P(def), "is enum", sizeof("is enum") - 1)) == NULL) { + php_error_docref(NULL, E_NOTICE, "Detected broken meta data. Missing 'is enum'"); + err = 1; + } + if (!err && (Z_TYPE_P(val) == IS_ARRAY || Z_TYPE_P(val) == IS_OBJECT)) { + php_error_docref(NULL, E_NOTICE, "Expects scalar values as field values"); + err = 1; + } + if (err) { + break; /* break out for() */ + } + + convert_to_boolean(is_enum); + if (Z_TYPE_P(is_enum) == IS_TRUE) { + /* enums need to be treated like strings */ + data_type = PG_TEXT; + } else { + data_type = php_pgsql_get_data_type(Z_STRVAL_P(type), Z_STRLEN_P(type)); + } + + switch(data_type) + { + case PG_BOOL: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRING(&new_val, "NULL"); + } + else { + if (!strcmp(Z_STRVAL_P(val), "t") || !strcmp(Z_STRVAL_P(val), "T") || + !strcmp(Z_STRVAL_P(val), "y") || !strcmp(Z_STRVAL_P(val), "Y") || + !strcmp(Z_STRVAL_P(val), "true") || !strcmp(Z_STRVAL_P(val), "True") || + !strcmp(Z_STRVAL_P(val), "yes") || !strcmp(Z_STRVAL_P(val), "Yes") || + !strcmp(Z_STRVAL_P(val), "1")) { + ZVAL_STRINGL(&new_val, "'t'", sizeof("'t'")-1); + } + else if (!strcmp(Z_STRVAL_P(val), "f") || !strcmp(Z_STRVAL_P(val), "F") || + !strcmp(Z_STRVAL_P(val), "n") || !strcmp(Z_STRVAL_P(val), "N") || + !strcmp(Z_STRVAL_P(val), "false") || !strcmp(Z_STRVAL_P(val), "False") || + !strcmp(Z_STRVAL_P(val), "no") || !strcmp(Z_STRVAL_P(val), "No") || + !strcmp(Z_STRVAL_P(val), "0")) { + ZVAL_STRINGL(&new_val, "'f'", sizeof("'f'")-1); + } + else { + php_error_docref(NULL, E_NOTICE, "Detected invalid value (%s) for PostgreSQL %s field (%s)", Z_STRVAL_P(val), Z_STRVAL_P(type), ZSTR_VAL(field)); + err = 1; + } + } + break; + + case IS_LONG: + if (Z_LVAL_P(val)) { + ZVAL_STRINGL(&new_val, "'t'", sizeof("'t'")-1); + } + else { + ZVAL_STRINGL(&new_val, "'f'", sizeof("'f'")-1); + } + break; + + case IS_TRUE: + ZVAL_STRINGL(&new_val, "'t'", sizeof("'t'")-1); + break; + + case IS_FALSE: + ZVAL_STRINGL(&new_val, "'f'", sizeof("'f'")-1); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects string, null, long or boolelan value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_OID: + case PG_INT2: + case PG_INT4: + case PG_INT8: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { + /* FIXME: better regex must be used */ +#define REGEX0 "^([+-]{0,1}[0-9]+)$" + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 0) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + } +#undef REGEX0 + } + break; + + case IS_DOUBLE: + ZVAL_DOUBLE(&new_val, Z_DVAL_P(val)); + convert_to_long_ex(&new_val); + break; + + case IS_LONG: + ZVAL_LONG(&new_val, Z_LVAL_P(val)); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for pgsql '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_NUMERIC: + case PG_MONEY: + case PG_FLOAT4: + case PG_FLOAT8: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { +#define REGEX0 "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$" +#define REGEX1 "^[+-]{0,1}(inf)(inity){0,1}$" + /* better regex? */ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 0) == FAILURE) { + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX1, sizeof(REGEX1)-1, 1) == FAILURE) { + err = 1; + } else { + ZVAL_STRING(&new_val, Z_STRVAL_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } + } + else { + ZVAL_STRING(&new_val, Z_STRVAL_P(val)); + } +#undef REGEX0 +#undef REGEX1 + } + break; + + case IS_LONG: + ZVAL_LONG(&new_val, Z_LVAL_P(val)); + break; + + case IS_DOUBLE: + ZVAL_DOUBLE(&new_val, Z_DVAL_P(val)); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + /* Exotic types are handled as string also. + Please feel free to add more valitions. Invalid query fails + at execution anyway. */ + case PG_TEXT: + case PG_CHAR: + case PG_VARCHAR: + /* bit */ + case PG_BIT: + case PG_VARBIT: + /* geometric */ + case PG_LINE: + case PG_LSEG: + case PG_POINT: + case PG_BOX: + case PG_PATH: + case PG_POLYGON: + case PG_CIRCLE: + /* unknown. JSON, Array etc */ + case PG_UNKNOWN: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + if (opt & PGSQL_CONV_FORCE_NULL) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } else { + ZVAL_STRINGL(&new_val, "''", sizeof("''")-1); + } + } + else { + zend_string *str; + /* PostgreSQL ignores \0 */ + str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0); + /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */ + ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + str = zend_string_truncate(str, ZSTR_LEN(str), 0); + ZVAL_NEW_STR(&new_val, str); + php_pgsql_add_quotes(&new_val, 1); + } + break; + + case IS_LONG: + ZVAL_STR(&new_val, zend_long_to_str(Z_LVAL_P(val))); + break; + + case IS_DOUBLE: + ZVAL_DOUBLE(&new_val, Z_DVAL_P(val)); + convert_to_string_ex(&new_val); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_UNIX_TIME: + case PG_UNIX_TIME_INTERVAL: + /* these are the actallay a integer */ + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { + /* better regex? */ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), "^[0-9]+$", sizeof("^[0-9]+$")-1, 0) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + convert_to_long_ex(&new_val); + } + } + break; + + case IS_DOUBLE: + ZVAL_DOUBLE(&new_val, Z_DVAL_P(val)); + convert_to_long_ex(&new_val); + break; + + case IS_LONG: + ZVAL_LONG(&new_val, Z_LVAL_P(val)); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_CIDR: + case PG_INET: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { +#define REGEX0 "^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])(\\/[0-9]{1,3})?$" +#define REGEX1 "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\\/[0-9]{1,3})?$" + /* The inet type holds an IPv4 or IPv6 host address, and optionally its subnet, all in one field. See more in the doc. + The regex might still be not perfect, but catches the most of IP variants. We might decide to remove the regex + at all though and let the server side to handle it.*/ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 0) == FAILURE + && php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX1, sizeof(REGEX1)-1, 0) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 +#undef REGEX1 + } + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or IPv4 or IPv6 address string for '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_TIME_WITH_TIMEZONE: + case PG_TIMESTAMP: + case PG_TIMESTAMP_WITH_TIMEZONE: + switch(Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } else if (!strcasecmp(Z_STRVAL_P(val), "now()")) { + ZVAL_STRINGL(&new_val, "NOW()", sizeof("NOW()")-1); + } else { +#define REGEX0 "^([0-9]{4}[/-][0-9]{1,2}[/-][0-9]{1,2})(([ \\t]+|T)(([0-9]{1,2}:[0-9]{1,2}){1}(:[0-9]{1,2}){0,1}(\\.[0-9]+){0,1}([ \\t]*([+-][0-9]{1,4}(:[0-9]{1,2}){0,1}|[-a-zA-Z_/+]{1,50})){0,1})){0,1}$" + /* better regex? */ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 1) == FAILURE) { + err = 1; + } else { + ZVAL_STRING(&new_val, Z_STRVAL_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 + } + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or string for PostgreSQL %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_DATE: + switch(Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { +#define REGEX0 "^([0-9]{4}[/-][0-9]{1,2}[/-][0-9]{1,2})$" + /* FIXME: better regex must be used */ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 1) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 + } + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or string for PostgreSQL %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_TIME: + switch(Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { +#define REGEX0 "^(([0-9]{1,2}:[0-9]{1,2}){1}(:[0-9]{1,2}){0,1}){0,1}$" + /* FIXME: better regex must be used */ + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 1) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 + } + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or string for PostgreSQL %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + case PG_INTERVAL: + switch(Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRING(&new_val, "NULL"); + } + else { + + /* From the Postgres docs: + + interval values can be written with the following syntax: + [@] quantity unit [quantity unit...] [direction] + + Where: quantity is a number (possibly signed); unit is second, minute, hour, + day, week, month, year, decade, century, millennium, or abbreviations or + plurals of these units [note not *all* abbreviations] ; direction can be + ago or empty. The at sign (@) is optional noise. + + ... + + Quantities of days, hours, minutes, and seconds can be specified without explicit + unit markings. For example, '1 12:59:10' is read the same as '1 day 12 hours 59 min 10 + sec'. + */ +#define REGEX0 \ + "^(@?[ \\t]+)?(" \ + /* Textual time units and their abbreviations: */ \ + "(([-+]?[ \\t]+)?" \ + "[0-9]+(\\.[0-9]*)?[ \\t]*" \ + "(millenniums|millennia|millennium|mil|mils|" \ + "centuries|century|cent|c|" \ + "decades|decade|dec|decs|" \ + "years|year|y|" \ + "months|month|mon|" \ + "weeks|week|w|" \ + "days|day|d|" \ + "hours|hour|hr|hrs|h|" \ + "minutes|minute|mins|min|m|" \ + "seconds|second|secs|sec|s))+|" \ + /* Textual time units plus (dd)* hh[:mm[:ss]] */ \ + "((([-+]?[ \\t]+)?" \ + "[0-9]+(\\.[0-9]*)?[ \\t]*" \ + "(millenniums|millennia|millennium|mil|mils|" \ + "centuries|century|cent|c|" \ + "decades|decade|dec|decs|" \ + "years|year|y|" \ + "months|month|mon|" \ + "weeks|week|w|" \ + "days|day|d))+" \ + "([-+]?[ \\t]+" \ + "([0-9]+[ \\t]+)+" /* dd */ \ + "(([0-9]{1,2}:){0,2}[0-9]{0,2})" /* hh:[mm:[ss]] */ \ + ")?))" \ + "([ \\t]+ago)?$" + + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 1) == FAILURE) { + err = 1; + } + else { + ZVAL_STRING(&new_val, Z_STRVAL_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 + } + break; + + case IS_NULL: + ZVAL_STRING(&new_val, "NULL"); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or string for PostgreSQL %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; +#ifdef HAVE_PQESCAPE + case PG_BYTEA: + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRING(&new_val, "NULL"); + } + else { + unsigned char *tmp; + size_t to_len; + smart_str s = {0}; +#ifdef HAVE_PQESCAPE_BYTEA_CONN + tmp = PQescapeByteaConn(pg_link, (unsigned char *)Z_STRVAL_P(val), Z_STRLEN_P(val), &to_len); +#else + tmp = PQescapeBytea(Z_STRVAL_P(val), (unsigned char *)Z_STRLEN_P(val), &to_len); +#endif + ZVAL_STRINGL(&new_val, (char *)tmp, to_len - 1); /* PQescapeBytea's to_len includes additional '\0' */ + PQfreemem(tmp); + php_pgsql_add_quotes(&new_val, 1); + smart_str_appendl(&s, Z_STRVAL(new_val), Z_STRLEN(new_val)); + smart_str_0(&s); + zval_ptr_dtor(&new_val); + ZVAL_NEW_STR(&new_val, s.s); + } + break; + + case IS_LONG: + ZVAL_STR(&new_val, zend_long_to_str(Z_LVAL_P(val))); + break; + + case IS_DOUBLE: + ZVAL_DOUBLE(&new_val, Z_DVAL_P(val)); + convert_to_string_ex(&new_val); + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + +#endif + case PG_MACADDR: + switch(Z_TYPE_P(val)) { + case IS_STRING: + if (Z_STRLEN_P(val) == 0) { + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + } + else { +#define REGEX0 "^([0-9a-f]{2,2}:){5,5}[0-9a-f]{2,2}$" + if (php_pgsql_convert_match(Z_STRVAL_P(val), Z_STRLEN_P(val), REGEX0, sizeof(REGEX0)-1, 1) == FAILURE) { + err = 1; + } + else { + ZVAL_STRINGL(&new_val, Z_STRVAL_P(val), Z_STRLEN_P(val)); + php_pgsql_add_quotes(&new_val, 1); + } +#undef REGEX0 + } + break; + + case IS_NULL: + ZVAL_STRINGL(&new_val, "NULL", sizeof("NULL")-1); + break; + + default: + err = 1; + } + PGSQL_CONV_CHECK_IGNORE(); + if (err) { + php_error_docref(NULL, E_NOTICE, "Expects NULL or string for PostgreSQL %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + } + break; + + default: + /* should not happen */ + php_error_docref(NULL, E_NOTICE, "Unknown or system data type '%s' for '%s'. Report error", Z_STRVAL_P(type), ZSTR_VAL(field)); + err = 1; + break; + } /* switch */ + + if (err) { + zval_ptr_dtor(&new_val); + break; /* break out for() */ + } + /* If field is NULL and HAS DEFAULT, should be skipped */ + if (!skip_field) { + if (_php_pgsql_detect_identifier_escape(ZSTR_VAL(field), ZSTR_LEN(field)) == SUCCESS) { + zend_hash_update(Z_ARRVAL_P(result), field, &new_val); + } else { + char *escaped = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field)); + add_assoc_zval(result, escaped, &new_val); + PGSQLfree(escaped); + } + } + } ZEND_HASH_FOREACH_END(); /* for */ + + zval_ptr_dtor(&meta); + + if (err) { + /* shouldn't destroy & free zval here */ + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +/* {{{ proto array pg_convert(resource db, string table, array values[, int options]) + Check and convert values for PostgreSQL SQL statement */ +PHP_FUNCTION(pg_convert) +{ + zval *pgsql_link, *values; + char *table_name; + size_t table_name_len; + zend_ulong option = 0; + PGconn *pg_link; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "rsa|l", &pgsql_link, &table_name, &table_name_len, &values, &option) == FAILURE) { + return; + } + if (option & ~PGSQL_CONV_OPTS) { + php_error_docref(NULL, E_WARNING, "Invalid option is specified"); + RETURN_FALSE; + } + if (!table_name_len) { + php_error_docref(NULL, E_NOTICE, "Table name is invalid"); + RETURN_FALSE; + } + + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (php_pgsql_flush_query(pg_link)) { + php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); + } + array_init(return_value); + if (php_pgsql_convert(pg_link, table_name, values, return_value, option) == FAILURE) { + zend_array_destroy(Z_ARR_P(return_value)); + RETURN_FALSE; + } +} +/* }}} */ + +static int do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link, zend_ulong opt) /* {{{ */ +{ + if (opt & PGSQL_DML_ASYNC) { + if (PQsendQuery(pg_link, ZSTR_VAL(querystr->s))) { + return 0; + } + } + else { + PGresult *pg_result; + + pg_result = PQexec(pg_link, ZSTR_VAL(querystr->s)); + if (PQresultStatus(pg_result) == expect) { + PQclear(pg_result); + return 0; + } else { + php_error_docref(NULL, E_WARNING, "%s", PQresultErrorMessage(pg_result)); + PQclear(pg_result); + } + } + + return -1; +} +/* }}} */ + +static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */ +{ + size_t table_len = strlen(table); + + /* schema.table should be "schema"."table" */ + const char *dot = memchr(table, '.', table_len); + size_t len = dot ? dot - table : table_len; + if (_php_pgsql_detect_identifier_escape(table, len) == SUCCESS) { + smart_str_appendl(querystr, table, len); + } else { + char *escaped = PGSQLescapeIdentifier(pg_link, table, len); + smart_str_appends(querystr, escaped); + PGSQLfree(escaped); + } + if (dot) { + const char *after_dot = dot + 1; + len = table_len - len - 1; + /* "schema"."table" format */ + if (_php_pgsql_detect_identifier_escape(after_dot, len) == SUCCESS) { + smart_str_appendc(querystr, '.'); + smart_str_appendl(querystr, after_dot, len); + } else { + char *escaped = PGSQLescapeIdentifier(pg_link, after_dot, len); + smart_str_appendc(querystr, '.'); + smart_str_appends(querystr, escaped); + PGSQLfree(escaped); + } + } +} +/* }}} */ + +/* {{{ php_pgsql_insert + */ +PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var_array, zend_ulong opt, zend_string **sql) +{ + zval *val, converted; + char buf[256]; + char *tmp; + smart_str querystr = {0}; + int ret = FAILURE; + zend_string *fld; + + assert(pg_link != NULL); + assert(table != NULL); + assert(Z_TYPE_P(var_array) == IS_ARRAY); + + ZVAL_UNDEF(&converted); + if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) { + smart_str_appends(&querystr, "INSERT INTO "); + build_tablename(&querystr, pg_link, table); + smart_str_appends(&querystr, " DEFAULT VALUES"); + + goto no_values; + } + + /* convert input array if needed */ + if (!(opt & (PGSQL_DML_NO_CONV|PGSQL_DML_ESCAPE))) { + array_init(&converted); + if (php_pgsql_convert(pg_link, table, var_array, &converted, (opt & PGSQL_CONV_OPTS)) == FAILURE) { + goto cleanup; + } + var_array = &converted; + } + + smart_str_appends(&querystr, "INSERT INTO "); + build_tablename(&querystr, pg_link, table); + smart_str_appends(&querystr, " ("); + + ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) { + if (fld == NULL) { + php_error_docref(NULL, E_NOTICE, "Expects associative array for values to be inserted"); + goto cleanup; + } + if (opt & PGSQL_DML_ESCAPE) { + tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + smart_str_appends(&querystr, tmp); + PGSQLfree(tmp); + } else { + smart_str_appendl(&querystr, ZSTR_VAL(fld), ZSTR_LEN(fld)); + } + smart_str_appendc(&querystr, ','); + } ZEND_HASH_FOREACH_END(); + ZSTR_LEN(querystr.s)--; + smart_str_appends(&querystr, ") VALUES ("); + + /* make values string */ + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) { + /* we can avoid the key_type check here, because we tested it in the other loop */ + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (opt & PGSQL_DML_ESCAPE) { + size_t new_len; + char *tmp; + tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); + new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + smart_str_appendc(&querystr, '\''); + smart_str_appendl(&querystr, tmp, new_len); + smart_str_appendc(&querystr, '\''); + efree(tmp); + } else { + smart_str_appendl(&querystr, Z_STRVAL_P(val), Z_STRLEN_P(val)); + } + break; + case IS_LONG: + smart_str_append_long(&querystr, Z_LVAL_P(val)); + break; + case IS_DOUBLE: + smart_str_appendl(&querystr, buf, snprintf(buf, sizeof(buf), "%F", Z_DVAL_P(val))); + break; + case IS_NULL: + smart_str_appendl(&querystr, "NULL", sizeof("NULL")-1); + break; + default: + php_error_docref(NULL, E_WARNING, "Expects scaler values. type = %d", Z_TYPE_P(val)); + goto cleanup; + break; + } + smart_str_appendc(&querystr, ','); + } ZEND_HASH_FOREACH_END(); + /* Remove the trailing "," */ + ZSTR_LEN(querystr.s)--; + smart_str_appends(&querystr, ");"); + +no_values: + + smart_str_0(&querystr); + + if ((opt & (PGSQL_DML_EXEC|PGSQL_DML_ASYNC)) && + do_exec(&querystr, PGRES_COMMAND_OK, pg_link, (opt & PGSQL_CONV_OPTS)) == 0) { + ret = SUCCESS; + } + else if (opt & PGSQL_DML_STRING) { + ret = SUCCESS; + } + +cleanup: + zval_ptr_dtor(&converted); + if (ret == SUCCESS && (opt & PGSQL_DML_STRING)) { + *sql = querystr.s; + } + else { + smart_str_free(&querystr); + } + return ret; +} +/* }}} */ + +/* {{{ proto mixed pg_insert(resource db, string table, array values[, int options]) + Insert values (filed=>value) to table */ +PHP_FUNCTION(pg_insert) +{ + zval *pgsql_link, *values; + char *table; + size_t table_len; + zend_ulong option = PGSQL_DML_EXEC, return_sql; + PGconn *pg_link; + PGresult *pg_result; + ExecStatusType status; + zend_string *sql = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rsa|l", + &pgsql_link, &table, &table_len, &values, &option) == FAILURE) { + return; + } + if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_ASYNC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) { + php_error_docref(NULL, E_WARNING, "Invalid option is specified"); + RETURN_FALSE; + } + + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (php_pgsql_flush_query(pg_link)) { + php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); + } + return_sql = option & PGSQL_DML_STRING; + if (option & PGSQL_DML_EXEC) { + /* return resource when executed */ + option = option & ~PGSQL_DML_EXEC; + if (php_pgsql_insert(pg_link, table, values, option|PGSQL_DML_STRING, &sql) == FAILURE) { + RETURN_FALSE; + } + pg_result = PQexec(pg_link, ZSTR_VAL(sql)); + if ((PGG(auto_reset_persistent) & 2) && PQstatus(pg_link) != CONNECTION_OK) { + PQclear(pg_result); + PQreset(pg_link); + pg_result = PQexec(pg_link, ZSTR_VAL(sql)); + } + efree(sql); + + if (pg_result) { + status = PQresultStatus(pg_result); + } else { + status = (ExecStatusType) PQstatus(pg_link); + } + + switch (status) { + case PGRES_EMPTY_QUERY: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + PHP_PQ_ERROR("Query failed: %s", pg_link); + PQclear(pg_result); + RETURN_FALSE; + break; + case PGRES_COMMAND_OK: /* successful command that did not return rows */ + default: + if (pg_result) { + pgsql_result_handle *pgsql_handle = (pgsql_result_handle *) emalloc(sizeof(pgsql_result_handle)); + pgsql_handle->conn = pg_link; + pgsql_handle->result = pg_result; + pgsql_handle->row = 0; + RETURN_RES(zend_register_resource(pgsql_handle, le_result)); + } else { + PQclear(pg_result); + RETURN_FALSE; + } + break; + } + } else if (php_pgsql_insert(pg_link, table, values, option, &sql) == FAILURE) { + RETURN_FALSE; + } + if (return_sql) { + RETURN_STR(sql); + return; + } + RETURN_TRUE; +} +/* }}} */ + +static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, HashTable *ht, int where_cond, const char *pad, int pad_len, zend_ulong opt) /* {{{ */ +{ + zend_string *fld; + zval *val; + + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, fld, val) { + if (fld == NULL) { + php_error_docref(NULL, E_NOTICE, "Expects associative array for values to be inserted"); + return -1; + } + if (opt & PGSQL_DML_ESCAPE) { + char *tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + smart_str_appends(querystr, tmp); + PGSQLfree(tmp); + } else { + smart_str_appendl(querystr, ZSTR_VAL(fld), ZSTR_LEN(fld)); + } + if (where_cond && (Z_TYPE_P(val) == IS_TRUE || Z_TYPE_P(val) == IS_FALSE || (Z_TYPE_P(val) == IS_STRING && !strcmp(Z_STRVAL_P(val), "NULL")))) { + smart_str_appends(querystr, " IS "); + } else { + smart_str_appendc(querystr, '='); + } + + switch (Z_TYPE_P(val)) { + case IS_STRING: + if (opt & PGSQL_DML_ESCAPE) { + char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); + size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + smart_str_appendc(querystr, '\''); + smart_str_appendl(querystr, tmp, new_len); + smart_str_appendc(querystr, '\''); + efree(tmp); + } else { + smart_str_appendl(querystr, Z_STRVAL_P(val), Z_STRLEN_P(val)); + } + break; + case IS_LONG: + smart_str_append_long(querystr, Z_LVAL_P(val)); + break; + case IS_DOUBLE: { + char buf[256]; + smart_str_appendl(querystr, buf, MIN(snprintf(buf, sizeof(buf), "%F", Z_DVAL_P(val)), sizeof(buf) - 1)); + } + break; + case IS_NULL: + smart_str_appendl(querystr, "NULL", sizeof("NULL")-1); + break; + default: + php_error_docref(NULL, E_WARNING, "Expects scaler values. type=%d", Z_TYPE_P(val)); + return -1; + } + smart_str_appendl(querystr, pad, pad_len); + } ZEND_HASH_FOREACH_END(); + if (querystr->s) { + ZSTR_LEN(querystr->s) -= pad_len; + } + + return 0; +} +/* }}} */ + +/* {{{ php_pgsql_update + */ +PHP_PGSQL_API int php_pgsql_update(PGconn *pg_link, const char *table, zval *var_array, zval *ids_array, zend_ulong opt, zend_string **sql) +{ + zval var_converted, ids_converted; + smart_str querystr = {0}; + int ret = FAILURE; + + assert(pg_link != NULL); + assert(table != NULL); + assert(Z_TYPE_P(var_array) == IS_ARRAY); + assert(Z_TYPE_P(ids_array) == IS_ARRAY); + assert(!(opt & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE))); + + if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0 + || zend_hash_num_elements(Z_ARRVAL_P(ids_array)) == 0) { + return FAILURE; + } + + ZVAL_UNDEF(&var_converted); + ZVAL_UNDEF(&ids_converted); + if (!(opt & (PGSQL_DML_NO_CONV|PGSQL_DML_ESCAPE))) { + array_init(&var_converted); + if (php_pgsql_convert(pg_link, table, var_array, &var_converted, (opt & PGSQL_CONV_OPTS)) == FAILURE) { + goto cleanup; + } + var_array = &var_converted; + array_init(&ids_converted); + if (php_pgsql_convert(pg_link, table, ids_array, &ids_converted, (opt & PGSQL_CONV_OPTS)) == FAILURE) { + goto cleanup; + } + ids_array = &ids_converted; + } + + smart_str_appends(&querystr, "UPDATE "); + build_tablename(&querystr, pg_link, table); + smart_str_appends(&querystr, " SET "); + + if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt)) + goto cleanup; + + smart_str_appends(&querystr, " WHERE "); + + if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) + goto cleanup; + + smart_str_appendc(&querystr, ';'); + smart_str_0(&querystr); + + if ((opt & PGSQL_DML_EXEC) && do_exec(&querystr, PGRES_COMMAND_OK, pg_link, opt) == 0) { + ret = SUCCESS; + } else if (opt & PGSQL_DML_STRING) { + ret = SUCCESS; + } + +cleanup: + zval_ptr_dtor(&var_converted); + zval_ptr_dtor(&ids_converted); + if (ret == SUCCESS && (opt & PGSQL_DML_STRING)) { + *sql = querystr.s; + } + else { + smart_str_free(&querystr); + } + return ret; +} +/* }}} */ + +/* {{{ proto mixed pg_update(resource db, string table, array fields, array ids[, int options]) + Update table using values (field=>value) and ids (id=>value) */ +PHP_FUNCTION(pg_update) +{ + zval *pgsql_link, *values, *ids; + char *table; + size_t table_len; + zend_ulong option = PGSQL_DML_EXEC; + PGconn *pg_link; + zend_string *sql = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rsaa|l", + &pgsql_link, &table, &table_len, &values, &ids, &option) == FAILURE) { + return; + } + if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) { + php_error_docref(NULL, E_WARNING, "Invalid option is specified"); + RETURN_FALSE; + } + + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (php_pgsql_flush_query(pg_link)) { + php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); + } + if (php_pgsql_update(pg_link, table, values, ids, option, &sql) == FAILURE) { + RETURN_FALSE; + } + if (option & PGSQL_DML_STRING) { + RETURN_STR(sql); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ php_pgsql_delete + */ +PHP_PGSQL_API int php_pgsql_delete(PGconn *pg_link, const char *table, zval *ids_array, zend_ulong opt, zend_string **sql) +{ + zval ids_converted; + smart_str querystr = {0}; + int ret = FAILURE; + + assert(pg_link != NULL); + assert(table != NULL); + assert(Z_TYPE_P(ids_array) == IS_ARRAY); + assert(!(opt & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE))); + + if (zend_hash_num_elements(Z_ARRVAL_P(ids_array)) == 0) { + return FAILURE; + } + + ZVAL_UNDEF(&ids_converted); + if (!(opt & (PGSQL_DML_NO_CONV|PGSQL_DML_ESCAPE))) { + array_init(&ids_converted); + if (php_pgsql_convert(pg_link, table, ids_array, &ids_converted, (opt & PGSQL_CONV_OPTS)) == FAILURE) { + goto cleanup; + } + ids_array = &ids_converted; + } + + smart_str_appends(&querystr, "DELETE FROM "); + build_tablename(&querystr, pg_link, table); + smart_str_appends(&querystr, " WHERE "); + + if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) + goto cleanup; + + smart_str_appendc(&querystr, ';'); + smart_str_0(&querystr); + + if ((opt & PGSQL_DML_EXEC) && do_exec(&querystr, PGRES_COMMAND_OK, pg_link, opt) == 0) { + ret = SUCCESS; + } else if (opt & PGSQL_DML_STRING) { + ret = SUCCESS; + } + +cleanup: + zval_ptr_dtor(&ids_converted); + if (ret == SUCCESS && (opt & PGSQL_DML_STRING)) { + *sql = querystr.s; + } + else { + smart_str_free(&querystr); + } + return ret; +} +/* }}} */ + +/* {{{ proto mixed pg_delete(resource db, string table, array ids[, int options]) + Delete records has ids (id=>value) */ +PHP_FUNCTION(pg_delete) +{ + zval *pgsql_link, *ids; + char *table; + size_t table_len; + zend_ulong option = PGSQL_DML_EXEC; + PGconn *pg_link; + zend_string *sql; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rsa|l", + &pgsql_link, &table, &table_len, &ids, &option) == FAILURE) { + return; + } + if (option & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) { + php_error_docref(NULL, E_WARNING, "Invalid option is specified"); + RETURN_FALSE; + } + + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (php_pgsql_flush_query(pg_link)) { + php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); + } + if (php_pgsql_delete(pg_link, table, ids, option, &sql) == FAILURE) { + RETURN_FALSE; + } + if (option & PGSQL_DML_STRING) { + RETURN_STR(sql); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ php_pgsql_result2array + */ +PHP_PGSQL_API int php_pgsql_result2array(PGresult *pg_result, zval *ret_array, long result_type) +{ + zval row; + char *field_name; + size_t num_fields; + int pg_numrows, pg_row; + uint32_t i; + assert(Z_TYPE_P(ret_array) == IS_ARRAY); + + if ((pg_numrows = PQntuples(pg_result)) <= 0) { + return FAILURE; + } + for (pg_row = 0; pg_row < pg_numrows; pg_row++) { + array_init(&row); + for (i = 0, num_fields = PQnfields(pg_result); i < num_fields; i++) { + field_name = PQfname(pg_result, i); + if (PQgetisnull(pg_result, pg_row, i)) { + if (result_type & PGSQL_ASSOC) { + add_assoc_null(&row, field_name); + } + if (result_type & PGSQL_NUM) { + add_next_index_null(&row); + } + } else { + char *element = PQgetvalue(pg_result, pg_row, i); + if (element) { + const size_t element_len = strlen(element); + if (result_type & PGSQL_ASSOC) { + add_assoc_stringl(&row, field_name, element, element_len); + } + if (result_type & PGSQL_NUM) { + add_next_index_stringl(&row, element, element_len); + } + } + } + } + add_index_zval(ret_array, pg_row, &row); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ php_pgsql_select + */ + PHP_PGSQL_API int php_pgsql_select(PGconn *pg_link, const char *table, zval *ids_array, zval *ret_array, zend_ulong opt, long result_type, zend_string **sql) +{ + zval ids_converted; + smart_str querystr = {0}; + int ret = FAILURE; + PGresult *pg_result; + + assert(pg_link != NULL); + assert(table != NULL); + assert(Z_TYPE_P(ids_array) == IS_ARRAY); + assert(Z_TYPE_P(ret_array) == IS_ARRAY); + assert(!(opt & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_ASYNC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE))); + + if (zend_hash_num_elements(Z_ARRVAL_P(ids_array)) == 0) { + return FAILURE; + } + + ZVAL_UNDEF(&ids_converted); + if (!(opt & (PGSQL_DML_NO_CONV|PGSQL_DML_ESCAPE))) { + array_init(&ids_converted); + if (php_pgsql_convert(pg_link, table, ids_array, &ids_converted, (opt & PGSQL_CONV_OPTS)) == FAILURE) { + goto cleanup; + } + ids_array = &ids_converted; + } + + smart_str_appends(&querystr, "SELECT * FROM "); + build_tablename(&querystr, pg_link, table); + smart_str_appends(&querystr, " WHERE "); + + if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) + goto cleanup; + + smart_str_appendc(&querystr, ';'); + smart_str_0(&querystr); + + pg_result = PQexec(pg_link, ZSTR_VAL(querystr.s)); + if (PQresultStatus(pg_result) == PGRES_TUPLES_OK) { + ret = php_pgsql_result2array(pg_result, ret_array, result_type); + } else { + php_error_docref(NULL, E_NOTICE, "Failed to execute '%s'", ZSTR_VAL(querystr.s)); + } + PQclear(pg_result); + +cleanup: + zval_ptr_dtor(&ids_converted); + if (ret == SUCCESS && (opt & PGSQL_DML_STRING)) { + *sql = querystr.s; + } + else { + smart_str_free(&querystr); + } + return ret; +} +/* }}} */ + +/* {{{ proto mixed pg_select(resource db, string table, array ids[, int options [, int result_type]) + Select records that has ids (id=>value) */ +PHP_FUNCTION(pg_select) +{ + zval *pgsql_link, *ids; + char *table; + size_t table_len; + zend_ulong option = PGSQL_DML_EXEC; + long result_type = PGSQL_ASSOC; + PGconn *pg_link; + zend_string *sql = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rsa|l", + &pgsql_link, &table, &table_len, &ids, &option, &result_type) == FAILURE) { + return; + } + if (option & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_ASYNC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) { + php_error_docref(NULL, E_WARNING, "Invalid option is specified"); + RETURN_FALSE; + } + if (!(result_type & PGSQL_BOTH)) { + php_error_docref(NULL, E_WARNING, "Invalid result type"); + RETURN_FALSE; + } + + if ((pg_link = (PGconn *)zend_fetch_resource2(Z_RES_P(pgsql_link), "PostgreSQL link", le_link, le_plink)) == NULL) { + RETURN_FALSE; + } + + if (php_pgsql_flush_query(pg_link)) { + php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); + } + array_init(return_value); + if (php_pgsql_select(pg_link, table, ids, return_value, option, result_type, &sql) == FAILURE) { + zval_ptr_dtor(return_value); + RETURN_FALSE; + } + if (option & PGSQL_DML_STRING) { + zval_ptr_dtor(return_value); + RETURN_STR(sql); + } + return; +} +/* }}} */ + +#endif diff --git a/ext/pgsql/tests/bug81720.phpt b/ext/pgsql/tests/bug81720.phpt new file mode 100644 index 0000000000000..d79f1fcdd6128 --- /dev/null +++ b/ext/pgsql/tests/bug81720.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #81720 (Uninitialized array in pg_query_params() leading to RCE) +--SKIPIF-- +<?php include("skipif.inc"); ?> +--FILE-- +<?php +include('config.inc'); + +$conn = pg_connect($conn_str); + +try { + pg_query_params($conn, 'SELECT $1, $2', [1, new stdClass()]); +} catch (Throwable $ex) { + echo $ex->getMessage(), PHP_EOL; +} + +try { + pg_send_prepare($conn, "my_query", 'SELECT $1, $2'); + pg_get_result($conn); + pg_send_execute($conn, "my_query", [1, new stdClass()]); +} catch (Throwable $ex) { + echo $ex->getMessage(), PHP_EOL; +} +?> +--EXPECT-- +Object of class stdClass could not be converted to string +Object of class stdClass could not be converted to string From a1c1796b5f52f9fddc104a53262966daa3ff5826 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:14 +0000 Subject: [PATCH 05/11] commit patch 18536304 --- ext/mysqlnd/mysqlnd_wireprotocol.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index b9a91c900e7e4..04b964a287507 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -771,7 +771,8 @@ php_mysqlnd_change_auth_response_write(MYSQLND_CONN_DATA * conn, void * _packet) MYSQLND_VIO * vio = conn->vio; MYSQLND_STATS * stats = conn->stats; MYSQLND_CONNECTION_STATE * connection_state = &conn->state; - zend_uchar * const buffer = pfc->cmd_buffer.length >= packet->auth_data_len? pfc->cmd_buffer.buffer : mnd_emalloc(packet->auth_data_len); + size_t total_packet_size = packet->auth_data_len + MYSQLND_HEADER_SIZE; + zend_uchar * const buffer = pfc->cmd_buffer.length >= total_packet_size? pfc->cmd_buffer.buffer : mnd_emalloc(total_packet_size); zend_uchar * p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */ DBG_ENTER("php_mysqlnd_change_auth_response_write"); From eedd042ac1c7843378cfd88e60b0ac1501557644 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:16 +0000 Subject: [PATCH 06/11] commit patch 21451244 --- ext/gd/gd.c | 7 + ext/gd/gd.c.orig | 5197 ++++++++++++++++++++++++++++++++++++ ext/gd/tests/bug81739.phpt | 24 + 3 files changed, 5228 insertions(+) create mode 100644 ext/gd/gd.c.orig create mode 100644 ext/gd/tests/bug81739.phpt diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 3480699a70b92..fa5b5c2ce3c9c 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1475,6 +1475,12 @@ PHP_FUNCTION(imageloadfont) font->w = FLIPWORD(font->w); font->h = FLIPWORD(font->h); font->nchars = FLIPWORD(font->nchars); + if (overflow2(font->nchars, font->h) || overflow2(font->nchars * font->h, font->w )) { + php_error_docref(NULL, E_WARNING, "Error reading font, invalid font header"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } body_size = font->w * font->h * font->nchars; } @@ -1485,6 +1491,7 @@ PHP_FUNCTION(imageloadfont) RETURN_FALSE; } + ZEND_ASSERT(body_size > 0); font->data = emalloc(body_size); b = 0; while (b < body_size && (n = php_stream_read(stream, &font->data[b], body_size - b)) > 0) { diff --git a/ext/gd/gd.c.orig b/ext/gd/gd.c.orig new file mode 100644 index 0000000000000..3480699a70b92 --- /dev/null +++ b/ext/gd/gd.c.orig @@ -0,0 +1,5197 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf <rasmus@php.net> | + | Stig Bakken <ssb@php.net> | + | Jim Winstead <jimw@php.net> | + +----------------------------------------------------------------------+ + */ + +/* gd 1.2 is copyright 1994, 1995, Quest Protein Database Center, + Cold Spring Harbor Labs. */ + +/* Note that there is no code from the gd package in this file */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/head.h" +#include <math.h> +#include "SAPI.h" +#include "php_gd.h" +#include "ext/standard/info.h" +#include "php_open_temporary_file.h" + + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef PHP_WIN32 +# include <io.h> +# include <fcntl.h> +# include <windows.h> +# include <Winuser.h> +# include <Wingdi.h> +#endif + +#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED) +# include <X11/xpm.h> +#endif + +# include "gd_compat.h" + + +static int le_gd, le_gd_font; + +#include <gd.h> +#include <gd_errors.h> +#include <gdfontt.h> /* 1 Tiny font */ +#include <gdfonts.h> /* 2 Small font */ +#include <gdfontmb.h> /* 3 Medium bold font */ +#include <gdfontl.h> /* 4 Large font */ +#include <gdfontg.h> /* 5 Giant font */ + +#if defined(HAVE_GD_FREETYPE) && defined(HAVE_GD_BUNDLED) +# include <ft2build.h> +# include FT_FREETYPE_H +#endif + +#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED) +# include "X11/xpm.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef HAVE_GD_FREETYPE +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int, int); +#endif + +#include "gd_ctx.c" + +/* as it is not really public, duplicate declaration here to avoid + pointless warnings */ +int overflow2(int a, int b); + +/* Section Filters Declarations */ +/* IMPORTANT NOTE FOR NEW FILTER + * Do not forget to update: + * IMAGE_FILTER_MAX: define the last filter index + * IMAGE_FILTER_MAX_ARGS: define the biggest amount of arguments + * image_filter array in PHP_FUNCTION(imagefilter) + * */ +#define IMAGE_FILTER_NEGATE 0 +#define IMAGE_FILTER_GRAYSCALE 1 +#define IMAGE_FILTER_BRIGHTNESS 2 +#define IMAGE_FILTER_CONTRAST 3 +#define IMAGE_FILTER_COLORIZE 4 +#define IMAGE_FILTER_EDGEDETECT 5 +#define IMAGE_FILTER_EMBOSS 6 +#define IMAGE_FILTER_GAUSSIAN_BLUR 7 +#define IMAGE_FILTER_SELECTIVE_BLUR 8 +#define IMAGE_FILTER_MEAN_REMOVAL 9 +#define IMAGE_FILTER_SMOOTH 10 +#define IMAGE_FILTER_PIXELATE 11 +#define IMAGE_FILTER_SCATTER 12 +#define IMAGE_FILTER_MAX 12 +#define IMAGE_FILTER_MAX_ARGS 6 +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS); + +/* End Section filters declarations */ +static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)()); +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()); +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()); +static int _php_image_type(char data[12]); +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_gd_info, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageloadfont, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetstyle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, styles) /* ARRAY_INFO(0, styles, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatetruecolor, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageistruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetruecolortopalette, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, ditherFlag) + ZEND_ARG_INFO(0, colorsWanted) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettetotruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolormatch, 0) + ZEND_ARG_INFO(0, im1) + ZEND_ARG_INFO(0, im2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetthickness, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, thickness) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledarc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, style) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagealphablending, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, blend) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesavealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, save) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagelayereffect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, effect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocatealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolvealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosestalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexactalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresampled, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +#ifdef PHP_WIN32 +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegrabwindow, 0, 0, 1) + ZEND_ARG_INFO(0, handle) + ZEND_ARG_INFO(0, client_area) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegrabscreen, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagerotate, 0, 0, 3) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, bgdcolor) + ZEND_ARG_INFO(0, ignoretransparent) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesettile, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, tile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetbrush, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, brush) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreate, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetypes, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromstring, 0) + ZEND_ARG_INFO(0, image) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgif, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromjpeg, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefrompng, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwebp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxbm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_XPM) +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxpm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwbmp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2part, 0) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, srcX) + ZEND_ARG_INFO(0, srcY) + ZEND_ARG_INFO(0, width) + ZEND_ARG_INFO(0, height) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_BMP) +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefrombmp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_GD_TGA) +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromtga, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagexbm, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegif, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagepng, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, quality) + ZEND_ARG_INFO(0, filters) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewebp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, quality) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagejpeg, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, quality) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd2, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, chunk_size) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_BMP) +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagebmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, compressed) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagedestroy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettecopy, 0) + ZEND_ARG_INFO(0, dst) + ZEND_ARG_INFO(0, src) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorat, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosest, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosesthwb, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolordeallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolve, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexact, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolorset, 0, 0, 5) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, color) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorsforindex, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegammacorrect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, inputgamma) + ZEND_ARG_INFO(0, outputgamma) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetpixel, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagedashedline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagerectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledrectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagearc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilltoborder, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, border) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefill, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorstotal, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolortransparent, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageinterlace, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, interlace) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageopenpolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledpolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontwidth, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontheight, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagechar, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecharup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestring, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestringup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopy, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymerge, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymergegray, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresized, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesx, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetclip, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegetclip, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_FREETYPE +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageftbbox, 0, 0, 4) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefttext, 0, 0, 8) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagettfbbox, 0) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagettftext, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_image2wbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_JPG) +ZEND_BEGIN_ARG_INFO(arginfo_jpeg2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_GD_PNG) +ZEND_BEGIN_ARG_INFO(arginfo_png2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefilter, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filtertype) + ZEND_ARG_INFO(0, arg1) + ZEND_ARG_INFO(0, arg2) + ZEND_ARG_INFO(0, arg3) + ZEND_ARG_INFO(0, arg4) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageconvolution, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, matrix3x3) /* ARRAY_INFO(0, matrix3x3, 0) */ + ZEND_ARG_INFO(0, div) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageflip, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageantialias, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, on) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecrop, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, rect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecropauto, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, threshold) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagescale, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, new_width) + ZEND_ARG_INFO(0, new_height) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffine, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, affine) + ZEND_ARG_INFO(0, clip) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffinematrixget, 0, 0, 1) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageaffinematrixconcat, 0) + ZEND_ARG_INFO(0, m1) + ZEND_ARG_INFO(0, m2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagesetinterpolation, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageresolution, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, res_x) + ZEND_ARG_INFO(0, res_y) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ gd_functions[] + */ +static const zend_function_entry gd_functions[] = { + PHP_FE(gd_info, arginfo_gd_info) + PHP_FE(imagearc, arginfo_imagearc) + PHP_FE(imageellipse, arginfo_imageellipse) + PHP_FE(imagechar, arginfo_imagechar) + PHP_FE(imagecharup, arginfo_imagecharup) + PHP_FE(imagecolorat, arginfo_imagecolorat) + PHP_FE(imagecolorallocate, arginfo_imagecolorallocate) + PHP_FE(imagepalettecopy, arginfo_imagepalettecopy) + PHP_FE(imagecreatefromstring, arginfo_imagecreatefromstring) + PHP_FE(imagecolorclosest, arginfo_imagecolorclosest) + PHP_FE(imagecolorclosesthwb, arginfo_imagecolorclosesthwb) + PHP_FE(imagecolordeallocate, arginfo_imagecolordeallocate) + PHP_FE(imagecolorresolve, arginfo_imagecolorresolve) + PHP_FE(imagecolorexact, arginfo_imagecolorexact) + PHP_FE(imagecolorset, arginfo_imagecolorset) + PHP_FE(imagecolortransparent, arginfo_imagecolortransparent) + PHP_FE(imagecolorstotal, arginfo_imagecolorstotal) + PHP_FE(imagecolorsforindex, arginfo_imagecolorsforindex) + PHP_FE(imagecopy, arginfo_imagecopy) + PHP_FE(imagecopymerge, arginfo_imagecopymerge) + PHP_FE(imagecopymergegray, arginfo_imagecopymergegray) + PHP_FE(imagecopyresized, arginfo_imagecopyresized) + PHP_FE(imagecreate, arginfo_imagecreate) + PHP_FE(imagecreatetruecolor, arginfo_imagecreatetruecolor) + PHP_FE(imageistruecolor, arginfo_imageistruecolor) + PHP_FE(imagetruecolortopalette, arginfo_imagetruecolortopalette) + PHP_FE(imagepalettetotruecolor, arginfo_imagepalettetotruecolor) + PHP_FE(imagesetthickness, arginfo_imagesetthickness) + PHP_FE(imagefilledarc, arginfo_imagefilledarc) + PHP_FE(imagefilledellipse, arginfo_imagefilledellipse) + PHP_FE(imagealphablending, arginfo_imagealphablending) + PHP_FE(imagesavealpha, arginfo_imagesavealpha) + PHP_FE(imagecolorallocatealpha, arginfo_imagecolorallocatealpha) + PHP_FE(imagecolorresolvealpha, arginfo_imagecolorresolvealpha) + PHP_FE(imagecolorclosestalpha, arginfo_imagecolorclosestalpha) + PHP_FE(imagecolorexactalpha, arginfo_imagecolorexactalpha) + PHP_FE(imagecopyresampled, arginfo_imagecopyresampled) + +#ifdef PHP_WIN32 + PHP_FE(imagegrabwindow, arginfo_imagegrabwindow) + PHP_FE(imagegrabscreen, arginfo_imagegrabscreen) +#endif + + PHP_FE(imagerotate, arginfo_imagerotate) + PHP_FE(imageflip, arginfo_imageflip) + + PHP_FE(imageantialias, arginfo_imageantialias) + PHP_FE(imagecrop, arginfo_imagecrop) + PHP_FE(imagecropauto, arginfo_imagecropauto) + PHP_FE(imagescale, arginfo_imagescale) + PHP_FE(imageaffine, arginfo_imageaffine) + PHP_FE(imageaffinematrixconcat, arginfo_imageaffinematrixconcat) + PHP_FE(imageaffinematrixget, arginfo_imageaffinematrixget) + PHP_FE(imagesetinterpolation, arginfo_imagesetinterpolation) + PHP_FE(imagesettile, arginfo_imagesettile) + PHP_FE(imagesetbrush, arginfo_imagesetbrush) + PHP_FE(imagesetstyle, arginfo_imagesetstyle) + +#ifdef HAVE_GD_PNG + PHP_FE(imagecreatefrompng, arginfo_imagecreatefrompng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagecreatefromwebp, arginfo_imagecreatefromwebp) +#endif + PHP_FE(imagecreatefromgif, arginfo_imagecreatefromgif) +#ifdef HAVE_GD_JPG + PHP_FE(imagecreatefromjpeg, arginfo_imagecreatefromjpeg) +#endif + PHP_FE(imagecreatefromwbmp, arginfo_imagecreatefromwbmp) + PHP_FE(imagecreatefromxbm, arginfo_imagecreatefromxbm) +#if defined(HAVE_GD_XPM) + PHP_FE(imagecreatefromxpm, arginfo_imagecreatefromxpm) +#endif + PHP_FE(imagecreatefromgd, arginfo_imagecreatefromgd) + PHP_FE(imagecreatefromgd2, arginfo_imagecreatefromgd2) + PHP_FE(imagecreatefromgd2part, arginfo_imagecreatefromgd2part) +#ifdef HAVE_GD_BMP + PHP_FE(imagecreatefrombmp, arginfo_imagecreatefrombmp) +#endif +#ifdef HAVE_GD_TGA + PHP_FE(imagecreatefromtga, arginfo_imagecreatefromtga) +#endif +#ifdef HAVE_GD_PNG + PHP_FE(imagepng, arginfo_imagepng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagewebp, arginfo_imagewebp) +#endif + PHP_FE(imagegif, arginfo_imagegif) +#ifdef HAVE_GD_JPG + PHP_FE(imagejpeg, arginfo_imagejpeg) +#endif + PHP_FE(imagewbmp, arginfo_imagewbmp) + PHP_FE(imagegd, arginfo_imagegd) + PHP_FE(imagegd2, arginfo_imagegd2) +#ifdef HAVE_GD_BMP + PHP_FE(imagebmp, arginfo_imagebmp) +#endif + + PHP_FE(imagedestroy, arginfo_imagedestroy) + PHP_FE(imagegammacorrect, arginfo_imagegammacorrect) + PHP_FE(imagefill, arginfo_imagefill) + PHP_FE(imagefilledpolygon, arginfo_imagefilledpolygon) + PHP_FE(imagefilledrectangle, arginfo_imagefilledrectangle) + PHP_FE(imagefilltoborder, arginfo_imagefilltoborder) + PHP_FE(imagefontwidth, arginfo_imagefontwidth) + PHP_FE(imagefontheight, arginfo_imagefontheight) + PHP_FE(imageinterlace, arginfo_imageinterlace) + PHP_FE(imageline, arginfo_imageline) + PHP_FE(imageloadfont, arginfo_imageloadfont) + PHP_FE(imagepolygon, arginfo_imagepolygon) + PHP_FE(imageopenpolygon, arginfo_imageopenpolygon) + PHP_FE(imagerectangle, arginfo_imagerectangle) + PHP_FE(imagesetpixel, arginfo_imagesetpixel) + PHP_FE(imagestring, arginfo_imagestring) + PHP_FE(imagestringup, arginfo_imagestringup) + PHP_FE(imagesx, arginfo_imagesx) + PHP_FE(imagesy, arginfo_imagesy) + PHP_FE(imagesetclip, arginfo_imagesetclip) + PHP_FE(imagegetclip, arginfo_imagegetclip) + PHP_FE(imagedashedline, arginfo_imagedashedline) + +#ifdef HAVE_GD_FREETYPE + PHP_FE(imagettfbbox, arginfo_imagettfbbox) + PHP_FE(imagettftext, arginfo_imagettftext) + PHP_FE(imageftbbox, arginfo_imageftbbox) + PHP_FE(imagefttext, arginfo_imagefttext) +#endif + + PHP_FE(imagetypes, arginfo_imagetypes) + +#if defined(HAVE_GD_JPG) + PHP_DEP_FE(jpeg2wbmp, arginfo_jpeg2wbmp) +#endif +#if defined(HAVE_GD_PNG) + PHP_DEP_FE(png2wbmp, arginfo_png2wbmp) +#endif + PHP_DEP_FE(image2wbmp, arginfo_image2wbmp) + PHP_FE(imagelayereffect, arginfo_imagelayereffect) + PHP_FE(imagexbm, arginfo_imagexbm) + + PHP_FE(imagecolormatch, arginfo_imagecolormatch) + +/* gd filters */ + PHP_FE(imagefilter, arginfo_imagefilter) + PHP_FE(imageconvolution, arginfo_imageconvolution) + + PHP_FE(imageresolution, arginfo_imageresolution) + + PHP_FE_END +}; +/* }}} */ + +zend_module_entry gd_module_entry = { + STANDARD_MODULE_HEADER, + "gd", + gd_functions, + PHP_MINIT(gd), + PHP_MSHUTDOWN(gd), + NULL, + PHP_RSHUTDOWN(gd), + PHP_MINFO(gd), + PHP_GD_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_GD +ZEND_GET_MODULE(gd) +#endif + +/* {{{ PHP_INI_BEGIN */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("gd.jpeg_ignore_warning", "1", PHP_INI_ALL, NULL) +PHP_INI_END() +/* }}} */ + +/* {{{ php_free_gd_image + */ +static void php_free_gd_image(zend_resource *rsrc) +{ + gdImageDestroy((gdImagePtr) rsrc->ptr); +} +/* }}} */ + +/* {{{ php_free_gd_font + */ +static void php_free_gd_font(zend_resource *rsrc) +{ + gdFontPtr fp = (gdFontPtr) rsrc->ptr; + + if (fp->data) { + efree(fp->data); + } + + efree(fp); +} +/* }}} */ + +/* {{{ php_gd_error_method + */ +void php_gd_error_method(int type, const char *format, va_list args) +{ + + switch (type) { +#ifndef PHP_WIN32 + case GD_DEBUG: + case GD_INFO: +#endif + case GD_NOTICE: + type = E_NOTICE; + break; + case GD_WARNING: + type = E_WARNING; + break; + default: + type = E_ERROR; + } + php_verror(NULL, "", type, format, args); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(gd) +{ + le_gd = zend_register_list_destructors_ex(php_free_gd_image, NULL, "gd", module_number); + le_gd_font = zend_register_list_destructors_ex(php_free_gd_font, NULL, "gd font", module_number); + +#if defined(HAVE_GD_FREETYPE) && defined(HAVE_GD_BUNDLED) + gdFontCacheMutexSetup(); +#endif + gdSetErrorMethod(php_gd_error_method); + + REGISTER_INI_ENTRIES(); + + REGISTER_LONG_CONSTANT("IMG_GIF", PHP_IMG_GIF, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPG", PHP_IMG_JPG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPEG", PHP_IMG_JPEG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_PNG", PHP_IMG_PNG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WBMP", PHP_IMG_WBMP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_XPM", PHP_IMG_XPM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WEBP", PHP_IMG_WEBP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BMP", PHP_IMG_BMP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_TGA", PHP_IMG_TGA, CONST_CS | CONST_PERSISTENT); + + /* special colours for gd */ + REGISTER_LONG_CONSTANT("IMG_COLOR_TILED", gdTiled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLED", gdStyled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_BRUSHED", gdBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLEDBRUSHED", gdStyledBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_TRANSPARENT", gdTransparent, CONST_CS | CONST_PERSISTENT); + + /* for imagefilledarc */ + REGISTER_LONG_CONSTANT("IMG_ARC_ROUNDED", gdArc, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_PIE", gdPie, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_CHORD", gdChord, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_NOFILL", gdNoFill, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_EDGED", gdEdged, CONST_CS | CONST_PERSISTENT); + + /* GD2 image format types */ + REGISTER_LONG_CONSTANT("IMG_GD2_RAW", GD2_FMT_RAW, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GD2_COMPRESSED", GD2_FMT_COMPRESSED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_HORIZONTAL", GD_FLIP_HORINZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_VERTICAL", GD_FLIP_VERTICAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_BOTH", GD_FLIP_BOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_REPLACE", gdEffectReplace, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_ALPHABLEND", gdEffectAlphaBlend, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_NORMAL", gdEffectNormal, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_OVERLAY", gdEffectOverlay, CONST_CS | CONST_PERSISTENT); +#ifdef gdEffectMultiply + REGISTER_LONG_CONSTANT("IMG_EFFECT_MULTIPLY", gdEffectMultiply, CONST_CS | CONST_PERSISTENT); +#endif + + REGISTER_LONG_CONSTANT("IMG_CROP_DEFAULT", GD_CROP_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_TRANSPARENT", GD_CROP_TRANSPARENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_BLACK", GD_CROP_BLACK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_WHITE", GD_CROP_WHITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_SIDES", GD_CROP_SIDES, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_THRESHOLD", GD_CROP_THRESHOLD, CONST_CS | CONST_PERSISTENT); + + + REGISTER_LONG_CONSTANT("IMG_BELL", GD_BELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BESSEL", GD_BESSEL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BILINEAR_FIXED", GD_BILINEAR_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC", GD_BICUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC_FIXED", GD_BICUBIC_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BLACKMAN", GD_BLACKMAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BOX", GD_BOX, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BSPLINE", GD_BSPLINE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CATMULLROM", GD_CATMULLROM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GAUSSIAN", GD_GAUSSIAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GENERALIZED_CUBIC", GD_GENERALIZED_CUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HERMITE", GD_HERMITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HAMMING", GD_HAMMING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HANNING", GD_HANNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_MITCHELL", GD_MITCHELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_POWER", GD_POWER, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_QUADRATIC", GD_QUADRATIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_SINC", GD_SINC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_NEAREST_NEIGHBOUR", GD_NEAREST_NEIGHBOUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WEIGHTED4", GD_WEIGHTED4, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_TRIANGLE", GD_TRIANGLE, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_AFFINE_TRANSLATE", GD_AFFINE_TRANSLATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SCALE", GD_AFFINE_SCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_ROTATE", GD_AFFINE_ROTATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_HORIZONTAL", GD_AFFINE_SHEAR_HORIZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_VERTICAL", GD_AFFINE_SHEAR_VERTICAL, CONST_CS | CONST_PERSISTENT); + +#if defined(HAVE_GD_BUNDLED) + REGISTER_LONG_CONSTANT("GD_BUNDLED", 1, CONST_CS | CONST_PERSISTENT); +#else + REGISTER_LONG_CONSTANT("GD_BUNDLED", 0, CONST_CS | CONST_PERSISTENT); +#endif + + /* Section Filters */ + REGISTER_LONG_CONSTANT("IMG_FILTER_NEGATE", IMAGE_FILTER_NEGATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GRAYSCALE", IMAGE_FILTER_GRAYSCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_BRIGHTNESS", IMAGE_FILTER_BRIGHTNESS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_CONTRAST", IMAGE_FILTER_CONTRAST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_COLORIZE", IMAGE_FILTER_COLORIZE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EDGEDETECT", IMAGE_FILTER_EDGEDETECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GAUSSIAN_BLUR", IMAGE_FILTER_GAUSSIAN_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SELECTIVE_BLUR", IMAGE_FILTER_SELECTIVE_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EMBOSS", IMAGE_FILTER_EMBOSS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_MEAN_REMOVAL", IMAGE_FILTER_MEAN_REMOVAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SMOOTH", IMAGE_FILTER_SMOOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_PIXELATE", IMAGE_FILTER_PIXELATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SCATTER", IMAGE_FILTER_SCATTER, CONST_CS | CONST_PERSISTENT); + /* End Section Filters */ + +#ifdef GD_VERSION_STRING + REGISTER_STRING_CONSTANT("GD_VERSION", GD_VERSION_STRING, CONST_CS | CONST_PERSISTENT); +#endif + +#if defined(GD_MAJOR_VERSION) && defined(GD_MINOR_VERSION) && defined(GD_RELEASE_VERSION) && defined(GD_EXTRA_VERSION) + REGISTER_LONG_CONSTANT("GD_MAJOR_VERSION", GD_MAJOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_MINOR_VERSION", GD_MINOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_RELEASE_VERSION", GD_RELEASE_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("GD_EXTRA_VERSION", GD_EXTRA_VERSION, CONST_CS | CONST_PERSISTENT); +#endif + + +#ifdef HAVE_GD_PNG + + /* + * cannot include #include "png.h" + * /usr/include/pngconf.h:310:2: error: #error png.h already includes setjmp.h with some additional fixup. + * as error, use the values for now... + */ + REGISTER_LONG_CONSTANT("PNG_NO_FILTER", 0x00, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_NONE", 0x08, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_SUB", 0x10, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_UP", 0x20, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_AVG", 0x40, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_PAETH", 0x80, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_ALL_FILTERS", 0x08 | 0x10 | 0x20 | 0x40 | 0x80, CONST_CS | CONST_PERSISTENT); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(gd) +{ +#if defined(HAVE_GD_FREETYPE) && defined(HAVE_GD_BUNDLED) + gdFontCacheMutexShutdown(); +#endif + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(gd) +{ +#ifdef HAVE_GD_FREETYPE + gdFontCacheShutdown(); +#endif + return SUCCESS; +} +/* }}} */ + +#if defined(HAVE_GD_BUNDLED) +#define PHP_GD_VERSION_STRING "bundled (2.1.0 compatible)" +#else +# define PHP_GD_VERSION_STRING GD_VERSION_STRING +#endif + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(gd) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "GD Support", "enabled"); + + /* need to use a PHPAPI function here because it is external module in windows */ + +#if defined(HAVE_GD_BUNDLED) + php_info_print_table_row(2, "GD Version", PHP_GD_VERSION_STRING); +#else + php_info_print_table_row(2, "GD headers Version", PHP_GD_VERSION_STRING); +#if defined(HAVE_GD_LIBVERSION) + php_info_print_table_row(2, "GD library Version", gdVersionString()); +#endif +#endif + +#ifdef HAVE_GD_FREETYPE + php_info_print_table_row(2, "FreeType Support", "enabled"); + php_info_print_table_row(2, "FreeType Linkage", "with freetype"); +#ifdef HAVE_GD_BUNDLED + { + char tmp[256]; + +#ifdef FREETYPE_PATCH + snprintf(tmp, sizeof(tmp), "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); +#elif defined(FREETYPE_MAJOR) + snprintf(tmp, sizeof(tmp), "%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR); +#else + snprintf(tmp, sizeof(tmp), "1.x"); +#endif + php_info_print_table_row(2, "FreeType Version", tmp); + } +#endif +#endif + + php_info_print_table_row(2, "GIF Read Support", "enabled"); + php_info_print_table_row(2, "GIF Create Support", "enabled"); + +#ifdef HAVE_GD_JPG + { + php_info_print_table_row(2, "JPEG Support", "enabled"); +#if defined(HAVE_GD_BUNDLED) + php_info_print_table_row(2, "libJPEG Version", gdJpegGetVersionString()); +#endif + } +#endif + +#ifdef HAVE_GD_PNG + php_info_print_table_row(2, "PNG Support", "enabled"); +#if defined(HAVE_GD_BUNDLED) + php_info_print_table_row(2, "libPNG Version", gdPngGetVersionString()); +#endif +#endif + php_info_print_table_row(2, "WBMP Support", "enabled"); +#if defined(HAVE_GD_XPM) + php_info_print_table_row(2, "XPM Support", "enabled"); +#if defined(HAVE_GD_BUNDLED) + { + char tmp[12]; + snprintf(tmp, sizeof(tmp), "%d", XpmLibraryVersion()); + php_info_print_table_row(2, "libXpm Version", tmp); + } +#endif +#endif + php_info_print_table_row(2, "XBM Support", "enabled"); +#if defined(USE_GD_JISX0208) + php_info_print_table_row(2, "JIS-mapped Japanese Font Support", "enabled"); +#endif +#ifdef HAVE_GD_WEBP + php_info_print_table_row(2, "WebP Support", "enabled"); +#endif +#ifdef HAVE_GD_BMP + php_info_print_table_row(2, "BMP Support", "enabled"); +#endif +#ifdef HAVE_GD_TGA + php_info_print_table_row(2, "TGA Read Support", "enabled"); +#endif + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ proto array gd_info() + */ +PHP_FUNCTION(gd_info) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + add_assoc_string(return_value, "GD Version", PHP_GD_VERSION_STRING); + +#ifdef HAVE_GD_FREETYPE + add_assoc_bool(return_value, "FreeType Support", 1); + add_assoc_string(return_value, "FreeType Linkage", "with freetype"); +#else + add_assoc_bool(return_value, "FreeType Support", 0); +#endif + add_assoc_bool(return_value, "GIF Read Support", 1); + add_assoc_bool(return_value, "GIF Create Support", 1); +#ifdef HAVE_GD_JPG + add_assoc_bool(return_value, "JPEG Support", 1); +#else + add_assoc_bool(return_value, "JPEG Support", 0); +#endif +#ifdef HAVE_GD_PNG + add_assoc_bool(return_value, "PNG Support", 1); +#else + add_assoc_bool(return_value, "PNG Support", 0); +#endif + add_assoc_bool(return_value, "WBMP Support", 1); +#if defined(HAVE_GD_XPM) + add_assoc_bool(return_value, "XPM Support", 1); +#else + add_assoc_bool(return_value, "XPM Support", 0); +#endif + add_assoc_bool(return_value, "XBM Support", 1); +#ifdef HAVE_GD_WEBP + add_assoc_bool(return_value, "WebP Support", 1); +#else + add_assoc_bool(return_value, "WebP Support", 0); +#endif +#ifdef HAVE_GD_BMP + add_assoc_bool(return_value, "BMP Support", 1); +#else + add_assoc_bool(return_value, "BMP Support", 0); +#endif +#ifdef HAVE_GD_TGA + add_assoc_bool(return_value, "TGA Read Support", 1); +#else + add_assoc_bool(return_value, "TGA Read Support", 0); +#endif +#if defined(USE_GD_JISX0208) + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 1); +#else + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 0); +#endif +} +/* }}} */ + +/* Need this for cpdf. See also comment in file.c php3i_get_le_fp() */ +PHP_GD_API int phpi_get_le_gd(void) +{ + return le_gd; +} +/* }}} */ + +#define FLIPWORD(a) (((a & 0xff000000) >> 24) | ((a & 0x00ff0000) >> 8) | ((a & 0x0000ff00) << 8) | ((a & 0x000000ff) << 24)) + +/* {{{ proto int imageloadfont(string filename) + Load a new font */ +PHP_FUNCTION(imageloadfont) +{ + zval *ind; + zend_string *file; + int hdr_size = sizeof(gdFont) - sizeof(char *); + int body_size, n = 0, b, i, body_size_check; + gdFontPtr font; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &file) == FAILURE) { + return; + } + + stream = php_stream_open_wrapper(ZSTR_VAL(file), "rb", IGNORE_PATH | IGNORE_URL_WIN | REPORT_ERRORS, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* Only supports a architecture-dependent binary dump format + * at the moment. + * The file format is like this on machines with 32-byte integers: + * + * byte 0-3: (int) number of characters in the font + * byte 4-7: (int) value of first character in the font (often 32, space) + * byte 8-11: (int) pixel width of each character + * byte 12-15: (int) pixel height of each character + * bytes 16-: (char) array with character data, one byte per pixel + * in each character, for a total of + * (nchars*width*height) bytes. + */ + font = (gdFontPtr) emalloc(sizeof(gdFont)); + b = 0; + while (b < hdr_size && (n = php_stream_read(stream, (char*)&font[b], hdr_size - b)) > 0) { + b += n; + } + + if (n <= 0) { + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading header"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading header"); + } + php_stream_close(stream); + RETURN_FALSE; + } + i = php_stream_tell(stream); + php_stream_seek(stream, 0, SEEK_END); + body_size_check = php_stream_tell(stream) - hdr_size; + php_stream_seek(stream, i, SEEK_SET); + + if (overflow2(font->nchars, font->h) || overflow2(font->nchars * font->h, font->w )) { + php_error_docref(NULL, E_WARNING, "Error reading font, invalid font header"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + body_size = font->w * font->h * font->nchars; + if (body_size != body_size_check) { + font->w = FLIPWORD(font->w); + font->h = FLIPWORD(font->h); + font->nchars = FLIPWORD(font->nchars); + body_size = font->w * font->h * font->nchars; + } + + if (body_size != body_size_check) { + php_error_docref(NULL, E_WARNING, "Error reading font"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + font->data = emalloc(body_size); + b = 0; + while (b < body_size && (n = php_stream_read(stream, &font->data[b], body_size - b)) > 0) { + b += n; + } + + if (n <= 0) { + efree(font->data); + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading body"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading body"); + } + php_stream_close(stream); + RETURN_FALSE; + } + php_stream_close(stream); + + ind = zend_list_insert(font, le_gd_font); + + /* Adding 5 to the font index so we will never have font indices + * that overlap with the old fonts (with indices 1-5). The first + * list index given out is always 1. + */ + RETURN_LONG(Z_RES_HANDLE_P(ind) + 5); +} +/* }}} */ + +/* {{{ proto bool imagesetstyle(resource im, array styles) + Set the line drawing styles for use with imageline and IMG_COLOR_STYLED. */ +PHP_FUNCTION(imagesetstyle) +{ + zval *IM, *styles, *item; + gdImagePtr im; + int *stylearr; + int index = 0; + uint32_t num_styles; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &styles) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + num_styles = zend_hash_num_elements(Z_ARRVAL_P(styles)); + if (num_styles == 0) { + php_error_docref(NULL, E_WARNING, "styles array must not be empty"); + RETURN_FALSE; + } + + /* copy the style values in the stylearr */ + stylearr = safe_emalloc(sizeof(int), num_styles, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(styles), item) { + stylearr[index++] = zval_get_long(item); + } ZEND_HASH_FOREACH_END(); + + gdImageSetStyle(im, stylearr, index); + + efree(stylearr); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreatetruecolor(int x_size, int y_size) + Create a new true color image */ +PHP_FUNCTION(imagecreatetruecolor) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreateTrueColor(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto bool imageistruecolor(resource im) + return true if the image uses truecolor */ +PHP_FUNCTION(imageistruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(im->trueColor); +} +/* }}} */ + +/* {{{ proto void imagetruecolortopalette(resource im, bool ditherFlag, int colorsWanted) + Convert a true color image to a palette based image with a number of colors, optionally using dithering. */ +PHP_FUNCTION(imagetruecolortopalette) +{ + zval *IM; + zend_bool dither; + zend_long ncolors; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rbl", &IM, &dither, &ncolors) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (ncolors <= 0 || ZEND_LONG_INT_OVFL(ncolors)) { + php_error_docref(NULL, E_WARNING, "Number of colors has to be greater than zero and no more than %d", INT_MAX); + RETURN_FALSE; + } + if (gdImageTrueColorToPalette(im, dither, (int)ncolors)) { + RETURN_TRUE; + } else { + php_error_docref(NULL, E_WARNING, "Couldn't convert to palette"); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void imagepalettetotruecolor(resource im) + Convert a palette based image to a true color image. */ +PHP_FUNCTION(imagepalettetotruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImagePaletteToTrueColor(im) == 0) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecolormatch(resource im1, resource im2) + Makes the colors of the palette version of an image more closely match the true color version */ +PHP_FUNCTION(imagecolormatch) +{ + zval *IM1, *IM2; + gdImagePtr im1, im2; + int result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM1, &IM2) == FAILURE) { + return; + } + + if ((im1 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM1), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + if ((im2 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM2), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + result = gdImageColorMatch(im1, im2); + switch (result) { + case -1: + php_error_docref(NULL, E_WARNING, "Image1 must be TrueColor" ); + RETURN_FALSE; + break; + case -2: + php_error_docref(NULL, E_WARNING, "Image2 must be Palette" ); + RETURN_FALSE; + break; + case -3: + php_error_docref(NULL, E_WARNING, "Image1 and Image2 must be the same size" ); + RETURN_FALSE; + break; + case -4: + php_error_docref(NULL, E_WARNING, "Image2 must have at least one color" ); + RETURN_FALSE; + break; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetthickness(resource im, int thickness) + Set line thickness for drawing lines, ellipses, rectangles, polygons etc. */ +PHP_FUNCTION(imagesetthickness) +{ + zval *IM; + zend_long thick; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &thick) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetThickness(im, thick); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imagefilledellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFilledEllipse(im, cx, cy, w, h, color); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledarc(resource im, int cx, int cy, int w, int h, int s, int e, int col, int style) + Draw a filled partial ellipse */ +PHP_FUNCTION(imagefilledarc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col, style; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col, &style) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageFilledArc(im, cx, cy, w, h, st, e, col, style); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagealphablending(resource im, bool on) + Turn alpha blending mode on or off for the given image */ +PHP_FUNCTION(imagealphablending) +{ + zval *IM; + zend_bool blend; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &blend) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, blend); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesavealpha(resource im, bool on) + Include alpha channel to a saved image */ +PHP_FUNCTION(imagesavealpha) +{ + zval *IM; + zend_bool save; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &save) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSaveAlpha(im, save); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagelayereffect(resource im, int effect) + Set the alpha blending flag to use the bundled libgd layering effects */ +PHP_FUNCTION(imagelayereffect) +{ + zval *IM; + zend_long effect; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &effect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, effect); + + RETURN_TRUE; +} +/* }}} */ + +#define CHECK_RGBA_RANGE(component, name) \ + if (component < 0 || component > gd##name##Max) { \ + php_error_docref(NULL, E_WARNING, #name " component is out of range"); \ + RETURN_FALSE; \ + } + +/* {{{ proto int imagecolorallocatealpha(resource im, int red, int green, int blue, int alpha) + Allocate a color with an alpha level. Works for true color and palette based images */ +PHP_FUNCTION(imagecolorallocatealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + CHECK_RGBA_RANGE(alpha, Alpha); + + ct = gdImageColorAllocateAlpha(im, red, green, blue, alpha); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG((zend_long)ct); +} +/* }}} */ + +/* {{{ proto int imagecolorresolvealpha(resource im, int red, int green, int blue, int alpha) + Resolve/Allocate a colour with an alpha level. Works for true colour and palette based images */ +PHP_FUNCTION(imagecolorresolvealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + CHECK_RGBA_RANGE(alpha, Alpha); + + RETURN_LONG(gdImageColorResolveAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosestalpha(resource im, int red, int green, int blue, int alpha) + Find the closest matching colour with alpha transparency */ +PHP_FUNCTION(imagecolorclosestalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + CHECK_RGBA_RANGE(alpha, Alpha); + + RETURN_LONG(gdImageColorClosestAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorexactalpha(resource im, int red, int green, int blue, int alpha) + Find exact match for colour with transparency */ +PHP_FUNCTION(imagecolorexactalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + CHECK_RGBA_RANGE(alpha, Alpha); + + RETURN_LONG(gdImageColorExactAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto bool imagecopyresampled(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image using resampling to help ensure clarity */ +PHP_FUNCTION(imagecopyresampled) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + gdImageCopyResampled(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + + RETURN_TRUE; +} +/* }}} */ + +#ifdef PHP_WIN32 +/* {{{ proto resource imagegrabwindow(int window_handle [, int client_area]) + Grab a window or its client area using a windows handle (HWND property in COM instance) */ +PHP_FUNCTION(imagegrabwindow) +{ + HWND window; + zend_long client_area = 0; + RECT rc = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + zend_long lwindow_handle; + gdImagePtr im = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &lwindow_handle, &client_area) == FAILURE) { + RETURN_FALSE; + } + + window = (HWND) lwindow_handle; + + if (!IsWindow(window)) { + php_error_docref(NULL, E_NOTICE, "Invalid window handle"); + RETURN_FALSE; + } + + hdc = GetDC(0); + + if (client_area) { + GetClientRect(window, &rc); + Width = rc.right; + Height = rc.bottom; + } else { + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + } + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + + PrintWindow(window, memDC, (UINT) client_area); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagegrabscreen() + Grab a screenshot */ +PHP_FUNCTION(imagegrabscreen) +{ + HWND window = GetDesktopWindow(); + RECT rc = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + gdImagePtr im; + hdc = GetDC(0); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!hdc) { + RETURN_FALSE; + } + + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + BitBlt( memDC, 0, 0, Width, Height , hdc, rc.left, rc.top , SRCCOPY ); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ +#endif /* PHP_WIN32 */ + +/* {{{ proto resource imagerotate(resource src_im, float angle, int bgdcolor [, int ignoretransparent]) + Rotate an image using a custom angle */ +PHP_FUNCTION(imagerotate) +{ + zval *SIM; + gdImagePtr im_dst, im_src; + double degrees; + zend_long color; + zend_long ignoretransparent = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdl|l", &SIM, °rees, &color, &ignoretransparent) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + im_dst = gdImageRotateInterpolated(im_src, (const float)degrees, color); + + if (im_dst != NULL) { + RETURN_RES(zend_register_resource(im_dst, le_gd)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagesettile(resource image, resource tile) + Set the tile image to $tile when filling $image with the "IMG_COLOR_TILED" color */ +PHP_FUNCTION(imagesettile) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetTile(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetbrush(resource image, resource brush) + Set the brush image to $brush when filling $image with the "IMG_COLOR_BRUSHED" color */ +PHP_FUNCTION(imagesetbrush) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetBrush(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreate(int x_size, int y_size) + Create a new image */ +PHP_FUNCTION(imagecreate) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreate(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto int imagetypes(void) + Return the types of images supported in a bitfield - 1=GIF, 2=JPEG, 4=PNG, 8=WBMP, 16=XPM */ +PHP_FUNCTION(imagetypes) +{ + int ret = 0; + ret = PHP_IMG_GIF; +#ifdef HAVE_GD_JPG + ret |= PHP_IMG_JPG; +#endif +#ifdef HAVE_GD_PNG + ret |= PHP_IMG_PNG; +#endif + ret |= PHP_IMG_WBMP; +#if defined(HAVE_GD_XPM) + ret |= PHP_IMG_XPM; +#endif +#ifdef HAVE_GD_WEBP + ret |= PHP_IMG_WEBP; +#endif +#ifdef HAVE_GD_BMP + ret |= PHP_IMG_BMP; +#endif +#ifdef HAVE_GD_TGA + ret |= PHP_IMG_TGA; +#endif + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ _php_ctx_getmbi + */ + +static int _php_ctx_getmbi(gdIOCtx *ctx) +{ + int i, mbi = 0; + + do { + i = (ctx->getC)(ctx); + if (i < 0) { + return -1; + } + mbi = (mbi << 7) | (i & 0x7f); + } while (i & 0x80); + + return mbi; +} +/* }}} */ + +/* {{{ _php_image_type + */ +static const char php_sig_gd2[3] = {'g', 'd', '2'}; + +static int _php_image_type (char data[12]) +{ + /* Based on ext/standard/image.c */ + + if (data == NULL) { + return -1; + } + + if (!memcmp(data, php_sig_gd2, sizeof(php_sig_gd2))) { + return PHP_GDIMG_TYPE_GD2; + } else if (!memcmp(data, php_sig_jpg, sizeof(php_sig_jpg))) { + return PHP_GDIMG_TYPE_JPG; + } else if (!memcmp(data, php_sig_png, sizeof(php_sig_png))) { + return PHP_GDIMG_TYPE_PNG; + } else if (!memcmp(data, php_sig_gif, sizeof(php_sig_gif))) { + return PHP_GDIMG_TYPE_GIF; + } else if (!memcmp(data, php_sig_bmp, sizeof(php_sig_bmp))) { + return PHP_GDIMG_TYPE_BMP; + } else if(!memcmp(data, php_sig_riff, sizeof(php_sig_riff)) && !memcmp(data + sizeof(php_sig_riff) + sizeof(uint32_t), php_sig_webp, sizeof(php_sig_webp))) { + return PHP_GDIMG_TYPE_WEBP; + } + else { + gdIOCtx *io_ctx; + io_ctx = gdNewDynamicCtxEx(8, data, 0); + if (io_ctx) { + if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) { + io_ctx->gd_free(io_ctx); + return PHP_GDIMG_TYPE_WBM; + } else { + io_ctx->gd_free(io_ctx); + } + } + } + return -1; +} +/* }}} */ + +/* {{{ _php_image_create_from_string + */ +gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)()) +{ + gdImagePtr im; + gdIOCtx *io_ctx; + + io_ctx = gdNewDynamicCtxEx(Z_STRLEN_P(data), Z_STRVAL_P(data), 0); + + if (!io_ctx) { + return NULL; + } + + im = (*ioctx_func_p)(io_ctx); + if (!im) { + php_error_docref(NULL, E_WARNING, "Passed data is not in '%s' format", tn); + io_ctx->gd_free(io_ctx); + return NULL; + } + + io_ctx->gd_free(io_ctx); + + return im; +} +/* }}} */ + +/* {{{ proto resource imagecreatefromstring(string image) + Create a new image from the image stream in the string */ +PHP_FUNCTION(imagecreatefromstring) +{ + zval *data; + gdImagePtr im; + int imtype; + char sig[12]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &data) == FAILURE) { + return; + } + + if (!try_convert_to_string(data)) { + return; + } + + if (Z_STRLEN_P(data) < sizeof(sig)) { + php_error_docref(NULL, E_WARNING, "Empty string or invalid image"); + RETURN_FALSE; + } + + memcpy(sig, Z_STRVAL_P(data), sizeof(sig)); + + imtype = _php_image_type(sig); + + switch (imtype) { + case PHP_GDIMG_TYPE_JPG: +#ifdef HAVE_GD_JPG + im = _php_image_create_from_string(data, "JPEG", gdImageCreateFromJpegCtx); +#else + php_error_docref(NULL, E_WARNING, "No JPEG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_PNG: +#ifdef HAVE_GD_PNG + im = _php_image_create_from_string(data, "PNG", gdImageCreateFromPngCtx); +#else + php_error_docref(NULL, E_WARNING, "No PNG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_GIF: + im = _php_image_create_from_string(data, "GIF", gdImageCreateFromGifCtx); + break; + + case PHP_GDIMG_TYPE_WBM: + im = _php_image_create_from_string(data, "WBMP", gdImageCreateFromWBMPCtx); + break; + + case PHP_GDIMG_TYPE_GD2: + im = _php_image_create_from_string(data, "GD2", gdImageCreateFromGd2Ctx); + break; + + case PHP_GDIMG_TYPE_BMP: + im = _php_image_create_from_string(data, "BMP", gdImageCreateFromBmpCtx); + break; + + case PHP_GDIMG_TYPE_WEBP: +#ifdef HAVE_GD_WEBP + im = _php_image_create_from_string(data, "WEBP", gdImageCreateFromWebpCtx); + break; +#else + php_error_docref(NULL, E_WARNING, "No WEBP support in this PHP build"); + RETURN_FALSE; +#endif + + default: + php_error_docref(NULL, E_WARNING, "Data is not in a recognized format"); + RETURN_FALSE; + } + + if (!im) { + php_error_docref(NULL, E_WARNING, "Couldn't create GD Image Stream out of Data"); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ _php_image_create_from + */ +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()) +{ + char *file; + size_t file_len; + zend_long srcx, srcy, width, height; + gdImagePtr im = NULL; + php_stream *stream; + FILE * fp = NULL; +#ifdef HAVE_GD_JPG + long ignore_warning; +#endif + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pllll", &file, &file_len, &srcx, &srcy, &width, &height) == FAILURE) { + return; + } + if (width < 1 || height < 1) { + php_error_docref(NULL, E_WARNING, "Zero width or height not allowed"); + RETURN_FALSE; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &file, &file_len) == FAILURE) { + return; + } + } + + + stream = php_stream_open_wrapper(file, "rb", REPORT_ERRORS|IGNORE_PATH|IGNORE_URL_WIN, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* try and avoid allocating a FILE* if the stream is not naturally a FILE* */ + if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS)) { + goto out_err; + } + } else if (ioctx_func_p) { + /* we can create an io context */ + gdIOCtx* io_ctx; + zend_string *buff; + char *pstr; + + buff = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0); + + if (!buff) { + php_error_docref(NULL, E_WARNING,"Cannot read image data"); + goto out_err; + } + + /* needs to be malloc (persistent) - GD will free() it later */ + pstr = pestrndup(ZSTR_VAL(buff), ZSTR_LEN(buff), 1); + io_ctx = gdNewDynamicCtxEx(ZSTR_LEN(buff), pstr, 0); + if (!io_ctx) { + pefree(pstr, 1); + zend_string_release_ex(buff, 0); + php_error_docref(NULL, E_WARNING,"Cannot allocate GD IO context"); + goto out_err; + } + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + im = (*ioctx_func_p)(io_ctx, srcx, srcy, width, height); + } else { + im = (*ioctx_func_p)(io_ctx); + } + io_ctx->gd_free(io_ctx); + pefree(pstr, 1); + zend_string_release_ex(buff, 0); + } + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + /* try and force the stream to be FILE* */ + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { + goto out_err; + } + } + + if (!im && fp) { + switch (image_type) { + case PHP_GDIMG_TYPE_GD2PART: + im = (*func_p)(fp, srcx, srcy, width, height); + break; +#if defined(HAVE_GD_XPM) + case PHP_GDIMG_TYPE_XPM: + im = gdImageCreateFromXpm(file); + break; +#endif + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im = gdImageCreateFromJpegEx(fp, ignore_warning); + break; +#endif + + default: + im = (*func_p)(fp); + break; + } + + fflush(fp); + } + +/* register_im: */ + if (im) { + RETVAL_RES(zend_register_resource(im, le_gd)); + php_stream_close(stream); + return; + } + + php_error_docref(NULL, E_WARNING, "'%s' is not a valid %s file", file, tn); +out_err: + php_stream_close(stream); + RETURN_FALSE; + +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgif(string filename) + Create a new image from GIF file or URL */ +PHP_FUNCTION(imagecreatefromgif) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageCreateFromGif, gdImageCreateFromGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_JPG +/* {{{ proto resource imagecreatefromjpeg(string filename) + Create a new image from JPEG file or URL */ +PHP_FUNCTION(imagecreatefromjpeg) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageCreateFromJpeg, gdImageCreateFromJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG +/* {{{ proto resource imagecreatefrompng(string filename) + Create a new image from PNG file or URL */ +PHP_FUNCTION(imagecreatefrompng) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImageCreateFromPng, gdImageCreateFromPngCtx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + +#ifdef HAVE_GD_WEBP +/* {{{ proto resource imagecreatefromwebp(string filename) + Create a new image from WEBP file or URL */ +PHP_FUNCTION(imagecreatefromwebp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageCreateFromWebp, gdImageCreateFromWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + +/* {{{ proto resource imagecreatefromxbm(string filename) + Create a new image from XBM file or URL */ +PHP_FUNCTION(imagecreatefromxbm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageCreateFromXbm, NULL); +} +/* }}} */ + +#if defined(HAVE_GD_XPM) +/* {{{ proto resource imagecreatefromxpm(string filename) + Create a new image from XPM file or URL */ +PHP_FUNCTION(imagecreatefromxpm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XPM, "XPM", gdImageCreateFromXpm, NULL); +} +/* }}} */ +#endif + +/* {{{ proto resource imagecreatefromwbmp(string filename) + Create a new image from WBMP file or URL */ +PHP_FUNCTION(imagecreatefromwbmp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageCreateFromWBMP, gdImageCreateFromWBMPCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd(string filename) + Create a new image from GD file or URL */ +PHP_FUNCTION(imagecreatefromgd) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageCreateFromGd, gdImageCreateFromGdCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2(string filename) + Create a new image from GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageCreateFromGd2, gdImageCreateFromGd2Ctx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2part(string filename, int srcX, int srcY, int width, int height) + Create a new image from a given part of GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2part) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2PART, "GD2", gdImageCreateFromGd2Part, gdImageCreateFromGd2PartCtx); +} +/* }}} */ + +#if defined(HAVE_GD_BMP) +/* {{{ proto resource imagecreatefrombmp(string filename) + Create a new image from BMP file or URL */ +PHP_FUNCTION(imagecreatefrombmp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_BMP, "BMP", gdImageCreateFromBmp, gdImageCreateFromBmpCtx); +} +/* }}} */ +#endif + +#if defined(HAVE_GD_TGA) +/* {{{ proto resource imagecreatefromtga(string filename) + Create a new image from TGA file or URL */ +PHP_FUNCTION(imagecreatefromtga) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_TGA, "TGA", gdImageCreateFromTga, gdImageCreateFromTgaCtx); +} +/* }}} */ +#endif + +/* {{{ _php_image_output + */ +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()) +{ + zval *imgind; + char *file = NULL; + zend_long quality = 0, type = 0; + gdImagePtr im; + char *fn = NULL; + FILE *fp; + size_t file_len = 0; + int argc = ZEND_NUM_ARGS(); + int q = -1, t = 1; + + /* The quality parameter for Wbmp stands for the foreground when called from image2wbmp() */ + /* The quality parameter for gd2 stands for chunk size */ + + if (zend_parse_parameters(argc, "r|pll", &imgind, &file, &file_len, &quality, &type) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(imgind), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + fn = file; + if (argc >= 3) { + q = quality; + if (argc == 4) { + t = type; + } + } + } + + if (argc >= 2 && file_len) { + PHP_GD_CHECK_OPEN_BASEDIR(fn, "Invalid filename"); + + fp = VCWD_FOPEN(fn, "wb"); + if (!fp) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, fp); + break; + case PHP_GDIMG_TYPE_GD: + (*func_p)(im, fp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, fp, q, t); + break; + default: + ZEND_ASSERT(0); + } + fflush(fp); + fclose(fp); + } else { + int b; + FILE *tmp; + char buf[4096]; + zend_string *path; + + tmp = php_open_temporary_file(NULL, NULL, &path); + if (tmp == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open temporary file"); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, tmp); + break; + case PHP_GDIMG_TYPE_GD: + (*func_p)(im, tmp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, tmp, q, t); + break; + default: + ZEND_ASSERT(0); + } + + fseek(tmp, 0, SEEK_SET); + + while ((b = fread(buf, 1, sizeof(buf), tmp)) > 0) { + php_write(buf, b); + } + + fclose(tmp); + VCWD_UNLINK((const char *)ZSTR_VAL(path)); /* make sure that the temporary file is removed */ + zend_string_release_ex(path, 0); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagexbm(int im, string filename [, int foreground]) + Output XBM image to browser or file */ +PHP_FUNCTION(imagexbm) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageXbmCtx); +} +/* }}} */ + +/* {{{ proto bool imagegif(resource im [, mixed to]) + Output GIF image to browser or file */ +PHP_FUNCTION(imagegif) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_PNG +/* {{{ proto bool imagepng(resource im [, mixed to]) + Output PNG image to browser or file */ +PHP_FUNCTION(imagepng) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImagePngCtxEx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + + +#ifdef HAVE_GD_WEBP +/* {{{ proto bool imagewebp(resource im [, mixed to[, int quality]] ) + Output WEBP image to browser or file */ +PHP_FUNCTION(imagewebp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + + +#ifdef HAVE_GD_JPG +/* {{{ proto bool imagejpeg(resource im [, mixed to [, int quality]]) + Output JPEG image to browser or file */ +PHP_FUNCTION(imagejpeg) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +/* {{{ proto bool imagewbmp(resource im [, mixed to [, int foreground]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(imagewbmp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageWBMPCtx); +} +/* }}} */ + +/* {{{ proto bool imagegd(resource im [, mixed to]) + Output GD image to browser or file */ +PHP_FUNCTION(imagegd) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageGd); +} +/* }}} */ + +/* {{{ proto bool imagegd2(resource im [, mixed to [, int chunk_size [, int type]]]) + Output GD2 image to browser or file */ +PHP_FUNCTION(imagegd2) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageGd2); +} +/* }}} */ + +#ifdef HAVE_GD_BMP +/* {{{ proto bool imagebmp(resource im [, mixed to [, bool compressed]]) + Output BMP image to browser or file */ +PHP_FUNCTION(imagebmp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_BMP, "BMP", gdImageBmpCtx); +} +/* }}} */ +#endif + +/* {{{ proto bool imagedestroy(resource im) + Destroy an image */ +PHP_FUNCTION(imagedestroy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + zend_list_close(Z_RES_P(IM)); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorallocate(resource im, int red, int green, int blue) + Allocate a color for an image */ +PHP_FUNCTION(imagecolorallocate) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + + ct = gdImageColorAllocate(im, red, green, blue); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG(ct); +} +/* }}} */ + +/* {{{ proto void imagepalettecopy(resource dst, resource src) + Copy the palette from the src image onto the dst image */ +PHP_FUNCTION(imagepalettecopy) +{ + zval *dstim, *srcim; + gdImagePtr dst, src; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &dstim, &srcim) == FAILURE) { + return; + } + + if ((dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(dstim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(srcim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImagePaletteCopy(dst, src); +} +/* }}} */ + +/* {{{ proto int imagecolorat(resource im, int x, int y) + Get the index of the color of a pixel */ +PHP_FUNCTION(imagecolorat) +{ + zval *IM; + zend_long x, y; + gdImagePtr im; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_RESOURCE(IM) + Z_PARAM_LONG(x) + Z_PARAM_LONG(y) + ZEND_PARSE_PARAMETERS_END(); + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + if (im->tpixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(gdImageTrueColorPixel(im, x, y)); + } else { + php_error_docref(NULL, E_NOTICE, "" ZEND_LONG_FMT "," ZEND_LONG_FMT " is out of bounds", x, y); + RETURN_FALSE; + } + } else { + if (im->pixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(im->pixels[y][x]); + } else { + php_error_docref(NULL, E_NOTICE, "" ZEND_LONG_FMT "," ZEND_LONG_FMT " is out of bounds", x, y); + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto int imagecolorclosest(resource im, int red, int green, int blue) + Get the index of the closest color to the specified color */ +PHP_FUNCTION(imagecolorclosest) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + + RETURN_LONG(gdImageColorClosest(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosesthwb(resource im, int red, int green, int blue) + Get the index of the color which has the hue, white and blackness nearest to the given color */ +PHP_FUNCTION(imagecolorclosesthwb) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + + RETURN_LONG(gdImageColorClosestHWB(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto bool imagecolordeallocate(resource im, int index) + De-allocate a color for an image */ +PHP_FUNCTION(imagecolordeallocate) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + /* We can return right away for a truecolor image as deallocating colours is meaningless here */ + if (gdImageTrueColor(im)) { + RETURN_TRUE; + } + + col = index; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + gdImageColorDeallocate(im, col); + RETURN_TRUE; + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imagecolorresolve(resource im, int red, int green, int blue) + Get the index of the specified color or its closest possible alternative */ +PHP_FUNCTION(imagecolorresolve) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + + RETURN_LONG(gdImageColorResolve(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorexact(resource im, int red, int green, int blue) + Get the index of the specified color */ +PHP_FUNCTION(imagecolorexact) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + + RETURN_LONG(gdImageColorExact(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto bool imagecolorset(resource im, int col, int red, int green, int blue) + Set the color for the specified palette index */ +PHP_FUNCTION(imagecolorset) +{ + zval *IM; + zend_long color, red, green, blue, alpha = 0; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &IM, &color, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + CHECK_RGBA_RANGE(red, Red); + CHECK_RGBA_RANGE(green, Green); + CHECK_RGBA_RANGE(blue, Blue); + CHECK_RGBA_RANGE(alpha, Alpha); + + col = color; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + im->red[col] = red; + im->green[col] = green; + im->blue[col] = blue; + im->alpha[col] = alpha; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imagecolorsforindex(resource im, int col) + Get the colors for an index */ +PHP_FUNCTION(imagecolorsforindex) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = index; + + if ((col >= 0 && gdImageTrueColor(im)) || (!gdImageTrueColor(im) && col >= 0 && col < gdImageColorsTotal(im))) { + array_init(return_value); + + add_assoc_long(return_value,"red", gdImageRed(im,col)); + add_assoc_long(return_value,"green", gdImageGreen(im,col)); + add_assoc_long(return_value,"blue", gdImageBlue(im,col)); + add_assoc_long(return_value,"alpha", gdImageAlpha(im,col)); + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagegammacorrect(resource im, float inputgamma, float outputgamma) + Apply a gamma correction to a GD image */ +PHP_FUNCTION(imagegammacorrect) +{ + zval *IM; + gdImagePtr im; + int i; + double input, output, gamma; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &IM, &input, &output) == FAILURE) { + return; + } + + if ( input <= 0.0 || output <= 0.0 ) { + php_error_docref(NULL, E_WARNING, "Gamma values should be positive"); + RETURN_FALSE; + } + + gamma = input / output; + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + int x, y, c; + + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + c = gdImageGetPixel(im, x, y); + gdImageSetPixel(im, x, y, + gdTrueColorAlpha( + (int) ((pow((gdTrueColorGetRed(c) / 255.0), gamma) * 255) + .5), + (int) ((pow((gdTrueColorGetGreen(c) / 255.0), gamma) * 255) + .5), + (int) ((pow((gdTrueColorGetBlue(c) / 255.0), gamma) * 255) + .5), + gdTrueColorGetAlpha(c) + ) + ); + } + } + RETURN_TRUE; + } + + for (i = 0; i < gdImageColorsTotal(im); i++) { + im->red[i] = (int)((pow((im->red[i] / 255.0), gamma) * 255) + .5); + im->green[i] = (int)((pow((im->green[i] / 255.0), gamma) * 255) + .5); + im->blue[i] = (int)((pow((im->blue[i] / 255.0), gamma) * 255) + .5); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetpixel(resource im, int x, int y, int col) + Set a single pixel */ +PHP_FUNCTION(imagesetpixel) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + ZEND_PARSE_PARAMETERS_START(4, 4) + Z_PARAM_RESOURCE(IM) + Z_PARAM_LONG(x) + Z_PARAM_LONG(y) + Z_PARAM_LONG(col) + ZEND_PARSE_PARAMETERS_END(); + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetPixel(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a line */ +PHP_FUNCTION(imageline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im->AA) { + gdImageSetAntiAliased(im, col); + col = gdAntiAliased; + } + gdImageLine(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagedashedline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a dashed line */ +PHP_FUNCTION(imagedashedline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageDashedLine(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagerectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a rectangle */ +PHP_FUNCTION(imagerectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledrectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a filled rectangle */ +PHP_FUNCTION(imagefilledrectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + gdImageFilledRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagearc(resource im, int cx, int cy, int w, int h, int s, int e, int col) + Draw a partial ellipse */ +PHP_FUNCTION(imagearc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageArc(im, cx, cy, w, h, st, e, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imageellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageEllipse(im, cx, cy, w, h, color); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilltoborder(resource im, int x, int y, int border, int col) + Flood fill to specific color */ +PHP_FUNCTION(imagefilltoborder) +{ + zval *IM; + zend_long x, y, border, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &x, &y, &border, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFillToBorder(im, x, y, border, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefill(resource im, int x, int y, int col) + Flood fill */ +PHP_FUNCTION(imagefill) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &x, &y, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFill(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorstotal(resource im) + Find out the number of colors in an image's palette */ +PHP_FUNCTION(imagecolorstotal) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorsTotal(im)); +} +/* }}} */ + +/* {{{ proto int imagecolortransparent(resource im [, int col]) + Define a color as transparent */ +PHP_FUNCTION(imagecolortransparent) +{ + zval *IM; + zend_long COL = 0; + gdImagePtr im; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &IM, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageColorTransparent(im, COL); + } + + RETURN_LONG(gdImageGetTransparent(im)); +} +/* }}} */ + +/* {{{ proto int imageinterlace(resource im [, int interlace]) + Enable or disable interlace */ +PHP_FUNCTION(imageinterlace) +{ + zval *IM; + int argc = ZEND_NUM_ARGS(); + zend_long INT = 0; + gdImagePtr im; + + if (zend_parse_parameters(argc, "r|l", &IM, &INT) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageInterlace(im, INT); + } + + RETURN_LONG(gdImageGetInterlaced(im)); +} +/* }}} */ + +/* {{{ php_imagepolygon + arg = -1 open polygon + arg = 0 normal polygon + arg = 1 filled polygon */ +/* im, points, num_points, col */ +static void php_imagepolygon(INTERNAL_FUNCTION_PARAMETERS, int filled) +{ + zval *IM, *POINTS; + zend_long NPOINTS, COL; + zval *var = NULL; + gdImagePtr im; + gdPointPtr points; + int npoints, col, nelem, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rall", &IM, &POINTS, &NPOINTS, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + npoints = NPOINTS; + col = COL; + + nelem = zend_hash_num_elements(Z_ARRVAL_P(POINTS)); + if (nelem < 6) { + php_error_docref(NULL, E_WARNING, "You must have at least 3 points in your array"); + RETURN_FALSE; + } + if (npoints <= 0) { + php_error_docref(NULL, E_WARNING, "You must give a positive number of points"); + RETURN_FALSE; + } + if (nelem < npoints * 2) { + php_error_docref(NULL, E_WARNING, "Trying to use %d points in array with only %d points", npoints, nelem/2); + RETURN_FALSE; + } + + points = (gdPointPtr) safe_emalloc(npoints, sizeof(gdPoint), 0); + + for (i = 0; i < npoints; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2))) != NULL) { + points[i].x = zval_get_long(var); + } + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2) + 1)) != NULL) { + points[i].y = zval_get_long(var); + } + } + + if (im->AA) { + gdImageSetAntiAliased(im, col); + col = gdAntiAliased; + } + switch (filled) { + case -1: + gdImageOpenPolygon(im, points, npoints, col); + break; + case 0: + gdImagePolygon(im, points, npoints, col); + break; + case 1: + gdImageFilledPolygon(im, points, npoints, col); + break; + } + + efree(points); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagepolygon(resource im, array point, int num_points, int col) + Draw a polygon */ +PHP_FUNCTION(imagepolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imageopenpolygon(resource im, array point, int num_points, int col) + Draw a polygon */ +PHP_FUNCTION(imageopenpolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1); +} +/* }}} */ + +/* {{{ proto bool imagefilledpolygon(resource im, array point, int num_points, int col) + Draw a filled polygon */ +PHP_FUNCTION(imagefilledpolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_find_gd_font + */ +static gdFontPtr php_find_gd_font(int size) +{ + gdFontPtr font; + + switch (size) { + case 1: + font = gdFontTiny; + break; + case 2: + font = gdFontSmall; + break; + case 3: + font = gdFontMediumBold; + break; + case 4: + font = gdFontLarge; + break; + case 5: + font = gdFontGiant; + break; + default: { + zval *zv = zend_hash_index_find(&EG(regular_list), size - 5); + if (!zv || (Z_RES_P(zv))->type != le_gd_font) { + if (size < 1) { + font = gdFontTiny; + } else { + font = gdFontGiant; + } + } else { + font = (gdFontPtr)Z_RES_P(zv)->ptr; + } + } + break; + } + + return font; +} +/* }}} */ + +/* {{{ php_imagefontsize + * arg = 0 ImageFontWidth + * arg = 1 ImageFontHeight + */ +static void php_imagefontsize(INTERNAL_FUNCTION_PARAMETERS, int arg) +{ + zend_long SIZE; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &SIZE) == FAILURE) { + return; + } + + font = php_find_gd_font(SIZE); + RETURN_LONG(arg ? font->h : font->w); +} +/* }}} */ + +/* {{{ proto int imagefontwidth(int font) + Get font width */ +PHP_FUNCTION(imagefontwidth) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto int imagefontheight(int font) + Get font height */ +PHP_FUNCTION(imagefontheight) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_gdimagecharup + * workaround for a bug in gd 1.2 */ +static void php_gdimagecharup(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) +{ + int cx, cy, px, py, fline; + cx = 0; + cy = 0; + + if ((c < f->offset) || (c >= (f->offset + f->nchars))) { + return; + } + + fline = (c - f->offset) * f->h * f->w; + for (py = y; (py > (y - f->w)); py--) { + for (px = x; (px < (x + f->h)); px++) { + if (f->data[fline + cy * f->w + cx]) { + gdImageSetPixel(im, px, py, color); + } + cy++; + } + cy = 0; + cx++; + } +} +/* }}} */ + +/* {{{ php_imagechar + * arg = 0 ImageChar + * arg = 1 ImageCharUp + * arg = 2 ImageString + * arg = 3 ImageStringUp + */ +static void php_imagechar(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zval *IM; + zend_long SIZE, X, Y, COL; + char *C; + size_t C_len; + gdImagePtr im; + int ch = 0, col, x, y, size, i, l = 0; + unsigned char *str = NULL; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllsl", &IM, &SIZE, &X, &Y, &C, &C_len, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = COL; + + if (mode < 2) { + ch = (int)((unsigned char)*C); + } else { + str = (unsigned char *) estrndup(C, C_len); + l = strlen((char *)str); + } + + y = Y; + x = X; + size = SIZE; + + font = php_find_gd_font(size); + + switch (mode) { + case 0: + gdImageChar(im, font, x, y, ch, col); + break; + case 1: + php_gdimagecharup(im, font, x, y, ch, col); + break; + case 2: + for (i = 0; (i < l); i++) { + gdImageChar(im, font, x, y, (int) ((unsigned char) str[i]), col); + x += font->w; + } + break; + case 3: { + for (i = 0; (i < l); i++) { + /* php_gdimagecharup(im, font, x, y, (int) str[i], col); */ + gdImageCharUp(im, font, x, y, (int) str[i], col); + y -= font->w; + } + break; + } + } + if (str) { + efree(str); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagechar(resource im, int font, int x, int y, string c, int col) + Draw a character */ +PHP_FUNCTION(imagechar) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imagecharup(resource im, int font, int x, int y, string c, int col) + Draw a character rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagecharup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto bool imagestring(resource im, int font, int x, int y, string str, int col) + Draw a string horizontally */ +PHP_FUNCTION(imagestring) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto bool imagestringup(resource im, int font, int x, int y, string str, int col) + Draw a string vertically - rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagestringup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto bool imagecopy(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h) + Copy part of an image */ +PHP_FUNCTION(imagecopy) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + + gdImageCopy(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymerge(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymerge) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMerge(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymergegray(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymergegray) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMergeGray(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopyresized(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image */ +PHP_FUNCTION(imagecopyresized) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + if (dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + gdImageCopyResized(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagesx(resource im) + Get image width */ +PHP_FUNCTION(imagesx) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSX(im)); +} +/* }}} */ + +/* {{{ proto int imagesy(resource im) + Get image height */ +PHP_FUNCTION(imagesy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSY(im)); +} +/* }}} */ + +/* {{{ proto bool imagesetclip(resource im, int x1, int y1, int x2, int y2) + Set the clipping rectangle. */ +PHP_FUNCTION(imagesetclip) +{ + zval *im_zval; + gdImagePtr im; + zend_long x1, y1, x2, y2; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &im_zval, &x1, &y1, &x2, &y2) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(im_zval), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetClip(im, x1, y1, x2, y2); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imagegetclip(resource im) + Get the clipping rectangle. */ +PHP_FUNCTION(imagegetclip) +{ + zval *im_zval; + gdImagePtr im; + int x1, y1, x2, y2; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &im_zval) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(im_zval), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageGetClip(im, &x1, &y1, &x2, &y2); + + array_init(return_value); + add_next_index_long(return_value, x1); + add_next_index_long(return_value, y1); + add_next_index_long(return_value, x2); + add_next_index_long(return_value, y2); +} +/* }}} */ + +#define TTFTEXT_DRAW 0 +#define TTFTEXT_BBOX 1 + +#ifdef HAVE_GD_FREETYPE +/* {{{ proto array imageftbbox(float size, float angle, string font_file, string text [, array extrainfo]) + Give the bounding box of a text using fonts via freetype2 */ +PHP_FUNCTION(imageftbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 1); +} +/* }}} */ + +/* {{{ proto array imagefttext(resource im, float size, float angle, int x, int y, int col, string font_file, string text [, array extrainfo]) + Write text to the image using fonts via freetype2 */ +PHP_FUNCTION(imagefttext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 1); +} +/* }}} */ + +/* {{{ proto array imagettfbbox(float size, float angle, string font_file, string text) + Give the bounding box of a text using TrueType fonts */ +PHP_FUNCTION(imagettfbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 0); +} +/* }}} */ + +/* {{{ proto array imagettftext(resource im, float size, float angle, int x, int y, int col, string font_file, string text) + Write text to the image using a TrueType font */ +PHP_FUNCTION(imagettftext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 0); +} +/* }}} */ + +/* {{{ php_imagettftext_common + */ +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int mode, int extended) +{ + zval *IM, *EXT = NULL; + gdImagePtr im=NULL; + zend_long col = -1, x = 0, y = 0; + size_t str_len, fontname_len; + int i, brect[8]; + double ptsize, angle; + char *str = NULL, *fontname = NULL; + char *error = NULL; + int argc = ZEND_NUM_ARGS(); + gdFTStringExtra strex = {0}; + + if (mode == TTFTEXT_BBOX) { + if (argc < 4 || argc > ((extended) ? 5 : 4)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "ddss|a", &ptsize, &angle, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + } else { + if (argc < 8 || argc > ((extended) ? 9 : 8)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "rddlllss|a", &IM, &ptsize, &angle, &x, &y, &col, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + } + + /* convert angle to radians */ + angle = angle * (M_PI/180); + + if (extended && EXT) { /* parse extended info */ + zval *item; + zend_string *key; + + /* walk the assoc array */ + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(EXT), key, item) { + if (key == NULL) { + continue; + } + if (strcmp("linespacing", ZSTR_VAL(key)) == 0) { + strex.flags |= gdFTEX_LINESPACE; + strex.linespacing = zval_get_double(item); + } + } ZEND_HASH_FOREACH_END(); + } + +#ifdef VIRTUAL_DIR + { + char tmp_font_path[MAXPATHLEN]; + + if (!VCWD_REALPATH(fontname, tmp_font_path)) { + fontname = NULL; + } + } +#endif /* VIRTUAL_DIR */ + + PHP_GD_CHECK_OPEN_BASEDIR(fontname, "Invalid font filename"); + + if (extended) { + error = gdImageStringFTEx(im, brect, col, fontname, ptsize, angle, x, y, str, &strex); + } else { + error = gdImageStringFT(im, brect, col, fontname, ptsize, angle, x, y, str); + } + + if (error) { + php_error_docref(NULL, E_WARNING, "%s", error); + RETURN_FALSE; + } + + array_init(return_value); + + /* return array with the text's bounding box */ + for (i = 0; i < 8; i++) { + add_next_index_long(return_value, brect[i]); + } +} +/* }}} */ +#endif /* HAVE_GD_FREETYPE */ + +/* {{{ proto bool image2wbmp(resource im [, string filename [, int foreground]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(image2wbmp) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_CONVERT_WBM, "WBMP", NULL); +} +/* }}} */ + +#if defined(HAVE_GD_JPG) +/* {{{ proto bool jpeg2wbmp(string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert JPEG image to WBMP image */ +PHP_FUNCTION(jpeg2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG); +} +/* }}} */ +#endif + +#if defined(HAVE_GD_PNG) +/* {{{ proto bool png2wbmp(string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert PNG image to WBMP image */ +PHP_FUNCTION(png2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG); +} +/* }}} */ +#endif + +/* {{{ _php_image_convert + * _php_image_convert converts jpeg/png images to wbmp and resizes them as needed */ +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type ) +{ + char *f_org, *f_dest; + size_t f_org_len, f_dest_len; + zend_long height, width, threshold; + gdImagePtr im_org, im_dest, im_tmp; + char *fn_org = NULL; + char *fn_dest = NULL; + FILE *org, *dest; + int dest_height = -1; + int dest_width = -1; + int org_height, org_width; + int white, black; + int color, color_org, median; + int int_threshold; + int x, y; + float x_ratio, y_ratio; +#ifdef HAVE_GD_JPG + zend_long ignore_warning; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pplll", &f_org, &f_org_len, &f_dest, &f_dest_len, &height, &width, &threshold) == FAILURE) { + return; + } + + fn_org = f_org; + fn_dest = f_dest; + dest_height = height; + dest_width = width; + int_threshold = threshold; + + /* Check threshold value */ + if (int_threshold < 0 || int_threshold > 8) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'", int_threshold); + RETURN_FALSE; + } + + /* Check origin file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_org, "Invalid origin filename"); + + /* Check destination file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_dest, "Invalid destination filename"); + + /* Open origin file */ + org = VCWD_FOPEN(fn_org, "rb"); + if (!org) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for reading", fn_org); + RETURN_FALSE; + } + + /* Open destination file */ + dest = VCWD_FOPEN(fn_dest, "wb"); + if (!dest) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn_dest); + fclose(org); + RETURN_FALSE; + } + + switch (image_type) { + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im_org = gdImageCreateFromJpegEx(org, ignore_warning); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid JPEG file", fn_dest); + fclose(org); + fclose(dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG + case PHP_GDIMG_TYPE_PNG: + im_org = gdImageCreateFromPng(org); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid PNG file", fn_dest); + fclose(org); + fclose(dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_PNG */ + + default: + php_error_docref(NULL, E_WARNING, "Format not supported"); + fclose(org); + fclose(dest); + RETURN_FALSE; + break; + } + + fclose(org); + + org_width = gdImageSX (im_org); + org_height = gdImageSY (im_org); + + x_ratio = (float) org_width / (float) dest_width; + y_ratio = (float) org_height / (float) dest_height; + + if (x_ratio > 1 && y_ratio > 1) { + if (y_ratio > x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width / x_ratio); + dest_height = (int) (org_height / y_ratio); + } else { + x_ratio = (float) dest_width / (float) org_width; + y_ratio = (float) dest_height / (float) org_height; + + if (y_ratio < x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width * x_ratio); + dest_height = (int) (org_height * y_ratio); + } + + im_tmp = gdImageCreate (dest_width, dest_height); + if (im_tmp == NULL ) { + php_error_docref(NULL, E_WARNING, "Unable to allocate temporary buffer"); + fclose(dest); + gdImageDestroy(im_org); + RETURN_FALSE; + } + + gdImageCopyResized (im_tmp, im_org, 0, 0, 0, 0, dest_width, dest_height, org_width, org_height); + + gdImageDestroy(im_org); + + im_dest = gdImageCreate(dest_width, dest_height); + if (im_dest == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to allocate destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + RETURN_FALSE; + } + + white = gdImageColorAllocate(im_dest, 255, 255, 255); + if (white == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + gdImageDestroy(im_dest); + RETURN_FALSE; + } + + black = gdImageColorAllocate(im_dest, 0, 0, 0); + if (black == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + gdImageDestroy(im_dest); + RETURN_FALSE; + } + + int_threshold = int_threshold * 32; + + for (y = 0; y < dest_height; y++) { + for (x = 0; x < dest_width; x++) { + color_org = gdImageGetPixel (im_tmp, x, y); + median = (im_tmp->red[color_org] + im_tmp->green[color_org] + im_tmp->blue[color_org]) / 3; + if (median < int_threshold) { + color = black; + } else { + color = white; + } + gdImageSetPixel (im_dest, x, y, color); + } + } + + gdImageDestroy (im_tmp ); + + gdImageWBMP(im_dest, black , dest); + + fflush(dest); + fclose(dest); + + gdImageDestroy(im_dest); + + RETURN_TRUE; +} +/* }}} */ + +/* Section Filters */ +#define PHP_GD_SINGLE_RES \ + zval *SIM; \ + gdImagePtr im_src; \ + if (zend_parse_parameters(1, "r", &SIM) == FAILURE) { \ + RETURN_FALSE; \ + } \ + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { \ + RETURN_FALSE; \ + } + +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageNegate(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGrayScale(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long brightness, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zll", &SIM, &tmp, &brightness) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageBrightness(im_src, (int)brightness) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long contrast, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll", &SIM, &tmp, &contrast) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageContrast(im_src, (int)contrast) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long r,g,b,tmp; + zend_long a = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &SIM, &tmp, &r, &g, &b, &a) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageColor(im_src, (int) r, (int) g, (int) b, (int) a) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEdgeDetectQuick(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEmboss(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGaussianBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageSelectiveBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageMeanRemoval(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + zend_long tmp; + gdImagePtr im_src; + double weight; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rld", &SIM, &tmp, &weight) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageSmooth(im_src, (float)weight)==1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *IM; + gdImagePtr im; + zend_long tmp, blocksize; + zend_bool mode = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll|b", &IM, &tmp, &blocksize, &mode) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImagePixelate(im, (int) blocksize, (const unsigned int) mode)) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *IM; + zval *hash_colors = NULL; + gdImagePtr im; + zend_long tmp; + zend_long scatter_sub, scatter_plus; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll|a", &IM, &tmp, &scatter_sub, &scatter_plus, &hash_colors) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (hash_colors) { + uint32_t i = 0; + uint32_t num_colors = zend_hash_num_elements(Z_ARRVAL_P(hash_colors)); + zval *color; + int *colors; + + if (num_colors == 0) { + RETURN_BOOL(gdImageScatter(im, (int)scatter_sub, (int)scatter_plus)); + } + + colors = emalloc(num_colors * sizeof(int)); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hash_colors), color) { + *(colors + i++) = (int) zval_get_long(color); + } ZEND_HASH_FOREACH_END(); + + RETVAL_BOOL(gdImageScatterColor(im, (int)scatter_sub, (int)scatter_plus, colors, num_colors)); + + efree(colors); + } else { + RETURN_BOOL(gdImageScatter(im, (int) scatter_sub, (int) scatter_plus)) + } +} + +/* {{{ proto bool imagefilter(resource src_im, int filtertype[, int arg1 [, int arg2 [, int arg3 [, int arg4 ]]]] ) + Applies Filter an image using a custom angle */ +PHP_FUNCTION(imagefilter) +{ + zval *tmp; + + typedef void (*image_filter)(INTERNAL_FUNCTION_PARAMETERS); + zend_long filtertype; + image_filter filters[] = + { + php_image_filter_negate , + php_image_filter_grayscale, + php_image_filter_brightness, + php_image_filter_contrast, + php_image_filter_colorize, + php_image_filter_edgedetect, + php_image_filter_emboss, + php_image_filter_gaussian_blur, + php_image_filter_selective_blur, + php_image_filter_mean_removal, + php_image_filter_smooth, + php_image_filter_pixelate, + php_image_filter_scatter + }; + + if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > IMAGE_FILTER_MAX_ARGS) { + WRONG_PARAM_COUNT; + } else if (zend_parse_parameters(2, "rl", &tmp, &filtertype) == FAILURE) { + return; + } + + if (filtertype >= 0 && filtertype <= IMAGE_FILTER_MAX) { + filters[filtertype](INTERNAL_FUNCTION_PARAM_PASSTHRU); + } +} +/* }}} */ + +/* {{{ proto resource imageconvolution(resource src_im, array matrix3x3, double div, double offset) + Apply a 3x3 convolution matrix, using coefficient div and offset */ +PHP_FUNCTION(imageconvolution) +{ + zval *SIM, *hash_matrix; + zval *var = NULL, *var2 = NULL; + gdImagePtr im_src = NULL; + double div, offset; + int nelem, i, j, res; + float matrix[3][3] = {{0,0,0}, {0,0,0}, {0,0,0}}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "radd", &SIM, &hash_matrix, &div, &offset) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + nelem = zend_hash_num_elements(Z_ARRVAL_P(hash_matrix)); + if (nelem != 3) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (i=0; i<3; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(hash_matrix), (i))) != NULL && Z_TYPE_P(var) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(var)) != 3 ) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (j=0; j<3; j++) { + if ((var2 = zend_hash_index_find(Z_ARRVAL_P(var), j)) != NULL) { + matrix[i][j] = (float) zval_get_double(var2); + } else { + php_error_docref(NULL, E_WARNING, "You must have a 3x3 matrix"); + RETURN_FALSE; + } + } + } + } + res = gdImageConvolution(im_src, matrix, (float)div, (float)offset); + + if (res) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ +/* End section: Filters */ + +/* {{{ proto bool imageflip(resource im, int mode) + Flip an image (in place) horizontally, vertically or both directions. */ +PHP_FUNCTION(imageflip) +{ + zval *IM; + zend_long mode; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &mode) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case GD_FLIP_VERTICAL: + gdImageFlipVertical(im); + break; + + case GD_FLIP_HORINZONTAL: + gdImageFlipHorizontal(im); + break; + + case GD_FLIP_BOTH: + gdImageFlipBoth(im); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown flip mode"); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageantialias(resource im, bool on) + Should antialiased functions used or not*/ +PHP_FUNCTION(imageantialias) +{ + zval *IM; + zend_bool alias; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &alias) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im->trueColor) { + im->AA = alias; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecrop(resource im, array rect) + Crop an image using the given coordinates and size, x, y, width and height. */ +PHP_FUNCTION(imagecrop) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_crop; + gdRect rect; + zval *z_rect; + zval *tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &z_rect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") -1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + + im_crop = gdImageCrop(im, &rect); + + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagecropauto(resource im [, int mode = GD_CROP_DEFAULT [, float threshold [, int color]]]) + Crop an image automatically using one of the available modes. */ +PHP_FUNCTION(imagecropauto) +{ + zval *IM; + zend_long mode = GD_CROP_DEFAULT; + zend_long color = -1; + double threshold = 0.5f; + gdImagePtr im; + gdImagePtr im_crop; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|ldl", &IM, &mode, &threshold, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case -1: + php_error_docref(NULL, E_DEPRECATED, "Crop mode -1 is deprecated. Use IMG_CROP_DEFAULT instead."); + mode = GD_CROP_DEFAULT; + /* FALLTHRU */ + case GD_CROP_DEFAULT: + case GD_CROP_TRANSPARENT: + case GD_CROP_BLACK: + case GD_CROP_WHITE: + case GD_CROP_SIDES: + im_crop = gdImageCropAuto(im, mode); + break; + + case GD_CROP_THRESHOLD: + if (color < 0 || (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im))) { + php_error_docref(NULL, E_WARNING, "Color argument missing with threshold mode"); + RETURN_FALSE; + } + im_crop = gdImageCropThreshold(im, color, (float) threshold); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown crop mode"); + RETURN_FALSE; + } + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagescale(resource im, int new_width[, int new_height[, int method]]) + Scale an image using the given new width and height. */ +PHP_FUNCTION(imagescale) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_scaled = NULL; + int new_width, new_height; + zend_long tmp_w, tmp_h=-1, tmp_m = GD_BILINEAR_FIXED; + gdInterpolationMethod method, old_method; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|ll", &IM, &tmp_w, &tmp_h, &tmp_m) == FAILURE) { + return; + } + method = tmp_m; + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (tmp_h < 0 || tmp_w < 0) { + /* preserve ratio */ + long src_x, src_y; + + src_x = gdImageSX(im); + src_y = gdImageSY(im); + + if (src_x && tmp_h < 0) { + tmp_h = tmp_w * src_y / src_x; + } + if (src_y && tmp_w < 0) { + tmp_w = tmp_h * src_x / src_y; + } + } + + if (tmp_h <= 0 || tmp_h > INT_MAX || tmp_w <= 0 || tmp_w > INT_MAX) { + RETURN_FALSE; + } + + new_width = tmp_w; + new_height = tmp_h; + + /* gdImageGetInterpolationMethod() is only available as of GD 2.1.1 */ + old_method = im->interpolation_id; + if (gdImageSetInterpolationMethod(im, method)) { + im_scaled = gdImageScale(im, new_width, new_height); + } + gdImageSetInterpolationMethod(im, old_method); + + if (im_scaled == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_scaled, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imageaffine(resource src, array affine[, array clip]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffine) +{ + zval *IM; + gdImagePtr src; + gdImagePtr dst; + gdRect rect; + gdRectPtr pRect = NULL; + zval *z_rect = NULL; + zval *z_affine; + zval *tmp; + double affine[6]; + int i, nelems; + zval *zval_affine_elem = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra|a", &IM, &z_affine, &z_rect) == FAILURE) { + return; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_affine))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine array must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < nelems; i++) { + if ((zval_affine_elem = zend_hash_index_find(Z_ARRVAL_P(z_affine), i)) != NULL) { + switch (Z_TYPE_P(zval_affine_elem)) { + case IS_LONG: + affine[i] = Z_LVAL_P(zval_affine_elem); + break; + case IS_DOUBLE: + affine[i] = Z_DVAL_P(zval_affine_elem); + break; + case IS_STRING: + affine[i] = zval_get_double(zval_affine_elem); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (z_rect != NULL) { + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") - 1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + pRect = ▭ + } else { + rect.x = -1; + rect.y = -1; + rect.width = gdImageSX(src); + rect.height = gdImageSY(src); + pRect = NULL; + } + + if (gdTransformAffineGetImage(&dst, src, pRect, affine) != GD_TRUE) { + RETURN_FALSE; + } + + if (dst == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(dst, le_gd)); + } +} +/* }}} */ + +/* {{{ proto array imageaffinematrixget(int type[, array options]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffinematrixget) +{ + double affine[6]; + zend_long type; + zval *options = NULL; + zval *tmp; + int res = GD_FALSE, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z", &type, &options) == FAILURE) { + return; + } + + switch((gdAffineStandardMatrix)type) { + case GD_AFFINE_TRANSLATE: + case GD_AFFINE_SCALE: { + double x, y; + if (!options || Z_TYPE_P(options) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Array expected as options"); + RETURN_FALSE; + } + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "x", sizeof("x") - 1)) != NULL) { + x = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "y", sizeof("y") - 1)) != NULL) { + y = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if (type == GD_AFFINE_TRANSLATE) { + res = gdAffineTranslate(affine, x, y); + } else { + res = gdAffineScale(affine, x, y); + } + break; + } + + case GD_AFFINE_ROTATE: + case GD_AFFINE_SHEAR_HORIZONTAL: + case GD_AFFINE_SHEAR_VERTICAL: { + double angle; + + if (!options) { + php_error_docref(NULL, E_WARNING, "Number is expected as option"); + RETURN_FALSE; + } + + angle = zval_get_double(options); + + if (type == GD_AFFINE_SHEAR_HORIZONTAL) { + res = gdAffineShearHorizontal(affine, angle); + } else if (type == GD_AFFINE_SHEAR_VERTICAL) { + res = gdAffineShearVertical(affine, angle); + } else { + res = gdAffineRotate(affine, angle); + } + break; + } + + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element " ZEND_LONG_FMT, type); + RETURN_FALSE; + } + + if (res == GD_FALSE) { + RETURN_FALSE; + } else { + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, affine[i]); + } + } +} /* }}} */ + +/* {{{ proto array imageaffineconcat(array m1, array m2) + Concat two matrices (as in doing many ops in one go) */ +PHP_FUNCTION(imageaffinematrixconcat) +{ + double m1[6]; + double m2[6]; + double mr[6]; + + zval *tmp; + zval *z_m1; + zval *z_m2; + int i, nelems; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &z_m1, &z_m2) == FAILURE) { + return; + } + + if (((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m1))) != 6) || (nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m2))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine arrays must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < 6; i++) { + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m1), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m1[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m1[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m1[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m2), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m2[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m2[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m2[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (gdAffineConcat (mr, m1, m2) != GD_TRUE) { + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, mr[i]); + } +} /* }}} */ + +/* {{{ proto resource imagesetinterpolation(resource im [, int method]]) + Set the default interpolation method, passing -1 or 0 sets it to the libgd default (bilinear). */ +PHP_FUNCTION(imagesetinterpolation) +{ + zval *IM; + gdImagePtr im; + zend_long method = GD_BILINEAR_FIXED; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &IM, &method) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (method == -1) { + method = GD_BILINEAR_FIXED; + } + RETURN_BOOL(gdImageSetInterpolationMethod(im, (gdInterpolationMethod) method)); +} +/* }}} */ + +/* {{{ proto array imageresolution(resource im [, res_x, [res_y]]) + Get or set the resolution of the image in DPI. */ +PHP_FUNCTION(imageresolution) +{ + zval *IM; + gdImagePtr im; + zend_long res_x = GD_RESOLUTION, res_y = GD_RESOLUTION; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|ll", &IM, &res_x, &res_y) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (ZEND_NUM_ARGS()) { + case 3: + gdImageSetResolution(im, res_x, res_y); + RETURN_TRUE; + case 2: + gdImageSetResolution(im, res_x, res_x); + RETURN_TRUE; + default: + array_init(return_value); + add_next_index_long(return_value, gdImageResolutionX(im)); + add_next_index_long(return_value, gdImageResolutionY(im)); + } +} +/* }}} */ diff --git a/ext/gd/tests/bug81739.phpt b/ext/gd/tests/bug81739.phpt new file mode 100644 index 0000000000000..cc2a90381bab4 --- /dev/null +++ b/ext/gd/tests/bug81739.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #81739 (OOB read due to insufficient validation in imageloadfont()) +--SKIPIF-- +<?php +if (!extension_loaded("gd")) die("skip gd extension not available"); +?> +--FILE-- +<?php +$s = fopen(__DIR__ . "/font.font", "w"); +// header without character data +fwrite($s, "\x01\x00\x00\x00\x20\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00"); +fclose($s); +var_dump(imageloadfont(__DIR__ . "/font.font")); +?> +--CLEAN-- +<?php +@unlink(__DIR__ . "/font.font"); +?> +--EXPECTF-- +Warning: imageloadfont(): %croduct of memory allocation multiplication would exceed INT_MAX, failing operation gracefully + in %s on line %d + +Warning: imageloadfont(): Error reading font, invalid font header in %s on line %d +bool(false) \ No newline at end of file From 194baa1c9bb6cce8517ef0298923117b8a3ef784 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:18 +0000 Subject: [PATCH 07/11] commit patch 21366347 --- ext/standard/crypt_blowfish.c | 8 - ext/standard/crypt_blowfish.c.orig | 917 ++++++++++++++++++ .../tests/crypt/bcrypt_salt_dollar.phpt | 82 ++ 3 files changed, 999 insertions(+), 8 deletions(-) create mode 100644 ext/standard/crypt_blowfish.c.orig create mode 100644 ext/standard/tests/crypt/bcrypt_salt_dollar.phpt diff --git a/ext/standard/crypt_blowfish.c b/ext/standard/crypt_blowfish.c index c1f945f29edd4..aa7e1bc2e6894 100644 --- a/ext/standard/crypt_blowfish.c +++ b/ext/standard/crypt_blowfish.c @@ -376,7 +376,6 @@ static unsigned char BF_atoi64[0x60] = { #define BF_safe_atoi64(dst, src) \ { \ tmp = (unsigned char)(src); \ - if (tmp == '$') break; /* PHP hack */ \ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ tmp = BF_atoi64[tmp]; \ if (tmp > 63) return -1; \ @@ -404,13 +403,6 @@ static int BF_decode(BF_word *dst, const char *src, int size) *dptr++ = ((c3 & 0x03) << 6) | c4; } while (dptr < end); - if (end - dptr == size) { - return -1; - } - - while (dptr < end) /* PHP hack */ - *dptr++ = 0; - return 0; } diff --git a/ext/standard/crypt_blowfish.c.orig b/ext/standard/crypt_blowfish.c.orig new file mode 100644 index 0000000000000..c1f945f29edd4 --- /dev/null +++ b/ext/standard/crypt_blowfish.c.orig @@ -0,0 +1,917 @@ +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer <solar at openwall.com> in 1998-2015. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2015 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres <dm at lcs.mit.edu>. For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include <string.h> + +#include <errno.h> +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_blowfish.h" + +#ifdef __i386__ +#define BF_ASM 0 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if (tmp == '$') break; /* PHP hack */ \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + if (end - dptr == size) { + return -1; + } + + while (dptr < end) /* PHP hack */ + *dptr++ = 0; + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +static int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *php_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +#if 0 +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} +#endif diff --git a/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt new file mode 100644 index 0000000000000..32e335f4b087e --- /dev/null +++ b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt @@ -0,0 +1,82 @@ +--TEST-- +bcrypt correctly rejects salts containing $ +--FILE-- +<?php +for ($i = 0; $i < 23; $i++) { + $salt = '$2y$04$' . str_repeat('0', $i) . '$'; + $result = crypt("foo", $salt); + var_dump($salt); + var_dump($result); + var_dump($result === $salt); +} +?> +--EXPECT-- +string(8) "$2y$04$$" +string(2) "*0" +bool(false) +string(9) "$2y$04$0$" +string(2) "*0" +bool(false) +string(10) "$2y$04$00$" +string(2) "*0" +bool(false) +string(11) "$2y$04$000$" +string(2) "*0" +bool(false) +string(12) "$2y$04$0000$" +string(2) "*0" +bool(false) +string(13) "$2y$04$00000$" +string(2) "*0" +bool(false) +string(14) "$2y$04$000000$" +string(2) "*0" +bool(false) +string(15) "$2y$04$0000000$" +string(2) "*0" +bool(false) +string(16) "$2y$04$00000000$" +string(2) "*0" +bool(false) +string(17) "$2y$04$000000000$" +string(2) "*0" +bool(false) +string(18) "$2y$04$0000000000$" +string(2) "*0" +bool(false) +string(19) "$2y$04$00000000000$" +string(2) "*0" +bool(false) +string(20) "$2y$04$000000000000$" +string(2) "*0" +bool(false) +string(21) "$2y$04$0000000000000$" +string(2) "*0" +bool(false) +string(22) "$2y$04$00000000000000$" +string(2) "*0" +bool(false) +string(23) "$2y$04$000000000000000$" +string(2) "*0" +bool(false) +string(24) "$2y$04$0000000000000000$" +string(2) "*0" +bool(false) +string(25) "$2y$04$00000000000000000$" +string(2) "*0" +bool(false) +string(26) "$2y$04$000000000000000000$" +string(2) "*0" +bool(false) +string(27) "$2y$04$0000000000000000000$" +string(2) "*0" +bool(false) +string(28) "$2y$04$00000000000000000000$" +string(2) "*0" +bool(false) +string(29) "$2y$04$000000000000000000000$" +string(2) "*0" +bool(false) +string(30) "$2y$04$0000000000000000000000$" +string(60) "$2y$04$000000000000000000000u2a2UpVexIt9k3FMJeAVr3c04F5tcI8K" +bool(false) From cdac33b273574f9daf4263878ae358c562213339 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:20 +0000 Subject: [PATCH 08/11] commit patch 17501063 --- ext/standard/crypt.c | 1 + ext/standard/crypt.c.orig | 277 ++++++++++++++++++ .../tests/password/password_bcrypt_short.phpt | 8 + 3 files changed, 286 insertions(+) create mode 100644 ext/standard/crypt.c.orig create mode 100644 ext/standard/tests/password/password_bcrypt_short.phpt diff --git a/ext/standard/crypt.c b/ext/standard/crypt.c index b727575fad6db..7fb5405cce0d6 100644 --- a/ext/standard/crypt.c +++ b/ext/standard/crypt.c @@ -146,6 +146,7 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch } else if ( salt[0] == '$' && salt[1] == '2' && + salt[2] != 0 && salt[3] == '$') { char output[PHP_MAX_SALT_LEN + 1]; diff --git a/ext/standard/crypt.c.orig b/ext/standard/crypt.c.orig new file mode 100644 index 0000000000000..b727575fad6db --- /dev/null +++ b/ext/standard/crypt.c.orig @@ -0,0 +1,277 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Stig Bakken <ssb@php.net> | + | Zeev Suraski <zeev@php.net> | + | Rasmus Lerdorf <rasmus@php.net> | + | Pierre Joye <pierre@php.net> | + +----------------------------------------------------------------------+ +*/ + +#include <stdlib.h> + +#include "php.h" + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if PHP_USE_PHP_CRYPT_R +# include "php_crypt_r.h" +# include "crypt_freesec.h" +#else +# if HAVE_CRYPT_H +# if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +# endif +# include <crypt.h> +# endif +#endif +#include <time.h> +#include <string.h> + +#ifdef PHP_WIN32 +#include <process.h> +#endif + +#include "php_crypt.h" +#include "php_random.h" + +/* sha512 crypt has the maximal salt length of 123 characters */ +#define PHP_MAX_SALT_LEN 123 + +/* Used to check DES salts to ensure that they contain only valid characters */ +#define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z')) + +#define DES_INVALID_SALT_ERROR "Supplied salt is not valid for DES. Possible bug in provided salt format." + + +PHP_MINIT_FUNCTION(crypt) /* {{{ */ +{ + REGISTER_LONG_CONSTANT("CRYPT_SALT_LENGTH", PHP_MAX_SALT_LEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_STD_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_EXT_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_MD5", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_BLOWFISH", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA256", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA512", 1, CONST_CS | CONST_PERSISTENT); + +#if PHP_USE_PHP_CRYPT_R + php_init_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */ +{ +#if PHP_USE_PHP_CRYPT_R + php_shutdown_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +static unsigned char itoa64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void php_to64(char *s, int n) /* {{{ */ +{ + while (--n >= 0) { + *s = itoa64[*s & 0x3f]; + s++; + } +} +/* }}} */ + +PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, zend_bool quiet) +{ + char *crypt_res; + zend_string *result; +/* Windows (win32/crypt) has a stripped down version of libxcrypt and + a CryptoApi md5_crypt implementation */ +#if PHP_USE_PHP_CRYPT_R + { + struct php_crypt_extended_data buffer; + + if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') { + char output[MD5_HASH_MAX_LEN], *out; + + out = php_md5_crypt_r(password, salt, output); + if (out) { + return zend_string_init(out, strlen(out), 0); + } + return NULL; + } else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if ( + salt[0] == '$' && + salt[1] == '2' && + salt[3] == '$') { + char output[PHP_MAX_SALT_LEN + 1]; + + memset(output, 0, PHP_MAX_SALT_LEN + 1); + + crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output)); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return result; + } + } else if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) { + return NULL; + } else { + /* DES Fallback */ + + /* Only check the salt if it's not EXT_DES */ + if (salt[0] != '_') { + /* DES style hashes */ + if (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1])) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + } + + memset(&buffer, 0, sizeof(buffer)); + _crypt_extended_init_r(); + + crypt_res = _crypt_extended_r((const unsigned char *) password, salt, &buffer); + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } + } + } +#else + + if (salt[0] != '$' && salt[0] != '_' && (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1]))) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + +# if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE)) + { +# if defined(CRYPT_R_STRUCT_CRYPT_DATA) + struct crypt_data buffer; + memset(&buffer, 0, sizeof(buffer)); +# elif defined(CRYPT_R_CRYPTD) + CRYPTD buffer; +# else +# error Data struct used by crypt_r() is unknown. Please report. +# endif + crypt_res = crypt_r(password, salt, &buffer); + } +# elif defined(HAVE_CRYPT) + crypt_res = crypt(password, salt); +# else +# error No crypt() implementation +# endif +#endif + + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } +} +/* }}} */ + + +/* {{{ proto string crypt(string str [, string salt]) + Hash a string */ +PHP_FUNCTION(crypt) +{ + char salt[PHP_MAX_SALT_LEN + 1]; + char *str, *salt_in = NULL; + size_t str_len, salt_in_len = 0; + zend_string *result; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(salt_in, salt_in_len) + ZEND_PARSE_PARAMETERS_END(); + + salt[0] = salt[PHP_MAX_SALT_LEN] = '\0'; + + /* This will produce suitable results if people depend on DES-encryption + * available (passing always 2-character salt). At least for glibc6.1 */ + memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1); + + if (salt_in) { + memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len)); + } else { + php_error_docref(NULL, E_NOTICE, "No salt parameter was specified. You must use a randomly generated salt and a strong hash function to produce a secure hash."); + } + + /* The automatic salt generation covers standard DES, md5-crypt and Blowfish (simple) */ + if (!*salt) { + memcpy(salt, "$1$", 3); + php_random_bytes_throw(&salt[3], 8); + php_to64(&salt[3], 8); + strncpy(&salt[11], "$", PHP_MAX_SALT_LEN - 11); + salt_in_len = strlen(salt); + } else { + salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len); + } + salt[salt_in_len] = '\0'; + + if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) { + if (salt[0] == '*' && salt[1] == '0') { + RETURN_STRING("*1"); + } else { + RETURN_STRING("*0"); + } + } + RETURN_STR(result); +} +/* }}} */ diff --git a/ext/standard/tests/password/password_bcrypt_short.phpt b/ext/standard/tests/password/password_bcrypt_short.phpt new file mode 100644 index 0000000000000..085bc8a239045 --- /dev/null +++ b/ext/standard/tests/password/password_bcrypt_short.phpt @@ -0,0 +1,8 @@ +--TEST-- +Test that password_hash() does not overread buffers when a short hash is passed +--FILE-- +<?php +var_dump(password_verify("foo", '$2')); +?> +--EXPECT-- +bool(false) From 05cca29fcb9128a8bc466487cdbf05117963b530 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:22 +0000 Subject: [PATCH 09/11] commit patch 28190908 --- sapi/cgi/cgi_main.c | 23 +- sapi/cgi/cgi_main.c.orig | 2711 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 2722 insertions(+), 12 deletions(-) create mode 100644 sapi/cgi/cgi_main.c.orig diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index cd79475fde4d0..4e210d9215205 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -1927,18 +1927,17 @@ int main(int argc, char *argv[]) /* check force_cgi after startup, so we have proper output */ if (cgi && CGIG(force_redirect)) { - /* Apache will generate REDIRECT_STATUS, - * Netscape and redirect.so will generate HTTP_REDIRECT_STATUS. - * redirect.so and installation instructions available from - * http://www.koehntopp.de/php. - * -- kk@netuse.de - */ - if (!getenv("REDIRECT_STATUS") && - !getenv ("HTTP_REDIRECT_STATUS") && - /* this is to allow a different env var to be configured - * in case some server does something different than above */ - (!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env))) - ) { + /* This is to allow a different environment variable to be configured + * in case the we cannot auto-detect which environment variable to use. + * Checking this first to allow user overrides in case the environment + * variable can be set by an untrusted party. */ + const char *redirect_status_env = CGIG(redirect_status_env); + if (!redirect_status_env) { + /* Apache will generate REDIRECT_STATUS. */ + redirect_status_env = "REDIRECT_STATUS"; + } + + if (!getenv(redirect_status_env)) { zend_try { SG(sapi_headers).http_response_code = 400; PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\ diff --git a/sapi/cgi/cgi_main.c.orig b/sapi/cgi/cgi_main.c.orig new file mode 100644 index 0000000000000..cd79475fde4d0 --- /dev/null +++ b/sapi/cgi/cgi_main.c.orig @@ -0,0 +1,2711 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Bakken <ssb@php.net> | + | Zeev Suraski <zeev@php.net> | + | FastCGI: Ben Mansell <php@slimyhorror.com> | + | Shane Caraveo <shane@caraveo.com> | + | Dmitry Stogov <dmitry@php.net> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_modules.h" + +#include "SAPI.h" + +#include <stdio.h> + +#ifdef PHP_WIN32 +# include "win32/time.h" +# include "win32/signal.h" +# include "win32/winutil.h" +# include <process.h> +#endif + +#if HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include <signal.h> + +#include <locale.h> + +#if HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif + +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "http_status_codes.h" +#include "ext/standard/php_standard.h" +#include "ext/standard/url.h" + +#ifdef PHP_WIN32 +# include <io.h> +# include <fcntl.h> +# include "win32/php_registry.h" +#endif + +#ifdef __riscos__ +# include <unixlib/local.h> +int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; +#endif + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" + +#include "php_getopt.h" + +#include "fastcgi.h" + +#if defined(PHP_WIN32) && defined(HAVE_OPENSSL) +# include "openssl/applink.c" +#endif + +#ifdef HAVE_VALGRIND +# include "valgrind/callgrind.h" +#endif + +#ifndef PHP_WIN32 +/* XXX this will need to change later when threaded fastcgi is implemented. shane */ +struct sigaction act, old_term, old_quit, old_int; +#endif + +static void (*php_php_import_environment_variables)(zval *array_ptr); + +/* these globals used for forking children on unix systems */ +/** + * Number of child processes that will get created to service requests + */ +static int children = 0; + + +/** + * Set to non-zero if we are the parent process + */ +static int parent = 1; + +#ifndef PHP_WIN32 +/* Did parent received exit signals SIG_TERM/SIG_INT/SIG_QUIT */ +static int exit_signal = 0; + +/* Is Parent waiting for children to exit */ +static int parent_waiting = 0; + +/** + * Process group + */ +static pid_t pgroup; +#endif + +#define PHP_MODE_STANDARD 1 +#define PHP_MODE_HIGHLIGHT 2 +#define PHP_MODE_LINT 4 +#define PHP_MODE_STRIP 5 + +static char *php_optarg = NULL; +static int php_optind = 1; +static zend_module_entry cgi_module_entry; + +static const opt_struct OPTIONS[] = { + {'a', 0, "interactive"}, + {'b', 1, "bindpath"}, + {'C', 0, "no-chdir"}, + {'c', 1, "php-ini"}, + {'d', 1, "define"}, + {'e', 0, "profile-info"}, + {'f', 1, "file"}, + {'h', 0, "help"}, + {'i', 0, "info"}, + {'l', 0, "syntax-check"}, + {'m', 0, "modules"}, + {'n', 0, "no-php-ini"}, + {'q', 0, "no-header"}, + {'s', 0, "syntax-highlight"}, + {'s', 0, "syntax-highlighting"}, + {'w', 0, "strip"}, + {'?', 0, "usage"},/* help alias (both '?' and 'usage') */ + {'v', 0, "version"}, + {'z', 1, "zend-extension"}, + {'T', 1, "timing"}, + {'-', 0, NULL} /* end of args */ +}; + +typedef struct _php_cgi_globals_struct { + HashTable user_config_cache; + char *redirect_status_env; + zend_bool rfc2616_headers; + zend_bool nph; + zend_bool check_shebang_line; + zend_bool fix_pathinfo; + zend_bool force_redirect; + zend_bool discard_path; + zend_bool fcgi_logging; +#ifdef PHP_WIN32 + zend_bool impersonate; +#endif +} php_cgi_globals_struct; + +/* {{{ user_config_cache + * + * Key for each cache entry is dirname(PATH_TRANSLATED). + * + * NOTE: Each cache entry config_hash contains the combination from all user ini files found in + * the path starting from doc_root through to dirname(PATH_TRANSLATED). There is no point + * storing per-file entries as it would not be possible to detect added / deleted entries + * between separate files. + */ +typedef struct _user_config_cache_entry { + time_t expires; + HashTable *user_config; +} user_config_cache_entry; + +static void user_config_cache_entry_dtor(zval *el) +{ + user_config_cache_entry *entry = (user_config_cache_entry *)Z_PTR_P(el); + zend_hash_destroy(entry->user_config); + free(entry->user_config); + free(entry); +} +/* }}} */ + +#ifdef ZTS +static int php_cgi_globals_id; +#define CGIG(v) ZEND_TSRMG(php_cgi_globals_id, php_cgi_globals_struct *, v) +#if defined(PHP_WIN32) +ZEND_TSRMLS_CACHE_DEFINE() +#endif +#else +static php_cgi_globals_struct php_cgi_globals; +#define CGIG(v) (php_cgi_globals.v) +#endif + +#ifdef PHP_WIN32 +#define TRANSLATE_SLASHES(path) \ + { \ + char *tmp = path; \ + while (*tmp) { \ + if (*tmp == '\\') *tmp = '/'; \ + tmp++; \ + } \ + } +#else +#define TRANSLATE_SLASHES(path) +#endif + +#ifdef PHP_WIN32 +#define WIN32_MAX_SPAWN_CHILDREN 64 +HANDLE kid_cgi_ps[WIN32_MAX_SPAWN_CHILDREN]; +int kids, cleaning_up = 0; +HANDLE job = NULL; +JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = { 0 }; +CRITICAL_SECTION cleanup_lock; +#endif + +#ifndef HAVE_ATTRIBUTE_WEAK +static void fcgi_log(int type, const char *format, ...) { + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} +#endif + +static int module_name_cmp(const void *a, const void *b) +{ + Bucket *f = (Bucket *) a; + Bucket *s = (Bucket *) b; + + return strcasecmp( ((zend_module_entry *)Z_PTR(f->val))->name, + ((zend_module_entry *)Z_PTR(s->val))->name); +} + +static void print_modules(void) +{ + HashTable sorted_registry; + zend_module_entry *module; + + zend_hash_init(&sorted_registry, 64, NULL, NULL, 1); + zend_hash_copy(&sorted_registry, &module_registry, NULL); + zend_hash_sort(&sorted_registry, module_name_cmp, 0); + ZEND_HASH_FOREACH_PTR(&sorted_registry, module) { + php_printf("%s\n", module->name); + } ZEND_HASH_FOREACH_END(); + zend_hash_destroy(&sorted_registry); +} + +static int print_extension_info(zend_extension *ext, void *arg) +{ + php_printf("%s\n", ext->name); + return 0; +} + +static int extension_name_cmp(const zend_llist_element **f, const zend_llist_element **s) +{ + zend_extension *fe = (zend_extension*)(*f)->data; + zend_extension *se = (zend_extension*)(*s)->data; + return strcmp(fe->name, se->name); +} + +static void print_extensions(void) +{ + zend_llist sorted_exts; + + zend_llist_copy(&sorted_exts, &zend_extensions); + sorted_exts.dtor = NULL; + zend_llist_sort(&sorted_exts, extension_name_cmp); + zend_llist_apply_with_argument(&sorted_exts, (llist_apply_with_arg_func_t) print_extension_info, NULL); + zend_llist_destroy(&sorted_exts); +} + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +static inline size_t sapi_cgi_single_write(const char *str, size_t str_length) +{ +#ifdef PHP_WRITE_STDOUT + int ret; + + ret = write(STDOUT_FILENO, str, str_length); + if (ret <= 0) return 0; + return ret; +#else + size_t ret; + + ret = fwrite(str, 1, MIN(str_length, 16384), stdout); + return ret; +#endif +} + +static size_t sapi_cgi_ub_write(const char *str, size_t str_length) +{ + const char *ptr = str; + size_t remaining = str_length; + size_t ret; + + while (remaining > 0) { + ret = sapi_cgi_single_write(ptr, remaining); + if (!ret) { + php_handle_aborted_connection(); + return str_length - remaining; + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + +static size_t sapi_fcgi_ub_write(const char *str, size_t str_length) +{ + const char *ptr = str; + size_t remaining = str_length; + fcgi_request *request = (fcgi_request*) SG(server_context); + + while (remaining > 0) { + int to_write = remaining > INT_MAX ? INT_MAX : (int)remaining; + int ret = fcgi_write(request, FCGI_STDOUT, ptr, to_write); + + if (ret <= 0) { + php_handle_aborted_connection(); + return str_length - remaining; + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + +static void sapi_cgi_flush(void *server_context) +{ + if (fflush(stdout) == EOF) { + php_handle_aborted_connection(); + } +} + +static void sapi_fcgi_flush(void *server_context) +{ + fcgi_request *request = (fcgi_request*) server_context; + + if ( + !parent && + request && !fcgi_flush(request, 0)) { + + php_handle_aborted_connection(); + } +} + +#define SAPI_CGI_MAX_HEADER_LENGTH 1024 + +static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers) +{ + sapi_header_struct *h; + zend_llist_position pos; + zend_bool ignore_status = 0; + int response_status = SG(sapi_headers).http_response_code; + + if (SG(request_info).no_headers == 1) { + return SAPI_HEADER_SENT_SUCCESSFULLY; + } + + if (CGIG(nph) || SG(sapi_headers).http_response_code != 200) + { + int len; + zend_bool has_status = 0; + char buf[SAPI_CGI_MAX_HEADER_LENGTH]; + + if (CGIG(rfc2616_headers) && SG(sapi_headers).http_status_line) { + char *s; + len = slprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH, "%s\r\n", SG(sapi_headers).http_status_line); + if ((s = strchr(SG(sapi_headers).http_status_line, ' '))) { + response_status = atoi((s + 1)); + } + + if (len > SAPI_CGI_MAX_HEADER_LENGTH) { + len = SAPI_CGI_MAX_HEADER_LENGTH; + } + + } else { + char *s; + + if (SG(sapi_headers).http_status_line && + (s = strchr(SG(sapi_headers).http_status_line, ' ')) != 0 && + (s - SG(sapi_headers).http_status_line) >= 5 && + strncasecmp(SG(sapi_headers).http_status_line, "HTTP/", 5) == 0 + ) { + len = slprintf(buf, sizeof(buf), "Status:%s\r\n", s); + response_status = atoi((s + 1)); + } else { + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if (h->header_len > sizeof("Status:")-1 && + strncasecmp(h->header, "Status:", sizeof("Status:")-1) == 0 + ) { + has_status = 1; + break; + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + if (!has_status) { + http_response_status_code_pair *err = (http_response_status_code_pair*)http_status_map; + + while (err->code != 0) { + if (err->code == SG(sapi_headers).http_response_code) { + break; + } + err++; + } + if (err->str) { + len = slprintf(buf, sizeof(buf), "Status: %d %s\r\n", SG(sapi_headers).http_response_code, err->str); + } else { + len = slprintf(buf, sizeof(buf), "Status: %d\r\n", SG(sapi_headers).http_response_code); + } + } + } + } + + if (!has_status) { + PHPWRITE_H(buf, len); + ignore_status = 1; + } + } + + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + /* prevent CRLFCRLF */ + if (h->header_len) { + if (h->header_len > sizeof("Status:")-1 && + strncasecmp(h->header, "Status:", sizeof("Status:")-1) == 0 + ) { + if (!ignore_status) { + ignore_status = 1; + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } else if (response_status == 304 && h->header_len > sizeof("Content-Type:")-1 && + strncasecmp(h->header, "Content-Type:", sizeof("Content-Type:")-1) == 0 + ) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } else { + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + PHPWRITE_H("\r\n", 2); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +#endif + +static size_t sapi_cgi_read_post(char *buffer, size_t count_bytes) +{ + size_t read_bytes = 0; + int tmp_read_bytes; + size_t remaining_bytes; + + assert(SG(request_info).content_length >= SG(read_post_bytes)); + + remaining_bytes = (size_t)(SG(request_info).content_length - SG(read_post_bytes)); + + count_bytes = MIN(count_bytes, remaining_bytes); + while (read_bytes < count_bytes) { +#ifdef PHP_WIN32 + size_t diff = count_bytes - read_bytes; + unsigned int to_read = (diff > UINT_MAX) ? UINT_MAX : (unsigned int)diff; + + tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, to_read); +#else + tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, count_bytes - read_bytes); +#endif + if (tmp_read_bytes <= 0) { + break; + } + read_bytes += tmp_read_bytes; + } + return read_bytes; +} + +static size_t sapi_fcgi_read_post(char *buffer, size_t count_bytes) +{ + size_t read_bytes = 0; + int tmp_read_bytes; + fcgi_request *request = (fcgi_request*) SG(server_context); + size_t remaining = SG(request_info).content_length - SG(read_post_bytes); + + if (remaining < count_bytes) { + count_bytes = remaining; + } + while (read_bytes < count_bytes) { + size_t diff = count_bytes - read_bytes; + int to_read = (diff > INT_MAX) ? INT_MAX : (int)diff; + + tmp_read_bytes = fcgi_read(request, buffer + read_bytes, to_read); + if (tmp_read_bytes <= 0) { + break; + } + read_bytes += tmp_read_bytes; + } + return read_bytes; +} + +#ifdef PHP_WIN32 +/* The result needs to be freed! See sapi_getenv(). */ +static char *cgi_getenv_win32(const char *name, size_t name_len) +{ + char *ret = NULL; + wchar_t *keyw, *valw; + size_t size; + int rc; + + keyw = php_win32_cp_conv_any_to_w(name, name_len, PHP_WIN32_CP_IGNORE_LEN_P); + if (!keyw) { + return NULL; + } + + rc = _wgetenv_s(&size, NULL, 0, keyw); + if (rc || 0 == size) { + free(keyw); + return NULL; + } + + valw = emalloc((size + 1) * sizeof(wchar_t)); + + rc = _wgetenv_s(&size, valw, size, keyw); + if (!rc) { + ret = php_win32_cp_w_to_any(valw); + } + + free(keyw); + efree(valw); + + return ret; +} +#endif + +static char *sapi_cgi_getenv(char *name, size_t name_len) +{ +#ifndef PHP_WIN32 + return getenv(name); +#else + return cgi_getenv_win32(name, name_len); +#endif +} + +static char *sapi_fcgi_getenv(char *name, size_t name_len) +{ + /* when php is started by mod_fastcgi, no regular environment + * is provided to PHP. It is always sent to PHP at the start + * of a request. So we have to do our own lookup to get env + * vars. This could probably be faster somehow. */ + fcgi_request *request = (fcgi_request*) SG(server_context); + char *ret = fcgi_getenv(request, name, (int)name_len); + +#ifndef PHP_WIN32 + if (ret) return ret; + /* if cgi, or fastcgi and not found in fcgi env + check the regular environment */ + return getenv(name); +#else + if (ret) { + /* The functions outside here don't know, where does it come + from. They'll need to free the returned memory as it's + not necessary from the fcgi env. */ + return strdup(ret); + } + /* if cgi, or fastcgi and not found in fcgi env + check the regular environment */ + return cgi_getenv_win32(name, name_len); +#endif +} + +static char *_sapi_cgi_putenv(char *name, size_t name_len, char *value) +{ +#if !HAVE_SETENV || !HAVE_UNSETENV + size_t len; + char *buf; +#endif + +#if HAVE_SETENV + if (value) { + setenv(name, value, 1); + } +#endif +#if HAVE_UNSETENV + if (!value) { + unsetenv(name); + } +#endif + +#if !HAVE_SETENV || !HAVE_UNSETENV + /* if cgi, or fastcgi and not found in fcgi env + check the regular environment + this leaks, but it's only cgi anyway, we'll fix + it for 5.0 + */ + len = name_len + (value ? strlen(value) : 0) + sizeof("=") + 2; + buf = (char *) malloc(len); + if (buf == NULL) { + return getenv(name); + } +#endif +#if !HAVE_SETENV + if (value) { + len = slprintf(buf, len - 1, "%s=%s", name, value); + putenv(buf); + } +#endif +#if !HAVE_UNSETENV + if (!value) { + len = slprintf(buf, len - 1, "%s=", name); + putenv(buf); + } +#endif + return getenv(name); +} + +static char *sapi_cgi_read_cookies(void) +{ + return getenv("HTTP_COOKIE"); +} + +static char *sapi_fcgi_read_cookies(void) +{ + fcgi_request *request = (fcgi_request*) SG(server_context); + + return FCGI_GETENV(request, "HTTP_COOKIE"); +} + +static void cgi_php_load_env_var(char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg) +{ + zval *array_ptr = (zval*)arg; + int filter_arg = (Z_ARR_P(array_ptr) == Z_ARR(PG(http_globals)[TRACK_VARS_ENV]))?PARSE_ENV:PARSE_SERVER; + size_t new_val_len; + + if (sapi_module.input_filter(filter_arg, var, &val, strlen(val), &new_val_len)) { + php_register_variable_safe(var, val, new_val_len, array_ptr); + } +} + +static void cgi_php_import_environment_variables(zval *array_ptr) +{ + if (PG(variables_order) && (strchr(PG(variables_order),'E') || strchr(PG(variables_order),'e'))) { + if (Z_TYPE(PG(http_globals)[TRACK_VARS_ENV]) != IS_ARRAY) { + zend_is_auto_global_str("_ENV", sizeof("_ENV")-1); + } + + if (Z_TYPE(PG(http_globals)[TRACK_VARS_ENV]) == IS_ARRAY && + Z_ARR_P(array_ptr) != Z_ARR(PG(http_globals)[TRACK_VARS_ENV])) { + zend_array_destroy(Z_ARR_P(array_ptr)); + Z_ARR_P(array_ptr) = zend_array_dup(Z_ARR(PG(http_globals)[TRACK_VARS_ENV])); + return; + } + } + + /* call php's original import as a catch-all */ + php_php_import_environment_variables(array_ptr); + + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + fcgi_loadenv(request, cgi_php_load_env_var, array_ptr); + } +} + +static void sapi_cgi_register_variables(zval *track_vars_array) +{ + size_t php_self_len; + char *php_self; + + /* In CGI mode, we consider the environment to be a part of the server + * variables + */ + php_import_environment_variables(track_vars_array); + + if (CGIG(fix_pathinfo)) { + char *script_name = SG(request_info).request_uri; + char *path_info; + int free_php_self; + ALLOCA_FLAG(use_heap) + + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + path_info = FCGI_GETENV(request, "PATH_INFO"); + } else { + path_info = getenv("PATH_INFO"); + } + + if (path_info) { + size_t path_info_len = strlen(path_info); + + if (script_name) { + size_t script_name_len = strlen(script_name); + + php_self_len = script_name_len + path_info_len; + php_self = do_alloca(php_self_len + 1, use_heap); + memcpy(php_self, script_name, script_name_len + 1); + memcpy(php_self + script_name_len, path_info, path_info_len + 1); + free_php_self = 1; + } else { + php_self = path_info; + php_self_len = path_info_len; + free_php_self = 0; + } + } else if (script_name) { + php_self = script_name; + php_self_len = strlen(script_name); + free_php_self = 0; + } else { + php_self = ""; + php_self_len = 0; + free_php_self = 0; + } + + /* Build the special-case PHP_SELF variable for the CGI version */ + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array); + } + if (free_php_self) { + free_alloca(php_self, use_heap); + } + } else { + php_self = SG(request_info).request_uri ? SG(request_info).request_uri : ""; + php_self_len = strlen(php_self); + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array); + } + } +} + +static void sapi_cgi_log_message(char *message, int syslog_type_int) +{ + if (fcgi_is_fastcgi() && CGIG(fcgi_logging)) { + fcgi_request *request; + + request = (fcgi_request*) SG(server_context); + if (request) { + int ret, len = (int)strlen(message); + char *buf = malloc(len+2); + + memcpy(buf, message, len); + memcpy(buf + len, "\n", sizeof("\n")); + ret = fcgi_write(request, FCGI_STDERR, buf, (int)(len + 1)); + free(buf); + if (ret < 0) { + php_handle_aborted_connection(); + } + } else { + fprintf(stderr, "%s\n", message); + } + /* ignore return code */ + } else { + fprintf(stderr, "%s\n", message); + } +} + +/* {{{ php_cgi_ini_activate_user_config + */ +static void php_cgi_ini_activate_user_config(char *path, size_t path_len, const char *doc_root, size_t doc_root_len) +{ + user_config_cache_entry *new_entry, *entry; + time_t request_time = (time_t)sapi_get_request_time(); + + /* Find cached config entry: If not found, create one */ + if ((entry = zend_hash_str_find_ptr(&CGIG(user_config_cache), path, path_len)) == NULL) { + new_entry = pemalloc(sizeof(user_config_cache_entry), 1); + new_entry->expires = 0; + new_entry->user_config = (HashTable *) pemalloc(sizeof(HashTable), 1); + zend_hash_init(new_entry->user_config, 8, NULL, (dtor_func_t) config_zval_dtor, 1); + entry = zend_hash_str_update_ptr(&CGIG(user_config_cache), path, path_len, new_entry); + } + + /* Check whether cache entry has expired and rescan if it is */ + if (request_time > entry->expires) { + char *real_path = NULL; + char *s1, *s2; + size_t s_len; + + /* Clear the expired config */ + zend_hash_clean(entry->user_config); + + if (!IS_ABSOLUTE_PATH(path, path_len)) { + size_t real_path_len; + real_path = tsrm_realpath(path, NULL); + if (real_path == NULL) { + return; + } + real_path_len = strlen(real_path); + path = real_path; + path_len = real_path_len; + } + + if (path_len > doc_root_len) { + s1 = (char *) doc_root; + s2 = path; + s_len = doc_root_len; + } else { + s1 = path; + s2 = (char *) doc_root; + s_len = path_len; + } + + /* we have to test if path is part of DOCUMENT_ROOT. + if it is inside the docroot, we scan the tree up to the docroot + to find more user.ini, if not we only scan the current path. + */ +#ifdef PHP_WIN32 + if (strnicmp(s1, s2, s_len) == 0) { +#else + if (strncmp(s1, s2, s_len) == 0) { +#endif + char *ptr = s2 + doc_root_len; +#ifdef PHP_WIN32 + while ((ptr = strpbrk(ptr, "\\/")) != NULL) { +#else + while ((ptr = strchr(ptr, DEFAULT_SLASH)) != NULL) { +#endif + *ptr = 0; + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config); + *ptr = '/'; + ptr++; + } + } else { + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config); + } + + if (real_path) { + efree(real_path); + } + entry->expires = request_time + PG(user_ini_cache_ttl); + } + + /* Activate ini entries with values from the user config hash */ + php_ini_activate_config(entry->user_config, PHP_INI_PERDIR, PHP_INI_STAGE_HTACCESS); +} +/* }}} */ + +static int sapi_cgi_activate(void) +{ + /* PATH_TRANSLATED should be defined at this stage but better safe than sorry :) */ + if (!SG(request_info).path_translated) { + return FAILURE; + } + + if (php_ini_has_per_host_config()) { + char *server_name; + + /* Activate per-host-system-configuration defined in php.ini and stored into configuration_hash during startup */ + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + server_name = FCGI_GETENV(request, "SERVER_NAME"); + } else { + server_name = getenv("SERVER_NAME"); + } + /* SERVER_NAME should also be defined at this stage..but better check it anyway */ + if (server_name) { + size_t server_name_len = strlen(server_name); + server_name = estrndup(server_name, server_name_len); + zend_str_tolower(server_name, server_name_len); + php_ini_activate_per_host_config(server_name, server_name_len); + efree(server_name); + } + } + + if (php_ini_has_per_dir_config() || + (PG(user_ini_filename) && *PG(user_ini_filename)) + ) { + char *path; + size_t path_len; + + /* Prepare search path */ + path_len = strlen(SG(request_info).path_translated); + + /* Make sure we have trailing slash! */ + if (!IS_SLASH(SG(request_info).path_translated[path_len])) { + path = emalloc(path_len + 2); + memcpy(path, SG(request_info).path_translated, path_len + 1); + path_len = zend_dirname(path, path_len); + path[path_len++] = DEFAULT_SLASH; + } else { + path = estrndup(SG(request_info).path_translated, path_len); + path_len = zend_dirname(path, path_len); + } + path[path_len] = 0; + + /* Activate per-dir-system-configuration defined in php.ini and stored into configuration_hash during startup */ + php_ini_activate_per_dir_config(path, path_len); /* Note: for global settings sake we check from root to path */ + + /* Load and activate user ini files in path starting from DOCUMENT_ROOT */ + if (PG(user_ini_filename) && *PG(user_ini_filename)) { + char *doc_root; + + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + doc_root = FCGI_GETENV(request, "DOCUMENT_ROOT"); + } else { + doc_root = getenv("DOCUMENT_ROOT"); + } + /* DOCUMENT_ROOT should also be defined at this stage..but better check it anyway */ + if (doc_root) { + size_t doc_root_len = strlen(doc_root); + if (doc_root_len > 0 && IS_SLASH(doc_root[doc_root_len - 1])) { + --doc_root_len; + } +#ifdef PHP_WIN32 + /* paths on windows should be case-insensitive */ + doc_root = estrndup(doc_root, doc_root_len); + zend_str_tolower(doc_root, doc_root_len); +#endif + php_cgi_ini_activate_user_config(path, path_len, doc_root, doc_root_len); + +#ifdef PHP_WIN32 + efree(doc_root); +#endif + } + } + + efree(path); + } + + return SUCCESS; +} + +static int sapi_cgi_deactivate(void) +{ + /* flush only when SAPI was started. The reasons are: + 1. SAPI Deactivate is called from two places: module init and request shutdown + 2. When the first call occurs and the request is not set up, flush fails on FastCGI. + */ + if (SG(sapi_started)) { + if (fcgi_is_fastcgi()) { + if ( + !parent && + !fcgi_finish_request((fcgi_request*)SG(server_context), 0)) { + php_handle_aborted_connection(); + } + } else { + sapi_cgi_flush(SG(server_context)); + } + } + return SUCCESS; +} + +static int php_cgi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &cgi_module_entry, 1) == FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +/* {{{ sapi_module_struct cgi_sapi_module + */ +static sapi_module_struct cgi_sapi_module = { + "cgi-fcgi", /* name */ + "CGI/FastCGI", /* pretty name */ + + php_cgi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + sapi_cgi_activate, /* activate */ + sapi_cgi_deactivate, /* deactivate */ + + sapi_cgi_ub_write, /* unbuffered write */ + sapi_cgi_flush, /* flush */ + NULL, /* get uid */ + sapi_cgi_getenv, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_cgi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_cgi_read_post, /* read POST data */ + sapi_cgi_read_cookies, /* read Cookies */ + + sapi_cgi_register_variables, /* register server variables */ + sapi_cgi_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ arginfo ext/standard/dl.c */ +ZEND_BEGIN_ARG_INFO(arginfo_dl, 0) + ZEND_ARG_INFO(0, extension_filename) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry additional_functions[] = { + ZEND_FE(dl, arginfo_dl) + PHP_FE_END +}; + +/* {{{ php_cgi_usage + */ +static void php_cgi_usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "php"; + } + + php_printf( "Usage: %s [-q] [-h] [-s] [-v] [-i] [-f <file>]\n" + " %s <file> [args...]\n" + " -a Run interactively\n" + " -b <address:port>|<port> Bind Path for external FASTCGI Server mode\n" + " -C Do not chdir to the script's directory\n" + " -c <path>|<file> Look for php.ini file in this directory\n" + " -n No php.ini file will be used\n" + " -d foo[=bar] Define INI entry foo with value 'bar'\n" + " -e Generate extended information for debugger/profiler\n" + " -f <file> Parse <file>. Implies `-q'\n" + " -h This help\n" + " -i PHP information\n" + " -l Syntax check only (lint)\n" + " -m Show compiled in modules\n" + " -q Quiet-mode. Suppress HTTP Header output.\n" + " -s Display colour syntax highlighted source.\n" + " -v Version number\n" + " -w Display source with stripped comments and whitespace.\n" + " -z <file> Load Zend extension <file>.\n" + " -T <count> Measure execution time of script repeated <count> times.\n", + prog, prog); +} +/* }}} */ + +/* {{{ is_valid_path + * + * some server configurations allow '..' to slip through in the + * translated path. We'll just refuse to handle such a path. + */ +static int is_valid_path(const char *path) +{ + const char *p = path; + + if (UNEXPECTED(!p)) { + return 0; + } + if (UNEXPECTED(*p == '.') && *(p+1) == '.' && (!*(p+2) || IS_SLASH(*(p+2)))) { + return 0; + } + while (*p) { + if (IS_SLASH(*p)) { + p++; + if (UNEXPECTED(*p == '.')) { + p++; + if (UNEXPECTED(*p == '.')) { + p++; + if (UNEXPECTED(!*p) || UNEXPECTED(IS_SLASH(*p))) { + return 0; + } + } + } + } + p++; + } + return 1; +} +/* }}} */ + +#define CGI_GETENV(name) \ + ((has_env) ? \ + FCGI_GETENV(request, name) : \ + getenv(name)) + +#define CGI_PUTENV(name, value) \ + ((has_env) ? \ + FCGI_PUTENV(request, name, value) : \ + _sapi_cgi_putenv(name, sizeof(name)-1, value)) + +/* {{{ init_request_info + + initializes request_info structure + + specificly in this section we handle proper translations + for: + + PATH_INFO + derived from the portion of the URI path following + the script name but preceding any query data + may be empty + + PATH_TRANSLATED + derived by taking any path-info component of the + request URI and performing any virtual-to-physical + translation appropriate to map it onto the server's + document repository structure + + empty if PATH_INFO is empty + + The env var PATH_TRANSLATED **IS DIFFERENT** than the + request_info.path_translated variable, the latter should + match SCRIPT_FILENAME instead. + + SCRIPT_NAME + set to a URL path that could identify the CGI script + rather than the interpreter. PHP_SELF is set to this + + REQUEST_URI + uri section following the domain:port part of a URI + + SCRIPT_FILENAME + The virtual-to-physical translation of SCRIPT_NAME (as per + PATH_TRANSLATED) + + These settings are documented at + http://cgi-spec.golux.com/ + + + Based on the following URL request: + + http://localhost/info.php/test?a=b + + should produce, which btw is the same as if + we were running under mod_cgi on apache (ie. not + using ScriptAlias directives): + + PATH_INFO=/test + PATH_TRANSLATED=/docroot/test + SCRIPT_NAME=/info.php + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/docroot/info.php + QUERY_STRING=a=b + + but what we get is (cgi/mod_fastcgi under apache): + + PATH_INFO=/info.php/test + PATH_TRANSLATED=/docroot/info.php/test + SCRIPT_NAME=/php/php-cgi (from the Action setting I suppose) + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/path/to/php/bin/php-cgi (Action setting translated) + QUERY_STRING=a=b + + Comments in the code below refer to using the above URL in a request + + */ +static void init_request_info(fcgi_request *request) +{ + int has_env = fcgi_has_env(request); + char *env_script_filename = CGI_GETENV("SCRIPT_FILENAME"); + char *env_path_translated = CGI_GETENV("PATH_TRANSLATED"); + char *script_path_translated = env_script_filename; + + /* some broken servers do not have script_filename or argv0 + * an example, IIS configured in some ways. then they do more + * broken stuff and set path_translated to the cgi script location */ + if (!script_path_translated && env_path_translated) { + script_path_translated = env_path_translated; + } + + /* initialize the defaults */ + SG(request_info).path_translated = NULL; + SG(request_info).request_method = NULL; + SG(request_info).proto_num = 1000; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = NULL; + SG(request_info).content_type = NULL; + SG(request_info).content_length = 0; + SG(sapi_headers).http_response_code = 200; + + /* script_path_translated being set is a good indication that + * we are running in a cgi environment, since it is always + * null otherwise. otherwise, the filename + * of the script will be retrieved later via argc/argv */ + if (script_path_translated) { + const char *auth; + char *content_length = CGI_GETENV("CONTENT_LENGTH"); + char *content_type = CGI_GETENV("CONTENT_TYPE"); + char *env_path_info = CGI_GETENV("PATH_INFO"); + char *env_script_name = CGI_GETENV("SCRIPT_NAME"); + +#ifdef PHP_WIN32 + /* Hack for buggy IIS that sets incorrect PATH_INFO */ + char *env_server_software = CGI_GETENV("SERVER_SOFTWARE"); + + if (env_server_software && + env_script_name && + env_path_info && + strncmp(env_server_software, "Microsoft-IIS", sizeof("Microsoft-IIS")-1) == 0 && + strncmp(env_path_info, env_script_name, strlen(env_script_name)) == 0 + ) { + env_path_info = CGI_PUTENV("ORIG_PATH_INFO", env_path_info); + env_path_info += strlen(env_script_name); + if (*env_path_info == 0) { + env_path_info = NULL; + } + env_path_info = CGI_PUTENV("PATH_INFO", env_path_info); + } +#endif + + if (CGIG(fix_pathinfo)) { + zend_stat_t st; + char *real_path = NULL; + char *env_redirect_url = CGI_GETENV("REDIRECT_URL"); + char *env_document_root = CGI_GETENV("DOCUMENT_ROOT"); + char *orig_path_translated = env_path_translated; + char *orig_path_info = env_path_info; + char *orig_script_name = env_script_name; + char *orig_script_filename = env_script_filename; + size_t script_path_translated_len; + + if (!env_document_root && PG(doc_root)) { + env_document_root = CGI_PUTENV("DOCUMENT_ROOT", PG(doc_root)); + /* fix docroot */ + TRANSLATE_SLASHES(env_document_root); + } + + if (env_path_translated != NULL && env_redirect_url != NULL && + env_path_translated != script_path_translated && + strcmp(env_path_translated, script_path_translated) != 0) { + /* + * pretty much apache specific. If we have a redirect_url + * then our script_filename and script_name point to the + * php executable + */ + script_path_translated = env_path_translated; + /* we correct SCRIPT_NAME now in case we don't have PATH_INFO */ + env_script_name = env_redirect_url; + } + +#ifdef __riscos__ + /* Convert path to unix format*/ + __riscosify_control |= __RISCOSIFY_DONT_CHECK_DIR; + script_path_translated = __unixify(script_path_translated, 0, NULL, 1, 0); +#endif + + /* + * if the file doesn't exist, try to extract PATH_INFO out + * of it by stat'ing back through the '/' + * this fixes url's like /info.php/test + */ + if (script_path_translated && + (script_path_translated_len = strlen(script_path_translated)) > 0 && + (script_path_translated[script_path_translated_len-1] == '/' || +#ifdef PHP_WIN32 + script_path_translated[script_path_translated_len-1] == '\\' || +#endif + (real_path = tsrm_realpath(script_path_translated, NULL)) == NULL) + ) { + char *pt = estrndup(script_path_translated, script_path_translated_len); + size_t len = script_path_translated_len; + char *ptr; + + while ((ptr = strrchr(pt, '/')) || (ptr = strrchr(pt, '\\'))) { + *ptr = 0; + if (zend_stat(pt, &st) == 0 && S_ISREG(st.st_mode)) { + /* + * okay, we found the base script! + * work out how many chars we had to strip off; + * then we can modify PATH_INFO + * accordingly + * + * we now have the makings of + * PATH_INFO=/test + * SCRIPT_FILENAME=/docroot/info.php + * + * we now need to figure out what docroot is. + * if DOCUMENT_ROOT is set, this is easy, otherwise, + * we have to play the game of hide and seek to figure + * out what SCRIPT_NAME should be + */ + size_t slen = len - strlen(pt); + size_t pilen = env_path_info ? strlen(env_path_info) : 0; + char *path_info = env_path_info ? env_path_info + pilen - slen : NULL; + + if (orig_path_info != path_info) { + if (orig_path_info) { + char old; + + CGI_PUTENV("ORIG_PATH_INFO", orig_path_info); + old = path_info[0]; + path_info[0] = 0; + if (!orig_script_name || + strcmp(orig_script_name, env_path_info) != 0) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_path_info); + } else { + SG(request_info).request_uri = orig_script_name; + } + path_info[0] = old; + } + env_path_info = CGI_PUTENV("PATH_INFO", path_info); + } + if (!orig_script_filename || + strcmp(orig_script_filename, pt) != 0) { + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", pt); + } + TRANSLATE_SLASHES(pt); + + /* figure out docroot + * SCRIPT_FILENAME minus SCRIPT_NAME + */ + if (env_document_root) { + size_t l = strlen(env_document_root); + size_t path_translated_len = 0; + char *path_translated = NULL; + + if (l && env_document_root[l - 1] == '/') { + --l; + } + + /* we have docroot, so we should have: + * DOCUMENT_ROOT=/docroot + * SCRIPT_FILENAME=/docroot/info.php + */ + + /* PATH_TRANSLATED = DOCUMENT_ROOT + PATH_INFO */ + path_translated_len = l + (env_path_info ? strlen(env_path_info) : 0); + path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, env_document_root, l); + if (env_path_info) { + memcpy(path_translated + l, env_path_info, (path_translated_len - l)); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + } + env_path_translated = CGI_PUTENV("PATH_TRANSLATED", path_translated); + efree(path_translated); + } else if ( env_script_name && + strstr(pt, env_script_name) + ) { + /* PATH_TRANSLATED = PATH_TRANSLATED - SCRIPT_NAME + PATH_INFO */ + size_t ptlen = strlen(pt) - strlen(env_script_name); + size_t path_translated_len = ptlen + (env_path_info ? strlen(env_path_info) : 0); + + char *path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, pt, ptlen); + if (env_path_info) { + memcpy(path_translated + ptlen, env_path_info, path_translated_len - ptlen); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + } + env_path_translated = CGI_PUTENV("PATH_TRANSLATED", path_translated); + efree(path_translated); + } + break; + } + } + if (!ptr) { + /* + * if we stripped out all the '/' and still didn't find + * a valid path... we will fail, badly. of course we would + * have failed anyway... we output 'no input file' now. + */ + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", NULL); + SG(sapi_headers).http_response_code = 404; + } + if (!SG(request_info).request_uri) { + if (!orig_script_name || + strcmp(orig_script_name, env_script_name) != 0) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_script_name); + } else { + SG(request_info).request_uri = orig_script_name; + } + } + if (pt) { + efree(pt); + } + } else { + /* make sure path_info/translated are empty */ + if (!orig_script_filename || + (script_path_translated != orig_script_filename && + strcmp(script_path_translated, orig_script_filename) != 0)) { + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", script_path_translated); + } + if (env_redirect_url) { + if (orig_path_info) { + CGI_PUTENV("ORIG_PATH_INFO", orig_path_info); + CGI_PUTENV("PATH_INFO", NULL); + } + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + CGI_PUTENV("PATH_TRANSLATED", NULL); + } + } + if (env_script_name != orig_script_name) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_script_name); + } else { + SG(request_info).request_uri = env_script_name; + } + efree(real_path); + } + } else { + /* pre 4.3 behaviour, shouldn't be used but provides BC */ + if (env_path_info) { + SG(request_info).request_uri = env_path_info; + } else { + SG(request_info).request_uri = env_script_name; + } + if (!CGIG(discard_path) && env_path_translated) { + script_path_translated = env_path_translated; + } + } + + if (is_valid_path(script_path_translated)) { + SG(request_info).path_translated = estrdup(script_path_translated); + } + + SG(request_info).request_method = CGI_GETENV("REQUEST_METHOD"); + /* FIXME - Work out proto_num here */ + SG(request_info).query_string = CGI_GETENV("QUERY_STRING"); + SG(request_info).content_type = (content_type ? content_type : "" ); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + + /* The CGI RFC allows servers to pass on unvalidated Authorization data */ + auth = CGI_GETENV("HTTP_AUTHORIZATION"); + php_handle_auth_data(auth); + } +} +/* }}} */ + +#ifndef PHP_WIN32 +/** + * Clean up child processes upon exit + */ +void fastcgi_cleanup(int signal) +{ +#ifdef DEBUG_FASTCGI + fprintf(stderr, "FastCGI shutdown, pid %d\n", getpid()); +#endif + + sigaction(SIGTERM, &old_term, 0); + + /* Kill all the processes in our process group */ + kill(-pgroup, SIGTERM); + + if (parent && parent_waiting) { + exit_signal = 1; + } else { + exit(0); + } +} +#else +BOOL WINAPI fastcgi_cleanup(DWORD sig) +{ + int i = kids; + + EnterCriticalSection(&cleanup_lock); + cleaning_up = 1; + LeaveCriticalSection(&cleanup_lock); + + while (0 < i--) { + if (NULL == kid_cgi_ps[i]) { + continue; + } + + TerminateProcess(kid_cgi_ps[i], 0); + CloseHandle(kid_cgi_ps[i]); + kid_cgi_ps[i] = NULL; + } + + if (job) { + CloseHandle(job); + } + + parent = 0; + + return TRUE; +} +#endif + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.check_shebang_line", "1", PHP_INI_SYSTEM, OnUpdateBool, check_shebang_line, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.force_redirect", "1", PHP_INI_SYSTEM, OnUpdateBool, force_redirect, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.redirect_status_env", NULL, PHP_INI_SYSTEM, OnUpdateString, redirect_status_env, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals) +#ifdef PHP_WIN32 + STD_PHP_INI_ENTRY("fastcgi.impersonate", "0", PHP_INI_SYSTEM, OnUpdateBool, impersonate, php_cgi_globals_struct, php_cgi_globals) +#endif +PHP_INI_END() + +/* {{{ php_cgi_globals_ctor + */ +static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals) +{ +#if defined(ZTS) && defined(PHP_WIN32) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + php_cgi_globals->rfc2616_headers = 0; + php_cgi_globals->nph = 0; + php_cgi_globals->check_shebang_line = 1; + php_cgi_globals->force_redirect = 1; + php_cgi_globals->redirect_status_env = NULL; + php_cgi_globals->fix_pathinfo = 1; + php_cgi_globals->discard_path = 0; + php_cgi_globals->fcgi_logging = 1; +#ifdef PHP_WIN32 + php_cgi_globals->impersonate = 0; +#endif + zend_hash_init(&php_cgi_globals->user_config_cache, 8, NULL, user_config_cache_entry_dtor, 1); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +static PHP_MINIT_FUNCTION(cgi) +{ + REGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(cgi) +{ + zend_hash_destroy(&CGIG(user_config_cache)); + + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +static PHP_MINFO_FUNCTION(cgi) +{ + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +PHP_FUNCTION(apache_child_terminate) /* {{{ */ +{ + if (zend_parse_parameters_none()) { + return; + } + if (fcgi_is_fastcgi()) { + fcgi_terminate(); + } +} +/* }}} */ + + +PHP_FUNCTION(apache_request_headers) /* {{{ */ +{ + if (zend_parse_parameters_none()) { + return; + } + array_init(return_value); + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + fcgi_loadenv(request, sapi_add_request_header, return_value); + } else { + char buf[128]; + char **env, *p, *q, *var, *val, *t = buf; + size_t alloc_size = sizeof(buf); + zend_ulong var_len; + + for (env = environ; env != NULL && *env != NULL; env++) { + val = strchr(*env, '='); + if (!val) { /* malformed entry? */ + continue; + } + var_len = val - *env; + if (var_len >= alloc_size) { + alloc_size = var_len + 64; + t = (t == buf ? emalloc(alloc_size): erealloc(t, alloc_size)); + } + var = *env; + if (var_len > 5 && + var[0] == 'H' && + var[1] == 'T' && + var[2] == 'T' && + var[3] == 'P' && + var[4] == '_') { + + var_len -= 5; + + if (var_len >= alloc_size) { + alloc_size = var_len + 64; + t = (t == buf ? emalloc(alloc_size): erealloc(t, alloc_size)); + } + p = var + 5; + + var = q = t; + /* First char keep uppercase */ + *q++ = *p++; + while (*p) { + if (*p == '=') { + /* End of name */ + break; + } else if (*p == '_') { + *q++ = '-'; + p++; + /* First char after - keep uppercase */ + if (*p && *p!='=') { + *q++ = *p++; + } + } else if (*p >= 'A' && *p <= 'Z') { + /* lowercase */ + *q++ = (*p++ - 'A' + 'a'); + } else { + *q++ = *p++; + } + } + *q = 0; + } else if (var_len == sizeof("CONTENT_TYPE")-1 && + memcmp(var, "CONTENT_TYPE", sizeof("CONTENT_TYPE")-1) == 0) { + var = "Content-Type"; + } else if (var_len == sizeof("CONTENT_LENGTH")-1 && + memcmp(var, "CONTENT_LENGTH", sizeof("CONTENT_LENGTH")-1) == 0) { + var = "Content-Length"; + } else { + continue; + } + val++; + add_assoc_string_ex(return_value, var, var_len, val); + } + if (t != buf && t != NULL) { + efree(t); + } + } +} +/* }}} */ + +static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{{ */ +{ + if (h->header_len > 0) { + char *s; + size_t len = 0; + ALLOCA_FLAG(use_heap) + + char *p = strchr(h->header, ':'); + if (NULL != p) { + len = p - h->header; + } + if (len > 0) { + while (len != 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) { + len--; + } + if (len) { + s = do_alloca(len + 1, use_heap); + memcpy(s, h->header, len); + s[len] = 0; + do { + p++; + } while (*p == ' ' || *p == '\t'); + add_assoc_stringl_ex(return_value, s, len, p, h->header_len - (p - h->header)); + free_alloca(s, use_heap); + } + } + } +} +/* }}} */ + +PHP_FUNCTION(apache_response_headers) /* {{{ */ +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value); +} +/* }}} */ + +ZEND_BEGIN_ARG_INFO(arginfo_no_args, 0) +ZEND_END_ARG_INFO() + +static const zend_function_entry cgi_functions[] = { + PHP_FE(apache_child_terminate, arginfo_no_args) + PHP_FE(apache_request_headers, arginfo_no_args) + PHP_FE(apache_response_headers, arginfo_no_args) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_no_args) + PHP_FE_END +}; + +static zend_module_entry cgi_module_entry = { + STANDARD_MODULE_HEADER, + "cgi-fcgi", + cgi_functions, + PHP_MINIT(cgi), + PHP_MSHUTDOWN(cgi), + NULL, + NULL, + PHP_MINFO(cgi), + PHP_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +/* {{{ main + */ +int main(int argc, char *argv[]) +{ + int free_query_string = 0; + int exit_status = SUCCESS; + int cgi = 0, c, i; + size_t len; + zend_file_handle file_handle; + char *s; + + /* temporary locals */ + int behavior = PHP_MODE_STANDARD; + int no_headers = 0; + int orig_optind = php_optind; + char *orig_optarg = php_optarg; + char *script_file = NULL; + size_t ini_entries_len = 0; + /* end of temporary locals */ + + int max_requests = 500; + int requests = 0; + int fastcgi; + char *bindpath = NULL; + int fcgi_fd = 0; + fcgi_request *request = NULL; + int warmup_repeats = 0; + int repeats = 1; + int benchmark = 0; +#if HAVE_GETTIMEOFDAY + struct timeval start, end; +#else + time_t start, end; +#endif +#ifndef PHP_WIN32 + int status = 0; +#endif + char *query_string; + char *decoded_query_string; + int skip_getopt = 0; + +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif + +#ifdef ZTS + php_tsrm_startup(); +# ifdef PHP_WIN32 + ZEND_TSRMLS_CACHE_UPDATE(); +# endif +#endif + + zend_signal_startup(); + +#ifdef ZTS + ts_allocate_id(&php_cgi_globals_id, sizeof(php_cgi_globals_struct), (ts_allocate_ctor) php_cgi_globals_ctor, NULL); +#else + php_cgi_globals_ctor(&php_cgi_globals); +#endif + + sapi_startup(&cgi_sapi_module); + fastcgi = fcgi_is_fastcgi(); + cgi_sapi_module.php_ini_path_override = NULL; + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /* sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + if (!fastcgi) { + /* Make sure we detect we are a cgi - a bit redundancy here, + * but the default case is that we have to check only the first one. */ + if (getenv("SERVER_SOFTWARE") || + getenv("SERVER_NAME") || + getenv("GATEWAY_INTERFACE") || + getenv("REQUEST_METHOD") + ) { + cgi = 1; + } + } + + if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) { + /* we've got query string that has no = - apache CGI will pass it to command line */ + unsigned char *p; + decoded_query_string = strdup(query_string); + php_url_decode(decoded_query_string, strlen(decoded_query_string)); + for (p = (unsigned char *)decoded_query_string; *p && *p <= ' '; p++) { + /* skip all leading spaces */ + } + if(*p == '-') { + skip_getopt = 1; + } + free(decoded_query_string); + } + + while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + case 'c': + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + cgi_sapi_module.php_ini_path_override = strdup(php_optarg); + break; + case 'n': + cgi_sapi_module.php_ini_ignore = 1; + break; + case 'd': { + /* define ini entries on command line */ + size_t len = strlen(php_optarg); + char *val; + + if ((val = strchr(php_optarg, '='))) { + val++; + if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\"\"\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, (val - php_optarg)); + ini_entries_len += (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"", 1); + ini_entries_len++; + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, val, len - (val - php_optarg)); + ini_entries_len += len - (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0")); + ini_entries_len += sizeof("\n\0\"") - 2; + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0")); + ini_entries_len += len + sizeof("\n\0") - 2; + } + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("=1\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0")); + ini_entries_len += len + sizeof("=1\n\0") - 2; + } + break; + } + /* if we're started on command line, check to see if + * we are being started as an 'external' fastcgi + * server by accepting a bindpath parameter. */ + case 'b': + if (!fastcgi) { + bindpath = strdup(php_optarg); + } + break; + case 's': /* generate highlighted HTML from source */ + behavior = PHP_MODE_HIGHLIGHT; + break; + } + } + php_optind = orig_optind; + php_optarg = orig_optarg; + + if (fastcgi || bindpath) { + /* Override SAPI callbacks */ + cgi_sapi_module.ub_write = sapi_fcgi_ub_write; + cgi_sapi_module.flush = sapi_fcgi_flush; + cgi_sapi_module.read_post = sapi_fcgi_read_post; + cgi_sapi_module.getenv = sapi_fcgi_getenv; + cgi_sapi_module.read_cookies = sapi_fcgi_read_cookies; + } + +#ifdef ZTS + SG(request_info).path_translated = NULL; +#endif + + cgi_sapi_module.executable_location = argv[0]; + if (!cgi && !fastcgi && !bindpath) { + cgi_sapi_module.additional_functions = additional_functions; + } + + /* startup after we get the above ini override se we get things right */ + if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) { +#ifdef ZTS + tsrm_shutdown(); +#endif + free(bindpath); + return FAILURE; + } + + /* check force_cgi after startup, so we have proper output */ + if (cgi && CGIG(force_redirect)) { + /* Apache will generate REDIRECT_STATUS, + * Netscape and redirect.so will generate HTTP_REDIRECT_STATUS. + * redirect.so and installation instructions available from + * http://www.koehntopp.de/php. + * -- kk@netuse.de + */ + if (!getenv("REDIRECT_STATUS") && + !getenv ("HTTP_REDIRECT_STATUS") && + /* this is to allow a different env var to be configured + * in case some server does something different than above */ + (!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env))) + ) { + zend_try { + SG(sapi_headers).http_response_code = 400; + PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\ +<p>This PHP CGI binary was compiled with force-cgi-redirect enabled. This\n\ +means that a page will only be served up if the REDIRECT_STATUS CGI variable is\n\ +set, e.g. via an Apache Action directive.</p>\n\ +<p>For more information as to <i>why</i> this behaviour exists, see the <a href=\"http://php.net/security.cgi-bin\">\ +manual page for CGI security</a>.</p>\n\ +<p>For more information about changing this behaviour or re-enabling this webserver,\n\ +consult the installation file that came with this distribution, or visit \n\ +<a href=\"http://php.net/install.windows\">the manual page</a>.</p>\n"); + } zend_catch { + } zend_end_try(); +#if defined(ZTS) && !defined(PHP_DEBUG) + /* XXX we're crashing here in msvc6 debug builds at + * php_message_handler_for_zend:839 because + * SG(request_info).path_translated is an invalid pointer. + * It still happens even though I set it to null, so something + * weird is going on. + */ + tsrm_shutdown(); +#endif + free(bindpath); + return FAILURE; + } + } + +#ifndef HAVE_ATTRIBUTE_WEAK + fcgi_set_logger(fcgi_log); +#endif + + if (bindpath) { + int backlog = 128; + if (getenv("PHP_FCGI_BACKLOG")) { + backlog = atoi(getenv("PHP_FCGI_BACKLOG")); + } + fcgi_fd = fcgi_listen(bindpath, backlog); + if (fcgi_fd < 0) { + fprintf(stderr, "Couldn't create FastCGI listen socket on port %s\n", bindpath); +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + fastcgi = fcgi_is_fastcgi(); + } + + /* make php call us to get _ENV vars */ + php_php_import_environment_variables = php_import_environment_variables; + php_import_environment_variables = cgi_php_import_environment_variables; + + if (fastcgi) { + /* How many times to run PHP scripts before dying */ + if (getenv("PHP_FCGI_MAX_REQUESTS")) { + max_requests = atoi(getenv("PHP_FCGI_MAX_REQUESTS")); + if (max_requests < 0) { + fprintf(stderr, "PHP_FCGI_MAX_REQUESTS is not valid\n"); + return FAILURE; + } + } + + /* library is already initialized, now init our request */ + request = fcgi_init_request(fcgi_fd, NULL, NULL, NULL); + + /* Pre-fork or spawn, if required */ + if (getenv("PHP_FCGI_CHILDREN")) { + char * children_str = getenv("PHP_FCGI_CHILDREN"); + children = atoi(children_str); + if (children < 0) { + fprintf(stderr, "PHP_FCGI_CHILDREN is not valid\n"); + return FAILURE; + } + fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, children_str, strlen(children_str)); + /* This is the number of concurrent requests, equals FCGI_MAX_CONNS */ + fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, children_str, strlen(children_str)); + } else { +#ifdef PHP_WIN32 + /* If this env var is set, the process was invoked as a child. Let + it show the original PHP_FCGI_CHILDREN value, while don't care + otherwise. */ + char * children_str = getenv("PHP_FCGI_CHILDREN_FOR_KID"); + if (children_str) { + char putenv_buf[sizeof("PHP_FCGI_CHILDREN")+5]; + + snprintf(putenv_buf, sizeof(putenv_buf), "%s=%s", "PHP_FCGI_CHILDREN", children_str); + putenv(putenv_buf); + putenv("PHP_FCGI_CHILDREN_FOR_KID="); + + SetEnvironmentVariable("PHP_FCGI_CHILDREN", children_str); + SetEnvironmentVariable("PHP_FCGI_CHILDREN_FOR_KID", NULL); + } +#endif + fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, "1", sizeof("1")-1); + fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, "1", sizeof("1")-1); + } + +#ifndef PHP_WIN32 + if (children) { + int running = 0; + pid_t pid; + + /* Create a process group for ourself & children */ + setsid(); + pgroup = getpgrp(); +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Process group %d\n", pgroup); +#endif + + /* Set up handler to kill children upon exit */ + act.sa_flags = 0; + act.sa_handler = fastcgi_cleanup; + if (sigaction(SIGTERM, &act, &old_term) || + sigaction(SIGINT, &act, &old_int) || + sigaction(SIGQUIT, &act, &old_quit) + ) { + perror("Can't set signals"); + exit(1); + } + + if (fcgi_in_shutdown()) { + goto parent_out; + } + + while (parent) { + do { +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Forking, %d running\n", running); +#endif + pid = fork(); + switch (pid) { + case 0: + /* One of the children. + * Make sure we don't go round the + * fork loop any more + */ + parent = 0; + + /* don't catch our signals */ + sigaction(SIGTERM, &old_term, 0); + sigaction(SIGQUIT, &old_quit, 0); + sigaction(SIGINT, &old_int, 0); + zend_signal_init(); + break; + case -1: + perror("php (pre-forking)"); + exit(1); + break; + default: + /* Fine */ + running++; + break; + } + } while (parent && (running < children)); + + if (parent) { +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Wait for kids, pid %d\n", getpid()); +#endif + parent_waiting = 1; + while (1) { + if (wait(&status) >= 0) { + running--; + break; + } else if (exit_signal) { + break; + } + } + if (exit_signal) { +#if 0 + while (running > 0) { + while (wait(&status) < 0) { + } + running--; + } +#endif + goto parent_out; + } + } + } + } else { + parent = 0; + zend_signal_init(); + } + +#else + if (children) { + wchar_t *cmd_line_tmp, cmd_line[PHP_WIN32_IOUTIL_MAXPATHLEN]; + size_t cmd_line_len; + char kid_buf[16]; + int i; + + ZeroMemory(&kid_cgi_ps, sizeof(kid_cgi_ps)); + kids = children < WIN32_MAX_SPAWN_CHILDREN ? children : WIN32_MAX_SPAWN_CHILDREN; + + InitializeCriticalSection(&cleanup_lock); + SetConsoleCtrlHandler(fastcgi_cleanup, TRUE); + + /* kids will inherit the env, don't let them spawn */ + SetEnvironmentVariable("PHP_FCGI_CHILDREN", NULL); + /* instead, set a temporary env var, so then the child can read and + show the actual setting correctly. */ + snprintf(kid_buf, 16, "%d", children); + SetEnvironmentVariable("PHP_FCGI_CHILDREN_FOR_KID", kid_buf); + + /* The current command line is used as is. This should normally be no issue, + even if there were some I/O redirection. If some issues turn out, an + extra parsing might be needed here. */ + cmd_line_tmp = GetCommandLineW(); + if (!cmd_line_tmp) { + DWORD err = GetLastError(); + char *err_text = php_win32_error_to_msg(err); + + fprintf(stderr, "unable to get current command line: [0x%08lx]: %s\n", err, err_text); + php_win32_error_msg_free(err_text); + + goto parent_out; + } + + cmd_line_len = wcslen(cmd_line_tmp); + if (cmd_line_len > sizeof(cmd_line) - 1) { + fprintf(stderr, "command line is too long\n"); + goto parent_out; + } + memmove(cmd_line, cmd_line_tmp, (cmd_line_len + 1)*sizeof(wchar_t)); + + job = CreateJobObject(NULL, NULL); + if (!job) { + DWORD err = GetLastError(); + char *err_text = php_win32_error_to_msg(err); + + fprintf(stderr, "unable to create job object: [0x%08lx]: %s\n", err, err_text); + + php_win32_error_msg_free(err_text); + + goto parent_out; + } + + job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info))) { + DWORD err = GetLastError(); + char *err_text = php_win32_error_to_msg(err); + + fprintf(stderr, "unable to configure job object: [0x%08lx]: %s\n", err, err_text); + php_win32_error_msg_free(err_text); + } + + while (parent) { + EnterCriticalSection(&cleanup_lock); + if (cleaning_up) { + goto parent_loop_end; + } + LeaveCriticalSection(&cleanup_lock); + + i = kids; + while (0 < i--) { + DWORD status; + + if (NULL != kid_cgi_ps[i]) { + if(!GetExitCodeProcess(kid_cgi_ps[i], &status) || status != STILL_ACTIVE) { + CloseHandle(kid_cgi_ps[i]); + kid_cgi_ps[i] = NULL; + } + } + } + + i = kids; + while (0 < i--) { + PROCESS_INFORMATION pi; + STARTUPINFOW si; + + if (NULL != kid_cgi_ps[i]) { + continue; + } + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = INVALID_HANDLE_VALUE; + si.hStdInput = (HANDLE)_get_osfhandle(fcgi_fd); + si.hStdError = INVALID_HANDLE_VALUE; + + if (CreateProcessW(NULL, cmd_line, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + kid_cgi_ps[i] = pi.hProcess; + if (!AssignProcessToJobObject(job, pi.hProcess)) { + DWORD err = GetLastError(); + char *err_text = php_win32_error_to_msg(err); + + fprintf(stderr, "unable to assign child process to job object: [0x%08lx]: %s\n", err, err_text); + php_win32_error_msg_free(err_text); + } + CloseHandle(pi.hThread); + } else { + DWORD err = GetLastError(); + char *err_text = php_win32_error_to_msg(err); + + kid_cgi_ps[i] = NULL; + + fprintf(stderr, "unable to spawn: [0x%08lx]: %s\n", err, err_text); + php_win32_error_msg_free(err_text); + } + } + + WaitForMultipleObjects(kids, kid_cgi_ps, FALSE, INFINITE); + } + +parent_loop_end: + /* restore my env */ + SetEnvironmentVariable("PHP_FCGI_CHILDREN", kid_buf); + + DeleteCriticalSection(&cleanup_lock); + + goto parent_out; + } else { + parent = 0; + } +#endif /* WIN32 */ + } + + zend_first_try { + while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 1, 2)) != -1) { + switch (c) { + case 'T': + benchmark = 1; + { + char *comma = strchr(php_optarg, ','); + if (comma) { + warmup_repeats = atoi(php_optarg); + repeats = atoi(comma + 1); +#ifdef HAVE_VALGRIND + if (warmup_repeats > 0) { + CALLGRIND_STOP_INSTRUMENTATION; + } +#endif + } else { + repeats = atoi(php_optarg); + } + } +#ifdef HAVE_GETTIMEOFDAY + gettimeofday(&start, NULL); +#else + time(&start); +#endif + break; + case 'h': + case '?': + case PHP_GETOPT_INVALID_ARG: + if (request) { + fcgi_destroy_request(request); + } + fcgi_shutdown(); + no_headers = 1; + SG(headers_sent) = 1; + php_cgi_usage(argv[0]); + php_output_end_all(); + exit_status = 0; + if (c == PHP_GETOPT_INVALID_ARG) { + exit_status = 1; + } + goto out; + } + } + php_optind = orig_optind; + php_optarg = orig_optarg; + + /* start of FAST CGI loop */ + /* Initialise FastCGI request structure */ +#ifdef PHP_WIN32 + /* attempt to set security impersonation for fastcgi + * will only happen on NT based OS, others will ignore it. */ + if (fastcgi && CGIG(impersonate)) { + fcgi_impersonate(); + } +#endif + while (!fastcgi || fcgi_accept_request(request) >= 0) { + SG(server_context) = fastcgi ? (void *)request : (void *) 1; + init_request_info(request); + + if (!cgi && !fastcgi) { + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + + case 'a': /* interactive mode */ + printf("Interactive mode enabled\n\n"); + break; + + case 'C': /* don't chdir to the script directory */ + SG(options) |= SAPI_OPTION_NO_CHDIR; + break; + + case 'e': /* enable extended info output */ + CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; + break; + + case 'f': /* parse file */ + if (script_file) { + efree(script_file); + } + script_file = estrdup(php_optarg); + no_headers = 1; + break; + + case 'i': /* php info & quit */ + if (script_file) { + efree(script_file); + } + if (php_request_startup() == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(); + free(bindpath); + return FAILURE; + } + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + php_print_info(0xFFFFFFFF); + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = 0; + goto out; + + case 'l': /* syntax check mode */ + no_headers = 1; + behavior = PHP_MODE_LINT; + break; + + case 'm': /* list compiled in modules */ + if (script_file) { + efree(script_file); + } + SG(headers_sent) = 1; + php_printf("[PHP Modules]\n"); + print_modules(); + php_printf("\n[Zend Modules]\n"); + print_extensions(); + php_printf("\n"); + php_output_end_all(); + fcgi_shutdown(); + exit_status = 0; + goto out; + + case 'q': /* do not generate HTTP headers */ + no_headers = 1; + break; + + case 'v': /* show php version & quit */ + if (script_file) { + efree(script_file); + } + no_headers = 1; + if (php_request_startup() == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(); + free(bindpath); + return FAILURE; + } + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; +#if ZEND_DEBUG + php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#else + php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#endif + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = 0; + goto out; + + case 'w': + behavior = PHP_MODE_STRIP; + break; + + case 'z': /* load extension file */ + zend_load_extension(php_optarg); + break; + + default: + break; + } + } + + if (script_file) { + /* override path_translated if -f on command line */ + if (SG(request_info).path_translated) efree(SG(request_info).path_translated); + SG(request_info).path_translated = script_file; + /* before registering argv to module exchange the *new* argv[0] */ + /* we can achieve this without allocating more memory */ + SG(request_info).argc = argc - (php_optind - 1); + SG(request_info).argv = &argv[php_optind - 1]; + SG(request_info).argv[0] = script_file; + } else if (argc > php_optind) { + /* file is on command line, but not in -f opt */ + if (SG(request_info).path_translated) efree(SG(request_info).path_translated); + SG(request_info).path_translated = estrdup(argv[php_optind]); + /* arguments after the file are considered script args */ + SG(request_info).argc = argc - php_optind; + SG(request_info).argv = &argv[php_optind]; + } + + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + + /* all remaining arguments are part of the query string + * this section of code concatenates all remaining arguments + * into a single string, separating args with a & + * this allows command lines like: + * + * test.php v1=test v2=hello+world! + * test.php "v1=test&v2=hello world!" + * test.php v1=test "v2=hello world!" + */ + if (!SG(request_info).query_string && argc > php_optind) { + size_t slen = strlen(PG(arg_separator).input); + len = 0; + for (i = php_optind; i < argc; i++) { + if (i < (argc - 1)) { + len += strlen(argv[i]) + slen; + } else { + len += strlen(argv[i]); + } + } + + len += 2; + s = malloc(len); + *s = '\0'; /* we are pretending it came from the environment */ + for (i = php_optind; i < argc; i++) { + strlcat(s, argv[i], len); + if (i < (argc - 1)) { + strlcat(s, PG(arg_separator).input, len); + } + } + SG(request_info).query_string = s; + free_query_string = 1; + } + } /* end !cgi && !fastcgi */ + + /* + we never take stdin if we're (f)cgi, always + rely on the web server giving us the info + we need in the environment. + */ + if (SG(request_info).path_translated || cgi || fastcgi) { + zend_stream_init_filename(&file_handle, SG(request_info).path_translated); + } else { + zend_stream_init_fp(&file_handle, stdin, "Standard input code"); + } + + /* request startup only after we've done all we can to + * get path_translated */ + if (php_request_startup() == FAILURE) { + if (fastcgi) { + fcgi_finish_request(request, 1); + } + SG(server_context) = NULL; + php_module_shutdown(); + return FAILURE; + } + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + + /* + at this point path_translated will be set if: + 1. we are running from shell and got filename was there + 2. we are running as cgi or fastcgi + */ + if (cgi || fastcgi || SG(request_info).path_translated) { + if (php_fopen_primary_script(&file_handle) == FAILURE) { + zend_try { + if (errno == EACCES) { + SG(sapi_headers).http_response_code = 403; + PUTS("Access denied.\n"); + } else { + SG(sapi_headers).http_response_code = 404; + PUTS("No input file specified.\n"); + } + } zend_catch { + } zend_end_try(); + /* we want to serve more requests if this is fastcgi + * so cleanup and continue, request shutdown is + * handled later */ + if (fastcgi) { + goto fastcgi_request_done; + } + + if (SG(request_info).path_translated) { + efree(SG(request_info).path_translated); + SG(request_info).path_translated = NULL; + } + + if (free_query_string && SG(request_info).query_string) { + free(SG(request_info).query_string); + SG(request_info).query_string = NULL; + } + + php_request_shutdown((void *) 0); + SG(server_context) = NULL; + php_module_shutdown(); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + free(bindpath); + return FAILURE; + } + } + + if (CGIG(check_shebang_line)) { + CG(skip_shebang) = 1; + } + + switch (behavior) { + case PHP_MODE_STANDARD: + php_execute_script(&file_handle); + break; + case PHP_MODE_LINT: + PG(during_request_startup) = 0; + exit_status = php_lint_script(&file_handle); + if (exit_status == SUCCESS) { + zend_printf("No syntax errors detected in %s\n", file_handle.filename); + } else { + zend_printf("Errors parsing %s\n", file_handle.filename); + } + break; + case PHP_MODE_STRIP: + if (open_file_for_scanning(&file_handle) == SUCCESS) { + zend_strip(); + zend_file_handle_dtor(&file_handle); + php_output_teardown(); + } + return SUCCESS; + break; + case PHP_MODE_HIGHLIGHT: + { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + if (open_file_for_scanning(&file_handle) == SUCCESS) { + php_get_highlight_struct(&syntax_highlighter_ini); + zend_highlight(&syntax_highlighter_ini); + if (fastcgi) { + goto fastcgi_request_done; + } + zend_file_handle_dtor(&file_handle); + php_output_teardown(); + } + return SUCCESS; + } + break; + } + +fastcgi_request_done: + { + if (SG(request_info).path_translated) { + efree(SG(request_info).path_translated); + SG(request_info).path_translated = NULL; + } + + php_request_shutdown((void *) 0); + + if (exit_status == 0) { + exit_status = EG(exit_status); + } + + if (free_query_string && SG(request_info).query_string) { + free(SG(request_info).query_string); + SG(request_info).query_string = NULL; + } + } + + if (!fastcgi) { + if (benchmark) { + if (warmup_repeats) { + warmup_repeats--; + if (!warmup_repeats) { +#ifdef HAVE_GETTIMEOFDAY + gettimeofday(&start, NULL); +#else + time(&start); +#endif +#ifdef HAVE_VALGRIND + CALLGRIND_START_INSTRUMENTATION; +#endif + } + continue; + } else { + repeats--; + if (repeats > 0) { + script_file = NULL; + php_optind = orig_optind; + php_optarg = orig_optarg; + continue; + } + } + } + break; + } + + /* only fastcgi will get here */ + requests++; + if (max_requests && (requests == max_requests)) { + fcgi_finish_request(request, 1); + free(bindpath); + if (max_requests != 1) { + /* no need to return exit_status of the last request */ + exit_status = 0; + } + break; + } + /* end of fastcgi loop */ + } + + if (request) { + fcgi_destroy_request(request); + } + fcgi_shutdown(); + + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + if (cgi_sapi_module.ini_entries) { + free(cgi_sapi_module.ini_entries); + } + } zend_catch { + exit_status = 255; + } zend_end_try(); + +out: + if (benchmark) { + int sec; +#ifdef HAVE_GETTIMEOFDAY + int usec; + + gettimeofday(&end, NULL); + sec = (int)(end.tv_sec - start.tv_sec); + if (end.tv_usec >= start.tv_usec) { + usec = (int)(end.tv_usec - start.tv_usec); + } else { + sec -= 1; + usec = (int)(end.tv_usec + 1000000 - start.tv_usec); + } + fprintf(stderr, "\nElapsed time: %d.%06d sec\n", sec, usec); +#else + time(&end); + sec = (int)(end - start); + fprintf(stderr, "\nElapsed time: %d sec\n", sec); +#endif + } + +parent_out: + + SG(server_context) = NULL; + php_module_shutdown(); + sapi_shutdown(); + +#ifdef ZTS + tsrm_shutdown(); +#endif + +#if defined(PHP_WIN32) && ZEND_DEBUG && 0 + _CrtDumpMemoryLeaks(); +#endif + + return exit_status; +} +/* }}} */ From 7b2ef07783f1c3bd6b12fd96dd1a31ae7f2302c1 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:24 +0000 Subject: [PATCH 10/11] commit patch 28296821 --- main/rfc1867.c | 7 + main/rfc1867.c.orig | 1334 ++++++++++++++++++++++++++ tests/basic/GHSA-9pqp-7h25-4f32.inc | 3 + tests/basic/GHSA-9pqp-7h25-4f32.phpt | 100 ++ 4 files changed, 1444 insertions(+) create mode 100644 main/rfc1867.c.orig create mode 100644 tests/basic/GHSA-9pqp-7h25-4f32.inc create mode 100644 tests/basic/GHSA-9pqp-7h25-4f32.phpt diff --git a/main/rfc1867.c b/main/rfc1867.c index 8bdc409296ae3..f5555e0ebeb08 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -753,6 +753,13 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ boundary_len = boundary_end-boundary; } + /* Boundaries larger than FILLUNIT-strlen("\r\n--") characters lead to + * erroneous parsing */ + if (boundary_len > FILLUNIT-strlen("\r\n--")) { + sapi_module.sapi_error(E_WARNING, "Boundary too large in multipart/form-data POST data"); + return; + } + /* Initialize the buffer */ if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) { sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer"); diff --git a/main/rfc1867.c.orig b/main/rfc1867.c.orig new file mode 100644 index 0000000000000..8bdc409296ae3 --- /dev/null +++ b/main/rfc1867.c.orig @@ -0,0 +1,1334 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf <rasmus@php.net> | + | Jani Taskinen <jani@php.net> | + +----------------------------------------------------------------------+ + */ + +/* + * This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/). + * + */ + +#include <stdio.h> +#include "php.h" +#include "php_open_temporary_file.h" +#include "zend_globals.h" +#include "php_globals.h" +#include "php_variables.h" +#include "rfc1867.h" +#include "ext/standard/php_string.h" +#include "zend_smart_string.h" + +#if defined(PHP_WIN32) && !defined(HAVE_ATOLL) +# define atoll(s) _atoi64(s) +# define HAVE_ATOLL 1 +#endif + +#ifndef DEBUG_FILE_UPLOAD +# define DEBUG_FILE_UPLOAD 0 +#endif + +static int dummy_encoding_translation(void) +{ + return 0; +} + +static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop); +static char *php_ap_getword_conf(const zend_encoding *encoding, char *str); + +static php_rfc1867_encoding_translation_t php_rfc1867_encoding_translation = dummy_encoding_translation; +static php_rfc1867_get_detect_order_t php_rfc1867_get_detect_order = NULL; +static php_rfc1867_set_input_encoding_t php_rfc1867_set_input_encoding = NULL; +static php_rfc1867_getword_t php_rfc1867_getword = php_ap_getword; +static php_rfc1867_getword_conf_t php_rfc1867_getword_conf = php_ap_getword_conf; +static php_rfc1867_basename_t php_rfc1867_basename = NULL; + +PHPAPI int (*php_rfc1867_callback)(unsigned int event, void *event_data, void **extra) = NULL; + +static void safe_php_register_variable(char *var, char *strval, size_t val_len, zval *track_vars_array, zend_bool override_protection); + +/* The longest property name we use in an uploaded file array */ +#define MAX_SIZE_OF_INDEX sizeof("[tmp_name]") + +/* The longest anonymous name */ +#define MAX_SIZE_ANONNAME 33 + +/* Errors */ +#define UPLOAD_ERROR_OK 0 /* File upload successful */ +#define UPLOAD_ERROR_A 1 /* Uploaded file exceeded upload_max_filesize */ +#define UPLOAD_ERROR_B 2 /* Uploaded file exceeded MAX_FILE_SIZE */ +#define UPLOAD_ERROR_C 3 /* Partially uploaded */ +#define UPLOAD_ERROR_D 4 /* No file uploaded */ +#define UPLOAD_ERROR_E 6 /* Missing /tmp or similar directory */ +#define UPLOAD_ERROR_F 7 /* Failed to write file to disk */ +#define UPLOAD_ERROR_X 8 /* File upload stopped by extension */ + +void php_rfc1867_register_constants(void) /* {{{ */ +{ + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_OK", UPLOAD_ERROR_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_INI_SIZE", UPLOAD_ERROR_A, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_FORM_SIZE", UPLOAD_ERROR_B, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_PARTIAL", UPLOAD_ERROR_C, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_FILE", UPLOAD_ERROR_D, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_TMP_DIR", UPLOAD_ERROR_E, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_CANT_WRITE", UPLOAD_ERROR_F, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_EXTENSION", UPLOAD_ERROR_X, CONST_CS | CONST_PERSISTENT); +} +/* }}} */ + +static void normalize_protected_variable(char *varname) /* {{{ */ +{ + char *s = varname, *index = NULL, *indexend = NULL, *p; + + /* overjump leading space */ + while (*s == ' ') { + s++; + } + + /* and remove it */ + if (s != varname) { + memmove(varname, s, strlen(s)+1); + } + + for (p = varname; *p && *p != '['; p++) { + switch(*p) { + case ' ': + case '.': + *p = '_'; + break; + } + } + + /* find index */ + index = strchr(varname, '['); + if (index) { + index++; + s = index; + } else { + return; + } + + /* done? */ + while (index) { + while (*index == ' ' || *index == '\r' || *index == '\n' || *index=='\t') { + index++; + } + indexend = strchr(index, ']'); + indexend = indexend ? indexend + 1 : index + strlen(index); + + if (s != index) { + memmove(s, index, strlen(index)+1); + s += indexend-index; + } else { + s = indexend; + } + + if (*s == '[') { + s++; + index = s; + } else { + index = NULL; + } + } + *s = '\0'; +} +/* }}} */ + +static void add_protected_variable(char *varname) /* {{{ */ +{ + normalize_protected_variable(varname); + zend_hash_str_add_empty_element(&PG(rfc1867_protected_variables), varname, strlen(varname)); +} +/* }}} */ + +static zend_bool is_protected_variable(char *varname) /* {{{ */ +{ + normalize_protected_variable(varname); + return zend_hash_str_exists(&PG(rfc1867_protected_variables), varname, strlen(varname)); +} +/* }}} */ + +static void safe_php_register_variable(char *var, char *strval, size_t val_len, zval *track_vars_array, zend_bool override_protection) /* {{{ */ +{ + if (override_protection || !is_protected_variable(var)) { + php_register_variable_safe(var, strval, val_len, track_vars_array); + } +} +/* }}} */ + +static void safe_php_register_variable_ex(char *var, zval *val, zval *track_vars_array, zend_bool override_protection) /* {{{ */ +{ + if (override_protection || !is_protected_variable(var)) { + php_register_variable_ex(var, val, track_vars_array); + } +} +/* }}} */ + +static void register_http_post_files_variable(char *strvar, char *val, zval *http_post_files, zend_bool override_protection) /* {{{ */ +{ + safe_php_register_variable(strvar, val, strlen(val), http_post_files, override_protection); +} +/* }}} */ + +static void register_http_post_files_variable_ex(char *var, zval *val, zval *http_post_files, zend_bool override_protection) /* {{{ */ +{ + safe_php_register_variable_ex(var, val, http_post_files, override_protection); +} +/* }}} */ + +static void free_filename(zval *el) { + zend_string *filename = Z_STR_P(el); + zend_string_release_ex(filename, 0); +} + +PHPAPI void destroy_uploaded_files_hash(void) /* {{{ */ +{ + zval *el; + + ZEND_HASH_FOREACH_VAL(SG(rfc1867_uploaded_files), el) { + zend_string *filename = Z_STR_P(el); + VCWD_UNLINK(ZSTR_VAL(filename)); + } ZEND_HASH_FOREACH_END(); + zend_hash_destroy(SG(rfc1867_uploaded_files)); + FREE_HASHTABLE(SG(rfc1867_uploaded_files)); +} +/* }}} */ + +/* {{{ Following code is based on apache_multipart_buffer.c from libapreq-0.33 package. */ + +#define FILLUNIT (1024 * 5) + +typedef struct { + + /* read buffer */ + char *buffer; + char *buf_begin; + int bufsize; + int bytes_in_buffer; + + /* boundary info */ + char *boundary; + char *boundary_next; + int boundary_next_len; + + const zend_encoding *input_encoding; + const zend_encoding **detect_order; + size_t detect_order_size; +} multipart_buffer; + +typedef struct { + char *key; + char *value; +} mime_header_entry; + +/* + * Fill up the buffer with client data. + * Returns number of bytes added to buffer. + */ +static int fill_buffer(multipart_buffer *self) +{ + int bytes_to_read, total_read = 0, actual_read = 0; + + /* shift the existing data if necessary */ + if (self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) { + memmove(self->buffer, self->buf_begin, self->bytes_in_buffer); + } + + self->buf_begin = self->buffer; + + /* calculate the free space in the buffer */ + bytes_to_read = self->bufsize - self->bytes_in_buffer; + + /* read the required number of bytes */ + while (bytes_to_read > 0) { + + char *buf = self->buffer + self->bytes_in_buffer; + + actual_read = (int)sapi_module.read_post(buf, bytes_to_read); + + /* update the buffer length */ + if (actual_read > 0) { + self->bytes_in_buffer += actual_read; + SG(read_post_bytes) += actual_read; + total_read += actual_read; + bytes_to_read -= actual_read; + } else { + break; + } + } + + return total_read; +} + +/* eof if we are out of bytes, or if we hit the final boundary */ +static int multipart_buffer_eof(multipart_buffer *self) +{ + return self->bytes_in_buffer == 0 && fill_buffer(self) < 1; +} + +/* create new multipart_buffer structure */ +static multipart_buffer *multipart_buffer_new(char *boundary, int boundary_len) +{ + multipart_buffer *self = (multipart_buffer *) ecalloc(1, sizeof(multipart_buffer)); + + int minsize = boundary_len + 6; + if (minsize < FILLUNIT) minsize = FILLUNIT; + + self->buffer = (char *) ecalloc(1, minsize + 1); + self->bufsize = minsize; + + spprintf(&self->boundary, 0, "--%s", boundary); + + self->boundary_next_len = (int)spprintf(&self->boundary_next, 0, "\n--%s", boundary); + + self->buf_begin = self->buffer; + self->bytes_in_buffer = 0; + + if (php_rfc1867_encoding_translation()) { + php_rfc1867_get_detect_order(&self->detect_order, &self->detect_order_size); + } else { + self->detect_order = NULL; + self->detect_order_size = 0; + } + + self->input_encoding = NULL; + + return self; +} + +/* + * Gets the next CRLF terminated line from the input buffer. + * If it doesn't find a CRLF, and the buffer isn't completely full, returns + * NULL; otherwise, returns the beginning of the null-terminated line, + * minus the CRLF. + * + * Note that we really just look for LF terminated lines. This works + * around a bug in internet explorer for the macintosh which sends mime + * boundaries that are only LF terminated when you use an image submit + * button in a multipart/form-data form. + */ +static char *next_line(multipart_buffer *self) +{ + /* look for LF in the data */ + char* line = self->buf_begin; + char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer); + + if (ptr) { /* LF found */ + + /* terminate the string, remove CRLF */ + if ((ptr - line) > 0 && *(ptr-1) == '\r') { + *(ptr-1) = 0; + } else { + *ptr = 0; + } + + /* bump the pointer */ + self->buf_begin = ptr + 1; + self->bytes_in_buffer -= (self->buf_begin - line); + + } else { /* no LF found */ + + /* buffer isn't completely full, fail */ + if (self->bytes_in_buffer < self->bufsize) { + return NULL; + } + /* return entire buffer as a partial line */ + line[self->bufsize] = 0; + self->buf_begin = ptr; + self->bytes_in_buffer = 0; + } + + return line; +} + +/* Returns the next CRLF terminated line from the client */ +static char *get_line(multipart_buffer *self) +{ + char* ptr = next_line(self); + + if (!ptr) { + fill_buffer(self); + ptr = next_line(self); + } + + return ptr; +} + +/* Free header entry */ +static void php_free_hdr_entry(mime_header_entry *h) +{ + if (h->key) { + efree(h->key); + } + if (h->value) { + efree(h->value); + } +} + +/* finds a boundary */ +static int find_boundary(multipart_buffer *self, char *boundary) +{ + char *line; + + /* loop through lines */ + while( (line = get_line(self)) ) + { + /* finished if we found the boundary */ + if (!strcmp(line, boundary)) { + return 1; + } + } + + /* didn't find the boundary */ + return 0; +} + +/* parse headers */ +static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) +{ + char *line; + mime_header_entry entry = {0}; + smart_string buf_value = {0}; + char *key = NULL; + + /* didn't find boundary, abort */ + if (!find_boundary(self, self->boundary)) { + return 0; + } + + /* get lines of text, or CRLF_CRLF */ + + while ((line = get_line(self)) && line[0] != '\0') { + /* add header to table */ + char *value = NULL; + + if (php_rfc1867_encoding_translation()) { + self->input_encoding = zend_multibyte_encoding_detector((const unsigned char *) line, strlen(line), self->detect_order, self->detect_order_size); + } + + /* space in the beginning means same header */ + if (!isspace(line[0])) { + value = strchr(line, ':'); + } + + if (value) { + if (buf_value.c && key) { + /* new entry, add the old one to the list */ + smart_string_0(&buf_value); + entry.key = key; + entry.value = buf_value.c; + zend_llist_add_element(header, &entry); + buf_value.c = NULL; + key = NULL; + } + + *value = '\0'; + do { value++; } while (isspace(*value)); + + key = estrdup(line); + smart_string_appends(&buf_value, value); + } else if (buf_value.c) { /* If no ':' on the line, add to previous line */ + smart_string_appends(&buf_value, line); + } else { + continue; + } + } + + if (buf_value.c && key) { + /* add the last one to the list */ + smart_string_0(&buf_value); + entry.key = key; + entry.value = buf_value.c; + zend_llist_add_element(header, &entry); + } + + return 1; +} + +static char *php_mime_get_hdr_value(zend_llist header, char *key) +{ + mime_header_entry *entry; + + if (key == NULL) { + return NULL; + } + + entry = zend_llist_get_first(&header); + while (entry) { + if (!strcasecmp(entry->key, key)) { + return entry->value; + } + entry = zend_llist_get_next(&header); + } + + return NULL; +} + +static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop) +{ + char *pos = *line, quote; + char *res; + + while (*pos && *pos != stop) { + if ((quote = *pos) == '"' || quote == '\'') { + ++pos; + while (*pos && *pos != quote) { + if (*pos == '\\' && pos[1] && pos[1] == quote) { + pos += 2; + } else { + ++pos; + } + } + if (*pos) { + ++pos; + } + } else ++pos; + } + if (*pos == '\0') { + res = estrdup(*line); + *line += strlen(*line); + return res; + } + + res = estrndup(*line, pos - *line); + + while (*pos == stop) { + ++pos; + } + + *line = pos; + return res; +} + +static char *substring_conf(char *start, int len, char quote) +{ + char *result = emalloc(len + 1); + char *resp = result; + int i; + + for (i = 0; i < len && start[i] != quote; ++i) { + if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) { + *resp++ = start[++i]; + } else { + *resp++ = start[i]; + } + } + + *resp = '\0'; + return result; +} + +static char *php_ap_getword_conf(const zend_encoding *encoding, char *str) +{ + while (*str && isspace(*str)) { + ++str; + } + + if (!*str) { + return estrdup(""); + } + + if (*str == '"' || *str == '\'') { + char quote = *str; + + str++; + return substring_conf(str, (int)strlen(str), quote); + } else { + char *strend = str; + + while (*strend && !isspace(*strend)) { + ++strend; + } + return substring_conf(str, strend - str, 0); + } +} + +static char *php_ap_basename(const zend_encoding *encoding, char *path) +{ + char *s = strrchr(path, '\\'); + char *s2 = strrchr(path, '/'); + + if (s && s2) { + if (s > s2) { + ++s; + } else { + s = ++s2; + } + return s; + } else if (s) { + return ++s; + } else if (s2) { + return ++s2; + } + return path; +} + +/* + * Search for a string in a fixed-length byte string. + * If partial is true, partial matches are allowed at the end of the buffer. + * Returns NULL if not found, or a pointer to the start of the first match. + */ +static void *php_ap_memstr(char *haystack, int haystacklen, char *needle, int needlen, int partial) +{ + int len = haystacklen; + char *ptr = haystack; + + /* iterate through first character matches */ + while( (ptr = memchr(ptr, needle[0], len)) ) { + + /* calculate length after match */ + len = haystacklen - (ptr - (char *)haystack); + + /* done if matches up to capacity of buffer */ + if (memcmp(needle, ptr, needlen < len ? needlen : len) == 0 && (partial || len >= needlen)) { + break; + } + + /* next character */ + ptr++; len--; + } + + return ptr; +} + +/* read until a boundary condition */ +static size_t multipart_buffer_read(multipart_buffer *self, char *buf, size_t bytes, int *end) +{ + size_t len, max; + char *bound; + + /* fill buffer if needed */ + if (bytes > (size_t)self->bytes_in_buffer) { + fill_buffer(self); + } + + /* look for a potential boundary match, only read data up to that point */ + if ((bound = php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 1))) { + max = bound - self->buf_begin; + if (end && php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 0)) { + *end = 1; + } + } else { + max = self->bytes_in_buffer; + } + + /* maximum number of bytes we are reading */ + len = max < bytes-1 ? max : bytes-1; + + /* if we read any data... */ + if (len > 0) { + + /* copy the data */ + memcpy(buf, self->buf_begin, len); + buf[len] = 0; + + if (bound && len > 0 && buf[len-1] == '\r') { + buf[--len] = 0; + } + + /* update the buffer */ + self->bytes_in_buffer -= (int)len; + self->buf_begin += len; + } + + return len; +} + +/* + XXX: this is horrible memory-usage-wise, but we only expect + to do this on small pieces of form data. +*/ +static char *multipart_buffer_read_body(multipart_buffer *self, size_t *len) +{ + char buf[FILLUNIT], *out=NULL; + size_t total_bytes=0, read_bytes=0; + + while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf), NULL))) { + out = erealloc(out, total_bytes + read_bytes + 1); + memcpy(out + total_bytes, buf, read_bytes); + total_bytes += read_bytes; + } + + if (out) { + out[total_bytes] = '\0'; + } + *len = total_bytes; + + return out; +} +/* }}} */ + +/* + * The combined READER/HANDLER + * + */ + +SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ +{ + char *boundary, *s = NULL, *boundary_end = NULL, *start_arr = NULL, *array_index = NULL; + char *lbuf = NULL, *abuf = NULL; + zend_string *temp_filename = NULL; + int boundary_len = 0, cancel_upload = 0, is_arr_upload = 0; + size_t array_len = 0; + int64_t total_bytes = 0, max_file_size = 0; + int skip_upload = 0, anonindex = 0, is_anonymous; + HashTable *uploaded_files = NULL; + multipart_buffer *mbuff; + zval *array_ptr = (zval *) arg; + int fd = -1; + zend_llist header; + void *event_extra_data = NULL; + unsigned int llen = 0; + int upload_cnt = INI_INT("max_file_uploads"); + const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding(); + php_rfc1867_getword_t getword; + php_rfc1867_getword_conf_t getword_conf; + php_rfc1867_basename_t _basename; + zend_long count = 0; + + if (php_rfc1867_encoding_translation() && internal_encoding) { + getword = php_rfc1867_getword; + getword_conf = php_rfc1867_getword_conf; + _basename = php_rfc1867_basename; + } else { + getword = php_ap_getword; + getword_conf = php_ap_getword_conf; + _basename = php_ap_basename; + } + + if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) { + sapi_module.sapi_error(E_WARNING, "POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes", SG(request_info).content_length, SG(post_max_size)); + return; + } + + /* Get the boundary */ + boundary = strstr(content_type_dup, "boundary"); + if (!boundary) { + int content_type_len = (int)strlen(content_type_dup); + char *content_type_lcase = estrndup(content_type_dup, content_type_len); + + php_strtolower(content_type_lcase, content_type_len); + boundary = strstr(content_type_lcase, "boundary"); + if (boundary) { + boundary = content_type_dup + (boundary - content_type_lcase); + } + efree(content_type_lcase); + } + + if (!boundary || !(boundary = strchr(boundary, '='))) { + sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data"); + return; + } + + boundary++; + boundary_len = (int)strlen(boundary); + + if (boundary[0] == '"') { + boundary++; + boundary_end = strchr(boundary, '"'); + if (!boundary_end) { + sapi_module.sapi_error(E_WARNING, "Invalid boundary in multipart/form-data POST data"); + return; + } + } else { + /* search for the end of the boundary */ + boundary_end = strpbrk(boundary, ",;"); + } + if (boundary_end) { + boundary_end[0] = '\0'; + boundary_len = boundary_end-boundary; + } + + /* Initialize the buffer */ + if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) { + sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer"); + return; + } + + /* Initialize $_FILES[] */ + zend_hash_init(&PG(rfc1867_protected_variables), 8, NULL, NULL, 0); + + ALLOC_HASHTABLE(uploaded_files); + zend_hash_init(uploaded_files, 8, NULL, free_filename, 0); + SG(rfc1867_uploaded_files) = uploaded_files; + + if (Z_TYPE(PG(http_globals)[TRACK_VARS_FILES]) != IS_ARRAY) { + /* php_auto_globals_create_files() might have already done that */ + array_init(&PG(http_globals)[TRACK_VARS_FILES]); + } + + zend_llist_init(&header, sizeof(mime_header_entry), (llist_dtor_func_t) php_free_hdr_entry, 0); + + if (php_rfc1867_callback != NULL) { + multipart_event_start event_start; + + event_start.content_length = SG(request_info).content_length; + if (php_rfc1867_callback(MULTIPART_EVENT_START, &event_start, &event_extra_data) == FAILURE) { + goto fileupload_done; + } + } + + while (!multipart_buffer_eof(mbuff)) + { + char buff[FILLUNIT]; + char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL; + size_t blen = 0, wlen = 0; + zend_off_t offset; + + zend_llist_clean(&header); + + if (!multipart_buffer_headers(mbuff, &header)) { + goto fileupload_done; + } + + if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) { + char *pair = NULL; + int end = 0; + + while (isspace(*cd)) { + ++cd; + } + + while (*cd && (pair = getword(mbuff->input_encoding, &cd, ';'))) + { + char *key = NULL, *word = pair; + + while (isspace(*cd)) { + ++cd; + } + + if (strchr(pair, '=')) { + key = getword(mbuff->input_encoding, &pair, '='); + + if (!strcasecmp(key, "name")) { + if (param) { + efree(param); + } + param = getword_conf(mbuff->input_encoding, pair); + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_param; + size_t new_param_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_param, &new_param_len, (unsigned char *)param, strlen(param), internal_encoding, mbuff->input_encoding)) { + efree(param); + param = (char *)new_param; + } + } + } else if (!strcasecmp(key, "filename")) { + if (filename) { + efree(filename); + } + filename = getword_conf(mbuff->input_encoding, pair); + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_filename; + size_t new_filename_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_filename, &new_filename_len, (unsigned char *)filename, strlen(filename), internal_encoding, mbuff->input_encoding)) { + efree(filename); + filename = (char *)new_filename; + } + } + } + } + if (key) { + efree(key); + } + efree(word); + } + + /* Normal form variable, safe to read all data into memory */ + if (!filename && param) { + size_t value_len; + char *value = multipart_buffer_read_body(mbuff, &value_len); + size_t new_val_len; /* Dummy variable */ + + if (!value) { + value = estrdup(""); + value_len = 0; + } + + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_value; + size_t new_value_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_value, &new_value_len, (unsigned char *)value, value_len, internal_encoding, mbuff->input_encoding)) { + efree(value); + value = (char *)new_value; + value_len = new_value_len; + } + } + + if (++count <= PG(max_input_vars) && sapi_module.input_filter(PARSE_POST, param, &value, value_len, &new_val_len)) { + if (php_rfc1867_callback != NULL) { + multipart_event_formdata event_formdata; + size_t newlength = new_val_len; + + event_formdata.post_bytes_processed = SG(read_post_bytes); + event_formdata.name = param; + event_formdata.value = &value; + event_formdata.length = new_val_len; + event_formdata.newlength = &newlength; + if (php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data) == FAILURE) { + efree(param); + efree(value); + continue; + } + new_val_len = newlength; + } + safe_php_register_variable(param, value, new_val_len, array_ptr, 0); + } else { + if (count == PG(max_input_vars) + 1) { + php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); + } + + if (php_rfc1867_callback != NULL) { + multipart_event_formdata event_formdata; + + event_formdata.post_bytes_processed = SG(read_post_bytes); + event_formdata.name = param; + event_formdata.value = &value; + event_formdata.length = value_len; + event_formdata.newlength = NULL; + php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data); + } + } + + if (!strcasecmp(param, "MAX_FILE_SIZE")) { +#ifdef HAVE_ATOLL + max_file_size = atoll(value); +#else + max_file_size = strtoll(value, NULL, 10); +#endif + } + + efree(param); + efree(value); + continue; + } + + /* If file_uploads=off, skip the file part */ + if (!PG(file_uploads)) { + skip_upload = 1; + } else if (upload_cnt <= 0) { + skip_upload = 1; + sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded"); + } + + /* Return with an error if the posted data is garbled */ + if (!param && !filename) { + sapi_module.sapi_error(E_WARNING, "File Upload Mime headers garbled"); + goto fileupload_done; + } + + if (!param) { + is_anonymous = 1; + param = emalloc(MAX_SIZE_ANONNAME); + snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++); + } else { + is_anonymous = 0; + } + + /* New Rule: never repair potential malicious user input */ + if (!skip_upload) { + long c = 0; + tmp = param; + + while (*tmp) { + if (*tmp == '[') { + c++; + } else if (*tmp == ']') { + c--; + if (tmp[1] && tmp[1] != '[') { + skip_upload = 1; + break; + } + } + if (c < 0) { + skip_upload = 1; + break; + } + tmp++; + } + /* Brackets should always be closed */ + if(c != 0) { + skip_upload = 1; + } + } + + total_bytes = cancel_upload = 0; + temp_filename = NULL; + fd = -1; + + if (!skip_upload && php_rfc1867_callback != NULL) { + multipart_event_file_start event_file_start; + + event_file_start.post_bytes_processed = SG(read_post_bytes); + event_file_start.name = param; + event_file_start.filename = &filename; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_START, &event_file_start, &event_extra_data) == FAILURE) { + temp_filename = NULL; + efree(param); + efree(filename); + continue; + } + } + + if (skip_upload) { + efree(param); + efree(filename); + continue; + } + + if (filename[0] == '\0') { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "No file uploaded"); +#endif + cancel_upload = UPLOAD_ERROR_D; + } + + offset = 0; + end = 0; + + if (!cancel_upload) { + /* only bother to open temp file if we have data */ + blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end); +#if DEBUG_FILE_UPLOAD + if (blen > 0) { +#else + /* in non-debug mode we have no problem with 0-length files */ + { +#endif + fd = php_open_temporary_fd_ex(PG(upload_tmp_dir), "php", &temp_filename, 1); + upload_cnt--; + if (fd == -1) { + sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file"); + cancel_upload = UPLOAD_ERROR_E; + } + } + } + + while (!cancel_upload && (blen > 0)) + { + if (php_rfc1867_callback != NULL) { + multipart_event_file_data event_file_data; + + event_file_data.post_bytes_processed = SG(read_post_bytes); + event_file_data.offset = offset; + event_file_data.data = buff; + event_file_data.length = blen; + event_file_data.newlength = &blen; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_DATA, &event_file_data, &event_extra_data) == FAILURE) { + cancel_upload = UPLOAD_ERROR_X; + continue; + } + } + + if (PG(upload_max_filesize) > 0 && (zend_long)(total_bytes+blen) > PG(upload_max_filesize)) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of " ZEND_LONG_FMT " bytes exceeded - file [%s=%s] not saved", PG(upload_max_filesize), param, filename); +#endif + cancel_upload = UPLOAD_ERROR_A; + } else if (max_file_size && ((zend_long)(total_bytes+blen) > max_file_size)) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "MAX_FILE_SIZE of %" PRId64 " bytes exceeded - file [%s=%s] not saved", max_file_size, param, filename); +#endif + cancel_upload = UPLOAD_ERROR_B; + } else if (blen > 0) { +#ifdef PHP_WIN32 + wlen = write(fd, buff, (unsigned int)blen); +#else + wlen = write(fd, buff, blen); +#endif + + if (wlen == (size_t)-1) { + /* write failed */ +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno)); +#endif + cancel_upload = UPLOAD_ERROR_F; + } else if (wlen < blen) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "Only %zd bytes were written, expected to write %zd", wlen, blen); +#endif + cancel_upload = UPLOAD_ERROR_F; + } else { + total_bytes += wlen; + } + offset += wlen; + } + + /* read data for next iteration */ + blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end); + } + + if (fd != -1) { /* may not be initialized if file could not be created */ + close(fd); + } + + if (!cancel_upload && !end) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "Missing mime boundary at the end of the data for file %s", filename[0] != '\0' ? filename : ""); +#endif + cancel_upload = UPLOAD_ERROR_C; + } +#if DEBUG_FILE_UPLOAD + if (filename[0] != '\0' && total_bytes == 0 && !cancel_upload) { + sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename); + cancel_upload = 5; + } +#endif + if (php_rfc1867_callback != NULL) { + multipart_event_file_end event_file_end; + + event_file_end.post_bytes_processed = SG(read_post_bytes); + event_file_end.temp_filename = temp_filename ? ZSTR_VAL(temp_filename) : NULL; + event_file_end.cancel_upload = cancel_upload; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_END, &event_file_end, &event_extra_data) == FAILURE) { + cancel_upload = UPLOAD_ERROR_X; + } + } + + if (cancel_upload) { + if (temp_filename) { + if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */ + unlink(ZSTR_VAL(temp_filename)); + } + zend_string_release_ex(temp_filename, 0); + } + temp_filename = NULL; + } else { + zend_hash_add_ptr(SG(rfc1867_uploaded_files), temp_filename, temp_filename); + } + + /* is_arr_upload is true when name of file upload field + * ends in [.*] + * start_arr is set to point to 1st [ */ + is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']'); + + if (is_arr_upload) { + array_len = strlen(start_arr); + if (array_index) { + efree(array_index); + } + array_index = estrndup(start_arr + 1, array_len - 2); + } + + /* Add $foo_name */ + if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) { + llen = (int)strlen(param); + lbuf = (char *) safe_erealloc(lbuf, llen, 1, MAX_SIZE_OF_INDEX + 1); + llen += MAX_SIZE_OF_INDEX + 1; + } + + if (is_arr_upload) { + if (abuf) efree(abuf); + abuf = estrndup(param, strlen(param)-array_len); + snprintf(lbuf, llen, "%s_name[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_name", param); + } + + /* The \ check should technically be needed for win32 systems only where + * it is a valid path separator. However, IE in all it's wisdom always sends + * the full path of the file on the user's filesystem, which means that unless + * the user does basename() they get a bogus file name. Until IE's user base drops + * to nill or problem is fixed this code must remain enabled for all systems. */ + s = _basename(internal_encoding, filename); + if (!s) { + s = filename; + } + + if (!is_anonymous) { + safe_php_register_variable(lbuf, s, strlen(s), NULL, 0); + } + + /* Add $foo[name] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[name][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[name]", param); + } + register_http_post_files_variable(lbuf, s, &PG(http_globals)[TRACK_VARS_FILES], 0); + efree(filename); + s = NULL; + + /* Possible Content-Type: */ + if (cancel_upload || !(cd = php_mime_get_hdr_value(header, "Content-Type"))) { + cd = ""; + } else { + /* fix for Opera 6.01 */ + s = strchr(cd, ';'); + if (s != NULL) { + *s = '\0'; + } + } + + /* Add $foo_type */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s_type[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_type", param); + } + if (!is_anonymous) { + safe_php_register_variable(lbuf, cd, strlen(cd), NULL, 0); + } + + /* Add $foo[type] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[type][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[type]", param); + } + register_http_post_files_variable(lbuf, cd, &PG(http_globals)[TRACK_VARS_FILES], 0); + + /* Restore Content-Type Header */ + if (s != NULL) { + *s = ';'; + } + s = ""; + + { + /* store temp_filename as-is (in case upload_tmp_dir + * contains escapeable characters. escape only the variable name.) */ + zval zfilename; + + /* Initialize variables */ + add_protected_variable(param); + + /* if param is of form xxx[.*] this will cut it to xxx */ + if (!is_anonymous) { + if (temp_filename) { + ZVAL_STR_COPY(&zfilename, temp_filename); + } else { + ZVAL_EMPTY_STRING(&zfilename); + } + safe_php_register_variable_ex(param, &zfilename, NULL, 1); + } + + /* Add $foo[tmp_name] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[tmp_name][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[tmp_name]", param); + } + add_protected_variable(lbuf); + if (temp_filename) { + ZVAL_STR_COPY(&zfilename, temp_filename); + } else { + ZVAL_EMPTY_STRING(&zfilename); + } + register_http_post_files_variable_ex(lbuf, &zfilename, &PG(http_globals)[TRACK_VARS_FILES], 1); + } + + { + zval file_size, error_type; + int size_overflow = 0; + char file_size_buf[65]; + + ZVAL_LONG(&error_type, cancel_upload); + + /* Add $foo[error] */ + if (cancel_upload) { + ZVAL_LONG(&file_size, 0); + } else { + if (total_bytes > ZEND_LONG_MAX) { +#ifdef PHP_WIN32 + if (_i64toa_s(total_bytes, file_size_buf, 65, 10)) { + file_size_buf[0] = '0'; + file_size_buf[1] = '\0'; + } +#else + { + int __len = snprintf(file_size_buf, 65, "%" PRId64, total_bytes); + file_size_buf[__len] = '\0'; + } +#endif + size_overflow = 1; + + } else { + ZVAL_LONG(&file_size, total_bytes); + } + } + + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[error][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[error]", param); + } + register_http_post_files_variable_ex(lbuf, &error_type, &PG(http_globals)[TRACK_VARS_FILES], 0); + + /* Add $foo_size */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s_size[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_size", param); + } + if (!is_anonymous) { + if (size_overflow) { + ZVAL_STRING(&file_size, file_size_buf); + } + safe_php_register_variable_ex(lbuf, &file_size, NULL, size_overflow); + } + + /* Add $foo[size] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[size][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[size]", param); + } + if (size_overflow) { + ZVAL_STRING(&file_size, file_size_buf); + } + register_http_post_files_variable_ex(lbuf, &file_size, &PG(http_globals)[TRACK_VARS_FILES], size_overflow); + } + efree(param); + } + } + +fileupload_done: + if (php_rfc1867_callback != NULL) { + multipart_event_end event_end; + + event_end.post_bytes_processed = SG(read_post_bytes); + php_rfc1867_callback(MULTIPART_EVENT_END, &event_end, &event_extra_data); + } + + if (lbuf) efree(lbuf); + if (abuf) efree(abuf); + if (array_index) efree(array_index); + zend_hash_destroy(&PG(rfc1867_protected_variables)); + zend_llist_destroy(&header); + if (mbuff->boundary_next) efree(mbuff->boundary_next); + if (mbuff->boundary) efree(mbuff->boundary); + if (mbuff->buffer) efree(mbuff->buffer); + if (mbuff) efree(mbuff); +} +/* }}} */ + +SAPI_API void php_rfc1867_set_multibyte_callbacks( + php_rfc1867_encoding_translation_t encoding_translation, + php_rfc1867_get_detect_order_t get_detect_order, + php_rfc1867_set_input_encoding_t set_input_encoding, + php_rfc1867_getword_t getword, + php_rfc1867_getword_conf_t getword_conf, + php_rfc1867_basename_t basename) /* {{{ */ +{ + php_rfc1867_encoding_translation = encoding_translation; + php_rfc1867_get_detect_order = get_detect_order; + php_rfc1867_set_input_encoding = set_input_encoding; + php_rfc1867_getword = getword; + php_rfc1867_getword_conf = getword_conf; + php_rfc1867_basename = basename; +} +/* }}} */ diff --git a/tests/basic/GHSA-9pqp-7h25-4f32.inc b/tests/basic/GHSA-9pqp-7h25-4f32.inc new file mode 100644 index 0000000000000..adf72a361a2cb --- /dev/null +++ b/tests/basic/GHSA-9pqp-7h25-4f32.inc @@ -0,0 +1,3 @@ +<?php +print "Hello world\n"; +var_dump($_POST); diff --git a/tests/basic/GHSA-9pqp-7h25-4f32.phpt b/tests/basic/GHSA-9pqp-7h25-4f32.phpt new file mode 100644 index 0000000000000..af81916370500 --- /dev/null +++ b/tests/basic/GHSA-9pqp-7h25-4f32.phpt @@ -0,0 +1,100 @@ +--TEST-- +GHSA-9pqp-7h25-4f32 +--SKIPIF-- +<?php +if (!getenv('TEST_PHP_CGI_EXECUTABLE')) { + die("skip php-cgi not available"); +} +?> +--FILE-- +<?php + +const FILLUNIT = 5 * 1024; + +function test($boundaryLen) { + printf("Boundary len: %d\n", $boundaryLen); + + $cmd = [ + getenv('TEST_PHP_CGI_EXECUTABLE'), + '-C', + '-n', + __DIR__ . '/GHSA-9pqp-7h25-4f32.inc', + ]; + + $boundary = str_repeat('A', $boundaryLen); + $body = "" + . "--$boundary\r\n" + . "Content-Disposition: form-data; name=\"koko\"\r\n" + . "\r\n" + . "BBB\r\n--" . substr($boundary, 0, -1) . "CCC\r\n" + . "--$boundary--\r\n" + ; + + $env = array_merge($_ENV, [ + 'REDIRECT_STATUS' => '1', + 'CONTENT_TYPE' => "multipart/form-data; boundary=$boundary", + 'CONTENT_LENGTH' => strlen($body), + 'REQUEST_METHOD' => 'POST', + 'SCRIPT_FILENAME' => __DIR__ . '/GHSA-9pqp-7h25-4f32.inc', + ]); + + $spec = [ + 0 => ['pipe', 'r'], + 1 => STDOUT, + 2 => STDOUT, + ]; + + $pipes = []; + + print "Starting...\n"; + + $handle = proc_open($cmd, $spec, $pipes, getcwd(), $env); + + fwrite($pipes[0], $body); + + $status = proc_close($handle); + + print "\n"; +} + +for ($offset = -1; $offset <= 1; $offset++) { + test(FILLUNIT - strlen("\r\n--") + $offset); +} + +?> +--EXPECTF-- +Boundary len: 5115 +Starting... +X-Powered-By: %s +Content-type: text/html; charset=UTF-8 + +Hello world +array(1) { + ["koko"]=> + string(5124) "BBB +--AAA%sCCC" +} + +Boundary len: 5116 +Starting... +X-Powered-By: %s +Content-type: text/html; charset=UTF-8 + +Hello world +array(1) { + ["koko"]=> + string(5125) "BBB +--AAA%sCCC" +} + +Boundary len: 5117 +Starting... +X-Powered-By: %s +Content-type: text/html; charset=UTF-8 + +<br /> +<b>Warning</b>: Boundary too large in multipart/form-data POST data in <b>Unknown</b> on line <b>0</b><br /> +Hello world +array(0) { +} + From b1420ec7a31dc00c41b442f1e09dbdc24d3c0a46 Mon Sep 17 00:00:00 2001 From: turly221 <feifan@scntist.com> Date: Wed, 11 Dec 2024 18:19:26 +0000 Subject: [PATCH 11/11] commit patch 28173196 --- sapi/fpm/fpm/fpm_stdio.c | 2 +- sapi/fpm/fpm/fpm_stdio.c.orig | 356 ++++++++++++++++++ .../log-bwp-msg-flush-split-sep-pos-end.phpt | 47 +++ ...log-bwp-msg-flush-split-sep-pos-start.phpt | 47 +++ 4 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 sapi/fpm/fpm/fpm_stdio.c.orig create mode 100644 sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt create mode 100644 sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c index ddedfb48c7cfc..9d87273314ad2 100644 --- a/sapi/fpm/fpm/fpm_stdio.c +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -177,7 +177,7 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) if ((sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos) <= in_buf && !memcmp(buf, &FPM_STDIO_CMD_FLUSH[cmd_pos], sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos)) { zlog_stream_finish(log_stream); - start = cmd_pos; + start = sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos; } else { zlog_stream_str(log_stream, &FPM_STDIO_CMD_FLUSH[0], cmd_pos); } diff --git a/sapi/fpm/fpm/fpm_stdio.c.orig b/sapi/fpm/fpm/fpm_stdio.c.orig new file mode 100644 index 0000000000000..ddedfb48c7cfc --- /dev/null +++ b/sapi/fpm/fpm/fpm_stdio.c.orig @@ -0,0 +1,356 @@ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "php_syslog.h" + +#include "fpm.h" +#include "fpm_children.h" +#include "fpm_cleanup.h" +#include "fpm_events.h" +#include "fpm_sockets.h" +#include "fpm_stdio.h" +#include "zlog.h" + +static int fd_stdout[2]; +static int fd_stderr[2]; + +int fpm_stdio_init_main() /* {{{ */ +{ + int fd = open("/dev/null", O_RDWR); + + if (0 > fd) { + zlog(ZLOG_SYSERROR, "failed to init stdio: open(\"/dev/null\")"); + return -1; + } + + if (0 > dup2(fd, STDIN_FILENO) || 0 > dup2(fd, STDOUT_FILENO)) { + zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()"); + close(fd); + return -1; + } + close(fd); + return 0; +} +/* }}} */ + +static inline int fpm_use_error_log() { /* {{{ */ + /* + * the error_log is NOT used when running in foreground + * and from a tty (user looking at output). + * So, error_log is used by + * - SysV init launch php-fpm as a daemon + * - Systemd launch php-fpm in foreground + */ +#if HAVE_UNISTD_H + if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) { +#else + if (fpm_global_config.daemonize) { +#endif + return 1; + } + return 0; +} + +/* }}} */ +int fpm_stdio_init_final() /* {{{ */ +{ + if (fpm_use_error_log()) { + /* prevent duping if logging to syslog */ + if (fpm_globals.error_log_fd > 0 && fpm_globals.error_log_fd != STDERR_FILENO) { + + /* there might be messages to stderr from other parts of the code, we need to log them all */ + if (0 > dup2(fpm_globals.error_log_fd, STDERR_FILENO)) { + zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()"); + return -1; + } + } +#ifdef HAVE_SYSLOG_H + else if (fpm_globals.error_log_fd == ZLOG_SYSLOG) { + /* dup to /dev/null when using syslog */ + dup2(STDOUT_FILENO, STDERR_FILENO); + } +#endif + } + zlog_set_launched(); + return 0; +} +/* }}} */ + +int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ +#ifdef HAVE_SYSLOG_H + if (fpm_globals.error_log_fd == ZLOG_SYSLOG) { + closelog(); /* ensure to close syslog not to interrupt with PHP syslog code */ + } else +#endif + + /* Notice: child cannot use master error_log + * because not aware when being reopen + * else, should use if (!fpm_use_error_log()) + */ + if (fpm_globals.error_log_fd > 0) { + close(fpm_globals.error_log_fd); + } + fpm_globals.error_log_fd = -1; + zlog_set_fd(-1); + + return 0; +} +/* }}} */ + +#define FPM_STDIO_CMD_FLUSH "\0fscf" + +int fpm_stdio_flush_child() /* {{{ */ +{ + return write(STDERR_FILENO, FPM_STDIO_CMD_FLUSH, sizeof(FPM_STDIO_CMD_FLUSH)); +} +/* }}} */ + +static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + static const int max_buf_size = 1024; + int fd = ev->fd; + char buf[max_buf_size]; + struct fpm_child_s *child; + int is_stdout; + struct fpm_event_s *event; + int in_buf = 0, cmd_pos = 0, pos, start; + int read_fail = 0, create_log_stream; + struct zlog_stream *log_stream; + + if (!arg) { + return; + } + child = (struct fpm_child_s *)arg; + + is_stdout = (fd == child->fd_stdout); + if (is_stdout) { + event = &child->ev_stdout; + } else { + event = &child->ev_stderr; + } + + create_log_stream = !child->log_stream; + if (create_log_stream) { + log_stream = child->log_stream = malloc(sizeof(struct zlog_stream)); + zlog_stream_init_ex(log_stream, ZLOG_WARNING, STDERR_FILENO); + zlog_stream_set_decorating(log_stream, child->wp->config->decorate_workers_output); + zlog_stream_set_wrapping(log_stream, ZLOG_TRUE); + zlog_stream_set_msg_prefix(log_stream, STREAM_SET_MSG_PREFIX_FMT, + child->wp->config->name, (int) child->pid, is_stdout ? "stdout" : "stderr"); + zlog_stream_set_msg_quoting(log_stream, ZLOG_TRUE); + zlog_stream_set_is_stdout(log_stream, is_stdout); + zlog_stream_set_child_pid(log_stream, (int)child->pid); + } else { + log_stream = child->log_stream; + // if fd type (stdout/stderr) or child's pid is changed, + // then the stream will be finished and msg's prefix will be reinitialized + if (log_stream->is_stdout != (unsigned int)is_stdout || log_stream->child_pid != (int)child->pid) { + zlog_stream_finish(log_stream); + zlog_stream_set_msg_prefix(log_stream, STREAM_SET_MSG_PREFIX_FMT, + child->wp->config->name, (int) child->pid, is_stdout ? "stdout" : "stderr"); + zlog_stream_set_is_stdout(log_stream, is_stdout); + zlog_stream_set_child_pid(log_stream, (int)child->pid); + } + } + + while (1) { +stdio_read: + in_buf = read(fd, buf, max_buf_size - 1); + if (in_buf <= 0) { /* no data */ + if (in_buf == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { + /* pipe is closed or error */ + read_fail = (in_buf < 0) ? in_buf : 1; + } + break; + } + start = 0; + if (cmd_pos > 0) { + if ((sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos) <= in_buf && + !memcmp(buf, &FPM_STDIO_CMD_FLUSH[cmd_pos], sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos)) { + zlog_stream_finish(log_stream); + start = cmd_pos; + } else { + zlog_stream_str(log_stream, &FPM_STDIO_CMD_FLUSH[0], cmd_pos); + } + cmd_pos = 0; + } + for (pos = start; pos < in_buf; pos++) { + switch (buf[pos]) { + case '\n': + zlog_stream_str(log_stream, buf + start, pos - start); + zlog_stream_finish(log_stream); + start = pos + 1; + break; + case '\0': + if (pos + sizeof(FPM_STDIO_CMD_FLUSH) <= in_buf) { + if (!memcmp(buf + pos, FPM_STDIO_CMD_FLUSH, sizeof(FPM_STDIO_CMD_FLUSH))) { + zlog_stream_str(log_stream, buf + start, pos - start); + zlog_stream_finish(log_stream); + start = pos + sizeof(FPM_STDIO_CMD_FLUSH); + pos = start - 1; + } + } else if (!memcmp(buf + pos, FPM_STDIO_CMD_FLUSH, in_buf - pos)) { + cmd_pos = in_buf - pos; + zlog_stream_str(log_stream, buf + start, pos - start); + goto stdio_read; + } + break; + } + } + if (start < pos) { + zlog_stream_str(log_stream, buf + start, pos - start); + } + } + + if (read_fail) { + if (create_log_stream) { + zlog_stream_set_msg_suffix(log_stream, NULL, ", pipe is closed"); + zlog_stream_finish(log_stream); + } + if (read_fail < 0) { + zlog(ZLOG_SYSERROR, "unable to read what child say"); + } + + fpm_event_del(event); + + if (is_stdout) { + close(child->fd_stdout); + child->fd_stdout = -1; + } else { + close(child->fd_stderr); + child->fd_stderr = -1; + } + } +} +/* }}} */ + +int fpm_stdio_prepare_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + if (0 > pipe(fd_stdout)) { + zlog(ZLOG_SYSERROR, "failed to prepare the stdout pipe"); + return -1; + } + + if (0 > pipe(fd_stderr)) { + zlog(ZLOG_SYSERROR, "failed to prepare the stderr pipe"); + close(fd_stdout[0]); + close(fd_stdout[1]); + return -1; + } + + if (0 > fd_set_blocked(fd_stdout[0], 0) || 0 > fd_set_blocked(fd_stderr[0], 0)) { + zlog(ZLOG_SYSERROR, "failed to unblock pipes"); + close(fd_stdout[0]); + close(fd_stdout[1]); + close(fd_stderr[0]); + close(fd_stderr[1]); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + close(fd_stdout[1]); + close(fd_stderr[1]); + + child->fd_stdout = fd_stdout[0]; + child->fd_stderr = fd_stderr[0]; + + fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child); + fpm_event_add(&child->ev_stdout, 0); + + fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child); + fpm_event_add(&child->ev_stderr, 0); + return 0; +} +/* }}} */ + +int fpm_stdio_discard_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + close(fd_stdout[1]); + close(fd_stderr[1]); + + close(fd_stdout[0]); + close(fd_stderr[0]); + return 0; +} +/* }}} */ + +void fpm_stdio_child_use_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (child->wp->config->catch_workers_output) { + dup2(fd_stdout[1], STDOUT_FILENO); + dup2(fd_stderr[1], STDERR_FILENO); + close(fd_stdout[0]); close(fd_stdout[1]); + close(fd_stderr[0]); close(fd_stderr[1]); + } else { + /* stdout of parent is always /dev/null */ + dup2(STDOUT_FILENO, STDERR_FILENO); + } +} +/* }}} */ + +int fpm_stdio_open_error_log(int reopen) /* {{{ */ +{ + int fd; + +#ifdef HAVE_SYSLOG_H + if (!strcasecmp(fpm_global_config.error_log, "syslog")) { + php_openlog(fpm_global_config.syslog_ident, LOG_PID | LOG_CONS, fpm_global_config.syslog_facility); + fpm_globals.error_log_fd = ZLOG_SYSLOG; + if (fpm_use_error_log()) { + zlog_set_fd(fpm_globals.error_log_fd); + } + return 0; + } +#endif + + fd = open(fpm_global_config.error_log, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); + if (0 > fd) { + zlog(ZLOG_SYSERROR, "failed to open error_log (%s)", fpm_global_config.error_log); + return -1; + } + + if (reopen) { + if (fpm_use_error_log()) { + dup2(fd, STDERR_FILENO); + } + + dup2(fd, fpm_globals.error_log_fd); + close(fd); + fd = fpm_globals.error_log_fd; /* for FD_CLOSEXEC to work */ + } else { + fpm_globals.error_log_fd = fd; + if (fpm_use_error_log()) { + zlog_set_fd(fpm_globals.error_log_fd); + } + } + if (0 > fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)) { + zlog(ZLOG_WARNING, "failed to change attribute of error_log"); + } + return 0; +} +/* }}} */ diff --git a/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt new file mode 100644 index 0000000000000..528263200803e --- /dev/null +++ b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt @@ -0,0 +1,47 @@ +--TEST-- +FPM: Buffered worker output plain log with msg with flush split position towards separator end +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +require_once "tester.inc"; + +$cfg = <<<EOT +[global] +error_log = {{FILE:LOG}} +[unconfined] +listen = {{ADDR}} +pm = dynamic +pm.max_children = 5 +pm.start_servers = 1 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +catch_workers_output = yes +decorate_workers_output = no +EOT; + +$code = <<<EOT +<?php +file_put_contents('php://stderr', str_repeat('a', 1013) . "Quarkslab\0fscf\0Quarkslab"); +EOT; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(); +$tester->expectLogStartNotices(); +$tester->request()->expectEmptyBody(); +$tester->expectLogLine(str_repeat('a', 1013) . "Quarkslab", decorated: false); +$tester->expectLogLine("Quarkslab", decorated: false); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- +<?php +require_once "tester.inc"; +FPM\Tester::clean(); +?> diff --git a/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt new file mode 100644 index 0000000000000..3490593855328 --- /dev/null +++ b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt @@ -0,0 +1,47 @@ +--TEST-- +FPM: Buffered worker output plain log with msg with flush split position towards separator start +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +require_once "tester.inc"; + +$cfg = <<<EOT +[global] +error_log = {{FILE:LOG}} +[unconfined] +listen = {{ADDR}} +pm = dynamic +pm.max_children = 5 +pm.start_servers = 1 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +catch_workers_output = yes +decorate_workers_output = no +EOT; + +$code = <<<EOT +<?php +file_put_contents('php://stderr', str_repeat('a', 1009) . "Quarkslab\0fscf\0Quarkslab"); +EOT; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(); +$tester->expectLogStartNotices(); +$tester->request()->expectEmptyBody(); +$tester->expectLogLine(str_repeat('a', 1009) . "Quarkslab", decorated: false); +$tester->expectLogLine("Quarkslab", decorated: false); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- +<?php +require_once "tester.inc"; +FPM\Tester::clean(); +?>