I'm learning PHP and 'web' development (having an offline programming background).
I plan to setup a simple storage server API that I'll adress with a javascript application.
The following code :
- does not consider authentification
- does provide file upload/deletion, folder listing
I got some inspiration from the Dropbox REST API for error values and action
names.
<?php
/*
the 'API' provided is :
* ?action=list_folder&path=</some/path>
* ?action=permanently_delete&path=</some/path>
* ?action=upload&path=</some/path>
The provided </some/path> values are joined to 'ROOT_FOLDER' constant.
Can't create folders or download files.
Needs PHP 5.5+ for `finally` clauses.
*/
error_reporting(0);
define('ROOT_FOLDER', 'files/' ); // IMPORTANT(nico) '/' at the end
//============================================================================
header('Access-Control-Allow-Origin: *');
header('Cache-Control: no-cache, must-revalidate');
$result = null;
try {
$action = _filesystem_checked_GET('action');
switch ($action) {
case 'list_folder':
$path = _filesystem_checked_GET('path');
$result = _filesystem_list_folder($path);
break;
case 'permanently_delete':
$path = _filesystem_checked_GET('path');
$result = _filesystem_permanently_delete($path);
break;
case 'upload':
$path = _filesystem_checked_GET('path');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new _filesystem_HTTPException('Bad Request', 400, 'POST expected');
}
$result = _filesystem_upload($path);
break;
default:
throw new _filesystem_HTTPException('Bad Request', 400, 'Unknown action');
}
}
catch (_filesystem_HTTPException $e) {
$e->setErrorHeader();
$result = ['error'=> ['.tag'=> 'other'], 'error_summary'=> $e->content];
}
catch (Exception $e) {
header("HTTP/1.1 500 Internal error");
$result = ['error'=> ['.tag'=> 'other'], 'error_summary'=> $e->getMessage()];
}
header('Content-type:application/json;charset=utf-8');
echo json_encode($result);
//============================================================================
function _filesystem_upload($path) {
$abspath = _filesystem_abspath($path);
$temppath = tempnam(dirname($abspath), 'temp');
if (!$temppath) {
throw new _filesystem_HTTPException("`tempnam` failed", 500);
}
try {
$dst = fopen($temppath, "wb");
$src = fopen("php://input", "r"); // POST raw data
try {
if (!$src || !$dst) {
throw new _filesystem_HTTPException("Could not create file", 500);
}
// copy streams
while ($data = fread($src, 1024))
{
if ($data === FALSE) {
throw new _filesystem_HTTPException("Could not read source data", 500); // FIXME(nico) endpoint error ?
}
$written = fwrite($dst, $data, 1024);
if ($written != strlen($written)) {
throw new _filesystem_HTTPException("Could not write to file", 500);
}
}
}
finally {
fclose($src);
fclose($dst);
}
// finalize destination file
if (!rename($temppath, $abspath)) {
throw new _filesystem_HTTPException("Could not finalize file", 500);
}
}
finally {
if (file_exists($temppath)) {
unlink($temppath);
}
}
$name = basename($abspath);
$result = _filesystem_metadata($name, $abspath);
return $result;
}
//----------------------------------------------------------------------------
function _filesystem_permanently_delete($path) {
$abspath = _filesystem_abspath($path);
if (unlink($abspath)) { // FIXME(nico) can trigger a warning, check file_exists first, and improve error reporting
return null;
}
else {
return ['error'=> ['.tag'=> 'other'], 'error_summary'=> "Could not unlink file"];
}
}
//----------------------------------------------------------------------------
function _filesystem_list_folder($path) {
$abspath = _filesystem_abspath($path);
$names = array_diff(scandir($abspath), array('..', '.'));
$result = [];
foreach ($names as $name) {
$path = _filesystem_path_join($abspath, $name);
$size = filesize($path);
$server_modified = date(DATE_ISO8601, filemtime($path));
$tag = null;
if (is_dir($path)) { $tag='folder'; }
elseif (is_file($path)) { $tag='file'; }
$metadata = _filesystem_metadata($name, $path);
if ($metadata['.tag'] != null) {
// NOTE(nico) do not include info on 'undefined' filesystem items
$result[] = $metadata;
}
}
return [ 'entries'=> $result, 'has_more'=> false ];
}
//============================================================================
function _filesystem_metadata($name, $path) {
$size = filesize($path);
$server_modified = date(DATE_ISO8601, filemtime($path));
$tag = null;
if (is_dir($path)) { $tag='folder'; }
elseif (is_file($path)) { $tag='file'; }
return [ ".tag"=>$tag, 'name'=>$name, 'server_modified'=>$server_modified, 'size'=>$size ];
}
function _filesystem_abspath($path) {
return ROOT_FOLDER . $path; // FIXME(nico) security check, path should be absolute starting with '/'
}
function _filesystem_path_join($root, $path) {
return $root . $path; // FIXME(nico) check '/' & stuff
}
function _filesystem_checked_GET($varname) {
if (!isset($_GET[$varname])) {
throw new _filesystem_HTTPException('Bad Request', 400, 'Missing parameter `' . $varname . '`');
}
return $_GET[$varname];
}
class _filesystem_HTTPException extends Exception {
public $content = null;
public function __construct($message = null, $code = 501, $content = null) {
parent::__construct($message, $code);
$this->content = $content;
}
public function setErrorHeader() {
header("HTTP/1.1 " . $this->code . ' ' . $this->getMessage());
}
}
//============================================================================
?>