summaryrefslogtreecommitdiffstats
path: root/application/controllers/file.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/controllers/file.php')
-rw-r--r--application/controllers/file.php704
1 files changed, 704 insertions, 0 deletions
diff --git a/application/controllers/file.php b/application/controllers/file.php
new file mode 100644
index 000000000..9616c0fc6
--- /dev/null
+++ b/application/controllers/file.php
@@ -0,0 +1,704 @@
+<?php
+/*
+ * Copyright 2009-2011 Florian "Bluewind" Pritz <bluewind@server-speed.net>
+ *
+ * Licensed under GPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class File extends CI_Controller {
+
+ public $data = array();
+ public $var;
+
+ function __construct()
+ {
+ parent::__construct();
+
+ $this->var = new StdClass();
+
+ $this->load->library('migration');
+ if ( ! $this->migration->current()) {
+ show_error($this->migration->error_string());
+ }
+
+ $old_path = getenv("PATH");
+ putenv("PATH=$old_path:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin");
+
+ mb_internal_encoding('UTF-8');
+ $this->load->helper(array('form', 'filebin'));
+ $this->load->model('mfile');
+ $this->load->model('muser');
+
+ $this->var->latest_client = false;
+ if (file_exists(FCPATH.'data/client/latest')) {
+ $this->var->latest_client = trim(file_get_contents(FCPATH.'data/client/latest'));
+ }
+
+
+ if (is_cli_client()) {
+ $this->var->view_dir = "file_plaintext";
+ } else {
+ $this->var->view_dir = "file";
+ }
+
+ if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_USER'] && $_SERVER['PHP_AUTH_PW']) {
+ if (!$this->muser->login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
+ // TODO: better message
+ echo "login failed.\n";
+ exit;
+ }
+ }
+
+ $this->data['username'] = $this->muser->get_username();
+ $this->data['title'] = "FileBin";
+ }
+
+ function index()
+ {
+ if ($this->input->is_cli_request()) {
+ echo "php index.php file <function> [arguments]\n";
+ echo "\n";
+ echo "Functions:\n";
+ echo " cron Cronjob\n";
+ echo " nuke_id <ID> Nukes all IDs sharing the same hash\n";
+ echo "\n";
+ echo "Functions that shouldn't have to be run:\n";
+ echo " clean_stale_files Remove files without database entries\n";
+ echo " update_file_sizes Update filesize in database\n";
+ exit;
+ }
+ // Try to guess what the user would like to do.
+ $id = $this->uri->segment(1);
+ if(isset($_FILES['file'])) {
+ $this->do_upload();
+ } elseif ($id != "file" && $this->mfile->id_exists($id)) {
+ $this->_download();
+ } elseif ($id && $id != "file") {
+ $this->_non_existent();
+ } else {
+ $this->upload_form();
+ }
+ }
+
+ function _download()
+ {
+ $id = $this->uri->segment(1);
+ $mode = urldecode($this->uri->segment(2));
+
+ $filedata = $this->mfile->get_filedata($id);
+ $file = $this->mfile->file($filedata['hash']);
+
+ if (!$this->mfile->valid_id($id)) {
+ $this->_non_existent();
+ return;
+ }
+
+ // don't allow unowned files to be downloaded
+ if ($filedata["user"] == 0) {
+ $this->_non_existent();
+ return;
+ }
+
+ // helps to keep traffic low when reloading
+ $etag = $filedata["hash"]."-".$filedata["date"];
+
+ // autodetect the mode for highlighting if the URL contains a / after the ID (/ID/)
+ // /ID/mode disables autodetection
+ $autodetect_mode = !$mode && substr_count(ltrim($this->uri->uri_string(), "/"), '/') >= 1;
+
+ if ($autodetect_mode) {
+ $mode = $this->mfile->get_highlight_mode($filedata["mimetype"], $filedata["filename"]);
+ }
+
+ // resolve aliases of modes
+ // this is mainly used for compatibility
+ $mode = $this->mfile->resolve_mode_alias($mode);
+
+ // create the qr code for /ID/
+ if ($mode == "qr") {
+ handle_etag($etag);
+ header("Content-disposition: inline; filename=\"".$id."_qr.png\"\n");
+ header("Content-Type: image/png\n");
+ passthru('qrencode -s 10 -o - '.escapeshellarg(site_url($id).'/'));
+ exit();
+ }
+
+ // user wants to the the plain file
+ if ($mode == 'plain') {
+ handle_etag($etag);
+ rangeDownload($file, $filedata["filename"], "text/plain");
+ exit();
+ }
+
+ if ($mode == 'info') {
+ $this->_display_info($id);
+ return;
+ }
+
+ // if there is no mimetype mapping we can't highlight it
+ $can_highlight = $this->mfile->can_highlight($filedata["mimetype"]);
+
+ $filesize_too_big = filesize($file) > $this->config->item('upload_max_text_size');
+
+ if (!$can_highlight || $filesize_too_big || !$mode) {
+ // prevent javascript from being executed and forbid frames
+ // this should allow us to serve user submitted HTML content without huge security risks
+ foreach (array("X-WebKit-CSP", "X-Content-Security-Policy") as $header_name) {
+ header("$header_name: allow 'none'; img-src *; media-src *; font-src *; style-src * 'unsafe-inline'; script-src 'none'; object-src *; frame-src 'none'; ");
+ }
+ handle_etag($etag);
+ rangeDownload($file, $filedata["filename"], $filedata["mimetype"]);
+ exit();
+ }
+
+ $this->data['title'] = htmlspecialchars($filedata['filename']);
+ $this->data['id'] = $id;
+
+ header("Content-Type: text/html\n");
+
+ $this->data['current_highlight'] = htmlspecialchars($mode);
+ $this->data['timeout'] = $this->mfile->get_timeout_string($id);
+
+ $this->load->view($this->var->view_dir.'/html_header', $this->data);
+
+ // highlight the file and chache the result
+ $this->load->library("MemcacheLibrary");
+ if (! $cached = $this->memcachelibrary->get($filedata['hash'].'_'.$mode)) {
+ ob_start();
+ if ($mode == "rmd") {
+ echo '<td class="markdownrender">'."\n";
+ passthru('perl '.FCPATH.'scripts/Markdown.pl '.escapeshellarg($file), $return_value);
+ } elseif ($mode == "ascii") {
+ echo '<td class="code"><pre class="text">'."\n";
+ passthru('perl '.FCPATH.'scripts/ansi2html '.escapeshellarg($file), $return_value);
+ echo "</pre>\n";
+ } else {
+ echo '<td class="numbers"><pre>';
+ // generate line numbers (links)
+ passthru('perl -ne \'print "<a href=\"#n$.\" id=\"n$.\">$.</a>\n"\' '.escapeshellarg($file), $return_value);
+ echo '</pre></td><td class="code">'."\n";
+ passthru('pygmentize -F codetagify -O encoding=guess,outencoding=utf8 -l '.escapeshellarg($mode).' -f html '.escapeshellarg($file), $return_value);
+ }
+ $cached = ob_get_contents();
+ ob_end_clean();
+ $this->memcachelibrary->set($filedata['hash'].'_'.$mode, $cached, 100);
+ }
+
+ if ($return_value != 0) {
+ show_error("Error trying to process the file. Either the lexer is unknown or something is broken.\n");
+ } else {
+ $this->output->append_output($cached);
+ }
+
+ $this->load->view($this->var->view_dir.'/html_footer', $this->data);
+ }
+
+ function _display_info($id)
+ {
+ $this->data["title"] .= " - Info $id";
+ $this->data["filedata"] = $this->mfile->get_filedata($id);
+ $this->data["id"] = $id;
+ $this->data['timeout'] = $this->mfile->get_timeout_string($id);
+
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/file_info', $this->data);
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+
+ function _non_existent()
+ {
+ $this->data["title"] .= " - Not Found";
+ $this->output->set_status_header(404);
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/non_existent', $this->data);
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+
+ function _show_url($id, $mode)
+ {
+ $redirect = false;
+
+ if (!$this->muser->logged_in()) {
+ $this->muser->require_session();
+ // keep the upload but require the user to login
+ $this->session->set_userdata("last_upload", array(
+ "id" => $id,
+ "mode" => $mode
+ ));
+ $this->session->set_flashdata("uri", "file/claim_id");
+ $this->muser->require_access();
+ }
+
+ if ($mode) {
+ $this->data['url'] = site_url($id).'/'.$mode;
+ } else {
+ $this->data['url'] = site_url($id).'/';
+
+ $filedata = $this->mfile->get_filedata($id);
+ $file = $this->mfile->file($filedata['hash']);
+ $type = $filedata['mimetype'];
+ $mode = $this->mfile->should_highlight($type);
+
+ // If we detected a highlightable file redirect,
+ // otherwise show the URL because browsers would just show a DL dialog
+ if ($mode) {
+ $redirect = true;
+ }
+ }
+
+ if (is_cli_client()) {
+ $redirect = false;
+ }
+ if ($redirect) {
+ redirect($this->data['url'], "location", 303);
+ } else {
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/show_url', $this->data);
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+ }
+
+ function client()
+ {
+ $this->data['title'] .= ' - Client';
+ if ($this->var->latest_client) {
+ $this->data['client_link'] = base_url().'data/client/fb-'.$this->var->latest_client.'.tar.gz';
+ } else {
+ $this->data['client_link'] = false;
+ }
+ $this->data['client_link_dir'] = base_url().'data/client/';
+ $this->data['client_link_deb'] = base_url().'data/client/deb/';
+ $this->data['client_link_slackware'] = base_url().'data/client/slackware/';
+
+ if (!is_cli_client()) {
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ }
+ $this->load->view($this->var->view_dir.'/client', $this->data);
+ if (!is_cli_client()) {
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+ }
+
+ function upload_form()
+ {
+ $this->data['title'] .= ' - Upload';
+ $this->data['small_upload_size'] = $this->config->item('small_upload_size');
+ $this->data['max_upload_size'] = $this->config->item('upload_max_size');
+ $this->data['upload_max_age'] = $this->config->item('upload_max_age')/60/60/24;
+ $this->data['contact_me_url'] = $this->config->item('contact_me_url');
+
+ $this->data['username'] = $this->muser->get_username();
+
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/upload_form', $this->data);
+ if (is_cli_client()) {
+ $this->client();
+ }
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+
+ // Allow CLI clients to query the server for the maxium filesize so they can
+ // stop the upload before wasting time and bandwith
+ function get_max_size()
+ {
+ echo $this->config->item('upload_max_size');
+ }
+
+ function upload_history()
+ {
+ $this->muser->require_access();
+
+ $user = $this->muser->get_userid();
+
+ $this->load->library("MemcacheLibrary");
+ if (! $cached = $this->memcachelibrary->get("history_".$this->var->view_dir."_".$user)) {
+ $query = array();
+ $lengths = array();
+
+ // key: database field name; value: display name
+ $fields = array(
+ "id" => "ID",
+ "filename" => "Filename",
+ "mimetype" => "Mimetype",
+ "date" => "Date",
+ "hash" => "Hash",
+ "filesize" => "Size"
+ );
+
+ $this->data['title'] .= ' - Upload history';
+ foreach($fields as $length_key => $value) {
+ $lengths[$length_key] = mb_strlen($value);
+ }
+
+ $query = $this->db->query("
+ SELECT ".implode(",", array_keys($fields))."
+ FROM files
+ WHERE user = ?
+ ORDER BY date
+ ", array($user))->result_array();
+
+ foreach($query as $key => $item) {
+ $query[$key]["date"] = date("r", $item["date"]);
+ $query[$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) {
+ $len = mb_strlen($query[$key][$length_key]);
+ if ($len > $lengths[$length_key]) {
+ $lengths[$length_key] = $len;
+ }
+ }
+ }
+ }
+
+ $total_size = $this->db->query("
+ SELECT sum(filesize) sum
+ FROM (
+ SELECT filesize
+ FROM files
+ WHERE user = ?
+ GROUP BY hash
+ ) sub
+ ", array($user))->row_array();
+
+ $this->data["query"] = $query;
+ $this->data["lengths"] = $lengths;
+ $this->data["fields"] = $fields;
+ $this->data["total_size"] = format_bytes($total_size["sum"]);
+
+ $cached = "";
+ $cached .= $this->load->view($this->var->view_dir.'/header', $this->data, true);
+ $cached .= $this->load->view($this->var->view_dir.'/upload_history', $this->data, true);
+ $cached .= $this->load->view($this->var->view_dir.'/footer', $this->data, true);
+
+ // disable for now. reenable if it causes too much load
+ //$this->memcachelibrary->set('history_'.$this->var->view_dir."_".$user, $cached, 42);
+ }
+
+ echo $cached;
+ }
+
+ function do_delete()
+ {
+ $this->muser->require_access();
+
+ $ids = $this->input->post("ids");
+ $errors = array();
+ $msgs = array();
+ $deleted_count = 0;
+ $total_count = 0;
+
+ if (!$ids) {
+ show_error("No IDs specified");
+ }
+
+ foreach ($ids as $id) {
+ $total_count++;
+
+ if (!$this->mfile->id_exists($id)) {
+ $errors[] = "'$id' didn't exist anymore.";
+ continue;
+ }
+
+ if ($this->mfile->delete_id($id)) {
+ $msgs[] = "'$id' has been removed.";
+ $deleted_count++;
+ } else {
+ $errors[] = "'$id' couldn't be deleted.";
+ }
+ }
+
+ $this->data["errors"] = $errors;
+ $this->data["msgs"] = $msgs;
+ $this->data["deleted_count"] = $deleted_count;
+ $this->data["total_count"] = $total_count;
+
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/deleted', $this->data);
+ $this->load->view($this->var->view_dir.'/footer', $this->data);
+ }
+
+ function delete()
+ {
+ $this->muser->require_access();
+
+ if (!is_cli_client()) {
+ echo "Not a listed cli client, please use the history to delete uploads.\n";
+ return;
+ }
+
+ $id = $this->uri->segment(3);
+ $this->data["id"] = $id;
+
+ if ($id && !$this->mfile->id_exists($id)) {
+ $this->output->set_status_header(404);
+ echo "Unknown ID '$id'.\n";
+ return;
+ }
+
+ if ($this->mfile->delete_id($id)) {
+ echo "$id has been deleted.\n";
+ } else {
+ echo "Deletion failed. Do you really own that file?\n";
+ }
+ }
+
+ // Handle pastes
+ function do_paste()
+ {
+ $content = $this->input->post("content");
+ $filesize = strlen($content);
+ $filename = "stdin";
+
+ if(!$content) {
+ $this->output->set_status_header(400);
+ $this->data["msg"] = "Nothing was pasted, content is empty.";
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/upload_error', $this->data);
+ $this->load->view($this->var->view_dir.'/footer');
+ return;
+ }
+
+ if ($filesize > $this->config->item('upload_max_size')) {
+ $this->output->set_status_header(413);
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/too_big');
+ $this->load->view($this->var->view_dir.'/footer');
+ return;
+ }
+
+ $id = $this->mfile->new_id();
+ $hash = md5($content);
+
+ $folder = $this->mfile->folder($hash);
+ file_exists($folder) || mkdir ($folder);
+ $file = $this->mfile->file($hash);
+
+ file_put_contents($file, $content);
+ chmod($file, 0600);
+ $this->mfile->add_file($hash, $id, $filename);
+ $this->_show_url($id, false);
+ }
+
+ // Handles uploaded files
+ function do_upload()
+ {
+ $extension = $this->input->post('extension');
+ if(!isset($_FILES['file']) || $_FILES['file']['error'] !== 0) {
+ $this->output->set_status_header(400);
+ $errors = array(
+ 0=>"There is no error, the file uploaded with success",
+ 1=>"The uploaded file exceeds the upload_max_filesize directive in php.ini",
+ 2=>"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
+ 3=>"The uploaded file was only partially uploaded",
+ 4=>"No file was uploaded",
+ 6=>"Missing a temporary folder"
+ );
+
+ $this->data["msg"] = "Unknown error.";
+
+ if (isset($_FILES["file"])) {
+ $this->data["msg"] = $errors[$_FILES['file']['error']];
+ }
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/upload_error', $this->data);
+ $this->load->view($this->var->view_dir.'/footer');
+ return;
+ }
+
+ $filesize = filesize($_FILES['file']['tmp_name']);
+ if ($filesize > $this->config->item('upload_max_size')) {
+ $this->output->set_status_header(413);
+ $this->load->view($this->var->view_dir.'/header', $this->data);
+ $this->load->view($this->var->view_dir.'/too_big');
+ $this->load->view($this->var->view_dir.'/footer');
+ return;
+ }
+
+ $id = $this->mfile->new_id();
+ $hash = md5_file($_FILES['file']['tmp_name']);
+
+ // work around a curl bug and allow the client to send the real filename base64 encoded
+ $filename = $this->input->post("filename");
+ if ($filename !== false) {
+ $filename = trim(base64_decode($filename, true), "\r\n\0\t\x0B");
+ }
+
+ // fall back if base64_decode failed
+ if ($filename === false) {
+ $filename = $_FILES['file']['name'];
+ }
+
+ $folder = $this->mfile->folder($hash);
+ file_exists($folder) || mkdir ($folder);
+ $file = $this->mfile->file($hash);
+
+ move_uploaded_file($_FILES['file']['tmp_name'], $file);
+ chmod($file, 0600);
+ $this->mfile->add_file($hash, $id, $filename);
+ $this->_show_url($id, $extension);
+ }
+
+ function claim_id()
+ {
+ $this->muser->require_access();
+
+ $last_upload = $this->session->userdata("last_upload");
+ $id = $last_upload["id"];
+
+ $filedata = $this->mfile->get_filedata($id);
+
+ if ($filedata["user"] != 0) {
+ show_error("Someone already owns '$id', can't reassign.");
+ }
+
+ $this->mfile->adopt($id);
+
+ $this->session->unset_userdata("last_upload");
+
+ $this->_show_url($id, $last_upload["mode"]);
+ }
+
+ /* Functions below this comment can only be run via the CLI
+ * `php index.php file <function name>`
+ */
+
+ // Removes old files
+ function cron()
+ {
+ if (!$this->input->is_cli_request()) return;
+
+ if ($this->config->item('upload_max_age') == 0) return;
+
+ $oldest_time = (time() - $this->config->item('upload_max_age'));
+ $oldest_session_time = (time() - $this->config->item("sess_expiration"));
+
+ $small_upload_size = $this->config->item('small_upload_size');
+
+ $query = $this->db->query('
+ SELECT hash, id, user
+ FROM files
+ WHERE date < ? OR (user = 0 AND date < ?)',
+ array($oldest_time, $oldest_session_time));
+
+ foreach($query->result_array() as $row) {
+ $file = $this->mfile->file($row['hash']);
+ if (!file_exists($file)) {
+ $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($row['id']));
+ continue;
+ }
+
+ if ($row["user"] == 0 || filesize($file) > $small_upload_size) {
+ if (filemtime($file) < $oldest_time) {
+ unlink($file);
+ $this->db->query('DELETE FROM files WHERE hash = ?', array($row['hash']));
+ } else {
+ $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($row['id']));
+ if ($this->mfile->stale_hash($row["hash"])) {
+ unlink($file);
+ }
+ }
+ }
+ }
+ }
+
+ /* remove files without database entries */
+ function clean_stale_files()
+ {
+ if (!$this->input->is_cli_request()) return;
+
+ $upload_path = $this->config->item("upload_path");
+ $outer_dh = opendir($upload_path);
+
+ while (($dir = readdir($outer_dh)) !== false) {
+ if (!is_dir($upload_path."/".$dir) || $dir == ".." || $dir == ".") {
+ continue;
+ }
+
+ $dh = opendir($upload_path."/".$dir);
+
+ $empty = true;
+
+ while (($file = readdir($dh)) !== false) {
+ if ($file == ".." || $file == ".") {
+ continue;
+ }
+
+ $query = $this->db->query("SELECT hash FROM files WHERE hash = ? LIMIT 1", array($file))->row_array();
+
+ if (empty($query)) {
+ unlink($upload_path."/".$dir."/".$file);
+ } else {
+ $empty = false;
+ }
+ }
+
+ closedir($dh);
+
+ if ($empty) {
+ rmdir($upload_path."/".$dir);
+ }
+ }
+ closedir($outer_dh);
+ }
+
+ function nuke_id()
+ {
+ if (!$this->input->is_cli_request()) return;
+
+ $id = $this->uri->segment(3);
+
+
+ $file_data = $this->mfile->get_filedata($id);
+
+ if (empty($file_data)) {
+ echo "unknown id \"$id\"\n";
+ return;
+ }
+
+ $hash = $file_data["hash"];
+
+ $this->db->query("
+ DELETE FROM files
+ WHERE hash = ?
+ ", array($hash));
+
+ unlink($this->mfile->file($hash));
+
+ echo "removed hash \"$hash\"\n";
+ }
+
+ function update_file_sizes()
+ {
+ if (!$this->input->is_cli_request()) return;
+
+ $chunk = 500;
+
+ $total = $this->db->count_all("files");
+
+ for ($limit = 0; $limit < $total; $limit += $chunk) {
+ $query = $this->db->query("
+ SELECT hash
+ FROM files
+ GROUP BY hash
+ LIMIT $limit, $chunk
+ ")->result_array();
+
+ foreach ($query as $key => $item) {
+ $hash = $item["hash"];
+ $filesize = intval(filesize($this->mfile->file($hash)));
+ $this->db->query("
+ UPDATE files
+ SET filesize = ?
+ WHERE hash = ?
+ ", array($filesize, $hash));
+ }
+ }
+
+
+ }
+}
+
+# vim: set noet: