summaryrefslogtreecommitdiffstats
path: root/application/models
diff options
context:
space:
mode:
Diffstat (limited to 'application/models')
-rw-r--r--application/models/mfile.php515
-rw-r--r--application/models/muser.php288
2 files changed, 803 insertions, 0 deletions
diff --git a/application/models/mfile.php b/application/models/mfile.php
new file mode 100644
index 000000000..c900b297c
--- /dev/null
+++ b/application/models/mfile.php
@@ -0,0 +1,515 @@
+<?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 hash, filename, mimetype, date, user, filesize
+ FROM `files`
+ WHERE `id` = ?
+ LIMIT 1';
+ $query = $this->db->query($sql, array($id));
+
+ if ($query->num_rows() == 1) {
+ $return = $query->result_array();
+ return $return[0];
+ } 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));
+ $query = $this->db->query('
+ INSERT INTO `files` (`hash`, `id`, `filename`, `user`, `date`, `mimetype`, `filesize`)
+ VALUES (?, ?, ?, ?, ?, ?, ?)',
+ array($hash, $id, $filename, $userid, time(), $mimetype, $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)) {
+ if (isset($filedata["hash"])) {
+ $this->db->query('DELETE FROM files WHERE hash = ?', array($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) {
+ unlink($file);
+ $this->db->query('DELETE FROM files WHERE hash = ?', array($filedata['hash']));
+ } else {
+ $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($id));
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ function get_timeout_string($id)
+ {
+ $filedata = $this->get_filedata($id);
+ $file = $this->file($filedata["hash"]);
+
+ if (filesize($file) > $this->config->item("small_upload_size")) {
+ return date("r", $filedata["date"] + $this->config->item("upload_max_age"));
+ } 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;
+ }
+ }
+
+ function delete_id($id)
+ {
+ $filedata = $this->get_filedata($id);
+ $userid = $this->muser->get_userid();
+
+ if (!$this->id_exists($id)) {
+ return false;
+ }
+
+ $sql = '
+ DELETE
+ FROM `files`
+ WHERE `id` = ?
+ AND user = ?
+ LIMIT 1';
+ $this->db->query($sql, array($id, $userid));
+
+ if ($this->id_exists($id)) {
+ return false;
+ }
+
+ if ($this->unused_file($filedata['hash'])) {
+ unlink($this->file($filedata['hash']));
+ @rmdir($this->folder($filedata['hash']));
+ }
+ return true;
+ }
+
+ 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/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);
+ }
+
+}
+