API.class.php (7275B)
1 <?php 2 3 namespace cms\api; 4 5 use BadMethodCallException; 6 use cms\action\RequestParams; 7 use cms\base\Startup; 8 use cms\Dispatcher; 9 use Exception; 10 use util\Http; 11 use logger\Logger; 12 use \util\exception\ObjectNotFoundException; 13 use util\exception\UIException; 14 use util\exception\SecurityException; 15 use util\json\JSON; 16 use util\Session; 17 use util\XML; 18 use util\YAML; 19 20 /** 21 * Entrypoint for all API requests. 22 */ 23 class API 24 { 25 const OUTPUT_PHPARRAY = 1; 26 const OUTPUT_PHPSERIALIZE = 2; 27 const OUTPUT_JSON = 3; 28 const OUTPUT_XML = 4; 29 const OUTPUT_YAML = 5; 30 const OUTPUT_HTML = 6; 31 const OUTPUT_PLAIN = 7; 32 33 34 /** 35 * Führt einen API-Request durch. 36 */ 37 public static function execute() 38 { 39 $createDataWithError = function( $status, $message, $cause ) { 40 41 Logger::warn($cause); 42 API::sendHTTPStatus($status, $message); 43 44 $data = [ 45 'status' => $status, 46 'message' => $message 47 ]; 48 49 // Traces only in DEVELOPMENT mode 50 // for security reasons, because traces may contain sensitive information. 51 if (!defined('DEVELOPMENT') || DEVELOPMENT) 52 $data['cause'] = API::exceptionToArray($cause); 53 54 return $data; 55 }; 56 57 try { 58 $request = new RequestParams(); 59 60 $dispatcher = new Dispatcher(); 61 62 $dispatcher->request = $request; 63 64 $data = $dispatcher->doAction(); 65 66 } catch (BadMethodCallException $e) { 67 $data = $createDataWithError( 204, 'Method not found' , $e ); 68 } catch (ObjectNotFoundException $e) { 69 $data = $createDataWithError( 204, 'Object not found' , $e ); 70 } catch (UIException $e) { 71 $data = $createDataWithError( 500, 'Internal CMS Error', $e ); 72 } catch (SecurityException $e) { 73 $data = $createDataWithError( 403, 'Forbidden' , $e ); 74 } catch (Exception $e) { 75 $data = $createDataWithError( 500, 'Internal Server Error', $e ); 76 } 77 78 79 if ( Logger::isTraceEnabled() ) 80 Logger::trace('Output' . "\n" . print_r($data, true)); 81 82 // Weitere Variablen anreichern. 83 $data['session'] = ['name' => session_name(), 'id' => session_id(), 'token' => Session::token()]; 84 $data['version'] = Startup::VERSION; 85 $data['api' ] = Startup::API_LEVEL; 86 87 88 switch (API::discoverOutputType()) { 89 90 case self::OUTPUT_PHPARRAY: 91 header('Content-Type: application/php-array; charset=UTF-8'); 92 $output = print_r($data, true); 93 break; 94 95 case self::OUTPUT_PLAIN: 96 header('Content-Type: text/plain; charset=UTF-8'); 97 $output = print_r($data, true); 98 break; 99 100 case self::OUTPUT_PHPSERIALIZE: 101 header('Content-Type: application/php-serialized; charset=UTF-8'); 102 $output = serialize($data); 103 break; 104 105 case self::OUTPUT_JSON: 106 header('Content-Type: application/json; charset=UTF-8'); 107 $output = JSON::encode($data); 108 break; 109 110 case self::OUTPUT_XML: 111 $xml = new XML(); 112 $xml->root = 'server'; // Name des XML-root-Elementes 113 header('Content-Type: application/xml; charset=UTF-8'); 114 $output = $xml->encode($data); 115 break; 116 117 case self::OUTPUT_HTML: 118 header('Content-Type: text/html; charset=UTF-8'); 119 $output = '<html><body><h1>API response:</h1><hr /><pre>'; 120 $output .= print_r($data,true); 121 $output .= '</pre></body></html>'; 122 break; 123 124 case self::OUTPUT_YAML: 125 header('Content-Type: application/yaml; charset=UTF-8'); 126 $output = YAML::dump($data); 127 break; 128 } 129 130 131 if (!headers_sent()) 132 // HTTP Spec: 133 // "Applications SHOULD use this field to indicate the transfer-length of the 134 // message-body, unless this is prohibited by the rules in section 4.4." 135 // 136 // And the overhead of 'Transfer-Encoding: chunked' is eliminated... 137 header('Content-Length: ' . strlen($output)); 138 139 echo $output; 140 } 141 142 /** 143 * Discovering the output-type for this API-request 144 * 145 * @return int constant of self::CMS_API_OUTPUT_* 146 */ 147 private static function discoverOutputType() 148 { 149 $types = Http::getAccept(); 150 151 $reqOutput = strtolower(@$_REQUEST['output']); 152 153 // First check: The output parameter has precedence over HTTP headers 154 if ( $reqOutput == 'php-array') 155 return self::OUTPUT_PHPARRAY; 156 157 if ( $reqOutput == 'php') 158 return self::OUTPUT_PHPSERIALIZE; 159 160 if ( $reqOutput == 'json') 161 return self::OUTPUT_JSON; 162 163 if ( $reqOutput == 'xml') 164 return self::OUTPUT_XML; 165 166 if ( $reqOutput == 'yaml') 167 return self::OUTPUT_YAML; 168 169 // Lets check the HTTP request headers 170 if (in_array('application/php-array', $types) ) 171 return self::OUTPUT_PHPARRAY; 172 173 if (in_array('application/php-serialized', $types) ) 174 return self::OUTPUT_PHPSERIALIZE; 175 176 if (in_array('application/json', $types) ) 177 return self::OUTPUT_JSON; 178 179 if (in_array('application/xml', $types) ) 180 return self::OUTPUT_XML; 181 182 if (in_array('application/yaml', $types) ) 183 return self::OUTPUT_YAML; 184 185 if (in_array('text/html', $types)) 186 return self::OUTPUT_HTML; // normally an ordinary browser. 187 188 return self::OUTPUT_PLAIN; // Fallback 189 } 190 191 /** 192 * @param $status int HTTP-Status 193 * @param $text string Statustext 194 */ 195 private static function sendHTTPStatus($status, $text) 196 { 197 if (headers_sent()) { 198 //echo "$status $text"; 199 ; // There is nothing we can do. Every output would destroy the JSON, XML, whatever. 200 } else { 201 header('HTTP/1.0 ' . intval($status) . ' ' . $text); 202 } 203 } 204 205 /** 206 * Converting an exception to an array. 207 * 208 * This will contain all exceptions out of the exception chain. 209 * 210 * @param $e Exception 211 */ 212 private static function exceptionToArray($e) 213 { 214 $data = array( 215 'error'=>get_class($e), 216 'description'=>$e->getMessage(), 217 'code'=>$e->getCode(), 218 219 'trace'=>array_merge( array( array( 220 'file'=>$e->getFile(), 221 'line'=>$e->getLine(), 222 'function'=>'', 223 'class' => '' 224 )), API::removeArgsFromTrace($e->getTrace())) 225 ); 226 227 // the cause of the exception is another exception. 228 if ( $e->getPrevious() ) 229 $data['cause'] = API::exceptionToArray($e->getPrevious() ); 230 231 return $data; 232 } 233 234 235 /** 236 * Removing the call argument from the trace. 237 * 238 * This is because of security reasons. The arguments could be an information leak. 239 * 240 * @param $trace array 241 * @return array 242 */ 243 private static function removeArgsFromTrace($trace) 244 { 245 foreach( $trace as &$t ) 246 { 247 unset($t['args']); 248 } 249 250 return $trace; 251 } 252 }