diff --git a/library/SOAP/Base.php b/library/SOAP/Base.php index 199f865..984ec45 100644 --- a/library/SOAP/Base.php +++ b/library/SOAP/Base.php @@ -1,1116 +1,1116 @@ Original Author * @author Shane Caraveo Port to PEAR and more * @author Chuck Hagenbuch Maintenance * @author Jan Schneider Maintenance * @copyright 2003-2007 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ define('MAIL_MIMEPART_CRLF', "\r\n"); require_once 'PEAR.php'; if (!defined('INF')) { define('INF', 1.8e307); } if (!defined('NAN')) { define('NAN', 0.0); } define('SOAP_LIBRARY_VERSION', '0.11.0'); define('SOAP_LIBRARY_NAME', 'PEAR-SOAP 0.11.0-beta'); // Set schema version. define('SOAP_XML_SCHEMA_VERSION', 'http://www.w3.org/2001/XMLSchema'); define('SOAP_XML_SCHEMA_INSTANCE', 'http://www.w3.org/2001/XMLSchema-instance'); define('SOAP_XML_SCHEMA_1999', 'http://www.w3.org/1999/XMLSchema'); define('SOAP_SCHEMA', 'http://schemas.xmlsoap.org/wsdl/soap/'); define('SOAP_SCHEMA_ENCODING', 'http://schemas.xmlsoap.org/soap/encoding/'); define('SOAP_ENVELOP', 'http://schemas.xmlsoap.org/soap/envelope/'); define('SCHEMA_DISCO', 'http://schemas.xmlsoap.org/disco/'); define('SCHEMA_DISCO_SCL', 'http://schemas.xmlsoap.org/disco/scl/'); define('SCHEMA_SOAP', 'http://schemas.xmlsoap.org/wsdl/soap/'); define('SCHEMA_SOAP12', 'http://schemas.xmlsoap.org/wsdl/soap12/'); define('SCHEMA_SOAP_HTTP', 'http://schemas.xmlsoap.org/soap/http'); define('SCHEMA_WSDL_HTTP', 'http://schemas.xmlsoap.org/wsdl/http/'); define('SCHEMA_MIME', 'http://schemas.xmlsoap.org/wsdl/mime/'); define('SCHEMA_WSDL', 'http://schemas.xmlsoap.org/wsdl/'); define('SCHEMA_DIME', 'http://schemas.xmlsoap.org/ws/2002/04/dime/wsdl/'); define('SCHEMA_CONTENT', 'http://schemas.xmlsoap.org/ws/2002/04/content-type/'); define('SCHEMA_REF', 'http://schemas.xmlsoap.org/ws/2002/04/reference/'); define('SOAP_DEFAULT_ENCODING', 'UTF-8'); class SOAP_Base_Object extends PEAR { /** * Supported encodings, limited by XML extension. * * @var array $_encodings */ var $_encodings = array('ISO-8859-1', 'US-ASCII', 'UTF-8'); /** * Fault code. * * @var string $_myfaultcode */ var $_myfaultcode = ''; /** * Recent PEAR_Error object. * * @var PEAR_Error $fault */ var $fault = null; /** * Constructor. * * @param string $faultcode Error code. */ function SOAP_Base_Object($faultcode = 'Client') { $this->_myfaultcode = $faultcode; parent::PEAR('SOAP_Fault'); } /** * Raises a SOAP error. * * Please refer to the SOAP definition for an impression of what a certain * parameter stands for. * * @param string|object $str Error message or object. * @param string $detail Detailed error message. * @param string $actorURI * @param mixed $code * @param mixed $mode * @param mixed $options * @param boolean $skipmsg */ function &_raiseSoapFault($str, $detail = '', $actorURI = '', $code = null, $mode = null, $options = null, $skipmsg = false) { // Pass through previous faults. $is_instance = isset($this) && is_a($this, 'SOAP_Base_Object'); if (is_object($str)) { $fault =& $str; } else { if (!$code) { $code = $is_instance ? $this->_myfaultcode : 'Client'; } require_once 'SOAP/Fault.php'; $fault =new SOAP_Fault($str, $code, $actorURI, $detail, $mode, $options); } if ($is_instance) { $this->fault =& $fault; } return $fault; } function _isfault() { return $this->fault != null; } function &_getfault() { return $this->fault; } } /** * Common base class of all SOAP classes. * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates */ class SOAP_Base extends SOAP_Base_Object { var $_XMLSchema = array('http://www.w3.org/2001/XMLSchema', 'http://www.w3.org/1999/XMLSchema'); var $_XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema'; // load types into typemap array var $_typemap = array( 'http://www.w3.org/2001/XMLSchema' => array( 'string' => 'string', 'boolean' => 'boolean', 'float' => 'float', 'double' => 'float', 'decimal' => 'float', 'duration' => 'integer', 'dateTime' => 'string', 'time' => 'string', 'date' => 'string', 'gYearMonth' => 'integer', 'gYear' => 'integer', 'gMonthDay' => 'integer', 'gDay' => 'integer', 'gMonth' => 'integer', 'hexBinary' => 'string', 'base64Binary' => 'string', // derived datatypes 'normalizedString' => 'string', 'token' => 'string', 'language' => 'string', 'NMTOKEN' => 'string', 'NMTOKENS' => 'string', 'Name' => 'string', 'NCName' => 'string', 'ID' => 'string', 'IDREF' => 'string', 'IDREFS' => 'string', 'ENTITY' => 'string', 'ENTITIES' => 'string', 'integer' => 'integer', 'nonPositiveInteger' => 'integer', 'negativeInteger' => 'integer', // longs (64bit ints) are not supported cross-platform. 'long' => 'string', 'int' => 'integer', 'short' => 'integer', 'byte' => 'string', 'nonNegativeInteger' => 'integer', 'unsignedLong' => 'integer', 'unsignedInt' => 'integer', 'unsignedShort' => 'integer', 'unsignedByte' => 'integer', 'positiveInteger' => 'integer', 'anyType' => 'string', 'anyURI' => 'string', 'QName' => 'string' ), 'http://www.w3.org/1999/XMLSchema' => array( 'i4' => 'integer', 'int' => 'integer', 'boolean' => 'boolean', 'string' => 'string', 'double' => 'float', 'float' => 'float', 'dateTime' => 'string', 'timeInstant' => 'string', 'base64Binary' => 'string', 'base64' => 'string', 'ur-type' => 'string' ), 'http://schemas.xmlsoap.org/soap/encoding/' => array( 'base64' => 'string', 'array' => 'array', 'Array' => 'array', 'Struct' => 'array') ); /** * Default class name to use for decoded response objects. * * @var string $_defaultObjectClassname */ var $_defaultObjectClassname = 'stdClass'; /** * Hash with used namespaces. * * @array */ var $_namespaces; /** * The default namespace. * * @string */ var $_namespace; var $_xmlEntities = array('&' => '&', '<' => '<', '>' => '>', "'" => ''', '"' => '"'); var $_doconversion = false; var $_attachments = array(); var $_wsdl = null; /** * True if we use section 5 encoding, or false if this is literal. * * @var boolean $_section5 */ var $_section5 = true; // Handle type to class mapping. var $_auto_translation = false; var $_type_translation = array(); /** * Constructor. * * @param string $faultcode Error code. */ function SOAP_Base($faultcode = 'Client') { parent::SOAP_Base_Object($faultcode); $this->_resetNamespaces(); } /** * Sets the default namespace. * * @param string $namespace The default namespace. */ function setDefaultNamespace($namespace) { $this->_namespace = $namespace; } function _resetNamespaces() { $this->_namespaces = array( 'http://schemas.xmlsoap.org/soap/envelope/' => 'SOAP-ENV', 'http://www.w3.org/2001/XMLSchema' => 'xsd', 'http://www.w3.org/2001/XMLSchema-instance' => 'xsi', 'http://schemas.xmlsoap.org/soap/encoding/' => 'SOAP-ENC'); } /** * Sets the schema version used in the SOAP message. * * @access private * @see $_XMLSchema * * @param string $schemaVersion The schema version. */ function _setSchemaVersion($schemaVersion) { if (!in_array($schemaVersion, $this->_XMLSchema)) { return $this->_raiseSoapFault("unsuported XMLSchema $schemaVersion"); } $this->_XMLSchemaVersion = $schemaVersion; $tmpNS = array_flip($this->_namespaces); $tmpNS['xsd'] = $this->_XMLSchemaVersion; $tmpNS['xsi'] = $this->_XMLSchemaVersion . '-instance'; $this->_namespaces = array_flip($tmpNS); } function _getNamespacePrefix($ns) { if ($this->_namespace && $ns == $this->_namespace) { return ''; } if (isset($this->_namespaces[$ns])) { return $this->_namespaces[$ns]; } $prefix = 'ns' . count($this->_namespaces); $this->_namespaces[$ns] = $prefix; return $prefix; } function _getNamespaceForPrefix($prefix) { $flipped = array_flip($this->_namespaces); if (isset($flipped[$prefix])) { return $flipped[$prefix]; } return null; } function _isSoapValue(&$value) { return is_a($value, 'SOAP_Value'); } function _serializeValue(&$value, $name = '', $type = false, $elNamespace = null, $typeNamespace = null, $options = array(), $attributes = array(), $artype = '') { $namespaces = array(); $arrayType = $array_depth = $xmlout_value = null; $typePrefix = $elPrefix = $xmlout_offset = $xmlout_arrayType = ''; $xmlout_type = $xmlns = $ptype = $array_type_ns = ''; if (!$name || is_numeric($name)) { $name = 'item'; } if ($this->_wsdl) { list($ptype, $arrayType, $array_type_ns, $array_depth) = $this->_wsdl->getSchemaType($type, $name, $typeNamespace); } if (!$arrayType) { $arrayType = $artype; } if (!$ptype) { $ptype = $this->_getType($value); } if (!$type) { $type = $ptype; } if (strcasecmp($ptype, 'Struct') == 0 || strcasecmp($type, 'Struct') == 0) { // Struct $vars = null; if (is_object($value)) { $vars = get_object_vars($value); } else { $vars = &$value; } if (is_array($vars)) { foreach (array_keys($vars) as $k) { // Hide private vars. if ($k[0] == '_') { continue; } if (is_object($vars[$k])) { if (is_a($vars[$k], 'SOAP_Value')) { $xmlout_value .= $vars[$k]->serialize($this); } else { // XXX get the members and serialize them instead // converting to an array is more overhead than we // should really do. $xmlout_value .= $this->_serializeValue(get_object_vars($vars[$k]), $k, false, $this->_section5 ? null : $elNamespace); } } else { $xmlout_value .= $this->_serializeValue($vars[$k], $k, false, $this->_section5 ? null : $elNamespace); } } } } elseif (strcasecmp($ptype, 'Array') == 0 || strcasecmp($type, 'Array') == 0) { // Array. $typeNamespace = SOAP_SCHEMA_ENCODING; $orig_type = $type; $type = 'Array'; $numtypes = 0; $value = (array)$value; // XXX this will be slow on larger arrays. Basically, it flattens // arrays to allow us to serialize multi-dimensional arrays. We // only do this if arrayType is set, which will typically only // happen if we are using WSDL if (isset($options['flatten']) || ($arrayType && (strchr($arrayType, ',') || strstr($arrayType, '][')))) { $numtypes = $this->_multiArrayType($value, $arrayType, $ar_size, $xmlout_value); } $array_type = $array_type_prefix = ''; if ($numtypes != 1) { $arrayTypeQName = new QName($arrayType); $arrayType = $arrayTypeQName->name; $array_types = array(); $array_val = null; // Serialize each array element. $ar_size = count($value); foreach ($value as $array_val) { if ($this->_isSoapValue($array_val)) { $array_type = $array_val->type; $array_types[$array_type] = 1; $array_type_ns = $array_val->type_namespace; $xmlout_value .= $array_val->serialize($this); } else { $array_type = $this->_getType($array_val); $array_types[$array_type] = 1; $xmlout_value .= $this->_serializeValue($array_val, 'item', $array_type, $this->_section5 ? null : $elNamespace); } } $xmlout_offset = ' SOAP-ENC:offset="[0]"'; if (!$arrayType) { $numtypes = count($array_types); if ($numtypes == 1) { $arrayType = $array_type; } // Using anyType is more interoperable. if ($array_type == 'Struct') { $array_type = ''; } elseif ($array_type == 'Array') { $arrayType = 'anyType'; $array_type_prefix = 'xsd'; } else { if (!$arrayType) { $arrayType = $array_type; } } } } if (!$arrayType || $numtypes > 1) { // Should reference what schema we're using. $arrayType = 'xsd:anyType'; } else { if ($array_type_ns) { $array_type_prefix = $this->_getNamespacePrefix($array_type_ns); } elseif (isset($this->_typemap[$this->_XMLSchemaVersion][$arrayType])) { $array_type_prefix = $this->_namespaces[$this->_XMLSchemaVersion]; } if ($array_type_prefix) { $arrayType = $array_type_prefix . ':' . $arrayType; } } $xmlout_arrayType = ' SOAP-ENC:arrayType="' . $arrayType; if ($array_depth != null) { for ($i = 0; $i < $array_depth; $i++) { $xmlout_arrayType .= '[]'; } } $xmlout_arrayType .= "[$ar_size]\""; } elseif ($this->_isSoapValue($value)) { $xmlout_value = $value->serialize($this); } elseif ($type == 'string') { $xmlout_value = htmlspecialchars($value); } elseif ($type == 'rawstring') { $xmlout_value =& $value; } elseif ($type == 'boolean') { $xmlout_value = $value ? 'true' : 'false'; } else { $xmlout_value =& $value; } // Add namespaces. if ($elNamespace) { $elPrefix = $this->_getNamespacePrefix($elNamespace); if ($elPrefix) { $xmlout_name = "$elPrefix:$name"; } else { $xmlout_name = $name; } } else { $xmlout_name = $name; } if ($typeNamespace) { $typePrefix = $this->_getNamespacePrefix($typeNamespace); if ($typePrefix) { $xmlout_type = "$typePrefix:$type"; } else { $xmlout_type = $type; } } elseif ($type && isset($this->_typemap[$this->_XMLSchemaVersion][$type])) { $typePrefix = $this->_namespaces[$this->_XMLSchemaVersion]; if ($typePrefix) { $xmlout_type = "$typePrefix:$type"; } else { $xmlout_type = $type; } } // Handle additional attributes. $xml_attr = ''; if (count($attributes)) { foreach ($attributes as $k => $v) { $kqn = new QName($k); $vqn = new QName($v); $xml_attr .= ' ' . $kqn->fqn() . '="' . $vqn->fqn() . '"'; } } // Store the attachment for mime encoding. if (isset($options['attachment']) && !PEAR::isError($options['attachment'])) { $this->_attachments[] = $options['attachment']; } if ($this->_section5) { if ($xmlout_type) { $xmlout_type = " xsi:type=\"$xmlout_type\""; } if (is_null($xmlout_value)) { $xml = "\r\n<$xmlout_name$xmlout_type$xmlns$xmlout_arrayType" . "$xml_attr xsi:nil=\"true\"/>"; } else { $xml = "\r\n<$xmlout_name$xmlout_type$xmlns$xmlout_arrayType" . "$xmlout_offset$xml_attr>$xmlout_value"; } } else { if (is_null($xmlout_value)) { $xml = "\r\n<$xmlout_name$xmlns$xml_attr/>"; } else { $xml = "\r\n<$xmlout_name$xmlns$xml_attr>" . $xmlout_value . ""; } } return $xml; } /** * Converts a PHP type to a SOAP type. * * @access private * * @param string $value The value to inspect. * * @return string The value's SOAP type. */ function _getType(&$value) { $type = gettype($value); switch ($type) { case 'object': if (is_a($value, 'soap_value')) { $type = $value->type; } else { $type = 'Struct'; } break; case 'array': // Hashes are always handled as structs. if ($this->_isHash($value)) { $type = 'Struct'; } else { $ar_size = count($value); reset($value); $key1 = key($value); if ($ar_size > 0 && is_a($key1, 'SOAP_Value')) { // FIXME: for non-wsdl structs that are all the same type $key2 = key($value); if ($ar_size > 1 && $this->_isSoapValue($key1) && $this->_isSoapValue($key2) && $key1->name != $key2->name) { // This is a struct, not an array. $type = 'Struct'; } else { $type = 'Array'; } } else { $type = 'Array'; } } break; case 'integer': case 'long': $type = 'int'; break; case 'boolean': break; case 'double': // double is deprecated in PHP 4.2 and later. $type = 'float'; break; case 'null': $type = ''; break; case 'string': default: break; } return $type; } function _multiArrayType($value, &$type, &$size, &$xml) { if (is_array($value)) { // Seems we have a multi dimensional array, figure it out if we // do. for ($i = 0, $c = count($value); $i < $c; ++$i) { $this->_multiArrayType($value[$i], $type, $size, $xml); } $sz = count($value); if ($size) { $size = $sz . ',' . $size; } else { $size = $sz; } return 1; } elseif (is_object($value)) { $type = $value->type; $xml .= $value->serialize($this); } else { $type = $this->_getType($value); $xml .= $this->_serializeValue($value, 'item', $type); } $size = null; return 1; } /** * Returns whether a type is a base64 type. * * @param string $type A type name. * * @return boolean True if the type name is a base64 type. */ function _isBase64Type($type) { return $type == 'base64' || $type == 'base64Binary'; } /** * Returns whether an array is a hash. * * @param array $a An array to check. * * @return boolean True if the specified array is a hash. */ function _isHash(&$a) { // I really dislike having to loop through this in PHP code, really // large arrays will be slow. We need a C function to do this. $it = 0; foreach ($a as $k => $v) { // Checking the type is faster than regexp. if (!is_int($k)) { return true; } // If someone has a large hash they should really be defining the // type. if ($it++ > 10) { $this->_raiseSoapFault('Large associative array passed where a SOAP_Value was expected'); return false; } } return false; } function _un_htmlentities($string) { $trans_tbl = get_html_translation_table(HTML_ENTITIES); $trans_tbl = array_flip($trans_tbl); return strtr($string, $trans_tbl); } /** * Converts a SOAP_Value object into a StdClass PHP object */ function &_decode(&$soapval) { if (!$this->_isSoapValue($soapval)) { return $soapval; } elseif (is_array($soapval->value)) { if ($soapval->type != 'Array') { $classname = $this->_defaultObjectClassname; if (isset($this->_type_translation[$soapval->tqn->fqn()])) { // This will force an error in PHP if the class does not // exist. $classname = $this->_type_translation[$soapval->tqn->fqn()]; } elseif (isset($this->_type_translation[$soapval->type])) { // This will force an error in PHP if the class does not // exist. $classname = $this->_type_translation[$soapval->type]; } elseif ($this->_auto_translation) { if (class_exists($soapval->type)) { $classname = $soapval->type; } elseif ($this->_wsdl) { $t = $this->_wsdl->getComplexTypeNameForElement($soapval->name, $soapval->namespace); if ($t && class_exists($t)) { $classname = $t; } } } $return =new $classname; } else { $return = array(); } $counter = 1; $isstruct = !is_array($return); foreach ($soapval->value as $item) { if (is_object($return)) { if ($this->_wsdl) { // Get this child's WSDL information. // /$soapval->ns/$soapval->type/$item->ns/$item->name $child_type = $this->_wsdl->getComplexTypeChildType( $soapval->namespace, $soapval->name, $item->namespace, $item->name); if ($child_type) { $item->type = $child_type; } } if (!$isstruct || $item->type == 'Array') { if (isset($return->{$item->name}) && is_object($return->{$item->name})) { $return->{$item->name} =& $this->_decode($item); } elseif (isset($return->{$item->name}) && is_array($return->{$item->name})) { $return->{$item->name}[] = $this->_decode($item); } elseif (isset($return->{$item->name})) { $return->{$item->name} = array( $return->{$item->name}, $this->_decode($item) ); } elseif (is_array($return)) { $return[] =& $this->_decode($item); } else { $return->{$item->name} =& $this->_decode($item); } } elseif (isset($return->{$item->name})) { //$isstruct = false; if (count(get_object_vars($return)) == 1) { $d =& $this->_decode($item); $return = array($return->{$item->name}, $d); } else { $d =& $this->_decode($item); $return->{$item->name} = array($return->{$item->name}, $d); } } else { $return->{$item->name} =& $this->_decode($item); } // Set the attributes as members in the class. if (method_exists($return, '__set_attribute')) { foreach ($soapval->attributes as $key => $value) { call_user_func_array(array(&$return, '__set_attribute'), array($key, $value)); } } } else { if ($soapval->arrayType && $this->_isSoapValue($item)) { if ($this->_isBase64Type($item->type) && !$this->_isBase64Type($soapval->arrayType)) { // Decode the value if we're losing the base64 // type information. $item->value = base64_decode($item->value); } $item->type = $soapval->arrayType; } if (!$isstruct) { $return[] = $this->_decode($item); } elseif (isset($return[$item->name])) { $isstruct = false; $d =& $this->_decode($item); $return = array($return[$item->name], $d); } else { $return[$item->name] = $this->_decode($item); } } } return $return; } if ($soapval->type == 'boolean') { if ($soapval->value != '0' && strcasecmp($soapval->value, 'false') != 0) { $soapval->value = true; } else { $soapval->value = false; } } elseif ($soapval->type && isset($this->_typemap[SOAP_XML_SCHEMA_VERSION][$soapval->type])) { // If we can, set variable type. settype($soapval->value, $this->_typemap[SOAP_XML_SCHEMA_VERSION][$soapval->type]); } return $soapval->value; } /** * Creates the SOAP envelope with the SOAP envelop data. * * @param mixed $method * @param array $headers * @param string $encoding * @param array $options * * @return string */ function makeEnvelope(&$method, &$headers, $encoding = SOAP_DEFAULT_ENCODING, $options = array()) { $smsg = $header_xml = $ns_string = ''; if ($headers) { $c = count($headers); for ($i = 0; $i < $c; $i++) { $header_xml .= $headers[$i]->serialize($this); } $header_xml = "\r\n$header_xml\r\n\r\n"; } if (!isset($options['input']) || $options['input'] == 'parse') { if (is_array($method)) { $c = count($method); for ($i = 0; $i < $c; $i++) { $smsg .= $method[$i]->serialize($this); } } else { $smsg = $method->serialize($this); } } else { $smsg = $method; } $body = "\r\n" . $smsg . "\r\n\r\n"; foreach ($this->_namespaces as $k => $v) { $ns_string .= " xmlns:$v=\"$k\"\r\n"; } if ($this->_namespace) { $ns_string .= " xmlns=\"{$this->_namespace}\"\r\n"; } /* If 'use' == 'literal', we do not put in the encodingStyle. This is * denoted by $this->_section5 being false. 'use' can be defined at a * more granular level than we are dealing with here, so this does not * work for all services. */ $xml = "\r\n\r\n". "_section5 ? ' SOAP-ENV:encodingStyle="' . SOAP_SCHEMA_ENCODING . '"' : ''). ">\r\n". "$header_xml$body\r\n"; return $xml; } function _makeMimeMessage($xml, $encoding = SOAP_DEFAULT_ENCODING) { if (!@include_once 'Mail/mimePart.php') { return $this->_raiseSoapFault('MIME messages are unsupported, the Mail_Mime package is not installed'); } // Encode any attachments. See http://www.w3.org/TR/SOAP-attachments // Now we have to mime encode the message. $params = array('content_type' => 'multipart/related; type="text/xml"'); $msg = new Mail_mimePart('', $params); // Add the xml part. $params['content_type'] = 'text/xml'; $params['charset'] = $encoding; $params['encoding'] = 'base64'; $msg->addSubPart($xml, $params); // Add the attachements for ($i = 0, $c = count($this->_attachments); $i < $c; ++$i) { $msg->addSubPart($this->_attachments[$i]['body'], $this->_attachments[$i]); } return $msg->encode(); } // TODO: this needs to be used from the Transport system. function _makeDIMEMessage($xml) { if (!@include_once 'Net/DIME.php') { return $this->_raiseSoapFault('DIME messages are unsupported, the Net_DIME package is not installed'); } // Encode any attachments. See // http://search.ietf.org/internet-drafts/draft-nielsen-dime-soap-00.txt // Now we have to DIME encode the message $dime = new Net_DIME_Message(); $msg = $dime->encodeData($xml, SOAP_ENVELOP, null, NET_DIME_TYPE_URI); // Add the attachments. $c = count($this->_attachments); for ($i = 0; $i < $c; $i++) { $msg .= $dime->encodeData($this->_attachments[$i]['body'], $this->_attachments[$i]['content_type'], $this->_attachments[$i]['cid'], NET_DIME_TYPE_MEDIA); } $msg .= $dime->endMessage(); return $msg; } function _decodeMimeMessage(&$data, &$headers, &$attachments) { if (!@include_once 'Mail/mimeDecode.php') { return $this->_raiseSoapFault('MIME messages are unsupported, the Mail_Mime package is not installed'); } $params['include_bodies'] = true; $params['decode_bodies'] = true; $params['decode_headers'] = true; // Lame thing to have to do for decoding. $decoder =new Mail_mimeDecode($data); $structure = $decoder->decode($params); if (isset($structure->body)) { $data = $structure->body; $headers = $structure->headers; return; } elseif (isset($structure->parts)) { $data = $structure->parts[0]->body; $headers = array_merge($structure->headers, $structure->parts[0]->headers); if (count($structure->parts) > 1) { $mime_parts = array_splice($structure->parts,1); // Prepare the parts for the SOAP parser. $c = count($mime_parts); for ($i = 0; $i < $c; $i++) { $p =& $mime_parts[$i]; if (isset($p->headers['content-location'])) { // TODO: modify location per SwA note section 3 // http://www.w3.org/TR/SOAP-attachments $attachments[$p->headers['content-location']] = $p->body; } else { $cid = 'cid:' . substr($p->headers['content-id'], 1, -1); $attachments[$cid] = $p->body; } } } return; } $this->_raiseSoapFault('Mime parsing error', '', '', 'Server'); } function _decodeDIMEMessage(&$data, &$headers, &$attachments) { if (!@include_once 'Net/DIME.php') { return $this->_raiseSoapFault('DIME messages are unsupported, the Net_DIME package is not installed'); } // This SHOULD be moved to the transport layer, e.g. PHP itself should // handle parsing DIME ;) $dime =new Net_DIME_Message(); $err = $dime->decodeData($data); if (PEAR::isError($err)) { $this->_raiseSoapFault('Failed to decode the DIME message!', '', '', 'Server'); return; } if (strcasecmp($dime->parts[0]['type'], SOAP_ENVELOP) != 0) { $this->_raiseSoapFault('DIME record 1 is not a SOAP envelop!', '', '', 'Server'); return; } $data = $dime->parts[0]['data']; // Fake it for now. $headers['content-type'] = 'text/xml'; $c = count($dime->parts); for ($i = 0; $i < $c; $i++) { $part =& $dime->parts[$i]; // We need to handle URI's better. $id = strncmp($part['id'], 'cid:', 4) ? 'cid:' . $part['id'] : $part['id']; $attachments[$id] = $part['data']; } } /** * @deprecated Use setTypeTranslation(). */ function __set_type_translation($type, $class = null) { $this->setTypeTranslation($type, $class); } /** * Explicitly sets the translation for a specific class. * * Auto translation works for all cases, but opens ANY class in the script * to be used as a data type, and may not be desireable. * * @param string $type A SOAP type. * @param string $class A PHP class name. */ function setTypeTranslation($type, $class = null) { $tq = new QName($type); if (!$class) { $class = $tq->name; } $this->_type_translation[$type]=$class; } } /** * Class used to handle QNAME values in XML. * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates */ class QName { var $name = ''; var $ns = ''; var $namespace=''; - function QName($name, $namespace = '') + function __construct($name, $namespace = '') { if ($name && $name[0] == '{') { preg_match('/\{(.*?)\}(.*)/', $name, $m); $this->name = $m[2]; $this->namespace = $m[1]; } elseif (substr_count($name, ':') == 1) { $s = explode(':', $name); $s = array_reverse($s); $this->name = $s[0]; $this->ns = $s[1]; $this->namespace = $namespace; } else { $this->name = $name; $this->namespace = $namespace; } // A little more magic than should be in a qname. $p = strpos($this->name, '['); if ($p) { // TODO: Need to re-examine this logic later. // Chop off []. $this->arraySize = explode(',', substr($this->name, $p + 1, -$p - 2)); $this->arrayInfo = substr($this->name, $p); $this->name = substr($this->name, 0, $p); } } function fqn() { if ($this->namespace) { return '{' . $this->namespace . '}' . $this->name; } elseif ($this->ns) { return $this->ns . ':' . $this->name; } return $this->name; } } diff --git a/library/SOAP/Client.php b/library/SOAP/Client.php index a73c0fa..9a4f3e0 100644 --- a/library/SOAP/Client.php +++ b/library/SOAP/Client.php @@ -1,855 +1,855 @@ Original Author * @author Shane Caraveo Port to PEAR and more * @author Chuck Hagenbuch Maintenance * @author Jan Schneider Maintenance * @copyright 2003-2005 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ require_once 'SOAP/Value.php'; require_once 'SOAP/Base.php'; require_once 'SOAP/Transport.php'; require_once 'SOAP/WSDL.php'; require_once 'SOAP/Fault.php'; require_once 'SOAP/Parser.php'; // Arnaud: the following code was taken from DataObject and adapted to suit // this will be horrifically slow!!!! // NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer // these two are BC/FC handlers for call in PHP4/5 if (!class_exists('SOAP_Client_Overload')) { if (substr(zend_version(), 0, 1) > 1) { class SOAP_Client_Overload extends SOAP_Base { function __call($method, $args) { $return = null; $this->_call($method, $args, $return); return $return; } } } else { if (!function_exists('clone')) { eval('function clone($t) { return $t; }'); } eval(' class SOAP_Client_Overload extends SOAP_Base { function __call($method, $args, &$return) { return $this->_call($method, $args, $return); } }'); } } /** * SOAP Client Class * * This class is the main interface for making soap requests. * * basic usage: * $soapclient = new SOAP_Client( string path [ , boolean wsdl] ); * echo $soapclient->call( string methodname [ , array parameters] ); * * or, if using PHP 5+ or the overload extension: * $soapclient = new SOAP_Client( string path [ , boolean wsdl] ); * echo $soapclient->methodname( [ array parameters] ); * * * Originally based on SOAPx4 by Dietrich Ayala * http://dietrich.ganx4.com/soapx4 * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates * @author Stig Bakken Conversion to PEAR * @author Dietrich Ayala Original Author */ class SOAP_Client extends SOAP_Client_Overload { /** * Communication endpoint. * * Currently the following transport formats are supported: * - HTTP * - SMTP * * Example endpoints: * http://www.example.com/soap/server.php * https://www.example.com/soap/server.php * mailto:soap@example.com * * @see SOAP_Client() * @var string */ var $_endpoint = ''; /** * The SOAP PORT name that is used by the client. * * @var string */ var $_portName = ''; /** * Endpoint type e.g. 'wdsl'. * * @var string */ var $_endpointType = ''; /** * The received xml. * * @var string */ var $xml; /** * The outgoing and incoming data stream for debugging. * * @var string */ var $wire; /** * The outgoing data stream for debugging. * * @var string */ var $_last_request = null; /** * The incoming data stream for debugging. * * @var string */ var $_last_response = null; /** * Options. * * @var array */ var $_options = array('trace' => false); /** * The character encoding used for XML parser, etc. * * @var string */ var $_encoding = SOAP_DEFAULT_ENCODING; /** * The array of SOAP_Headers that we are sending. * * @var array */ var $headersOut = null; /** * The headers we recieved back in the response. * * @var array */ var $headersIn = null; /** * Options for the HTTP_Request class (see HTTP/Request.php). * * @var array */ var $_proxy_params = array(); /** * The SOAP_Transport instance. * * @var SOAP_Transport */ var $_soap_transport = null; /** * Constructor. * * @access public * * @param string $endpoint An URL. * @param boolean $wsdl Whether the endpoint is a WSDL file. * @param string $portName The service's port name to use. * @param array $proxy_params Options for the HTTP_Request class * @see HTTP_Request * @param boolean|string $cache Use WSDL caching? The cache directory if * a string. */ - function SOAP_Client($endpoint, $wsdl = false, $portName = false, + function __construct($endpoint, $wsdl = false, $portName = false, $proxy_params = array(), $cache = false) { parent::SOAP_Base('Client'); $this->_endpoint = $endpoint; $this->_portName = $portName; $this->_proxy_params = $proxy_params; // This hack should perhaps be removed as it might cause unexpected // behaviour. $wsdl = $wsdl ? $wsdl : strtolower(substr($endpoint, -4)) == 'wsdl'; // make values if ($wsdl) { $this->_endpointType = 'wsdl'; // instantiate wsdl class $this->_wsdl = new SOAP_WSDL($this->_endpoint, $this->_proxy_params, $cache); if ($this->_wsdl->fault) { $this->_raiseSoapFault($this->_wsdl->fault); } } } function _reset() { $this->xml = null; $this->wire = null; $this->_last_request = null; $this->_last_response = null; $this->headersIn = null; $this->headersOut = null; } /** * Sets the character encoding. * * Limited to 'UTF-8', 'US_ASCII' and 'ISO-8859-1'. * * @access public * * @param string encoding * * @return mixed SOAP_Fault on error. */ function setEncoding($encoding) { if (in_array($encoding, $this->_encodings)) { $this->_encoding = $encoding; return; } return $this->_raiseSoapFault('Invalid Encoding'); } /** * Adds a header to the envelope. * * @access public * * @param SOAP_Header $soap_value A SOAP_Header or an array with the * elements 'name', 'namespace', * 'mustunderstand', and 'actor' to send * as a header. */ function addHeader($soap_value) { // Add a new header to the message. if (is_a($soap_value, 'SOAP_Header')) { $this->headersOut[] = $soap_value; } elseif (is_array($soap_value)) { // name, value, namespace, mustunderstand, actor $this->headersOut[] = new SOAP_Header($soap_value[0], null, $soap_value[1], $soap_value[2], $soap_value[3]); } else { $this->_raiseSoapFault('Invalid parameter provided to addHeader(). Must be an array or a SOAP_Header.'); } } /** * Calls a method on the SOAP endpoint. * * The namespace parameter is overloaded to accept an array of options * that can contain data necessary for various transports if it is used as * an array, it MAY contain a namespace value and a soapaction value. If * it is overloaded, the soapaction parameter is ignored and MUST be * placed in the options array. This is done to provide backwards * compatibility with current clients, but may be removed in the future. * The currently supported values are:
      *   namespace
      *   soapaction
      *   timeout (HTTP socket timeout)
      *   transfer-encoding (SMTP, Content-Transfer-Encoding: header)
      *   from (SMTP, From: header)
      *   subject (SMTP, Subject: header)
      *   headers (SMTP, hash of extra SMTP headers)
      * 
* * @access public * * @param string $method The method to call. * @param array $params The method parameters. * @param string|array $namespace Namespace or hash with options. * @param string $soapAction * * @return mixed The method result or a SOAP_Fault on error. */ function &call($method, $params, $namespace = false, $soapAction = false) { $this->headersIn = null; $this->_last_request = null; $this->_last_response = null; $this->wire = null; $this->xml = null; $soap_data = $this->_generate($method, $params, $namespace, $soapAction); if ((new PEAR)->isError($soap_data)) { $fault = $this->_raiseSoapFault($soap_data); return $fault; } // _generate() may have changed the endpoint if the WSDL has more // than one service, so we need to see if we need to generate a new // transport to hook to a different URI. Since the transport protocol // can also change, we need to get an entirely new object. This could // probably be optimized. if (!$this->_soap_transport || $this->_endpoint != $this->_soap_transport->url) { $this->_soap_transport =& SOAP_Transport::getTransport($this->_endpoint); if ((new PEAR)->isError($this->_soap_transport)) { $fault =& $this->_soap_transport; $this->_soap_transport = null; $fault = $this->_raiseSoapFault($fault); return $fault; } } $this->_soap_transport->encoding = $this->_encoding; // Send the message. $transport_options = array_merge_recursive($this->_proxy_params, $this->_options); $this->xml = $this->_soap_transport->send($soap_data, $transport_options); // Save the wire information for debugging. if ($this->_options['trace']) { $this->_last_request = $this->_soap_transport->outgoing_payload; $this->_last_response = $this->_soap_transport->incoming_payload; $this->wire = $this->getWire(); } if ($this->_soap_transport->fault) { $fault = $this->_raiseSoapFault($this->xml); return $fault; } if (isset($this->_options['result']) && $this->_options['result'] != 'parse') { return $this->xml; } $this->__result_encoding = $this->_soap_transport->result_encoding; $result = &$this->parseResponse($this->xml, $this->__result_encoding, $this->_soap_transport->attachments); return $result; } /** * Sets an option to use with the transport layers. * * For example: * * $soapclient->setOpt('curl', CURLOPT_VERBOSE, 1) * * to pass a specific option to curl if using an SSL connection. * * @access public * * @param string $category Category to which the option applies or option * name. * @param string $option An option name if $category is a category name, * an option value if $category is an option name. * @param string $value An option value if $category is a category * name. */ function setOpt($category, $option, $value = null) { if (!is_null($value)) { if (!isset($this->_options[$category])) { $this->_options[$category] = array(); } $this->_options[$category][$option] = $value; } else { $this->_options[$category] = $option; } } /** * Call method supporting the overload extension. * * If the overload extension is loaded, you can call the client class with * a soap method name: * * $soap = new SOAP_Client(....); * $value = $soap->getStockQuote('MSFT'); * * * @access public * * @param string $method The method to call. * @param array $params The method parameters. * @param mixed $return_value Will get the method's return value * assigned. * * @return boolean Always true. */ function _call($method, $params, &$return_value) { // Overloading lowercases the method name, we need to look into the // WSDL and try to find the correct method name to get the correct // case for the call. if ($this->_wsdl) { $this->_wsdl->matchMethod($method); } $return_value =& $this->call($method, $params); return true; } /** * @deprecated Use getLastRequest(). */ function &__getlastrequest() { $request = $this->getLastRequest(); return $request; } /** * Returns the XML content of the last SOAP request. * * @return string The last request. */ function getLastRequest() { return $this->_last_request; } /** * @deprecated Use getLastResponse(). */ function &__getlastresponse() { $response =& $this->getLastResponse; return $response; } /** * Returns the XML content of the last SOAP response. * * @return string The last response. */ function getLastResponse() { return $this->_last_response; } /** * @deprecated Use setUse(). */ function __use($use) { $this->setUse($use); } /** * Sets the SOAP encoding. * * @param string $use Either 'literal' or 'encoded' (section 5). */ function setUse($use) { $this->_options['use'] = $use; } /** * @deprecated Use setStyle(). */ function __style($style) { $this->setStyle($style); } /** * Sets the SOAP encoding style. * * @param string $style Either 'document' or 'rpc'. */ function setStyle($style) { $this->_options['style'] = $style; } /** * @deprecated Use setTrace(). */ function __trace($level) { $this->setTrace($level); } /** * Sets whether to trace the traffic on the transport level. * * @see getWire() * * @param boolean $trace */ function setTrace($trace) { $this->_options['trace'] = $trace; } function _generate($method, &$params, $namespace = false, $soapAction = false) { $this->fault = null; $this->_options['input'] = 'parse'; $this->_options['result'] = 'parse'; $this->_options['parameters'] = false; if ($params && gettype($params) != 'array') { $params = array($params); } if (gettype($namespace) == 'array') { foreach ($namespace as $optname => $opt) { $this->_options[strtolower($optname)] = $opt; } if (isset($this->_options['namespace'])) { $namespace = $this->_options['namespace']; } else { $namespace = false; } } else { // We'll place $soapAction into our array for usage in the // transport. $this->_options['soapaction'] = $soapAction; $this->_options['namespace'] = $namespace; } if ($this->_endpointType == 'wsdl') { $this->_setSchemaVersion($this->_wsdl->xsd); // Get port name. if (!$this->_portName) { $this->_portName = $this->_wsdl->getPortName($method); } if ((new PEAR)->isError($this->_portName)) { return $this->_raiseSoapFault($this->_portName); } // Get endpoint. $this->_endpoint = $this->_wsdl->getEndpoint($this->_portName); if ((new PEAR)->isError($this->_endpoint)) { return $this->_raiseSoapFault($this->_endpoint); } // Get operation data. $opData = $this->_wsdl->getOperationData($this->_portName, $method); if ((new PEAR)->isError($opData)) { return $this->_raiseSoapFault($opData); } $namespace = $opData['namespace']; $this->_options['style'] = $opData['style']; $this->_options['use'] = $opData['input']['use']; $this->_options['soapaction'] = $opData['soapAction']; // Set input parameters. if ($this->_options['input'] == 'parse') { $this->_options['parameters'] = $opData['parameters']; $nparams = array(); if (isset($opData['input']['parts']) && count($opData['input']['parts'])) { foreach ($opData['input']['parts'] as $name => $part) { $xmlns = ''; $attrs = array(); // Is the name a complex type? if (isset($part['element'])) { $xmlns = $this->_wsdl->namespaces[$part['namespace']]; $part = $this->_wsdl->elements[$part['namespace']][$part['type']]; $name = $part['name']; } if (isset($params[$name]) || $this->_wsdl->getDataHandler($name, $part['namespace'])) { $nparams[$name] =& $params[$name]; } else { // We now force an associative array for // parameters if using WSDL. return $this->_raiseSoapFault("The named parameter $name is not in the call parameters."); } if (gettype($nparams[$name]) != 'object' || !is_a($nparams[$name], 'SOAP_Value')) { // Type is likely a qname, split it apart, and get // the type namespace from WSDL. $qname = new QName($part['type']); if ($qname->ns) { $type_namespace = $this->_wsdl->namespaces[$qname->ns]; } elseif (isset($part['namespace'])) { $type_namespace = $this->_wsdl->namespaces[$part['namespace']]; } else { $type_namespace = null; } $qname->namespace = $type_namespace; $pqname = $name; if ($xmlns) { $pqname = '{' . $xmlns . '}' . $name; } $nparams[$name] = new SOAP_Value($pqname, $qname->fqn(), $nparams[$name], $attrs); } else { // WSDL fixups to the SOAP value. } } } $params =& $nparams; unset($nparams); } } else { $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION); } // Serialize the message. $this->_section5 = (!isset($this->_options['use']) || $this->_options['use'] != 'literal'); if (!isset($this->_options['style']) || $this->_options['style'] == 'rpc') { $this->_options['style'] = 'rpc'; $this->docparams = true; $mqname = new QName($method, $namespace); $methodValue = new SOAP_Value($mqname->fqn(), 'Struct', $params); $soap_msg = $this->makeEnvelope($methodValue, $this->headersOut, $this->_encoding, $this->_options); } else { if (!$params) { $mqname = new QName($method, $namespace); $mynull = null; $params = new SOAP_Value($mqname->fqn(), 'Struct', $mynull); } elseif ($this->_options['input'] == 'parse') { if (is_array($params)) { $nparams = array(); $keys = array_keys($params); foreach ($keys as $k) { if (gettype($params[$k]) != 'object') { $nparams[] = new SOAP_Value($k, false, $params[$k]); } else { $nparams[] =& $params[$k]; } } $params =& $nparams; } if ($this->_options['parameters']) { $mqname = new QName($method, $namespace); $params = new SOAP_Value($mqname->fqn(), 'Struct', $params); } } $soap_msg = $this->makeEnvelope($params, $this->headersOut, $this->_encoding, $this->_options); } unset($this->headersOut); if ((new PEAR)->isError($soap_msg)) { return $this->_raiseSoapFault($soap_msg); } // Handle MIME or DIME encoding. // TODO: DIME encoding should move to the transport, do it here for // now and for ease of getting it done. if (count($this->_attachments)) { if ((isset($this->_options['attachments']) && $this->_options['attachments'] == 'Mime') || isset($this->_options['Mime'])) { $soap_msg = $this->_makeMimeMessage($soap_msg, $this->_encoding); } else { // default is dime $soap_msg = $this->_makeDIMEMessage($soap_msg, $this->_encoding); $this->_options['headers']['Content-Type'] = 'application/dime'; } if ((new PEAR)->isError($soap_msg)) { return $this->_raiseSoapFault($soap_msg); } } // Instantiate client. if (is_array($soap_msg)) { $soap_data = $soap_msg['body']; if (count($soap_msg['headers'])) { if (isset($this->_options['headers'])) { $this->_options['headers'] = array_merge($this->_options['headers'], $soap_msg['headers']); } else { $this->_options['headers'] = $soap_msg['headers']; } } } else { $soap_data = $soap_msg; } return $soap_data; } /** * @deprecated Use parseResponse(). */ function &__parse(&$response, $encoding, &$attachments) { return $this->parseResponse($response, $encoding, $attachments); } /** * Parses a SOAP response. * * @see SOAP_Parser:: * * @param string $response XML content of SOAP response. * @param string $encoding Character set encoding, defaults to 'UTF-8'. * @param array $attachments List of attachments. */ function &parseResponse($response, $encoding, &$attachments) { // Parse the response. $response =new SOAP_Parser($response, $encoding, $attachments); if ($response->fault) { $fault = $this->_raiseSoapFault($response->fault); return $fault; } // Return array of parameters. $return = $response->getResponse(); $headers = $response->getHeaders(); if ($headers) { $this->headersIn = $this->_decodeResponse($headers, false); } $decoded = $this->_decodeResponse($return); return $decoded; } /** * Converts a complex SOAP_Value into a PHP Array * * @param SOAP_Value $response value object * @param boolean $shift FIXME * @return Array */ function &_decodeResponse($response, $shift = true) { if (!$response) { $decoded = null; return $decoded; } // Check for valid response. if ((new PEAR)->isError($response)) { $fault =& $this->_raiseSoapFault($response); return $fault; } elseif (!is_a($response, 'soap_value')) { $fault =& $this->_raiseSoapFault("Didn't get SOAP_Value object back from client"); return $fault; } // Decode to native php datatype. $returnArray =& $this->_decode($response); // Fault? if ((new PEAR)->isError($returnArray)) { $fault =& $this->_raiseSoapFault($returnArray); return $fault; } if (is_object($returnArray) && strcasecmp(get_class($returnArray), 'stdClass') == 0) { $returnArray = get_object_vars($returnArray); } if (is_array($returnArray)) { if (isset($returnArray['faultcode']) || isset($returnArray['SOAP-ENV:faultcode'])) { $faultcode = $faultstring = $faultdetail = $faultactor = ''; foreach ($returnArray as $k => $v) { if (stristr($k, 'faultcode')) $faultcode = $v; if (stristr($k, 'faultstring')) $faultstring = $v; if (stristr($k, 'detail')) $faultdetail = $v; if (stristr($k, 'faultactor')) $faultactor = $v; } $fault =& $this->_raiseSoapFault($faultstring, $faultdetail, $faultactor, $faultcode); return $fault; } // Return array of return values. if ($shift && count($returnArray) == 1) { $decoded = array_shift($returnArray); return $decoded; } return $returnArray; } return $returnArray; } /** * @deprecated Use getWire(). */ function __get_wire() { return $this->getWire(); } /** * Returns the outgoing and incoming traffic on the transport level. * * Tracing has to be enabled. * * @see setTrace() * * @return string The complete traffic between the client and the server. */ function getWire() { if ($this->_options['trace'] && ($this->_last_request || $this->_last_response)) { return "OUTGOING:\n\n" . $this->_last_request . "\n\nINCOMING\n\n" . preg_replace("/>\r\n<", $this->_last_response); } return null; } } diff --git a/library/SOAP/Fault.php b/library/SOAP/Fault.php index 594b817..0d35781 100644 --- a/library/SOAP/Fault.php +++ b/library/SOAP/Fault.php @@ -1,127 +1,127 @@ Original Author * @author Shane Caraveo Port to PEAR and more * @author Chuck Hagenbuch Maintenance * @author Jan Schneider Maintenance * @copyright 2003-2006 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ require_once 'PEAR.php'; /** * PEAR::Error wrapper used to match SOAP Faults to PEAR Errors * * SOAP_Fault transmissions normally contain a complete backtrace of the * error. Revealing these details in a public web services is a bad idea * because it can be used by attackers. Backtrace information can be kept out * of SOAP_Fault responses by putting the following code in your script after * your "require_once 'SOAP/Server.php';" line: * * * $skiptrace =& PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); * $skiptrace = true; * * * @package SOAP * @access public * @author Shane Caraveo Port to PEAR and more * @author Dietrich Ayala Original Author */ class SOAP_Fault extends PEAR_Error { /** * Constructor. * * @param string $faultstring Message string for fault. * @param mixed $faultcode The faultcode. * @param mixed $faultactor * @param mixed $detail @see PEAR_Error * @param array $mode @see PEAR_Error * @param array $options @see PEAR_Error */ - function SOAP_Fault($faultstring = 'unknown error', $faultcode = 'Client', + function __construct($faultstring = 'unknown error', $faultcode = 'Client', $faultactor = null, $detail = null, $mode = null, $options = null) { parent::PEAR_Error($faultstring, $faultcode, $mode, $options, $detail); if ($faultactor) { $this->error_message_prefix = $faultactor; } } /** * Returns a SOAP XML message that can be sent as a server response. * * @return string */ function message($encoding = SOAP_DEFAULT_ENCODING) { $msg = new SOAP_Base(); $params = array(); $params[] = new SOAP_Value('faultcode', 'QName', 'SOAP-ENV:' . $this->code); $params[] = new SOAP_Value('faultstring', 'string', $this->message); $params[] = new SOAP_Value('faultactor', 'anyURI', $this->error_message_prefix); if (isset($this->backtrace)) { $params[] = new SOAP_Value('detail', 'string', $this->backtrace); } else { $params[] = new SOAP_Value('detail', 'string', $this->userinfo); } $methodValue = new SOAP_Value('{' . SOAP_ENVELOP . '}Fault', 'Struct', $params); $headers = null; return $msg->makeEnvelope($methodValue, $headers, $encoding); } /** * Returns a simple native PHP array containing the fault data. * * @return array */ function getFault() { $fault = new stdClass(); $fault->faultcode = $this->code; $fault->faultstring = $this->message; $fault->faultactor = $this->error_message_prefix; $fault->detail = $this->userinfo; return $fault; } /** * Returns the SOAP actor for the fault. * * @return string */ function getActor() { return $this->error_message_prefix; } /** * Returns the fault detail. * * @return string */ function getDetail() { return $this->userinfo; } } diff --git a/library/SOAP/Parser.php b/library/SOAP/Parser.php index d353fc5..fa40c7f 100644 --- a/library/SOAP/Parser.php +++ b/library/SOAP/Parser.php @@ -1,504 +1,504 @@ Original Author * @author Shane Caraveo Port to PEAR and more * @author Chuck Hagenbuch Maintenance * @author Jan Schneider Maintenance * @copyright 2003-2005 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ require_once 'SOAP/Base.php'; require_once 'SOAP/Value.php'; /** * SOAP Parser * * This class is used by SOAP::Message and SOAP::Server to parse soap * packets. Originally based on SOAPx4 by Dietrich Ayala * http://dietrich.ganx4.com/soapx4 * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates * @author Dietrich Ayala Original Author */ class SOAP_Parser extends SOAP_Base { var $status = ''; var $position = 0; var $depth = 0; var $default_namespace = ''; var $message = array(); var $depth_array = array(); var $parent = 0; var $root_struct_name = array(); var $header_struct_name = array(); var $curent_root_struct_name = ''; var $root_struct = array(); var $header_struct = array(); var $curent_root_struct = 0; var $references = array(); var $need_references = array(); /** * Used to handle non-root elements before root body element. * * @var integer */ var $bodyDepth; /** * Constructor. * * @param string $xml XML content. * @param string $encoding Character set encoding, defaults to 'UTF-8'. * @param array $attachments List of attachments. */ - function SOAP_Parser($xml, $encoding = SOAP_DEFAULT_ENCODING, + function __construct($xml, $encoding = SOAP_DEFAULT_ENCODING, $attachments = null) { parent::SOAP_Base('Parser'); $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION); $this->attachments = $attachments; // Check the XML tag for encoding. if (preg_match('/<\?xml[^>]+encoding\s*?=\s*?(\'([^\']*)\'|"([^"]*)")[^>]*?[\?]>/', $xml, $m)) { $encoding = strtoupper($m[2] ? $m[2] : $m[3]); } // Determine where in the message we are (envelope, header, body, // method). Check whether content has been read. if (!empty($xml)) { // Prepare the XML parser. $parser = xml_parser_create($encoding); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_set_object($parser, $this); xml_set_element_handler($parser, '_startElement', '_endElement'); xml_set_character_data_handler($parser, '_characterData'); // Some lame SOAP implementations add nul bytes at the end of the // SOAP stream, and expat chokes on that. if ($xml[strlen($xml) - 1] == 0) { $xml = trim($xml); } // Parse the XML file. if (!xml_parse($parser, $xml, true)) { $err = sprintf('XML error on line %d col %d byte %d %s', xml_get_current_line_number($parser), xml_get_current_column_number($parser), xml_get_current_byte_index($parser), xml_error_string(xml_get_error_code($parser))); $this->_raiseSoapFault($err, htmlspecialchars($xml)); } xml_parser_free($parser); } } /** * Returns an array of responses. * * After parsing a SOAP message, use this to get the response. * * @return array */ function getResponse() { if (!empty($this->root_struct[0])) { return $this->_buildResponse($this->root_struct[0]); } else { return $this->_raiseSoapFault('Cannot build response'); } } /** * Returns an array of header responses. * * After parsing a SOAP message, use this to get the response. * * @return array */ function getHeaders() { if (!empty($this->header_struct[0])) { return $this->_buildResponse($this->header_struct[0]); } else { // We don't fault if there are no headers; that can be handled by // the application if necessary. return null; } } /** * Recurses to build a multi dimensional array. * * @see _buildResponse() */ function _domulti($d, &$ar, &$r, &$v, $ad = 0) { if ($d) { $this->_domulti($d - 1, $ar, $r[$ar[$ad]], $v, $ad + 1); } else { $r = $v; } } /** * Loops through the message, building response structures. * * @param integer $pos Position. * * @return SOAP_Value */ function _buildResponse($pos) { $response = null; if (isset($this->message[$pos]['children'])) { $children = explode('|', $this->message[$pos]['children']); foreach ($children as $c => $child_pos) { if ($this->message[$child_pos]['type'] != null) { $response[] = $this->_buildResponse($child_pos); } } if (isset($this->message[$pos]['arraySize'])) { $ardepth = count($this->message[$pos]['arraySize']); if ($ardepth > 1) { $ar = array_pad(array(), $ardepth, 0); if (isset($this->message[$pos]['arrayOffset'])) { for ($i = 0; $i < $ardepth; $i++) { $ar[$i] += $this->message[$pos]['arrayOffset'][$i]; } } $elc = count($response); for ($i = 0; $i < $elc; $i++) { // Recurse to build a multi dimensional array. $this->_domulti($ardepth, $ar, $newresp, $response[$i]); // Increment our array pointers. $ad = $ardepth - 1; $ar[$ad]++; while ($ad > 0 && $ar[$ad] >= $this->message[$pos]['arraySize'][$ad]) { $ar[$ad] = 0; $ad--; $ar[$ad]++; } } $response = $newresp; } elseif (isset($this->message[$pos]['arrayOffset']) && $this->message[$pos]['arrayOffset'][0] > 0) { // Check for padding. $pad = $this->message[$pos]['arrayOffset'][0] + count($response) * -1; $response = array_pad($response, $pad, null); } } } // Build attributes. $attrs = array(); foreach ($this->message[$pos]['attrs'] as $atn => $atv) { if (!strstr($atn, 'xmlns') && !strpos($atn, ':')) { $attrs[$atn] = $atv; } } // Add current node's value. if ($response) { $nqn = new QName($this->message[$pos]['name'], $this->message[$pos]['namespace']); $tqn = new QName($this->message[$pos]['type'], $this->message[$pos]['type_namespace']); $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), $response, $attrs); if (isset($this->message[$pos]['arrayType'])) { $response->arrayType = $this->message[$pos]['arrayType']; } } else { $nqn = new QName($this->message[$pos]['name'], $this->message[$pos]['namespace']); $tqn = new QName($this->message[$pos]['type'], $this->message[$pos]['type_namespace']); // Check if value is an empty array if ($tqn->name == 'Array') { $response =new SOAP_Value($nqn->fqn(), $tqn->fqn(), array(), $attrs); } else { $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), $this->message[$pos]['cdata'], $attrs); } } // Handle header attribute that we need. if (array_key_exists('actor', $this->message[$pos])) { $response->actor = $this->message[$pos]['actor']; } if (array_key_exists('mustUnderstand', $this->message[$pos])) { $response->mustunderstand = $this->message[$pos]['mustUnderstand']; } return $response; } /** * Start element handler used with the XML parser. */ function _startElement($parser, $name, $attrs) { // Position in a total number of elements, starting from 0. // Update class level position. $pos = $this->position++; // And set mine. $this->message[$pos] = array( 'type' => '', 'type_namespace' => '', 'cdata' => '', 'pos' => $pos, 'id' => ''); // Parent/child/depth determinations. // depth = How many levels removed from root? // Set mine as current global depth and increment global depth value. $this->message[$pos]['depth'] = $this->depth++; // Else add self as child to whoever the current parent is. if ($pos != 0) { if (isset($this->message[$this->parent]['children'])) { $this->message[$this->parent]['children'] .= '|' . $pos; } else { $this->message[$this->parent]['children'] = $pos; } } // Set my parent. $this->message[$pos]['parent'] = $this->parent; // Set self as current value for this depth. $this->depth_array[$this->depth] = $pos; // Set self as current parent. $this->parent = $pos; $qname = new QName($name); // Set status. if (strcasecmp('envelope', $qname->name) == 0) { $this->status = 'envelope'; } elseif (strcasecmp('header', $qname->name) == 0) { $this->status = 'header'; $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name; $this->header_struct[] = $this->curent_root_struct = $pos; $this->message[$pos]['type'] = 'Struct'; } elseif (strcasecmp('body', $qname->name) == 0) { $this->status = 'body'; $this->bodyDepth = $this->depth; // Set method } elseif ($this->status == 'body') { // Is this element allowed to be a root? // TODO: this needs to be optimized, we loop through $attrs twice // now. $can_root = $this->depth == $this->bodyDepth + 1; if ($can_root) { foreach ($attrs as $key => $value) { if (stristr($key, ':root') && !$value) { $can_root = false; } } } if ($can_root) { $this->status = 'method'; $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name; $this->root_struct[] = $this->curent_root_struct = $pos; $this->message[$pos]['type'] = 'Struct'; } } // Set my status. $this->message[$pos]['status'] = $this->status; // Set name. $this->message[$pos]['name'] = htmlspecialchars($qname->name); // Set attributes. $this->message[$pos]['attrs'] = $attrs; // Loop through attributes, logging ns and type declarations. foreach ($attrs as $key => $value) { // If ns declarations, add to class level array of valid // namespaces. $kqn = new QName($key); if ($kqn->ns == 'xmlns') { $prefix = $kqn->name; if (in_array($value, $this->_XMLSchema)) { $this->_setSchemaVersion($value); } $this->_namespaces[$value] = $prefix; // Set method namespace. } elseif ($key == 'xmlns') { $qname->ns = $this->_getNamespacePrefix($value); $qname->namespace = $value; } elseif ($kqn->name == 'actor') { $this->message[$pos]['actor'] = $value; } elseif ($kqn->name == 'mustUnderstand') { $this->message[$pos]['mustUnderstand'] = $value; // If it's a type declaration, set type. } elseif ($kqn->name == 'type') { $vqn = new QName($value); $this->message[$pos]['type'] = $vqn->name; $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->ns); // Should do something here with the namespace of specified // type? } elseif ($kqn->name == 'arrayType') { $vqn = new QName($value); $this->message[$pos]['type'] = 'Array'; if (isset($vqn->arraySize)) { $this->message[$pos]['arraySize'] = $vqn->arraySize; } $this->message[$pos]['arrayType'] = $vqn->name; } elseif ($kqn->name == 'offset') { $this->message[$pos]['arrayOffset'] = split(',', substr($value, 1, strlen($value) - 2)); } elseif ($kqn->name == 'id') { // Save id to reference array. $this->references[$value] = $pos; $this->message[$pos]['id'] = $value; } elseif ($kqn->name == 'href') { if ($value[0] == '#') { $ref = substr($value, 1); if (isset($this->references[$ref])) { // cdata, type, inval. $ref_pos = $this->references[$ref]; $this->message[$pos]['children'] = &$this->message[$ref_pos]['children']; $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata']; $this->message[$pos]['type'] = &$this->message[$ref_pos]['type']; $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize']; $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType']; } else { // Reverse reference, store in 'need reference'. if (!isset($this->need_references[$ref])) { $this->need_references[$ref] = array(); } $this->need_references[$ref][] = $pos; } } elseif (isset($this->attachments[$value])) { $this->message[$pos]['cdata'] = $this->attachments[$value]; } } } // See if namespace is defined in tag. if (isset($attrs['xmlns:' . $qname->ns])) { $namespace = $attrs['xmlns:' . $qname->ns]; } elseif ($qname->ns && !$qname->namespace) { $namespace = $this->_getNamespaceForPrefix($qname->ns); } else { // Get namespace. $namespace = $qname->namespace ? $qname->namespace : $this->default_namespace; } $this->message[$pos]['namespace'] = $namespace; $this->default_namespace = $namespace; } /** * End element handler used with the XML parser. */ function _endElement($parser, $name) { // Position of current element is equal to the last value left in // depth_array for my depth. $pos = $this->depth_array[$this->depth]; // Bring depth down a notch. $this->depth--; $qname = new QName($name); // Get type if not explicitly declared in an xsi:type attribute. // TODO: check on integrating WSDL validation here. if ($this->message[$pos]['type'] == '') { if (isset($this->message[$pos]['children'])) { /* this is slow, need to look at some faster method $children = explode('|', $this->message[$pos]['children']); if (count($children) > 2 && $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) { $this->message[$pos]['type'] = 'Array'; } else { $this->message[$pos]['type'] = 'Struct'; }*/ $this->message[$pos]['type'] = 'Struct'; } else { $parent = $this->message[$pos]['parent']; if ($this->message[$parent]['type'] == 'Array' && isset($this->message[$parent]['arrayType'])) { $this->message[$pos]['type'] = $this->message[$parent]['arrayType']; } else { $this->message[$pos]['type'] = 'string'; } } } // If tag we are currently closing is the method wrapper. if ($pos == $this->curent_root_struct) { $this->status = 'body'; } elseif ($qname->name == 'Body' || $qname->name == 'Header') { $this->status = 'envelope'; } // Set parent back to my parent. $this->parent = $this->message[$pos]['parent']; // Handle any reverse references now. $idref = $this->message[$pos]['id']; if ($idref != '' && isset($this->need_references[$idref])) { foreach ($this->need_references[$idref] as $ref_pos) { // XXX is this stuff there already? $this->message[$ref_pos]['children'] = &$this->message[$pos]['children']; $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata']; $this->message[$ref_pos]['type'] = &$this->message[$pos]['type']; $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize']; $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType']; } } } /** * Element content handler used with the XML parser. */ function _characterData($parser, $data) { $pos = $this->depth_array[$this->depth]; if (isset($this->message[$pos]['cdata'])) { $this->message[$pos]['cdata'] .= $data; } else { $this->message[$pos]['cdata'] = $data; } } } diff --git a/library/SOAP/Transport/HTTP.php b/library/SOAP/Transport/HTTP.php index 7ba8f7d..b47ccea 100644 --- a/library/SOAP/Transport/HTTP.php +++ b/library/SOAP/Transport/HTTP.php @@ -1,620 +1,620 @@ * @author Jan Schneider * @copyright 2003-2006 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ /** * HTTP Transport class * * @package SOAP * @category Web_Services */ /** * Needed Classes */ require_once 'SOAP/Transport.php'; /** * HTTP Transport for SOAP * * @access public * @package SOAP * @author Shane Caraveo * @author Jan Schneider */ class SOAP_Transport_HTTP extends SOAP_Transport { /** * Basic Auth string. * * @var array */ var $headers = array(); /** * Cookies. * * @var array */ var $cookies; /** * Connection timeout in seconds. 0 = none. * * @var integer */ var $timeout = 4; /** * HTTP-Response Content-Type. */ var $result_content_type; var $result_headers = array(); var $result_cookies = array(); /** * SOAP_Transport_HTTP Constructor * * @access public * * @param string $url HTTP url to SOAP endpoint. * @param string $encoding Encoding to use. */ - function SOAP_Transport_HTTP($url, $encoding = SOAP_DEFAULT_ENCODING) + function __construct($url, $encoding = SOAP_DEFAULT_ENCODING) { parent::SOAP_Base('HTTP'); $this->urlparts = @parse_url($url); $this->url = $url; $this->encoding = $encoding; } /** * Sends and receives SOAP data. * * @access public * * @param string Outgoing SOAP data. * @param array Options. * * @return string|SOAP_Fault */ function send($msg, $options = array()) { $this->fault = null; if (!$this->_validateUrl()) { return $this->fault; } if (isset($options['timeout'])) { $this->timeout = (int)$options['timeout']; } if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) { return $this->_sendHTTP($msg, $options); } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) { return $this->_sendHTTPS($msg, $options); } return $this->_raiseSoapFault('Invalid url scheme ' . $this->url); } /** * Sets data for HTTP authentication, creates authorization header. * * @param string $username Username. * @param string $password Response data, minus HTTP headers. * * @access public */ function setCredentials($username, $password) { $this->headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password); } /** * Adds a cookie. * * @access public * @param string $name Cookie name. * @param mixed $value Cookie value. */ function addCookie($name, $value) { $this->cookies[$name] = $value; } /** * Generates the correct headers for the cookies. * * @access private * * @param array $options Cookie options. If 'nocookies' is set and true * the cookies from the last response are added * automatically. 'cookies' is name-value-hash with * a list of cookies to add. * * @return string The cookie header value. */ function _generateCookieHeader($options) { $this->cookies = array(); if (empty($options['nocookies']) && isset($this->result_cookies)) { // Add the cookies we got from the last request. foreach ($this->result_cookies as $cookie) { if ($cookie['domain'] == $this->urlparts['host']) { $this->cookies[$cookie['name']] = $cookie['value']; } } } // Add cookies the user wants to set. if (isset($options['cookies'])) { foreach ($options['cookies'] as $cookie) { if ($cookie['domain'] == $this->urlparts['host']) { $this->cookies[$cookie['name']] = $cookie['value']; } } } $cookies = ''; foreach ($this->cookies as $name => $value) { if (!empty($cookies)) { $cookies .= '; '; } $cookies .= urlencode($name) . '=' . urlencode($value); } return $cookies; } /** * Validate url data passed to constructor. * * @access private * @return boolean */ function _validateUrl() { if (!is_array($this->urlparts) ) { $this->_raiseSoapFault('Unable to parse URL ' . $this->url); return false; } if (!isset($this->urlparts['host'])) { $this->_raiseSoapFault('No host in URL ' . $this->url); return false; } if (!isset($this->urlparts['port'])) { if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) { $this->urlparts['port'] = 80; } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) { $this->urlparts['port'] = 443; } } if (isset($this->urlparts['user'])) { $this->setCredentials(urldecode($this->urlparts['user']), urldecode($this->urlparts['pass'])); } if (!isset($this->urlparts['path']) || !$this->urlparts['path']) { $this->urlparts['path'] = '/'; } return true; } /** * Finds out what the encoding is. * Sets the object property accordingly. * * @access private * @param array $headers Headers. */ function _parseEncoding($headers) { $h = stristr($headers, 'Content-Type'); preg_match_all('/^Content-Type:\s*(.*)$/im', $h, $ct, PREG_SET_ORDER); $n = count($ct); $ct = $ct[$n - 1]; // Strip the string of \r. $this->result_content_type = str_replace("\r", '', $ct[1]); if (preg_match('/(.*?)(?:;\s?charset=)(.*)/i', $this->result_content_type, $m)) { $this->result_content_type = $m[1]; if (count($m) > 2) { $enc = strtoupper(str_replace('"', '', $m[2])); if (in_array($enc, $this->_encodings)) { $this->result_encoding = $enc; } } } // Deal with broken servers that don't set content type on faults. if (!$this->result_content_type) { $this->result_content_type = 'text/xml'; } } /** * Parses the headers. * * @param array $headers The headers. */ function _parseHeaders($headers) { /* Largely borrowed from HTTP_Request. */ $this->result_headers = array(); $headers = preg_split("/\r?\n/", $headers); foreach ($headers as $value) { if (strpos($value,':') === false) { $this->result_headers[0] = $value; continue; } list($name, $value) = preg_split('/:/', $value); $headername = strtolower($name); $headervalue = trim($value); $this->result_headers[$headername] = $headervalue; if ($headername == 'set-cookie') { // Parse a SetCookie header to fill _cookies array. $cookie = array('expires' => null, 'domain' => $this->urlparts['host'], 'path' => null, 'secure' => false); if (!strpos($headervalue, ';')) { // Only a name=value pair. list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $headervalue)); $cookie['name'] = urldecode($cookie['name']); $cookie['value'] = urldecode($cookie['value']); } else { // Some optional parameters are supplied. $elements = explode(';', $headervalue); list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $elements[0])); $cookie['name'] = urldecode($cookie['name']); $cookie['value'] = urldecode($cookie['value']); for ($i = 1; $i < count($elements);$i++) { list($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); if ('secure' == $elName) { $cookie['secure'] = true; } elseif ('expires' == $elName) { $cookie['expires'] = str_replace('"', '', $elValue); } elseif ('path' == $elName OR 'domain' == $elName) { $cookie[$elName] = urldecode($elValue); } else { $cookie[$elName] = $elValue; } } } $this->result_cookies[] = $cookie; } } } /** * Removes HTTP headers from response. * * @return boolean * @access private */ function _parseResponse() { if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $this->incoming_payload, $match)) { $this->response = $match[2]; // Find the response error, some servers response with 500 for // SOAP faults. $this->_parseHeaders($match[1]); list(, $code, $msg) = sscanf($this->result_headers[0], '%s %s %s'); unset($this->result_headers[0]); switch($code) { case 100: // Continue $this->incoming_payload = $match[2]; return $this->_parseResponse(); case 400: $this->_raiseSoapFault("HTTP Response $code Bad Request"); return false; break; case 401: $this->_raiseSoapFault("HTTP Response $code Authentication Failed"); return false; break; case 403: $this->_raiseSoapFault("HTTP Response $code Forbidden"); return false; break; case 404: $this->_raiseSoapFault("HTTP Response $code Not Found"); return false; break; case 407: $this->_raiseSoapFault("HTTP Response $code Proxy Authentication Required"); return false; break; case 408: $this->_raiseSoapFault("HTTP Response $code Request Timeout"); return false; break; case 410: $this->_raiseSoapFault("HTTP Response $code Gone"); return false; break; default: if ($code >= 400 && $code < 500) { $this->_raiseSoapFault("HTTP Response $code Not Found, Server message: $msg"); return false; } } $this->_parseEncoding($match[1]); if ($this->result_content_type == 'application/dime') { // XXX quick hack insertion of DIME if (PEAR::isError($this->_decodeDIMEMessage($this->response, $this->headers, $this->attachments))) { // _decodeDIMEMessage already raised $this->fault return false; } $this->result_content_type = $this->headers['content-type']; } elseif (stristr($this->result_content_type, 'multipart/related')) { $this->response = $this->incoming_payload; if (PEAR::isError($this->_decodeMimeMessage($this->response, $this->headers, $this->attachments))) { // _decodeMimeMessage already raised $this->fault return false; } } elseif ($this->result_content_type != 'text/xml') { $this->_raiseSoapFault($this->response); return false; } // if no content, return false return strlen($this->response) > 0; } $this->_raiseSoapFault('Invalid HTTP Response'); return false; } /** * Creates an HTTP request, including headers, for the outgoing request. * * @access private * * @param string $msg Outgoing SOAP package. * @param array $options Options. * * @return string Outgoing payload. */ function _getRequest($msg, $options) { $this->headers = array(); $action = isset($options['soapaction']) ? $options['soapaction'] : ''; $fullpath = $this->urlparts['path']; if (isset($this->urlparts['query'])) { $fullpath .= '?' . $this->urlparts['query']; } if (isset($this->urlparts['fragment'])) { $fullpath .= '#' . $this->urlparts['fragment']; } if (isset($options['proxy_host'])) { $fullpath = 'http://' . $this->urlparts['host'] . ':' . $this->urlparts['port'] . $fullpath; } if (isset($options['proxy_user'])) { $this->headers['Proxy-Authorization'] = 'Basic ' . base64_encode($options['proxy_user'] . ':' . $options['proxy_pass']); } if (isset($options['user'])) { $this->setCredentials($options['user'], $options['pass']); } $this->headers['User-Agent'] = $this->_userAgent; $this->headers['Host'] = $this->urlparts['host']; $this->headers['Content-Type'] = "text/xml; charset=$this->encoding"; $this->headers['Content-Length'] = strlen($msg); $this->headers['SOAPAction'] = '"' . $action . '"'; $this->headers['Connection'] = 'close'; if (isset($options['headers'])) { $this->headers = array_merge($this->headers, $options['headers']); } $cookies = $this->_generateCookieHeader($options); if ($cookies) { $this->headers['Cookie'] = $cookies; } $headers = ''; foreach ($this->headers as $k => $v) { $headers .= "$k: $v\r\n"; } $this->outgoing_payload = "POST $fullpath HTTP/1.0\r\n" . $headers . "\r\n" . $msg; return $this->outgoing_payload; } /** * Sends the outgoing HTTP request and reads and parses the response. * * @access private * * @param string $msg Outgoing SOAP package. * @param array $options Options. * * @return string Response data without HTTP headers. */ function _sendHTTP($msg, $options) { $this->incoming_payload = ''; $this->_getRequest($msg, $options); $host = $this->urlparts['host']; $port = $this->urlparts['port']; if (isset($options['proxy_host'])) { $host = $options['proxy_host']; $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; } // Send. if ($this->timeout > 0) { $fp = @fsockopen($host, $port, $this->errno, $this->errmsg, $this->timeout); } else { $fp = @fsockopen($host, $port, $this->errno, $this->errmsg); } if (!$fp) { return $this->_raiseSoapFault("Connect Error to $host:$port"); } if ($this->timeout > 0) { // some builds of PHP do not support this, silence the warning @socket_set_timeout($fp, $this->timeout); } if (!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { return $this->_raiseSoapFault("Error POSTing Data to $host"); } // get reponse // XXX time consumer do { $data = fread($fp, 4096); $_tmp_status = socket_get_status($fp); if ($_tmp_status['timed_out']) { return $this->_raiseSoapFault("Timed out read from $host"); } else { $this->incoming_payload .= $data; } } while (!$_tmp_status['eof']); fclose($fp); if (!$this->_parseResponse()) { return $this->fault; } return $this->response; } /** * Sends the outgoing HTTPS request and reads and parses the response. * * @access private * * @param string $msg Outgoing SOAP package. * @param array $options Options. * * @return string Response data without HTTP headers. */ function _sendHTTPS($msg, $options) { /* Check if the required curl extension is installed. */ if (!extension_loaded('curl')) { return $this->_raiseSoapFault('CURL Extension is required for HTTPS'); } $ch = curl_init(); if (isset($options['proxy_host'])) { $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; curl_setopt($ch, CURLOPT_PROXY, $options['proxy_host'] . ':' . $port); } if (isset($options['proxy_user'])) { curl_setopt($ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_pass']); } if (isset($options['user'])) { curl_setopt($ch, CURLOPT_USERPWD, $options['user'] . ':' . $options['pass']); } if (!isset($options['soapaction'])) { $options['soapaction'] = ''; } if (!isset($options['headers']['Content-Type'])) { $options['headers']['Content-Type'] = 'text/xml'; } curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: ' . $options['headers']['Content-Type'] . ';charset=' . $this->encoding, 'SOAPAction: "' . $options['soapaction'] . '"')); curl_setopt($ch, CURLOPT_USERAGENT, $this->_userAgent); if ($this->timeout) { curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); } curl_setopt($ch, CURLOPT_POSTFIELDS, $msg); curl_setopt($ch, CURLOPT_URL, $this->url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_FAILONERROR, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 1); if (defined('CURLOPT_HTTP_VERSION')) { curl_setopt($ch, CURLOPT_HTTP_VERSION, 1); } if (!ini_get('safe_mode') && !ini_get('open_basedir')) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); } $cookies = $this->_generateCookieHeader($options); if ($cookies) { curl_setopt($ch, CURLOPT_COOKIE, $cookies); } if (isset($options['curl'])) { foreach ($options['curl'] as $key => $val) { curl_setopt($ch, $key, $val); } } // Save the outgoing XML. This doesn't quite match _sendHTTP as CURL // generates the headers, but having the XML is usually the most // important part for tracing/debugging. $this->outgoing_payload = $msg; $this->incoming_payload = curl_exec($ch); if (!$this->incoming_payload) { $m = 'curl_exec error ' . curl_errno($ch) . ' ' . curl_error($ch); curl_close($ch); return $this->_raiseSoapFault($m); } curl_close($ch); if (!$this->_parseResponse()) { return $this->fault; } return $this->response; } } diff --git a/library/SOAP/Value.php b/library/SOAP/Value.php index 907d7a4..c51557e 100644 --- a/library/SOAP/Value.php +++ b/library/SOAP/Value.php @@ -1,238 +1,238 @@ Original Author * @author Shane Caraveo Port to PEAR and more * @author Chuck Hagenbuch Maintenance * @author Jan Schneider Maintenance * @copyright 2003-2007 The PHP Group * @license http://www.php.net/license/2_02.txt PHP License 2.02 * @link http://pear.php.net/package/SOAP */ require_once 'SOAP/Base.php'; /** * SOAP::Value * * This class converts values between PHP and SOAP. * * Originally based on SOAPx4 by Dietrich Ayala * http://dietrich.ganx4.com/soapx4 * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates * @author Dietrich Ayala Original Author */ class SOAP_Value { /** * @var string */ var $value = null; /** * @var string */ var $name = ''; /** * @var string */ var $type = ''; /** * Namespace * * @var string */ var $namespace = ''; var $type_namespace = ''; var $attributes = array(); /** * @var string */ var $arrayType = ''; var $options = array(); var $nqn; var $tqn; /** * Constructor. * * @param string $name Name of the SOAP value {namespace}name. * @param mixed $type SOAP value {namespace}type. Determined * automatically if not set. * @param mixed $value Value to set. * @param array $attributes Attributes. */ - function SOAP_Value($name = '', $type = false, $value = null, + function __construct($name = '', $type = false, $value = null, $attributes = array()) { // Detect type if not passed. $this->nqn = new QName($name); $this->name = $this->nqn->name; $this->namespace = $this->nqn->namespace; $this->tqn = new QName($type); $this->type = $this->tqn->name; $this->type_prefix = $this->tqn->ns; $this->type_namespace = $this->tqn->namespace; $this->value = $value; $this->attributes = $attributes; } /** * Serializes this value. * * @param SOAP_Base $serializer A SOAP_Base instance or subclass to * serialize with. * * @return string XML representation of $this. */ function serialize(&$serializer) { return $serializer->_serializeValue($this->value, $this->name, $this->type, $this->namespace, $this->type_namespace, $this->options, $this->attributes, $this->arrayType); } } /** * This class converts values between PHP and SOAP. It is a simple wrapper * around SOAP_Value, adding support for SOAP actor and mustunderstand * parameters. * * Originally based on SOAPx4 by Dietrich Ayala * http://dietrich.ganx4.com/soapx4 * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates * @author Dietrich Ayala Original Author */ class SOAP_Header extends SOAP_Value { /** * Constructor * * @param string $name Name of the SOAP value {namespace}name. * @param mixed $type SOAP value {namespace}type. Determined * automatically if not set. * @param mixed $value Value to set * @param integer $mustunderstand Zero or one. * @param mixed $attributes Attributes. */ function SOAP_Header($name = '', $type, $value, $mustunderstand = 0, $attributes = array()) { if (!is_array($attributes)) { $actor = $attributes; $attributes = array(); } parent::SOAP_Value($name, $type, $value, $attributes); if (isset($actor)) { $this->attributes['SOAP-ENV:actor'] = $actor; } elseif (!isset($this->attributes['SOAP-ENV:actor'])) { $this->attributes['SOAP-ENV:actor'] = 'http://schemas.xmlsoap.org/soap/actor/next'; } $this->attributes['SOAP-ENV:mustUnderstand'] = (int)$mustunderstand; } } /** * This class handles MIME attachements per W3C's Note on Soap Attachements at * http://www.w3.org/TR/SOAP-attachments * * @access public * @package SOAP * @author Shane Caraveo Conversion to PEAR and updates */ class SOAP_Attachment extends SOAP_Value { /** * Constructor. * * @param string $name Name of the SOAP value * @param string $type The attachment's MIME type. * @param string $filename The attachment's file name. Ignored if $file * is provide. * @param string $file The attachment data. */ function SOAP_Attachment($name = '', $type = 'application/octet-stream', $filename, $file = null) { parent::SOAP_Value($name, null, null); $filedata = ($file === null) ? $this->_file2str($filename) : $file; $filename = basename($filename); if (PEAR::isError($filedata)) { $this->options['attachment'] = $filedata; return; } $cid = md5(uniqid(time())); $this->attributes['href'] = 'cid:' . $cid; $this->options['attachment'] = array('body' => $filedata, 'disposition' => $filename, 'content_type' => $type, 'encoding' => 'base64', 'cid' => $cid); } /** * Returns the contents of the given file name as string. * * @access private * * @param string $file_name The file location. * * @return string The file data or a PEAR_Error. */ function _file2str($file_name) { if (!is_readable($file_name)) { return PEAR::raiseError('File is not readable: ' . $file_name); } if (function_exists('file_get_contents')) { return file_get_contents($file_name); } if (!$fd = fopen($file_name, 'rb')) { return PEAR::raiseError('Could not open ' . $file_name); } $cont = fread($fd, filesize($file_name)); fclose($fd); return $cont; } }