diff options
Diffstat (limited to 'application/models')
-rw-r--r-- | application/models/mfile.php | 569 | ||||
-rw-r--r-- | application/models/mmultipaste.php | 160 | ||||
-rw-r--r-- | application/models/muser.php | 288 |
3 files changed, 1017 insertions, 0 deletions
diff --git a/application/models/mfile.php b/application/models/mfile.php new file mode 100644 index 000000000..427c74a18 --- /dev/null +++ b/application/models/mfile.php @@ -0,0 +1,569 @@ +<?php +/* + * Copyright 2009-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Mfile extends CI_Model { + + function __construct() + { + parent::__construct(); + $this->load->model("muser"); + } + + // Returns an unused ID + function new_id($min = 3, $max = 6) + { + static $id_blacklist = NULL; + + if ($id_blacklist == NULL) { + // This prevents people from being unable to access their uploads + // because of URL rewriting + $id_blacklist = scandir(FCPATH); + $id_blacklist[] = "file"; + $id_blacklist[] = "user"; + } + + $max_tries = 100; + + for ($try = 0; $try < $max_tries; $try++) { + $id = random_alphanum($min, $max); + + if ($this->id_exists($id) || in_array($id, $id_blacklist)) { + continue; + } + + return $id; + } + + show_error("Failed to find unused ID after $max_tries tries."); + } + + function id_exists($id) + { + if (!$id) { + return false; + } + + $sql = ' + SELECT id + FROM `files` + WHERE `id` = ? + LIMIT 1'; + $query = $this->db->query($sql, array($id)); + + if ($query->num_rows() == 1) { + return true; + } else { + return false; + } + } + + public function stale_hash($hash) + { + return $this->unused_file($hash); + } + + function get_filedata($id) + { + $sql = ' + SELECT id, hash, filename, mimetype, date, user, filesize + FROM `files` + WHERE `id` = ? + LIMIT 1'; + $query = $this->db->query($sql, array($id)); + + if ($query->num_rows() > 0) { + return $query->row_array(); + } else { + return false; + } + } + + // return the folder in which the file with $hash is stored + function folder($hash) { + return $this->config->item('upload_path').'/'.substr($hash, 0, 3); + } + + // Returns the full path to the file with $hash + function file($hash) { + return $this->folder($hash).'/'.$hash; + } + + // Return mimetype of file + function mimetype($file) { + $fileinfo = new finfo(FILEINFO_MIME_TYPE); + $mimetype = $fileinfo->file($file); + + return $mimetype; + } + + public function image_dimension($file) + { + list($width, $height) = getimagesize($file); + + return "${width}x${height}"; + } + + /* + * This returns a square thumbnail for the input image + * Source: http://salman-w.blogspot.co.at/2009/04/crop-to-fit-image-using-aspphp.html + */ + public function makeThumb($id, $size = 150, $target_type = null) + { + $filedata = $this->get_filedata($id); + if (!$filedata) { + return false; + } + + $source_path = $this->file($filedata["hash"]); + + $source_gdim = imagecreatefromstring(file_get_contents($source_path)); + if ($source_gdim === false) { + show_error("Unsupported image type"); + } + + list($source_width, $source_height, $source_type) = getimagesize($source_path); + + if ($target_type === null) { + $target_type = $source_type; + } + + $target_width = $size; + $target_height = $size; + + $source_aspect_ratio = $source_width / $source_height; + $desired_aspect_ratio = $target_width / $target_height; + + if ($source_aspect_ratio > $desired_aspect_ratio) { + // Triggered when source image is wider + $temp_height = $target_height; + $temp_width = round(($target_height * $source_aspect_ratio)); + } else { + // Triggered otherwise (i.e. source image is similar or taller) + $temp_width = $target_width; + $temp_height = round(($target_width / $source_aspect_ratio)); + } + + /* + * Resize the image into a temporary GD image + */ + + $temp_gdim = imagecreatetruecolor($temp_width, $temp_height); + imagecopyresampled( + $temp_gdim, + $source_gdim, + 0, 0, + 0, 0, + $temp_width, $temp_height, + $source_width, $source_height + ); + + /* + * Copy cropped region from temporary image into the desired GD image + */ + + $x0 = ($temp_width - $target_width) / 2; + $y0 = ($temp_height - $target_height) / 2; + $thumb = imagecreatetruecolor($target_width, $target_height); + imagecopy( + $thumb, + $temp_gdim, + 0, 0, + $x0, $y0, + $target_width, $target_height + ); + + ob_start(); + switch ($target_type) { + case IMAGETYPE_GIF: + $ret = imagegif($thumb); + break; + case IMAGETYPE_JPEG: + $ret = imagejpeg($thumb); + break; + case IMAGETYPE_PNG: + $ret = imagepng($thumb); + break; + default: + assert(0); + } + $result = ob_get_clean(); + + if (!$ret) { + show_error("Failed to create thumbnail"); + } + + imagedestroy($thumb); + imagedestroy($temp_gdim); + imagedestroy($source_gdim); + + return $result; + } + + // Add a hash to the DB + function add_file($hash, $id, $filename) + { + $userid = $this->muser->get_userid(); + + $mimetype = $this->mimetype($this->file($hash)); + + $filesize = filesize($this->file($hash)); + $this->db->insert("files", array( + "id" => $id, + "hash" => $hash, + "filename" => $filename, + "date" => time(), + "user" => $userid, + "mimetype" => $mimetype, + "filesize" => $filesize, + )); + } + + function adopt($id) + { + $userid = $this->muser->get_userid(); + + $this->db->query(" + UPDATE files + SET user = ? + WHERE id = ? + ", array($userid, $id)); + } + + // remove old/invalid/broken IDs + function valid_id($id) + { + $filedata = $this->get_filedata($id); + if (!$filedata) { + return false; + } + $file = $this->file($filedata['hash']); + + if (!file_exists($file)) { + $this->delete_hash($filedata["hash"]); + return false; + } + + // 0 age disables age checks + if ($this->config->item('upload_max_age') == 0) return true; + + // small files don't expire + if (filesize($file) <= $this->config->item("small_upload_size")) { + return true; + } + + // files older than this should be removed + $remove_before = (time()-$this->config->item('upload_max_age')); + + if ($filedata["date"] < $remove_before) { + // if the file has been uploaded multiple times the mtime is the time + // of the last upload + if (filemtime($file) < $remove_before) { + $this->delete_hash($filedata["hash"]); + } else { + $this->delete_id($id); + } + return false; + } + + return true; + } + + public function get_timeout($id) + { + $filedata = $this->get_filedata($id); + $file = $this->file($filedata["hash"]); + + if ($this->config->item("upload_max_age") == 0) { + return -1; + } + + if (filesize($file) > $this->config->item("small_upload_size")) { + return $filedata["date"] + $this->config->item("upload_max_age"); + } else { + return -1; + } + } + + public function get_timeout_string($id) + { + $timeout = $this->get_timeout($id); + + if ($timeout >= 0) { + return date("r", $timeout); + } else { + return "unknown"; + } + } + + private function unused_file($hash) + { + $sql = ' + SELECT id + FROM `files` + WHERE `hash` = ? + LIMIT 1'; + $query = $this->db->query($sql, array($hash)); + + if ($query->num_rows() == 0) { + return true; + } else { + return false; + } + } + + public function delete_id($id) + { + $filedata = $this->get_filedata($id); + + // Delete the file and all multipastes using it + // Note that this does not delete all relations in multipaste_file_map + // which is actually done by a SQL contraint. + // TODO: make it work properly without the constraint + $this->db->query(' + DELETE m, mfm, f + FROM files f + LEFT JOIN multipaste_file_map mfm ON f.id = mfm.file_url_id + LEFT JOIN multipaste m ON mfm.multipaste_id = m.multipaste_id + WHERE f.id = ? + ', array($id)); + + if ($this->id_exists($id)) { + return false; + } + + if ($filedata !== false) { + assert(isset($filedata["hash"])); + if ($this->unused_file($filedata['hash'])) { + unlink($this->file($filedata['hash'])); + $dir = $this->folder($filedata['hash']); + if (count(scandir($dir)) == 2) { + rmdir($dir); + } + } + } + return true; + } + + public function delete_hash($hash) + { + // Delete all files with this hash and all multipastes using any of those files + // Note that this does not delete all relations in multipaste_file_map + // which is actually done by a SQL contraint. + // TODO: make it work properly without the constraint + $this->db->query(' + DELETE m, mfm, f + FROM files f + LEFT JOIN multipaste_file_map mfm ON f.id = mfm.file_url_id + LEFT JOIN multipaste m ON mfm.multipaste_id = m.multipaste_id + WHERE f.hash = ? + ', array($hash)); + + if (file_exists($this->file($hash))) { + unlink($this->file($hash)); + $dir = $this->folder($hash); + if (count(scandir($dir)) == 2) { + rmdir($dir); + } + } + return true; + } + + public function get_owner($id) + { + return $this->db->query(" + SELECT user + FROM files + WHERE id = ? + ", array($id))->row_array()["user"]; + } + + public function get_lexers() { + return cache_function('lexers', 1800, function() { + $lexers = array(); + $last_desc = ""; + exec("python ".escapeshellarg(FCPATH."scripts/get_lexer_list.py"), $output); + + foreach ($output as $line) { + list($name, $desc) = explode("|", $line); + if ($desc == $last_desc) { + continue; + } + $last_desc = $desc; + $lexers[$name] = $desc; + } + $lexers["text"] = "Plain text"; + return $lexers; + }); + } + + public function should_highlight($type) + { + if ($this->mime2lexer($type)) return true; + + return false; + } + + // Allow certain types to be highlight without doing it automatically + public function can_highlight($type) + { + $typearray = array( + 'image/svg+xml', + ); + if (in_array($type, $typearray)) return true; + + if ($this->mime2lexer($type)) return true; + + return false; + } + + // Return the lexer that should be used for highlighting + public function autodetect_lexer($type, $filename) + { + if (!$this->can_highlight($type)) { + return false; + } + + $lexer = $this->mime2lexer($type); + + // filename lexers overwrite mime type mappings + $filename_lexer = $this->filename2lexer($filename); + if ($filename_lexer) { + return $filename_lexer; + } + + return $lexer; + } + + // Map MIME types to lexers needed for highlighting + private function mime2lexer($type) + { + $typearray = array( + 'application/javascript' => 'javascript', + 'application/mbox' => 'text', + 'application/smil' => 'ocaml', + 'application/x-applix-spreadsheet' => 'actionscript', + 'application/x-awk' => 'awk', + 'application/x-desktop' => 'text', + 'application/x-fluid' => 'text', + 'application/x-genesis-rom' => 'text', + 'application/x-java' => 'java', + 'application/x-m4' => 'text', + 'application/xml-dtd' => "xml", + 'application/xml' => 'xml', + 'application/x-perl' => 'perl', + 'application/x-php' => 'php', + 'application/x-ruby' => 'ruby', + 'application/x-shellscript' => 'bash', + 'application/xslt+xml' => "xml", + 'application/x-x509-ca-cert' => 'text', + 'message/rfc822' => 'text', + 'text/css' => 'css', + 'text/html' => 'xml', + 'text/plain-ascii' => 'ascii', + 'text/plain' => 'text', + 'text/troff' => 'groff', + 'text/x-asm' => 'nasm', + 'text/x-awk' => 'awk', + 'text/x-c' => 'c', + 'text/x-c++' => 'cpp', + 'text/x-c++hdr' => 'c', + 'text/x-chdr' => 'c', + 'text/x-csrc' => 'c', + 'text/x-c++src' => 'cpp', + 'text/x-diff' => 'diff', + 'text/x-gawk' => 'awk', + 'text/x-haskell' => 'haskell', + 'text/x-java' => 'java', + 'text/x-lisp' => 'cl', + 'text/x-literate-haskell' => 'haskell', + 'text/x-lua' => 'lua', + 'text/x-makefile' => 'make', + 'text/x-ocaml' => 'ocaml', + 'text/x-patch' => 'diff', + 'text/x-perl' => 'perl', + 'text/x-php' => 'php', + 'text/x-python' => 'python', + 'text/x-ruby' => 'ruby', + 'text/x-scheme' => 'scheme', + 'text/x-shellscript' => 'bash', + 'text/x-subviewer' => 'bash', + 'text/x-tcl' => 'tcl', + 'text/x-tex' => 'tex', + ); + if (array_key_exists($type, $typearray)) return $typearray[$type]; + + if (strpos($type, 'text/') === 0) return 'text'; + + # default + return false; + } + + // Map special filenames to lexers + private function filename2lexer($name) + { + $namearray = array( + 'PKGBUILD' => 'bash', + '.vimrc' => 'vim' + ); + if (array_key_exists($name, $namearray)) return $namearray[$name]; + + + if (strpos($name, ".") !== false) { + $extension = substr($name, strrpos($name, ".") + 1); + + $extensionarray = array( + 'awk' => 'awk', + 'c' => 'c', + 'coffee' => 'coffee-script', + 'cpp' => 'cpp', + 'diff' => 'diff', + 'h' => 'c', + 'hs' => 'haskell', + 'html' => 'xml', + 'java' => 'java', + 'js' => 'js', + 'lua' => 'lua', + 'mli' => 'ocaml', + 'mll' => 'ocaml', + 'ml' => 'ocaml', + 'mly' => 'ocaml', + 'patch' => 'diff', + 'php' => 'php', + 'pl' => 'perl', + 'py' => 'python', + 'rb' => 'ruby', + 's' => 'asm', + 'sh' => 'bash', + 'tcl' => 'tcl', + 'tex' => 'tex', + ); + if (array_key_exists($extension, $extensionarray)) return $extensionarray[$extension]; + } + + return false; + } + + // Handle lexer aliases + public function resolve_lexer_alias($alias) + { + if ($alias === false) return false; + $aliasarray = array( + 'py' => 'python', + 'sh' => 'bash', + 's' => 'asm', + 'pl' => 'perl' + ); + if (array_key_exists($alias, $aliasarray)) return $aliasarray[$alias]; + + return $alias; + } + +} + +# vim: set noet: diff --git a/application/models/mmultipaste.php b/application/models/mmultipaste.php new file mode 100644 index 000000000..723132a50 --- /dev/null +++ b/application/models/mmultipaste.php @@ -0,0 +1,160 @@ +<?php +/* + * Copyright 2014 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Mmultipaste extends CI_Model { + + function __construct() + { + parent::__construct(); + $this->load->model("muser"); + $this->load->model("mfile"); + } + + /** + * Returns an unused ID + * + * @param min minimal length of the resulting ID + * @param max maximum length of the resulting ID + */ + public function new_id($min = 3, $max = 6) + { + static $id_blacklist = NULL; + + if ($id_blacklist == NULL) { + // This prevents people from being unable to access their uploads + // because of URL rewriting + $id_blacklist = scandir(FCPATH); + $id_blacklist[] = "file"; + $id_blacklist[] = "user"; + } + + $max_tries = 100; + + for ($try = 0; $try < $max_tries; $try++) { + $id = "m-".random_alphanum($min, $max); + + // TODO: try to insert the id into file_groups instead of checking with + // id_exists (prevents race conditio) + if ($this->id_exists($id) || in_array($id, $id_blacklist)) { + continue; + } + + $this->db->insert("multipaste", array( + "url_id" => $id, + "user_id" => $this->muser->get_userid(), + "date" => time(), + )); + + return $id; + } + + show_error("Failed to find unused ID after $max_tries tries."); + } + + public function id_exists($id) + { + if (!$id) { + return false; + } + + $sql = ' + SELECT multipaste.url_id + FROM multipaste + WHERE multipaste.url_id = ? + LIMIT 1'; + $query = $this->db->query($sql, array($id)); + + if ($query->num_rows() == 1) { + return true; + } else { + return false; + } + } + + public function valid_id($id) + { + $files = $this->get_files($id); + foreach ($files as $file) { + if (!$this->mfile->valid_id($file["id"])) { + return false; + } + } + return true; + } + + public function delete_id($id) + { + $this->db->query(' + DELETE m, mfm + FROM multipaste m + LEFT JOIN multipaste_file_map mfm ON mfm.multipaste_id = m.multipaste_id + WHERE m.url_id = ? + ', array($id)); + + if ($this->id_exists($id)) { + return false; + } + + return true; + } + + public function get_owner($id) + { + return $this->db->query(" + SELECT user_id + FROM multipaste + WHERE url_id = ? + ", array($id))->row_array()["user_id"]; + } + + public function get_multipaste($id) + { + return $this->db->query(" + SELECT url_id, user_id, date + FROM multipaste + WHERE url_id = ? + ", array($id))->row_array(); + } + + public function get_files($url_id) + { + $ret = array(); + + $query = $this->db->query(" + SELECT mfm.file_url_id + FROM multipaste_file_map mfm + JOIN multipaste m ON m.multipaste_id = mfm.multipaste_id + WHERE m.url_id = ? + ORDER BY mfm.sort_order + ", array($url_id))->result_array(); + + foreach ($query as $row) { + $filedata = $this->mfile->get_filedata($row["file_url_id"]); + $ret[] = $filedata; + } + + return $ret; + } + + public function get_multipaste_id($url_id) + { + $query = $this->db->query(" + SELECT multipaste_id + FROM multipaste + WHERE url_id = ? + ", array($url_id)); + + if ($query->num_rows() > 0) { + return $query->row_array()["multipaste_id"]; + } + + return false; + } + +} diff --git a/application/models/muser.php b/application/models/muser.php new file mode 100644 index 000000000..a1d8f18e5 --- /dev/null +++ b/application/models/muser.php @@ -0,0 +1,288 @@ +<?php +/* + * Copyright 2012-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Muser extends CI_Model { + + private $default_upload_id_limits = "3-6"; + + // last level has the most access + private $access_levels = array("basic", "apikey", "full"); + + function __construct() + { + parent::__construct(); + + if ($this->has_session() && !$this->logged_in()) { + $this->session->keep_flashdata("uri"); + } + + $this->load->helper("filebin"); + $this->load->driver("duser"); + } + + function has_session() + { + // checking $this doesn't work + $CI =& get_instance(); + if (property_exists($CI, "session")) { + return true; + } + + // Only load the session class if we already have a cookie that might need to be renewed. + // Otherwise we just create lots of stale sessions. + if (isset($_COOKIE[$this->config->item("sess_cookie_name")])) { + $this->load->library("session"); + return true; + } + + return false; + } + + function require_session() + { + if (!$this->has_session()) { + $this->load->library("session"); + } + } + + function logged_in() + { + if ($this->has_session()) { + return $this->session->userdata('logged_in') == true; + } + + return false; + } + + function login($username, $password) + { + $this->require_session(); + return $this->duser->login($username, $password); + } + + private function login_cli_client() + { + $username = $this->input->post("username"); + $password = $this->input->post("password"); + + // prefer post parameters if either (username or password) is set + if ($username === false && $password === false) { + if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { + $username = $_SERVER['PHP_AUTH_USER']; + $password = $_SERVER['PHP_AUTH_PW']; + } + } + + if ($username !== false && $password !== false) { + if ($this->login($username, $password)) { + return true; + } else { + show_error("Login failed", 401); + } + } + + return null; + } + + function apilogin($apikey) + { + $this->require_session(); + + // get rid of spaces and newlines + $apikey = trim($apikey); + + $query = $this->db->query(" + SELECT a.user userid, a.access_level + FROM apikeys a + WHERE a.key = ? + ", array($apikey))->row_array(); + + if (isset($query["userid"])) { + $this->session->set_userdata(array( + 'logged_in' => true, + 'username' => '', + 'userid' => $query["userid"], + 'access_level' => $query["access_level"], + )); + return true; + } + + show_error("API key login failed", 401); + } + + function logout() + { + $this->require_session(); + $this->session->unset_userdata('logged_in'); + $this->session->unset_userdata('username'); + $this->session->unset_userdata('userid'); + $this->session->sess_destroy(); + } + + function get_username() + { + if (!$this->logged_in()) { + return ""; + } + + return $this->session->userdata('username'); + } + + function get_userid() + { + if (!$this->logged_in()) { + return 0; + } + + return $this->session->userdata("userid"); + } + + function get_email($userid) + { + return $this->duser->get_email($userid); + } + + public function get_access_levels() + { + return $this->access_levels; + } + + private function check_access_level($wanted_level) + { + $session_level = $this->session->userdata("access_level"); + + $wanted = array_search($wanted_level, $this->access_levels); + $have = array_search($session_level, $this->access_levels); + + if ($wanted === false || $have === false) { + show_error("Failed to determine access level"); + } + + if ($have >= $wanted) { + return true; + } + + show_error("Access denied: Access level too low", 403); + } + + function require_access($wanted_level = "full") + { + if ($this->input->post("apikey") !== false) { + $this->apilogin($this->input->post("apikey")); + } + + if (is_cli_client()) { + $this->login_cli_client(); + } + + if ($this->logged_in()) { + return $this->check_access_level($wanted_level); + } + + if (!stateful_client()) { + show_error("Not authenticated. FileBin requires you to have an account, please go to the homepage for more information.\n", 401); + } + + // desktop clients get redirected to the login form + $this->require_session(); + if (!$this->session->userdata("flash:new:uri")) { + $this->session->set_flashdata("uri", $this->uri->uri_string()); + } + redirect('user/login'); + exit(); + } + + function username_exists($username) + { + return $this->duser->username_exists($username); + } + + function get_action($action, $key) + { + $query = $this->db->query(" + SELECT * + FROM actions + WHERE `key` = ? + AND `action` = ? + ", array($key, $action))->row_array(); + + if (!isset($query["key"]) || $key != $query["key"]) { + show_error("Invalid action key"); + } + + return $query; + } + + public function get_profile_data() + { + $userid = $this->get_userid(); + + $fields = array( + "user" => $userid, + "upload_id_limits" => $this->default_upload_id_limits, + ); + + $query = $this->db->query(" + SELECT ".implode(", ", array_keys($fields))." + FROM `profiles` + WHERE user = ? + ", array($userid))->row_array(); + + $extra_fields = array( + "username" => $this->get_username(), + "email" => $this->get_email($userid), + ); + + return array_merge($fields, $query, $extra_fields); + } + + public function update_profile($data) + { + assert(is_array($data)); + + $data["user"] = $this->get_userid(); + + $exists_in_db = $this->db->get_where("profiles", array("user" => $data["user"]))->num_rows() > 0; + + if ($exists_in_db) { + $this->db->where("user", $data["user"]); + $this->db->update("profiles", $data); + } else { + $this->db->insert("profiles", $data); + } + } + + public function get_upload_id_limits() + { + $userid = $this->get_userid(); + + $query = $this->db->query(" + SELECT upload_id_limits + FROM `profiles` + WHERE user = ? + ", array($userid))->row_array(); + + if (empty($query)) { + return explode("-", $this->default_upload_id_limits); + } + + return explode("-", $query["upload_id_limits"]); + } + + function hash_password($password) + { + + require_once APPPATH."third_party/PasswordHash.php"; + + $hasher = new PasswordHash(9, false); + return $hasher->HashPassword($password); + } + +} + |