Completed
Push — 3.0 ( 80deee...572da2 )
by Jeroen
99:03 queued 39:21
created

mod/web_services/lib/web_services.php (1 issue)

1
<?php
2
/**
3
 * Elgg web services API library
4
 * Functions and objects for exposing custom web services.
5
 *
6
 */
7
8
/**
9
 * Check that the method call has the proper API and user authentication
10
 *
11
 * @param string $method The api name that was exposed
12
 *
13
 * @return true or throws an exception
14
 * @throws APIException
15
 * @since 1.7.0
16
 * @access private
17
 */
18
function authenticate_method($method) {
19 4
	global $API_METHODS;
20
21
	// method must be exposed
22 4
	if (!isset($API_METHODS[$method])) {
23 1
		throw new APIException(elgg_echo('APIException:MethodCallNotImplemented', [$method]));
24
	}
25
26
	// check API authentication if required
27 3
	if ($API_METHODS[$method]["require_api_auth"] == true) {
28 1
		$api_pam = new ElggPAM('api');
29 1
		if ($api_pam->authenticate() !== true) {
30 1
			throw new APIException(elgg_echo('APIException:APIAuthenticationFailed'));
31
		}
32
	}
33
34 2
	$user_pam = new ElggPAM('user');
35 2
	$user_auth_result = $user_pam->authenticate([]);
36
37
	// check if user authentication is required
38 2
	if ($API_METHODS[$method]["require_user_auth"] == true) {
39 1
		if (!$user_auth_result) {
40 1
			throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN);
41
		}
42
	}
43
44 1
	return true;
45
}
46
47
/**
48
 * Executes a method.
49
 * A method is a function which you have previously exposed using expose_function.
50
 *
51
 * @param string $method Method, e.g. "foo.bar"
52
 *
53
 * @return GenericResult The result of the execution.
54
 * @throws APIException|CallException
55
 * @access private
56
 */
57
function execute_method($method) {
58 5
	global $API_METHODS;
59
60
	// method must be exposed
61 5
	if (!isset($API_METHODS[$method])) {
62 1
		$msg = elgg_echo('APIException:MethodCallNotImplemented', [$method]);
63 1
		throw new APIException($msg);
64
	}
65
66
	// function must be callable
67 4
	$function = elgg_extract('function', $API_METHODS[$method]);
68 4
	if (!$function || !is_callable($function)) {
69 1
		$msg = elgg_echo('APIException:FunctionDoesNotExist', [$method]);
70 1
		throw new APIException($msg);
71
	}
72
73
	// check http call method
74 3
	if (strcmp(get_call_method(), $API_METHODS[$method]["call_method"]) != 0) {
75 1
		$msg = elgg_echo('CallException:InvalidCallMethod', [$method,
76 1
		$API_METHODS[$method]["call_method"]]);
77 1
		throw new CallException($msg);
78
	}
79
80 2
	$parameters = get_parameters_for_method($method);
81
82
	// may throw exception, which is not caught here
83 2
	verify_parameters($method, $parameters);
84
85 2
	$serialised_parameters = serialise_parameters($method, $parameters);
86
87
	// Execute function: Construct function and calling parameters
88 2
	$serialised_parameters = trim($serialised_parameters, ", ");
89
90
	// Sadly we probably can't get rid of this eval() in 2.x. Doing so would involve
91
	// replacing serialise_parameters(), which does a bunch of weird stuff we need to
92
	// stay BC with 2.x. There are tests for a lot of these quirks in ElggCoreWebServicesApiTest
93
	// particularly in testSerialiseParametersCasting().
94 2
	$arguments = eval("return [$serialised_parameters];");
95
96 2
	if ($API_METHODS[$method]['assoc']) {
97 1
		$argument = array_combine(_elgg_ws_get_parameter_names($method), $arguments);
98 1
		$result = call_user_func($function, $argument);
99
	} else {
100 1
		$result = call_user_func_array($function, $arguments);
101
	}
102
103 2
	$result = elgg_trigger_plugin_hook('rest:output', $method, $parameters, $result);
104
	
105
	// Sanity check result
106
	// If this function returns an api result itself, just return it
107 2
	if ($result instanceof GenericResult) {
108
		return $result;
109
	}
110
111 2
	if ($result === false) {
112
		$msg = elgg_echo('APIException:FunctionParseError', [$function, $serialised_parameters]);
113
		throw new APIException($msg);
114
	}
115
116 2
	if ($result === null) {
117
		// If no value
118
		$msg = elgg_echo('APIException:FunctionNoReturn', [$function, $serialised_parameters]);
119
		throw new APIException($msg);
120
	}
121
122
	// Otherwise assume that the call was successful and return it as a success object.
123 2
	return SuccessResult::getInstance($result);
124
}
125
126
/**
127
 * Get the request method.
128
 *
129
 * @return string HTTP request method
130
 * @access private
131
 */
132
function get_call_method() {
133 23
	return _elgg_services()->request->server->get('REQUEST_METHOD');
134
}
135
136
/**
137
 * This function analyses all expected parameters for a given method
138
 *
139
 * This function sanitizes the input parameters and returns them in
140
 * an associated array.
141
 *
142
 * @param string $method The method
143
 *
144
 * @return array containing parameters as key => value
145
 * @access private
146
 */
147
function get_parameters_for_method($method) {
148 2
	global $API_METHODS;
149
150 2
	$sanitised = [];
151
152
	// if there are parameters, sanitize them
153 2
	if (isset($API_METHODS[$method]['parameters'])) {
154 2
		foreach ($API_METHODS[$method]['parameters'] as $k => $v) {
155 2
			$param = get_input($k); // Make things go through the sanitiser
156 2
			if ($param !== '' && $param !== null) {
157 2
				$sanitised[$k] = $param;
158
			} else {
159
				// parameter wasn't passed so check for default
160 2
				if (isset($v['default'])) {
161 2
					$sanitised[$k] = $v['default'];
162
				}
163
			}
164
		}
165
	}
166
167 2
	return $sanitised;
168
}
169
170
/**
171
 * Get POST data
172
 * Since this is called through a handler, we need to manually get the post data
173
 *
174
 * @return false|string POST data as string encoded as multipart/form-data
175
 * @access private
176
 */
177
function get_post_data() {
178
	return file_get_contents('php://input');
179
}
180
181
/**
182
 * Verify that the required parameters are present
183
 *
184
 * @param string $method     Method name
185
 * @param array  $parameters List of expected parameters
186
 *
187
 * @return true on success or exception
188
 * @throws APIException
189
 * @since 1.7.0
190
 * @access private
191
 */
192
function verify_parameters($method, $parameters) {
193 5
	global $API_METHODS;
194
195
	// are there any parameters for this method
196 5
	if (!(isset($API_METHODS[$method]["parameters"]))) {
197
		return true; // no so return
198
	}
199
200
	// check that the parameters were registered correctly and all required ones are there
201 5
	foreach ($API_METHODS[$method]['parameters'] as $key => $value) {
202
		// this tests the expose structure: must be array to describe parameter and type must be defined
203 5
		if (!is_array($value) || !isset($value['type'])) {
204 1
			$msg = elgg_echo('APIException:InvalidParameter', [$key, $method]);
205 1
			throw new APIException($msg);
206
		}
207
208
		// Check that the variable is present in the request if required
209 4
		if ($value['required'] && !array_key_exists($key, $parameters)) {
210 1
			$msg = elgg_echo('APIException:MissingParameterInMethod', [$key, $method]);
211 4
			throw new APIException($msg);
212
		}
213
	}
214
215 3
	return true;
216
}
217
218
/**
219
 * Get the names of a method's parameters
220
 *
221
 * @param string $method the api method to get the params for
222
 * @return string[]
223
 * @access private
224
 */
225
function _elgg_ws_get_parameter_names($method) {
226 1
	global $API_METHODS;
227
228 1
	if (!isset($API_METHODS[$method]["parameters"])) {
229
		return [];
230
	}
231
232 1
	return array_keys($API_METHODS[$method]["parameters"]);
233
}
234
235
/**
236
 * Serialize an array of parameters for an API method call, applying transformations
237
 * to values depending on the declared parameter type, and returning a string of PHP
238
 * code representing the contents of a PHP array literal.
239
 *
240
 * A leading comma needs to be removed from the output.
241
 *
242
 * @see \ElggCoreWebServicesApiTest::testSerialiseParametersCasting
243
 *
244
 * @param string $method     API method name
245
 * @param array  $parameters Array of parameters
246
 *
247
 * @return string or exception E.g. ',"foo",2.1'
248
 * @throws APIException
249
 * @since 1.7.0
250
 * @access private
251
 *
252
 * @todo in 3.0 this should return an array of parameter values instead of a string of code.
253
 */
254
function serialise_parameters($method, $parameters) {
255 4
	global $API_METHODS;
256
257
	// are there any parameters for this method
258 4
	if (!(isset($API_METHODS[$method]["parameters"]))) {
259
		return ''; // if not, return
260
	}
261
262 4
	$serialised_parameters = "";
263 4
	foreach ($API_METHODS[$method]['parameters'] as $key => $value) {
264
		// avoid warning on parameters that are not required and not present
265 4
		if (!isset($parameters[$key])) {
266 3
			$serialised_parameters .= ',null';
267 3
			continue;
268
		}
269
270
		// Set variables casting to type.
271 4
		switch (strtolower($value['type'])) {
272 4
			case 'int':
273 4
			case 'integer' :
274 4
				$serialised_parameters .= "," . (int) trim($parameters[$key]);
275 4
				break;
276 4
			case 'bool':
277 2
			case 'boolean':
278
				// change word false to boolean false
279 4
				if (strcasecmp(trim($parameters[$key]), "false") == 0) {
280 1
					$serialised_parameters .= ',false';
281 4
				} else if ($parameters[$key] == 0) {
282 2
					$serialised_parameters .= ',false';
283
				} else {
284 3
					$serialised_parameters .= ',true';
285
				}
286
287 4
				break;
288 2
			case 'string':
289 2
				$serialised_parameters .= ',' . var_export(trim($parameters[$key]), true);
290 2
				break;
291 2
			case 'float':
292 2
				$serialised_parameters .= "," . (float) trim($parameters[$key]);
293 2
				break;
294 2
			case 'array':
295
				// we can handle an array of strings, maybe ints, definitely not booleans or other arrays
296 2
				if (!is_array($parameters[$key])) {
297
					$msg = elgg_echo('APIException:ParameterNotArray', [$key]);
298
					throw new APIException($msg);
299
				}
300
301 2
				$array = "array(";
302
303 2
				foreach ($parameters[$key] as $k => $v) {
304
					// This is using sanitise_string() to escape characters to be inside a
305
					// single-quoted string literal in PHP code. Not sure what we have to do
306
					// to keep this safe in 3.0...
307 2
					$k = sanitise_string($k);
308 2
					$v = sanitise_string($v);
309
310 2
					$array .= "'$k'=>'$v',";
311
				}
312
313 2
				$array = trim($array, ",");
314
315 2
				$array .= ")";
316 2
				$array = ",$array";
317
318 2
				$serialised_parameters .= $array;
319 2
				break;
320
			default:
321 1
				$msg = elgg_echo('APIException:UnrecognisedTypeCast', [$value['type'], $key, $method]);
322 4
				throw new APIException($msg);
323
		}
324
	}
325
326 4
	return $serialised_parameters;
327
}
328
329
// API authorization handlers /////////////////////////////////////////////////////////////////////
330
331
/**
332
 * PAM: Confirm that the call includes a valid API key
333
 *
334
 * @return bool true if good API key - otherwise throws exception
335
 * @throws APIException
336
 * @since 1.7.0
337
 * @access private
338
 */
339
function api_auth_key() {
340
	// check that an API key is present
341 2
	$api_key = get_input('api_key');
342 2
	if ($api_key == "") {
343 1
		throw new APIException(elgg_echo('APIException:MissingAPIKey'));
344
	}
345
346
	// check that it is active
347 1
	$api_user = get_api_user($api_key);
348 1
	if (!$api_user) {
349
		// key is not active or does not exist
350 1
		throw new APIException(elgg_echo('APIException:BadAPIKey'));
351
	}
352
353
	// can be used for keeping stats
354
	// plugin can also return false to fail this authentication method
355
	return elgg_trigger_plugin_hook('api_key', 'use', $api_key, true);
356
}
357
358
/**
359
 * PAM: Confirm the HMAC signature
360
 *
361
 * @return true if success - otherwise throws exception
362
 *
363
 * @throws SecurityException
364
 * @since 1.7.0
365
 * @access private
366
 */
367
function api_auth_hmac() {
368
	// Get api header
369
	$api_header = get_and_validate_api_headers();
370
371
	// Pull API user details
372
	$api_user = get_api_user($api_header->api_key);
373
374
	if (!$api_user) {
375
		throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'),
376
		ErrorResult::$RESULT_FAIL_APIKEY_INVALID);
377
	}
378
379
	// Get the secret key
380
	$secret_key = $api_user->secret;
381
382
	// get the query string
383
	$query = _elgg_services()->request->server->get('REQUEST_URI');
384
	$query = substr($query, strpos($query, '?') + 1);
385
386
	// calculate expected HMAC
387
	$hmac = calculate_hmac(	$api_header->hmac_algo,
388
							$api_header->time,
389
							$api_header->nonce,
390
							$api_header->api_key,
391
							$secret_key,
392
							$query,
393
							$api_header->method == 'POST' ? $api_header->posthash : "");
394
395
396
	if ($api_header->hmac !== $hmac) {
397
		throw new SecurityException("HMAC is invalid.  {$api_header->hmac} != [calc]$hmac");
398
	}
399
400
	// Now make sure this is not a replay
401
	if (cache_hmac_check_replay($hmac)) {
402
		throw new SecurityException(elgg_echo('SecurityException:DupePacket'));
403
	}
404
405
	// Validate post data
406
	if ($api_header->method == "POST") {
407
		$postdata = get_post_data();
408
		$calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo);
409
410
		if (strcmp($api_header->posthash, $calculated_posthash) != 0) {
411
			$msg = elgg_echo('SecurityException:InvalidPostHash',
412
			[$calculated_posthash, $api_header->posthash]);
413
414
			throw new SecurityException($msg);
415
		}
416
	}
417
418
	return true;
419
}
420
421
// HMAC /////////////////////////////////////////////////////////////////////
422
423
/**
424
 * This function extracts the various header variables needed for the HMAC PAM
425
 *
426
 * @return stdClass Containing all the values.
427
 * @throws APIException Detailing any error.
428
 * @access private
429
 */
430
function get_and_validate_api_headers() {
431
	$result = new stdClass;
432
433
	$result->method = get_call_method();
434
	// Only allow these methods
435
	if (($result->method != "GET") && ($result->method != "POST")) {
436
		throw new APIException(elgg_echo('APIException:NotGetOrPost'));
437
	}
438
439
	$server = _elgg_services()->request->server;
440
441
	$result->api_key = $server->get('HTTP_X_ELGG_APIKEY');
442
	if ($result->api_key == "") {
443
		throw new APIException(elgg_echo('APIException:MissingAPIKey'));
444
	}
445
446
	$result->hmac = $server->get('HTTP_X_ELGG_HMAC');
447
	if ($result->hmac == "") {
448
		throw new APIException(elgg_echo('APIException:MissingHmac'));
449
	}
450
451
	$result->hmac_algo = $server->get('HTTP_X_ELGG_HMAC_ALGO');
452
	if ($result->hmac_algo == "") {
453
		throw new APIException(elgg_echo('APIException:MissingHmacAlgo'));
454
	}
455
456
	$result->time = $server->get('HTTP_X_ELGG_TIME');
457
	if ($result->time == "") {
458
		throw new APIException(elgg_echo('APIException:MissingTime'));
459
	}
460
461
	// Must have been sent within 25 hour period.
462
	// 25 hours is more than enough to handle server clock drift.
463
	// This values determines how long the HMAC cache needs to store previous
464
	// signatures. Heavy use of HMAC is better handled with a shorter sig lifetime.
465
	// See cache_hmac_check_replay()
466
	if (($result->time < (time() - 90000)) || ($result->time > (time() + 90000))) {
467
		throw new APIException(elgg_echo('APIException:TemporalDrift'));
468
	}
469
470
	$result->nonce = $server->get('HTTP_X_ELGG_NONCE');
471
	if ($result->nonce == "") {
472
		throw new APIException(elgg_echo('APIException:MissingNonce'));
473
	}
474
475
	if ($result->method == "POST") {
476
		$result->posthash = $server->get('HTTP_X_ELGG_POSTHASH');
477
		if ($result->posthash == "") {
478
			throw new APIException(elgg_echo('APIException:MissingPOSTHash'));
479
		}
480
481
		$result->posthash_algo = $server->get('HTTP_X_ELGG_POSTHASH_ALGO');
482
		if ($result->posthash_algo == "") {
483
			throw new APIException(elgg_echo('APIException:MissingPOSTAlgo'));
484
		}
485
486
		$result->content_type = $server->get('CONTENT_TYPE');
487
		if ($result->content_type == "") {
488
			throw new APIException(elgg_echo('APIException:MissingContentType'));
489
		}
490
	}
491
492
	return $result;
493
}
494
495
/**
496
 * Map various algorithms to their PHP equivs.
497
 * This also gives us an easy way to disable algorithms.
498
 *
499
 * @param string $algo The algorithm
500
 *
501
 * @return string The php algorithm
502
 * @throws APIException if an algorithm is not supported.
503
 * @access private
504
 */
505
function map_api_hash($algo) {
506
	$algo = strtolower(sanitise_string($algo));
507
	$supported_algos = [
508
		"md5" => "md5",	// @todo Consider phasing this out
509
		"sha" => "sha1", // alias for sha1
510
		"sha1" => "sha1",
511
		"sha256" => "sha256"
512
	];
513
514
	if (array_key_exists($algo, $supported_algos)) {
515
		return $supported_algos[$algo];
516
	}
517
518
	throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', [$algo]));
519
}
520
521
/**
522
 * Calculate the HMAC for the http request.
523
 * This function signs an api request using the information provided. The signature returned
524
 * has been base64 encoded and then url encoded.
525
 *
526
 * @param string $algo          The HMAC algorithm used
527
 * @param string $time          String representation of unix time
528
 * @param string $nonce         Nonce
529
 * @param string $api_key       Your api key
530
 * @param string $secret_key    Your private key
531
 * @param string $get_variables URLEncoded string representation of the get variable parameters,
532
 *                              eg "method=user&guid=2"
533
 * @param string $post_hash     Optional sha1 hash of the post data.
534
 *
535
 * @return string The HMAC signature
536
 * @access private
537
 */
538
function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key,
539
$get_variables, $post_hash = "") {
540
541
	elgg_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash");
542
543
	$ctx = hash_init(map_api_hash($algo), HASH_HMAC, $secret_key);
544
545
	hash_update($ctx, trim($time));
546
	hash_update($ctx, trim($nonce));
547
	hash_update($ctx, trim($api_key));
548
	hash_update($ctx, trim($get_variables));
549
	if (trim($post_hash) != "") {
550
		hash_update($ctx, trim($post_hash));
551
	}
552
553
	return urlencode(base64_encode(hash_final($ctx, true)));
554
}
555
556
/**
557
 * Calculate a hash for some post data.
558
 *
559
 * @todo Work out how to handle really large bits of data.
560
 *
561
 * @param string $postdata The post data.
562
 * @param string $algo     The algorithm used.
563
 *
564
 * @return string The hash.
565
 * @access private
566
 */
567
function calculate_posthash($postdata, $algo) {
568
	$ctx = hash_init(map_api_hash($algo));
569
570
	hash_update($ctx, $postdata);
571
572
	return hash_final($ctx);
573
}
574
575
/**
576
 * This function will do two things. Firstly it verifies that a HMAC signature
577
 * hasn't been seen before, and secondly it will add the given hmac to the cache.
578
 *
579
 * @param string $hmac The hmac string.
580
 *
581
 * @return bool True if replay detected, false if not.
582
 * @access private
583
 */
584
function cache_hmac_check_replay($hmac) {
585
	// cache lifetime is 25 hours (this should be related to the time drift
586
	// allowed in get_and_validate_headers
587
	$cache = new ElggHMACCache(90000);
588
589
	if (!$cache->load($hmac)) {
590
		$cache->save($hmac, $hmac);
591
592
		return false;
593
	}
594
595
	return true;
596
}
597
598
/**
599
 * Check the user token
600
 * This examines whether an authentication token is present and returns true if
601
 * it is present and is valid. The user gets logged in so with the current
602
 * session code of Elgg, that user will be logged out of all other sessions.
603
 *
604
 * @return bool
605
 * @access private
606
 */
607
function pam_auth_usertoken() {
608
	$token = get_input('auth_token');
609
	if (!$token) {
610
		return false;
611
	}
612
	
613
	$validated_userid = validate_user_token($token);
614
615
	if ($validated_userid) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $validated_userid of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
616
		$u = get_entity($validated_userid);
617
618
		// Could we get the user?
619
		if (!$u) {
620
			return false;
621
		}
622
623
		// Not an elgg user
624
		if ((!$u instanceof ElggUser)) {
625
			return false;
626
		}
627
628
		// User is banned
629
		if ($u->isBanned()) {
630
			return false;
631
		}
632
633
		// Fail if we couldn't log the user in
634
		if (!login($u)) {
635
			return false;
636
		}
637
638
		return true;
639
	}
640
641
	return false;
642
}
643
644
/**
645
 * See if the user has a valid login sesson
646
 *
647
 * @return bool
648
 * @access private
649
 */
650
function pam_auth_session() {
651
	return elgg_is_logged_in();
652
}
653
654
/**
655
 * API PHP Error handler function.
656
 * This function acts as a wrapper to catch and report PHP error messages.
657
 *
658
 * @see http://uk3.php.net/set-error-handler
659
 *
660
 * @param int    $errno    Error number
661
 * @param string $errmsg   Human readable message
662
 * @param string $filename Filename
663
 * @param int    $linenum  Line number
664
 * @param array  $vars     Vars
665
 *
666
 * @return void
667
 * @access private
668
 *
669
 * @throws Exception
670
 */
671
function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) {
672
	global $ERRORS;
673
674
	$error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file "
675
	. $filename . " (line " . $linenum . ")";
676
677
	switch ($errno) {
678
		case E_USER_ERROR:
679
			error_log("ERROR: " . $error);
680
			$ERRORS[] = "ERROR: " . $error;
681
682
			// Since this is a fatal error, we want to stop any further execution but do so gracefully.
683
			throw new Exception("ERROR: " . $error);
684
			break;
685
686
		case E_WARNING :
687
		case E_USER_WARNING :
688
			error_log("WARNING: " . $error);
689
			$ERRORS[] = "WARNING: " . $error;
690
			break;
691
692
		default:
693
			error_log("DEBUG: " . $error);
694
			$ERRORS[] = "DEBUG: " . $error;
695
	}
696
}
697
698
/**
699
 * API PHP Exception handler.
700
 * This is a generic exception handler for PHP exceptions. This will catch any
701
 * uncaught exception, end API execution and return the result to the requestor
702
 * as an ErrorResult in the requested format.
703
 *
704
 * @param Exception $exception Exception
705
 *
706
 * @return void
707
 * @access private
708
 */
709
function _php_api_exception_handler($exception) {
710
711
	error_log("*** FATAL EXCEPTION (API) *** : " . $exception);
712
713
	$code   = $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode();
714
	$result = new ErrorResult($exception->getMessage(), $code, null);
715
716
	echo elgg_view_page($exception->getMessage(), elgg_view("api/output", ["result" => $result]));
717
}
718
719
720
/**
721
 * Services handler - turns request over to the registered handler
722
 * If no handler is found, this returns a 404 error
723
 *
724
 * @param string $handler Handler name
725
 * @param array  $request Request string
726
 *
727
 * @return void
728
 * @access private
729
 */
730
function service_handler($handler, $request) {
731
	elgg_set_context('api');
732
733
	$request = explode('/', $request);
734
735
	// after the handler, the first identifier is response format
736
	// ex) http://example.org/services/api/rest/json/?method=test
737
	$response_format = array_shift($request);
738
	if (!$response_format) {
739
		$response_format = 'json';
740
	}
741
742
	if (!ctype_alpha($response_format)) {
743
		header("HTTP/1.0 400 Bad Request");
744
		header("Content-type: text/plain");
745
		echo "Invalid format.";
746
		exit;
747
	}
748
749
	elgg_set_viewtype($response_format);
750
751
	$servicehandler = _elgg_config()->servicehandler;
752
753
	if (!isset($servicehandler) || empty($handler)) {
754
		// no handlers set or bad url
755
		header("HTTP/1.0 404 Not Found");
756
		exit;
757
	} else if (isset($servicehandler[$handler]) && is_callable($servicehandler[$handler])) {
758
		$function = $servicehandler[$handler];
759
		call_user_func($function, $request, $handler);
760
	} else {
761
		// no handler for this web service
762
		header("HTTP/1.0 404 Not Found");
763
		exit;
764
	}
765
}
766