1: <?php
2: /**
3: * The Horde_Rpc:: class provides a set of server and client methods for
4: * RPC communication.
5: *
6: * TODO:
7: * - Introspection documentation and method signatures.
8: *
9: * EXAMPLE:
10: * <code>
11: * $response = Horde_Rpc::request('xmlrpc',
12: * 'http://localhost:80/horde/rpc.php',
13: * 'contacts.search',
14: * $transport_client,
15: * array(array('jan'), array('localsql'),
16: * array('name', 'email')));
17: * </code>
18: *
19: * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
20: *
21: * See the enclosed file COPYING for license information (LGPL). If you
22: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
23: *
24: * @author Jan Schneider <jan@horde.org>
25: * @package Rpc
26: */
27: class Horde_Rpc
28: {
29: /**
30: * All driver-specific parameters.
31: *
32: * @var array
33: */
34: var $_params = array();
35:
36: /**
37: * Do we need an authenticated user?
38: *
39: * @var boolean
40: */
41: var $_requireAuthorization = true;
42:
43: /**
44: * Whether we should exit if auth fails instead of requesting
45: * authorization credentials.
46: *
47: * @var boolean
48: */
49: var $_requestMissingAuthorization = true;
50:
51: /**
52: * Request variables, cookies etc...
53: *
54: * @var Horde_Controller_Request_Http
55: */
56: protected $_request;
57:
58: /**
59: * Logging
60: *
61: * @var Horde_Log_Logger
62: */
63: protected $_logger;
64:
65: /**
66: * RPC server constructor.
67: *
68: * @param Horde_Controller_Request_Http The request object
69: *
70: * @param array $params A hash containing any additional configuration or
71: * connection parameters a subclass might need.
72: *
73: * @return Horde_Rpc An RPC server instance.
74: */
75: public function __construct($request, $params = array())
76: {
77: // Create a stub if we don't have a useable logger.
78: if (isset($params['logger'])
79: && is_callable(array($params['logger'], 'log'))) {
80: $this->_logger = $params['logger'];
81: unset($params['logger']);
82: } else {
83: $this->_logger = new Horde_Support_Stub;
84: }
85:
86: $this->_params = $params;
87: $this->_request = $request;
88:
89: if (isset($params['requireAuthorization'])) {
90: $this->_requireAuthorization = $params['requireAuthorization'];
91: }
92: if (isset($params['requestMissingAuthorization'])) {
93: $this->_requestMissingAuthorization = $params['requestMissingAuthorization'];
94: }
95:
96: $this->_logger->debug('Horde_Rpc::__construct complete');
97: }
98:
99: /**
100: * Check authentication. Different backends may handle
101: * authentication in different ways. The base class implementation
102: * checks for HTTP Authentication against the Horde auth setup.
103: *
104: * @return boolean Returns true if authentication is successful.
105: * Should send appropriate "not authorized" headers
106: * or other response codes/body if auth fails,
107: * and take care of exiting.
108: */
109: function authorize()
110: {
111: $this->_logger->debug('Horde_Rpc::authorize() starting');
112: if (!$this->_requireAuthorization) {
113: return true;
114: }
115:
116: // @TODO: inject this
117: $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
118: $serverVars = $this->_request->getServerVars();
119: if (!empty($serverVars['PHP_AUTH_USER'])) {
120: $user = $serverVars['PHP_AUTH_USER'];
121: $pass = $serverVars['PHP_AUTH_PW'];
122: } elseif (!empty($serverVars['Authorization'])) {
123: $hash = str_replace('Basic ', '', $serverVars['Authorization']);
124: $hash = base64_decode($hash);
125: if (strpos($hash, ':') !== false) {
126: list($user, $pass) = explode(':', $hash, 2);
127: }
128: }
129:
130: if (!isset($user)
131: || !$auth->authenticate($user, array('password' => $pass))) {
132: if ($this->_requestMissingAuthorization) {
133: header('WWW-Authenticate: Basic realm="Horde RPC"');
134: }
135: header('HTTP/1.0 401 Unauthorized');
136: echo '401 Unauthorized';
137: exit;
138: }
139:
140: $this->_logger->debug('Horde_Rpc::authorize() exiting');
141: return true;
142: }
143:
144: /**
145: * Get the request body input. Different RPC backends can override
146: * this to return an open stream to php://stdin, for instance -
147: * whatever is easiest to handle in the getResponse() method.
148: *
149: * The base class implementation looks for $HTTP_RAW_POST_DATA and
150: * returns that if it's available; otherwise, it returns the
151: * contents of php://stdin.
152: *
153: * @return mixed The input - a string (default), a filehandle, etc.
154: */
155: function getInput()
156: {
157: if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
158: return $GLOBALS['HTTP_RAW_POST_DATA'];
159: } else {
160: return implode("\r\n", file('php://input'));
161: }
162: }
163:
164: /**
165: * Sends an RPC request to the server and returns the result.
166: *
167: * @param string The raw request string.
168: *
169: * @return string The XML encoded response from the server.
170: */
171: function getResponse($request)
172: {
173: return 'not implemented';
174: }
175:
176: /**
177: * Returns the Content-Type of the response.
178: *
179: * @return string The MIME Content-Type of the RPC response.
180: */
181: function getResponseContentType()
182: {
183: return 'text/xml';
184: }
185:
186: /**
187: * Send the output back to the client
188: *
189: * @param string $output The output to send back to the client. Can be
190: * overridden in classes if needed.
191: *
192: * @return void
193: */
194: function sendOutput($output)
195: {
196: header('Content-Type: ' . $this->getResponseContentType());
197: header('Content-length: ' . strlen($output));
198: header('Accept-Charset: UTF-8');
199: echo $output;
200: }
201:
202: /**
203: * Builds an RPC request and sends it to the RPC server.
204: *
205: * This statically called method is actually the RPC client.
206: *
207: * @param string $driver The protocol driver to use. Currently
208: * 'soap', 'xmlrpc' and 'jsonrpc' are
209: * available.
210: * @param string|Horde_Url $url The path to the RPC server on the called
211: * host.
212: * @param string $method The method to call.
213: * @param mixed $client An appropriate request client for the type
214: * of request. (Horde_Http_Request, SoapClient
215: * etc..)
216: * @param array $params A hash containing any necessary parameters
217: * for the method call.
218: *
219: * @return mixed The returned result from the method
220: * @throws Horde_Rpc_Exception
221: */
222: public static function request($driver, $url, $method, $client, $params = null)
223: {
224: $driver = Horde_String::ucfirst(basename($driver));
225: $class = 'Horde_Rpc_' . $driver;
226: if (class_exists($class)) {
227: return call_user_func(array($class, 'request'), $url, $method, $client, $params);
228: } else {
229: throw new Horde_Rpc_Exception('Class definition of ' . $class . ' not found.');
230: }
231: }
232:
233: /**
234: * Attempts to return a concrete RPC server instance based on
235: * $driver.
236: *
237: * @param mixed $driver The type of concrete Horde_Rpc subclass to return.
238: * @param array $params A hash containing any additional configuration or
239: * connection parameters a subclass might need.
240: *
241: * @return Horde_Rpc The newly created concrete Horde_Rpc server instance,
242: * or an exception if there is an error.
243: */
244: public static function factory($driver, $request, $params = null)
245: {
246: $driver = basename($driver);
247: $class = 'Horde_Rpc_' . $driver;
248: if (class_exists($class)) {
249: return new $class($request, $params);
250: } else {
251: throw new Horde_Rpc_Exception('Class definition of ' . $class . ' not found.');
252: }
253: }
254:
255: }
256: