From d59962443687127ea1defc2f8ac41af1c2c02fe4 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Sat, 25 Oct 2014 13:55:08 +0200 Subject: first go at reworking; needs to be redesigned Signed-off-by: Florian Pritz --- application/config/routes.php | 1 + application/controllers/api.php | 38 ++++++++ application/controllers/api/api_controller.php | 15 ++++ application/controllers/api/v1.php | 83 ++++++++++++++++++ application/controllers/file.php | 92 ++++---------------- application/helpers/filebin_helper.php | 16 ++++ application/service/files.php | 116 +++++++++++++++++++++++++ 7 files changed, 285 insertions(+), 76 deletions(-) create mode 100644 application/controllers/api.php create mode 100644 application/controllers/api/api_controller.php create mode 100644 application/controllers/api/v1.php create mode 100644 application/service/files.php (limited to 'application') diff --git a/application/config/routes.php b/application/config/routes.php index e0d963405..23c9bebee 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -42,6 +42,7 @@ $route['default_controller'] = "file"; $route['user/(:any)'] = "user/$1"; $route['file/(:any)'] = "file/$1"; $route['tools/(:any)'] = "tools/$1"; +$route['api/(:any)'] = "api/route/$1"; $route['(:any)'] = "file/index/$1"; $route['404_override'] = ''; diff --git a/application/controllers/api.php b/application/controllers/api.php new file mode 100644 index 000000000..626e7b91a --- /dev/null +++ b/application/controllers/api.php @@ -0,0 +1,38 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Api extends MY_Controller { + + public function __construct() + { + parent::__construct(); + + $this->load->model('mfile'); + $this->load->model('mmultipaste'); + } + + public function route() { + $requested_version = $this->uri->segment(2); + $function = $this->uri->segment(3); + $major = intval(explode(".", $requested_version)[0]); + + $class = "controllers\\api\\v".$major; + + if (!class_exists($class) || version_compare($class::get_version(), $requested_version, "<")) { + return send_json_error_reply("Requested API version is not supported"); + } + + if (!preg_match("/^[a-zA-Z-_]+$/", $function)) { + return send_json_error_reply("Invalid function requested"); + } + + $controller = new $class; + return $controller->$function(); + } +} diff --git a/application/controllers/api/api_controller.php b/application/controllers/api/api_controller.php new file mode 100644 index 000000000..ca24dae59 --- /dev/null +++ b/application/controllers/api/api_controller.php @@ -0,0 +1,15 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace controllers\api; + +abstract class api_controller { + abstract static public function get_version(); +} + diff --git a/application/controllers/api/v1.php b/application/controllers/api/v1.php new file mode 100644 index 000000000..e6d3c56fe --- /dev/null +++ b/application/controllers/api/v1.php @@ -0,0 +1,83 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ +namespace controllers\api; + +class v1 extends api_controller { + protected $json_enabled_functions = array( + "upload", + "get_config", + "history", + ); + + static public function get_version() + { + return "1.0.1"; + } + + public function __construct() + { + parent::__construct(); + + $this->load->model('mfile'); + $this->load->model('mmultipaste'); + } + + public function upload() + { + $this->muser->require_access("basic"); + + $files = getNormalizedFILES(); + + if (empty($files)) { + show_error("No file was uploaded or unknown error occured."); + } + + $errors = service\files::verify_uploaded_files($files); + if (!empty($errors)) { + return send_json_reply($errors, "upload-error"); + } + + $limits = $this->muser->get_upload_id_limits(); + $urls = array(); + + foreach ($files as $file) { + $id = $this->mfile->new_id($limits[0], $limits[1]); + service\files::add_file($id, $file["tmp_name"], $file["name"]); + $ids[] = $id; + $urls[] = site_url($id).'/'; + } + + return send_json_reply(array( + "ids" => $ids, + "urls" => $urls, + )); + } + + public function get_config() + { + return send_json_reply(array( + "upload_max_size" => $this->config->item("upload_max_size"), + )); + } + + public function history() + { + $this->muser->require_access("apikey"); + $history = service\files::history($this->muser->get_userid()); + return send_json_reply($history); + } + + public function delete() + { + $this->muser->require_access("apikey"); + + + } +} +# vim: set noet: diff --git a/application/controllers/file.php b/application/controllers/file.php index 2617d4840..ac2c4b4ca 100644 --- a/application/controllers/file.php +++ b/application/controllers/file.php @@ -623,10 +623,7 @@ class File extends MY_Controller { { $this->muser->require_access("apikey"); - $user = $this->muser->get_userid(); - - $query = array(); - $lengths = array(); + $history = service\files::history($this->muser->get_userid()); // key: database field name; value: display name $fields = array( @@ -645,22 +642,7 @@ class File extends MY_Controller { $order = is_cli_client() ? "ASC" : "DESC"; - $items = $this->db->select(implode(',', array_keys($fields))) - ->from('files') - ->where('user', $user) - ->get()->result_array(); - - $query = $this->db->query(" - SELECT m.url_id id, sum(f.filesize) filesize, m.date, '' hash, '' mimetype, concat(count(*), ' file(s)') filename - FROM multipaste m - JOIN multipaste_file_map mfm ON m.multipaste_id = mfm.multipaste_id - JOIN files f ON f.id = mfm.file_url_id - WHERE m.user_id = ? - GROUP BY m.url_id - ", array($user))->result_array(); - - $items = array_merge($items, $query); - uasort($items, function($a, $b) use ($order) { + uasort($history["items"], function($a, $b) use ($order) { if ($order == "ASC") { return $a["date"] - $b["date"]; } else { @@ -668,12 +650,8 @@ class File extends MY_Controller { } }); - if (static_storage("response_type") == "json") { - return send_json_reply($items); - } - - foreach($items as $key => $item) { - $items[$key]["filesize"] = format_bytes($item["filesize"]); + foreach($history["items"] as $key => $item) { + $history["items"][$key]["filesize"] = format_bytes($item["filesize"]); if (is_cli_client()) { // Keep track of longest string to pad plaintext output correctly foreach($fields as $length_key => $value) { @@ -685,19 +663,10 @@ class File extends MY_Controller { } } - $total_size = $this->db->query(" - SELECT sum(filesize) sum - FROM ( - SELECT DISTINCT hash, filesize - FROM files - WHERE user = ? - ) sub - ", array($user))->row_array(); - - $this->data["items"] = $items; + $this->data["items"] = $history["items"]; $this->data["lengths"] = $lengths; $this->data["fields"] = $fields; - $this->data["total_size"] = format_bytes($total_size["sum"]); + $this->data["total_size"] = format_bytes($history["total_size"]); $this->load->view('header', $this->data); $this->load->view($this->var->view_dir.'/upload_history', $this->data); @@ -882,6 +851,7 @@ class File extends MY_Controller { show_error("Error while uploading: File too big", 413); } + // FIXME: this duplicates service\files::add_file (kind of) $limits = $this->muser->get_upload_id_limits(); $id = $this->mfile->new_id($limits[0], $limits[1]); $hash = md5($content); @@ -915,44 +885,19 @@ class File extends MY_Controller { show_error("No file was uploaded or unknown error occured."); } - // Check for errors before doing anything - // First error wins and is displayed, these shouldn't happen that often anyway. - foreach ($files as $key => $file) { - // getNormalizedFILES() removes any file with error == 4 - if ($file['error'] !== UPLOAD_ERR_OK) { - // ERR_OK only for completeness, condition above ignores it - $errors = array( - UPLOAD_ERR_OK => "There is no error, the file uploaded with success", - UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini", - UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", - UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded", - UPLOAD_ERR_NO_FILE => "No file was uploaded", - UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder", - UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk", - UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload", - ); - - $msg = "Unknown error."; - - if (isset($errors[$file['error']])) { - $msg = $errors[$file['error']]; - } else { - $msg = "Unknown error code: ".$file['error'].". Please report a bug."; - } - - show_error("Error while uploading: ".$msg, 400); - } - - $filesize = filesize($file['tmp_name']); - if ($filesize > $this->config->item('upload_max_size')) { - show_error("Error while uploading: File too big", 413); + $errors = service\files::verify_uploaded_files($files); + if (!empty($errors)) { + $messages = array(); + foreach ($errors as $error) { + $messages[] = htmlspecialchars($error["filename"]).": ".$error["message"]; } + show_error("Error(s) occured while uploading:
".implode("
", $messages), 400); } + $limits = $this->muser->get_upload_id_limits(); + foreach ($files as $key => $file) { - $limits = $this->muser->get_upload_id_limits(); $id = $this->mfile->new_id($limits[0], $limits[1]); - $hash = md5_file($file['tmp_name']); // work around a curl bug and allow the client to send the real filename base64 encoded // TODO: this interface currently sets the same filename for every file if you use multiupload @@ -968,12 +913,7 @@ class File extends MY_Controller { $filename = trim($filename, "\r\n\0\t\x0B"); - $folder = $this->mfile->folder($hash); - file_exists($folder) || mkdir ($folder); - $file_path = $this->mfile->file($hash); - - move_uploaded_file($file['tmp_name'], $file_path); - $this->mfile->add_file($hash, $id, $filename); + service\files::add_file($id, $file["tmp_name"], $filename); $ids[] = $id; } diff --git a/application/helpers/filebin_helper.php b/application/helpers/filebin_helper.php index e5637ce94..465f865f6 100644 --- a/application/helpers/filebin_helper.php +++ b/application/helpers/filebin_helper.php @@ -235,6 +235,22 @@ function send_json_reply($array, $status = "success") $CI->output->set_output(json_encode($reply)); } +function send_json_error_reply($message, $array = null) +{ + $reply = array(); + $reply["status"] = "error"; + $reply["message"] = $message; + + if ($array !== null) { + $reply["data"] = $array; + } + + $CI =& get_instance(); + $CI->output->set_status_header(400); + $CI->output->set_content_type('application/json'); + $CI->output->set_output(json_encode($reply)); +} + function static_storage($key, $value = null) { static $storage = array(); diff --git a/application/service/files.php b/application/service/files.php new file mode 100644 index 000000000..68072e95a --- /dev/null +++ b/application/service/files.php @@ -0,0 +1,116 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace service; + +class files { + + static public function history($user) + { + $CI =& get_instance(); + $query = array(); + + $fields = array("id", "filename", "mimetype", "date", "hash", "filesize"); + + $items = $CI->db->select(implode(',', $fields)) + ->from('files') + ->where('user', $user) + ->get()->result_array(); + + // TODO: split this and provide an array of pastes for a multipaste? + $query = $CI->db->query(" + SELECT m.url_id id, sum(f.filesize) filesize, m.date, '' hash, '' mimetype, concat(count(*), ' file(s)') filename + FROM multipaste m + JOIN multipaste_file_map mfm ON m.multipaste_id = mfm.multipaste_id + JOIN files f ON f.id = mfm.file_url_id + WHERE m.user_id = ? + GROUP BY m.url_id + ", array($user))->result_array(); + + $items = array_merge($items, $query); + + $total_size = $CI->db->query(" + SELECT sum(filesize) sum + FROM ( + SELECT DISTINCT hash, filesize + FROM files + WHERE user = ? + ) sub + ", array($user))->row_array(); + + $ret["items"] = $items; + $ret["total_size"] = $total_size["sum"]; + + return $ret; + } + + static public function add_file($id, $file, $filename) + { + $CI =& get_instance(); + $hash = md5_file($file); + + $dir = $CI->mfile->folder($hash); + file_exists($dir) || mkdir ($dir); + $new_path = $CI->mfile->file($hash); + + // TODO: make this operation atomic (move to temp name, then to final) + // the source can be a different file system so this might do a copy + move_uploaded_file($file, $new_path); + $CI->mfile->add_file($hash, $id, $filename); + } + + static public function verify_uploaded_files($files) + { + $CI =& get_instance(); + $errors = array(); + + foreach ($files as $key => $file) { + $error_message = ""; + + // getNormalizedFILES() removes any file with error == 4 + if ($file['error'] !== UPLOAD_ERR_OK) { + // ERR_OK only for completeness, condition above ignores it + $error_msgs = array( + UPLOAD_ERR_OK => "There is no error, the file uploaded with success", + UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini", + UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", + UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded", + UPLOAD_ERR_NO_FILE => "No file was uploaded", + UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder", + UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk", + UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload", + ); + + $error_message = "Unknown error."; + + if (isset($error_msgs[$file['error']])) { + $error_message = $error_msgs[$file['error']]; + } else { + $error_message = "Unknown error code: ".$file['error'].". Please report a bug."; + } + + } + + $filesize = filesize($file['tmp_name']); + if ($filesize > $CI->config->item('upload_max_size')) { + $error_message = "File too big"; + } + + if ($error_message != "") { + $errors[] = array( + "filename" => $file["name"], + "formfield" => $file["formfield"], + "message" => $error_message, + ); + } + } + + return $errors; + } +} -- cgit v1.2.3-24-g4f1b