Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

mod/web_services/lib/web_services.php (4 issues)

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 == false) {
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 POST data as string encoded as multipart/form-data
175
 * @access private
176
 */
177
function get_post_data() {
178
179
	$postdata = file_get_contents('php://input');
180
181
	return $postdata;
182
}
183
184
/**
185
 * Verify that the required parameters are present
186
 *
187
 * @param string $method     Method name
188
 * @param array  $parameters List of expected parameters
189
 *
190
 * @return true on success or exception
191
 * @throws APIException
192
 * @since 1.7.0
193
 * @access private
194
 */
195
function verify_parameters($method, $parameters) {
196 5
	global $API_METHODS;
197
198
	// are there any parameters for this method
199 5
	if (!(isset($API_METHODS[$method]["parameters"]))) {
200
		return true; // no so return
201
	}
202
203
	// check that the parameters were registered correctly and all required ones are there
204 5
	foreach ($API_METHODS[$method]['parameters'] as $key => $value) {
205
		// this tests the expose structure: must be array to describe parameter and type must be defined
206 5
		if (!is_array($value) || !isset($value['type'])) {
207 1
			$msg = elgg_echo('APIException:InvalidParameter', [$key, $method]);
208 1
			throw new APIException($msg);
209
		}
210
211
		// Check that the variable is present in the request if required
212 4
		if ($value['required'] && !array_key_exists($key, $parameters)) {
213 1
			$msg = elgg_echo('APIException:MissingParameterInMethod', [$key, $method]);
214 4
			throw new APIException($msg);
215
		}
216
	}
217
218 3
	return true;
219
}
220
221
/**
222
 * Get the names of a method's parameters
223
 *
224
 * @param string $method the api method to get the params for
225
 * @return string[]
226
 * @access private
227
 */
228
function _elgg_ws_get_parameter_names($method) {
229 1
	global $API_METHODS;
230
231 1
	if (!isset($API_METHODS[$method]["parameters"])) {
232
		return [];
233
	}
234
235 1
	return array_keys($API_METHODS[$method]["parameters"]);
236
}
237
238
/**
239
 * Serialize an array of parameters for an API method call, applying transformations
240
 * to values depending on the declared parameter type, and returning a string of PHP
241
 * code representing the contents of a PHP array literal.
242
 *
243
 * A leading comma needs to be removed from the output.
244
 *
245
 * @see \ElggCoreWebServicesApiTest::testSerialiseParametersCasting
246
 *
247
 * @param string $method     API method name
248
 * @param array  $parameters Array of parameters
249
 *
250
 * @return string or exception E.g. ',"foo",2.1'
251
 * @throws APIException
252
 * @since 1.7.0
253
 * @access private
254
 *
255
 * @todo in 3.0 this should return an array of parameter values instead of a string of code.
256
 */
257
function serialise_parameters($method, $parameters) {
258 4
	global $API_METHODS;
259
260
	// are there any parameters for this method
261 4
	if (!(isset($API_METHODS[$method]["parameters"]))) {
262
		return ''; // if not, return
263
	}
264
265 4
	$serialised_parameters = "";
266 4
	foreach ($API_METHODS[$method]['parameters'] as $key => $value) {
267
		// avoid warning on parameters that are not required and not present
268 4
		if (!isset($parameters[$key])) {
269 3
			$serialised_parameters .= ',null';
270 3
			continue;
271
		}
272
273
		// Set variables casting to type.
274 4
		switch (strtolower($value['type'])) {
275
			case 'int':
276
			case 'integer' :
277 4
				$serialised_parameters .= "," . (int) trim($parameters[$key]);
278 4
				break;
279
			case 'bool':
280
			case 'boolean':
281
				// change word false to boolean false
282 4
				if (strcasecmp(trim($parameters[$key]), "false") == 0) {
283 1
					$serialised_parameters .= ',false';
284 4
				} else if ($parameters[$key] == 0) {
285 2
					$serialised_parameters .= ',false';
286
				} else {
287 3
					$serialised_parameters .= ',true';
288
				}
289
290 4
				break;
291
			case 'string':
292 2
				$serialised_parameters .= ',' . var_export(trim($parameters[$key]), true);
293 2
				break;
294
			case 'float':
295 2
				$serialised_parameters .= "," . (float) trim($parameters[$key]);
296 2
				break;
297
			case 'array':
298
				// we can handle an array of strings, maybe ints, definitely not booleans or other arrays
299 2
				if (!is_array($parameters[$key])) {
300
					$msg = elgg_echo('APIException:ParameterNotArray', [$key]);
301
					throw new APIException($msg);
302
				}
303
304 2
				$array = "array(";
305
306 2
				foreach ($parameters[$key] as $k => $v) {
307
					// This is using sanitise_string() to escape characters to be inside a
308
					// single-quoted string literal in PHP code. Not sure what we have to do
309
					// to keep this safe in 3.0...
310 2
					$k = sanitise_string($k);
311 2
					$v = sanitise_string($v);
312
313 2
					$array .= "'$k'=>'$v',";
314
				}
315
316 2
				$array = trim($array, ",");
317
318 2
				$array .= ")";
319 2
				$array = ",$array";
320
321 2
				$serialised_parameters .= $array;
322 2
				break;
323
			default:
324 1
				$msg = elgg_echo('APIException:UnrecognisedTypeCast', [$value['type'], $key, $method]);
325 4
				throw new APIException($msg);
326
		}
327
	}
328
329 4
	return $serialised_parameters;
330
}
331
332
// API authorization handlers /////////////////////////////////////////////////////////////////////
333
334
/**
335
 * PAM: Confirm that the call includes a valid API key
336
 *
337
 * @return bool true if good API key - otherwise throws exception
338
 * @throws APIException
339
 * @since 1.7.0
340
 * @access private
341
 */
342
function api_auth_key() {
343
	// check that an API key is present
344 2
	$api_key = get_input('api_key');
345 2
	if ($api_key == "") {
346 1
		throw new APIException(elgg_echo('APIException:MissingAPIKey'));
347
	}
348
349
	// check that it is active
350 1
	$api_user = get_api_user(elgg_get_site_entity()->guid, $api_key);
0 ignored issues
show
The call to get_api_user() has too many arguments starting with $api_key. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

350
	$api_user = /** @scrutinizer ignore-call */ get_api_user(elgg_get_site_entity()->guid, $api_key);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
351 1
	if (!$api_user) {
352
		// key is not active or does not exist
353 1
		throw new APIException(elgg_echo('APIException:BadAPIKey'));
354
	}
355
356
	// can be used for keeping stats
357
	// plugin can also return false to fail this authentication method
358
	return elgg_trigger_plugin_hook('api_key', 'use', $api_key, true);
359
}
360
361
/**
362
 * PAM: Confirm the HMAC signature
363
 *
364
 * @return true if success - otherwise throws exception
365
 *
366
 * @throws SecurityException
367
 * @since 1.7.0
368
 * @access private
369
 */
370
function api_auth_hmac() {
371
	// Get api header
372
	$api_header = get_and_validate_api_headers();
373
374
	// Pull API user details
375
	$api_user = get_api_user(elgg_get_site_entity()->guid, $api_header->api_key);
0 ignored issues
show
The call to get_api_user() has too many arguments starting with $api_header->api_key. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

375
	$api_user = /** @scrutinizer ignore-call */ get_api_user(elgg_get_site_entity()->guid, $api_header->api_key);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
376
377
	if (!$api_user) {
378
		throw new SecurityException(elgg_echo('SecurityException:InvalidAPIKey'),
379
		ErrorResult::$RESULT_FAIL_APIKEY_INVALID);
380
	}
381
382
	// Get the secret key
383
	$secret_key = $api_user->secret;
384
385
	// get the query string
386
	$query = _elgg_services()->request->server->get('REQUEST_URI');
387
	$query = substr($query, strpos($query, '?') + 1);
388
389
	// calculate expected HMAC
390
	$hmac = calculate_hmac(	$api_header->hmac_algo,
391
							$api_header->time,
392
							$api_header->nonce,
393
							$api_header->api_key,
394
							$secret_key,
395
							$query,
396
							$api_header->method == 'POST' ? $api_header->posthash : "");
397
398
399
	if ($api_header->hmac !== $hmac) {
400
		throw new SecurityException("HMAC is invalid.  {$api_header->hmac} != [calc]$hmac");
401
	}
402
403
	// Now make sure this is not a replay
404
	if (cache_hmac_check_replay($hmac)) {
405
		throw new SecurityException(elgg_echo('SecurityException:DupePacket'));
406
	}
407
408
	// Validate post data
409
	if ($api_header->method == "POST") {
410
		$postdata = get_post_data();
411
		$calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo);
412
413
		if (strcmp($api_header->posthash, $calculated_posthash) != 0) {
414
			$msg = elgg_echo('SecurityException:InvalidPostHash',
415
			[$calculated_posthash, $api_header->posthash]);
416
417
			throw new SecurityException($msg);
418
		}
419
	}
420
421
	return true;
422
}
423
424
// HMAC /////////////////////////////////////////////////////////////////////
425
426
/**
427
 * This function extracts the various header variables needed for the HMAC PAM
428
 *
429
 * @return stdClass Containing all the values.
430
 * @throws APIException Detailing any error.
431
 * @access private
432
 */
433
function get_and_validate_api_headers() {
434
	$result = new stdClass;
435
436
	$result->method = get_call_method();
437
	// Only allow these methods
438
	if (($result->method != "GET") && ($result->method != "POST")) {
439
		throw new APIException(elgg_echo('APIException:NotGetOrPost'));
440
	}
441
442
	$server = _elgg_services()->request->server;
443
444
	$result->api_key = $server->get('HTTP_X_ELGG_APIKEY');
445
	if ($result->api_key == "") {
446
		throw new APIException(elgg_echo('APIException:MissingAPIKey'));
447
	}
448
449
	$result->hmac = $server->get('HTTP_X_ELGG_HMAC');
450
	if ($result->hmac == "") {
451
		throw new APIException(elgg_echo('APIException:MissingHmac'));
452
	}
453
454
	$result->hmac_algo = $server->get('HTTP_X_ELGG_HMAC_ALGO');
455
	if ($result->hmac_algo == "") {
456
		throw new APIException(elgg_echo('APIException:MissingHmacAlgo'));
457
	}
458
459
	$result->time = $server->get('HTTP_X_ELGG_TIME');
460
	if ($result->time == "") {
461
		throw new APIException(elgg_echo('APIException:MissingTime'));
462
	}
463
464
	// Must have been sent within 25 hour period.
465
	// 25 hours is more than enough to handle server clock drift.
466
	// This values determines how long the HMAC cache needs to store previous
467
	// signatures. Heavy use of HMAC is better handled with a shorter sig lifetime.
468
	// See cache_hmac_check_replay()
469
	if (($result->time < (time() - 90000)) || ($result->time > (time() + 90000))) {
470
		throw new APIException(elgg_echo('APIException:TemporalDrift'));
471
	}
472
473
	$result->nonce = $server->get('HTTP_X_ELGG_NONCE');
474
	if ($result->nonce == "") {
475
		throw new APIException(elgg_echo('APIException:MissingNonce'));
476
	}
477
478
	if ($result->method == "POST") {
479
		$result->posthash = $server->get('HTTP_X_ELGG_POSTHASH');
480
		if ($result->posthash == "") {
481
			throw new APIException(elgg_echo('APIException:MissingPOSTHash'));
482
		}
483
484
		$result->posthash_algo = $server->get('HTTP_X_ELGG_POSTHASH_ALGO');
485
		if ($result->posthash_algo == "") {
486
			throw new APIException(elgg_echo('APIException:MissingPOSTAlgo'));
487
		}
488
489
		$result->content_type = $server->get('CONTENT_TYPE');
490
		if ($result->content_type == "") {
491
			throw new APIException(elgg_echo('APIException:MissingContentType'));
492
		}
493
	}
494
495
	return $result;
496
}
497
498
/**
499
 * Map various algorithms to their PHP equivs.
500
 * This also gives us an easy way to disable algorithms.
501
 *
502
 * @param string $algo The algorithm
503
 *
504
 * @return string The php algorithm
505
 * @throws APIException if an algorithm is not supported.
506
 * @access private
507
 */
508
function map_api_hash($algo) {
509
	$algo = strtolower(sanitise_string($algo));
510
	$supported_algos = [
511
		"md5" => "md5",	// @todo Consider phasing this out
512
		"sha" => "sha1", // alias for sha1
513
		"sha1" => "sha1",
514
		"sha256" => "sha256"
515
	];
516
517
	if (array_key_exists($algo, $supported_algos)) {
518
		return $supported_algos[$algo];
519
	}
520
521
	throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', [$algo]));
522
}
523
524
/**
525
 * Calculate the HMAC for the http request.
526
 * This function signs an api request using the information provided. The signature returned
527
 * has been base64 encoded and then url encoded.
528
 *
529
 * @param string $algo          The HMAC algorithm used
530
 * @param string $time          String representation of unix time
531
 * @param string $nonce         Nonce
532
 * @param string $api_key       Your api key
533
 * @param string $secret_key    Your private key
534
 * @param string $get_variables URLEncoded string representation of the get variable parameters,
535
 *                              eg "method=user&guid=2"
536
 * @param string $post_hash     Optional sha1 hash of the post data.
537
 *
538
 * @return string The HMAC signature
539
 * @access private
540
 */
541
function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key,
542
$get_variables, $post_hash = "") {
543
544
	elgg_log("HMAC Parts: $algo, $time, $api_key, $secret_key, $get_variables, $post_hash");
545
546
	$ctx = hash_init(map_api_hash($algo), HASH_HMAC, $secret_key);
547
548
	hash_update($ctx, trim($time));
549
	hash_update($ctx, trim($nonce));
550
	hash_update($ctx, trim($api_key));
551
	hash_update($ctx, trim($get_variables));
552
	if (trim($post_hash) != "") {
553
		hash_update($ctx, trim($post_hash));
554
	}
555
556
	return urlencode(base64_encode(hash_final($ctx, true)));
557
}
558
559
/**
560
 * Calculate a hash for some post data.
561
 *
562
 * @todo Work out how to handle really large bits of data.
563
 *
564
 * @param string $postdata The post data.
565
 * @param string $algo     The algorithm used.
566
 *
567
 * @return string The hash.
568
 * @access private
569
 */
570
function calculate_posthash($postdata, $algo) {
571
	$ctx = hash_init(map_api_hash($algo));
572
573
	hash_update($ctx, $postdata);
574
575
	return hash_final($ctx);
576
}
577
578
/**
579
 * This function will do two things. Firstly it verifies that a HMAC signature
580
 * hasn't been seen before, and secondly it will add the given hmac to the cache.
581
 *
582
 * @param string $hmac The hmac string.
583
 *
584
 * @return bool True if replay detected, false if not.
585
 * @access private
586
 */
587
function cache_hmac_check_replay($hmac) {
588
	// cache lifetime is 25 hours (this should be related to the time drift
589
	// allowed in get_and_validate_headers
590
	$cache = new ElggHMACCache(90000);
591
592
	if (!$cache->load($hmac)) {
593
		$cache->save($hmac, $hmac);
594
595
		return false;
596
	}
597
598
	return true;
599
}
600
601
/**
602
 * Check the user token
603
 * This examines whether an authentication token is present and returns true if
604
 * it is present and is valid. The user gets logged in so with the current
605
 * session code of Elgg, that user will be logged out of all other sessions.
606
 *
607
 * @return bool
608
 * @access private
609
 */
610
function pam_auth_usertoken() {
611
	$token = get_input('auth_token');
612
	if (!$token) {
613
		return false;
614
	}
615
	
616
	$validated_userid = validate_user_token($token, elgg_get_site_entity()->guid);
0 ignored issues
show
The call to validate_user_token() has too many arguments starting with elgg_get_site_entity()->guid. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

616
	$validated_userid = /** @scrutinizer ignore-call */ validate_user_token($token, elgg_get_site_entity()->guid);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
617
618
	if ($validated_userid) {
619
		$u = get_entity($validated_userid);
620
621
		// Could we get the user?
622
		if (!$u) {
623
			return false;
624
		}
625
626
		// Not an elgg user
627
		if ((!$u instanceof ElggUser)) {
628
			return false;
629
		}
630
631
		// User is banned
632
		if ($u->isBanned()) {
633
			return false;
634
		}
635
636
		// Fail if we couldn't log the user in
637
		if (!login($u)) {
638
			return false;
639
		}
640
641
		return true;
642
	}
643
644
	return false;
645
}
646
647
/**
648
 * See if the user has a valid login sesson
649
 *
650
 * @return bool
651
 * @access private
652
 */
653
function pam_auth_session() {
654
	return elgg_is_logged_in();
655
}
656
657
/**
658
 * API PHP Error handler function.
659
 * This function acts as a wrapper to catch and report PHP error messages.
660
 *
661
 * @see http://uk3.php.net/set-error-handler
662
 *
663
 * @param int    $errno    Error number
664
 * @param string $errmsg   Human readable message
665
 * @param string $filename Filename
666
 * @param int    $linenum  Line number
667
 * @param array  $vars     Vars
668
 *
669
 * @return void
670
 * @access private
671
 *
672
 * @throws Exception
673
 */
674
function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) {
675
	global $ERRORS;
676
677
	$error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file "
678
	. $filename . " (line " . $linenum . ")";
679
680
	switch ($errno) {
681
		case E_USER_ERROR:
682
			error_log("ERROR: " . $error);
683
			$ERRORS[] = "ERROR: " . $error;
684
685
			// Since this is a fatal error, we want to stop any further execution but do so gracefully.
686
			throw new Exception("ERROR: " . $error);
687
			break;
688
689
		case E_WARNING :
690
		case E_USER_WARNING :
691
			error_log("WARNING: " . $error);
692
			$ERRORS[] = "WARNING: " . $error;
693
			break;
694
695
		default:
696
			error_log("DEBUG: " . $error);
697
			$ERRORS[] = "DEBUG: " . $error;
698
	}
699
}
700
701
/**
702
 * API PHP Exception handler.
703
 * This is a generic exception handler for PHP exceptions. This will catch any
704
 * uncaught exception, end API execution and return the result to the requestor
705
 * as an ErrorResult in the requested format.
706
 *
707
 * @param Exception $exception Exception
708
 *
709
 * @return void
710
 * @access private
711
 */
712
function _php_api_exception_handler($exception) {
713
714
	error_log("*** FATAL EXCEPTION (API) *** : " . $exception);
715
716
	$code   = $exception->getCode() == 0 ? ErrorResult::$RESULT_FAIL : $exception->getCode();
717
	$result = new ErrorResult($exception->getMessage(), $code, null);
718
719
	echo elgg_view_page($exception->getMessage(), elgg_view("api/output", ["result" => $result]));
720
}
721
722
723
/**
724
 * Services handler - turns request over to the registered handler
725
 * If no handler is found, this returns a 404 error
726
 *
727
 * @param string $handler Handler name
728
 * @param array  $request Request string
729
 *
730
 * @return void
731
 * @access private
732
 */
733
function service_handler($handler, $request) {
734
	elgg_set_context('api');
735
736
	$request = explode('/', $request);
0 ignored issues
show
$request of type array is incompatible with the type string expected by parameter $string of explode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

736
	$request = explode('/', /** @scrutinizer ignore-type */ $request);
Loading history...
737
738
	// after the handler, the first identifier is response format
739
	// ex) http://example.org/services/api/rest/json/?method=test
740
	$response_format = array_shift($request);
741
	if (!$response_format) {
742
		$response_format = 'json';
743
	}
744
745
	if (!ctype_alpha($response_format)) {
746
		header("HTTP/1.0 400 Bad Request");
747
		header("Content-type: text/plain");
748
		echo "Invalid format.";
749
		exit;
750
	}
751
752
	elgg_set_viewtype($response_format);
753
754
	$servicehandler = _elgg_config()->servicehandler;
755
756
	if (!isset($servicehandler) || empty($handler)) {
757
		// no handlers set or bad url
758
		header("HTTP/1.0 404 Not Found");
759
		exit;
760
	} else if (isset($servicehandler[$handler]) && is_callable($servicehandler[$handler])) {
761
		$function = $servicehandler[$handler];
762
		call_user_func($function, $request, $handler);
763
	} else {
764
		// no handler for this web service
765
		header("HTTP/1.0 404 Not Found");
766
		exit;
767
	}
768
}
769