upgrade zend (#1559)
[openemr.git] / vendor / zendframework / zend-soap / src / Server.php
blob1262fc64a69ecfcdc4b334710ef58d1d3f57316a
1 <?php
2 /**
3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
8 */
10 namespace Zend\Soap;
12 use SoapServer;
13 use SoapFault;
14 use Traversable;
15 use DOMDocument;
16 use DOMNode;
17 use SimpleXMLElement;
18 use ReflectionClass;
19 use Zend\Server\Server as ZendServerServer;
20 use Zend\Stdlib\ArrayUtils;
22 class Server implements ZendServerServer
24 /**
25 * Actor URI
26 * @var string URI
28 protected $actor;
30 /**
31 * Class registered with this server
32 * @var string
34 protected $class;
36 /**
37 * Server instance
38 * @var SoapServer
40 protected $server = null;
41 /**
42 * Arguments to pass to {@link $class} constructor
43 * @var array
45 protected $classArgs = [];
47 /**
48 * Array of SOAP type => PHP class pairings for handling return/incoming values
49 * @var array
51 protected $classmap;
53 /**
54 * Encoding
55 * @var string
57 protected $encoding;
59 /**
60 * Registered fault exceptions
61 * @var array
63 protected $faultExceptions = [];
65 /**
66 * Container for caught exception during business code execution
67 * @var \Exception
69 protected $caughtException = null;
71 /**
72 * SOAP Server Features
73 * @var int
75 protected $features;
77 /**
78 * Functions registered with this server; may be either an array or the SOAP_FUNCTIONS_ALL constant
79 * @var array|int
81 protected $functions = [];
83 /**
84 * Object registered with this server
86 protected $object;
88 /**
89 * Informs if the soap server is in debug mode
90 * @var bool
92 protected $debug = false;
94 /**
95 * Persistence mode; should be one of the SOAP persistence constants
96 * @var int
98 protected $persistence;
101 * Request XML
102 * @var string
104 protected $request;
107 * Response XML
108 * @var string
110 protected $response;
113 * Flag: whether or not {@link handle()} should return a response instead of automatically emitting it.
114 * @var bool
116 protected $returnResponse = false;
119 * SOAP version to use; SOAP_1_2 by default, to allow processing of headers
120 * @var int
122 protected $soapVersion = SOAP_1_2;
125 * Array of type mappings
126 * @var array
128 protected $typemap;
131 * URI namespace for SOAP server
132 * @var string URI
134 protected $uri;
137 * URI or path to WSDL
138 * @var string
140 protected $wsdl;
143 * WSDL Caching Options of SOAP Server
144 * @var mixed
146 protected $wsdlCache;
149 * The send_errors Options of SOAP Server
150 * @var bool
152 protected $sendErrors;
155 * Allows LIBXML_PARSEHUGE Options of DOMDocument->loadXML( string $source [, int $options = 0 ] ) to be set
156 * @var bool
158 protected $parseHuge;
161 * Constructor
163 * Sets display_errors INI setting to off (prevent client errors due to bad
164 * XML in response). Registers {@link handlePhpErrors()} as error handler
165 * for E_USER_ERROR.
167 * If $wsdl is provided, it is passed on to {@link setWSDL()}; if any
168 * options are specified, they are passed on to {@link setOptions()}.
170 * @param string $wsdl
171 * @param array $options
172 * @throws Exception\ExtensionNotLoadedException
174 public function __construct($wsdl = null, array $options = null)
176 if (! extension_loaded('soap')) {
177 throw new Exception\ExtensionNotLoadedException('SOAP extension is not loaded.');
180 if (null !== $wsdl) {
181 $this->setWSDL($wsdl);
184 if (null !== $options) {
185 $this->setOptions($options);
190 * Set Options
192 * Allows setting options as an associative array of option => value pairs.
194 * @param array|\Traversable $options
195 * @return self
197 public function setOptions($options)
199 if ($options instanceof Traversable) {
200 $options = ArrayUtils::iteratorToArray($options);
203 foreach ($options as $key => $value) {
204 switch (strtolower($key)) {
205 case 'actor':
206 $this->setActor($value);
207 break;
209 case 'classmap':
210 case 'class_map':
211 $this->setClassmap($value);
212 break;
214 case 'typemap':
215 case 'type_map':
216 $this->setTypemap($value);
217 break;
219 case 'encoding':
220 $this->setEncoding($value);
221 break;
223 case 'soapversion':
224 case 'soap_version':
225 $this->setSoapVersion($value);
226 break;
228 case 'uri':
229 $this->setUri($value);
230 break;
232 case 'wsdl':
233 $this->setWSDL($value);
234 break;
236 case 'cache_wsdl':
237 $this->setWSDLCache($value);
238 break;
240 case 'features':
241 $this->setSoapFeatures($value);
242 break;
244 case 'send_errors':
245 $this->setSendErrors($value);
246 break;
248 case 'parse_huge':
249 $this->setParseHuge($value);
250 break;
252 default:
253 break;
257 return $this;
261 * Return array of options suitable for using with SoapServer constructor
263 * @return array
265 public function getOptions()
267 $options = [];
268 if (null !== $this->actor) {
269 $options['actor'] = $this->getActor();
272 if (null !== $this->classmap) {
273 $options['classmap'] = $this->getClassmap();
276 if (null !== $this->typemap) {
277 $options['typemap'] = $this->getTypemap();
280 if (null !== $this->encoding) {
281 $options['encoding'] = $this->getEncoding();
284 if (null !== $this->soapVersion) {
285 $options['soap_version'] = $this->getSoapVersion();
288 if (null !== $this->uri) {
289 $options['uri'] = $this->getUri();
292 if (null !== $this->features) {
293 $options['features'] = $this->getSoapFeatures();
296 if (null !== $this->wsdlCache) {
297 $options['cache_wsdl'] = $this->getWSDLCache();
300 if (null !== $this->sendErrors) {
301 $options['send_errors'] = $this->getSendErrors();
304 if (null !== $this->parseHuge) {
305 $options['parse_huge'] = $this->getParseHuge();
308 return $options;
312 * Set encoding
314 * @param string $encoding
315 * @return self
316 * @throws Exception\InvalidArgumentException with invalid encoding argument
318 public function setEncoding($encoding)
320 if (! is_string($encoding)) {
321 throw new Exception\InvalidArgumentException('Invalid encoding specified');
324 $this->encoding = $encoding;
325 return $this;
329 * Get encoding
331 * @return string
333 public function getEncoding()
335 return $this->encoding;
339 * Set SOAP version
341 * @param int $version One of the SOAP_1_1 or SOAP_1_2 constants
342 * @return self
343 * @throws Exception\InvalidArgumentException with invalid soap version argument
345 public function setSoapVersion($version)
347 if (! in_array($version, [SOAP_1_1, SOAP_1_2])) {
348 throw new Exception\InvalidArgumentException('Invalid soap version specified');
351 $this->soapVersion = $version;
352 return $this;
356 * Get SOAP version
358 * @return int
360 public function getSoapVersion()
362 return $this->soapVersion;
366 * Check for valid URN
368 * @param string $urn
369 * @return true
370 * @throws Exception\InvalidArgumentException on invalid URN
372 public function validateUrn($urn)
374 $scheme = parse_url($urn, PHP_URL_SCHEME);
375 if ($scheme === false || $scheme === null) {
376 throw new Exception\InvalidArgumentException('Invalid URN');
379 return true;
383 * Set actor
385 * Actor is the actor URI for the server.
387 * @param string $actor
388 * @return self
390 public function setActor($actor)
392 $this->validateUrn($actor);
393 $this->actor = $actor;
394 return $this;
398 * Retrieve actor
400 * @return string
402 public function getActor()
404 return $this->actor;
408 * Set URI
410 * URI in SoapServer is actually the target namespace, not a URI; $uri must begin with 'urn:'.
412 * @param string $uri
413 * @return self
415 public function setUri($uri)
417 $this->validateUrn($uri);
418 $this->uri = $uri;
419 return $this;
423 * Retrieve URI
425 * @return string
427 public function getUri()
429 return $this->uri;
433 * Set classmap
435 * @param array $classmap
436 * @return self
437 * @throws Exception\InvalidArgumentException for any invalid class in the class map
439 public function setClassmap($classmap)
441 if (! is_array($classmap)) {
442 throw new Exception\InvalidArgumentException('Classmap must be an array');
444 foreach ($classmap as $class) {
445 if (! class_exists($class)) {
446 throw new Exception\InvalidArgumentException('Invalid class in class map');
450 $this->classmap = $classmap;
451 return $this;
455 * Retrieve classmap
457 * @return mixed
459 public function getClassmap()
461 return $this->classmap;
465 * Set typemap with xml to php type mappings with appropriate validation.
467 * @param array $typeMap
468 * @return self
469 * @throws Exception\InvalidArgumentException
471 public function setTypemap($typeMap)
473 if (! is_array($typeMap)) {
474 throw new Exception\InvalidArgumentException('Typemap must be an array');
477 foreach ($typeMap as $type) {
478 if (! is_callable($type['from_xml'])) {
479 throw new Exception\InvalidArgumentException(sprintf(
480 'Invalid from_xml callback for type: %s',
481 $type['type_name']
484 if (! is_callable($type['to_xml'])) {
485 throw new Exception\InvalidArgumentException('Invalid to_xml callback for type: ' . $type['type_name']);
489 $this->typemap = $typeMap;
490 return $this;
494 * Retrieve typemap
496 * @return array
498 public function getTypemap()
500 return $this->typemap;
504 * Set wsdl
506 * @param string $wsdl URI or path to a WSDL
507 * @return self
509 public function setWSDL($wsdl)
511 $this->wsdl = $wsdl;
512 return $this;
516 * Retrieve wsdl
518 * @return string
520 public function getWSDL()
522 return $this->wsdl;
526 * Set the SOAP Feature options.
528 * @param string|int $feature
529 * @return self
531 public function setSoapFeatures($feature)
533 $this->features = $feature;
534 return $this;
538 * Return current SOAP Features options
540 * @return int
542 public function getSoapFeatures()
544 return $this->features;
548 * Set the SOAP WSDL Caching Options
550 * @param string|int|bool $options
551 * @return self
553 public function setWSDLCache($options)
555 $this->wsdlCache = $options;
556 return $this;
560 * Get current SOAP WSDL Caching option
562 public function getWSDLCache()
564 return $this->wsdlCache;
568 * Set the SOAP send_errors Option
570 * @param bool $sendErrors
571 * @return self
573 public function setSendErrors($sendErrors)
575 $this->sendErrors = (bool) $sendErrors;
576 return $this;
580 * Get current SOAP send_errors option
582 * @return bool
584 public function getSendErrors()
586 return $this->sendErrors;
590 * Set flag to allow DOMDocument->loadXML() to parse huge nodes
592 * @param bool $parseHuge
593 * @return self
595 public function setParseHuge($parseHuge)
597 $this->parseHuge = (bool) $parseHuge;
598 return $this;
602 * Get flag to allow DOMDocument->loadXML() to parse huge nodes
604 * @return bool
606 public function getParseHuge()
608 return $this->parseHuge;
612 * Attach a function as a server method
614 * @param array|string $function Function name, array of function names to attach,
615 * or SOAP_FUNCTIONS_ALL to attach all functions
616 * @param string $namespace Ignored
617 * @return self
618 * @throws Exception\InvalidArgumentException on invalid functions
620 public function addFunction($function, $namespace = '')
622 // Bail early if set to SOAP_FUNCTIONS_ALL
623 if ($this->functions == SOAP_FUNCTIONS_ALL) {
624 return $this;
627 if (is_array($function)) {
628 foreach ($function as $func) {
629 if (is_string($func) && function_exists($func)) {
630 $this->functions[] = $func;
631 } else {
632 throw new Exception\InvalidArgumentException('One or more invalid functions specified in array');
635 } elseif (is_string($function) && function_exists($function)) {
636 $this->functions[] = $function;
637 } elseif ($function == SOAP_FUNCTIONS_ALL) {
638 $this->functions = SOAP_FUNCTIONS_ALL;
639 } else {
640 throw new Exception\InvalidArgumentException('Invalid function specified');
643 if (is_array($this->functions)) {
644 $this->functions = array_unique($this->functions);
647 return $this;
651 * Attach a class to a server
653 * Accepts a class name to use when handling requests. Any additional
654 * arguments will be passed to that class' constructor when instantiated.
656 * See {@link setObject()} to set pre-configured object instances as request handlers.
658 * @param string|object $class Class name or object instance which executes
659 * SOAP Requests at endpoint.
660 * @param string $namespace
661 * @param null|array $argv
662 * @return self
663 * @throws Exception\InvalidArgumentException if called more than once, or if class does not exist
665 public function setClass($class, $namespace = '', $argv = null)
667 if (isset($this->class)) {
668 throw new Exception\InvalidArgumentException(
669 'A class has already been registered with this soap server instance'
673 if (is_object($class)) {
674 return $this->setObject($class);
677 if (! is_string($class)) {
678 throw new Exception\InvalidArgumentException(sprintf(
679 'Invalid class argument (%s)',
680 gettype($class)
684 if (! class_exists($class)) {
685 throw new Exception\InvalidArgumentException(sprintf(
686 'Class "%s" does not exist',
687 $class
691 $this->class = $class;
692 if (2 < func_num_args()) {
693 $argv = func_get_args();
694 $this->classArgs = array_slice($argv, 2);
697 return $this;
701 * Attach an object to a server
703 * Accepts an instantiated object to use when handling requests.
705 * @param object $object
706 * @return self
707 * @throws Exception\InvalidArgumentException
709 public function setObject($object)
711 if (! is_object($object)) {
712 throw new Exception\InvalidArgumentException(sprintf(
713 'Invalid object argument (%s)',
714 gettype($object)
718 if (isset($this->object)) {
719 throw new Exception\InvalidArgumentException(
720 'An object has already been registered with this soap server instance'
724 $this->object = $object;
725 return $this;
729 * Return a server definition array
731 * Returns a list of all functions registered with {@link addFunction()},
732 * merged with all public methods of the class set with {@link setClass()}
733 * (if any).
735 * @return array
737 public function getFunctions()
739 $functions = [];
740 if (null !== $this->class) {
741 $functions = get_class_methods($this->class);
742 } elseif (null !== $this->object) {
743 $functions = get_class_methods($this->object);
746 return array_merge((array) $this->functions, $functions);
750 * Unimplemented: Load server definition
752 * @param array $definition
753 * @throws Exception\RuntimeException Unimplemented
755 public function loadFunctions($definition)
757 throw new Exception\RuntimeException('Unimplemented method.');
761 * Set server persistence
763 * @param int $mode SOAP_PERSISTENCE_SESSION or SOAP_PERSISTENCE_REQUEST constants
764 * @return self
765 * @throws Exception\InvalidArgumentException
767 public function setPersistence($mode)
769 if (! in_array($mode, [SOAP_PERSISTENCE_SESSION, SOAP_PERSISTENCE_REQUEST])) {
770 throw new Exception\InvalidArgumentException('Invalid persistence mode specified');
773 $this->persistence = $mode;
774 return $this;
778 * Get server persistence
780 * @return int
782 public function getPersistence()
784 return $this->persistence;
788 * Set request
790 * $request may be any of:
791 * - DOMDocument; if so, then cast to XML
792 * - DOMNode; if so, then grab owner document and cast to XML
793 * - SimpleXMLElement; if so, then cast to XML
794 * - stdClass; if so, calls __toString() and verifies XML
795 * - string; if so, verifies XML
797 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request
798 * @return self
799 * @throws Exception\InvalidArgumentException
801 protected function setRequest($request)
803 $xml = null;
805 if ($request instanceof DOMDocument) {
806 $xml = $request->saveXML();
807 } elseif ($request instanceof DOMNode) {
808 $xml = $request->ownerDocument->saveXML();
809 } elseif ($request instanceof SimpleXMLElement) {
810 $xml = $request->asXML();
811 } elseif (is_object($request) || is_string($request)) {
812 if (is_object($request)) {
813 $xml = $request->__toString();
814 } else {
815 $xml = $request;
817 $xml = trim($xml);
819 if (strlen($xml) === 0) {
820 throw new Exception\InvalidArgumentException('Empty request');
823 $loadEntities = libxml_disable_entity_loader(true);
825 $dom = new DOMDocument();
827 if (true === $this->getParseHuge()) {
828 $loadStatus = $dom->loadXML($xml, LIBXML_PARSEHUGE);
829 } else {
830 $loadStatus = $dom->loadXML($xml);
833 libxml_disable_entity_loader($loadEntities);
835 // @todo check libxml errors ? validate document ?
836 if (! $loadStatus) {
837 throw new Exception\InvalidArgumentException('Invalid XML');
840 foreach ($dom->childNodes as $child) {
841 if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
842 throw new Exception\InvalidArgumentException('Invalid XML: Detected use of illegal DOCTYPE');
847 $this->request = $xml;
848 return $this;
852 * Retrieve request XML
854 * @return string
856 public function getLastRequest()
858 return $this->request;
862 * Set return response flag
864 * If true, {@link handle()} will return the response instead of
865 * automatically sending it back to the requesting client.
867 * The response is always available via {@link getResponse()}.
869 * @param bool $flag
870 * @return self
872 public function setReturnResponse($flag = true)
874 $this->returnResponse = (bool) $flag;
875 return $this;
879 * Retrieve return response flag
881 * @return bool
883 public function getReturnResponse()
885 return $this->returnResponse;
889 * Get response XML
891 * @return string
893 public function getResponse()
895 return $this->response;
899 * Get SoapServer object
901 * Uses {@link $wsdl} and return value of {@link getOptions()} to instantiate
902 * SoapServer object, and then registers any functions or class with it, as
903 * well as persistence.
905 * @return SoapServer
907 public function getSoap()
909 if ($this->server instanceof SoapServer) {
910 return $this->server;
913 $options = $this->getOptions();
914 $server = new SoapServer($this->wsdl, $options);
916 if (! empty($this->functions)) {
917 $server->addFunction($this->functions);
920 if (! empty($this->class)) {
921 $args = $this->classArgs;
922 array_unshift($args, $this->class);
923 call_user_func_array([$server, 'setClass'], $args);
926 if (! empty($this->object)) {
927 $server->setObject($this->object);
930 if (null !== $this->persistence) {
931 $server->setPersistence($this->persistence);
934 $this->server = $server;
935 return $this->server;
939 * Proxy for _getSoap method
940 * @see _getSoap
941 * @return SoapServer the soapServer instance
942 public function getSoap()
944 return $this->_getSoap();
949 * Handle a request
951 * Instantiates SoapServer object with options set in object, and
952 * dispatches its handle() method.
954 * $request may be any of:
955 * - DOMDocument; if so, then cast to XML
956 * - DOMNode; if so, then grab owner document and cast to XML
957 * - SimpleXMLElement; if so, then cast to XML
958 * - stdClass; if so, calls __toString() and verifies XML
959 * - string; if so, verifies XML
961 * If no request is passed, pulls request using php:://input (for
962 * cross-platform compatibility purposes).
964 * @param DOMDocument|DOMNode|SimpleXMLElement|\stdClass|string $request Optional request
965 * @return void|string
967 public function handle($request = null)
969 if (null === $request) {
970 $request = file_get_contents('php://input');
973 // Set Server error handler
974 $displayErrorsOriginalState = $this->initializeSoapErrorContext();
976 $setRequestException = null;
977 try {
978 $this->setRequest($request);
979 } catch (\Exception $e) {
980 $setRequestException = $e;
983 $soap = $this->getSoap();
985 $fault = false;
986 $this->response = '';
988 if ($setRequestException instanceof \Exception) {
989 // Create SOAP fault message if we've caught a request exception
990 $fault = $this->fault($setRequestException->getMessage(), 'Sender');
991 } else {
992 ob_start();
993 try {
994 $soap->handle($this->request);
995 } catch (\Exception $e) {
996 $fault = $this->fault($e);
998 $this->response = ob_get_clean();
1001 // Restore original error handler
1002 restore_error_handler();
1003 ini_set('display_errors', (string) $displayErrorsOriginalState);
1005 // Send a fault, if we have one
1006 if ($fault instanceof SoapFault && ! $this->returnResponse) {
1007 $soap->fault($fault->faultcode, $fault->getMessage());
1009 return;
1012 // Echo the response, if we're not returning it
1013 if (! $this->returnResponse) {
1014 echo $this->response;
1016 return;
1019 // Return a fault, if we have it
1020 if ($fault instanceof SoapFault) {
1021 return $fault;
1024 // Return the response
1025 return $this->response;
1029 * Method initializes the error context that the SOAPServer environment will run in.
1031 * @return bool display_errors original value
1033 protected function initializeSoapErrorContext()
1035 $displayErrorsOriginalState = ini_get('display_errors');
1036 ini_set('display_errors', '0');
1037 set_error_handler([$this, 'handlePhpErrors'], E_USER_ERROR);
1038 return $displayErrorsOriginalState;
1042 * Set the debug mode.
1043 * In debug mode, all exceptions are send to the client.
1045 * @param bool $debug
1046 * @return self
1048 public function setDebugMode($debug)
1050 $this->debug = $debug;
1051 return $this;
1055 * Validate and register fault exception
1057 * @param string|array $class Exception class or array of exception classes
1058 * @return self
1059 * @throws Exception\InvalidArgumentException
1061 public function registerFaultException($class)
1063 if (is_array($class)) {
1064 foreach ($class as $row) {
1065 $this->registerFaultException($row);
1067 } elseif (is_string($class)
1068 && class_exists($class)
1069 && (is_subclass_of($class, 'Exception') || 'Exception' === $class)
1071 $ref = new ReflectionClass($class);
1073 $this->faultExceptions[] = $ref->getName();
1074 $this->faultExceptions = array_unique($this->faultExceptions);
1075 } else {
1076 throw new Exception\InvalidArgumentException(
1077 'Argument for Zend\Soap\Server::registerFaultException should be'
1078 . ' string or array of strings with valid exception names'
1082 return $this;
1086 * Checks if provided fault name is registered as valid in this server.
1088 * @param string $fault Name of a fault class
1089 * @return bool
1091 public function isRegisteredAsFaultException($fault)
1093 if ($this->debug) {
1094 return true;
1097 $ref = new ReflectionClass($fault);
1098 $classNames = $ref->getName();
1099 return in_array($classNames, $this->faultExceptions);
1103 * Deregister a fault exception from the fault exception stack
1105 * @param string $class
1106 * @return bool
1108 public function deregisterFaultException($class)
1110 if (in_array($class, $this->faultExceptions, true)) {
1111 $index = array_search($class, $this->faultExceptions);
1112 unset($this->faultExceptions[$index]);
1113 return true;
1116 return false;
1120 * Return fault exceptions list
1122 * @return array
1124 public function getFaultExceptions()
1126 return $this->faultExceptions;
1130 * Return caught exception during business code execution
1131 * @return null|\Exception caught exception
1133 public function getException()
1135 return $this->caughtException;
1139 * Generate a server fault
1141 * Note that the arguments are reverse to those of SoapFault.
1143 * If an exception is passed as the first argument, its message and code
1144 * will be used to create the fault object if it has been registered via
1145 * {@Link registerFaultException()}.
1147 * @link http://www.w3.org/TR/soap12-part1/#faultcodes
1148 * @param string|\Exception $fault
1149 * @param string $code SOAP Fault Codes
1150 * @return SoapFault
1152 public function fault($fault = null, $code = 'Receiver')
1154 $this->caughtException = (is_string($fault)) ? new \Exception($fault) : $fault;
1156 if ($fault instanceof \Exception) {
1157 if ($this->isRegisteredAsFaultException($fault)) {
1158 $message = $fault->getMessage();
1159 $eCode = $fault->getCode();
1160 $code = empty($eCode) ? $code : $eCode;
1161 } else {
1162 $message = 'Unknown error';
1164 } elseif (is_string($fault)) {
1165 $message = $fault;
1166 } else {
1167 $message = 'Unknown error';
1170 $allowedFaultModes = [
1171 'VersionMismatch',
1172 'MustUnderstand',
1173 'DataEncodingUnknown',
1174 'Sender',
1175 'Receiver',
1176 'Server'
1178 if (! in_array($code, $allowedFaultModes)) {
1179 $code = 'Receiver';
1182 return new SoapFault($code, $message);
1186 * Throw PHP errors as SoapFaults
1188 * @param int $errno
1189 * @param string $errstr
1190 * @throws SoapFault
1192 public function handlePhpErrors($errno, $errstr)
1194 throw $this->fault($errstr, 'Receiver');