From 31ac172fd00e2028212cd77925d67927038df3b1 Mon Sep 17 00:00:00 2001 From: Valery Ivashchanka Date: Thu, 15 Oct 2026 20:05:38 +0000 Subject: [PATCH 1/2] ext/soap: report WSDL and schema errors without a bailout WSDL and XML schema errors were raised with soap_errorN(E_ERROR, ...). SoapClient turned the fatal error into a SoapFault and caught the bailout in SOAP_CLIENT_END_CODE(), but zend_bailout() had already called gc_protect(1), so the cycle collector stayed off for the rest of the request (GH-10026). The WSDL and schema loaders now throw the SoapFault with soap_throw_error() and return an error to their callers, which release what they own on the way out. The message and the fault code ("WSDL") are the same as before. SoapServer and SoapClient with the "exceptions" option disabled keep reporting these errors as fatal errors: the constructor turns the SoapFault back into the same E_ERROR once the loader has cleaned up. Since nothing bails out of the loaders any more, the error paths that existed only to survive a bailout are gone: the zend_try in load_wsdl_ex(), requestify_string() and the copies of targetNamespace in schema_load_file(). delete_type() now also frees an unresolved "ref", which an error before schema_pass2() can leave behind. Errors from encoding and decoding values still bail out and are not covered by this change. Co-Authored-By: Claude Opus 5.5 --- NEWS | 3 + ext/soap/php_schema.c | 723 +++++++++++----- ext/soap/php_schema.h | 5 +- ext/soap/php_sdl.c | 946 ++++++++++++--------- ext/soap/php_soap.h | 2 + ext/soap/soap.c | 64 ++ ext/soap/tests/bugs/gh10026.phpt | 41 + ext/soap/tests/wsdl_errors_no_bailout.phpt | 176 ++++ 8 files changed, 1353 insertions(+), 607 deletions(-) create mode 100644 ext/soap/tests/bugs/gh10026.phpt create mode 100644 ext/soap/tests/wsdl_errors_no_bailout.phpt diff --git a/NEWS b/NEWS index 67c44a4420e9..7eeb4b54f1dd 100644 --- a/NEWS +++ b/NEWS @@ -103,6 +103,9 @@ PHP NEWS - SOAP: . Fixed the SOAP client accepting a response body shorter than its Content-Length and reusing the connection. (Ilia Alshanetsky) + . SoapClient no longer bails out on WSDL and XML schema errors, which kept + the cycle collector disabled for the rest of the request (GH-10026). + (Valery Ivashchanka) - SPL: . Fixed bug GH-23385 (SplDoublyLinkedList::serialize() use-after-free when diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index 195c5abe45eb..d5bd90390703 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -39,7 +39,7 @@ static int schema_restriction_var_int(xmlNodePtr val, sdlRestrictionIntPtr *valp static int schema_restriction_var_char(xmlNodePtr val, sdlRestrictionCharPtr *valptr); -static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type); +static bool schema_type_fixup(sdlCtx *ctx, sdlTypePtr type); static bool node_is_equal_xsd(xmlNodePtr node, const char *name) { @@ -53,7 +53,7 @@ static bool node_is_equal_xsd(xmlNodePtr node, const char *name) return node_is_equal_ex_one_of(node, name, ns); } -static int schema_parse_int(const xmlChar *value, const char *name, bool allow_negative) +static bool schema_parse_int(const xmlChar *value, const char *name, bool allow_negative, int *result) { const char *str = (const char *) value; zend_long lval = 0; @@ -64,15 +64,18 @@ static int schema_parse_int(const xmlChar *value, const char *name, bool allow_n errno = 0; lval = ZEND_STRTOL(str, NULL, 10); if (UNEXPECTED(oflow_info || (errno == ERANGE && lval != 0))) { - soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); + soap_throw_error("Parsing Schema: %s value is out of range", name); + return false; } } if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(lval) || (!allow_negative && lval < 0))) { - soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); + soap_throw_error("Parsing Schema: %s value is out of range", name); + return false; } - return (int) lval; + *result = (int) lval; + return true; } static encodePtr create_encoder(sdlPtr sdl, sdlTypePtr cur_type, const xmlChar *ns, const xmlChar *type) @@ -130,14 +133,7 @@ static encodePtr get_create_encoder(sdlPtr sdl, sdlTypePtr cur_type, const xmlCh return enc; } -/* Necessary for some error paths to avoid leaking persistent memory. */ -static void requestify_string(xmlChar **str) { - xmlChar *copy = (xmlChar *) estrdup((const char *) *str); - xmlFree(*str); - *str = copy; -} - -static void schema_load_file(sdlCtx *ctx, xmlAttrPtr ns, xmlChar *location, xmlAttrPtr tns, int import) { +static bool schema_load_file(sdlCtx *ctx, xmlAttrPtr ns, xmlChar *location, xmlAttrPtr tns, int import) { if (location != NULL && !zend_hash_str_exists(&ctx->docs, (char*)location, xmlStrlen(location))) { xmlDocPtr doc; @@ -149,35 +145,30 @@ static void schema_load_file(sdlCtx *ctx, xmlAttrPtr ns, xmlChar *location, xmlA sdl_restore_uri_credentials(ctx); if (doc == NULL) { - requestify_string(&location); - soap_error1(E_ERROR, "Parsing Schema: can't import schema from '%s'", location); + soap_throw_error("Parsing Schema: can't import schema from '%s'", location); + return false; } schema = get_node(doc->children, "schema"); if (schema == NULL) { - requestify_string(&location); + soap_throw_error("Parsing Schema: can't import schema from '%s'", location); xmlFreeDoc(doc); - soap_error1(E_ERROR, "Parsing Schema: can't import schema from '%s'", location); + return false; } new_tns = get_attribute(schema->properties, "targetNamespace"); if (import) { if (ns != NULL && (new_tns == NULL || xmlStrcmp(ns->children->content, new_tns->children->content) != 0)) { - requestify_string(&location); if (new_tns == NULL) { - xmlFreeDoc(doc); - soap_error2(E_ERROR, "Parsing Schema: can't import schema from '%s', missing 'targetNamespace', expected '%s'", location, ns->children->content); + soap_throw_error("Parsing Schema: can't import schema from '%s', missing 'targetNamespace', expected '%s'", location, ns->children->content); } else { - /* Have to make a copy to avoid a UAF after freeing `doc` */ - const char *target_ns_copy = estrdup((const char *) new_tns->children->content); - xmlFreeDoc(doc); - soap_error3(E_ERROR, "Parsing Schema: can't import schema from '%s', unexpected 'targetNamespace'='%s', expected '%s'", location, target_ns_copy, ns->children->content); + soap_throw_error("Parsing Schema: can't import schema from '%s', unexpected 'targetNamespace'='%s', expected '%s'", location, new_tns->children->content, ns->children->content); } + xmlFreeDoc(doc); + return false; } if (ns == NULL && new_tns != NULL) { - requestify_string(&location); - /* Have to make a copy to avoid a UAF after freeing `doc` */ - const char *target_ns_copy = estrdup((const char *) new_tns->children->content); + soap_throw_error("Parsing Schema: can't import schema from '%s', unexpected 'targetNamespace'='%s', expected no 'targetNamespace'", location, new_tns->children->content); xmlFreeDoc(doc); - soap_error2(E_ERROR, "Parsing Schema: can't import schema from '%s', unexpected 'targetNamespace'='%s', expected no 'targetNamespace'", location, target_ns_copy); + return false; } } else { new_tns = get_attribute(schema->properties, "targetNamespace"); @@ -186,14 +177,15 @@ static void schema_load_file(sdlCtx *ctx, xmlAttrPtr ns, xmlChar *location, xmlA xmlSetProp(schema, BAD_CAST("targetNamespace"), tns->children->content); } } else if (tns != NULL && xmlStrcmp(tns->children->content, new_tns->children->content) != 0) { - requestify_string(&location); + soap_throw_error("Parsing Schema: can't include schema from '%s', different 'targetNamespace'", location); xmlFreeDoc(doc); - soap_error1(E_ERROR, "Parsing Schema: can't include schema from '%s', different 'targetNamespace'", location); + return false; } } zend_hash_str_add_ptr(&ctx->docs, (char*)location, xmlStrlen(location), doc); - load_schema(ctx, schema); + return load_schema(ctx, schema); } + return true; } /* Returned uri must be freed by the caller. */ @@ -232,7 +224,7 @@ xmlChar *schema_location_construct_uri(const xmlAttr *attribute) Content: ((include | import | redefine | annotation)*, (((simpleType | complexType | group | attributeGroup) | element | attribute | notation), annotation*)*) */ -int load_schema(sdlCtx *ctx, xmlNodePtr schema) +bool load_schema(sdlCtx *ctx, xmlNodePtr schema) { xmlNodePtr trav; xmlAttrPtr tns; @@ -263,11 +255,15 @@ int load_schema(sdlCtx *ctx, xmlNodePtr schema) location = get_attribute(trav->properties, "schemaLocation"); if (location == NULL) { - soap_error0(E_ERROR, "Parsing Schema: include has no 'schemaLocation' attribute"); + soap_throw_error("Parsing Schema: include has no 'schemaLocation' attribute"); + return false; } else { xmlChar *uri = schema_location_construct_uri(location); - schema_load_file(ctx, NULL, uri, tns, 0); + bool loaded = schema_load_file(ctx, NULL, uri, tns, 0); xmlFree(uri); + if (!loaded) { + return false; + } } } else if (node_is_equal_xsd(trav,"redefine")) { @@ -275,11 +271,15 @@ int load_schema(sdlCtx *ctx, xmlNodePtr schema) location = get_attribute(trav->properties, "schemaLocation"); if (location == NULL) { - soap_error0(E_ERROR, "Parsing Schema: redefine has no 'schemaLocation' attribute"); + soap_throw_error("Parsing Schema: redefine has no 'schemaLocation' attribute"); + return false; } else { xmlChar *uri = schema_location_construct_uri(location); - schema_load_file(ctx, NULL, uri, tns, 0); + bool loaded = schema_load_file(ctx, NULL, uri, tns, 0); xmlFree(uri); + if (!loaded) { + return false; + } /* TODO: support */ } @@ -292,16 +292,21 @@ int load_schema(sdlCtx *ctx, xmlNodePtr schema) if (ns != NULL && tns != NULL && xmlStrcmp(ns->children->content, tns->children->content) == 0) { if (location) { - soap_error1(E_ERROR, "Parsing Schema: can't import schema from '%s', namespace must not match the enclosing schema 'targetNamespace'", location->children->content); + soap_throw_error("Parsing Schema: can't import schema from '%s', namespace must not match the enclosing schema 'targetNamespace'", location->children->content); + return false; } else { - soap_error0(E_ERROR, "Parsing Schema: can't import schema. Namespace must not match the enclosing schema 'targetNamespace'"); + soap_throw_error("Parsing Schema: can't import schema. Namespace must not match the enclosing schema 'targetNamespace'"); + return false; } } if (location) { uri = schema_location_construct_uri(location); } - schema_load_file(ctx, ns, uri, tns, 1); + bool loaded = schema_load_file(ctx, ns, uri, tns, 1); if (uri != NULL) {xmlFree(uri);} + if (!loaded) { + return false; + } } else if (node_is_equal_xsd(trav,"annotation")) { /* TODO: support */ /* annotation cleanup @@ -319,27 +324,40 @@ int load_schema(sdlCtx *ctx, xmlNodePtr schema) while (trav != NULL) { if (node_is_equal_xsd(trav,"simpleType")) { - schema_simpleType(ctx->sdl, tns, trav, NULL); + if (!schema_simpleType(ctx->sdl, tns, trav, NULL)) { + return false; + } } else if (node_is_equal_xsd(trav,"complexType")) { - schema_complexType(ctx->sdl, tns, trav, NULL); + if (!schema_complexType(ctx->sdl, tns, trav, NULL)) { + return false; + } } else if (node_is_equal_xsd(trav,"group")) { - schema_group(ctx->sdl, tns, trav, NULL, NULL); + if (!schema_group(ctx->sdl, tns, trav, NULL, NULL)) { + return false; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(ctx->sdl, tns, trav, NULL, ctx); + if (!schema_attributeGroup(ctx->sdl, tns, trav, NULL, ctx)) { + return false; + } } else if (node_is_equal_xsd(trav,"element")) { - schema_element(ctx->sdl, tns, trav, NULL, NULL); + if (!schema_element(ctx->sdl, tns, trav, NULL, NULL)) { + return false; + } } else if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(ctx->sdl, tns, trav, NULL, ctx); + if (!schema_attribute(ctx->sdl, tns, trav, NULL, ctx)) { + return false; + } } else if (node_is_equal_xsd(trav,"notation")) { /* TODO: support */ } else if (node_is_equal_xsd(trav,"annotation")) { /* TODO: support */ } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in schema", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in schema", trav->name); + return false; } trav = trav->next; } - return TRUE; + return true; } /* @@ -419,7 +437,8 @@ static int schema_simpleType(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr simpleType, create_encoder(sdl, cur_type, ns->children->content, name->children->content); } else { - soap_error0(E_ERROR, "Parsing Schema: simpleType has no 'name' attribute"); + soap_throw_error("Parsing Schema: simpleType has no 'name' attribute"); + return FALSE; } trav = simpleType->children; @@ -429,24 +448,33 @@ static int schema_simpleType(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr simpleType, } if (trav != NULL) { if (node_is_equal_xsd(trav,"restriction")) { - schema_restriction_simpleContent(sdl, tns, trav, cur_type, 1); + if (!schema_restriction_simpleContent(sdl, tns, trav, cur_type, 1)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"list")) { cur_type->kind = XSD_TYPEKIND_LIST; - schema_list(sdl, tns, trav, cur_type); + if (!schema_list(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"union")) { cur_type->kind = XSD_TYPEKIND_UNION; - schema_union(sdl, tns, trav, cur_type); + if (!schema_union(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in simpleType", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in simpleType", trav->name); + return FALSE; } } else { - soap_error0(E_ERROR, "Parsing Schema: expected , or in simpleType"); + soap_throw_error("Parsing Schema: expected , or in simpleType"); + return FALSE; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in simpleType", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in simpleType", trav->name); + return FALSE; } return TRUE; @@ -502,7 +530,8 @@ static int schema_list(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr listType, sdlTypeP sdlTypePtr newType; if (itemType != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'itemType' attribute and subtype"); + soap_throw_error("Parsing Schema: element has both 'itemType' attribute and subtype"); + return FALSE; } newType = emalloc(sizeof(sdlType)); @@ -525,12 +554,15 @@ static int schema_list(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr listType, sdlTypeP } zend_hash_next_index_insert_ptr(cur_type->elements, newType); - schema_simpleType(sdl, tns, trav, newType); + if (!schema_simpleType(sdl, tns, trav, newType)) { + return FALSE; + } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in list", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in list", trav->name); + return FALSE; } return TRUE; } @@ -622,15 +654,19 @@ static int schema_union(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr unionType, sdlTyp } zend_hash_next_index_insert_ptr(cur_type->elements, newType); - schema_simpleType(sdl, tns, trav, newType); + if (!schema_simpleType(sdl, tns, trav, newType)) { + return FALSE; + } } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in union", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in union", trav->name); + return FALSE; } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in union", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in union", trav->name); + return FALSE; } return TRUE; } @@ -654,20 +690,27 @@ static int schema_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr simpCompT if (trav != NULL) { if (node_is_equal_xsd(trav, "restriction")) { cur_type->kind = XSD_TYPEKIND_RESTRICTION; - schema_restriction_simpleContent(sdl, tns, trav, cur_type, 0); + if (!schema_restriction_simpleContent(sdl, tns, trav, cur_type, 0)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav, "extension")) { cur_type->kind = XSD_TYPEKIND_EXTENSION; - schema_extension_simpleContent(sdl, tns, trav, cur_type); + if (!schema_extension_simpleContent(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in simpleContent", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in simpleContent", trav->name); + return FALSE; } } else { - soap_error0(E_ERROR, "Parsing Schema: expected or in simpleContent"); + soap_throw_error("Parsing Schema: expected or in simpleContent"); + return FALSE; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in simpleContent", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in simpleContent", trav->name); + return FALSE; } return TRUE; @@ -705,7 +748,8 @@ static int schema_restriction_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodeP } if (ns) {efree(ns);} } else if (!simpleType) { - soap_error0(E_ERROR, "Parsing Schema: restriction has no 'base' attribute"); + soap_throw_error("Parsing Schema: restriction has no 'base' attribute"); + return FALSE; } if (cur_type->restrictions == NULL) { @@ -719,36 +763,63 @@ static int schema_restriction_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodeP trav = trav->next; } if (trav != NULL && node_is_equal_xsd(trav, "simpleType")) { - schema_simpleType(sdl, tns, trav, cur_type); + if (!schema_simpleType(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } while (trav != NULL) { if (node_is_equal_xsd(trav, "minExclusive")) { - schema_restriction_var_int(trav, &cur_type->restrictions->minExclusive); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->minExclusive)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "minInclusive")) { - schema_restriction_var_int(trav, &cur_type->restrictions->minInclusive); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->minInclusive)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "maxExclusive")) { - schema_restriction_var_int(trav, &cur_type->restrictions->maxExclusive); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->maxExclusive)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "maxInclusive")) { - schema_restriction_var_int(trav, &cur_type->restrictions->maxInclusive); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->maxInclusive)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "totalDigits")) { - schema_restriction_var_int(trav, &cur_type->restrictions->totalDigits); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->totalDigits)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "fractionDigits")) { - schema_restriction_var_int(trav, &cur_type->restrictions->fractionDigits); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->fractionDigits)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "length")) { - schema_restriction_var_int(trav, &cur_type->restrictions->length); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->length)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "minLength")) { - schema_restriction_var_int(trav, &cur_type->restrictions->minLength); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->minLength)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "maxLength")) { - schema_restriction_var_int(trav, &cur_type->restrictions->maxLength); + if (!schema_restriction_var_int(trav, &cur_type->restrictions->maxLength)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "whiteSpace")) { - schema_restriction_var_char(trav, &cur_type->restrictions->whiteSpace); + if (!schema_restriction_var_char(trav, &cur_type->restrictions->whiteSpace)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "pattern")) { - schema_restriction_var_char(trav, &cur_type->restrictions->pattern); + if (!schema_restriction_var_char(trav, &cur_type->restrictions->pattern)) { + return FALSE; + } } else if (node_is_equal_xsd(trav, "enumeration")) { sdlRestrictionCharPtr enumval = NULL; - schema_restriction_var_char(trav, &enumval); + if (!schema_restriction_var_char(trav, &enumval)) { + delete_restriction_var_char_int(enumval); + return FALSE; + } if (cur_type->restrictions->enumeration == NULL) { cur_type->restrictions->enumeration = emalloc(sizeof(HashTable)); zend_hash_init(cur_type->restrictions->enumeration, 0, NULL, delete_restriction_var_char, 0); @@ -764,21 +835,27 @@ static int schema_restriction_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodeP if (!simpleType) { while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(sdl, tns, trav, cur_type, NULL); + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"anyAttribute")) { /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in restriction", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in restriction", trav->name); + return FALSE; } trav = trav->next; } } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in restriction", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in restriction", trav->name); + return FALSE; } return TRUE; @@ -810,7 +887,8 @@ static int schema_restriction_complexContent(sdlPtr sdl, xmlAttrPtr tns, xmlNode } if (ns) {efree(ns);} } else { - soap_error0(E_ERROR, "Parsing Schema: restriction has no 'base' attribute"); + soap_throw_error("Parsing Schema: restriction has no 'base' attribute"); + return FALSE; } trav = restType->children; @@ -820,35 +898,49 @@ static int schema_restriction_complexContent(sdlPtr sdl, xmlAttrPtr tns, xmlNode } if (trav != NULL) { if (node_is_equal_xsd(trav,"group")) { - schema_group(sdl, tns, trav, cur_type, NULL); + if (!schema_group(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"all")) { - schema_all(sdl, tns, trav, cur_type, NULL); + if (!schema_all(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"choice")) { - schema_choice(sdl, tns, trav, cur_type, NULL); + if (!schema_choice(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"sequence")) { - schema_sequence(sdl, tns, trav, cur_type, NULL); + if (!schema_sequence(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } } while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(sdl, tns, trav, cur_type, NULL); + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"anyAttribute")) { /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in restriction", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in restriction", trav->name); + return FALSE; } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in restriction", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in restriction", trav->name); + return FALSE; } return TRUE; @@ -873,12 +965,11 @@ static int schema_restriction_var_int(xmlNodePtr val, sdlRestrictionIntPtr *valp value = get_attribute(val->properties, "value"); if (value == NULL) { - soap_error0(E_ERROR, "Parsing Schema: missing restriction value"); + soap_throw_error("Parsing Schema: missing restriction value"); + return FALSE; } - (*valptr)->value = schema_parse_int(value->children->content, (const char *) val->name, true); - - return TRUE; + return schema_parse_int(value->children->content, (const char *) val->name, true, &(*valptr)->value); } static int schema_restriction_var_char(xmlNodePtr val, sdlRestrictionCharPtr *valptr) @@ -901,7 +992,8 @@ static int schema_restriction_var_char(xmlNodePtr val, sdlRestrictionCharPtr *va value = get_attribute(val->properties, "value"); if (value == NULL) { - soap_error0(E_ERROR, "Parsing Schema: missing restriction value"); + soap_throw_error("Parsing Schema: missing restriction value"); + return FALSE; } (*valptr)->value = estrdup((char*)value->children->content); @@ -935,7 +1027,8 @@ static int schema_extension_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr } if (ns) {efree(ns);} } else { - soap_error0(E_ERROR, "Parsing Schema: extension has no 'base' attribute"); + soap_throw_error("Parsing Schema: extension has no 'base' attribute"); + return FALSE; } trav = extType->children; @@ -945,20 +1038,26 @@ static int schema_extension_simpleContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr } while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(sdl, tns, trav, cur_type, NULL); + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"anyAttribute")) { /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in extension", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in extension", trav->name); + return FALSE; } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in extension", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in extension", trav->name); + return FALSE; } return TRUE; } @@ -990,7 +1089,8 @@ static int schema_extension_complexContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePt } if (ns) {efree(ns);} } else { - soap_error0(E_ERROR, "Parsing Schema: extension has no 'base' attribute"); + soap_throw_error("Parsing Schema: extension has no 'base' attribute"); + return FALSE; } trav = extType->children; @@ -1000,45 +1100,61 @@ static int schema_extension_complexContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePt } if (trav != NULL) { if (node_is_equal_xsd(trav,"group")) { - schema_group(sdl, tns, trav, cur_type, NULL); + if (!schema_group(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"all")) { - schema_all(sdl, tns, trav, cur_type, NULL); + if (!schema_all(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"choice")) { - schema_choice(sdl, tns, trav, cur_type, NULL); + if (!schema_choice(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"sequence")) { - schema_sequence(sdl, tns, trav, cur_type, NULL); + if (!schema_sequence(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } } while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(sdl, tns, trav, cur_type, NULL); + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"anyAttribute")) { /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in extension", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in extension", trav->name); + return FALSE; } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in extension", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in extension", trav->name); + return FALSE; } return TRUE; } -void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) +static bool schema_min_max(xmlNodePtr node, sdlContentModelPtr model) { xmlAttrPtr attr = get_attribute(node->properties, "minOccurs"); if (attr) { - model->min_occurs = schema_parse_int(attr->children->content, "minOccurs", false); + if (!schema_parse_int(attr->children->content, "minOccurs", false, &model->min_occurs)) { + return false; + } } else { model->min_occurs = 1; } @@ -1047,12 +1163,13 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) if (attr) { if (!strncmp((char*)attr->children->content, "unbounded", sizeof("unbounded"))) { model->max_occurs = -1; - } else { - model->max_occurs = schema_parse_int(attr->children->content, "maxOccurs", false); + } else if (!schema_parse_int(attr->children->content, "maxOccurs", false, &model->max_occurs)) { + return false; } } else { model->max_occurs = 1; } + return true; } /* @@ -1079,7 +1196,9 @@ static int schema_all(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr all, sdlTypePtr cur zend_hash_next_index_insert_ptr(model->u.content, newModel); } - schema_min_max(all, newModel); + if (!schema_min_max(all, newModel)) { + return FALSE; + } trav = all->children; if (trav != NULL && node_is_equal_xsd(trav,"annotation")) { @@ -1088,9 +1207,12 @@ static int schema_all(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr all, sdlTypePtr cur } while (trav != NULL) { if (node_is_equal_xsd(trav,"element")) { - schema_element(sdl, tns, trav, cur_type, newModel); + if (!schema_element(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in all", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in all", trav->name); + return FALSE; } trav = trav->next; } @@ -1177,7 +1299,15 @@ static int schema_group(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr groupType, sdlTyp zend_hash_init(sdl->groups, 0, NULL, delete_type, 0); } if (zend_hash_add_ptr(sdl->groups, key.s, newType) == NULL) { - soap_error1(E_ERROR, "Parsing Schema: group '%s' already defined", ZSTR_VAL(key.s)); + zval zv; + + soap_throw_error("Parsing Schema: group '%s' already defined", ZSTR_VAL(key.s)); + smart_str_free(&key); + ZVAL_PTR(&zv, newType); + delete_type(&zv); + ZVAL_PTR(&zv, newModel); + delete_model(&zv); + return FALSE; } cur_type = newType; @@ -1190,10 +1320,13 @@ static int schema_group(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr groupType, sdlTyp zend_hash_next_index_insert_ptr(model->u.content, newModel); } } else { - soap_error0(E_ERROR, "Parsing Schema: group has no 'name' nor 'ref' attributes"); + soap_throw_error("Parsing Schema: group has no 'name' nor 'ref' attributes"); + return FALSE; } - schema_min_max(groupType, newModel); + if (!schema_min_max(groupType, newModel)) { + return FALSE; + } trav = groupType->children; if (trav != NULL && node_is_equal_xsd(trav,"annotation")) { @@ -1203,31 +1336,42 @@ static int schema_group(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr groupType, sdlTyp if (trav != NULL) { if (node_is_equal_xsd(trav,"choice")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: group has both 'ref' attribute and subcontent"); + soap_throw_error("Parsing Schema: group has both 'ref' attribute and subcontent"); + return FALSE; } newModel->kind = XSD_CONTENT_CHOICE; - schema_choice(sdl, tns, trav, cur_type, newModel); + if (!schema_choice(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"sequence")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: group has both 'ref' attribute and subcontent"); + soap_throw_error("Parsing Schema: group has both 'ref' attribute and subcontent"); + return FALSE; } newModel->kind = XSD_CONTENT_SEQUENCE; - schema_sequence(sdl, tns, trav, cur_type, newModel); + if (!schema_sequence(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"all")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: group has both 'ref' attribute and subcontent"); + soap_throw_error("Parsing Schema: group has both 'ref' attribute and subcontent"); + return FALSE; } newModel->kind = XSD_CONTENT_ALL; - schema_all(sdl, tns, trav, cur_type, newModel); + if (!schema_all(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } trav = trav->next; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in group", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in group", trav->name); + return FALSE; } } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in group", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in group", trav->name); + return FALSE; } return TRUE; } @@ -1255,7 +1399,9 @@ static int schema_choice(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr choiceType, sdlT zend_hash_next_index_insert_ptr(model->u.content, newModel); } - schema_min_max(choiceType, newModel); + if (!schema_min_max(choiceType, newModel)) { + return FALSE; + } trav = choiceType->children; if (trav != NULL && node_is_equal_xsd(trav,"annotation")) { @@ -1264,17 +1410,28 @@ static int schema_choice(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr choiceType, sdlT } while (trav != NULL) { if (node_is_equal_xsd(trav,"element")) { - schema_element(sdl, tns, trav, cur_type, newModel); + if (!schema_element(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"group")) { - schema_group(sdl, tns, trav, cur_type, newModel); + if (!schema_group(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"choice")) { - schema_choice(sdl, tns, trav, cur_type, newModel); + if (!schema_choice(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"sequence")) { - schema_sequence(sdl, tns, trav, cur_type, newModel); + if (!schema_sequence(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"any")) { - schema_any(sdl, tns, trav, cur_type, newModel); + if (!schema_any(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in choice", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in choice", trav->name); + return FALSE; } trav = trav->next; } @@ -1305,7 +1462,9 @@ static int schema_sequence(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr seqType, sdlTy zend_hash_next_index_insert_ptr(model->u.content, newModel); } - schema_min_max(seqType, newModel); + if (!schema_min_max(seqType, newModel)) { + return FALSE; + } trav = seqType->children; if (trav != NULL && node_is_equal_xsd(trav,"annotation")) { @@ -1314,17 +1473,28 @@ static int schema_sequence(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr seqType, sdlTy } while (trav != NULL) { if (node_is_equal_xsd(trav,"element")) { - schema_element(sdl, tns, trav, cur_type, newModel); + if (!schema_element(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"group")) { - schema_group(sdl, tns, trav, cur_type, newModel); + if (!schema_group(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"choice")) { - schema_choice(sdl, tns, trav, cur_type, newModel); + if (!schema_choice(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"sequence")) { - schema_sequence(sdl, tns, trav, cur_type, newModel); + if (!schema_sequence(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"any")) { - schema_any(sdl, tns, trav, cur_type, newModel); + if (!schema_any(sdl, tns, trav, cur_type, newModel)) { + return FALSE; + } } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in sequence", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in sequence", trav->name); + return FALSE; } trav = trav->next; } @@ -1350,7 +1520,10 @@ static int schema_any(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr anyType, sdlTypePtr newModel = emalloc(sizeof(sdlContentModel)); newModel->kind = XSD_CONTENT_ANY; - schema_min_max(anyType, newModel); + if (!schema_min_max(anyType, newModel)) { + efree(newModel); + return FALSE; + } zend_hash_next_index_insert_ptr(model->u.content, newModel); } @@ -1377,20 +1550,27 @@ static int schema_complexContent(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr compCont if (trav != NULL) { if (node_is_equal_xsd(trav, "restriction")) { cur_type->kind = XSD_TYPEKIND_RESTRICTION; - schema_restriction_complexContent(sdl, tns, trav, cur_type); + if (!schema_restriction_complexContent(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav, "extension")) { cur_type->kind = XSD_TYPEKIND_EXTENSION; - schema_extension_complexContent(sdl, tns, trav, cur_type); + if (!schema_extension_complexContent(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in complexContent", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in complexContent", trav->name); + return FALSE; } } else { - soap_error0(E_ERROR, "Parsing Schema: or expected in complexContent"); + soap_throw_error("Parsing Schema: or expected in complexContent"); + return FALSE; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in complexContent", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in complexContent", trav->name); + return FALSE; } return TRUE; @@ -1469,7 +1649,7 @@ static int schema_complexType(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr compType, s cur_type = ptr; create_encoder(sdl, cur_type, ns->children->content, name->children->content); } else { - soap_error0(E_ERROR, "Parsing Schema: complexType has no 'name' attribute"); + soap_throw_error("Parsing Schema: complexType has no 'name' attribute"); return FALSE; } @@ -1480,43 +1660,61 @@ static int schema_complexType(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr compType, s } if (trav != NULL) { if (node_is_equal_xsd(trav,"simpleContent")) { - schema_simpleContent(sdl, tns, trav, cur_type); + if (!schema_simpleContent(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"complexContent")) { - schema_complexContent(sdl, tns, trav, cur_type); + if (!schema_complexContent(sdl, tns, trav, cur_type)) { + return FALSE; + } trav = trav->next; } else { if (node_is_equal_xsd(trav,"group")) { - schema_group(sdl, tns, trav, cur_type, NULL); + if (!schema_group(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"all")) { - schema_all(sdl, tns, trav, cur_type, NULL); + if (!schema_all(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"choice")) { - schema_choice(sdl, tns, trav, cur_type, NULL); + if (!schema_choice(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } else if (node_is_equal_xsd(trav,"sequence")) { - schema_sequence(sdl, tns, trav, cur_type, NULL); + if (!schema_sequence(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } trav = trav->next; } while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { - schema_attribute(sdl, tns, trav, cur_type, NULL); + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"attributeGroup")) { - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; + } } else if (node_is_equal_xsd(trav,"anyAttribute")) { /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in complexType", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in complexType", trav->name); + return FALSE; } trav = trav->next; } } } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in complexType", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in complexType", trav->name); + return FALSE; } return TRUE; } @@ -1619,7 +1817,13 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp smart_str_0(&key); if (zend_hash_add_ptr(addHash, key.s, newType) == NULL) { if (cur_type == NULL) { - soap_error1(E_ERROR, "Parsing Schema: element '%s' already defined", ZSTR_VAL(key.s)); + zval zv; + + soap_throw_error("Parsing Schema: element '%s' already defined", ZSTR_VAL(key.s)); + smart_str_free(&key); + ZVAL_PTR(&zv, newType); + delete_type(&zv); + return FALSE; } else { zend_hash_next_index_insert_ptr(addHash, newType); } @@ -1632,14 +1836,18 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp newModel->kind = XSD_CONTENT_ELEMENT; newModel->u.element = newType; - schema_min_max(element, newModel); + if (!schema_min_max(element, newModel)) { + efree(newModel); + return FALSE; + } zend_hash_next_index_insert_ptr(model->u.content, newModel); } cur_type = newType; } else { - soap_error0(E_ERROR, "Parsing Schema: element has no 'name' nor 'ref' attributes"); + soap_throw_error("Parsing Schema: element has no 'name' nor 'ref' attributes"); + return FALSE; } /* nillable = boolean : false */ @@ -1647,7 +1855,8 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp attr = get_attribute(attrs, "nillable"); if (attr) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'ref' and 'nillable' attributes"); + soap_throw_error("Parsing Schema: element has both 'ref' and 'nillable' attributes"); + return FALSE; } if (!stricmp((char*)attr->children->content, "true") || !stricmp((char*)attr->children->content, "1")) { @@ -1662,7 +1871,8 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp attr = get_attribute(attrs, "fixed"); if (attr) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'ref' and 'fixed' attributes"); + soap_throw_error("Parsing Schema: element has both 'ref' and 'fixed' attributes"); + return FALSE; } cur_type->fixed = estrdup((char*)attr->children->content); } @@ -1670,7 +1880,8 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp attr = get_attribute(attrs, "default"); if (attr) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'default' and 'fixed' attributes"); + soap_throw_error("Parsing Schema: element has both 'default' and 'fixed' attributes"); + return FALSE; } cur_type->def = estrdup((char*)attr->children->content); } @@ -1716,7 +1927,8 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp xmlNsPtr nsptr; if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'ref' and 'type' attributes"); + soap_throw_error("Parsing Schema: element has both 'ref' and 'type' attributes"); + return FALSE; } parse_namespace(type->children->content, &cptype, &str_ns); nsptr = xmlSearchNs(element->doc, element, BAD_CAST(str_ns)); @@ -1734,19 +1946,27 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp if (trav != NULL) { if (node_is_equal_xsd(trav,"simpleType")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'ref' attribute and subtype"); + soap_throw_error("Parsing Schema: element has both 'ref' attribute and subtype"); + return FALSE; } else if (type != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'type' attribute and subtype"); + soap_throw_error("Parsing Schema: element has both 'type' attribute and subtype"); + return FALSE; + } + if (!schema_simpleType(sdl, tns, trav, cur_type)) { + return FALSE; } - schema_simpleType(sdl, tns, trav, cur_type); trav = trav->next; } else if (node_is_equal_xsd(trav,"complexType")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'ref' attribute and subtype"); + soap_throw_error("Parsing Schema: element has both 'ref' attribute and subtype"); + return FALSE; } else if (type != NULL) { - soap_error0(E_ERROR, "Parsing Schema: element has both 'type' attribute and subtype"); + soap_throw_error("Parsing Schema: element has both 'type' attribute and subtype"); + return FALSE; + } + if (!schema_complexType(sdl, tns, trav, cur_type)) { + return FALSE; } - schema_complexType(sdl, tns, trav, cur_type); trav = trav->next; } } @@ -1758,7 +1978,8 @@ static int schema_element(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr element, sdlTyp } else if (node_is_equal_xsd(trav,"keyref")) { /* TODO: support */ } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in element", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in element", trav->name); + return FALSE; } trav = trav->next; } @@ -1848,12 +2069,18 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl } if (zend_hash_add_ptr(addHash, key.s, newAttr) == NULL) { - soap_error1(E_ERROR, "Parsing Schema: attribute '%s' already defined", ZSTR_VAL(key.s)); + zval zv; + + soap_throw_error("Parsing Schema: attribute '%s' already defined", ZSTR_VAL(key.s)); + smart_str_free(&key); + ZVAL_PTR(&zv, newAttr); + delete_attribute(&zv); + return FALSE; } smart_str_free(&key); } else{ - soap_error0(E_ERROR, "Parsing Schema: attribute has no 'name' nor 'ref' attributes"); - return FALSE; /* the above call is noreturn, but not marked as such */ + soap_throw_error("Parsing Schema: attribute has no 'name' nor 'ref' attributes"); + return FALSE; } /* type = QName */ @@ -1864,7 +2091,8 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl xmlNsPtr nsptr; if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attribute has both 'ref' and 'type' attributes"); + soap_throw_error("Parsing Schema: attribute has both 'ref' and 'type' attributes"); + return FALSE; } parse_namespace(type->children->content, &cptype, &str_ns); nsptr = xmlSearchNs(attrType->doc, attrType, BAD_CAST(str_ns)); @@ -1973,9 +2201,11 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl zval zv; if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attribute has both 'ref' attribute and subtype"); + soap_throw_error("Parsing Schema: attribute has both 'ref' attribute and subtype"); + return FALSE; } else if (type != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attribute has both 'type' attribute and subtype"); + soap_throw_error("Parsing Schema: attribute has both 'type' attribute and subtype"); + return FALSE; } dummy_type = emalloc(sizeof(sdlType)); memset(dummy_type, 0, sizeof(sdlType)); @@ -1989,15 +2219,19 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl dummy_type->name = str; } dummy_type->namens = estrdup((char*)tns->children->content); - schema_simpleType(sdl, tns, trav, dummy_type); + bool parsed = schema_simpleType(sdl, tns, trav, dummy_type); newAttr->encode = dummy_type->encode; ZVAL_PTR(&zv, dummy_type); delete_type(&zv); + if (!parsed) { + return FALSE; + } trav = trav->next; } } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in attribute", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in attribute", trav->name); + return FALSE; } return TRUE; } @@ -2033,7 +2267,13 @@ static int schema_attributeGroup(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrGrou smart_str_0(&key); if (zend_hash_add_ptr(ctx->attributeGroups, key.s, newType) == NULL) { - soap_error1(E_ERROR, "Parsing Schema: attributeGroup '%s' already defined", ZSTR_VAL(key.s)); + zval zv; + + soap_throw_error("Parsing Schema: attributeGroup '%s' already defined", ZSTR_VAL(key.s)); + smart_str_free(&key); + ZVAL_PTR(&zv, newType); + delete_type(&zv); + return FALSE; } cur_type = newType; smart_str_free(&key); @@ -2067,7 +2307,8 @@ static int schema_attributeGroup(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrGrou cur_type = NULL; } } else{ - soap_error0(E_ERROR, "Parsing Schema: attributeGroup has no 'name' nor 'ref' attributes"); + soap_throw_error("Parsing Schema: attributeGroup has no 'name' nor 'ref' attributes"); + return FALSE; } trav = attrGroup->children; @@ -2078,28 +2319,37 @@ static int schema_attributeGroup(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrGrou while (trav != NULL) { if (node_is_equal_xsd(trav,"attribute")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + soap_throw_error("Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + return FALSE; + } + if (!schema_attribute(sdl, tns, trav, cur_type, NULL)) { + return FALSE; } - schema_attribute(sdl, tns, trav, cur_type, NULL); } else if (node_is_equal_xsd(trav,"attributeGroup")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + soap_throw_error("Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + return FALSE; + } + if (!schema_attributeGroup(sdl, tns, trav, cur_type, NULL)) { + return FALSE; } - schema_attributeGroup(sdl, tns, trav, cur_type, NULL); } else if (node_is_equal_xsd(trav,"anyAttribute")) { if (ref != NULL) { - soap_error0(E_ERROR, "Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + soap_throw_error("Parsing Schema: attributeGroup has both 'ref' attribute and subattribute"); + return FALSE; } /* TODO: support */ trav = trav->next; break; } else { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in attributeGroup", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in attributeGroup", trav->name); + return FALSE; } trav = trav->next; } if (trav != NULL) { - soap_error1(E_ERROR, "Parsing Schema: unexpected <%s> in attributeGroup", trav->name); + soap_throw_error("Parsing Schema: unexpected <%s> in attributeGroup", trav->name); + return FALSE; } return TRUE; } @@ -2184,7 +2434,7 @@ static void schema_attribute_fixup(sdlCtx *ctx, sdlAttributePtr attr) } } -static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashTable *ht) +static bool schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashTable *ht) { sdlTypePtr tmp; sdlAttributePtr tmp_attr; @@ -2194,7 +2444,8 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT tmp = (sdlTypePtr)schema_find_by_ref(ctx->attributeGroups, attr->ref); if (tmp) { if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)(uintptr_t)tmp) != NULL) { - soap_error1(E_ERROR, "Parsing Schema: recursive attributeGroup 'ref' attribute '%s'", attr->ref); + soap_throw_error("Parsing Schema: recursive attributeGroup 'ref' attribute '%s'", attr->ref); + return false; } zend_hash_index_add_ptr(&ctx->fixupInProgress, (zend_ulong)(uintptr_t)tmp, tmp); if (tmp->attributes) { @@ -2226,7 +2477,9 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT } else { zend_ulong index; - schema_attributegroup_fixup(ctx, tmp_attr, ht); + if (!schema_attributegroup_fixup(ctx, tmp_attr, ht)) { + return false; + } zend_hash_get_current_key(tmp->attributes, NULL, &index); zend_hash_index_del(tmp->attributes, index); } @@ -2238,9 +2491,10 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT efree(attr->ref); attr->ref = NULL; } + return true; } -static void schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model) +static bool schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model) { switch (model->kind) { case XSD_CONTENT_GROUP_REF: { @@ -2248,14 +2502,18 @@ static void schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model) if (ctx->sdl->groups && (tmp = zend_hash_str_find_ptr(ctx->sdl->groups, model->u.group_ref, strlen(model->u.group_ref))) != NULL) { if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)(uintptr_t)tmp) != NULL) { - soap_error1(E_ERROR, "Parsing Schema: recursive group 'ref' attribute '%s'", model->u.group_ref); + soap_throw_error("Parsing Schema: recursive group 'ref' attribute '%s'", model->u.group_ref); + return false; + } + if (!schema_type_fixup(ctx, tmp)) { + return false; } - schema_type_fixup(ctx, tmp); efree(model->u.group_ref); model->kind = XSD_CONTENT_GROUP; model->u.group = tmp; } else { - soap_error1(E_ERROR, "Parsing Schema: unresolved group 'ref' attribute '%s'", model->u.group_ref); + soap_throw_error("Parsing Schema: unresolved group 'ref' attribute '%s'", model->u.group_ref); + return false; } break; } @@ -2279,16 +2537,19 @@ static void schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model) sdlContentModelPtr tmp; ZEND_HASH_FOREACH_PTR(model->u.content, tmp) { - schema_content_model_fixup(ctx, tmp); + if (!schema_content_model_fixup(ctx, tmp)) { + return false; + } } ZEND_HASH_FOREACH_END(); break; } default: break; } + return true; } -static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) +static bool schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) { sdlTypePtr tmp; sdlAttributePtr attr; @@ -2314,7 +2575,8 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) } else if (strcmp(type->ref, XSD_NAMESPACE ":schema") == 0) { type->encode = get_conversion(XSD_ANYXML); } else { - soap_error1(E_ERROR, "Parsing Schema: unresolved element 'ref' attribute '%s'", type->ref); + soap_throw_error("Parsing Schema: unresolved element 'ref' attribute '%s'", type->ref); + return false; } } efree(type->ref); @@ -2322,11 +2584,13 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) } if (type->elements) { ZEND_HASH_FOREACH_PTR(type->elements, tmp) { - schema_type_fixup(ctx, tmp); + if (!schema_type_fixup(ctx, tmp)) { + return false; + } } ZEND_HASH_FOREACH_END(); } - if (type->model) { - schema_content_model_fixup(ctx, type->model); + if (type->model && !schema_content_model_fixup(ctx, type->model)) { + return false; } if (type->attributes) { HashPosition pos; @@ -2341,23 +2605,24 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) zend_result result = zend_hash_move_forward_ex(type->attributes, &pos); ZEND_ASSERT(result == SUCCESS); } else { - schema_attributegroup_fixup(ctx, attr, type->attributes); + if (!schema_attributegroup_fixup(ctx, attr, type->attributes)) { + return false; + } zend_result result = zend_hash_index_del(type->attributes, index); ZEND_ASSERT(result == SUCCESS); } } } zend_hash_index_del(&ctx->fixupInProgress, (zend_ulong)(uintptr_t)type); + return true; } -void schema_pass2(sdlCtx *ctx) +static bool schema_pass2_fixup(sdlCtx *ctx) { sdlPtr sdl = ctx->sdl; sdlAttributePtr attr; sdlTypePtr type; - zend_hash_init(&ctx->fixupInProgress, 0, NULL, NULL, 0); - if (ctx->attributes) { ZEND_HASH_FOREACH_PTR(ctx->attributes, attr) { schema_attribute_fixup(ctx, attr); @@ -2365,34 +2630,60 @@ void schema_pass2(sdlCtx *ctx) } if (ctx->attributeGroups) { ZEND_HASH_MAP_FOREACH_PTR(ctx->attributeGroups, type) { - schema_type_fixup(ctx, type); + if (!schema_type_fixup(ctx, type)) { + return false; + } } ZEND_HASH_FOREACH_END(); } if (sdl->elements) { ZEND_HASH_MAP_FOREACH_PTR(sdl->elements, type) { - schema_type_fixup(ctx, type); + if (!schema_type_fixup(ctx, type)) { + return false; + } } ZEND_HASH_FOREACH_END(); } if (sdl->groups) { ZEND_HASH_MAP_FOREACH_PTR(sdl->groups, type) { - schema_type_fixup(ctx, type); + if (!schema_type_fixup(ctx, type)) { + return false; + } } ZEND_HASH_FOREACH_END(); } if (sdl->types) { ZEND_HASH_FOREACH_PTR(sdl->types, type) { - schema_type_fixup(ctx, type); + if (!schema_type_fixup(ctx, type)) { + return false; + } } ZEND_HASH_FOREACH_END(); } + return true; +} + +bool schema_pass2(sdlCtx *ctx) +{ + bool result; + + zend_hash_init(&ctx->fixupInProgress, 0, NULL, NULL, 0); + result = schema_pass2_fixup(ctx); + zend_hash_destroy(&ctx->fixupInProgress); + schema_free_ctx(ctx); + + return result; +} + +/* Frees the parts of the context that only load_schema() and schema_pass2() use. */ +void schema_free_ctx(sdlCtx *ctx) +{ if (ctx->attributes) { zend_hash_destroy(ctx->attributes); efree(ctx->attributes); + ctx->attributes = NULL; } if (ctx->attributeGroups) { zend_hash_destroy(ctx->attributeGroups); efree(ctx->attributeGroups); + ctx->attributeGroups = NULL; } - - zend_hash_destroy(&ctx->fixupInProgress); } void delete_model(zval *zv) @@ -2459,6 +2750,10 @@ void delete_type(zval *zv) if (type->fixed) { efree(type->fixed); } + if (type->ref) { + /* Only set when parsing failed before schema_pass2() resolved it */ + efree(type->ref); + } if (type->elements) { zend_hash_destroy(type->elements); efree(type->elements); diff --git a/ext/soap/php_schema.h b/ext/soap/php_schema.h index b92c8a8fb4c4..ec8c155bdf0f 100644 --- a/ext/soap/php_schema.h +++ b/ext/soap/php_schema.h @@ -17,8 +17,9 @@ #ifndef PHP_SCHEMA_H #define PHP_SCHEMA_H -int load_schema(sdlCtx *ctx, xmlNodePtr schema); -void schema_pass2(sdlCtx *ctx); +bool load_schema(sdlCtx *ctx, xmlNodePtr schema); +bool schema_pass2(sdlCtx *ctx); +void schema_free_ctx(sdlCtx *ctx); xmlChar *schema_location_construct_uri(const xmlAttr *attribute); diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index 3ffa67cfb910..e331502b77e9 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -183,6 +183,9 @@ encodePtr get_encoder_ex(sdlPtr sdl, const char *nscat, size_t len) return NULL; } +/* Returns 1 for an element of the WSDL namespace, 0 for an extension element + * that can be ignored, and -1 after throwing a SoapFault for an unknown + * extension element that is marked as required. */ static int is_wsdl_element(xmlNodePtr node) { if (node->ns && strcmp((char*)node->ns->href, WSDL_NAMESPACE) != 0) { @@ -191,7 +194,8 @@ static int is_wsdl_element(xmlNodePtr node) 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); + soap_throw_error("Parsing WSDL: Unknown required WSDL extension '%s'", node->ns->href); + return -1; } return 0; } @@ -292,14 +296,14 @@ void sdl_restore_uri_credentials(sdlCtx *ctx) #define SAFE_STR(a) ((a)?((const char *)a):"") -static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include) +static bool load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include) { sdlPtr tmpsdl = ctx->sdl; xmlDocPtr wsdl; xmlNodePtr root, definitions, trav; if (zend_hash_str_exists(&ctx->docs, struri, strlen(struri))) { - return; + return true; } sdl_set_uri_credentials(ctx, struri); @@ -310,10 +314,11 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include const xmlError *xmlErrorPtr = xmlGetLastError(); if (xmlErrorPtr) { - soap_error2(E_ERROR, "Parsing WSDL: Couldn't load from '%s' : %s", struri, xmlErrorPtr->message); + soap_throw_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); + soap_throw_error("Parsing WSDL: Couldn't load from '%s'", struri); } + return false; } zend_hash_str_add_ptr(&ctx->docs, struri, strlen(struri), wsdl); @@ -324,11 +329,11 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include if (include) { xmlNodePtr schema = get_node_ex(root, "schema", XSD_NAMESPACE); if (schema) { - load_schema(ctx, schema); - return; + return load_schema(ctx, schema); } } - soap_error1(E_ERROR, "Parsing WSDL: Couldn't find in '%s'", struri); + soap_throw_error("Parsing WSDL: Couldn't find in '%s'", struri); + return false; } if (!include) { @@ -340,7 +345,11 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include trav = definitions->children; while (trav != NULL) { - if (!is_wsdl_element(trav)) { + int wsdl_element = is_wsdl_element(trav); + if (wsdl_element < 0) { + return false; + } + if (!wsdl_element) { trav = trav->next; continue; } @@ -350,9 +359,18 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include 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>", SAFE_STR(trav2->name)); + if (!load_schema(ctx, trav2)) { + return false; + } + } else { + wsdl_element = is_wsdl_element(trav2); + if (wsdl_element < 0) { + return false; + } + if (wsdl_element && !node_is_equal(trav2,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + return false; + } } trav2 = trav2->next; } @@ -361,59 +379,67 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, bool include xmlAttrPtr tmp = get_attribute(trav->properties, "location"); if (tmp) { xmlChar *uri = schema_location_construct_uri(tmp); - zend_try { - load_wsdl_ex(this_ptr, (char*)uri, ctx, true); - } zend_catch { - xmlFree(uri); - zend_bailout(); - } zend_end_try(); + bool loaded = load_wsdl_ex(this_ptr, (char*)uri, ctx, true); xmlFree(uri); + if (!loaded) { + return false; + } } } 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: '%s' already defined", name->children->content); + soap_throw_error("Parsing WSDL: '%s' already defined", name->children->content); + return false; } } else { - soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + soap_throw_error("Parsing WSDL: has no name attribute"); + return false; } } 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: '%s' already defined", name->children->content); + soap_throw_error("Parsing WSDL: '%s' already defined", name->children->content); + return false; } } else { - soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + soap_throw_error("Parsing WSDL: has no name attribute"); + return false; } } 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: '%s' already defined", name->children->content); + soap_throw_error("Parsing WSDL: '%s' already defined", name->children->content); + return false; } } else { - soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + soap_throw_error("Parsing WSDL: has no name attribute"); + return false; } } 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: '%s' already defined", name->children->content); + soap_throw_error("Parsing WSDL: '%s' already defined", name->children->content); + return false; } } else { - soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + soap_throw_error("Parsing WSDL: has no name attribute"); + return false; } } else if (!node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + return false; } trav = trav->next; } + return true; } static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xmlNodePtr header, char* wsdl_soap_namespace, int fault) @@ -425,7 +451,8 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml tmp = get_attribute(header->properties, "message"); if (!tmp) { - soap_error0(E_ERROR, "Parsing WSDL: Missing message attribute for
"); + soap_throw_error("Parsing WSDL: Missing message attribute for
"); + return NULL; } ctype = strrchr((char*)tmp->children->content,':'); @@ -435,16 +462,19 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml ++ctype; } if ((message = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype))) == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", tmp->children->content); + soap_throw_error("Parsing WSDL: Missing with name '%s'", tmp->children->content); + return NULL; } tmp = get_attribute(header->properties, "part"); if (!tmp) { - soap_error0(E_ERROR, "Parsing WSDL: Missing part attribute for
"); + soap_throw_error("Parsing WSDL: Missing part attribute for
"); + return NULL; } 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 ", tmp->children->content); + soap_throw_error("Parsing WSDL: Missing part '%s' in ", tmp->children->content); + return NULL; } h = emalloc(sizeof(sdlSoapBindingFunctionHeader)); @@ -471,10 +501,14 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml } 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); + soap_throw_error("Parsing WSDL: Unknown encodingStyle '%s'", tmp->children->content); + delete_header_int(h); + return NULL; } } else { - soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + soap_throw_error("Parsing WSDL: Unspecified encodingStyle"); + delete_header_int(h); + return NULL; } } @@ -504,6 +538,11 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml sdlSoapBindingFunctionHeaderPtr hf = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, 1); smart_str key = {0}; + if (hf == NULL) { + delete_header_int(h); + return NULL; + } + if (h->headerfaults == NULL) { h->headerfaults = emalloc(sizeof(HashTable)); zend_hash_init(h->headerfaults, 0, NULL, delete_header, 0); @@ -519,8 +558,17 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml 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>", SAFE_STR(trav->name)); + } else { + int wsdl_element = is_wsdl_element(trav); + if (wsdl_element < 0) { + delete_header_int(h); + return NULL; + } + if (wsdl_element && !node_is_equal(trav,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + delete_header_int(h); + return NULL; + } } trav = trav->next; } @@ -528,7 +576,7 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml return h; } -static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap_namespace, sdlSoapBindingFunctionBody *binding, HashTable* params) +static bool wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap_namespace, sdlSoapBindingFunctionBody *binding, HashTable* params) { xmlNodePtr trav; @@ -577,7 +625,10 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap } } ZEND_HASH_FOREACH_END(); if (!found) { - soap_error1(E_ERROR, "Parsing WSDL: Missing part '%s' in ", parts); + soap_throw_error("Parsing WSDL: Missing part '%s' in ", parts); + if (end) *end = ' '; + zend_hash_destroy(&ht); + return false; } parts += strlen(parts); if (end) *end = ' '; @@ -594,16 +645,22 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap } else if (strncmp((char*)encodingStyleAttribute->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'", encodingStyleAttribute->children->content); + soap_throw_error("Parsing WSDL: Unknown encodingStyle '%s'", encodingStyleAttribute->children->content); + return false; } } else { - soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + soap_throw_error("Parsing WSDL: Unspecified encodingStyle"); + return false; } } } 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 (h == NULL) { + return false; + } + if (binding->headers == NULL) { binding->headers = emalloc(sizeof(HashTable)); zend_hash_init(binding->headers, 0, NULL, delete_header, 0); @@ -619,11 +676,19 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap 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>", SAFE_STR(trav->name)); + } else { + int wsdl_element = is_wsdl_element(trav); + if (wsdl_element < 0) { + return false; + } + if (wsdl_element && !node_is_equal(trav,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + return false; + } } trav = trav->next; } + return true; } static HashTable* wsdl_message(const sdlCtx *ctx, const xmlChar* message_name) @@ -639,7 +704,8 @@ static HashTable* wsdl_message(const sdlCtx *ctx, const xmlChar* message_name) xmlNodePtr message = zend_hash_str_find_ptr(&ctx->messages, ctype, strlen(ctype)); if (message == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", (const char*)message_name); + soap_throw_error("Parsing WSDL: Missing with name '%s'", (const char*)message_name); + return NULL; } parameters = emalloc(sizeof(HashTable)); @@ -651,25 +717,29 @@ static HashTable* wsdl_message(const sdlCtx *ctx, const 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>", SAFE_STR(trav->name)); + soap_throw_error("Parsing WSDL: Unexpected extensibility element <%s>", SAFE_STR(trav->name)); + goto error; } 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>", SAFE_STR(trav->name)); + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + goto error; } xmlNodePtr 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 '%s'", SAFE_STR(message->name)); + soap_throw_error("Parsing WSDL: No name associated with '%s'", SAFE_STR(message->name)); + goto error; } + param = emalloc(sizeof(sdlParam)); + memset(param,0,sizeof(sdlParam)); + param->order = 0; + param->paramName = estrdup((char*)name->children->content); type = get_attribute(part->properties, "type"); @@ -690,11 +760,22 @@ static HashTable* wsdl_message(const sdlCtx *ctx, const xmlChar* message_name) trav = trav->next; } return parameters; + +error: + zend_hash_destroy(parameters); + efree(parameters); + return NULL; } static sdlPtr load_wsdl(zval *this_ptr, char *struri) { sdlCtx ctx = {0}; + /* Not yet owned by ctx.sdl, freed on error */ + sdlBindingPtr tmpbinding = NULL; + sdlFunctionPtr function = NULL; + sdlFaultPtr f = NULL; + uint32_t n; + zval zv; ctx.sdl = emalloc(sizeof(sdl)); memset(ctx.sdl, 0, sizeof(sdl)); @@ -707,438 +788,498 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) zend_hash_init(&ctx.portTypes, 0, NULL, NULL, 0); zend_hash_init(&ctx.services, 0, NULL, NULL, 0); - zend_try { - load_wsdl_ex(this_ptr, struri, &ctx, false); - schema_pass2(&ctx); + if (!load_wsdl_ex(this_ptr, struri, &ctx, false) || !schema_pass2(&ctx)) { + goto error; + } - uint32_t n = zend_hash_num_elements(&ctx.services); - if (n == 0) { - soap_error0(E_ERROR, "Parsing WSDL: Couldn't bind to service"); - } + n = zend_hash_num_elements(&ctx.services); + if (n == 0) { + soap_throw_error("Parsing WSDL: Couldn't bind to service"); + goto error; + } - zend_hash_internal_pointer_reset(&ctx.services); - for (uint32_t i = 0; i < n; i++) { - xmlNodePtr service, tmp; - xmlNodePtr trav, port; - bool has_soap_port = false; + zend_hash_internal_pointer_reset(&ctx.services); + for (uint32_t i = 0; i < n; i++) { + xmlNodePtr service, tmp; + xmlNodePtr trav, port; + bool has_soap_port = false; - service = tmp = zend_hash_get_current_data_ptr(&ctx.services); + 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; + trav = service->children; + while (trav != NULL) { + xmlAttrPtr type, name, bindingAttr, location; + xmlNodePtr portType, operation; + xmlNodePtr address, binding, trav2; + char *ctype; + char *wsdl_soap_namespace = NULL; + int wsdl_element = is_wsdl_element(trav); - 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>", SAFE_STR(trav->name)); - } + if (wsdl_element < 0) { + goto error; + } + if (!wsdl_element || node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"port")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + goto error; + } - port = trav; + port = trav; - tmpbinding = emalloc(sizeof(sdlBinding)); - memset(tmpbinding, 0, sizeof(sdlBinding)); + 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 "); - } + bindingAttr = get_attribute(port->properties, "binding"); + if (bindingAttr == NULL) { + soap_throw_error("Parsing WSDL: No binding associated with "); + goto error; + } - /* 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>", SAFE_STR(trav2->name)); + /* 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; } - 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 "); + if (trav2 != address) { + wsdl_element = is_wsdl_element(trav2); + if (wsdl_element < 0) { + goto error; + } + if (wsdl_element && !node_is_equal(trav2,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + goto error; } } - has_soap_port = true; - - location = get_attribute(address->properties, "location"); - if (!location) { - soap_error0(E_ERROR, "Parsing WSDL: No location associated with "); + trav2 = trav2->next; + } + if (!address || tmpbinding->bindingType == BINDING_HTTP) { + if (has_soap_port || trav->next || i < n-1) { + efree(tmpbinding); + tmpbinding = NULL; + trav = trav->next; + continue; + } else if (!address) { + soap_throw_error("Parsing WSDL: No address associated with "); + goto error; } + } + has_soap_port = true; - tmpbinding->location = estrdup((char*)location->children->content); + location = get_attribute(address->properties, "location"); + if (!location) { + soap_throw_error("Parsing WSDL: No location associated with "); + goto error; + } - 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 element with name '%s'", ctype); - } - binding = tmp; + tmpbinding->location = estrdup((char*)location->children->content); - if (tmpbinding->bindingType == BINDING_SOAP) { - sdlSoapBindingPtr soapBinding; - xmlNodePtr soapBindingNode; + 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_throw_error("Parsing WSDL: No element with name '%s'", ctype); + goto error; + } + binding = tmp; - soapBinding = emalloc(sizeof(sdlSoapBinding)); - memset(soapBinding, 0, sizeof(sdlSoapBinding)); - soapBinding->style = SOAP_DOCUMENT; + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapBindingNode; - soapBindingNode = get_node_ex(binding->children, "binding", wsdl_soap_namespace); - if (soapBindingNode) { - xmlAttrPtr styleAttribute = get_attribute(soapBindingNode->properties, "style"); - if (styleAttribute && !strncmp((char*)styleAttribute->children->content, "rpc", sizeof("rpc"))) { - soapBinding->style = SOAP_RPC; - } + soapBinding = emalloc(sizeof(sdlSoapBinding)); + memset(soapBinding, 0, sizeof(sdlSoapBinding)); + soapBinding->style = SOAP_DOCUMENT; - xmlAttrPtr transportAttribute = get_attribute(soapBindingNode->properties, "transport"); - if (transportAttribute) { - if (strncmp((char*)transportAttribute->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; - } + soapBindingNode = get_node_ex(binding->children, "binding", wsdl_soap_namespace); + if (soapBindingNode) { + xmlAttrPtr styleAttribute = get_attribute(soapBindingNode->properties, "style"); + if (styleAttribute && !strncmp((char*)styleAttribute->children->content, "rpc", sizeof("rpc"))) { + soapBinding->style = SOAP_RPC; + } + + xmlAttrPtr transportAttribute = get_attribute(soapBindingNode->properties, "transport"); + if (transportAttribute) { + if (strncmp((char*)transportAttribute->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); + tmpbinding = NULL; + trav = trav->next; + continue; } } - tmpbinding->bindingAttributes = (void *)soapBinding; } + tmpbinding->bindingAttributes = (void *)soapBinding; + } - name = get_attribute(binding->properties, "name"); - if (name == NULL) { - soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for "); - } - tmpbinding->name = estrdup((char*)name->children->content); + name = get_attribute(binding->properties, "name"); + if (name == NULL) { + soap_throw_error("Parsing WSDL: Missing 'name' attribute for "); + goto error; + } + 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 "); - } + type = get_attribute(binding->properties, "type"); + if (type == NULL) { + soap_throw_error("Parsing WSDL: Missing 'type' attribute for "); + goto error; + } - ctype = strchr((char*)type->children->content,':'); - if (ctype == NULL) { - ctype = (char*)type->children->content; - } else { - ++ctype; + ctype = strchr((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_throw_error("Parsing WSDL: Missing with name '%s'", name->children->content); + goto error; + } + portType = tmp; + + trav2 = binding->children; + while (trav2 != NULL) { + xmlNodePtr input, output, fault, portTypeOperation, trav3; + xmlAttrPtr op_name, paramOrder; + + if (tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav2, "binding", wsdl_soap_namespace)) { + trav2 = trav2->next; + continue; } - if ((tmp = zend_hash_str_find_ptr(&ctx.portTypes, ctype, strlen(ctype))) == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing with name '%s'", name->children->content); + wsdl_element = is_wsdl_element(trav2); + if (wsdl_element < 0) { + goto error; + } + if (!wsdl_element || node_is_equal(trav2,"documentation")) { + trav2 = trav2->next; + continue; + } + if (!node_is_equal(trav2,"operation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + goto error; } - 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>", SAFE_STR(trav2->name)); - } - operation = trav2; + operation = trav2; - op_name = get_attribute(operation->properties, "name"); - if (op_name == NULL) { - soap_error0(E_ERROR, "Parsing WSDL: Missing 'name' attribute for "); - } + op_name = get_attribute(operation->properties, "name"); + if (op_name == NULL) { + soap_throw_error("Parsing WSDL: Missing 'name' attribute for "); + goto error; + } - 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>", SAFE_STR(trav3->name)); + trav3 = operation->children; + while (trav3 != NULL) { + if (tmpbinding->bindingType == BINDING_SOAP && + node_is_equal_ex(trav3, "operation", wsdl_soap_namespace)) { + } else { + wsdl_element = is_wsdl_element(trav3); + if (wsdl_element < 0) { + goto error; + } + if (wsdl_element && + !node_is_equal(trav3,"input") && + !node_is_equal(trav3,"output") && + !node_is_equal(trav3,"fault") && + !node_is_equal(trav3,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav3->name)); + goto error; } - trav3 = trav3->next; } + 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 / with name '%s'", op_name->children->content); - } + portTypeOperation = get_node_with_attribute_ex(portType->children, "operation", WSDL_NAMESPACE, "name", (char*)op_name->children->content, NULL); + if (portTypeOperation == NULL) { + soap_throw_error("Parsing WSDL: Missing / with name '%s'", op_name->children->content); + goto error; + } - function = emalloc(sizeof(sdlFunction)); - memset(function, 0, sizeof(sdlFunction)); - function->functionName = estrdup((char*)op_name->children->content); + function = emalloc(sizeof(sdlFunction)); + memset(function, 0, sizeof(sdlFunction)); + function->functionName = estrdup((char*)op_name->children->content); + function->binding = tmpbinding; - if (tmpbinding->bindingType == BINDING_SOAP) { - sdlSoapBindingFunctionPtr soapFunctionBinding; - sdlSoapBindingPtr soapBinding; - xmlNodePtr soapOperation; - - 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) { - xmlAttrPtr soapActionAttribute = get_attribute(soapOperation->properties, "soapAction"); - if (soapActionAttribute) { - soapFunctionBinding->soapAction = estrdup((char*)soapActionAttribute->children->content); - } + if (tmpbinding->bindingType == BINDING_SOAP) { + sdlSoapBindingFunctionPtr soapFunctionBinding; + sdlSoapBindingPtr soapBinding; + xmlNodePtr soapOperation; + + 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) { + xmlAttrPtr soapActionAttribute = get_attribute(soapOperation->properties, "soapAction"); + if (soapActionAttribute) { + soapFunctionBinding->soapAction = estrdup((char*)soapActionAttribute->children->content); + } - xmlAttrPtr styleAttribute = get_attribute(soapOperation->properties, "style"); - if (styleAttribute) { - if (!strncmp((char*)styleAttribute->children->content, "rpc", sizeof("rpc"))) { - soapFunctionBinding->style = SOAP_RPC; - } else { - soapFunctionBinding->style = SOAP_DOCUMENT; - } + xmlAttrPtr styleAttribute = get_attribute(soapOperation->properties, "style"); + if (styleAttribute) { + if (!strncmp((char*)styleAttribute->children->content, "rpc", sizeof("rpc"))) { + soapFunctionBinding->style = SOAP_RPC; } else { - soapFunctionBinding->style = soapBinding->style; + 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; + function->bindingAttributes = (void *)soapFunctionBinding; + } - message = get_attribute(input->properties, "message"); - if (message == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); - } - function->requestParameters = wsdl_message(&ctx, message->children->content); + input = get_node_ex(portTypeOperation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + xmlAttrPtr message; + + message = get_attribute(input->properties, "message"); + if (message == NULL) { + soap_throw_error("Parsing WSDL: Missing name for of '%s'", op_name->children->content); + goto error; + } + function->requestParameters = wsdl_message(&ctx, message->children->content); + if (function->requestParameters == NULL) { + goto error; + } /* FIXME - xmlAttrPtr name = get_attribute(input->properties, "name"); - if (name != NULL) { - function->requestName = estrdup(name->children->content); - } else { + xmlAttrPtr name = get_attribute(input->properties, "name"); + if (name != NULL) { + function->requestName = estrdup(name->children->content); + } else { */ - { - function->requestName = estrdup(function->functionName); - } + { + 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); + if (tmpbinding->bindingType == BINDING_SOAP) { + input = get_node_ex(operation->children, "input", WSDL_NAMESPACE); + if (input != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + if (!wsdl_soap_binding_body(&ctx, input, wsdl_soap_namespace, &soapFunctionBinding->input, function->requestParameters)) { + goto error; } } } + } - output = get_node_ex(portTypeOperation->children, "output", WSDL_NAMESPACE); - if (output != NULL) { - xmlAttrPtr message; + 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 of '%s'", op_name->children->content); - } - function->responseParameters = wsdl_message(&ctx, message->children->content); + message = get_attribute(output->properties, "message"); + if (message == NULL) { + soap_throw_error("Parsing WSDL: Missing name for of '%s'", op_name->children->content); + goto error; + } + function->responseParameters = wsdl_message(&ctx, message->children->content); + if (function->responseParameters == NULL) { + goto error; + } /* 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 { + 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 { */ - { - size_t len = strlen(function->functionName); - function->responseName = emalloc(len + sizeof("Response")); - memcpy(function->responseName, function->functionName, len); - memcpy(function->responseName+len, "Response", sizeof("Response")); - } + { + size_t 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); + if (tmpbinding->bindingType == BINDING_SOAP) { + output = get_node_ex(operation->children, "output", WSDL_NAMESPACE); + if (output != NULL) { + sdlSoapBindingFunctionPtr soapFunctionBinding = function->bindingAttributes; + if (!wsdl_soap_binding_body(&ctx, output, wsdl_soap_namespace, &soapFunctionBinding->output, function->responseParameters)) { + goto error; } } } + } - 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; - sdlFaultPtr f; - - xmlAttrPtr faultNameAttribute = get_attribute(fault->properties, "name"); - if (faultNameAttribute == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); - } - message = get_attribute(fault->properties, "message"); - if (message == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: Missing name for of '%s'", op_name->children->content); - } + paramOrder = get_attribute(portTypeOperation->properties, "parameterOrder"); + if (paramOrder) { + /* FIXME: */ + } - f = emalloc(sizeof(sdlFault)); - memset(f, 0, sizeof(sdlFault)); + fault = portTypeOperation->children; + while (fault != NULL) { + if (node_is_equal_ex(fault, "fault", WSDL_NAMESPACE)) { + xmlAttrPtr message; - f->name = estrdup((char*)faultNameAttribute->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); - } + xmlAttrPtr faultNameAttribute = get_attribute(fault->properties, "name"); + if (faultNameAttribute == NULL) { + soap_throw_error("Parsing WSDL: Missing name for of '%s'", op_name->children->content); + goto error; + } + message = get_attribute(fault->properties, "message"); + if (message == NULL) { + soap_throw_error("Parsing WSDL: Missing name for of '%s'", op_name->children->content); + goto error; + } - if (tmpbinding->bindingType == BINDING_SOAP) { - xmlNodePtr soapFault = get_node_with_attribute_ex(operation->children, "fault", WSDL_NAMESPACE, "name", f->name, NULL); - if (soapFault != NULL) { - xmlNodePtr faultNodes = soapFault->children; - while (faultNodes != NULL) { - if (node_is_equal_ex(faultNodes, "fault", wsdl_soap_namespace)) { - sdlSoapBindingFunctionFaultPtr faultBinding; + f = emalloc(sizeof(sdlFault)); + memset(f, 0, sizeof(sdlFault)); - faultBinding = f->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); - memset(f->bindingAttributes, 0, sizeof(sdlSoapBindingFunctionFault)); + f->name = estrdup((char*)faultNameAttribute->children->content); + f->details = wsdl_message(&ctx, message->children->content); + if (f->details == NULL) { + goto error; + } + if (zend_hash_num_elements(f->details) > 1) { + soap_throw_error("Parsing WSDL: The fault message '%s' must have a single part", message->children->content); + goto error; + } - xmlAttrPtr faultUseAttribute = get_attribute(faultNodes->properties, "use"); - if (faultUseAttribute && !strncmp((char*)faultUseAttribute->children->content, "encoded", sizeof("encoded"))) { - faultBinding->use = SOAP_ENCODED; - } else { - faultBinding->use = SOAP_LITERAL; - } + if (tmpbinding->bindingType == BINDING_SOAP) { + xmlNodePtr soapFault = get_node_with_attribute_ex(operation->children, "fault", WSDL_NAMESPACE, "name", f->name, NULL); + if (soapFault != NULL) { + xmlNodePtr faultNodes = soapFault->children; + while (faultNodes != NULL) { + if (node_is_equal_ex(faultNodes, "fault", wsdl_soap_namespace)) { + sdlSoapBindingFunctionFaultPtr faultBinding; + + faultBinding = f->bindingAttributes = emalloc(sizeof(sdlSoapBindingFunctionFault)); + memset(f->bindingAttributes, 0, sizeof(sdlSoapBindingFunctionFault)); + + xmlAttrPtr faultUseAttribute = get_attribute(faultNodes->properties, "use"); + if (faultUseAttribute && !strncmp((char*)faultUseAttribute->children->content, "encoded", sizeof("encoded"))) { + faultBinding->use = SOAP_ENCODED; + } else { + faultBinding->use = SOAP_LITERAL; + } - xmlAttrPtr faultNamespaceAttribute = get_attribute(faultNodes->properties, "namespace"); - if (faultNamespaceAttribute) { - faultBinding->ns = estrdup((char*)faultNamespaceAttribute->children->content); - } + xmlAttrPtr faultNamespaceAttribute = get_attribute(faultNodes->properties, "namespace"); + if (faultNamespaceAttribute) { + faultBinding->ns = estrdup((char*)faultNamespaceAttribute->children->content); + } - if (faultBinding->use == SOAP_ENCODED) { - xmlAttrPtr faultEncodingStyleAttribute = get_attribute(faultNodes->properties, "encodingStyle"); - if (faultEncodingStyleAttribute) { - if (strncmp((char*)faultEncodingStyleAttribute->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { - faultBinding->encodingStyle = SOAP_ENCODING_1_1; - } else if (strncmp((char*)faultEncodingStyleAttribute->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { - faultBinding->encodingStyle = SOAP_ENCODING_1_2; - } else { - soap_error1(E_ERROR, "Parsing WSDL: Unknown encodingStyle '%s'", faultEncodingStyleAttribute->children->content); - } + if (faultBinding->use == SOAP_ENCODED) { + xmlAttrPtr faultEncodingStyleAttribute = get_attribute(faultNodes->properties, "encodingStyle"); + if (faultEncodingStyleAttribute) { + if (strncmp((char*)faultEncodingStyleAttribute->children->content, SOAP_1_1_ENC_NAMESPACE, sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) { + faultBinding->encodingStyle = SOAP_ENCODING_1_1; + } else if (strncmp((char*)faultEncodingStyleAttribute->children->content, SOAP_1_2_ENC_NAMESPACE, sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) { + faultBinding->encodingStyle = SOAP_ENCODING_1_2; } else { - soap_error0(E_ERROR, "Parsing WSDL: Unspecified encodingStyle"); + soap_throw_error("Parsing WSDL: Unknown encodingStyle '%s'", faultEncodingStyleAttribute->children->content); + goto error; } + } else { + soap_throw_error("Parsing WSDL: Unspecified encodingStyle"); + goto error; } - } else if (is_wsdl_element(faultNodes) && !node_is_equal(faultNodes,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(faultNodes->name)); } - faultNodes = faultNodes->next; + } else { + wsdl_element = is_wsdl_element(faultNodes); + if (wsdl_element < 0) { + goto error; + } + if (wsdl_element && !node_is_equal(faultNodes,"documentation")) { + soap_throw_error("Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(faultNodes->name)); + goto error; + } } + faultNodes = faultNodes->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: with name '%s' already defined in '%s'", f->name, op_name->children->content); - } } - fault = fault->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_throw_error("Parsing WSDL: with name '%s' already defined in '%s'", f->name, op_name->children->content); + goto error; + } + f = NULL; } + fault = fault->next; + } - function->binding = tmpbinding; + { + size_t function_name_len = strlen(function->functionName); + char *lc_function_name = zend_str_tolower_dup(function->functionName, function_name_len); - { - size_t function_name_len = strlen(function->functionName); - char *lc_function_name = zend_str_tolower_dup(function->functionName, function_name_len); + if (zend_hash_str_add_ptr(&ctx.sdl->functions, lc_function_name, function_name_len, function) == NULL) { + zend_hash_next_index_insert_ptr(&ctx.sdl->functions, function); + } + efree(lc_function_name); - if (zend_hash_str_add_ptr(&ctx.sdl->functions, lc_function_name, function_name_len, function) == NULL) { - zend_hash_next_index_insert_ptr(&ctx.sdl->functions, function); + 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); } - efree(lc_function_name); - 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); - } - - size_t request_name_len = strlen(function->requestName); - char *lc_request_name = zend_str_tolower_dup(function->requestName, request_name_len); - zend_hash_str_add_ptr(ctx.sdl->requests, lc_request_name, request_name_len, function); - efree(lc_request_name); - } + size_t request_name_len = strlen(function->requestName); + char *lc_request_name = zend_str_tolower_dup(function->requestName, request_name_len); + zend_hash_str_add_ptr(ctx.sdl->requests, lc_request_name, request_name_len, function); + efree(lc_request_name); } - trav2 = trav2->next; - } - - if (!ctx.sdl->bindings) { - ctx.sdl->bindings = emalloc(sizeof(HashTable)); - zend_hash_init(ctx.sdl->bindings, 0, NULL, delete_binding, 0); } + function = NULL; + trav2 = trav2->next; + } - 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; + if (!ctx.sdl->bindings) { + ctx.sdl->bindings = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.sdl->bindings, 0, NULL, delete_binding, 0); } - zend_hash_move_forward(&ctx.services); + 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); + } + tmpbinding = NULL; + trav= trav->next; } - 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_hash_move_forward(&ctx.services); + } - } zend_catch { - /* Avoid persistent memory leak. */ - zend_hash_destroy(&ctx.docs); - zend_bailout(); - } zend_end_try(); + if (ctx.sdl->bindings == NULL || ctx.sdl->bindings->nNumOfElements == 0) { + soap_throw_error("Parsing WSDL: Could not find any usable binding services in WSDL."); + goto error; + } zend_hash_destroy(&ctx.messages); zend_hash_destroy(&ctx.bindings); @@ -1147,6 +1288,29 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) zend_hash_destroy(&ctx.docs); return ctx.sdl; + +error: + if (f) { + ZVAL_PTR(&zv, f); + delete_fault(&zv); + } + if (function) { + ZVAL_PTR(&zv, function); + delete_function(&zv); + } + schema_free_ctx(&ctx); + 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); + /* Functions of the binding being parsed point to it, so delete it after them. */ + delete_sdl(ctx.sdl); + if (tmpbinding) { + ZVAL_PTR(&zv, tmpbinding); + delete_binding(&zv); + } + return NULL; } #define WSDL_CACHE_VERSION 0x11 diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h index d3c273615eb6..aea3ab0a9415 100644 --- a/ext/soap/php_soap.h +++ b/ext/soap/php_soap.h @@ -200,6 +200,8 @@ extern HashTable php_soap_defEncNs, php_soap_defEnc, php_soap_defEncIndex; void add_soap_fault(zval *obj, const char *fault_code, const char *fault_string, zend_string *fault_actor, zval *fault_detail, zend_string *lang); +ZEND_COLD void soap_throw_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2); + #define soap_error0(severity, format) \ php_error(severity, "SOAP-ERROR: " format) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 7266b06f8294..d445da7bf584 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -69,6 +69,7 @@ static void delete_service(soapServicePtr service); static void delete_argv(struct _soap_class *class); static void soap_error_handler(int error_num, zend_string *error_filename, uint32_t error_lineno, zend_string *message); +ZEND_NORETURN static ZEND_COLD void soap_fault_to_error(char *code); #define SOAP_SERVER_BEGIN_CODE() \ bool _old_handler = SOAP_GLOBAL(use_soap_error_handler);\ @@ -1108,6 +1109,9 @@ PHP_METHOD(SoapServer, __construct) if (wsdl) { zend_try { service->sdl = get_sdl(ZEND_THIS, ZSTR_VAL(wsdl), cache_wsdl); + if (service->sdl == NULL) { + soap_fault_to_error("WSDL"); + } } zend_catch { xmlCharEncCloseFunc(service->encoding); service->encoding = NULL; @@ -1990,6 +1994,58 @@ ZEND_NORETURN static void soap_server_fault_en(const char *code, const char *str soap_server_fault(code, string, NULL, NULL, NULL, soap_lang_en); } +/* Throws a SoapFault for an error detected by ext/soap itself, with the same + * "SOAP-ERROR: " message that soap_errorN() produces. + * Unlike soap_errorN(E_ERROR, ...) this does not bail out: the caller must + * release what it owns and report the failure to its own caller. */ +ZEND_COLD void soap_throw_error(const char *format, ...) /* {{{ */ +{ + va_list args; + zend_string *details, *message; + zval fault; + const char *code = SOAP_GLOBAL(error_code); + + va_start(args, format); + details = zend_vstrpprintf(0, format, args); + va_end(args); + message = zend_string_concat2(ZEND_STRL("SOAP-ERROR: "), ZSTR_VAL(details), ZSTR_LEN(details)); + zend_string_release(details); + + if (code == NULL) { + code = "Client"; + } + + if (Z_OBJ(SOAP_GLOBAL(error_object))) { + add_soap_fault_ex_en(&fault, &SOAP_GLOBAL(error_object), code, ZSTR_VAL(message)); + Z_ADDREF(fault); + } else { + ZVAL_NULL(&fault); + set_soap_fault(&fault, NULL, code, ZSTR_VAL(message), NULL, NULL, NULL, soap_lang_en); + } + zend_string_release(message); + zend_throw_exception_object(&fault); +} +/* }}} */ + +/* SoapServer and SoapClient with the "exceptions" option disabled report + * errors as fatal errors. Turns the SoapFault thrown by soap_throw_error() + * into the same fatal error that soap_errorN(E_ERROR, ...) raises. */ +ZEND_NORETURN static ZEND_COLD void soap_fault_to_error(char *code) /* {{{ */ +{ + zval fault; + zend_string *message; + + ZEND_ASSERT(EG(exception) != NULL && instanceof_function(EG(exception)->ce, soap_fault_class_entry)); + ZVAL_OBJ(&fault, EG(exception)); + message = zval_get_string(Z_FAULT_STRING_P(&fault)); + zend_clear_exception(); + + SOAP_GLOBAL(error_code) = code; + php_error(E_ERROR, "%s", ZSTR_VAL(message)); + ZEND_UNREACHABLE(); +} +/* }}} */ + static zend_never_inline ZEND_COLD void soap_real_error_handler(int error_num, zend_string *error_filename, const uint32_t error_lineno, zend_string *message) /* {{{ */ { bool _old_in_compilation; @@ -2345,6 +2401,13 @@ PHP_METHOD(SoapClient, __construct) SOAP_GLOBAL(soap_version) = soap_version; sdl = get_sdl(this_ptr, ZSTR_VAL(wsdl), cache_wsdl); + if (sdl == NULL) { + if (Z_TYPE_P(Z_CLIENT_EXCEPTIONS_P(this_ptr)) == IS_FALSE) { + soap_fault_to_error("WSDL"); + } + SOAP_GLOBAL(soap_version) = old_soap_version; + goto end; + } zval *sdl_zval = Z_CLIENT_SDL_P(this_ptr); if (Z_TYPE_P(sdl_zval) == IS_OBJECT) { @@ -2362,6 +2425,7 @@ PHP_METHOD(SoapClient, __construct) soap_client_object_fetch(Z_OBJ_P(this_ptr))->typemap = soap_create_typemap(sdl, typemap_ht); } +end: ; SOAP_CLIENT_END_CODE(); } /* }}} */ diff --git a/ext/soap/tests/bugs/gh10026.phpt b/ext/soap/tests/bugs/gh10026.phpt new file mode 100644 index 000000000000..cb9e74c0695a --- /dev/null +++ b/ext/soap/tests/bugs/gh10026.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-10026 (Garbage collection stops after exception in SoapClient) +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=0 +--FILE-- +self = $node; + } +} + +$wsdl = __DIR__ . '/gh10026.wsdl'; +file_put_contents($wsdl, ''); + +try { + new SoapClient($wsdl); +} catch (SoapFault $e) { + echo $e->faultcode, ": ", $e->getMessage(), "\n"; +} + +var_dump(gc_status()['protected']); +make_cycles(20000); +gc_collect_cycles(); +var_dump(gc_status()['collected']); +?> +--CLEAN-- + +--EXPECT-- +WSDL: SOAP-ERROR: Parsing WSDL: has no name attribute +bool(false) +int(20000) diff --git a/ext/soap/tests/wsdl_errors_no_bailout.phpt b/ext/soap/tests/wsdl_errors_no_bailout.phpt new file mode 100644 index 000000000000..62ba19e75d71 --- /dev/null +++ b/ext/soap/tests/wsdl_errors_no_bailout.phpt @@ -0,0 +1,176 @@ +--TEST-- +SoapClient reports WSDL and schema errors as SoapFault without a bailout +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=0 +--FILE-- + '{schema}', + 'schema' => '', + 'messages' => '', + 'port_type' => '{pt_extra}', + 'pt_extra' => '', + 'binding' => '{b_extra}{op_extra}{in_body}{out_body}{b_fault}', + 'b_extra' => '', + 'op_extra' => '', + 'in_body' => '', + 'out_body' => '', + 'b_fault' => '', + 'service' => '{port_body}{svc_extra}', + 'port_body' => '', + 'svc_extra' => '', + 'extra' => '', + ]; + $pairs = []; + foreach ($parts as $k => $v) { + $pairs['{' . $k . '}'] = $v; + } + // Placeholders can nest up to three levels deep + $body = '{types}{messages}{port_type}{binding}{service}{extra}'; + for ($i = 0; $i < 4; $i++) { + $body = strtr($body, $pairs); + } + return '' + . '' + . $body . ''; +} + +$dir = __DIR__ . '/wsdl_errors_no_bailout'; +@mkdir($dir); +$helpers = [ + 'not-a-schema.xml' => '', + 'other-tns.xsd' => '', + 'broken-schema.xsd' => '', + 'broken-schema-notns.xsd' => '', + 'broken-import.wsdl' => '', +]; +foreach ($helpers as $name => $content) { + file_put_contents("$dir/$name", $content); +} + +$cases = [ + 'sdl_required_ext_defs' => ['extra' => ''], + 'sdl_required_ext_port' => ['port_body' => ''], + 'sdl_unexpected_in_types' => ['types' => ''], + 'sdl_import_broken' => ['extra' => ''], + 'sdl_import_schema_broken' => ['extra' => ''], + 'sdl_message_dup' => ['messages' => ''], + 'sdl_no_service' => ['service' => ''], + 'sdl_port_no_location' => ['port_body' => ''], + 'sdl_no_usable_binding' => ['binding' => ''], + 'sdl_operation_not_in_porttype' => ['b_extra' => ''], + 'sdl_output_unknown_message' => ['port_type' => ''], + 'sdl_part_no_name_second' => ['messages' => ''], + 'sdl_body_missing_part' => ['in_body' => ''], + 'sdl_body_unknown_encoding' => ['in_body' => ''], + 'sdl_header_unknown_encoding' => ['in_body' => ''], + 'sdl_headerfault_broken' => ['in_body' => ''], + 'sdl_header_required_ext' => ['in_body' => ''], + 'sdl_fault_two_parts' => ['messages' => '', 'pt_extra' => ''], + 'sdl_fault_dup' => ['pt_extra' => ''], + 'sdl_fault_unknown_encoding' => ['pt_extra' => '', 'b_fault' => ''], + 'sdl_fault_required_ext' => ['pt_extra' => '', 'b_fault' => ''], + 'sdl_second_operation_broken' => ['port_type' => '', 'b_extra' => ''], + 'sdl_second_port_broken' => ['svc_extra' => ''], + 'xsd_minoccurs_overflow' => ['schema' => ''], + 'xsd_any_minoccurs' => ['schema' => ''], + 'xsd_restriction_int_overflow' => ['schema' => ''], + 'xsd_enumeration_no_value' => ['schema' => ''], + 'xsd_import_no_schema' => ['schema' => ''], + 'xsd_import_wrong_tns' => ['schema' => ''], + 'xsd_include_wrong_tns' => ['schema' => ''], + 'xsd_import_broken_content' => ['schema' => ''], + 'xsd_list_subtype_broken' => ['schema' => ''], + 'xsd_union_subtype_broken' => ['schema' => ''], + 'xsd_complex_extension_choice_broken' => ['schema' => ''], + 'xsd_group_dup' => ['schema' => ''], + 'xsd_element_dup' => ['schema' => ''], + 'xsd_element_anon_complex_broken' => ['schema' => ''], + 'xsd_attribute_dup_in_type' => ['schema' => ''], + 'xsd_attribute_subtype_broken' => ['schema' => ''], + 'xsd_attributegroup_dup' => ['schema' => ''], + 'xsd_attributegroup_attr_broken' => ['schema' => ''], + 'xsd_recursive_attributegroup' => ['schema' => ''], + 'xsd_recursive_group' => ['schema' => ''], + 'xsd_unresolved_group' => ['schema' => ''], + 'xsd_unresolved_element_deep' => ['schema' => ''], + 'xsd_p_choice_any' => ['schema' => ''], + 'xsd_p_recursive_attributegroup_pair' => ['schema' => ''], + 'xsd_p_group_ref_later_broken_group' => ['schema' => ''], +]; +foreach ($cases as $name => $parts) { + $file = "$dir/$name.wsdl"; + file_put_contents($file, make_wsdl($parts)); + try { + new SoapClient($file); + echo "$name: no error\n"; + } catch (SoapFault $e) { + echo "$name: ", str_replace([$dir, strtr($dir, '\\', '/')], 'DIR', $e->getMessage()), "\n"; + } + unlink($file); +} +var_dump(gc_status()['protected']); +?> +--CLEAN-- + +--EXPECT-- +sdl_required_ext_defs: SOAP-ERROR: Parsing WSDL: Unknown required WSDL extension 'urn:ext' +sdl_required_ext_port: SOAP-ERROR: Parsing WSDL: Unknown required WSDL extension 'urn:ext' +sdl_unexpected_in_types: SOAP-ERROR: Parsing WSDL: Unexpected WSDL element +sdl_import_broken: SOAP-ERROR: Parsing WSDL: has no name attribute +sdl_import_schema_broken: SOAP-ERROR: Parsing Schema: complexType has no 'name' attribute +sdl_message_dup: SOAP-ERROR: Parsing WSDL: 'Req' already defined +sdl_no_service: SOAP-ERROR: Parsing WSDL: Couldn't bind to service +sdl_port_no_location: SOAP-ERROR: Parsing WSDL: No location associated with +sdl_no_usable_binding: SOAP-ERROR: Parsing WSDL: Could not find any usable binding services in WSDL. +sdl_operation_not_in_porttype: SOAP-ERROR: Parsing WSDL: Missing / with name 'nope' +sdl_output_unknown_message: SOAP-ERROR: Parsing WSDL: Missing with name 'tns:Nope' +sdl_part_no_name_second: SOAP-ERROR: Parsing WSDL: No name associated with 'message' +sdl_body_missing_part: SOAP-ERROR: Parsing WSDL: Missing part 'nope' in +sdl_body_unknown_encoding: SOAP-ERROR: Parsing WSDL: Unknown encodingStyle 'urn:nope' +sdl_header_unknown_encoding: SOAP-ERROR: Parsing WSDL: Unknown encodingStyle 'urn:nope' +sdl_headerfault_broken: SOAP-ERROR: Parsing WSDL: Missing with name 'tns:Nope' +sdl_header_required_ext: SOAP-ERROR: Parsing WSDL: Unknown required WSDL extension 'urn:ext' +sdl_fault_two_parts: SOAP-ERROR: Parsing WSDL: The fault message 'tns:F' must have a single part +sdl_fault_dup: SOAP-ERROR: Parsing WSDL: with name 'f' already defined in 'op' +sdl_fault_unknown_encoding: SOAP-ERROR: Parsing WSDL: Unknown encodingStyle 'urn:nope' +sdl_fault_required_ext: SOAP-ERROR: Parsing WSDL: Unknown required WSDL extension 'urn:ext' +sdl_second_operation_broken: SOAP-ERROR: Parsing WSDL: Missing with name 'tns:Nope' +sdl_second_port_broken: SOAP-ERROR: Parsing WSDL: No element with name 'Nope' +xsd_minoccurs_overflow: SOAP-ERROR: Parsing Schema: minOccurs value is out of range +xsd_any_minoccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range +xsd_restriction_int_overflow: SOAP-ERROR: Parsing Schema: maxInclusive value is out of range +xsd_enumeration_no_value: SOAP-ERROR: Parsing Schema: missing restriction value +xsd_import_no_schema: SOAP-ERROR: Parsing Schema: can't import schema from 'DIR/not-a-schema.xml' +xsd_import_wrong_tns: SOAP-ERROR: Parsing Schema: can't import schema from 'DIR/other-tns.xsd', unexpected 'targetNamespace'='urn:other', expected 'urn:i' +xsd_include_wrong_tns: SOAP-ERROR: Parsing Schema: can't include schema from 'DIR/other-tns.xsd', different 'targetNamespace' +xsd_import_broken_content: SOAP-ERROR: Parsing Schema: complexType has no 'name' attribute +xsd_list_subtype_broken: SOAP-ERROR: Parsing Schema: unexpected in simpleType +xsd_union_subtype_broken: SOAP-ERROR: Parsing Schema: unexpected in simpleType +xsd_complex_extension_choice_broken: SOAP-ERROR: Parsing Schema: unexpected in choice +xsd_group_dup: SOAP-ERROR: Parsing Schema: group 'urn:t:G' already defined +xsd_element_dup: SOAP-ERROR: Parsing Schema: element 'urn:t:E' already defined +xsd_element_anon_complex_broken: SOAP-ERROR: Parsing Schema: unexpected in sequence +xsd_attribute_dup_in_type: SOAP-ERROR: Parsing Schema: attribute 'urn:t:a' already defined +xsd_attribute_subtype_broken: SOAP-ERROR: Parsing Schema: unexpected in simpleType +xsd_attributegroup_dup: SOAP-ERROR: Parsing Schema: attributeGroup 'urn:t:AG' already defined +xsd_attributegroup_attr_broken: SOAP-ERROR: Parsing Schema: attribute has no 'name' nor 'ref' attributes +xsd_recursive_attributegroup: SOAP-ERROR: Parsing Schema: recursive attributeGroup 'ref' attribute 'urn:t:AG' +xsd_recursive_group: SOAP-ERROR: Parsing Schema: recursive group 'ref' attribute 'urn:t:G' +xsd_unresolved_group: SOAP-ERROR: Parsing Schema: unresolved group 'ref' attribute 'urn:t:Nope' +xsd_unresolved_element_deep: SOAP-ERROR: Parsing Schema: unresolved element 'ref' attribute 'urn:t:Nope' +xsd_p_choice_any: SOAP-ERROR: Parsing Schema: minOccurs value is out of range +xsd_p_recursive_attributegroup_pair: SOAP-ERROR: Parsing Schema: recursive attributeGroup 'ref' attribute 'urn:t:AG1' +xsd_p_group_ref_later_broken_group: SOAP-ERROR: Parsing Schema: unresolved element 'ref' attribute 'urn:t:Nope' +bool(false) From a3d96332d8329361df00a7a65ac9ddc02a4c93d0 Mon Sep 17 00:00:00 2001 From: Valery Ivashchanka Date: Thu, 15 Oct 2026 23:30:06 +0000 Subject: [PATCH 2/2] ext/soap: report encoding errors without a bailout Errors in encoding a request or decoding a response were raised with soap_errorN(E_ERROR, ...). SoapClient turned the fatal error into a SoapFault and caught the bailout in SOAP_CLIENT_END_CODE(), but zend_bailout() had already called gc_protect(1), so the cycle collector stayed off for the rest of the request (GH-10026). These errors are now reported with soap_encoding_error(). In a call of a SoapClient that uses exceptions it throws the same SoapFault as before (same message, fault code "Client", also stored in __soap_fault) and returns: the encoder returns a placeholder value that its callers can handle, and the call discards the request or the response. Only the first error is thrown; once it is pending, master_to_xml() and master_to_zval() skip the rest of the value, the request is not sent, and decoded values are no longer stored, so a typed property or the shared array of output headers cannot be written to. Skipping the rest also keeps a cycle of references that fails with "Nesting level too deep" from taking exponential time. SoapServer and SoapClient with the "exceptions" option disabled still get the same fatal error as before. get_conversion() can now return NULL after the error, so guess_xml_convert() and get_array_type() handle that instead of recursing or dereferencing it. One case behaves differently: if a callback (for example a typemap) has already thrown an exception when the error happens, that exception becomes the previous exception of the SoapFault. Before, the fatal error printed it as an uncaught exception warning and discarded it. Co-Authored-By: Claude Opus 5.5 --- NEWS | 3 + ext/soap/php_encoding.c | 153 ++++++++----- ext/soap/php_packet_soap.c | 24 ++- ext/soap/php_soap.h | 28 +++ ext/soap/soap.c | 31 ++- ext/soap/tests/bugs/gh10026_encoding.phpt | 62 ++++++ ext/soap/tests/encoding_errors_nesting.phpt | 43 ++++ .../tests/encoding_errors_no_bailout.phpt | 201 ++++++++++++++++++ 8 files changed, 489 insertions(+), 56 deletions(-) create mode 100644 ext/soap/tests/bugs/gh10026_encoding.phpt create mode 100644 ext/soap/tests/encoding_errors_nesting.phpt create mode 100644 ext/soap/tests/encoding_errors_no_bailout.phpt diff --git a/NEWS b/NEWS index 7eeb4b54f1dd..8ab2ddfec235 100644 --- a/NEWS +++ b/NEWS @@ -106,6 +106,9 @@ PHP NEWS . SoapClient no longer bails out on WSDL and XML schema errors, which kept the cycle collector disabled for the rest of the request (GH-10026). (Valery Ivashchanka) + . SoapClient no longer bails out on errors in encoding a request or decoding + a response, which kept the cycle collector disabled for the rest of the + request (GH-10026). (Valery Ivashchanka) - SPL: . Fixed bug GH-23385 (SplDoublyLinkedList::serialize() use-after-free when diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 5d52ba7d7798..9884361c8f6e 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -366,6 +366,13 @@ static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xml xmlNodePtr node = NULL; int add_type = 0; + /* The request is discarded, skip the rest of it */ + if (SOAP_ENCODING_ERROR_THROWN()) { + node = xmlNewDocNode(parent->doc, NULL, BAD_CAST("BOGUS"), NULL); + xmlAddChild(parent, node); + return node; + } + if (data) { ZVAL_DEREF(data); } @@ -376,7 +383,10 @@ static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xml zval *ztype = Z_VAR_ENC_TYPE_P(data); if (Z_TYPE_P(ztype) != IS_LONG) { - soap_error0(E_ERROR, "Encoding: SoapVar has no 'enc_type' property"); + soap_encoding_error("Encoding: SoapVar has no 'enc_type' property"); + node = xmlNewDocNode(parent->doc, NULL, BAD_CAST("BOGUS"), NULL); + xmlAddChild(parent, node); + return node; } zval *zstype = Z_VAR_ENC_STYPE_P(data); @@ -497,6 +507,13 @@ xmlNodePtr master_to_xml(encodePtr encode, zval *data, int style, xmlNodePtr par static zval *master_to_zval_int(zval *ret, encodePtr encode, xmlNodePtr data) { + /* The response is discarded, skip the rest of it. Going on could take + * exponential time on references that fail with "Nesting level too deep". */ + if (SOAP_ENCODING_ERROR_THROWN()) { + ZVAL_NULL(ret); + return ret; + } + if (SOAP_GLOBAL(typemap)) { if (encode->details.type_str) { smart_str nscat = {0}; @@ -540,7 +557,9 @@ static zval *master_to_zval_int(zval *ret, encodePtr encode, xmlNodePtr data) } if (encode->to_zval) { if (SOAP_GLOBAL(decode_depth) >= SOAP_MAX_DECODE_DEPTH) { - soap_error0(E_ERROR, "Encoding: Nesting level too deep"); + soap_encoding_error("Encoding: Nesting level too deep"); + ZVAL_NULL(ret); + return ret; } SOAP_GLOBAL(decode_depth)++; ret = encode->to_zval(ret, &encode->details, data); @@ -592,7 +611,7 @@ xmlNodePtr to_xml_user(encodeTypePtr type, zval *data, int style, xmlNodePtr par ZVAL_NULL(&return_value); if (call_user_function(NULL, NULL, &type->map->to_xml, &return_value, 1, data) == FAILURE) { - soap_error0(E_ERROR, "Encoding: Error calling to_xml callback"); + soap_encoding_error("Encoding: Error calling to_xml callback"); } if (Z_TYPE(return_value) == IS_STRING) { xmlDocPtr doc = soap_xmlParseMemory(Z_STRVAL(return_value), Z_STRLEN(return_value)); @@ -629,7 +648,8 @@ zval *to_zval_user(zval *ret, encodeTypePtr type, xmlNodePtr node) xmlFreeNode(copy); if (call_user_function(NULL, NULL, &type->map->to_zval, ret, 1, &data) == FAILURE) { - soap_error0(E_ERROR, "Encoding: Error calling from_xml callback"); + soap_encoding_error("Encoding: Error calling from_xml callback"); + ZVAL_NULL(ret); } else if (EG(exception)) { ZVAL_NULL(ret); } @@ -666,7 +686,7 @@ static zval *to_zval_string(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) { ZVAL_STRING(ret, (char*)data->children->content); } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_EMPTY_STRING(ret); @@ -699,7 +719,7 @@ static zval *to_zval_stringr(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) { ZVAL_STRING(ret, (char*)data->children->content); } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_EMPTY_STRING(ret); @@ -732,7 +752,7 @@ static zval *to_zval_stringc(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) { ZVAL_STRING(ret, (char*)data->children->content); } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_EMPTY_STRING(ret); @@ -751,17 +771,19 @@ static zval *to_zval_base64(zval *ret, encodeTypePtr type, xmlNodePtr data) whiteSpace_collapse(data->children->content); str = php_base64_decode(data->children->content, strlen((char*)data->children->content)); if (!str) { - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); + return ret; } ZVAL_STR(ret, str); } else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) { str = php_base64_decode(data->children->content, strlen((char*)data->children->content)); if (!str) { - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); + return ret; } ZVAL_STR(ret, str); } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_EMPTY_STRING(ret); @@ -782,12 +804,12 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data) if (data->children->type == XML_TEXT_NODE && data->children->next == NULL) { whiteSpace_collapse(data->children->content); } else if (data->children->type != XML_CDATA_SECTION_NODE || data->children->next != NULL) { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); return ret; } content_len = strlen((char*) data->children->content); if (content_len % 2 != 0) { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain an even number of hexadecimal digits", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain an even number of hexadecimal digits", soap_type_name(type)); return ret; } str = zend_string_alloc(content_len / 2, 0); @@ -800,7 +822,9 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (c >= 'A' && c <= 'F') { ZSTR_VAL(str)[i] = (c - 'A' + 10) << 4; } else { - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); + zend_string_efree(str); + return ret; } c = data->children->content[j++]; if (c >= '0' && c <= '9') { @@ -810,7 +834,9 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (c >= 'A' && c <= 'F') { ZSTR_VAL(str)[i] |= c - 'A' + 10; } else { - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); + zend_string_efree(str); + return ret; } } ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0'; @@ -929,7 +955,8 @@ static xmlNodePtr to_xml_string(encodeTypePtr type, zval *data, int style, xmlNo err[i++] = 0; } - soap_error1(E_ERROR, "Encoding: string '%s' is not a valid utf-8 string", err); + soap_encoding_error("Encoding: string '%s' is not a valid utf-8 string", err); + efree(err); } text = xmlNewTextLen(BAD_CAST(str), new_len); @@ -1031,11 +1058,11 @@ static zval *to_zval_double(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (strncasecmp((char*)data->children->content, "-INF", sizeof("-INF")-1) == 0) { ZVAL_DOUBLE(ret, -php_get_inf()); } else { - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); } } } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_NULL(ret); @@ -1064,10 +1091,10 @@ static zval *to_zval_long(zval *ret, encodeTypePtr type, xmlNodePtr data) ZVAL_DOUBLE(ret, dval); break; default: - soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type)); + soap_encoding_error("Encoding: Invalid value for type '%s'", soap_type_name(type)); } } else { - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); } } else { ZVAL_NULL(ret); @@ -1132,8 +1159,9 @@ static zval *to_zval_bool(zval *ret, encodeTypePtr type, xmlNodePtr data) return ret; } if (data->children->type != XML_TEXT_NODE || data->children->next != NULL) { - // TODO Convert to exception? - soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + soap_encoding_error("Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type)); + ZVAL_NULL(ret); + return ret; } whiteSpace_collapse(data->children->content); @@ -1191,6 +1219,11 @@ static xmlNodePtr to_xml_null(encodeTypePtr type, zval *data, int style, xmlNode static void set_zval_property(zval* object, char* name, zval* val) { + if (SOAP_ENCODING_ERROR_THROWN()) { + /* The object is discarded; a typed property could throw again and hide the error */ + zval_ptr_dtor(val); + return; + } zend_update_property(Z_OBJCE_P(object), Z_OBJ_P(object), name, strlen(name), val); Z_TRY_DELREF_P(val); } @@ -1315,7 +1348,7 @@ static void model_to_zval_object(zval *ret, sdlContentModelPtr model, xmlNodePtr r_node = check_and_resolve_href(node); if (r_node && r_node->children && r_node->children->content) { if (model->u.element->fixed && strcmp(model->u.element->fixed, (char*)r_node->children->content) != 0) { - soap_error3(E_ERROR, "Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, r_node->children->content); + soap_encoding_error("Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, r_node->children->content); } master_to_zval(&val, model->u.element->encode, r_node); } else if (model->u.element->fixed) { @@ -1340,7 +1373,7 @@ static void model_to_zval_object(zval *ret, sdlContentModelPtr model, xmlNodePtr ZVAL_NULL(&val); if (node && node->children && node->children->content) { if (model->u.element->fixed && strcmp(model->u.element->fixed, (char*)node->children->content) != 0) { - soap_error3(E_ERROR, "Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, node->children->content); + soap_encoding_error("Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, node->children->content); } master_to_zval(&val, model->u.element->encode, node); } else if (model->u.element->fixed) { @@ -1543,7 +1576,7 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z if (val && val->children && val->children->content) { str_val = (char*)val->children->content; if (attr->fixed && strcmp(attr->fixed, str_val) != 0) { - soap_error3(E_ERROR, "Encoding: Attribute '%s' has fixed value '%s' (value '%s' is not allowed)", attr->name, attr->fixed, str_val); + soap_encoding_error("Encoding: Attribute '%s' has fixed value '%s' (value '%s' is not allowed)", attr->name, attr->fixed, str_val); } } else if (attr->fixed) { str_val = attr->fixed; @@ -1666,7 +1699,7 @@ static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval * property = master_to_xml(enc, val, style, node); if (property->children && property->children->content && model->u.element->fixed && strcmp(model->u.element->fixed, (char*)property->children->content) != 0) { - soap_error3(E_ERROR, "Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, property->children->content); + soap_encoding_error("Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, property->children->content); } } xmlNodeSetName(property, BAD_CAST(model->u.element->name)); @@ -1688,7 +1721,7 @@ static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval * property = master_to_xml(enc, data, style, node); if (property->children && property->children->content && model->u.element->fixed && strcmp(model->u.element->fixed, (char*)property->children->content) != 0) { - soap_error3(E_ERROR, "Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, property->children->content); + soap_encoding_error("Encoding: Element '%s' has fixed value '%s' (value '%s' is not allowed)", model->u.element->name, model->u.element->fixed, property->children->content); } } xmlNodeSetName(property, BAD_CAST(model->u.element->name)); @@ -1715,7 +1748,7 @@ static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval * return 2; } else { if (strict) { - soap_error1(E_ERROR, "Encoding: object has no '%s' property", model->u.element->name); + soap_encoding_error("Encoding: object has no '%s' property", model->u.element->name); } return 0; } @@ -1746,7 +1779,7 @@ static int model_to_xml_object(xmlNodePtr node, sdlContentModelPtr model, zval * return 2; } else { if (strict) { - soap_error0(E_ERROR, "Encoding: object has no 'any' property"); + soap_encoding_error("Encoding: object has no 'any' property"); } return 0; } @@ -1951,7 +1984,7 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo dummy = master_to_xml(attr->encode, zattr, SOAP_LITERAL, xmlParam); if (dummy->children && dummy->children->content) { if (attr->fixed && strcmp(attr->fixed, (char*)dummy->children->content) != 0) { - soap_error3(E_ERROR, "Encoding: Attribute '%s' has fixed value '%s' (value '%s' is not allowed)", attr->name, attr->fixed, dummy->children->content); + soap_encoding_error("Encoding: Attribute '%s' has fixed value '%s' (value '%s' is not allowed)", attr->name, attr->fixed, dummy->children->content); } /* we need to handle xml: namespace specially, since it is an implicit schema. Otherwise, use form. @@ -2053,7 +2086,7 @@ static int calc_dimension_12(const char* str) flag = 1; } } else if (*str == '*') { - soap_error0(E_ERROR, "Encoding: '*' may only be first arraySize value in list"); + soap_encoding_error("Encoding: '*' may only be first arraySize value in list"); } else { flag = 0; } @@ -2065,7 +2098,8 @@ static int calc_dimension_12(const char* str) static void soap_array_position_add_digit(int *position, int digit) { if (UNEXPECTED(*position > (INT_MAX - digit) / 10)) { - soap_error0(E_ERROR, "Encoding: array index out of range"); + soap_encoding_error("Encoding: array index out of range"); + return; } *position = (*position * 10) + digit; @@ -2093,7 +2127,7 @@ static int* get_position_12(int dimension, const char* str) } soap_array_position_add_digit(&pos[i], *str - '0'); } else if (*str == '*') { - soap_error0(E_ERROR, "Encoding: '*' may only be first arraySize value in list"); + soap_encoding_error("Encoding: '*' may only be first arraySize value in list"); } else { flag = 0; } @@ -2715,8 +2749,9 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data) efree(dims); efree(pos); zval_ptr_dtor(ret); - ZVAL_UNDEF(ret); - soap_error0(E_ERROR, "Encoding: array index out of range"); + ZVAL_NULL(ret); + soap_encoding_error("Encoding: array index out of range"); + return ret; } pos[i]++; if (pos[i] < dims[i]) { @@ -2809,12 +2844,14 @@ static zval *to_zval_map(zval *ret, encodeTypePtr type, xmlNodePtr data) FOREACHNODE(trav, "item", item) { xmlKey = get_node(item->children, "key"); if (!xmlKey) { - soap_error0(E_ERROR, "Encoding: Can't decode apache map, missing key"); + soap_encoding_error("Encoding: Can't decode apache map, missing key"); + continue; } xmlValue = get_node(item->children, "value"); if (!xmlValue) { - soap_error0(E_ERROR, "Encoding: Can't decode apache map, missing value"); + soap_encoding_error("Encoding: Can't decode apache map, missing value"); + continue; } ZVAL_NULL(&key); @@ -2827,7 +2864,8 @@ static zval *to_zval_map(zval *ret, encodeTypePtr type, xmlNodePtr data) } else if (Z_TYPE(key) == IS_LONG) { zend_hash_index_update(Z_ARRVAL_P(ret), Z_LVAL(key), &value); } else { - soap_error0(E_ERROR, "Encoding: Can't decode apache map, only Strings or Longs are allowed as keys"); + soap_encoding_error("Encoding: Can't decode apache map, only Strings or Longs are allowed as keys"); + zval_ptr_dtor(&value); } zval_ptr_dtor(&key); } @@ -2849,6 +2887,12 @@ static xmlNodePtr guess_xml_convert(encodeTypePtr type, zval *data, int style, x } else { enc = get_conversion(IS_NULL); } + if (!enc) { + /* Not an encodable type, the error has been thrown */ + ret = xmlNewDocNode(parent->doc, NULL, BAD_CAST("BOGUS"), NULL); + xmlAddChild(parent, ret); + return ret; + } ret = master_to_xml_int(enc, data, style, parent, 0); /* if (style == SOAP_LITERAL && SOAP_GLOBAL(sdl)) { @@ -2959,7 +3003,8 @@ static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *forma ta = php_localtime_r(×tamp, &tmbuf); /*ta = php_gmtime_r(×tamp, &tmbuf);*/ if (!ta) { - soap_error1(E_ERROR, "Encoding: Invalid timestamp " ZEND_LONG_FMT, Z_LVAL_P(data)); + soap_encoding_error("Encoding: Invalid timestamp " ZEND_LONG_FMT, Z_LVAL_P(data)); + return xmlParam; } buf = (char *) emalloc(buf_len); @@ -3004,7 +3049,7 @@ static xmlNodePtr to_xml_datetime_ex(encodeTypePtr type, zval *data, char *forma xmlNodeSetContentLen(xmlParam, BAD_CAST(ZSTR_VAL(formatted_date_string)), ZSTR_LEN(formatted_date_string)); zend_string_release_ex(formatted_date_string, false); } else { - soap_error0(E_ERROR, "Encoding: Invalid DateTimeInterface"); + soap_encoding_error("Encoding: Invalid DateTimeInterface"); } } } @@ -3104,7 +3149,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP } smart_str_appends(&list, (char*)dummy->children->content); } else { - soap_error2(E_ERROR, + soap_encoding_error( "Encoding: Failed to encode list item of type '%s' for list type '%s'", soap_type_name(&list_enc->details), soap_type_name(enc)); } @@ -3148,7 +3193,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP } smart_str_appends(&list, (char*)dummy->children->content); } else { - soap_error2(E_ERROR, + soap_encoding_error( "Encoding: Failed to encode list item of type '%s' for list type '%s'", soap_type_name(&list_enc->details), soap_type_name(enc)); } @@ -3315,7 +3360,7 @@ zval *sdl_guess_convert_zval(zval *ret, encodeTypePtr enc, xmlNodePtr data) } return to_zval_object(ret, enc, data); default: - soap_error0(E_ERROR, "Encoding: Internal Error"); + soap_encoding_error("Encoding: Internal Error"); return guess_zval_convert(ret, enc, data); } } @@ -3383,7 +3428,9 @@ xmlNodePtr sdl_guess_convert_xml(encodeTypePtr enc, zval *data, int style, xmlNo } break; default: - soap_error0(E_ERROR, "Encoding: Internal Error"); + soap_encoding_error("Encoding: Internal Error"); + ret = xmlNewDocNode(parent->doc, NULL, BAD_CAST("BOGUS"), NULL); + xmlAddChild(parent, ret); break; } if (style == SOAP_ENCODED) { @@ -3401,12 +3448,13 @@ static xmlNodePtr check_and_resolve_href(xmlNodePtr data) if (href->children->content[0] == '#') { xmlNodePtr ret = get_node_with_attribute_recursive(data->doc->children, NULL, "id", (char*)&href->children->content[1]); if (!ret) { - soap_error1(E_ERROR, "Encoding: Unresolved reference '%s'", href->children->content); + soap_encoding_error("Encoding: Unresolved reference '%s'", href->children->content); + return data; } return ret; } else { /* TODO: External href....? */ - soap_error1(E_ERROR, "Encoding: External reference '%s'", href->children->content); + soap_encoding_error("Encoding: External reference '%s'", href->children->content); } } /* SOAP 1.2 enc:id enc:ref */ @@ -3422,9 +3470,10 @@ static xmlNodePtr check_and_resolve_href(xmlNodePtr data) } ret = get_node_with_attribute_recursive_ex(data->doc->children, NULL, NULL, "id", (char*)id, SOAP_1_2_ENC_NAMESPACE); if (!ret) { - soap_error1(E_ERROR, "Encoding: Unresolved reference '%s'", href->children->content); + soap_encoding_error("Encoding: Unresolved reference '%s'", href->children->content); + return data; } else if (ret == data) { - soap_error1(E_ERROR, "Encoding: Violation of id and ref information items '%s'", href->children->content); + soap_encoding_error("Encoding: Violation of id and ref information items '%s'", href->children->content); } return ret; } @@ -3575,7 +3624,7 @@ encodePtr get_conversion(int encode) encodePtr enc; if ((enc = zend_hash_index_find_ptr(&php_soap_defEncIndex, encode)) == NULL) { - soap_error0(E_ERROR, "Encoding: Cannot find encoding"); + soap_encoding_error("Encoding: Cannot find encoding"); return NULL; } else { return enc; @@ -3605,7 +3654,9 @@ static encodePtr get_array_type(xmlNodePtr node, zval *array, smart_str *type) Z_OBJCE_P(tmp) == soap_var_class_entry) { zval *ztype = Z_VAR_ENC_TYPE_P(tmp); if (Z_TYPE_P(ztype) != IS_LONG) { - soap_error0(E_ERROR, "Encoding: SoapVar has no 'enc_type' property"); + soap_encoding_error("Encoding: SoapVar has no 'enc_type' property"); + different = TRUE; + break; } cur_type = Z_LVAL_P(ztype); if (cur_type == UNKNOWN_TYPE) { @@ -3682,6 +3733,10 @@ static encodePtr get_array_type(xmlNodePtr node, zval *array, smart_str *type) return enc; } else { enc = get_conversion(cur_type); + if (!enc) { + smart_str_appendl(type, "xsd:anyType", sizeof("xsd:anyType")-1); + return get_conversion(XSD_ANYTYPE); + } get_type_str(node, enc->details.ns, enc->details.type_str, type); return enc; } diff --git a/ext/soap/php_packet_soap.c b/ext/soap/php_packet_soap.c index 29ed1294f364..70afc763cf91 100644 --- a/ext/soap/php_packet_soap.c +++ b/ext/soap/php_packet_soap.c @@ -261,7 +261,15 @@ bool parse_packet_soap(zval *this_ptr, zend_string *buffer, sdlFunctionPtr fn, z master_to_zval_with_doc_cleanup(&details, NULL, tmp, response); } } - add_soap_fault(this_ptr, faultcode, faultstring ? ZSTR_VAL(faultstring) : NULL, faultactor, &details, lang); + if (SOAP_ENCODING_ERROR_THROWN()) { + /* Decoding the fault failed, the client reports that error instead */ + zval_ptr_dtor(&details); + } else { + add_soap_fault(this_ptr, faultcode, faultstring ? ZSTR_VAL(faultstring) : NULL, faultactor, &details, lang); + if (Z_REFCOUNTED(details)) { + Z_DELREF(details); + } + } if (faultstring) { zend_string_release_ex(faultstring, 0); } @@ -269,9 +277,6 @@ bool parse_packet_soap(zval *this_ptr, zend_string *buffer, sdlFunctionPtr fn, z zend_string_release_ex(faultactor, 0); } zend_string_release_ex(lang, false); - if (Z_REFCOUNTED(details)) { - Z_DELREF(details); - } xmlFreeDoc(response); return false; } @@ -442,12 +447,23 @@ bool parse_packet_soap(zval *this_ptr, zend_string *buffer, sdlFunctionPtr fn, z smart_str_free(&key); } master_to_zval_with_doc_cleanup(&val, enc, trav, response); + if (SOAP_ENCODING_ERROR_THROWN()) { + /* The trace of the SoapFault shares the array of output headers */ + zval_ptr_dtor(&val); + break; + } add_assoc_zval(soap_headers, (const char*)trav->name, &val); } trav = trav->next; } } + if (SOAP_ENCODING_ERROR_THROWN()) { + /* The result is incomplete */ + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + } + xmlFreeDoc(response); return true; } diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h index aea3ab0a9415..2809c14f73bd 100644 --- a/ext/soap/php_soap.h +++ b/ext/soap/php_soap.h @@ -202,6 +202,34 @@ void add_soap_fault(zval *obj, const char *fault_code, const char *fault_string, ZEND_COLD void soap_throw_error(const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 1, 2); +typedef enum { + SOAP_ENCODING_ERROR_FATAL, + SOAP_ENCODING_ERROR_THROW, + SOAP_ENCODING_ERROR_IGNORE, +} soap_encoding_error_mode; + +soap_encoding_error_mode soap_get_encoding_error_mode(void); + +/* Reports an error in encoding or decoding a value. It is a fatal error, as with soap_errorN(E_ERROR, ...), except in a + * call of a SoapClient that uses exceptions: there it is thrown as a SoapFault and does not bail out, so the encoder + * must return a value its callers can handle. The call discards the result once encoding is done. */ +#define soap_encoding_error(...) do { \ + switch (soap_get_encoding_error_mode()) { \ + case SOAP_ENCODING_ERROR_FATAL: \ + php_error(E_ERROR, "SOAP-ERROR: " __VA_ARGS__); \ + break; \ + case SOAP_ENCODING_ERROR_THROW: \ + soap_throw_error(__VA_ARGS__); \ + break; \ + case SOAP_ENCODING_ERROR_IGNORE: \ + break; \ + } \ + } while (0) + +/* An encoding error has been thrown as a SoapFault: the rest of the call is skipped */ +#define SOAP_ENCODING_ERROR_THROWN() \ + (UNEXPECTED(EG(exception)) && soap_get_encoding_error_mode() == SOAP_ENCODING_ERROR_IGNORE) + #define soap_error0(severity, format) \ php_error(severity, "SOAP-ERROR: " format) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index d445da7bf584..c087b2c96cc6 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -2027,6 +2027,24 @@ ZEND_COLD void soap_throw_error(const char *format, ...) /* {{{ */ } /* }}} */ +/* Encoding errors are thrown only where a fatal error used to become a SoapFault: in a call of a SoapClient that uses + * exceptions. Only the first one is thrown, the encoding that is still running after it reports nothing. */ +soap_encoding_error_mode soap_get_encoding_error_mode(void) /* {{{ */ +{ + zval *client = &SOAP_GLOBAL(error_object); + + if (!Z_OBJ_P(client) || + !instanceof_function(Z_OBJCE_P(client), soap_class_entry) || + Z_TYPE_P(Z_CLIENT_EXCEPTIONS_P(client)) == IS_FALSE) { + return SOAP_ENCODING_ERROR_FATAL; + } + if (EG(exception) && instanceof_function(EG(exception)->ce, soap_fault_class_entry)) { + return SOAP_ENCODING_ERROR_IGNORE; + } + return SOAP_ENCODING_ERROR_THROW; +} +/* }}} */ + /* SoapServer and SoapClient with the "exceptions" option disabled report * errors as fatal errors. Turns the SoapFault thrown by soap_throw_error() * into the same fatal error that soap_errorN(E_ERROR, ...) raises. */ @@ -2587,6 +2605,7 @@ static void do_soap_call(zend_execute_data *execute_data, SOAP_GLOBAL(features) = 0; } + ZVAL_NULL(&response); zend_try { if (sdl != NULL) { fn = get_function(sdl, ZSTR_VAL(function), ZSTR_LEN(function)); @@ -2610,10 +2629,14 @@ static void do_soap_call(zend_execute_data *execute_data, if (binding->bindingType == BINDING_SOAP) { sdlSoapBindingFunctionPtr fnb = (sdlSoapBindingFunctionPtr)fn->bindingAttributes; request = serialize_function_call(this_ptr, fn, NULL, fnb->input.ns, real_args, arg_count, soap_version, soap_headers); - ret = do_request(this_ptr, request, location_c_str, fnb->soapAction, soap_version, one_way, &response); + if (!SOAP_ENCODING_ERROR_THROWN()) { + ret = do_request(this_ptr, request, location_c_str, fnb->soapAction, soap_version, one_way, &response); + } } else { request = serialize_function_call(this_ptr, fn, NULL, sdl->target_ns, real_args, arg_count, soap_version, soap_headers); - ret = do_request(this_ptr, request, location_c_str, NULL, soap_version, one_way, &response); + if (!SOAP_ENCODING_ERROR_THROWN()) { + ret = do_request(this_ptr, request, location_c_str, NULL, soap_version, one_way, &response); + } } xmlFreeDoc(request); @@ -2667,7 +2690,9 @@ static void do_soap_call(zend_execute_data *execute_data, } smart_str_0(&action); - ret = do_request(this_ptr, request, ZSTR_VAL(location), ZSTR_VAL(action.s), soap_version, 0, &response); + if (!SOAP_ENCODING_ERROR_THROWN()) { + ret = do_request(this_ptr, request, ZSTR_VAL(location), ZSTR_VAL(action.s), soap_version, 0, &response); + } smart_str_free(&action); xmlFreeDoc(request); diff --git a/ext/soap/tests/bugs/gh10026_encoding.phpt b/ext/soap/tests/bugs/gh10026_encoding.phpt new file mode 100644 index 000000000000..8df03808dd51 --- /dev/null +++ b/ext/soap/tests/bugs/gh10026_encoding.phpt @@ -0,0 +1,62 @@ +--TEST-- +GH-10026 (Garbage collection stops after exception in SoapClient): encoding errors +--EXTENSIONS-- +soap +--FILE-- +self = $node; + } +} + +class TestSoapClient extends SoapClient { + public function __doRequest($request, $location, $action, $version, $oneWay = false, ?string $uriParserClass = null): string { + return <<<'XML' + + + + + not a number + + + +XML; + } +} + +$client = new TestSoapClient(null, ['location' => 'test://', 'uri' => 'urn:test']); + +// Encoding the request fails +try { + $client->test("\xff"); +} catch (SoapFault $e) { + echo $e->faultcode, ": ", $e->getMessage(), "\n"; +} +var_dump(gc_status()['protected']); + +// Decoding the response fails +try { + $client->test("ok"); +} catch (SoapFault $e) { + echo $e->faultcode, ": ", $e->getMessage(), "\n"; +} +var_dump(gc_status()['protected']); + +make_cycles(20000); +gc_collect_cycles(); +var_dump(gc_status()['collected']); +?> +--EXPECT-- +Client: SOAP-ERROR: Encoding: string '\xff...' is not a valid utf-8 string +bool(false) +Client: SOAP-ERROR: Encoding: Invalid value for type 'int' +bool(false) +int(20000) diff --git a/ext/soap/tests/encoding_errors_nesting.phpt b/ext/soap/tests/encoding_errors_nesting.phpt new file mode 100644 index 000000000000..7c92cf31461f --- /dev/null +++ b/ext/soap/tests/encoding_errors_nesting.phpt @@ -0,0 +1,43 @@ +--TEST-- +SoapClient reports "Nesting level too deep" as SoapFault without a bailout +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- +response; + } +} + +$client = new TestSoapClient(null, ['location' => 'test://', 'uri' => 'urn:test']); + +// An array that contains itself 1 and 8 times: decoding stops at the first error +foreach ([1, 8] as $n) { + $client->response = '' + . '' + . '' + . str_repeat('', $n) + . ''; + try { + $client->test(); + } catch (SoapFault $e) { + echo "$n: $e->faultcode: $e->faultstring\n"; + } +} +var_dump(gc_status()['protected']); +?> +--EXPECT-- +1: Client: SOAP-ERROR: Encoding: Nesting level too deep +8: Client: SOAP-ERROR: Encoding: Nesting level too deep +bool(false) diff --git a/ext/soap/tests/encoding_errors_no_bailout.phpt b/ext/soap/tests/encoding_errors_no_bailout.phpt new file mode 100644 index 000000000000..f925e93e3c1d --- /dev/null +++ b/ext/soap/tests/encoding_errors_no_bailout.phpt @@ -0,0 +1,201 @@ +--TEST-- +SoapClient reports encoding and decoding errors as SoapFault without a bailout +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=0 +--FILE-- +newInstanceWithoutConstructor(); +} + +$big = ''; + +class Client extends SoapClient +{ + public $response; + public function __doRequest($request, $location, $action, $version, $oneWay = false, ?string $uriParserClass = null): ?string + { + return $this->response; + } +} + +function envelope(string $body, string $header = ''): string +{ + return '' . $header . '' . $body . ''; +} + +$wsdl = __DIR__ . '/encoding_errors_no_bailout.wsdl'; +file_put_contents($wsdl, <<<'WSDL' + + + + + + + + + + + + + + + + + + + +WSDL); + +$cases = [ + + 'dec_string_elem' => ['value' => ''], + 'dec_normalized_elem' => ['value' => ''], + 'dec_token_elem' => ['value' => ''], + 'dec_base64_elem' => ['value' => ''], + 'dec_hexbin_elem' => ['value' => ''], + 'dec_double_elem' => ['value' => ''], + 'dec_long_elem' => ['value' => ''], + 'dec_bool_elem' => ['value' => ''], + 'dec_hexbin_odd' => ['value' => 'ABC'], + 'dec_hexbin_high' => ['value' => 'ZA'], + 'dec_hexbin_low' => ['value' => 'AZ'], + 'dec_double_invalid' => ['value' => 'abc'], + 'dec_long_invalid' => ['value' => 'abc'], + 'dec_two_values_first_wins' => ['value' => 'abcABC'], + 'dec_map_no_key' => ['value' => '1'], + 'dec_map_no_value' => ['value' => 'k'], + 'dec_map_bad_key' => ['value' => '11'], + 'dec_array_star' => ['value' => 'a'], + 'dec_array_position_digits' => ['value' => 'a'], + 'dec_array_position_increment' => ['value' => 'ab'], + 'dec_href_unresolved' => ['value' => ''], + 'dec_href_external' => ['value' => ''], + 'dec_ref12_unresolved' => ['value' => ''], + 'dec_ref12_self' => ['value' => ''], + 'dec_fault_string' => ['body' => 'SOAP-ENV:Server'], + 'dec_header' => ['header' => '

1

abc

', 'value' => 'abc'], + 'dec_classmap_typed' => ['wsdl' => 'Need', 'value' => 'abc', 'opts' => ['classmap' => ['Need' => 'TypedNeed']]], + 'enc_enum_after_error' => ['args' => fn() => [no_ctor(SoapVar::class), Plain::A]], + 'dec_fixed_element' => ['wsdl' => 'Fixed1', 'value' => 'zzz'], + 'dec_fixed_element_repeated' => ['wsdl' => 'FixedN', 'value' => 'abczzz'], + 'dec_fixed_attribute' => ['wsdl' => 'FixedAttr', 'value' => ''], + + 'enc_soapvar_no_enc_type' => ['args' => fn() => [no_ctor(SoapVar::class)]], + 'enc_soapvar_no_enc_type_in_array' => ['args' => fn() => [[no_ctor(SoapVar::class), no_ctor(SoapVar::class)]]], + 'enc_soapvar_unknown_type' => ['args' => function () { $v = no_ctor(SoapVar::class); $v->enc_type = 99999; $v->enc_value = 1; return [$v]; }], + 'enc_resource' => ['args' => fn() => [fopen('php://memory', 'r')]], + 'enc_resource_in_object' => ['args' => fn() => [(object) ['a' => 1, 'r' => fopen('php://memory', 'r'), 'b' => 2]]], + 'enc_invalid_utf8' => ['args' => fn() => ["ab\xffcd"]], + 'enc_invalid_utf8_twice' => ['args' => fn() => ["ab\xffcd", "\xfe"]], + 'enc_invalid_datetime' => ['args' => fn() => [new SoapVar(no_ctor(DateTime::class), XSD_DATETIME)]], + 'enc_fixed_element' => ['wsdl' => 'Fixed1', 'args' => fn() => [(object) ['f' => 'zzz']]], + 'enc_fixed_element_array' => ['wsdl' => 'FixedN', 'args' => fn() => [(object) ['f' => ['abc', 'zzz']]]], + 'enc_fixed_attribute' => ['wsdl' => 'FixedAttr', 'args' => fn() => [(object) ['a' => 'zzz']]], + 'enc_missing_property' => ['wsdl' => 'Need', 'args' => fn() => [(object) ['y' => 1]]], + 'enc_missing_any' => ['wsdl' => 'NeedAny', 'args' => fn() => [(object) []]], + 'enc_list_item' => ['wsdl' => 'IntList', 'args' => fn() => [[1, null, 3]]], +]; + +foreach ($cases as $name => $case) { + $fn = isset($case['wsdl']) ? 'op' . $case['wsdl'] : 'test'; + $opts = ($case['opts'] ?? []) + (isset($case['wsdl']) ? [] : ['location' => 'test://', 'uri' => 'urn:t']); + $client = new Client(isset($case['wsdl']) ? $wsdl : null, $opts + ['trace' => true]); + $value = $case['value'] ?? null; + $body = $case['body'] ?? ($value ? "$value" : ""); + $client->response = envelope($body, $case['header'] ?? ''); + $headers = []; + try { + $client->__soapCall($fn, isset($case['args']) ? ($case['args'])() : [], null, null, $headers); + echo "$name: no error\n"; + } catch (SoapFault $e) { + echo "$name: $e->faultcode: $e->faultstring\n"; + $fault = (new ReflectionProperty(SoapClient::class, '__soap_fault'))->getValue($client); + if ($fault !== $e) { + echo " __soap_fault is not the thrown SoapFault\n"; + } + if ($e->getPrevious()) { + echo " previous: ", get_class($e->getPrevious()), "\n"; + } + if (isset($case['args']) && $client->__getLastRequest() !== null) { + echo " the request was sent\n"; + } + if ($headers) { + echo " output headers: ", implode(', ', array_keys($headers)), "\n"; + } + } +} +var_dump(gc_status()['protected']); +?> +--CLEAN-- + +--EXPECT-- +dec_string_elem: Client: SOAP-ERROR: Encoding: Type 'string' value must contain a single text or CDATA node +dec_normalized_elem: Client: SOAP-ERROR: Encoding: Type 'normalizedString' value must contain a single text or CDATA node +dec_token_elem: Client: SOAP-ERROR: Encoding: Type 'token' value must contain a single text or CDATA node +dec_base64_elem: Client: SOAP-ERROR: Encoding: Type 'base64Binary' value must contain a single text or CDATA node +dec_hexbin_elem: Client: SOAP-ERROR: Encoding: Type 'hexBinary' value must contain a single text or CDATA node +dec_double_elem: Client: SOAP-ERROR: Encoding: Type 'double' value must contain a single text or CDATA node +dec_long_elem: Client: SOAP-ERROR: Encoding: Type 'int' value must contain a single text or CDATA node +dec_bool_elem: Client: SOAP-ERROR: Encoding: Type 'boolean' value must contain a single text or CDATA node +dec_hexbin_odd: Client: SOAP-ERROR: Encoding: Type 'hexBinary' value must contain an even number of hexadecimal digits +dec_hexbin_high: Client: SOAP-ERROR: Encoding: Invalid value for type 'hexBinary' +dec_hexbin_low: Client: SOAP-ERROR: Encoding: Invalid value for type 'hexBinary' +dec_double_invalid: Client: SOAP-ERROR: Encoding: Invalid value for type 'double' +dec_long_invalid: Client: SOAP-ERROR: Encoding: Invalid value for type 'int' +dec_two_values_first_wins: Client: SOAP-ERROR: Encoding: Invalid value for type 'int' +dec_map_no_key: Client: SOAP-ERROR: Encoding: Can't decode apache map, missing key +dec_map_no_value: Client: SOAP-ERROR: Encoding: Can't decode apache map, missing value +dec_map_bad_key: Client: SOAP-ERROR: Encoding: Can't decode apache map, only Strings or Longs are allowed as keys +dec_array_star: Client: SOAP-ERROR: Encoding: '*' may only be first arraySize value in list +dec_array_position_digits: Client: SOAP-ERROR: Encoding: array index out of range +dec_array_position_increment: Client: SOAP-ERROR: Encoding: array index out of range +dec_href_unresolved: Client: SOAP-ERROR: Encoding: Unresolved reference '#nope' +dec_href_external: Client: SOAP-ERROR: Encoding: External reference 'http://example.org/x' +dec_ref12_unresolved: Client: SOAP-ERROR: Encoding: Unresolved reference 'nope' +dec_ref12_self: Client: SOAP-ERROR: Encoding: Violation of id and ref information items 'me' +dec_fault_string: Client: SOAP-ERROR: Encoding: Type 'string' value must contain a single text or CDATA node +dec_header: Client: SOAP-ERROR: Encoding: Invalid value for type 'int' + output headers: h1 +dec_classmap_typed: Client: SOAP-ERROR: Encoding: Invalid value for type 'int' +enc_enum_after_error: Client: SOAP-ERROR: Encoding: SoapVar has no 'enc_type' property +dec_fixed_element: Client: SOAP-ERROR: Encoding: Element 'f' has fixed value 'abc' (value 'zzz' is not allowed) +dec_fixed_element_repeated: Client: SOAP-ERROR: Encoding: Element 'f' has fixed value 'abc' (value 'zzz' is not allowed) +dec_fixed_attribute: Client: SOAP-ERROR: Encoding: Attribute 'a' has fixed value 'x' (value 'zzz' is not allowed) +enc_soapvar_no_enc_type: Client: SOAP-ERROR: Encoding: SoapVar has no 'enc_type' property +enc_soapvar_no_enc_type_in_array: Client: SOAP-ERROR: Encoding: SoapVar has no 'enc_type' property +enc_soapvar_unknown_type: Client: SOAP-ERROR: Encoding: Cannot find encoding +enc_resource: Client: SOAP-ERROR: Encoding: Cannot find encoding +enc_resource_in_object: Client: SOAP-ERROR: Encoding: Cannot find encoding +enc_invalid_utf8: Client: SOAP-ERROR: Encoding: string 'ab\xff...' is not a valid utf-8 string +enc_invalid_utf8_twice: Client: SOAP-ERROR: Encoding: string 'ab\xff...' is not a valid utf-8 string +enc_invalid_datetime: Client: SOAP-ERROR: Encoding: Invalid DateTimeInterface +enc_fixed_element: Client: SOAP-ERROR: Encoding: Element 'f' has fixed value 'abc' (value 'zzz' is not allowed) +enc_fixed_element_array: Client: SOAP-ERROR: Encoding: Element 'f' has fixed value 'abc' (value 'zzz' is not allowed) +enc_fixed_attribute: Client: SOAP-ERROR: Encoding: Attribute 'a' has fixed value 'x' (value 'zzz' is not allowed) +enc_missing_property: Client: SOAP-ERROR: Encoding: object has no 'x' property +enc_missing_any: Client: SOAP-ERROR: Encoding: object has no 'any' property +enc_list_item: Client: SOAP-ERROR: Encoding: Failed to encode list item of type 'int' for list type 'IntList' +bool(false)