/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com)     |
   | Copyright (c) 1997-2010 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/soap/sdl.h"

#include <string>
#include <memory>

#include <folly/Conv.h>

#include "hphp/runtime/ext/soap/soap.h"
#include "hphp/util/text-util.h"

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

sdlCtx::~sdlCtx() {
  for (auto const& doc : docs) {
    xmlFreeDoc(doc.second);
  }
}

encodePtr get_encoder_from_prefix(sdl *sdl, xmlNodePtr node,
                                  const xmlChar *type) {
  std::string ns, cptype;
  parse_namespace(type, cptype, ns);
  xmlNsPtr nsptr = xmlSearchNs(node->doc, node, NS_STRING(ns));
  encodePtr enc;
  if (nsptr) {
    enc = get_encoder(sdl, (char*)nsptr->href, cptype.c_str());
    if (enc == NULL) {
      enc = get_encoder_ex(sdl, cptype);
    }
  } else {
    enc = get_encoder_ex(sdl, (char*)type);
  }
  return enc;
}

static sdlTypePtr get_element(sdl *sdl, xmlNodePtr node, const xmlChar *type) {
  sdlTypePtr ret;
  if (!sdl->elements.empty()) {
    std::string ns, cptype;
    parse_namespace(type, cptype, ns);
    xmlNsPtr nsptr = xmlSearchNs(node->doc, node, NS_STRING(ns));
    if (nsptr) {
      std::string nscat = (char*)nsptr->href;
      nscat += ':';
      nscat += cptype;
      sdlTypeMap::iterator iter = sdl->elements.find(nscat);
      if (iter != sdl->elements.end()) {
        ret = iter->second;
      } else {
        iter = sdl->elements.find((char*)type);
        if (iter != sdl->elements.end()) {
          ret = iter->second;
        }
      }
    } else {
      sdlTypeMap::iterator iter = sdl->elements.find((char*)type);
      if (iter != sdl->elements.end()) {
        ret = iter->second;
      }
    }
  }
  return ret;
}

encodePtr get_encoder(sdl *sdl, const char *ns, const char *type) {
  std::string nscat = ns;
  nscat += ':';
  nscat += type;
  encodePtr enc = get_encoder_ex(sdl, nscat);

  if (!enc &&
      (strcmp(ns, SOAP_1_1_ENC_NAMESPACE) == 0 ||
       strcmp(ns, SOAP_1_2_ENC_NAMESPACE) == 0)) {
    std::string enc_nscat = XSD_NAMESPACE;
    enc_nscat += ':';
    enc_nscat += type;
    enc = get_encoder_ex(NULL, enc_nscat);
    if (enc && sdl) {
      encodePtr new_enc(new encode());
      *new_enc = *enc;
      sdl->encoders[nscat] = new_enc;
      enc = new_enc;
    }
  }
  return enc;
}

encodePtr get_encoder_ex(sdl *sdl, const std::string &nscat) {
  USE_SOAP_GLOBAL;
  encodeMap::iterator iter = SOAP_GLOBAL(defEnc).find(nscat);
  if (iter != SOAP_GLOBAL(defEnc).end()) {
    return iter->second;
  }
  if (sdl) {
    iter = sdl->encoders.find(nscat);
    if (iter != sdl->encoders.end()) {
      return iter->second;
    }
  }
  return encodePtr();
}

sdlBindingPtr get_binding_from_type(sdl *sdl, int type) {
  if (sdl) {
    for (sdlBindingMap::iterator iter = sdl->bindings.begin();
         iter != sdl->bindings.end(); ++iter) {
      if (iter->second->bindingType == type) {
        return iter->second;
      }
    }
  }
  return sdlBindingPtr();
}

sdlBindingPtr get_binding_from_name(sdl *sdl, char *name, char *ns) {
  std::string key = ns;
  key += ':';
  key += name;
  sdlBindingMap::iterator iter = sdl->bindings.find(key);
  if (iter != sdl->bindings.end()) {
    return iter->second;
  }
  return sdlBindingPtr();
}

static bool is_wsdl_element(xmlNodePtr node) {
  if (node->ns && strcmp((char*)node->ns->href, WSDL_NAMESPACE) != 0) {
    xmlAttrPtr attr = get_attribute_ex(node->properties, "required",
                                       WSDL_NAMESPACE);
    if (attr && attr->children && attr->children->content &&
        (strcmp((char*)attr->children->content, "1") == 0 ||
         strcmp((char*)attr->children->content, "true") == 0)) {
      throw SoapException("Parsing WSDL: Unknown required WSDL extension '%s'",
                          node->ns->href);
    }
    return false;
  }
  return true;
}

static void load_wsdl_ex(char *struri, sdlCtx *ctx, bool include,
                         HttpClient *http) {
  if (ctx->docs.find(struri) != ctx->docs.end()) {
    return;
  }

  xmlDocPtr wsdl;
  if (http) {
    HeaderMap headers;
    StringBuffer response;
    int code = http->get(struri, response, &headers);
    if (code != 200) {
      throw SoapException("Parsing WSDL: Couldn't load from '%s'", struri);
    }
    String msg = response.detach();
    wsdl = soap_xmlParseMemory(msg.data(), msg.size(), false);
    if (wsdl) {
      wsdl->URL = xmlCharStrdup(struri);
    }
  } else {
    wsdl = soap_xmlParseFile(struri);
  }
  if (!wsdl) {
    xmlErrorPtr xmlErrorPtr = xmlGetLastError();
    if (xmlErrorPtr) {
      throw SoapException("Parsing WSDL: Couldn't load from '%s' : %s",
                          struri, xmlErrorPtr->message);
    } else {
      throw SoapException("Parsing WSDL: Couldn't load from '%s'", struri);
    }
  }

  sdlPtr tmpsdl = ctx->sdl;
  ctx->docs[struri] = wsdl;

  xmlNodePtr root = wsdl->children;
  xmlNodePtr definitions = get_node_ex(root, "definitions", WSDL_NAMESPACE);
  if (!definitions) {
    if (include) {
      xmlNodePtr schema = get_node_ex(root, "schema", XSD_NAMESPACE);
      if (schema) {
        load_schema(ctx, schema);
        return;
      }
    }
    throw SoapException("Parsing WSDL: Couldn't find <definitions> in '%s'",
                        struri);
  }

  xmlAttrPtr targetNamespace;
  if (!include) {
    targetNamespace = get_attribute(definitions->properties,
                                    "targetNamespace");
    if (targetNamespace) {
      tmpsdl->target_ns = (char*)targetNamespace->children->content;
    }
  }

  xmlNodePtr trav = definitions->children;
  while (trav) {
    if (!is_wsdl_element(trav)) {
      trav = trav->next;
      continue;
    }
    if (node_is_equal(trav,"types")) {
      /* TODO: Only one "types" is allowed */
      xmlNodePtr trav2 = trav->children;
      while (trav2) {
        if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) {
          load_schema(ctx, trav2);
        } else if (is_wsdl_element(trav2) &&
                   !node_is_equal(trav2,"documentation")) {
          throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                              trav2->name);
        }
        trav2 = trav2->next;
      }
    } else if (node_is_equal(trav,"import")) {
      /* TODO: namespace ??? */
      xmlAttrPtr tmp = get_attribute(trav->properties, "location");
      if (tmp) {
        xmlChar *uri;
        xmlChar *base = xmlNodeGetBase(trav->doc, trav);
        if (base == NULL) {
          uri = xmlBuildURI(tmp->children->content, trav->doc->URL);
        } else {
          uri = xmlBuildURI(tmp->children->content, base);
          xmlFree(base);
        }
        load_wsdl_ex((char*)uri, ctx, true, NULL);
        xmlFree(uri);
      }

    } else if (node_is_equal(trav,"message")) {
      xmlAttrPtr name = get_attribute(trav->properties, "name");
      if (name && name->children && name->children->content) {
        char *content = (char*)name->children->content;
        if (ctx->messages.find(content) != ctx->messages.end()) {
          throw SoapException("Parsing WSDL: <message> '%s' already defined",
                              content);
        } else {
          ctx->messages[content] = trav;
        }
      } else {
        throw SoapException("Parsing WSDL: <message> hasn't name attribute");
      }

    } else if (node_is_equal(trav,"portType")) {
      xmlAttrPtr name = get_attribute(trav->properties, "name");
      if (name && name->children && name->children->content) {
        char *content = (char*)name->children->content;
        if (ctx->portTypes.find(content) != ctx->portTypes.end()) {
          throw SoapException("Parsing WSDL: <portType> '%s' already defined",
                              content);
        } else {
          ctx->portTypes[content] = trav;
        }
      } else {
        throw SoapException("Parsing WSDL: <portType> hasn't name attribute");
      }

    } else if (node_is_equal(trav,"binding")) {
      xmlAttrPtr name = get_attribute(trav->properties, "name");
      if (name && name->children && name->children->content) {
        char *content = (char*)name->children->content;
        if (ctx->bindings.find(content) != ctx->bindings.end()) {
          throw SoapException("Parsing WSDL: <binding> '%s' already defined",
                              content);
        } else {
          ctx->bindings[content] = trav;
        }
      } else {
        throw SoapException("Parsing WSDL: <binding> hasn't name attribute");
      }

    } else if (node_is_equal(trav,"service")) {
      xmlAttrPtr name = get_attribute(trav->properties, "name");
      if (name && name->children && name->children->content) {
        char *content = (char*)name->children->content;
        if (ctx->services.find(content) != ctx->services.end()) {
          throw SoapException("Parsing WSDL: <service> '%s' already defined",
                              content);
        } else {
          ctx->services[content] = trav;
        }
      } else {
        throw SoapException("Parsing WSDL: <service> hasn't name attribute");
      }
    } else if (!node_is_equal(trav,"documentation")) {
      throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                          trav->name);
    }
    trav = trav->next;
  }
}

static std::shared_ptr<sdlSoapBindingFunctionHeader> wsdl_soap_binding_header
(sdlCtx* ctx, xmlNodePtr header, char* wsdl_soap_namespace, bool fault) {
  xmlAttrPtr tmp = get_attribute(header->properties, "message");
  if (!tmp) {
    throw SoapException("Parsing WSDL: Missing message attribute for <header>");
  }

  char *ctype = strrchr((char*)tmp->children->content,':');
  if (ctype == NULL) {
    ctype = (char*)tmp->children->content;
  } else {
    ++ctype;
  }
  xmlNodeMap::iterator iter = ctx->messages.find(ctype);
  if (iter == ctx->messages.end()) {
    throw SoapException("Parsing WSDL: Missing <message> with name '%s'",
                        tmp->children->content);
  }
  xmlNodePtr message = iter->second;

  tmp = get_attribute(header->properties, "part");
  if (!tmp) {
    throw SoapException("Parsing WSDL: Missing part attribute for <header>");
  }
  xmlNodePtr part = get_node_with_attribute_ex
    (message->children, "part", WSDL_NAMESPACE, "name",
     (char*)tmp->children->content, NULL);
  if (!part) {
    throw SoapException("Parsing WSDL: Missing part '%s' in <message>",
                        tmp->children->content);
  }

  auto h = std::make_shared<sdlSoapBindingFunctionHeader>();
  h->name = (char*)tmp->children->content;

  tmp = get_attribute(header->properties, "use");
  if (tmp &&
      !strncmp((char*)tmp->children->content, "encoded", sizeof("encoded"))) {
    h->use = SOAP_ENCODED;
  } else {
    h->use = SOAP_LITERAL;
  }

  tmp = get_attribute(header->properties, "namespace");
  if (tmp) {
    h->ns = (char*)tmp->children->content;
  }

  if (h->use == SOAP_ENCODED) {
    tmp = get_attribute(header->properties, "encodingStyle");
    if (tmp) {
      if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE,
                  sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) {
        h->encodingStyle = SOAP_ENCODING_1_1;
      } else if (strncmp((char*)tmp->children->content, SOAP_1_2_ENC_NAMESPACE,
                         sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) {
        h->encodingStyle = SOAP_ENCODING_1_2;
      } else {
        throw SoapException("Parsing WSDL: Unknown encodingStyle '%s'",
                            tmp->children->content);
      }
    } else {
      throw SoapException("Parsing WSDL: Unspecified encodingStyle");
    }
  }

  tmp = get_attribute(part->properties, "type");
  if (tmp) {
    h->encode = get_encoder_from_prefix(ctx->sdl.get(), part,
                                        tmp->children->content);
  } else {
    tmp = get_attribute(part->properties, "element");
    if (tmp) {
      h->element = get_element(ctx->sdl.get(), part, tmp->children->content);
      if (h->element) {
        h->encode = h->element->encode;
        if (h->ns.empty() && !h->element->namens.empty()) {
          h->ns = h->element->namens;
        }
        if (!h->element->name.empty()) {
          h->name = h->element->name;
        }
      }
    }
  }
  if (!fault) {
    xmlNodePtr trav = header->children;
    while (trav) {
      if (node_is_equal_ex(trav, "headerfault", wsdl_soap_namespace)) {
        auto hf = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, true);
        std::string key;
        if (!hf->ns.empty()) {
          key += hf->ns;
          key += ':';
        }
        key += hf->name;
        h->headerfaults[key] = hf;
      } else if (is_wsdl_element(trav) &&
                 !node_is_equal(trav,"documentation")) {
        throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                            trav->name);
      }
      trav = trav->next;
    }
  }
  return h;
}

static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node,
                                   char* wsdl_soap_namespace,
                                   sdlSoapBindingFunctionBody *binding,
                                   sdlParamVec &params) {
  xmlNodePtr body;
  xmlAttrPtr tmp;
  xmlNodePtr trav = node->children;
  while (trav) {
    if (node_is_equal_ex(trav, "body", wsdl_soap_namespace)) {
      body = trav;
      tmp = get_attribute(body->properties, "use");
      if (tmp &&
          !strncmp((char*)tmp->children->content, "literal",
                   sizeof("literal"))) {
        binding->use = SOAP_LITERAL;
      } else {
        binding->use = SOAP_ENCODED;
      }

      tmp = get_attribute(body->properties, "namespace");
      if (tmp) {
        binding->ns = (char*)tmp->children->content;
      }

      tmp = get_attribute(body->properties, "parts");
      if (tmp) {
        sdlParamVec ht;
        char *parts = (char*)tmp->children->content;

        /* Delete all parts those are not in the "parts" attribute */
        while (*parts) {
          bool found = false;
          while (*parts == ' ') ++parts;
          if (*parts == '\0') break;
          char *end = strchr(parts, ' ');
          if (end) *end = '\0';
          for (unsigned int i = 0; i < params.size(); i++) {
            sdlParamPtr param = params[i];
            if (param->paramName == parts) {
              sdlParamPtr x_param(new sdlParam);
              *x_param = *param;
              ht.push_back(x_param);
              found = true;
              break;
            }
          }
          if (!found) {
            throw SoapException("Parsing WSDL: Missing part '%s' in <message>",
                                parts);
          }
          parts += strlen(parts);
          if (end) *end = ' ';
        }
        params = ht;
      }

      if (binding->use == SOAP_ENCODED) {
        tmp = get_attribute(body->properties, "encodingStyle");
        if (tmp) {
          if (strncmp((char*)tmp->children->content, SOAP_1_1_ENC_NAMESPACE,
                      sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) {
            binding->encodingStyle = SOAP_ENCODING_1_1;
          } else if (strncmp((char*)tmp->children->content,
                             SOAP_1_2_ENC_NAMESPACE,
                             sizeof(SOAP_1_2_ENC_NAMESPACE)) == 0) {
            binding->encodingStyle = SOAP_ENCODING_1_2;
          } else {
            throw SoapException("Parsing WSDL: Unknown encodingStyle '%s'",
                                tmp->children->content);
          }
        } else {
          throw SoapException("Parsing WSDL: Unspecified encodingStyle");
        }
      }
    } else if (node_is_equal_ex(trav, "header", wsdl_soap_namespace)) {
      auto h = wsdl_soap_binding_header(ctx, trav, wsdl_soap_namespace, false);
      std::string key;
      if (!h->ns.empty()) {
        key += h->ns;
        key += ':';
      }
      key += h->name;
      binding->headers[key] = h;
    } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) {
      throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                          trav->name);
    }
    trav = trav->next;
  }
}

static void wsdl_message(sdlCtx *ctx, sdlParamVec &parameters,
                         xmlChar* message_name) {
  char *ctype = strrchr((char*)message_name,':');
  if (ctype == NULL) {
    ctype = (char*)message_name;
  } else {
    ++ctype;
  }
  xmlNodeMap::iterator iter = ctx->messages.find(ctype);
  if (iter == ctx->messages.end()) {
    throw SoapException("Parsing WSDL: Missing <message> with name '%s'",
                        message_name);
  }

  xmlNodePtr part;
  xmlNodePtr message = iter->second;
  xmlNodePtr trav = message->children;
  while (trav) {
    if (trav->ns && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) {
      throw SoapException("Parsing WSDL: Unexpected extensibility "
                          "element <%s>", trav->name);
    }
    if (node_is_equal(trav,"documentation")) {
      trav = trav->next;
      continue;
    }
    if (!node_is_equal(trav,"part")) {
      throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                          trav->name);
    }
    part = trav;
    sdlParamPtr param(new sdlParam);
    param->order = 0;
    xmlAttrPtr name = get_attribute(part->properties, "name");
    if (name == NULL) {
      throw SoapException("Parsing WSDL: No name associated with <part> '%s'",
                          message->name);
    }

    param->paramName = (char*)name->children->content;
    xmlAttrPtr type = get_attribute(part->properties, "type");
    if (type) {
      param->encode = get_encoder_from_prefix(ctx->sdl.get(), part,
                                              type->children->content);
    } else {
      xmlAttrPtr element = get_attribute(part->properties, "element");
      if (element) {
        param->element = get_element(ctx->sdl.get(), part,
                                     element->children->content);
        if (param->element) {
          param->encode = param->element->encode;
        }
      }
    }

    parameters.push_back(param);
    trav = trav->next;
  }
}

sdlPtr load_wsdl(char *struri, HttpClient *http) {
  sdlCtx ctx;
  ctx.sdl = sdlPtr(new sdl());
  ctx.sdl->source = struri;

  load_wsdl_ex(struri, &ctx, false, http);
  schema_pass2(&ctx);

  int i = 0;
  for (xmlNodeMap::iterator iter = ctx.services.begin();
       iter != ctx.services.end(); ++iter, ++i) {
    xmlNodePtr service = iter->second;
    xmlNodePtr port;
    bool has_soap_port = false;
    xmlNodePtr trav = service->children;
    while (trav) {
      if (!is_wsdl_element(trav) || node_is_equal(trav,"documentation")) {
        trav = trav->next;
        continue;
      }
      if (!node_is_equal(trav,"port")) {
        throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                            trav->name);
      }

      port = trav;

      sdlBindingPtr tmpbinding(new sdlBinding());
      xmlAttrPtr bindingAttr = get_attribute(port->properties, "binding");
      if (bindingAttr == NULL) {
        throw SoapException("Parsing WSDL: No binding associated with <port>");
      }

      char *wsdl_soap_namespace = NULL;

        /* find address and figure out binding type */
      xmlNodePtr address = NULL;
      xmlNodePtr trav2 = port->children;
      while (trav2) {
        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")) {
          throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                              trav2->name);
        }
        trav2 = trav2->next;
      }
      if (!address || tmpbinding->bindingType == BINDING_HTTP) {
        if (has_soap_port || trav->next || i < (int)ctx.services.size() - 1) {
          trav = trav->next;
          continue;
        } else if (!address) {
          throw SoapException("Parsing WSDL: No address associated "
                              "with <port>");
        }
      }
      has_soap_port = true;

      xmlAttrPtr location = get_attribute(address->properties, "location");
      if (!location) {
        throw SoapException("Parsing WSDL: No location associated "
                            "with <port>");
      }
      tmpbinding->location = (char*)location->children->content;

      char *ctype = strrchr((char*)bindingAttr->children->content,':');
      if (ctype == NULL) {
        ctype = (char*)bindingAttr->children->content;
      } else {
        ++ctype;
      }
      xmlNodeMap::iterator iterBinding = ctx.bindings.find(ctype);
      if (iterBinding == ctx.bindings.end()) {
        throw SoapException("Parsing WSDL: No <binding> element "
                            "with name '%s'", ctype);
      }
      xmlNodePtr binding = iterBinding->second;

      if (tmpbinding->bindingType == BINDING_SOAP) {
        xmlAttrPtr tmp;

        sdlSoapBindingPtr soapBinding(new sdlSoapBinding());
        soapBinding->style = SOAP_DOCUMENT;
        xmlNodePtr soapBindingNode = get_node_ex(binding->children, "binding",
                                                 wsdl_soap_namespace);
        if (soapBindingNode) {
          tmp = get_attribute(soapBindingNode->properties, "style");
          if (tmp &&
              !strncmp((char*)tmp->children->content, "rpc", sizeof("rpc"))) {
            soapBinding->style = SOAP_RPC;
          }

          tmp = get_attribute(soapBindingNode->properties, "transport");
          if (tmp) {
            if (strncmp((char*)tmp->children->content, WSDL_HTTP_TRANSPORT,
                        sizeof(WSDL_HTTP_TRANSPORT)) == 0) {
              soapBinding->transport = SOAP_TRANSPORT_HTTP;
            } else {
              throw SoapException("Parsing WSDL: PHP-SOAP doesn't support "
                                  "transport '%s'", tmp->children->content);
            }
          }
        }
        tmpbinding->bindingAttributes = soapBinding;
      }

      xmlAttrPtr name = get_attribute(binding->properties, "name");
      if (name == NULL) {
        throw SoapException("Parsing WSDL: Missing 'name' attribute "
                            "for <binding>");
      }
      tmpbinding->name = (char*)name->children->content;

      xmlAttrPtr type = get_attribute(binding->properties, "type");
      if (type == NULL) {
        throw SoapException("Parsing WSDL: Missing 'type' attribute "
                            "for <binding>");
      }

      xmlNodePtr portType, operation;
      ctype = strrchr((char*)type->children->content,':');
      if (ctype == NULL) {
        ctype = (char*)type->children->content;
      } else {
        ++ctype;
      }
      xmlNodeMap::iterator iter = ctx.portTypes.find(ctype);
      if (iter == ctx.portTypes.end()) {
        throw SoapException("Parsing WSDL: Missing <portType> with name '%s'",
                            name->children->content);
      }
      portType = iter->second;

      trav2 = binding->children;
      while (trav2) {
        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")) {
          throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                              trav2->name);
        }

        operation = trav2;
        xmlAttrPtr op_name = get_attribute(operation->properties, "name");
        if (op_name == NULL) {
          throw SoapException("Parsing WSDL: Missing 'name' attribute "
                              "for <operation>");
        }

        xmlNodePtr trav3 = operation->children;
        while  (trav3) {
          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")) {
            throw SoapException("Parsing WSDL: Unexpected WSDL element <%s>",
                                trav3->name);
          }
          trav3 = trav3->next;
        }

        xmlNodePtr portTypeOperation =
          get_node_with_attribute_ex(portType->children, "operation",
                                     WSDL_NAMESPACE, "name",
                                     (char*)op_name->children->content, NULL);
        if (portTypeOperation == NULL) {
          throw SoapException("Parsing WSDL: Missing <portType>/<operation> "
                              "with name '%s'", op_name->children->content);
        }

        xmlNodePtr input, output, fault;
        xmlAttrPtr paramOrder;

        auto function = std::make_shared<sdlFunction>();
        function->functionName = (char*)op_name->children->content;

        if (tmpbinding->bindingType == BINDING_SOAP) {
          sdlSoapBindingFunctionPtr soapFunctionBinding
            (new sdlSoapBindingFunction());
          sdlSoapBindingPtr soapBinding = tmpbinding->bindingAttributes;
          soapFunctionBinding->style = soapBinding->style;

          xmlNodePtr soapOperation = get_node_ex
            (operation->children, "operation", wsdl_soap_namespace);
          xmlAttrPtr tmp;
          if (soapOperation) {
            tmp = get_attribute(soapOperation->properties, "soapAction");
            if (tmp) {
              soapFunctionBinding->soapAction = (char*)tmp->children->content;
            }

            tmp = get_attribute(soapOperation->properties, "style");
            if (tmp) {
              if (!strncmp((char*)tmp->children->content, "rpc",
                           sizeof("rpc"))) {
                soapFunctionBinding->style = SOAP_RPC;
              } else {
                soapFunctionBinding->style = SOAP_DOCUMENT;
              }
            } else {
              soapFunctionBinding->style = soapBinding->style;
            }
          }

          function->bindingAttributes = soapFunctionBinding;
        }

        input = get_node_ex(portTypeOperation->children, "input",
                            WSDL_NAMESPACE);
        if (input) {
          xmlAttrPtr message = get_attribute(input->properties, "message");
          if (message == NULL) {
            throw SoapException("Parsing WSDL: Missing name for <input> "
                                "of '%s'", op_name->children->content);
          }
          wsdl_message(&ctx, function->requestParameters,
                       message->children->content);
/* FIXME
          xmlAttrPtr name = get_attribute(input->properties, "name");
            if (name) {
              function->requestName = estrdup(name->children->content);
            } else {
*/
          {
            function->requestName = function->functionName;
          }

          if (tmpbinding->bindingType == BINDING_SOAP) {
            input = get_node_ex(operation->children, "input", WSDL_NAMESPACE);
            if (input) {
              sdlSoapBindingFunctionPtr soapFunctionBinding =
                function->bindingAttributes;
              wsdl_soap_binding_body(&ctx, input, wsdl_soap_namespace,
                                     &soapFunctionBinding->input,
                                     function->requestParameters);
            }
          }
        }

        output = get_node_ex(portTypeOperation->children, "output",
                             WSDL_NAMESPACE);
        if (output) {
          xmlAttrPtr message = get_attribute(output->properties, "message");
          if (message == NULL) {
            throw SoapException("Parsing WSDL: Missing name for <output> "
                                "of '%s'", op_name->children->content);
          }
          wsdl_message(&ctx, function->responseParameters,
                       message->children->content);

/* FIXME
            xmlAttrPtr name = get_attribute(output->properties, "name");
            if (name) {
              function->responseName = estrdup(name->children->content);
            } else if (input == NULL) {
              function->responseName = estrdup(function->functionName);
            } else {
*/
          {
            function->responseName = function->functionName + "Response";
          }

          if (tmpbinding->bindingType == BINDING_SOAP) {
            output = get_node_ex(operation->children, "output",
                                 WSDL_NAMESPACE);
            if (output) {
              sdlSoapBindingFunctionPtr soapFunctionBinding =
                function->bindingAttributes;
              wsdl_soap_binding_body(&ctx, output, wsdl_soap_namespace,
                                     &soapFunctionBinding->output,
                                     function->responseParameters);
            }
          }
        }

        paramOrder = get_attribute(portTypeOperation->properties,
                                   "parameterOrder");
        if (paramOrder) {
          /* FIXME: */
        }

        fault = portTypeOperation->children;
        while (fault) {
          if (node_is_equal_ex(fault, "fault", WSDL_NAMESPACE)) {
            xmlAttrPtr name = get_attribute(fault->properties, "name");
            if (name == NULL) {
              throw SoapException("Parsing WSDL: Missing name for <fault> "
                                  "of '%s'", op_name->children->content);
            }
            xmlAttrPtr message = get_attribute(fault->properties, "message");
            if (message == NULL) {
              throw SoapException("Parsing WSDL: Missing name for <output> "
                                  "of '%s'", op_name->children->content);
            }

            auto f = std::make_shared<sdlFault>();
            f->name = (char*)name->children->content;
            wsdl_message(&ctx, f->details, message->children->content);
            if (f->details.size() != 1) {
              throw SoapException("Parsing WSDL: The fault message '%s' must "
                                  "have a single part",
                                  message->children->content);
            }

            if (tmpbinding->bindingType == BINDING_SOAP) {
              xmlNodePtr soap_fault =
                get_node_with_attribute_ex(operation->children, "fault",
                                           WSDL_NAMESPACE, "name",
                                           (char*)f->name.c_str(), NULL);
              if (soap_fault) {
                xmlNodePtr trav = soap_fault->children;
                while (trav) {
                  if (node_is_equal_ex(trav, "fault", wsdl_soap_namespace)) {
                    sdlSoapBindingFunctionFaultPtr binding =
                      f->bindingAttributes = sdlSoapBindingFunctionFaultPtr
                      (new sdlSoapBindingFunctionFault());
                    xmlAttrPtr tmp = get_attribute(trav->properties, "use");
                    if (tmp && !strncmp((char*)tmp->children->content,
                                        "encoded", sizeof("encoded"))) {
                      binding->use = SOAP_ENCODED;
                    } else {
                      binding->use = SOAP_LITERAL;
                    }

                    tmp = get_attribute(trav->properties, "namespace");
                    if (tmp) {
                      binding->ns = (char*)tmp->children->content;
                    }

                    if (binding->use == SOAP_ENCODED) {
                      tmp = get_attribute(trav->properties, "encodingStyle");
                      if (tmp) {
                        if (strncmp((char*)tmp->children->content,
                                    SOAP_1_1_ENC_NAMESPACE,
                                    sizeof(SOAP_1_1_ENC_NAMESPACE)) == 0) {
                          binding->encodingStyle = SOAP_ENCODING_1_1;
                        } else if (strncmp((char*)tmp->children->content,
                                           SOAP_1_2_ENC_NAMESPACE,
                                           sizeof(SOAP_1_2_ENC_NAMESPACE))
                                   == 0) {
                          binding->encodingStyle = SOAP_ENCODING_1_2;
                        } else {
                          throw SoapException("Parsing WSDL: Unknown "
                                              "encodingStyle '%s'",
                                              tmp->children->content);
                        }
                      } else {
                        throw SoapException("Parsing WSDL: Unspecified "
                                            "encodingStyle");
                      }
                    }
                  } else if (is_wsdl_element(trav) &&
                             !node_is_equal(trav,"documentation")) {
                    throw SoapException("Parsing WSDL: Unexpected WSDL "
                                        "element <%s>", trav->name);
                  }
                  trav = trav->next;
                }
              }
            }
            sdlFaultMap::iterator iter = function->faults.find(f->name);
            if (iter != function->faults.end()) {
              throw SoapException("Parsing WSDL: <fault> with name '%s' "
                                  "already defined in '%s'", f->name.c_str(),
                                  op_name->children->content);
            }
            function->faults[f->name] = f;
          }
          fault = fault->next;
        }

        function->binding = tmpbinding;

        {
          std::string tmp = toLower(function->functionName);
          sdlFunctionMap::iterator iter = ctx.sdl->functions.find(tmp);
          if (iter != ctx.sdl->functions.end()) {
            ctx.sdl->functions[folly::to<std::string>
                               (ctx.sdl->functions.size())] = function;
          } else {
            ctx.sdl->functions[tmp] = function;
          }
          ctx.sdl->functionsOrder.push_back(tmp);
          if (function->requestName != function->functionName) {
            ctx.sdl->requests[toLower(function->requestName)] = function;
          }
        }
        trav2 = trav2->next;
      }

      ctx.sdl->bindings[tmpbinding->name] = tmpbinding;
      trav= trav->next;
    }
  }

  if (i == 0) {
    throw SoapException("Parsing WSDL: Couldn't bind to service");
  }
  return ctx.sdl;
}

///////////////////////////////////////////////////////////////////////////////
}
