conn = new PDO('sqlite:../db/api.db'); } public function getApiSecret($apikey) { $query = "SELECT secret FROM keys WHERE api_key=".$this->conn->quote($apikey); $result = $this->conn->query($query); return $result->fetchColumn(); } public function createDocument($filePath, $contentType) { $query = "INSERT INTO documents VALUES ( ".$this->conn->quote($filePath).", ".$this->conn->quote($contentType).", datetime('now'))"; $this->conn->exec($query); return $this->conn->lastInsertId(); } public function updateDocument($id, $filePath, $contentType) { $query = "UPDATE documents SET file_path = ".$this->conn->quote($filePath).", content_type = ".$this->conn->quote($contentType)." WHERE rowid=".(int)$id; $this->conn->exec($query); } public function deleteDocument($id) { $query = "DELETE FROM documents WHERE rowid=".(int)$id; $this->conn->exec($query); } public function getDocument($id) { $query = "SELECT * FROM documents WHERE rowid=".(int)$id; $result = $this->conn->query($query); return $result->fetch(PDO::FETCH_ASSOC); } public function getAllDocuments() { $query = "SELECT rowid as id, * FROM documents"; $result = $this->conn->query($query); return $result->fetchAll(PDO::FETCH_ASSOC); } } class Auth { /** * Sign the params with the secret key (as per Flickr API) * * @param type $secret * @param type $params * @return type */ static private function sign($secret, $params) { ksort($params); $signing = ''; foreach ($params as $key => $value) { $signing .= $key . $value; } return md5($secret . $signing); } /** * Check the signature of a request * * @param type $params * @return type */ static public function checkSig($params) { if (!isset($params['api_sig'])) { return false; } // Get api signature $apiSig = $params['api_sig']; unset($params['api_sig']); unset($params['_url']); if (!($secret = self::getApiSecret())) { return false; } return self::sign($secret, $params) == $apiSig; } /** * Grab the users api key from the headers and return their secret key * * @return boolean */ static private function getApiSecret() { $request_headers = apache_request_headers(); if (!isset($request_headers['X-Auth'])) { return false; } $apikey = $request_headers['X-Auth']; $db = new ApiDb(); return $db->getApiSecret($apikey); } } class HandlerManger { protected $handlers = array(); function add($path, Handler $handler) { $this->handlers[$path] = $handler; } function handle($path) { if (array_key_exists($path, $this->handlers)) { switch ($_SERVER['REQUEST_METHOD']) { case 'GET': $this->handlers[$path]->handleGet($_GET); break; case 'POST': $this->handlers[$path]->handlePost($_POST); break; case 'PUT': parse_str(file_get_contents("php://input"), $post_vars); $this->handlers[$path]->handlePut(array_merge($_GET, $post_vars)); break; case 'DELETE': $this->handlers[$path]->handleDelete($_POST); break; default: return Response::send(null, 405); } } else { return Response::send(null, 404); } } } class Response { private static $STATUS_CODE = array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported'); public static function send($data, $status = 200) { header("HTTP/1.1 " . $status . " " . self::$STATUS_CODE[$status]); header("Content-Type: application/json", true); if (!is_null($data)) { echo json_encode($data); } } public static function sendFile($filePath, $metadata, $contentType, $status = 200) { header("HTTP/1.1 " . $status . " " . self::$STATUS_CODE[$status]); header("Content-Type: ".$contentType, true); header("X-Metadata: " . json_encode($metadata), true); readfile($filePath); } } interface Handler { public function handlePost($params); public function handlePut($params); public function handleDelete($params); public function handleGet($params); } class DocumentHandler implements Handler { const API_PATH = '/api/documents'; private function checkPathExists($path) { // File does not exist if (!($path = realpath($path))) { return false; } return true; } private function checkPathIsInCwd($path) { $path = realpath($path); // File is not within the www folder if (strpos($path, '/var/www') !== 0) { return false; } return true; } /** * Create a new document record * TODO: Implement the upload data function (instead of FTP) * * @param type $params * @return type */ public function handlePost($params) { if (!Auth::checkSig($params)) { return Response::send(array('error' => 'API signature failed.'), 401); } $db = new ApiDb(); if (!isset($params['filepath'])) { return Response::send(array('error' => 'Missing file path'), 500); } $path = $params['filepath']; if (!$this->checkPathExists($path)) { return Response::send(array('error' => 'File path does not exist'), 500); } if (!$this->checkPathIsInCwd($path)) { return Response::send(array('error' => 'File path cannot be below /var/www'), 500); } if (!isset($params['contenttype'])) { return Response::send(array('error' => 'Missing content type'), 500); } logRestEntry($params); $rowid = $db->createDocument($path, $params['contenttype']); return Response::send(array('id' => $rowid, 'uri' => self::API_PATH.'/id/'.$rowid), 201); } /** * Change the document file path (moved or renamed documents) * TODO: Actually move/rename documents * * @param type $params * @return type */ public function handlePut($params) { if (!Auth::checkSig($params)) { return Response::send(array('error' => 'API signature failed.'), 401); } $db = new ApiDb(); if (!isset($params['id'])) { return Response::send(array('error' => 'Missing id'), 500); } $id = $params['id']; if (!($document = $db->getDocument($id))) { return Response::send(null, 404); } if (!isset($params['filepath'])) { return Response::send(array('error' => 'Missing file path'), 500); } $path = $params['filepath']; if (!$this->checkPathExists($path)) { return Response::send(array('error' => 'File path does not exist'), 500); } if (!$this->checkPathIsInCwd($path)) { return Response::send(array('error' => 'File path cannot be below current path'), 500); } if (!isset($params['contenttype'])) { return Response::send(array('error' => 'Missing content type'), 500); } logRestEntry($params); $db->updateDocument($id, $path, $params['contenttype']); return Response::send(array(), 200); } /** * Deletes a document record * TODO: Actually delete the document file * * @param type $params * @return type */ public function handleDelete($params) { if (!Auth::checkSig($params)) { return Response::send(array('error' => 'API signature failed.'), 401); } $db = new ApiDb(); if (!isset($params['id'])) { return Response::send(array('error' => 'Missing id'), 500); } $id = $params['id']; if (!($document = $db->getDocument($id))) { return Response::send(null, 404); } logRestEntry($params); $db->deleteDocument($id); return Response::send(array(), 200); } /** * Returns a document JSON object or a list of document URIs * * @param type $params * @return type */ public function handleGet($params) { $db = new ApiDb(); if (isset($params['id'])) { $id = $params['id']; if (!($document = $db->getDocument($id))) { return Response::send(null, 404); } logRestEntry($params); return Response::sendFile($document['file_path'], $document, $document['content_type'], 200); } else { $results = $db->getAllDocuments(); $documents = array(); foreach($results as $document) { $document['uri'] = self::API_PATH . '/id/'.$document['id']; unset($document['id']); $documents[] = $document; } logRestEntry($params); return Response::send($documents, 200); } } } $hm = new HandlerManger(); $hm->add('/documents', new DocumentHandler()); $hm->handle($_GET['_url']);