From 19f0aab3221dd7760387cbec745c1eca9b215af7 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Sat, 9 Sep 2017 16:08:00 +0200 Subject: WIP: CI3 migration Signed-off-by: Florian Pritz --- application/controllers/Main.php | 989 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 989 insertions(+) create mode 100644 application/controllers/Main.php (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php new file mode 100644 index 000000000..b1ec81710 --- /dev/null +++ b/application/controllers/Main.php @@ -0,0 +1,989 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Main extends MY_Controller { + + function __construct() + { + parent::__construct(); + + $this->load->model('mfile'); + $this->load->model('mmultipaste'); + } + + function index() + { + if ($this->input->is_cli_request()) { + $this->load->library("../controllers/tools"); + return $this->tools->index(); + } + + // Try to guess what the user would like to do. + $id = $this->uri->segment(1); + if (strpos($id, "m-") === 0 && $this->mmultipaste->id_exists($id)) { + $this->_download(); + } elseif ($id != "file" && $this->mfile->id_exists($id)) { + $this->_download(); + } elseif ($id && $id != "file") { + $this->_non_existent(); + } else { + $this->upload_form(); + } + } + + /** + * Generate a page title of the format "Multipaste - $filename, $filename, … (N more)". + * This mainly helps in IRC channels to quickly determine what is in a multipaste. + * + * @param files array of filedata + * @return title to be used + */ + private function _multipaste_page_title(array $files) + { + $filecount = count($files); + $title = "Multipaste ($filecount files) - "; + $titlenames = array(); + $len = strlen($title); + $delimiter = ', '; + $maxlen = 100; + + foreach ($files as $file) { + if ($len > $maxlen) break; + + $filename = $file['filename']; + $titlenames[] = htmlspecialchars($filename); + $len += strlen($filename) + strlen($delimiter); + } + + $title .= implode($delimiter, $titlenames); + + $leftover_count = $filecount - count($titlenames); + + if ($leftover_count > 0) { + $title .= $delimiter.'… ('.$leftover_count.' more)'; + } + + return $title; + } + + function _download() + { + $id = $this->uri->segment(1); + $lexer = urldecode($this->uri->segment(2)); + + $is_multipaste = false; + if ($this->mmultipaste->id_exists($id)) { + $is_multipaste = true; + + if(!$this->mmultipaste->valid_id($id)) { + $this->mmultipaste->delete_id($id); + return $this->_non_existent(); + } + $files = $this->mmultipaste->get_files($id); + $this->data["title"] = $this->_multipaste_page_title($files); + } elseif ($this->mfile->id_exists($id)) { + if (!$this->mfile->valid_id($id)) { + return $this->_non_existent(); + } + + $files = array($this->mfile->get_filedata($id)); + $this->data["title"] = htmlspecialchars($files[0]["filename"]); + } else { + assert(0); + } + + assert($files !== false); + assert(is_array($files)); + assert(count($files) >= 1); + + // don't allow unowned files to be downloaded + foreach ($files as $filedata) { + if ($filedata["user"] == 0) { + return $this->_non_existent(); + } + } + + $etag = ""; + foreach ($files as $filedata) { + $etag = sha1($etag.$filedata["data_id"]); + } + + // handle some common "lexers" here + switch ($lexer) { + case "": + break; + + case "qr": + handle_etag($etag); + header("Content-disposition: inline; filename=\"".$id."_qr.png\"\n"); + header("Content-Type: image/png\n"); + $qr = new \Endroid\QrCode\QrCode(); + $qr->setText(site_url($id).'/') + ->setSize(350) + ->setErrorCorrection('low') + ->render(); + exit(); + + case "info": + return $this->_display_info($id); + + case "tar": + if ($is_multipaste) { + return $this->_tarball($id); + } + + case "pls": + if ($is_multipaste) { + return $this->_generate_playlist($id); + } + + default: + if ($is_multipaste) { + throw new \exceptions\UserInputException("file/download/invalid-action", "Invalid action \"".htmlspecialchars($lexer)."\""); + } + break; + } + + $this->load->driver("ddownload"); + + // user wants the plain file + if ($lexer == 'plain') { + assert(count($files) == 1); + handle_etag($etag); + + $filedata = $files[0]; + $filepath = $this->mfile->file($filedata["data_id"]); + $this->ddownload->serveFile($filepath, $filedata["filename"], "text/plain"); + exit(); + } + + $output_cache = new \libraries\Output_cache(); + + foreach ($files as $key => $filedata) { + $file = $this->mfile->file($filedata['data_id']); + $pygments = new \libraries\Pygments($file, $filedata["mimetype"], $filedata["filename"]); + + // autodetect the lexer for highlighting if the URL contains a / after the ID (/ID/) + // /ID/lexer disables autodetection + $autodetect_lexer = !$lexer && substr_count(ltrim($this->uri->uri_string(), "/"), '/') >= 1; + $autodetect_lexer = $is_multipaste ? true : $autodetect_lexer; + if ($autodetect_lexer) { + $lexer = $pygments->autodetect_lexer(); + } + + // resolve aliases + // this is mainly used for compatibility + $lexer = $pygments->resolve_lexer_alias($lexer); + + // if there is no mimetype mapping we can't highlight it + $can_highlight = $pygments->can_highlight(); + + $filesize_too_big = filesize($file) > $this->config->item('upload_max_text_size'); + + if (!$can_highlight || $filesize_too_big || !$lexer) { + if (!$is_multipaste) { + // 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", "Content-Security-Policy") as $header_name) { + header("$header_name: default-src 'none'; img-src *; media-src *; font-src *; style-src 'unsafe-inline' *; script-src 'none'; object-src *; frame-src 'none'; "); + } + handle_etag($etag); + $this->ddownload->serveFile($file, $filedata["filename"], $filedata["mimetype"]); + exit(); + } else { + $mimetype = $filedata["mimetype"]; + $base = explode("/", $filedata["mimetype"])[0]; + + if (\libraries\Image::type_supported($mimetype)) { + $filedata["tooltip"] = \service\files::tooltip($filedata); + $filedata["orientation"] = libraries\Image::get_exif_orientation($file); + $output_cache->add_merge( + array("items" => array($filedata)), + 'file/fragments/thumbnail' + ); + } else if ($base == "audio") { + $output_cache->add(array("filedata" => $filedata), "file/fragments/audio-player"); + } else if ($base == "video") { + $output_cache->add(array("filedata" => $filedata), "file/fragments/video-player"); + } else { + $output_cache->add_merge( + array("items" => array($filedata)), + 'file/fragments/uploads_table' + ); + } + continue; + } + } + + if ($lexer == "asciinema") { + $output_cache->add(array("filedata" => $filedata), "file/fragments/asciinema-player"); + } else { + $output_cache->add_function(function() use ($output_cache, $filedata, $lexer, $is_multipaste) { + $renderer = new \service\renderer($output_cache, $this->mfile, $this->data); + $renderer->highlight_file($filedata, $lexer, $is_multipaste); + }); + } + } + + // TODO: move lexers json to dedicated URL + $this->data['lexers'] = \libraries\Pygments::get_lexers(); + + // Output everything + // Don't use the output class/append_output because it does too + // much magic ({elapsed_time} and {memory_usage}). + // Direct echo puts us on the safe side. + echo $this->load->view('file/html_header', $this->data, true); + $output_cache->render(); + echo $this->load->view('file/html_footer', $this->data, true); + } + + private function _display_info($id) + { + if ($this->mmultipaste->id_exists($id)) { + $files = $this->mmultipaste->get_files($id); + + $this->data["title"] .= " - Info $id"; + + $multipaste = $this->mmultipaste->get_multipaste($id); + $total_size = 0; + $timeout = -1; + foreach($files as $filedata) { + $total_size += $filedata["filesize"]; + $file_timeout = $this->mfile->get_timeout($filedata["id"]); + if ($timeout == -1 || ($timeout > $file_timeout && $file_timeout >= 0)) { + $timeout = $file_timeout; + } + } + + $data = array_merge($this->data, array( + 'timeout_string' => $timeout >= 0 ? date("r", $timeout) : "Never", + 'upload_date' => $multipaste["date"], + 'id' => $id, + 'size' => $total_size, + 'file_count' => count($files), + )); + + $this->load->view('header', $this->data); + $this->load->view('file/multipaste_info', $data); + $this->load->view('footer', $this->data); + return; + } elseif ($this->mfile->id_exists($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('header', $this->data); + $this->load->view('file/file_info', $this->data); + $this->load->view('footer', $this->data); + } + } + + private function _tarball($id) + { + if ($this->mmultipaste->id_exists($id)) { + $seen = array(); + $path = $this->mmultipaste->get_tarball_path($id); + $archive = new \service\storage($path); + + if (!$archive->exists()) { + $files = $this->mmultipaste->get_files($id); + + $total_size = 0; + foreach ($files as $filedata) { + $total_size += $filedata["filesize"]; + } + + if ($total_size > $this->config->item("tarball_max_size")) { + throw new \exceptions\PublicApiException("file/tarball/tarball-filesize-limit", "Tarball too large, refusing to create."); + } + + $tmpfile = $archive->begin(); + // create empty tar archive so PharData has something to open + file_put_contents($tmpfile, str_repeat("\0", 1024*10)); + $a = new PharData($tmpfile); + + foreach ($files as $filedata) { + $filename = $filedata["filename"]; + if (isset($seen[$filename]) && $seen[$filename]) { + $filename = $filedata["id"]."-".$filedata["filename"]; + } + assert(!isset($seen[$filename])); + $a->addFile($this->mfile->file($filedata["data_id"]), $filename); + $seen[$filename] = true; + } + $archive->gzip_compress(); + $archive->commit(); + } + + // update mtime so the cronjob will keep the file for longer + $lock = fopen($archive->get_file(), "r+"); + flock($lock, LOCK_SH); + touch($archive->get_file()); + flock($lock, LOCK_UN); + + assert(filesize($archive->get_file()) > 0); + + $this->load->driver("ddownload"); + $this->ddownload->serveFile($archive->get_file(), "$id.tar.gz", "application/x-gzip"); + } + } + + /** + * Generate a PLS v2 playlist + */ + private function _generate_playlist($id) + { + $files = $this->mmultipaste->get_files($id); + $counter = 1; + + $playlist = "[playlist]\n"; + foreach ($files as $file) { + // only add audio/video files + $base = explode("/", $file['mimetype'])[0]; + if (!($base === "audio" || $base === "video")) { + continue; + } + + $url = site_url($file["id"]); + $playlist .= sprintf("File%d=%s\n", $counter++, $url); + } + $playlist .= sprintf("NumberOfEntries=%d\n", $counter - 1); + $playlist .= "Version=2\n"; + + $this->output->set_content_type('audio/x-scpls'); + $this->output->set_output($playlist); + } + + function _non_existent() + { + $this->data["title"] .= " - Not Found"; + $this->output->set_status_header(404); + $this->load->view('header', $this->data); + $this->load->view('file/non_existent', $this->data); + $this->load->view('footer', $this->data); + } + + private function _prepare_claim($ids, $lexer) + { + if (!$this->muser->logged_in()) { + $this->muser->require_session(); + // keep the upload but require the user to login + $last_upload = $this->session->userdata("last_upload"); + if ($last_upload === false) { + $last_upload = array( + "ids" => [], + "lexer" => "", + ); + } + $last_upload = array( + "ids" => array_merge($last_upload['ids'], $ids), + "lexer" => "", + ); + $this->session->set_userdata("last_upload", $last_upload); + $this->data["redirect_uri"] = "file/claim_id"; + $this->muser->require_access("basic"); + } + + } + + function _show_url($ids, $lexer) + { + $redirect = false; + $this->_prepare_claim($ids, $lexer); + + foreach ($ids as $id) { + if ($lexer) { + $this->data['urls'][] = site_url($id).'/'.$lexer; + } else { + $this->data['urls'][] = site_url($id).'/'; + + if (count($ids) == 1) { + $filedata = $this->mfile->get_filedata($id); + $file = $this->mfile->file($filedata['data_id']); + $pygments = new \libraries\Pygments($file, $filedata["mimetype"], $filedata["filename"]); + $lexer = $pygments->should_highlight(); + + // If we detected a highlightable file redirect, + // otherwise show the URL because browsers would just show a DL dialog + if ($lexer) { + $redirect = true; + } + } + } + } + + if ($redirect && count($ids) == 1) { + redirect($this->data['urls'][0], "location", 303); + } else { + $this->load->view('header', $this->data); + $this->load->view('file/show_url', $this->data); + $this->load->view('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['username'] = $this->muser->get_username(); + + $repaste_id = $this->input->get("repaste"); + + if ($repaste_id) { + $filedata = $this->mfile->get_filedata($repaste_id); + + $pygments = new \libraries\Pygments($this->mfile->file($filedata["data_id"]), $filedata["mimetype"], $filedata["filename"]); + if ($filedata !== false && $pygments->can_highlight()) { + $this->data["textarea_content"] = file_get_contents($this->mfile->file($filedata["data_id"])); + } + } + + if (file_exists(FCPATH.'data/client/latest')) { + $this->var->latest_client = trim(file_get_contents(FCPATH.'data/client/latest')); + $this->data['client_link'] = base_url().'data/client/fb-'.$this->var->latest_client.'.tar.gz'; + } else { + $this->data['client_link'] = false; + } + + $this->load->view('header', $this->data); + $this->load->view('file/upload_form', $this->data); + $this->load->view('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 thumbnail() + { + $id = $this->uri->segment(3); + + if (!$this->mfile->valid_id($id)) { + return $this->_non_existent(); + } + + $etag = "$id-thumb"; + handle_etag($etag); + + $thumb_size = 150; + $cache_timeout = 60*60*24*30; # 1 month + + $filedata = $this->mfile->get_filedata($id); + if (!$filedata) { + throw new \exceptions\ApiException("file/thumbnail/filedata-unavailable", "Failed to get file data"); + } + + $cache_key = $filedata['data_id'].'_thumb_'.$thumb_size; + + $thumb = cache_function($cache_key, $cache_timeout, function() use ($filedata, $thumb_size){ + $CI =& get_instance(); + $img = new libraries\Image($this->mfile->file($filedata["data_id"])); + $img->makeThumb($thumb_size, $thumb_size); + $thumb = $img->get(IMAGETYPE_JPEG); + return $thumb; + }); + + $this->output->set_header("Cache-Control:max-age=31536000, public"); + $this->output->set_header("Expires: ".date("r", time() + 365 * 24 * 60 * 60)); + $this->output->set_content_type("image/jpeg"); + $this->output->set_output($thumb); + } + + function upload_history_thumbnails() + { + $this->muser->require_access(); + + $user = $this->muser->get_userid(); + + // TODO: move to \service\files and possibly use \s\f::history() + $query = $this->db + ->select('files.id, filename, mimetype, files.date, hash, file_storage.id storage_id, filesize, user') + ->from('files') + ->join('file_storage', 'file_storage.id = files.file_storage_id') + ->where(' + (files.user = '.$this->db->escape($user).') + AND ( + mimetype LIKE \'image%\' + OR mimetype IN (\'application/pdf\') + )', null, false) + ->order_by('date', 'desc') + ->get()->result_array(); + + foreach($query as $key => $item) { + assert($item["user"] === $user); + $item["data_id"] = $item['hash']."-".$item['storage_id']; + $query[$key]["data_id"] = $item["data_id"]; + if (!$this->mfile->valid_filedata($item)) { + unset($query[$key]); + continue; + } + $query[$key]["tooltip"] = \service\files::tooltip($item); + $query[$key]["orientation"] = libraries\Image::get_exif_orientation($this->mfile->file($item["data_id"])); + } + + $this->data["items"] = $query; + + $this->load->view('header', $this->data); + $this->load->view('file/upload_history_thumbnails', $this->data); + $this->load->view('footer', $this->data); + } + + public function handle_history_submit() + { + $this->muser->require_access("apikey"); + + $process = $this->input->post("process"); + + $dispatcher = [ + "delete" => function() { + return $this->do_delete(); + }, + "multipaste" => function() { + return $this->_append_multipaste_queue(); + }, + ]; + + if (isset($dispatcher[$process])) { + $dispatcher[$process](); + } else { + throw new \exceptions\UserInputException("file/handle_history_submit/invalid-process-value", "Value in process field not found in dispatch table"); + } + } + + private function _append_multipaste_queue() + { + $ids = $this->input->post_array("ids"); + if ($ids === false) { + $ids = []; + } + + $m = new \service\multipaste_queue(); + $m->append($ids); + redirect("file/multipaste/queue"); + } + + function upload_history() + { + $this->muser->require_access("apikey"); + + $history = service\files::history($this->muser->get_userid()); + + // 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); + } + + foreach ($history["multipaste_items"] as $key => $item) { + $size = 0; + $filenames = array(); + $files = array(); + $max_filenames = 10; + + foreach ($item["items"] as $i) { + $size += $history["items"][$i["id"]]["filesize"]; + $files[] = array( + "filename" => $history["items"][$i["id"]]['filename'], + "sort_order" => $i["sort_order"], + ); + } + + uasort($files, function ($a, $b) { + return $a['sort_order'] - $b['sort_order']; + }); + + $filenames = array_map(function ($a) {return $a['filename'];}, $files); + + if (count($filenames) > $max_filenames) { + $filenames = array_slice($filenames, 0, $max_filenames); + $filenames[] = "..."; + } + + $history["items"][] = array( + "id" => $item["url_id"], + "filename" => count($item["items"])." file(s)", + "mimetype" => "", + "date" => $item["date"], + "hash" => "", + "filesize" => $size, + "preview_text" => implode("\n", $filenames), + ); + } + + uasort($history["items"], function($a, $b) { + return $b["date"] - $a["date"]; + }); + + foreach($history["items"] as $key => $item) { + $history["items"][$key]["filesize"] = format_bytes($item["filesize"]); + + if (isset($item['preview_text'])) { + $history["items"][$key]["preview_text"] = htmlentities($item['preview_text']); + } + } + + $this->data["items"] = $history["items"]; + $this->data["lengths"] = $lengths; + $this->data["fields"] = $fields; + $this->data["total_size"] = format_bytes($history["total_size"]); + + $this->load->view('header', $this->data); + $this->load->view('file/upload_history', $this->data); + $this->load->view('footer', $this->data); + } + + function do_delete() + { + $this->muser->require_access("apikey"); + + $ids = $this->input->post_array("ids"); + + $ret = \service\files::delete($ids); + + $this->data["errors"] = $ret["errors"]; + $this->data["deleted_count"] = $ret["deleted_count"]; + $this->data["total_count"] = $ret["total_count"]; + + $this->load->view('header', $this->data); + $this->load->view('file/deleted', $this->data); + $this->load->view('footer', $this->data); + } + + function do_multipaste() + { + $this->muser->require_access("basic"); + + $ids = $this->input->post_array("ids"); + $userid = $this->muser->get_userid(); + $limits = $this->muser->get_upload_id_limits(); + + $ret = \service\files::create_multipaste($ids, $userid, $limits); + + return $this->_show_url(array($ret["url_id"]), false); + } + + /** + * Handle submissions from the web form (files and textareas). + */ + public function do_websubmit() + { + $files = getNormalizedFILES(); + $contents = $this->input->post_array("content"); + $filenames = $this->input->post_array("filename"); + + if (!is_array($filenames) || !is_array($contents)) { + throw new \exceptions\UserInputException('file/websubmit/invalid-form', 'The submitted POST form is invalid'); + } + + $ids = array(); + $ids = array_merge($ids, $this->_handle_textarea($contents, $filenames)); + $ids = array_merge($ids, $this->_handle_files($files)); + + + if (empty($ids)) { + throw new \exceptions\UserInputException("file/websubmit/no-input", "You didn't enter any text or upload any files"); + } + + if (count($ids) > 1) { + $userid = $this->muser->get_userid(); + $limits = $this->muser->get_upload_id_limits(); + $multipaste_id = \service\files::create_multipaste($ids, $userid, $limits)["url_id"]; + + $ids[] = $multipaste_id; + $this->_prepare_claim($ids, false); + + redirect(site_url($multipaste_id)."/"); + } + + $this->_show_url($ids, false); + } + + private function _handle_files($files) + { + $ids = array(); + + if (!empty($files)) { + $limits = $this->muser->get_upload_id_limits(); + $userid = $this->muser->get_userid(); + service\files::verify_uploaded_files($files); + + foreach ($files as $key => $file) { + $id = $this->mfile->new_id($limits[0], $limits[1]); + service\files::add_uploaded_file($userid, $id, $file["tmp_name"], $file["name"]); + $ids[] = $id; + } + } + + return $ids; + } + + private function _handle_textarea($contents, $filenames) + { + $ids = array(); + + foreach ($contents as $key => $content) { + $filesize = strlen($content); + + if ($filesize == 0) { + unset($contents[$key]); + } + + if ($filesize > $this->config->item('upload_max_size')) { + throw new \exceptions\RequestTooBigException("file/websubmit/request-too-big", "Error while uploading: Paste too big"); + } + } + + $limits = $this->muser->get_upload_id_limits(); + $userid = $this->muser->get_userid(); + + foreach ($contents as $key => $content) { + $filename = "stdin"; + if (isset($filenames[$key]) && $filenames[$key] != "") { + $filename = $filenames[$key]; + } + + $id = $this->mfile->new_id($limits[0], $limits[1]); + service\files::add_file_data($userid, $id, $content, $filename); + $ids[] = $id; + } + + return $ids; + } + + function claim_id() + { + $this->muser->require_access(); + + $last_upload = $this->session->userdata("last_upload"); + + if ($last_upload === false) { + throw new \exceptions\PublicApiException("file/claim_id/last_upload-failed", "Failed to get last upload data, unable to claim uploads"); + } + + $ids = $last_upload["ids"]; + $errors = array(); + + assert(is_array($ids)); + + foreach ($ids as $key => $id) { + $affected = 0; + $affected += $this->mfile->adopt($id); + $affected += $this->mmultipaste->adopt($id); + + if ($affected == 0) { + $errors[] = $id; + } + } + + if (!empty($errors)) { + throw new \exceptions\PublicApiException("file/claim_id/failed", "Failed to claim ".implode(", ", $errors).""); + } + + $this->session->unset_userdata("last_upload"); + + $this->_show_url($ids, $last_upload["lexer"]); + } + + function contact() + { + $file = FCPATH."data/local/contact-info.php"; + if (file_exists($file)) { + $this->data["contact_info"] = file_get_contents($file); + } else { + $this->data["contact_info"] = '

Contact info not available.

'; + } + + $this->load->view('header', $this->data); + $this->load->view('contact', $this->data); + $this->load->view('footer', $this->data); + } + + /* Functions below this comment can only be run via the CLI + * `php index.php file ` + */ + + // Removes old files + function cron() + { + $this->_require_cli_request(); + + $tarball_dir = $this->config->item("upload_path")."/special/multipaste-tarballs"; + if (is_dir($tarball_dir)) { + $tarball_cache_time = $this->config->item("tarball_cache_time"); + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($tarball_dir), RecursiveIteratorIterator::SELF_FIRST); + + foreach ($it as $file) { + if ($file->isFile()) { + if ($file->getMTime() < time() - $tarball_cache_time) { + $lock = fopen($file, "r+"); + flock($lock, LOCK_EX); + unlink($file); + flock($lock, LOCK_UN); + } + } + } + } + + $oldest_time = (time() - $this->config->item('upload_max_age')); + $oldest_session_time = (time() - $this->config->item("sess_expiration")); + $config = array( + "upload_max_age" => $this->config->item("upload_max_age"), + "small_upload_size" => $this->config->item("small_upload_size"), + "sess_expiration" => $this->config->item("sess_expiration"), + ); + + $query = $this->db->select('file_storage_id storage_id, files.id, user, files.date, hash') + ->from('files') + ->join('file_storage', "file_storage.id = files.file_storage_id") + ->where("user", 0) + ->where("files.date <", $oldest_session_time) + ->get()->result_array(); + + foreach($query as $row) { + $row['data_id'] = $row['hash'].'-'.$row['storage_id']; + \service\files::valid_id($row, $config, $this->mfile, time()); + } + + // 0 age disables age checks + if ($this->config->item('upload_max_age') == 0) return; + + $query = $this->db->select('hash, files.id, user, files.date, file_storage.id storage_id') + ->from('files') + ->join('file_storage', "file_storage.id = files.file_storage_id") + ->where('files.date <', $oldest_time) + ->get()->result_array(); + + foreach($query as $row) { + $row['data_id'] = $row['hash'].'-'.$row['storage_id']; + \service\files::valid_id($row, $config, $this->mfile, time()); + } + } + + /* remove files without database entries */ + function clean_stale_files() + { + $this->_require_cli_request(); + + $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 == "." || $dir == "special") { + continue; + } + + $dh = opendir($upload_path."/".$dir); + + $empty = true; + + while (($file = readdir($dh)) !== false) { + if ($file == ".." || $file == ".") { + continue; + } + + try { + list($hash, $storage_id) = explode("-", $file); + } catch (\ErrorException $e) { + unlink($upload_path."/".$dir."/".$file); + continue; + } + + $query = $this->db->select('hash, id') + ->from('file_storage') + ->where('hash', $hash) + ->where('id', $storage_id) + ->limit(1) + ->get()->row_array(); + + if (empty($query)) { + $this->mfile->delete_data_id($file); + } else { + $empty = false; + } + } + + closedir($dh); + + if ($empty && file_exists($upload_path."/".$dir)) { + rmdir($upload_path."/".$dir); + } + } + closedir($outer_dh); + + // TODO: clean up special/multipaste-tarballs? cron() already expires + // after a rather short time, do we really need this here then? + } + + function nuke_id() + { + $this->_require_cli_request(); + + $id = $this->uri->segment(3); + + $file_data = $this->mfile->get_filedata($id); + + if (empty($file_data)) { + echo "unknown id \"$id\"\n"; + return; + } + + $data_id = $file_data["data_id"]; + $this->mfile->delete_data_id($data_id); + echo "removed data_id \"$data_id\"\n"; + } + + function update_file_metadata() + { + $this->_require_cli_request(); + + $chunk = 500; + + $total = $this->db->count_all("file_storage"); + + for ($limit = 0; $limit < $total; $limit += $chunk) { + $query = $this->db->select('hash, id') + ->from('file_storage') + ->limit($chunk, $limit) + ->get()->result_array(); + + foreach ($query as $key => $item) { + $data_id = $item["hash"].'-'.$item['id']; + $filepath = $this->mfile->file($data_id); + $mimetype = mimetype($filepath); + $filesize = filesize($filepath); + + $this->db->where('id', $item['id']) + ->set(array( + 'mimetype' => $mimetype, + 'filesize' => $filesize, + )) + ->update('file_storage'); + } + } + } +} + +# vim: set noet: -- cgit v1.2.3-24-g4f1b From e0f11bb3ada4171f186bf92e4bf8995e2b2d8944 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Sat, 16 Sep 2017 14:47:02 +0200 Subject: Update negativ return check of session userdata for CI3 Signed-off-by: Florian Pritz --- application/controllers/Main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index b1ec81710..19cb63d4f 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -376,7 +376,7 @@ class Main extends MY_Controller { $this->muser->require_session(); // keep the upload but require the user to login $last_upload = $this->session->userdata("last_upload"); - if ($last_upload === false) { + if ($last_upload === NULL) { $last_upload = array( "ids" => [], "lexer" => "", @@ -778,7 +778,7 @@ class Main extends MY_Controller { $last_upload = $this->session->userdata("last_upload"); - if ($last_upload === false) { + if ($last_upload === NULL) { throw new \exceptions\PublicApiException("file/claim_id/last_upload-failed", "Failed to get last upload data, unable to claim uploads"); } -- cgit v1.2.3-24-g4f1b From 6be5f77331a82d0154f1ed82b07b64917c2a102d Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Mon, 18 Sep 2017 12:14:27 +0200 Subject: Remove secondary controller for CLI usage output Signed-off-by: Florian Pritz --- application/controllers/Main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index 19cb63d4f..2422d96bf 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -20,8 +20,8 @@ class Main extends MY_Controller { function index() { if ($this->input->is_cli_request()) { - $this->load->library("../controllers/tools"); - return $this->tools->index(); + output_cli_usage(); + exit; } // Try to guess what the user would like to do. -- cgit v1.2.3-24-g4f1b From 4388f9080ded6d4e4db9a333e94a005ba072a8cd Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Mon, 18 Sep 2017 12:45:31 +0200 Subject: Migrate return code checks for CI3 Signed-off-by: Florian Pritz --- application/controllers/Main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index 2422d96bf..e7775d6d2 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -566,7 +566,7 @@ class Main extends MY_Controller { private function _append_multipaste_queue() { $ids = $this->input->post_array("ids"); - if ($ids === false) { + if ($ids === null) { $ids = []; } -- cgit v1.2.3-24-g4f1b From 4a8d9de07e65b93a2329e9f7176b3148f7d7bcef Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Thu, 21 Sep 2017 11:16:07 +0200 Subject: Fix detection of URL ending in slash CI trims tailing slashes from the URL and this should work just fine too. Signed-off-by: Florian Pritz --- application/controllers/Main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index e7775d6d2..279d34520 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -171,7 +171,7 @@ class Main extends MY_Controller { // autodetect the lexer for highlighting if the URL contains a / after the ID (/ID/) // /ID/lexer disables autodetection - $autodetect_lexer = !$lexer && substr_count(ltrim($this->uri->uri_string(), "/"), '/') >= 1; + $autodetect_lexer = !$lexer && preg_match('/^[^?]*\/(\?.*)?$/', $_SERVER['REQUEST_URI']); $autodetect_lexer = $is_multipaste ? true : $autodetect_lexer; if ($autodetect_lexer) { $lexer = $pygments->autodetect_lexer(); -- cgit v1.2.3-24-g4f1b From 161d8524cf1c37d3ae0a4ae3442ed03fbca2e48c Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Thu, 21 Sep 2017 11:20:49 +0200 Subject: Close session in long running methods These do not write to the session but may run for some time. There is no need to keep the session locked while they perform their work. Signed-off-by: Florian Pritz --- application/controllers/Main.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index 279d34520..31100f3d7 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -74,6 +74,7 @@ class Main extends MY_Controller { function _download() { + session_write_close(); $id = $this->uri->segment(1); $lexer = urldecode($this->uri->segment(2)); @@ -469,6 +470,7 @@ class Main extends MY_Controller { function thumbnail() { + session_write_close(); $id = $this->uri->segment(3); if (!$this->mfile->valid_id($id)) { -- cgit v1.2.3-24-g4f1b From d8978f969093cc1d244a4a293d8135f0a4029109 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Thu, 21 Sep 2017 18:03:09 +0200 Subject: Remove usage of deprecated is_cli_request() Signed-off-by: Florian Pritz --- application/controllers/Main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/controllers/Main.php') diff --git a/application/controllers/Main.php b/application/controllers/Main.php index 31100f3d7..2e8ab3851 100644 --- a/application/controllers/Main.php +++ b/application/controllers/Main.php @@ -19,7 +19,7 @@ class Main extends MY_Controller { function index() { - if ($this->input->is_cli_request()) { + if (is_cli()) { output_cli_usage(); exit; } -- cgit v1.2.3-24-g4f1b