Can I modify the http headers sent via a SOAP request - php

I'm having trouble getting SOAP compression working and after reading the documentation for the service I've realised it's because they've decided to ignore the HTTP standard and do their own thing.
Basically I need to be able to set the content-encoding header to:
Content-Encoding: accept-gzip,accept-deflate
Rather than using Accept-Encoding, they've decided to use content-encoding which is incredibly annoying and there's zero chance of them changing it.
If I set the compression option in the SOAP client it sends
Content-Encoding: gzip
Which then causes the SOAP client to throw an exception saying "Unknown Content-Encoding".
So is it possible to alter the http request that gets sent using the default php SOAP client?

Maye this help somebody:
$mode = array (
'soap_version' => 'SOAP_1_1', // use soap 1.1 client
'keep_alive' => true,
'trace' => 1,
'encoding' =>'UTF-8',
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP | SOAP_COMPRESSION_DEFLATE,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'stream_context' => stream_context_create (
array (
'http' => array('header' => 'Content-Encoding: XXXXXXX'),
)
)
);
// initialize soap client
$client = new LoEnvioSoapClient ( $this->wsdl, $mode );

Why not set your own soap headers? If needed, extend the default class and implemented your own version of it.

It's possible to change the HTTP headers used but only by extending PHP's native SoapClient class.
Something like this:
class MySoapClient extends \SoapClient
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
//Send the HTTP request and get the response using curl or fsockopen,
//of course setting Content-Encoding to accept-gzip,accept-deflate.
//Also set Accept-Encoding to deflate
//Put the response in a variable called $response
//Set the headers used for this request; this is how you would do it if you used curl:
$this->__last_request_headers = curl_getinfo($curlHandle, CURLINFO_HEADER_OUT);
$this->__last_request = $request;
//decompress the response
$response = gzinflate( substr($response, 10, -8) );
return $response;
}
}
It seems that the OP is already aware of this, but here's a tip for others who may not be aware of this: to see the SOAP request as it would be sent out by PHP's native SoapClient class, set the 'trace' option to true:
$client = new \SoapClient($wsdlPath, array('trace'=>true));
Then, after executing your SOAP request you can do this to see the headers that were used:
var_dump( $client->__getLastRequestHeaders() );

Related

Adding WSSE security headers to PHP's SoapServer Response, using robrichards/wse-php

I am trying to add WSSE Security headers to a SOAP XML message that is created from PHP's SoapServer::handle(). This should be done using SoapServer::addSoapHeaders(new SoapHeaders(...)), but I am unsure how to set specific WSSE security headers to the response by using robrichards/wse-php package.
Firstly the SoapServer is created. Then the incoming request gets handled, which return some stdClass with data that the handle() function presumably automatically converts to a XML SOAP envelope. This is wrapped in Laravel's Illuminate\Http\Response object and returned.
ini_set('soap.wsdl_cache_enabled', 0);
ini_set('soap.wsdl_cache_ttl', 0);
ini_set('default_socket_timeout', 80);
header("Connection: close");
$soap = $this->createSoapServer();
ob_start();
// Response automatically becomes a XML, because of soap->handle() from PHP's SoapServer.
$response = new Response($soap->handle($xml_request), 200);
$response->header('Content-Type', 'text/xml');
return $response;
Within the createSoapServer() function I create a SoapServer and want to add WSSE Security headers to the SoapServer using addSoapHeaders(). The headers I need to add are all present withing an empty soap envelope in the headers_xml variable $headers_xml = $objWSSE->saveXML();.
I don't know how to get these headers separately.
I wish to know how to add these headers to the XML response created by the SoapServer. I should be able to add them using addSoapHeaders(), though I do not know how.
private function createSoapServer($soap_settings = []) {
$soap_settings = $this->assembleSoapSettings($soap_settings);
$wsdl_path = $soap_settings['wsdl_path'];
// Set soap's own options
$soap_settings['soap_options'] = array_merge([
WSDL_CACHE_NONE,
SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => !$this->isProduction,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'use' => SOAP_LITERAL,
'connection_timeout' => 80,
'soap_version' => SOAP_1_2,
], $soap_settings['soap_options']);
$soap = new SoapServer($wsdl_path,
array_merge([
'location' => $soap_settings['soap_location'],
'local_cert' => $soap_settings['ssl_cert_path'],
'passphrase' => $soap_settings['ssl_cert_password'],
], $soap_settings['soap_options'])
);
// Sets the server php class where the incoming request gets handled.
$soap->setClass($service_server);
// Retrieve empty XML envenlope to set headers in.
// NOTICE: This is not the correct approach,
$request = file_get_contents(app_path('Connect/Register/empty_soap.xml'));
$options = $this->soapclient_options;
$dom = new DOMDocument('1.0');
$dom->loadXML($request);
$objWSA = new WSASoap($dom, WSASoap::WSANS_2005);
Log::channel('soap-response')->info("Hit 3");
/** Add Addressing */
$objWSA->addFrom($options['wsa_addressing_from']);
$objWSA->addTo($options['wsa_addressing_to']);
$objWSA->addAction($options['wsaAction']);
/** Set needed soap header settings */
$objWSA->addMessageID();
$dom = $objWSA->getDoc();
/* Sign all headers to include signing the WS-Addressing headers */
$objWSSE = new WSSESoap($dom);
$objWSSE->signAllHeaders = true;
$objWSSE->addTimestamp();
/* create new XMLSec Key using RSA SHA256 and type is private key */
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private'));
/* load the private key from file*/
if (isset($options['ssl_private_key_passphrase'])) {
$objKey->passphrase = $options['ssl_private_key_passphrase'];
}
$objKey->loadKey($options['ssl_private_key_path'], true);
/* Sign the message - also signs appropraite WS-Security items */
$objWSSE->signSoapDoc($objKey,
[
'algorithm' => XMLSecurityDSig::SHA256,
'insertBefore' => false,
]
);
/* Add certificate (BinarySecurityToken) to the message and attach pointer to Signature */
$token = $objWSSE->addBinaryToken(file_get_contents($options['ssl_cert_path']));
$objWSSE->attachTokentoSig($token);
/** NOTICE: Problem here! How to get correct type of headers to put into 'addSoapHeaders' of PHP's SoapServer */
$headers_xml = $objWSSE->saveXML();
$soap->addSoapHeaders(new SoapHeader("ns", $headers_xml, "value"));
return $soap;
}
(Please tell if this question is badly formatted or missing information, as this is my first time writing.)

How to properly set a Cookie/Header for a SOAP client in PHP?

I am trying to enable xDebug support for a SOAP call following several topics I have found out there (see list at the end of this post) and this is what I have done so far:
$this->_client_soap = new SoapClient(
$this->ecase_wsdl,
array(
'trace' => 1,
'exceptions' => true,
'soap_version' => SOAP_1_1,
'cache_wsdl' => WSDL_CACHE_NONE
)
);
// Xdebug Support
$xdebug_remote_address = $this->CI->config->item('xdebug_remote_address');
$xdebug_cookie = $this->CI->config->item('xdebug_cookie');
if ($xdebug_remote_address && $xdebug_cookie) {
$this->_client_soap->setCookie('X-Xdebug-Remote-Address', $xdebug_remote_address);
$this->_client_soap->setCookie('Cookie', $xdebug_cookie);
}
$soap_string = $this->build_add_new_case_xml_string();
$ecase_response = $this->_client_soap->__doRequest(
$soap_string,
$this->ecase_wsdl,
$this->service,
SOAP_1_1
);
But I am getting the following SoapFault error message:
Function ("setCookie") is not a valid method for this service
What I am missing here? What is the right way to set a Cookie/Header? My PHP version is 5.3.3
Articles checked before:
https://gist.github.com/Marko-M/10238b76c268ca9d47e5
Debugging a SOAP service using xDebug
http://www.practicalweb.co.uk/blog/2010/10/14/debugging-soap-with-xdebug-and-eclipse/
Nusoap set and get headers in both client and server side
http://php.net/manual/en/soapclient.setcookie.php
But seeing that the function is defined as magic, you're not supposed to call it directly.
Take care.

SOAP Server always returns "rpc:ProcedureNotPresentProcedure not present"

I'm setting up a Soap service using PHP's SoapServer. No matter what I do I get "rpc:ProcedureNotPresentProcedure not present."
I'm using curl to post to it. It looks like this.
$payload = $data['xml'];
try {
$soap = curl_init('http://localhost/Connector/index.php');
curl_setopt_array($soap, array (
CURLOPT_CONNECTTIMEOUT => 120,
CURLOPT_TIMEOUT => 120,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_SSL_VERIFYHOST => FALSE,
CURLOPT_POST => TRUE
));
curl_setopt_array($soap, array (
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => array (
'Content-Type: text/xml; charset=utf-8',
'Content-Length: ' . strlen($payload)
)
));
$response = curl_exec($soap);
The $payload variable contains the xml that looks like this.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Envelope xmlns:ns2="http://www.w3.org/2003/05/soap-envelope" xmlns:ns3="http://localhost/Connector/">
<ns2:Body>
<ns3:addNewAccount>
<systemData>
<id>QWQ</id>
</systemData>
<customerData>
<id>666</id>
<name>ACME UNLIMITED</name>
<billCycleDay>1</billCycleDay>
</customerData>
</ns3:addNewAccount>
</ns2:Body>
</ns2:Envelope>
My Soap Server looks like this.
<?php
$soapoptions = array (
'classmap' => ConnectorService::$classmap,
);
$soapoptions['cache_wsdl'] = WSDL_CACHE_NONE;
$soapoptions['trace'] = TRUE;
file_put_contents('/tmp/debug', print_r(file_get_contents('php://input'), TRUE), FILE_APPEND);
$connectorService = new \SoapServer("/ConnectorService/wsdl/connector.wsdl", $soapoptions);
$connectorService->setClass("ConnectorService");
file_put_contents('/tmp/debug', print_r($connectorService->getFunctions(), TRUE), FILE_APPEND);
$connectorService->handle();
I'm getting the xml in the request, it gets written to the /tmp/debug file, as does the functions available (addNewAccount).
The ConnectorService class looks like this.
class ConnectorService implements AccountInterface
{
public static $classmap = array(
'addNewAccount'=>'addNewAccount'
);
public function addNewAccount($mixed = NULL)
{
$args = func_get_args();
$response = new AccountResponse();
$handler = new AddNewAccountHandler();
$result = $handler->process($args[0]);
// return the result
return $result->getSoapVar();
}
}
I can't for the life of me figure out why I'm getting "rpc:ProcedureNotPresentProcedure not present."
UPDATE
If I don't rely on php://input to provide the xml to the SoapServer, but rather I load xml from the filesystem and pass it to the SoapServer->handle() method, I'm getting it to work.
If I dump the contents of php://input, it does have the xml in it. So why doesn't SoapServer pick it up. As I understand it, if you don't provide a parameter to SoapServer->handle() it supposed to check php://input for the xml.
What would cause php://input to not work or for SoapServer to not be reading it?
I figured it out.
I've got a new development stack setup on VirtualBox with Ubuntu 14.04 and I didn't have the php-soap package installed. I don't know what this package provides, without it I was able to instantiate SoapServer and SoapClient and I was able to make calls to methods in my wsdl, but it didn't read php://input when I wanted to just throw xml at the Soap server.
So I installed php-soap
apt-get install php-soap
It installed php-auth-sasl php-http-request php-mail php-mail-mime php-net-dime php-net-smtp php-net-socket php-net-url php-soap
I think that php-http-request is probably the one that I really needed. But either way, that's what you need to do if you run into this goofy problem. Total durp problem that I spent way too much time noodling around with.

PHP SoapClient::__construct uncustomized request

I use PHP SoapClient to work with my JAVA application.
Here's a pseudo-code of my extended SoapClient class:
class MyClient extends SoapClient {
public function __construct($url) {
$params = Array (
'login' => 'some_login',
'password' => 'some_password',
'trace' => true,
'exceptions' => true,
'connection_timeout' => 10,
'cache_wsdl' => WSDL_CACHE_NONE,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'encoding' => 'UTF-8'
);
try {
parent::__construct($url, $params);
}
catch(Exception $e) {
/* some logging action */
}
catch(SoapFault $e) {
/* some logging action */
}
}
So I make a new object using my class:
$obj = new MyClient("http://location/file.wsdl");
and it sending a GET request to get xml structure in response:
GET /file.wsdl HTTP/1.0
Host: location
Authorization: Basic ....
A problem is that I can't use SoapClient::__getLastRequest, SoapClient:: __getLastResponse, SoapClient::__getLastResponseHeaders and SoapClient::__setCookie, even SoapClient::_cookies in constructor, because all of these functions are valid after __call or __doRequest only, but a constructor never fire __doRequest.
So I can't customize a constructor request to set and get cookies in result. Because of this my application session management doesn't work correct, because a constructor request fire a new session but I can't get jsessionid cookie in a response.
I've tried to search for ::__construct source code to override it with my own cookie-supported code but nothing.
All of tips in the Internet to use CURL for SOAP requests based on overriding __call or __doRequest functions but a constructor doesnt use them.
Please, any ideas how can I extend a constructor with a custom request?
UPDATE:
I just used soapclient in non-WSDL mode which doesn't fetch a file in a constructor and doesn't send any uncustomized requests. Thanx for your tips, problem solved.

SoapClient set custom HTTP Header

I am doing some work writing a PHP-based SOAP client application that uses the SOAP libraries native to PHP5. I need to send a an HTTP cookie and an additional HTTP header as part of the request. The cookie part is no problem:
Code:
$client = new SoapClient($webServiceURI, array("exceptions" => 0, "trace" => 1, "encoding" => $phpInternalEncoding));
$client->__setCookie($kkey, $vvalue);
My problem is the HTTP header. I was hoping there would have been a function named
__setHeader
or
__setHttpHeader
in the SOAP libraries. But no such luck.
Anyone else dealt with this? Is there a workaround? Would a different SOAP library be easier to work with? Thanks.
(I found this unanswerd question here http://www.phpfreaks.com/forums/index.php?topic=125387.0, I copied it b/c i've the same issue)
Try setting a stream context for the soap client:
$client = new SoapClient($webServiceURI, array(
"exceptions" => 0,
"trace" => 1,
"encoding" => $phpInternalEncoding,
'stream_context' => stream_context_create(array(
'http' => array(
'header' => 'SomeCustomHeader: value'
),
)),
));
This answer is the proper way to do it in PHP 5.3+ SoapClient set custom HTTP Header
However, PHP 5.2 does not take all of the values from the stream context into consideration. To get around this, you can make a subclass that handles it for you (in a hacky way, but it works).
class SoapClientBackport extends SoapClient {
public function __construct($wsdl, $options = array()){
if($options['stream_context'] && is_resource($options['stream_context'])){
$stream_context_options = stream_context_get_options($options['stream_context']);
$user_agent = (isset($stream_context_options['http']['user_agent']) ? $stream_context_options['http']['user_agent'] : "PHP-SOAP/" . PHP_VERSION) . "\r\n";
if(isset($stream_context_options['http']['header'])){
if(is_string($stream_context_options['http']['header'])){
$user_agent .= $stream_context_options['http']['header'] . "\r\n";
}
else if(is_array($stream_context_options['http']['header'])){
$user_agent .= implode("\r\n", $stream_context_options['http']['header']);
}
}
$options['user_agent'] = $user_agent;
}
parent::__construct($wsdl, $options);
}
}
I ran into a situation where I had to provide a hash of all the text of the soap request in the HTTP header of the request for authentication purposes. I accomplished this by subclassing SoapClient and using the stream_context option to set the header:
class AuthenticatingSoapClient extends SoapClient {
private $secretKey = "secretKeyString";
private $context;
function __construct($wsdl, $options) {
// Create the stream_context and add it to the options
$this->context = stream_context_create();
$options = array_merge($options, array('stream_context' => $this->context));
parent::SoapClient($wsdl, $options);
}
// Override doRequest to calculate the authentication hash from the $request.
function __doRequest($request, $location, $action, $version, $one_way = 0) {
// Grab all the text from the request.
$xml = simplexml_load_string($request);
$innerText = dom_import_simplexml($xml)->textContent;
// Calculate the authentication hash.
$encodedText = utf8_encode($innerText);
$authHash = base64_encode(hash_hmac("sha256", $encodedText, $this->secretKey, true));
// Set the HTTP headers.
stream_context_set_option($this->context, array('http' => array('header' => 'AuthHash: '. $authHash)));
return (parent::__doRequest($request, $location, $action, $version, $one_way));
}
}
Maybe someone searching will find this useful.
its easy to implement in nuSoap:
NUSOAP.PHP
add to class nusoap_base:
var additionalHeaders = array();
then goto function send of the same class
and add
foreach ($this->additionalHeaders as $key => $value) {
$http->setHeader($key, $value);
}
somewhere around (just before)
$http->setSOAPAction($soapaction); (line 7596)
now you can easy set headers:
$soapClient = new nusoap_client('wsdl adress','wsdl');
$soapClient->additionalHeaders = array('key'=>'val','key2'=>'val');
The SoapClient::__soapCall method has an $input_headers argument, which takes an array of SoapHeaders.
You could also use Zend Framework's SOAP client, which provides an addSoapInputHeader convenience method.

Categories