.gitignore000064400000000036144761607100006541 0ustar00/vendor/ /tests/ composer.lockcomposer.json000064400000000740144761607100007275 0ustar00{ "name": "boru/dhapi", "type": "library", "autoload": { "psr-4": { "boru\\dhapi\\": "src/" } }, "authors": [ { "name": "Daniel Hayes", "email": "dhayes@boruapps.com" } ], "require": { "boru/dhutils": ">=2.0.0", "boru/dhttp": "*" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] } instructions-composer.txt000064400000000300144761607100011675 0ustar00{ "require": { "boru/dhapi": "*" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] }src/API.php000064400000020012144761607100006456 0ustar00request = $request; $this->router = $router; $this->container = new Container(); } private $baseClasss = ""; /** @return Request */ public static function requestFromGlobals() { $method = $_SERVER["REQUEST_METHOD"]; if($method == "POST" && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) { if(in_array($_SERVER['HTTP_X_HTTP_METHOD'],['DELETE','PUT','PATCH'])) { $method = $_SERVER['HTTP_X_HTTP_METHOD']; } else { return Response::fromError(405,"Unusual Request Method: ".$_SERVER['HTTP_X_HTTP_METHOD'],true,true); } } if(!in_array($method,static::$allowedMethods)) { Response::fromError(405,"Method Not Allowed",true,true); } $rawBody = file_get_contents("php://input"); $body = []; $content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : ''; if(!empty($rawBody) && strpos($content_type,'application/json') !== false) { $body = json_decode($rawBody,true); if($body === null) { return Response::fromError(400,"Invalid JSON",true,true); } } elseif(!empty($_POST)) { $body = $_POST; } $path = "/"; if(isset($_GET[static::$requestVar])) { $path.= trim($_GET[static::$requestVar],"/"); unset($_GET[static::$requestVar]); } $actualUrl = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . "://".$_SERVER['HTTP_HOST']; $requestUri = $_SERVER['REQUEST_URI']; if(!empty($_SERVER['REQUEST_URI']) && !empty($_SERVER['PHP_SELF'])) { if(strtolower($_SERVER["REQUEST_URI"]) == strtolower($_SERVER["PHP_SELF"])) { $pieces = explode("/",$_SERVER["REQUEST_URI"]); $requestUri = implode("/",array_slice($pieces,0,-1)); } } $actualUrl.= $requestUri; $parts = explode("?", $actualUrl); if(strlen($path) > 1) { $rootUrl = str_replace($path,"",$parts[0]); } else { $rootUrl = $parts[0]; } //echo "
";print_r($_REQUEST); echo "
";exit(); $request = new Request($_GET,$body,getallheaders(),$method,$path); $request->setRootUrl($rootUrl); $request->setRawBody($rawBody); return $request; } /** * @param Request $request * @return API */ public function setRequest($request) { $this->request = $request; return $this; } /** * @param Router $router * @return API */ public function setRouter($router) { $this->router = $router; return $this; } /** * @param Container $container * @return API */ public function setContainer($container) { $this->container = $container; return $this; } public function setClassBaseNamespace($namespace) { $this->baseClasss = $namespace; return $this; } public function getClassBaseNamespace() { return $this->baseClasss; } /** @return Request */ public function getRequest() { if(is_null($this->request)) { $this->request = static::requestFromGlobals(); } if(!($this->request instanceof Request)) { throw new \Exception("Request is not a Request object"); } return $this->request; } /** @return Router */ public function getRouter() { return $this->router; } /** @return Container */ public function getContainer() { return $this->container; } public function getMethod() { return $this->request->getMethod(); } public function getPath() { return $this->request->getPath(); } public function body($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function get($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function attr($key=null,$defaultValue=null) { return $this->container->get($key,$defaultValue); } public function header($key=null) { if(is_null($key)) { return $this->request->getHeaders(); } return $this->request->getHeader($key); } public function headers() { return $this->request->getHeaders(); } /** * @return Response */ public function process() { $time = -microtime(true); if(($this->route = $this->router->matchFromPath($this->getPath(), $this->getMethod(),$this)) !== false) { $dispatcher = new Dispatcher($this,$this->route); static::applyMiddleware($dispatcher,$this->getMiddleware()); static::applyMiddleware($dispatcher,$this->router->getMiddleware()); static::applyMiddleware($dispatcher,$this->route->getMiddleware()); $response = $dispatcher->dispatch(); static::applyMiddleware($response,$this->route->getMiddlewareResponse()); static::applyMiddleware($response,$this->router->getMiddlewareResponse()); static::applyMiddleware($dispatcher,$this->getMiddlewareResponse()); return $response; } else { return Response::fromError("Endpoint Not Found",404,true,true); } } /** * @param \mrr\api\v3\router\Route $route */ public function addRoute($route) { if(is_null($this->router)) { $this->router = new Router(); } $this->router->add($route); } public function addMiddleware($middleware) { $this->middlewares[] = static::assertMiddleware($middleware); } public function getMiddleware() { return $this->middlewares; } public function addMiddlewareResponse($middleware) { $this->middlewareResponse[] = API::assertMiddleware($middleware); } public function getMiddlewareResponse() { return $this->middlewareResponse; } public static function assertMiddleware(&$middleware) { if(is_callable($middleware)) { $middleware = new CallbackMiddleware($middleware); } if(!($middleware instanceof MiddlewareInterface)) { throw new \Exception("Invalid middleware"); } return $middleware; } public static function applyMiddleware(&$object,$middlewareList=[]) { if(empty($middlewareList)) { return; } $queue = new \SplQueue(); reset($middlewareList); foreach($middlewareList as $middleware) { $queue->enqueue($middleware); } $next = function($obj) use ($queue,&$next) { if($queue->isEmpty()) { return $obj; } $middleware = $queue->dequeue(); return $middleware($obj,$next); }; $object = $next($object); } }src/Controller.php000064400000000474144761607100010202 0ustar00"test/endpoint2!","args"=>$api->getArgs(),"apiArgs"=>$api->getRequest()->body()]); } }src/core/Container.php000064400000007216144761607100010732 0ustar00setFromArray($data); } } public function get($key=null,$defaultValue=null) { if(is_null($key)) { return $this->toArray(false); } $key = $this->camelKey($key); $value = dhGlobal::getVal($this->data,$key,$defaultValue); if(is_callable($value)) { return $value(); } return $value; } public function set($key,$value=null) { if(is_array($key)) { $this->data=$key; return $this; } $key = $this->camelKey($key); if(strpos($key,".")!==false) { dhGlobal::dotAssign($this->data,$key,$value); } else { $this->data[$key] = $value; } if(is_callable($value)) { $this->callableKeys[] = $key; } } public function getRaw($key=null,$defaultValue=null) { if(is_null($key)) { return $this->rawData; } return isset($this->rawData[$key]) ? $this->rawData[$key] : $defaultValue; } public function setRaw($key,$value=null) { if(is_array($key)) { $this->rawData=$key; return $this; } $this->rawData[$key] = $value; return $this; } public function toArray($hideNonPublic=true) { $data = $this->data; if($hideNonPublic) { foreach($this->nonPublicFields as $field) { dhGlobal::dotDelete($data,$field); } } foreach($this->callableKeys as $callableKey) { if($this->dotExists($callableKey)) { $value = $this->get($callableKey); dhGlobal::dotAssign($data,$callableKey,$value); } } return $data; } public function __toString() { return json_encode($this->toArray(),JSON_PRETTY_PRINT); } public function jsonSerialize() { return $this->toArray(); } public function setFromArray($array) { if(empty($array)) { return false; } $this->rawData = $array; foreach($array as $key=>$value) { //add it to our data set $this->set($key,$value); } } public function camelKey($key) { $time = -microtime(true); if(strpos($key,".")===false) { return dhGlobal::camelize($key); } $parts = explode(".",trim($key)); $newKey = []; for($i=0;$idotExists(implode(".",$tempKey))) { $newPart = dhGlobal::camelize($part); $newKey[] = $newPart; } else { $newKey[] = $part; } } $time += microtime(true); echo "Time: ".$time."\n"; $time = -microtime(true); return implode(".",$newKey); } public function dotExists($dotString) { $pieces = explode(".", $dotString); $pointer = $this->data; for ($i = 0; $i < count($pieces); $i++) { if (isset($pointer[$pieces[$i]])) { $pointer = $pointer[$pieces[$i]]; } else { return false; } } return true; } }src/core/Dispatcher.php000064400000011175144761607100011075 0ustar00API = $API; } public function __construct($API,$route) { $this->API = $API; $this->route = $route; $this->router = $API->getRouter(); $this->request = $API->getRequest(); $this->container = $API->getContainer(); $this->args = new Container(); } public function dispatch() { $time = -microtime(true); if($this->route instanceof Response) { return $this->route; } $handler = $this->route->getHandler(); // $arguments = ['id' => 2] $this->args = new Container($this->route->getVars()); if(is_array($handler)) { $response = $this->displatchClassController($handler); } else { $response = $this->dispatchCallable($handler); } if(!($response instanceof Response)) { //If the response is not a response object, we didn't return valid data from our endpoint.. so not implemented ob_clean(); return Response::fromError("Endpoint method not implemented",404,true,true); } return $response; } private function dispatchCallable($handler) { $controller = $handler; if (!is_callable($controller)) { return Response::fromError("Endpoint controller method not found",404,true,true); } return $controller($this); } private function displatchClassController($handler) { $controllerName = $handler[0]; if(substr($controllerName,0,1) !== "\\" && !class_exists($controllerName)) { $controllerName = $this->API->getClassBaseNamespace().$controllerName; } $methodName = $handler[1] ? $handler[1] : null; if(!class_exists($controllerName)) { return Response::fromError("Endpoint controller Not Found - $controllerName",404,true,true); } $controller = new $controllerName(); if (!is_callable($controller)) { $controller = [$controller, $methodName]; } if (!is_callable($controller)) { return Response::fromError("Endpoint controller method not found",404,true,true); } return $controller($this); } public function getRequest() { return $this->request; } public function getRouter() { return $this->router; } public function getRoute() { return $this->route; } public function getApi() { return $this->API; } public function getArgs() { return $this->args; } public function getContainer() { return $this->container; } public function getPath() { return $this->request->getPath(); } public function path() { return $this->request->getPath(); } public function baseUrl() { return $this->request->getRootUrl(); } public function rawBody() { return $this->request->getRawBody(); } public function request($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function body($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function data($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function head($key=null,$defaultValue=null) { return $this->request->head($key,$defaultValue); } public function header($key=null,$defaultValue=null) { return $this->request->head($key,$defaultValue); } public function args($key=null,$defaultValue=null) { return $this->args->get($key,$defaultValue); } public function arg($key=null,$defaultValue=null) { return $this->args->get($key,$defaultValue); } public function attr($key=null,$defaultValue=null) { if(is_null($key)) { return $this->container; } return $this->container->get($key,$defaultValue); } public function setAttr($key,$value) { $this->container->set($key,$value); } }src/core/ErrorResponse.php000064400000000371144761607100011613 0ustar00_cleanInputs($get); $data = array_merge($data,$this->_cleanInputs($body)); $data = $this->_parseDots($data); $this->setHeaders($headers); $this->setMethod($method); $this->setPath($path); $this->setData($data); } public function body($key=null,$defaultValue=null) { if($key === null) { return $this->data; } return dhGlobal::getVal($this->data,$key,$defaultValue); } public function data($key=null,$defaultValue=null) { return $this->body($key,$defaultValue); } public function head($key=null,$defaultValue=null) { if($key === null) { return $this->headers; } return dhGlobal::getVal($this->headers,$key,$defaultValue); } public function header($key=null,$defaultValue=null) { return $this->head($key,$defaultValue); } public function getData() { return $this->data; } public function getHeader($key) { $key = strtolower($key); if(isset($this->headers[$key])) { return $this->headers[$key]; } return null; } public function getHeaders() { return $this->headers; } public function getMethod() { return $this->method; } public function getPath() { return $this->path; } public function getRootUrl() { return $this->rootUri; } public function getRawBody() { return $this->rawBody; } public function setMethod($method) { $this->method = $method; } public function setPath($path) { $this->path = $path; } public function setData($data) { $this->data = $data; } public function setRootUrl($rootUrl) { $this->rootUri = $rootUrl; } public function setRawBody($rawBody) { $this->rawBody = $rawBody; } public function setHeader($key,$value) { $key = strtolower($key); $this->headers[$key] = $value; } public function setHeaders($headers) { foreach($headers as $k=>$v) { $this->setHeader($k,$v); } } //utility functions private function _cleanInputs($data) { $clean_input = Array(); if (is_array($data)) { foreach ($data as $k => $v) { $clean_input[$k] = $this->_cleanInputs($v); } } else { $clean_input = trim($data); } return $clean_input; } private function _parseDots($array) { foreach($array as $k=>$v) { if(is_array($v)) { $array[$k] = $this->_parseDots($v); } if(strpos($k,".") !== false) { $this->_assignArrayByPath($array,$k,$v); unset($array[$k]); } } return $array; } private function _assignArrayByPath(&$arr, $path, $value, $separator='.') { $keys = explode($separator, $path); foreach ($keys as $key) { $arr = &$arr[$key]; } $arr = $value; } }src/core/Response.php000064400000017626144761607100010614 0ustar00"OK", 400=>"Bad Request", 401=>"Unauthorized", 403=>"Forbidden", 404=>"Not Found", 405=>"Method Not Allowed", 406=>"Not Acceptable", 409=>"Conflict", 410=>"Gone", 411=>"Length Required", 412=>"Precondition Failed", 413=>"Payload Too Large", 414=>"URI Too Long", 415=>"Unsupported Media Type", 416=>"Range Not Satisfiable", 417=>"Expectation Failed", 418=>"I'm a teapot", 420=>"Missing Required Inputs", 421=>"Invalid Inputs", 422=>"Unprocessable Entity", 423=>"Locked", 424=>"Failed Dependency", 425=>"Too Early", 426=>"Upgrade Required", 428=>"Precondition Required", 429=>"Too Many Requests", 431=>"Request Header Fields Too Large", 451=>"Unavailable For Legal Reasons", 500=>"Internal Server Error", 501=>"Not Implemented", 502=>"Bad Gateway", 503=>"Service Unavailable", 504=>"Gateway Timeout", 505=>"HTTP Version Not Supported", 506=>"Variant Also Negotiates", 507=>"Insufficient Storage", 508=>"Loop Detected", 510=>"Not Extended", 511=>"Network Authentication Required", ]; protected $data = []; protected $statusCode = 200; protected $message = null; protected static $pretty = true; protected static $fieldList = []; public function __construct($data=[], $status=200, $message=null) { $this->setData($data); $this->setStatus($status); $this->setMessage($message); } public function emit($terminate=null) { $this->emitStatusCode(); header("Content-Type: application/json"); echo $this->jsonEncode(static::parseFieldList($this->data)); if(static::shouldTerminate($terminate)) { exit; } } private function jsonEncode($data) { return json_encode($data, $this->getJsonFlags()); } private function emitStatusCode() { if(!isset(self::$requestCodeStatus[$this->statusCode])) { $this->statusCode = 500; } header("HTTP/1.1 " .$this->statusCode ." " .self::$requestCodeStatus[$this->statusCode]); } private function getJsonFlags() { if(static::$pretty) { return JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; } else { return JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; } } public function setStatus($status) { $this->statusCode = $status; } public function setMessage($message) { $this->message = $message; } public static function setPretty($pretty) { static::$pretty = $pretty; } /** * Set the field list to use when returning data * @param array $fieldList the fields to return */ public static function setFieldList($fieldList) { if(!is_array($fieldList)) { $fieldList = Utils::splitToArray($fieldList); } static::$fieldList = $fieldList; } public function setData($data) { $this->data = $data; } private static function parseFieldList($data) { if(!empty(static::$fieldList) && is_array($data) && !empty($data)) { $newData = static::filterArray(json_decode(json_encode($data,JSON_PARTIAL_OUTPUT_ON_ERROR),true) , static::$fieldList); if(empty($newData)) { return null; } else { return $newData; } } else { return $data; } } private static function filterArray($array,$fieldFilter,$currentKey='') { $result = []; foreach ($array as $key => $value) { $newKey = $currentKey === '' ? $key : $currentKey . '.' . $key; $matchedFilter = ''; foreach ($fieldFilter as $filter) { $pattern = "(\.|\.[0-9]+\.)"; $prePattern = "([0-9]+\.)?"; $newFilter = str_replace('.', $pattern, $filter); $regex = '/^'.$prePattern. $newFilter . '(\.|$)/'; if (preg_match($regex, $newKey)) { $matchedFilter = $filter; break; } } if ($matchedFilter !== '') { $result[$key] = $value; } elseif (is_array($value)) { $filteredValue = static::filterArray($value, $fieldFilter, $newKey); if (!empty($filteredValue)) { $result[$key] = $filteredValue; } } } return $result; } // Static Methods /** * Create a new Response object from an error, emits and terminates (exit) by default. * @param string|array $error the error message or data array * @param int $code (default:400) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromError($error, $code=400, $emit=null, $terminate=null) { $response = new ErrorResponse([ "error"=>$error, "code"=>$code, ], $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } /** * Create a new Response object from an exception, emits and terminates (exit) by default. * @param \Exception $exception the exception object * @param int $code (default:500) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromException($exception, $code=500, $emit=null, $terminate=null) { $response = new ErrorResponse([ "error"=>$exception->getMessage(), "code"=>$code, ], $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } /** * Create a new Response object from a success, emits and terminates (exit) by default. * @param string|array $data the data to return * @param int $code (default:200) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromSuccess($data, $code=200, $emit=null, $terminate=null) { $response = new Response($data, $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } public static function shouldEmit($emit=null) { if($emit===null) { return self::$autoEmit; } else { return $emit; } } public static function shouldTerminate($terminate=null) { if($terminate===null) { return self::$autoTerminate; } else { return $terminate; } } }src/core/Utils.php000064400000000446144761607100010106 0ustar00"authed", "permitted"=>"permitted", "authUser"=>"authUser", "authMessage"=>"authMessage", "authInfo"=>"authInfo", ]; private $attributes = [ "authed"=>false, "permitted"=>false, "authUser"=>null, "authMessage"=>"", "authInfo"=>[], ]; public function __invoke($object, $next) { $this->object = $object; $this->setup($object); $this->_authenticate(); $this->_checkPermissed(); $this->attributesToObject($object); return $next($object); } public function attributesToObject(&$object) { foreach($this->attributeMap as $key=>$value) { $object->setAttr($value,$this->attributes[$key]); } $object->setAttr("auth",$this->attributes); } private function _authenticate() { if($this->attributes["authed"]) { return true; } $this->setAuthed(false); if($this->authenticate()) { $this->setAuthed(true); $this->authSuccess(); } else { $this->authFailed(); } return $this->getAuthed(); } public function _checkPermissed() { if(!$this->getAuthed()) { $this->setPermissed(false); $this->notPermissed(); } else { $this->setPermissed($this->checkPermissed()); if($this->getPermissed()) { $this->wasPermissed(); } else { $this->notPermissed(); } } } abstract public function setup($object); abstract public function authenticate(); abstract public function checkPermissed(); abstract public function authSuccess(); abstract public function authFailed(); abstract public function wasPermissed(); abstract public function notPermissed(); public function getAuthed() { return $this->attributes["authed"]; } public function setAuthed($authed) { $this->setAttribute("authed",$this->assertBool($authed)); } public function getPermissed() { return $this->attributes["permitted"]; } public function setPermissed($permissed) { $this->setAttribute("permitted",$this->assertBool($permissed)); } public function getAuthUser() { return $this->attributes["authUser"]; } public function setAuthUser($authUser) { $this->setAttribute("authUser",$authUser); } public function getAuthMessage() { return $this->attributes["authMessage"]; } public function setAuthMessage($authMessage) { $this->setAttribute("authMessage",$authMessage); } public function getAuthInfo() { return $this->attributes["authInfo"]; } public function setAuthInfo($authInfo) { $this->setAttribute("authInfo",$authInfo); } public function getAttribute($key,$default=null) { if(isset($this->attributes[$key])) { return $this->attributes[$key]; } return $default; } public function setAttribute($key,$value) { $this->attributes[$key] = $value; } protected function addAttributeMap($key,$value) { $this->attributeMap[$key] = $value; } public function getAttributes() { return $this->attributes; } private function assertBool($bool) { if(is_array($bool) || is_object($bool)) { $bool = false; } return $bool ? true : false; } } src/middleware/CallbackMiddleware.php000064400000000540144761607100013660 0ustar00callback = $callback; } public function __invoke($object,$next) { $callback = $this->callback; return $callback($object,$next); } }src/middleware/HmacAuthentication.php000064400000012602144761607100013740 0ustar00assertCallbacks(); $this->apiKey = $object->header($this->headerApiKey,false); $this->apiNonce = $object->header($this->headerApiNonce,false); $this->apiSign = $object->header($this->headerApiSign,false); $this->path = $this->getPath($object); $this->object = $object; $this->apiKeyInfo = $this->getApiKeyInfo(); if(isset($this->apiKeyInfo["secret"])) { $this->apiSecret = $this->apiKeyInfo["secret"]; } else { $this->apiSecret = null; } $this->setAttribute("authUser",$this->apiKeyInfo); } public function authenticate() { if(is_null($this->apiSecret)) { $this->setAuthMessage("invalid api key"); return false; } $signedString = $this->getSignature(); if($signedString == $this->apiSign) { if($this->validateNonce && isset($this->apiKeyInfo["nonce"])) { if($this->apiNonce <= $this->apiKeyInfo["nonce"]) { $this->setAuthMessage("invalid nonce"); return false; } } return true; } $this->setAuthMessage("invalid signature"); return false; } public function authSuccess() { $cb = $this->authSuccessCallback; return $cb($this->apiKey,$this->apiNonce); } public function getApiKeyInfo() { $cb = $this->getKeyInfoCallback; return $cb($this->apiKey); } public function __construct($options=[],$getKeyInfoCallback=null,$authSuccessCallback=null,$permissionCallback=null) { if(isset($options["headerApiKey"])) { $this->headerApiKey = $options["headerApiKey"]; } if(isset($options["headerApiNonce"])) { $this->headerApiNonce = $options["headerApiNonce"]; } if(isset($options["headerApiSign"])) { $this->headerApiSign = $options["headerApiSign"]; } if(isset($options["signatureTemplate"])) { $this->signatureTemplate = $options["signatureTemplate"]; } if(isset($options["hashAlgo"])) { $this->hashAlgo = $options["hashAlgo"]; } if(isset($options["withLeadingSlash"])) { $this->withLeadingSlash = $options["withLeadingSlash"]; } if(is_callable($getKeyInfoCallback)) { $this->getKeyInfoCallback = $getKeyInfoCallback; } if(is_callable($authSuccessCallback)) { $this->authSuccessCallback = $authSuccessCallback; } if(is_callable($permissionCallback)) { $this->permissedCallback = $permissionCallback; } } public function getSignature() { $signature = str_replace("{APIKEY}",$this->apiKey,$this->signatureTemplate); $signature = str_replace("{NONCE}",$this->apiNonce,$signature); $signature = str_replace("{PATH}",$this->path,$signature); $signature = hash_hmac($this->hashAlgo,$signature,$this->apiSecret); return $signature; } private function getPath($object) { $path = dhGlobal::trimString($object->getPath(),"/"); if($this->withLeadingSlash) { $path = "/".$path; } return $path; } public function setHeaderApiKey($headerApiKey) { $this->headerApiKey = $headerApiKey; } public function setHeaderApiNonce($headerApiNonce) { $this->headerApiNonce = $headerApiNonce; } public function setHeaderApiSign($headerApiSign) { $this->headerApiSign = $headerApiSign; } public function setSignatureTemplate($signatureTemplate) { $this->signatureTemplate = $signatureTemplate; } public function setHashAlgo($hashAlgo) { $this->hashAlgo = $hashAlgo; } private function assertCallbacks() { if(!is_callable($this->getKeyInfoCallback)) { $this->getKeyInfoCallback = function($apiKey) { return false; }; } if(!is_callable($this->authSuccessCallback)) { $this->authSuccessCallback = function($apiKey,$apiNonce) { return false; }; } if(!is_callable($this->permissedCallback)) { $this->permissedCallback = function($apiKey,$object) { return true; }; } } }src/middleware/MiddlewareInterface.php000064400000000173144761607100014066 0ustar00apiUser = $object->head($this->headerUsername); $this->apiPass = $object->head($this->headerPassword); } public function authenticate() { $this->assertCallbacks(); $cb = $this->authCheckCallback; if($cb($this->apiUser,$this->apiPass)) { return true; } return false; } public function checkPermissed() { $this->assertCallbacks(); $cb = $this->permissedCallback; if($cb($this->apiUser,$this->object)) { return true; } } public function authSuccess() { $this->assertCallbacks(); $cbAuth = $this->authSuccessCallback; $cbAuth($this->apiUser,$this->apiPass); } public function authFailed() {} public function wasPermissed() {} public function notPermissed() {} public function assertCallbacks() { if(!is_callable($this->authCheckCallback)) { $this->authCheckCallback = function($apiKey) { return false; }; } if(!is_callable($this->authSuccessCallback)) { $this->authSuccessCallback = function($apiKey,$apiNonce) { return false; }; } if(!is_callable($this->permissedCallback)) { $this->permissedCallback = function($apiKey,$object) { return true; }; } } public function __construct($options=[],$authCheckCallback=null,$authSuccessCallback=null,$permissionCallback=null) { if(isset($options["headerUsername"])) { $this->headerUsername = $options["headerUsername"]; } if(isset($options["headerPassword"])) { $this->headerPassword = $options["headerPassword"]; } if(is_callable($authCheckCallback)) { $this->authCheckCallback = $authCheckCallback; } if(is_callable($authSuccessCallback)) { $this->authSuccessCallback = $authSuccessCallback; } if(is_callable($permissionCallback)) { $this->permissedCallback = $permissionCallback; } } }src/routing/Group.php000064400000000152144761607100010633 0ustar00name = $name; $this->path = $path; $this->methods = $methods; $this->handler = $handler; $this->options = $options; if(isset($options["middleware"])) { foreach($options["middleware"] as $middleware) { $this->addMiddleware($middleware); } } if(isset($options["middlewareResponse"])) { foreach($options["middlewareResponse"] as $middleware) { $this->addMiddlewareResponse($middleware); } } } public function setRouter($router) { $this->router = $router; } public function addMiddleware($middleware) { $this->middlewares[] = API::assertMiddleware($middleware); } public function getMiddleware() { return $this->middlewares; } public function addMiddlewareResponse($middleware) { $this->middlewareResponse[] = API::assertMiddleware($middleware); } public function getMiddlewareResponse() { return $this->middlewareResponse; } public function match($path, $method) { $regex = $this->getPath(); $this->varCallbacks = []; foreach ($this->getVarsNames() as $variable) { $varName = trim($variable, '{\}'); $parts = explode("|",$varName); $paramName = array_shift($parts); $nameParts = explode(":",$paramName); $regexPattern = "[^/]+"; if(count($nameParts) > 1) { $paramName = array_shift($nameParts); $shortcut = $this->router->getRegex($nameParts[0]); $regexPattern = $shortcut["regex"]; if($shortcut["callback"] !== null && (!isset($this->varCallbacks[$paramName]) || !in_array($shortcut["callback"],$this->varCallbacks[$paramName]))) { $this->varCallbacks[$paramName][] = $shortcut["callback"]; } } $regex = str_replace($variable, '(?P<' . $paramName . '>'.$regexPattern.'+)', $regex); if(!empty($parts)) { foreach($parts as $part) { if(!isset($this->varCallbacks[$paramName]) || !in_array($part,$this->varCallbacks[$paramName])) { $this->varCallbacks[$paramName][] = $part; } } } } if (in_array($method, $this->getMethods()) && preg_match('#^' . $regex . '$#sD', self::trimPath($path), $matches)) { $values = array_filter($matches, static function ($key) { return is_string($key); }, ARRAY_FILTER_USE_KEY); $this->vars = $values; $this->parseVarCallbacks(); return true; } $this->varCallbacks = []; return false; } public function getName() { return $this->name; } public function getPath() { return $this->path; } public function getHandler() { $handler = $this->handler; if(!empty($this->vars)) { if(is_array($handler) && (strpos($handler[0],"{") !== false || strpos($handler[1],"{") !== false)) { foreach($this->vars as $name=>$val) { $classVal=$methodVal=null; if(is_array($val)) { $classVal = implode("\\",$val); } else { $classVal = $val; $methodVal = $val; } if(is_string($handler[0]) && !is_null($classVal)) { $handler[0] = str_replace("{".$name."}",$classVal,$handler[0]); } if(is_string($handler[1]) && !is_null($classVal)) { $handler[1] = str_replace("{".$name."}",$methodVal,$handler[1]); } } } } return $handler; } public function getMethods() { return $this->methods; } public function getVarsNames() { preg_match_all('/{[^}]*}/', $this->path, $matches); $reset = reset($matches); return $reset ? $reset : []; } public function hasVars() { return $this->getVarsNames() !== []; } public function getVars() { return $this->vars; } public static function trimPath($path) { return '/' . rtrim(ltrim(trim($path), '/'), '/'); } public function parseVar($var,$value) { if(($callable = $this->router->getVariableCallback($var)) !== false) { if (!is_callable($callable)) { return $value; } return call_user_func($callable, $value); } return $value; } private function parseVarCallbacks() { foreach($this->varCallbacks as $varName=>$callbacks) { if(isset($this->vars[$varName])) { $value = $this->vars[$varName]; foreach($callbacks as $callback) { if(($callable = $this->router->getVariableCallback($callback)) !== false) { if (!is_callable($callable)) { continue; } $value = call_user_func($callable, $value); } } $this->vars[$varName] = $value; } } } public static function fromArray($path,$methods,$callable,$requireAuth = false) { return new Route($path,$path,$methods,$callable,$requireAuth); } }src/routing/Router.php000064400000007213144761607100011024 0ustar00 [ "regex" => "[0-9]+", "callback" => null, ], "idlist"=> [ "regex" => "[0-9,;]+", "callback" => "split", ], "number" => [ "regex" => "[0-9]+", "callback" => null, ], "numberlist"=> [ "regex" => "[0-9,;]+", "callback" => "split", ], "string" => [ "regex" => "[a-zA-Z0-9\-_]+", "callback" => null, ], "stringlist" => [ "regex" => "[a-zA-Z0-9\-_,;]+", "callback" => "split", ], "alpha" => [ "regex" => "[a-zA-Z]+", "callback" => null, ], "alphalist" => [ "regex" => "[a-zA-Z,;]+", "callback" => "split", ], "all" => [ "regex" => ".+", "callback" => null, ], ]; private $variableCallbacks = [ "split" => ["\\boru\\dhapi\\routing\\Router","splitToArray"], ]; /** @var MiddlewareInterface[] */ private $middlewares = []; /** @var MiddlewareInterface[] */ private $middlewareResponse = []; /** * @var Route[] $routes */ public function __construct($routes=[]) { foreach($routes as $route) { $this->add($route); } } /** * @var Route $route */ public function add($route) { $route->setRouter($this); $this->routes[$route->getName()] = $route; return $this; } public function addRoute($routeName,$path,$methods,$parameters,$options=[]) { return $this->add(new Route($routeName,$path,$methods,$parameters,$options)); } public function addCallback($name,$callback) { $this->variableCallbacks[$name] = $callback; return $this; } public function addRegex($name,$regex,$callback=null) { $this->regexShortcuts[$name] = [ "regex" => $regex, "callback" => $callback, ]; return $this; } public function matchFromPath($pathString, $method, $api) { foreach ($this->routes as $route) { if ($route->match($pathString, $method) === false) { continue; } return $route; } return false; } public function addMiddleware($middleware) { $this->middlewares[] = API::assertMiddleware($middleware); } public function getMiddleware() { return $this->middlewares; } public function addMiddlewareResponse($middleware) { $this->middlewareResponse[] = API::assertMiddleware($middleware); } public function getMiddlewareResponse() { return $this->middlewareResponse; } public function getRegex($name) { if(isset($this->regexShortcuts[$name])) { return $this->regexShortcuts[$name]; } return ["regex" => "[^/]+", "callback" => null]; } public function getVariableCallback($name) { if(isset($this->variableCallbacks[$name])) { return $this->variableCallbacks[$name]; } return false; } public static function splitToArray($idInput="") { if(empty($idInput)) return []; $delims = [",",";"]; $idInput = str_replace($delims,$delims[0],$idInput); return explode($delims[0],$idInput); } }test/init.php000064400000000073144761607100007205 0ustar00 RewriteEngine On RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule .+ - [L] #RewriteRule ^(.+)/?$ index.php?PATH=$1&%{QUERY_STRING} [L] RewriteRule ^(.+)/?$ v2test.php?APIREQ=$1&%{QUERY_STRING} [L] test/web/api/test/load.php000064400000000260144761607100011464 0ustar00"; } public function process() { echo "process!
"; } }test/web/api/whoami.php000064400000000467144761607100011063 0ustar00api = $api; } public function process($opts=[]) { $data = ["class"=>__CLASS__,"opts"=>$opts]; echo json_encode($data); } }test/web/index.php000064400000000000144761607100010114 0ustar00test/web/v2test.php000064400000002525144761607100010252 0ustar00"test/endpoint2!", "args"=>$api->getArgs(), "apiArgs"=>$api->getRequest()->body(), "auth"=>$api->attr("auth"), "rootUrl"=>$api->getRequest()->getRootUrl(), ]); }; $hmacAuthenticator = new HmacAuthentication([],function($apiKey) { return ["secret"=>"test"]; },function($apiKey,$apiNonce) { return true; },function($apiKey) { return true; }); $options = [ "middleware"=>[$hmacAuthenticator] ]; $router = new Router(); $router->add(new Route("helloWorld", "/helloworld", ["GET","POST"], ["\\boru\\dhapi\\Controller","helloWorld"])); $router->add(new Route("helloWorld", "/helloworld/{var}/list", ["GET","POST"], ["\\boru\\{var}\\Controller","helloWorld"])); $router->add(new Route("test", "/test/{id:idlist}", ["GET","POST"], $endpoint, $options)); //$router->addClass("\\boru\\dhapi\\ClassController"); $request = API::requestFromGlobals(); $api = new API($request,$router); $response = $api->process(); $response->emit(true);