diff options
Diffstat (limited to 'application')
94 files changed, 5546 insertions, 258 deletions
diff --git a/application/config/.gitignore b/application/config/.gitignore new file mode 100644 index 000000000..45e1c5158 --- /dev/null +++ b/application/config/.gitignore @@ -0,0 +1,3 @@ +config-local.php +database.php +memcached.php diff --git a/application/config/autoload.php b/application/config/autoload.php index 53129c9c6..a471f3ab2 100644 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -52,7 +52,7 @@ $autoload['packages'] = array(); | $autoload['libraries'] = array('database', 'session', 'xmlrpc'); */ -$autoload['libraries'] = array(); +$autoload['libraries'] = array('database'); /* @@ -64,7 +64,7 @@ $autoload['libraries'] = array(); | $autoload['helper'] = array('url', 'file'); */ -$autoload['helper'] = array(); +$autoload['helper'] = array('url'); /* diff --git a/application/config/config.php b/application/config/config.php index 1ec65435e..d386a77b8 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -248,7 +248,7 @@ $config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; -$config['sess_use_database'] = FALSE; +$config['sess_use_database'] = true; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; @@ -293,7 +293,7 @@ $config['global_xss_filtering'] = FALSE; | 'csrf_cookie_name' = The cookie name | 'csrf_expire' = The number in seconds the token should expire. */ -$config['csrf_protection'] = FALSE; +$config['csrf_protection'] = FALSE; // our controller enables this later $config['csrf_token_name'] = 'csrf_test_name'; $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; @@ -357,6 +357,94 @@ $config['rewrite_short_tags'] = FALSE; */ $config['proxy_ips'] = ''; +/* +|-------------------------------------------------------------------------- +| FileBin +|-------------------------------------------------------------------------- + */ + +// upload_path should NOT be readable/served by the server, but only by the script +$config['upload_path'] = FCPATH.'data/uploads'; + +// Make sure to adjust PHP's limits (post_max_size, upload_max_filesize) if necessary +$config['upload_max_size'] = 256*1024*1024; // 256MiB + +// Files smaller than this will be highlit, larger ones will simply be downloaded +// even if requested to be highlit. +$config['upload_max_text_size'] = 2*1024*1024; // 2MiB + +// Files older than this will be deleted by the cron job. +// 0 disables deletion. +$config['upload_max_age'] = 60*60*24*5; // 5 days +$config['actions_max_age'] = 60*60*24*5; // 5 days + +// Files smaller than this won't be deleted (even if they are old enough) +$config['small_upload_size'] = 1024*10; // 10KiB + + +// Possible values: +// - apc: needs the apc module and is only useful on long running php processes +// - file: you will have to clean up the cache directory yourself (./application/cache/) +// - memcached: config in application/config/memcached.php; you need the memcached module (with the D) +// - dummy: disables caching +// +// It is highly suggested to enable the cache. +$config['cache_backend'] = "dummy"; + + +// For possible drivers look into ./application/libraries/Duser/drivers/ +$config['authentication_driver'] = 'db'; + +// This is only used it the driver is set to ldap +if (extension_loaded("ldap")) { + $config['auth_ldap'] = array( + "host" => 'ldaps://ldap.example.com', + "port" => 636, + "basedn" => "dc=example,dc=com", + "scope" => "one", // possible values: base, one, subtree + "options" => array( + // key/values pairs for ldap_set_option + // http://php.net/manual/en/function.ldap-set-option.php + LDAP_OPT_PROTOCOL_VERSION => 3 + ), + // Please note that php-ldap converts attributes to lowercase + "userid_field" => "uidnumber", // This has to be a unique integer + "username_field" => "uid" // This is the value the user supplies on the login form + ); +} + +// This is only used it the driver is set to fluxbb +$config['auth_fluxbb'] = array( + 'database' => 'fluxbb' +); + + +// Possible values: production, development +// "development" enables features like profiling and display of SQL queries. +$config['environment'] = "production"; + + +// This sets the download implementation. Possible values are php, nginx and lighttpd. +// The nginx and lighttpd drivers make use of the server's sendfile feature. +// +// The lighttpd driver requires the following directive to be set in your fastcgi.server configuration: +// "allow-x-send-file" => "enable" +// See http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI#X-Sendfile +// +// When using the nginx download driver you need to define an internal location +// from which nginx will serve your uploads: +// location ^~ /protected-uploads/ { +// internal; +// alias <upload_path>/; +// } +// See http://wiki.nginx.org/X-accel +$config['download_driver'] = 'php'; + +$config['download_nginx_location'] = '/protected-uploads'; + +if (file_exists(FCPATH.'application/config/config-local.php')) { + include FCPATH.'application/config/config-local.php'; +} /* End of file config.php */ /* Location: ./application/config/config.php */ diff --git a/application/config/constants.php b/application/config/constants.php index 4a879d360..1185dbca0 100644 --- a/application/config/constants.php +++ b/application/config/constants.php @@ -1,5 +1,7 @@ <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +putenv('HOME='.FCPATH); + /* |-------------------------------------------------------------------------- | File and Directory Modes diff --git a/application/config/example/.gitignore b/application/config/example/.gitignore new file mode 100644 index 000000000..f9be8dfe0 --- /dev/null +++ b/application/config/example/.gitignore @@ -0,0 +1 @@ +!* diff --git a/application/config/example/config-local.php b/application/config/example/config-local.php new file mode 100644 index 000000000..941c8b119 --- /dev/null +++ b/application/config/example/config-local.php @@ -0,0 +1,16 @@ +<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); + +/* + * Use this file to override any settings from config.php + * + * For descriptions of the options please refer to config.php. + */ + +// set this to a 32char random string +$config['encryption_key'] = ''; + +$config['upload_path'] = FCPATH.'data/uploads'; + +$config['index_page'] = 'index.php'; + +$config['cache_backend'] = "dummy"; diff --git a/application/config/database.php b/application/config/example/database.php index b4b34bf66..51b666b38 100644 --- a/application/config/database.php +++ b/application/config/example/database.php @@ -48,18 +48,18 @@ $active_group = 'default'; $active_record = TRUE; -$db['default']['hostname'] = 'localhost'; -$db['default']['username'] = ''; -$db['default']['password'] = ''; -$db['default']['database'] = ''; -$db['default']['dbdriver'] = 'mysql'; -$db['default']['dbprefix'] = ''; +$db['default']['hostname'] = "localhost"; +$db['default']['username'] = ""; +$db['default']['password'] = ""; +$db['default']['database'] = ""; +$db['default']['dbdriver'] = "mysqli"; +$db['default']['dbprefix'] = ""; $db['default']['pconnect'] = TRUE; $db['default']['db_debug'] = TRUE; $db['default']['cache_on'] = FALSE; -$db['default']['cachedir'] = ''; -$db['default']['char_set'] = 'utf8'; -$db['default']['dbcollat'] = 'utf8_general_ci'; +$db['default']['cachedir'] = ""; +$db['default']['char_set'] = "utf8"; +$db['default']['dbcollat'] = "utf8_bin"; $db['default']['swap_pre'] = ''; $db['default']['autoinit'] = TRUE; $db['default']['stricton'] = FALSE; diff --git a/application/config/example/index.html b/application/config/example/index.html new file mode 100644 index 000000000..c942a79ce --- /dev/null +++ b/application/config/example/index.html @@ -0,0 +1,10 @@ +<html> +<head> + <title>403 Forbidden</title> +</head> +<body> + +<p>Directory access is forbidden.</p> + +</body> +</html>
\ No newline at end of file diff --git a/application/config/example/memcached.php b/application/config/example/memcached.php new file mode 100644 index 000000000..29b145ec8 --- /dev/null +++ b/application/config/example/memcached.php @@ -0,0 +1,17 @@ +<?php + +$config = array( + "default" => array( + "hostname" => "127.0.0.1", + "port" => 11211, + "weight" => 1, + ), + "socket" => array( + "hostname" => FCPATH.'/memcached.sock', + "port" => 0, + "weight" => 2, + ), +); + + +?> diff --git a/application/config/migration.php b/application/config/migration.php index df42a3cae..75e9bc19b 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -8,7 +8,7 @@ | whenever you intend to do a schema migration. | */ -$config['migration_enabled'] = FALSE; +$config['migration_enabled'] = true; /* @@ -21,7 +21,7 @@ $config['migration_enabled'] = FALSE; | be upgraded / downgraded to. | */ -$config['migration_version'] = 0; +$config['migration_version'] = 13; /* @@ -38,4 +38,4 @@ $config['migration_path'] = APPPATH . 'migrations/'; /* End of file migration.php */ -/* Location: ./application/config/migration.php */
\ No newline at end of file +/* Location: ./application/config/migration.php */ diff --git a/application/config/routes.php b/application/config/routes.php index 5f9a58343..e0d963405 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -38,7 +38,11 @@ | */ -$route['default_controller'] = "welcome"; +$route['default_controller'] = "file"; +$route['user/(:any)'] = "user/$1"; +$route['file/(:any)'] = "file/$1"; +$route['tools/(:any)'] = "tools/$1"; +$route['(:any)'] = "file/index/$1"; $route['404_override'] = ''; diff --git a/application/controllers/file.php b/application/controllers/file.php new file mode 100644 index 000000000..e1b43d314 --- /dev/null +++ b/application/controllers/file.php @@ -0,0 +1,1122 @@ +<?php +/* + * Copyright 2009-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class File extends MY_Controller { + + protected $json_enabled_functions = array( + "upload_history", + "do_upload", + "do_delete", + "do_multipaste", + ); + + function __construct() + { + parent::__construct(); + + $this->load->model('mfile'); + $this->load->model('mmultipaste'); + + if (is_cli_client()) { + $this->var->view_dir = "file_plaintext"; + } else { + $this->var->view_dir = "file"; + } + } + + 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 (!empty($_FILES)) { + $this->do_upload(); + } elseif (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(); + } + } + + 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)) { + return $this->_non_existent(); + } + $files = $this->mmultipaste->get_files($id); + $this->data["title"] = "Multipaste"; + } 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["hash"]); + } + + // 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"); + passthru('qrencode -s 10 -o - '.escapeshellarg(site_url($id).'/')); + exit(); + + case "info": + return $this->_display_info($id); + + default: + if ($is_multipaste) { + show_error("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["hash"]); + $this->ddownload->serveFile($filepath, $filedata["filename"], "text/plain"); + exit(); + } + + $this->load->library("output_cache"); + + foreach ($files as $key => $filedata) { + $file = $this->mfile->file($filedata['hash']); + + // 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 = $this->mfile->autodetect_lexer($filedata["mimetype"], $filedata["filename"]); + } + + // resolve aliases + // this is mainly used for compatibility + $lexer = $this->mfile->resolve_lexer_alias($lexer); + + // 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 || !$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 { + switch ($filedata["mimetype"]) { + // TODO: handle video/audio + // TODO: handle more image formats (thumbnails needs to be improved) + case "image/jpeg": + case "image/png": + case "image/gif": + $filedata["tooltip"] = $this->_tooltip_for_image($filedata); + $this->output_cache->add_merge( + array("items" => array($filedata)), + 'file/fragments/thumbnail' + ); + + break; + + default: + $this->output_cache->add_merge( + array("items" => array($filedata)), + 'file/fragments/uploads_table' + ); + break; + } + continue; + } + } + + $this->output_cache->add_function(function() use ($filedata, $lexer, $is_multipaste) { + $this->_highlight_file($filedata, $lexer, $is_multipaste); + }); + } + + // TODO: move lexers json to dedicated URL + $this->data['lexers'] = $this->mfile->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($this->var->view_dir.'/html_header', $this->data, true); + $this->output_cache->render(); + echo $this->load->view($this->var->view_dir.'/html_footer', $this->data, true); + } + + private function _colorify($file, $lexer, $anchor_id = false) + { + $return_value = 0; + $output = ""; + $lines_to_remove = 0; + + $output .= '<div class="code content table">'."\n"; + $output .= '<div class="highlight"><pre>'."\n"; + + ob_start(); + if ($lexer == "ascii") { + passthru('ansi2html -p < '.escapeshellarg($file), $return_value); + // Last line is empty + $lines_to_remove = 1; + } else { + passthru('pygmentize -F codetagify -O encoding=guess,outencoding=utf8,stripnl=False -l '.escapeshellarg($lexer).' -f html '.escapeshellarg($file), $return_value); + // Last 2 items are "</pre></div>" and "" + $lines_to_remove = 2; + } + $buf = ob_get_contents(); + ob_end_clean(); + + + $buf = explode("\n", $buf); + $line_count = count($buf); + + for ($i = 1; $i <= $lines_to_remove; $i++) { + unset($buf[$line_count - $i]); + } + + foreach ($buf as $key => $line) { + $line_number = $key + 1; + if ($key == 0) { + $line = str_replace("<div class=\"highlight\"><pre>", "", $line); + } + + $anchor = "n$line_number"; + if ($anchor_id !== false) { + $anchor = "n-$anchor_id-$line_number"; + } + + // Be careful not to add superflous whitespace here (we are in a <pre>) + $output .= "<div class=\"table-row\">" + ."<a href=\"#$anchor\" class=\"linenumber table-cell\">" + ."<span class=\"anchor\" id=\"$anchor\"> </span>" + ."</a>" + ."<span class=\"line table-cell\">".$line."</span>\n"; + $output .= "</div>"; + } + + $output .= "</pre></div>"; + $output .= "</div>"; + + return array( + "return_value" => $return_value, + "output" => $output + ); + } + + private function _highlight_file($filedata, $lexer, $is_multipaste) + { + // highlight the file and cache the result, fall back to plain text if $lexer fails + foreach (array($lexer, "text") as $lexer) { + $highlit = cache_function($filedata['hash'].'_'.$lexer, 100, + function() use ($filedata, $lexer, $is_multipaste) { + $file = $this->mfile->file($filedata['hash']); + if ($lexer == "rmd") { + ob_start(); + + echo '<div class="code content table markdownrender">'."\n"; + echo '<div class="table-row">'."\n"; + echo '<div class="table-cell">'."\n"; + passthru('perl '.FCPATH.'scripts/Markdown.pl '.escapeshellarg($file), $return_value); + echo '</div></div></div>'; + + return array( + "output" => ob_get_clean(), + "return_value" => $return_value, + ); + } else { + return get_instance()->_colorify($file, $lexer, $is_multipaste ? $filedata["id"] : false); + } + }); + + if ($highlit["return_value"] == 0) { + break; + } else { + $message = "Error trying to process the file. Either the lexer is unknown or something is broken."; + if ($lexer != "text") { + $message .= " Falling back to plain text."; + } + $this->output_cache->render_now( + array("error_message" => "<p>$message</p>"), + "file/fragments/alert-wide" + ); + } + } + + $data = array_merge($this->data, array( + 'title' => htmlspecialchars($filedata['filename']), + 'id' => $filedata["id"], + 'current_highlight' => htmlspecialchars($lexer), + 'timeout' => $this->mfile->get_timeout_string($filedata["id"]), + 'filedata' => $filedata, + )); + + $this->output_cache->render_now($data, $this->var->view_dir.'/html_paste_header'); + $this->output_cache->render_now($highlit["output"]); + $this->output_cache->render_now($data, $this->var->view_dir.'/html_paste_footer'); + } + + private function _tooltip_for_image($filedata) + { + $filesize = format_bytes($filedata["filesize"]); + $dimensions = $this->mfile->image_dimension($this->mfile->file($filedata["hash"])); + $upload_date = date("r", $filedata["date"]); + + $tooltip = "${filedata["id"]} - $filesize<br>"; + $tooltip .= "$upload_date<br>"; + $tooltip .= "$dimensions - ${filedata["mimetype"]}<br>"; + + return $tooltip; + } + + 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($this->var->view_dir.'/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($this->var->view_dir.'/file_info', $this->data); + $this->load->view('footer', $this->data); + } + } + + function _non_existent() + { + $this->data["title"] .= " - Not Found"; + $this->output->set_status_header(404); + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/non_existent', $this->data); + $this->load->view('footer', $this->data); + } + + function _show_url($ids, $lexer) + { + $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( + "ids" => $ids, + "lexer" => $lexer + )); + $this->session->set_flashdata("uri", "file/claim_id"); + $this->muser->require_access("basic"); + } + + 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['hash']); + $type = $filedata['mimetype']; + $lexer = $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 ($lexer) { + $redirect = true; + } + } + } + } + + if (static_storage("response_type") == "json") { + return send_json_reply($this->data["urls"]); + } + + if (is_cli_client()) { + $redirect = false; + } + + if ($redirect && count($ids) == 1) { + redirect($this->data['urls'][0], "location", 303); + } else { + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/show_url', $this->data); + $this->load->view('footer', $this->data); + } + } + + function client() + { + $this->data['title'] .= ' - Client'; + + 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->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 (preg_match('#^https?://(.*?)/.*$#', site_url(), $matches) === 1) { + $this->data["domain"] = $matches[1]; + } else { + $this->data["domain"] = "unknown domain"; + } + + if (!is_cli_client()) { + $this->load->view('header', $this->data); + } + $this->load->view($this->var->view_dir.'/client', $this->data); + if (!is_cli_client()) { + $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); + + if ($filedata !== false && $this->mfile->can_highlight($filedata["mimetype"])) { + $this->data["textarea_content"] = file_get_contents($this->mfile->file($filedata["hash"])); + } + } + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/upload_form', $this->data); + if (is_cli_client()) { + $this->client(); + } + $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; + + $filedata = $this->mfile->get_filedata($id); + if (!$filedata) { + show_error("Failed to get file data"); + } + + $cache_key = $filedata['hash'].'_thumb_'.$thumb_size; + + $thumb = cache_function($cache_key, 100, function() use ($id, $thumb_size){ + $CI =& get_instance(); + $thumb = $CI->mfile->makeThumb($id, $thumb_size, IMAGETYPE_JPEG); + + if ($thumb === false) { + show_error("Failed to generate thumbnail"); + } + + 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(); + + $query = $this->db->query(" + SELECT `id`, `filename`, `mimetype`, `date`, `hash`, `filesize` + FROM files + WHERE user = ? + AND mimetype IN ('image/jpeg', 'image/png', 'image/gif') + ORDER BY date DESC + ", array($user))->result_array(); + + foreach($query as $key => $item) { + if (!$this->mfile->valid_id($item["id"])) { + unset($query[$key]); + continue; + } + $query[$key]["tooltip"] = $this->_tooltip_for_image($item); + } + + $this->data["items"] = $query; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/upload_history_thumbnails', $this->data); + $this->load->view('footer', $this->data); + } + + function upload_history() + { + $this->muser->require_access("apikey"); + + $user = $this->muser->get_userid(); + + $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); + } + + $order = is_cli_client() ? "ASC" : "DESC"; + + $items = $this->db->query(" + SELECT ".implode(",", array_keys($fields))." + FROM files + WHERE user = ? + ", array($user))->result_array(); + + $query = $this->db->query(" + SELECT m.url_id id, sum(f.filesize) filesize, m.date, '' hash, '' mimetype, concat(count(*), ' file(s)') filename + FROM multipaste m + JOIN multipaste_file_map mfm ON m.multipaste_id = mfm.multipaste_id + JOIN files f ON f.id = mfm.file_url_id + WHERE m.user_id = ? + GROUP BY m.url_id + ", array($user))->result_array(); + + $items = array_merge($items, $query); + uasort($items, function($a, $b) use ($order) { + if ($order == "ASC") { + return $a["date"] - $b["date"]; + } else { + return $b["date"] - $a["date"]; + } + }); + + if (static_storage("response_type") == "json") { + return send_json_reply($items); + } + + foreach($items as $key => $item) { + $items[$key]["filesize"] = format_bytes($item["filesize"]); + if (is_cli_client()) { + // Keep track of longest string to pad plaintext output correctly + foreach($fields as $length_key => $value) { + $len = mb_strlen($items[$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["items"] = $items; + $this->data["lengths"] = $lengths; + $this->data["fields"] = $fields; + $this->data["total_size"] = format_bytes($total_size["sum"]); + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/upload_history', $this->data); + $this->load->view('footer', $this->data); + } + + function do_delete() + { + $this->muser->require_access("apikey"); + + $ids = $this->input->post("ids"); + $userid = $this->muser->get_userid(); + $errors = array(); + $deleted = array(); + $deleted_count = 0; + $total_count = 0; + + if (!$ids || !is_array($ids)) { + show_error("No IDs specified"); + } + + foreach ($ids as $id) { + $total_count++; + $next = false; + + foreach (array($this->mfile, $this->mmultipaste) as $model) { + if ($model->id_exists($id)) { + if ($model->get_owner($id) !== $userid) { + $errors[] = array( + "id" => $id, + "reason" => "wrong owner", + ); + continue; + } + if ($model->delete_id($id)) { + $deleted[] = $id; + $deleted_count++; + $next = true; + } else { + $errors[] = array( + "id" => $id, + "reason" => "unknown error", + ); + } + } + } + + if ($next) { + continue; + } + + $errors[] = array( + "id" => $id, + "reason" => "doesn't exist", + ); + } + + if (static_storage("response_type") == "json") { + return send_json_reply(array( + "errors" => $errors, + "deleted" => $deleted, + "total_count" => $total_count, + "deleted_count" => $deleted_count, + )); + } + + $this->data["errors"] = $errors; + $this->data["deleted_count"] = $deleted_count; + $this->data["total_count"] = $total_count; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'/deleted', $this->data); + $this->load->view('footer', $this->data); + } + + function do_multipaste() + { + $this->muser->require_access("apikey"); + + $ids = $this->input->post("ids"); + $errors = array(); + + if (!$ids || !is_array($ids)) { + show_error("No IDs specified"); + } + + if (count(array_unique($ids)) != count($ids)) { + show_error("Duplicate IDs are not supported"); + } + + foreach ($ids as $id) { + if (!$this->mfile->id_exists($id)) { + $errors[] = array( + "id" => $id, + "reason" => "doesn't exist", + ); + } + + $filedata = $this->mfile->get_filedata($id); + if ($filedata["user"] != $this->muser->get_userid()) { + $errors[] = array( + "id" => $id, + "reason" => "not owned by you", + ); + } + } + + if (!empty($errors)) { + $errorstring = ""; + foreach ($errors as $error) { + $errorstring .= $error["id"]." ".$error["reason"]."<br>\n"; + } + show_error($errorstring); + } + + $limits = $this->muser->get_upload_id_limits(); + $url_id = $this->mmultipaste->new_id($limits[0], $limits[1]); + + $multipaste_id = $this->mmultipaste->get_multipaste_id($url_id); + assert($multipaste_id !== false); + + foreach ($ids as $id) { + $this->db->insert("multipaste_file_map", array( + "file_url_id" => $id, + "multipaste_id" => $multipaste_id, + )); + } + + return $this->_show_url(array($url_id), false); + } + + function delete() + { + $this->muser->require_access("apikey"); + + if (!is_cli_client()) { + show_error("Not a listed cli client, please use the history to delete uploads.\n", 403); + } + + $id = $this->uri->segment(3); + $this->data["id"] = $id; + $userid = $this->muser->get_userid(); + + foreach (array($this->mfile, $this->mmultipaste) as $model) { + if ($model->id_exists($id)) { + if ($model->get_owner($id) !== $userid) { + echo "You don't own this file\n"; + return; + } + if ($model->delete_id($id)) { + echo "$id has been deleted.\n"; + } else { + echo "Deletion failed. Unknown error\n"; + } + return; + } + } + + show_error("Unknown ID '$id'.", 404); + } + + // Handle pastes + // TODO: merge with do_upload and also merge the forms + // TODO: add support for multiple textareas (+ view) + function do_paste() + { + // stateful clients get a cookie to claim the ID later + // don't force them to log in just yet + if (!stateful_client()) { + $this->muser->require_access(); + } + + $content = $this->input->post("content"); + $filesize = strlen($content); + $filename = "stdin"; + + if (!$content) { + show_error("Nothing was pasted, content is empty.", 400); + } + + if ($filesize > $this->config->item('upload_max_size')) { + show_error("Error while uploading: File too big", 413); + } + + $limits = $this->muser->get_upload_id_limits(); + $id = $this->mfile->new_id($limits[0], $limits[1]); + $hash = md5($content); + + $folder = $this->mfile->folder($hash); + file_exists($folder) || mkdir ($folder); + $file = $this->mfile->file($hash); + + file_put_contents($file, $content); + $this->mfile->add_file($hash, $id, $filename); + $this->_show_url(array($id), false); + } + + // Handles uploaded files + function do_upload() + { + // stateful clients get a cookie to claim the ID later + // don't force them to log in just yet + if (!stateful_client()) { + $this->muser->require_access("basic"); + } + + $ids = array(); + + $extension = $this->input->post('extension'); + $multipaste = $this->input->post('multipaste'); + + $files = getNormalizedFILES(); + + if (empty($files)) { + show_error("No file was uploaded or unknown error occured."); + } + + // Check for errors before doing anything + // First error wins and is displayed, these shouldn't happen that often anyway. + foreach ($files as $key => $file) { + // getNormalizedFILES() removes any file with error == 4 + if ($file['error'] !== UPLOAD_ERR_OK) { + // ERR_OK only for completeness, condition above ignores it + $errors = array( + UPLOAD_ERR_OK => "There is no error, the file uploaded with success", + UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini", + UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", + UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded", + UPLOAD_ERR_NO_FILE => "No file was uploaded", + UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder", + UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk", + UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload", + ); + + $msg = "Unknown error."; + + if (isset($errors[$file['error']])) { + $msg = $errors[$file['error']]; + } else { + $msg = "Unknown error code: ".$file['error'].". Please report a bug."; + } + + show_error("Error while uploading: ".$msg, 400); + } + + $filesize = filesize($file['tmp_name']); + if ($filesize > $this->config->item('upload_max_size')) { + show_error("Error while uploading: File too big", 413); + } + } + + foreach ($files as $key => $file) { + $limits = $this->muser->get_upload_id_limits(); + $id = $this->mfile->new_id($limits[0], $limits[1]); + $hash = md5_file($file['tmp_name']); + + // work around a curl bug and allow the client to send the real filename base64 encoded + // TODO: this interface currently sets the same filename for every file if you use multiupload + $filename = $this->input->post("filename"); + if ($filename !== false) { + $filename = base64_decode($filename, true); + } + + // fall back if base64_decode failed + if ($filename === false) { + $filename = $file['name']; + } + + $filename = trim($filename, "\r\n\0\t\x0B"); + + $folder = $this->mfile->folder($hash); + file_exists($folder) || mkdir ($folder); + $file_path = $this->mfile->file($hash); + + move_uploaded_file($file['tmp_name'], $file_path); + $this->mfile->add_file($hash, $id, $filename); + $ids[] = $id; + } + + if ($multipaste !== false) { + $multipaste_url_id = $this->mmultipaste->new_id($limits[0], $limits[1]); + + $multipaste_id = $this->mmultipaste->get_multipaste_id($multipaste_url_id); + assert($multipaste_id !== false); + + foreach ($ids as $id) { + $this->db->insert("multipaste_file_map", array( + "file_url_id" => $id, + "multipaste_id" => $multipaste_id, + )); + } + $ids[] = $multipaste_url_id; + } + + $this->_show_url($ids, $extension); + } + + function claim_id() + { + $this->muser->require_access(); + + $last_upload = $this->session->userdata("last_upload"); + + if ($last_upload === false) { + show_error("Failed to get last upload data"); + } + + $ids = $last_upload["ids"]; + $errors = array(); + + assert(is_array($ids)); + + foreach ($ids as $key => $id) { + $filedata = $this->mfile->get_filedata($id); + + if ($filedata["user"] != 0) { + $errors[] = $id; + } + + $this->mfile->adopt($id); + } + + if (!empty($errors)) { + show_error("Someone already owns '".implode(", ", $errors)."', can't reassign."); + } + + $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"] = '<p>Contact info not available.</p>'; + } + + $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 <function name>` + */ + + // Removes old files + function cron() + { + if (!$this->input->is_cli_request()) return; + + // 0 age disables age checks + 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->mfile->delete_id($row["id"]); + continue; + } + + if ($row["user"] == 0 || filesize($file) > $small_upload_size) { + if (filemtime($file) < $oldest_time) { + unlink($file); + $this->mfile->delete_hash($row["hash"]); + } else { + $this->mfile->delete_id($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->mfile->delete_hash($hash); + echo "removed hash \"$hash\"\n"; + } + + function update_file_metadata() + { + 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))); + $mimetype = $this->mfile->mimetype($this->mfile->file($hash)); + + $this->db->query(" + UPDATE files + SET filesize = ?, mimetype = ? + WHERE hash = ? + ", array($filesize, $mimetype, $hash)); + } + } + } +} + +# vim: set noet: diff --git a/application/controllers/tools.php b/application/controllers/tools.php new file mode 100644 index 000000000..b80dc5024 --- /dev/null +++ b/application/controllers/tools.php @@ -0,0 +1,45 @@ +<?php +/* + * Copyright 2014 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Tools extends MY_Controller { + + function __construct() + { + parent::__construct(); + + $this->load->model('mfile'); + if (!$this->input->is_cli_request()) { + show_error("This can only be called via CLI"); + } + } + + function index() + { + echo "php index.php <controller> <function> [arguments]\n"; + echo "\n"; + echo "Functions:\n"; + echo " file cron Cronjob\n"; + echo " file nuke_id <ID> Nukes all IDs sharing the same hash\n"; + echo " user cron Cronjob\n"; + echo " tools update_database Update/Initialise the database\n"; + echo "\n"; + echo "Functions that shouldn't have to be run:\n"; + echo " file clean_stale_files Remove files without database entries\n"; + echo " file update_file_metadata Update filesize and mimetype in database\n"; + exit; + } + + function update_database() + { + $this->load->library('migration'); + if ( ! $this->migration->current()) { + show_error($this->migration->error_string()); + } + } +} diff --git a/application/controllers/user.php b/application/controllers/user.php new file mode 100644 index 000000000..079f1665c --- /dev/null +++ b/application/controllers/user.php @@ -0,0 +1,516 @@ +<?php +/* + * Copyright 2012-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class User extends MY_Controller { + protected $json_enabled_functions = array( + "create_apikey", + "apikeys", + ); + + + function __construct() + { + parent::__construct(); + + $this->var->view_dir = "user/"; + } + + function index() + { + if ($this->input->is_cli_request()) { + $this->load->library("../controllers/tools"); + return $this->tools->index(); + } + + $this->data["username"] = $this->muser->get_username(); + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'index', $this->data); + $this->load->view('footer', $this->data); + } + + function test_login() + { + $username = $this->input->post('username'); + $password = $this->input->post('password'); + + if ($this->muser->login($username, $password)) { + $this->output->set_status_header(204); + } else { + $this->output->set_status_header(401); + } + } + + function login() + { + $this->muser->require_session(); + $this->session->keep_flashdata("uri"); + + if ($this->input->post('process') !== false) { + $username = $this->input->post('username'); + $password = $this->input->post('password'); + + $result = $this->muser->login($username, $password); + + if ($result !== true) { + $this->data['login_error'] = true; + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'login', $this->data); + $this->load->view('footer', $this->data); + } else { + $uri = $this->session->flashdata("uri"); + if ($uri) { + redirect($uri); + } else { + redirect("/"); + } + } + } else { + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'login', $this->data); + $this->load->view('footer', $this->data); + } + } + + function create_apikey() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $comment = $this->input->post("comment"); + $comment = $comment === false ? "" : $comment; + $access_level = $this->input->post("access_level"); + + if ($access_level === false) { + $access_level = "apikey"; + } + + $valid_levels = $this->muser->get_access_levels(); + if (array_search($access_level, $valid_levels) === false) { + show_error("Invalid access levels requested."); + } + + if (strlen($comment) > 255) { + show_error("Comment may only be 255 chars long."); + } + + $key = random_alphanum(32); + + $this->db->query(" + INSERT INTO `apikeys` + (`key`, `user`, `comment`, `access_level`) + VALUES (?, ?, ?, ?) + ", array($key, $userid, $comment, $access_level)); + + if (static_storage("response_type") == "json") { + return send_json_reply(array("new_key" => $key)); + } + + if (is_cli_client()) { + echo "$key\n"; + } else { + redirect("user/apikeys"); + } + } + + function delete_apikey() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + $key = $this->input->post("key"); + + $this->db->query(" + DELETE FROM `apikeys` + WHERE `user` = ? + AND `key` = ? + ", array($userid, $key)); + + redirect("user/apikeys"); + } + + function apikeys() + { + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + + $query = $this->db->query(" + SELECT `key`, UNIX_TIMESTAMP(`created`) `created`, `comment`, `access_level` + FROM `apikeys` + WHERE `user` = ? order by created desc + ", array($userid))->result_array(); + + if (static_storage("response_type") == "json") { + return send_json_reply($query); + } + + $this->data["query"] = $query; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'apikeys', $this->data); + $this->load->view('footer', $this->data); + } + + function create_invitation_key() + { + $this->duser->require_implemented("can_register_new_users"); + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + + $query = $this->db->query(" + SELECT count(*) count + FROM `actions` + WHERE `user` = ? + AND `action` = 'invitation' + ", array($userid))->row_array(); + + if ($query["count"] + 1 > 3) { + show_error("You can't create more invitation keys at this time."); + } + + $key = random_alphanum(12, 16); + + $this->db->query(" + INSERT INTO `actions` + (`key`, `user`, `date`, `action`) + VALUES (?, ?, ?, 'invitation') + ", array($key, $userid, time())); + + redirect("user/invite"); + } + + function invite() + { + $this->duser->require_implemented("can_register_new_users"); + $this->muser->require_access(); + + $userid = $this->muser->get_userid(); + + $query = $this->db->query(" + SELECT `key`, `date` + FROM `actions` + WHERE `user` = ? + AND `action` = 'invitation' + ", array($userid))->result_array(); + + $this->data["query"] = $query; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'invite', $this->data); + $this->load->view('footer', $this->data); + } + + function register() + { + $this->duser->require_implemented("can_register_new_users"); + $key = $this->uri->segment(3); + $process = $this->input->post("process"); + $values = array( + "username" => "", + "email" => "" + ); + $error = array(); + + $query = $this->muser->get_action("invitation", $key); + + $referrer = $query["user"]; + + if ($process !== false) { + $username = $this->input->post("username"); + $email = $this->input->post("email"); + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + + if (!$username || strlen($username) > 32 || !preg_match("/^[a-z0-9]+$/", $username)) { + $error[]= "Invalid username (only up to 32 chars of a-z0-9 are allowed)."; + } else { + if ($this->muser->username_exists($username)) { + $error[] = "Username already exists."; + } + } + + $this->load->helper("email"); + if (!valid_email($email)) { + $error[]= "Invalid email."; + } + + if (!$password || $password != $password_confirm) { + $error[]= "No password or passwords don't match."; + } + + if (empty($error)) { + $this->db->query(" + INSERT INTO users + (`username`, `password`, `email`, `referrer`) + VALUES(?, ?, ?, ?) + ", array( + $username, + $this->muser->hash_password($password), + $email, + $referrer + )); + $this->db->query(" + DELETE FROM actions + WHERE `key` = ? + ", array($key)); + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'registered', $this->data); + $this->load->view('footer', $this->data); + return; + } else { + $values["username"] = $username; + $values["email"] = $email; + } + } + + $this->data["key"] = $key; + $this->data["values"] = $values; + $this->data["error"] = $error; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'register', $this->data); + $this->load->view('footer', $this->data); + } + + // This routes the different steps of a password reset + function reset_password() + { + $this->duser->require_implemented("can_reset_password"); + $key = $this->uri->segment(3); + + if ($_SERVER["REQUEST_METHOD"] == "GET" && $key === false) { + return $this->_reset_password_username_form(); + } + + if ($key === false) { + return $this->_reset_password_send_mail(); + } + + if ($key !== false) { + return $this->_reset_password_form(); + } + } + + // This simply queries the username + function _reset_password_username_form() + { + $this->data['username'] = $this->muser->get_username(); + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'reset_password_username_form', $this->data); + $this->load->view('footer', $this->data); + } + + // This sends a mail to the user containing the reset link + function _reset_password_send_mail() + { + $key = random_alphanum(12, 16); + $username = $this->input->post("username"); + + if (!$this->muser->username_exists($username)) { + show_error("Invalid username"); + } + + $userinfo = $this->db->query(" + SELECT id, email, username + FROM users + WHERE username = ? + ", array($username))->row_array(); + + $this->load->library("email"); + + $this->db->query(" + INSERT INTO `actions` + (`key`, `user`, `date`, `action`) + VALUES (?, ?, ?, 'passwordreset') + ", array($key, $userinfo["id"], time())); + + $admininfo = $this->db->query(" + SELECT email + FROM users + WHERE referrer is null + ORDER BY id asc + LIMIT 1 + ")->row_array(); + + $this->email->from($admininfo["email"]); + $this->email->to($userinfo["email"]); + $this->email->subject("FileBin password reset"); + $this->email->message("" + ."Someone requested a password reset for the account '${userinfo["username"]}'\n" + ."from the IP address '${_SERVER["REMOTE_ADDR"]}'.\n" + ."\n" + ."Please follow this link to reset your password:\n" + .site_url("user/reset_password/$key") + ); + $this->email->send(); + + // don't disclose full email addresses + $this->data["email_domain"] = substr($userinfo["email"], strpos($userinfo["email"], "@") + 1); + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'reset_password_link_sent', $this->data); + $this->load->view('footer', $this->data); + } + + // This displays a form and handles the reset if the form has been filled out correctly + function _reset_password_form() + { + $process = $this->input->post("process"); + $key = $this->uri->segment(3); + $error = array(); + + $query = $this->muser->get_action("passwordreset", $key); + + $userid = $query["user"]; + + if ($process !== false) { + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + + if (!$password || $password != $password_confirm) { + $error[]= "No password or passwords don't match."; + } + + if (empty($error)) { + $this->db->query(" + UPDATE users + SET `password` = ? + WHERE `id` = ? + ", array($this->muser->hash_password($password), $userid)); + $this->db->query(" + DELETE FROM actions + WHERE `key` = ? + ", array($key)); + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'reset_password_success', $this->data); + $this->load->view('footer', $this->data); + return; + } + } + + $this->data["key"] = $key; + $this->data["error"] = $error; + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'reset_password_form', $this->data); + $this->load->view('footer', $this->data); + } + + function profile() + { + $this->muser->require_access(); + + if ($this->input->post("process") !== false) { + $this->_save_profile(); + } + + $this->data["profile_data"] = $this->muser->get_profile_data(); + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'profile', $this->data); + $this->load->view('footer', $this->data); + } + + private function _save_profile() + { + $this->muser->require_access(); + + /* + * Key = name of the form field + * Value = function that sanatizes the value and returns it + * TODO: some kind of error handling that doesn't loose correctly filled out fields + */ + $value_processor = array(); + + $value_processor["upload_id_limits"] = function($value) { + $values = explode("-", $value); + + if (!is_array($values) || count($values) != 2) { + show_error("Invalid upload id limit value"); + } + + $lower = intval($values[0]); + $upper = intval($values[1]); + + if ($lower > $upper) { + show_error("lower limit > upper limit"); + } + + if ($lower < 3 || $upper > 64) { + show_error("upper or lower limit out of bounds (3-64)"); + } + + return $lower."-".$upper; + }; + + $data = array(); + foreach (array_keys($value_processor) as $field) { + $value = $this->input->post($field); + + if ($value !== false) { + $data[$field] = $value_processor[$field]($value); + } + } + + if (!empty($data)) { + $this->muser->update_profile($data); + } + + $this->data["alerts"][] = array( + "type" => "success", + "message" => "Changes saved", + ); + + return true; + } + + function logout() + { + $this->muser->logout(); + redirect('/'); + } + + function hash_password() + { + $process = $this->input->post("process"); + $password = $this->input->post("password"); + $password_confirm = $this->input->post("password_confirm"); + $this->data["hash"] = false; + $this->data["password"] = $password; + + if ($process !== false) { + if (!$password || $password != $password_confirm) { + $error[]= "No password or passwords don't match."; + } else { + $this->data["hash"] = $this->muser->hash_password($password); + } + } + + $this->load->view('header', $this->data); + $this->load->view($this->var->view_dir.'hash_password', $this->data); + $this->load->view('footer', $this->data); + } + + function cron() + { + if (!$this->input->is_cli_request()) return; + + if ($this->config->item('actions_max_age') == 0) return; + + $oldest_time = (time() - $this->config->item('actions_max_age')); + + $this->db->query(" + DELETE FROM actions + WHERE date < ? + ", array($oldest_time)); + } +} diff --git a/application/controllers/welcome.php b/application/controllers/welcome.php deleted file mode 100644 index 21bef43d9..000000000 --- a/application/controllers/welcome.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); - -class Welcome extends CI_Controller { - - /** - * Index Page for this controller. - * - * Maps to the following URL - * http://example.com/index.php/welcome - * - or - - * http://example.com/index.php/welcome/index - * - or - - * Since this controller is set as the default controller in - * config/routes.php, it's displayed at http://example.com/ - * - * So any other public methods not prefixed with an underscore will - * map to /index.php/welcome/<method_name> - * @see http://codeigniter.com/user_guide/general/urls.html - */ - public function index() - { - $this->load->view('welcome_message'); - } -} - -/* End of file welcome.php */ -/* Location: ./application/controllers/welcome.php */
\ No newline at end of file diff --git a/application/core/MY_Controller.php b/application/core/MY_Controller.php new file mode 100644 index 000000000..a98245b1b --- /dev/null +++ b/application/core/MY_Controller.php @@ -0,0 +1,119 @@ +<?php +/* + * Copyright 2009-2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class MY_Controller extends CI_Controller { + public $data = array(); + public $var; + + protected $json_enabled_functions = array( + ); + + function __construct() + { + parent::__construct(); + + $this->var = new StdClass(); + $csrf_protection = true; + + // check if DB is up to date + if (!$this->input->is_cli_request()) { + if (!$this->db->table_exists('migrations')){ + show_error("Database not initialized. Can't find migrations table. Please run the migration script."); + } else { + $this->config->load("migration", true); + $target_version = $this->config->item("migration_version", "migration"); + + // TODO: wait 20 seconds for an update so requests don't get lost for short updates? + $row = $this->db->get('migrations')->row(); + + $current_version = $row ? $row->version : 0; + if ($current_version != $target_version) { + show_error("Database version is $current_version, we want $target_version. Please run the migration script."); + } + } + } + + $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')); + + // TODO: proper accept header handling or is this enough? + if (isset($_SERVER["HTTP_ACCEPT"])) { + if ($_SERVER["HTTP_ACCEPT"] == "application/json") { + static_storage("response_type", "json"); + } + } + + // Allow for easier testing in browser + if ($this->input->get("json") !== false) { + static_storage("response_type", "json"); + } + + if (static_storage("response_type") == "json" && ! in_array($this->uri->rsegment(2), $this->json_enabled_functions)) { + show_error("Function not JSON enabled"); + } + + if ($this->input->post("apikey") !== false) { + /* This relies on the authentication code always verifying the supplied + * apikey. If the key is not verified/logged in an attacker could simply + * add an empty "apikey" field to the CSRF form to circumvent the + * protection. If we always log in if a key is supplied we can ensure + * that an attacker (and the victim since they get a cookie) can only + * access the attacker's account. + */ + $csrf_protection = false; + } + + $uri_start = $this->uri->rsegment(1)."/".$this->uri->rsegment(2); + $csrf_whitelisted_handlers = array( + "always" => array( + /* Whitelist the upload pages because they don't cause harm and a user + * might keep the upload page open for more than csrf_expire seconds + * and we don't want to annoy them when they upload a big file and the + * CSRF check fails. + */ + "file/do_upload", + "file/do_paste", + ), + "cli_client" => array( + "file/do_delete", + "file/delete", + "file/upload_history", + "user/create_apikey", + "file/get_max_size", + ), + ); + if (in_array($uri_start, $csrf_whitelisted_handlers["always"])) { + $csrf_protection = false; + } + + if (is_cli_client() && in_array($uri_start, $csrf_whitelisted_handlers["cli_client"])) { + $csrf_protection = false; + } + + if ($csrf_protection && !$this->input->is_cli_request()) { + // 2 functions for accessing config options, really? + $this->config->set_item('csrf_protection', true); + config_item("csrf_protection", true); + $this->security->__construct(); + $this->security->csrf_verify(); + } + + if ($this->config->item("environment") == "development" && static_storage("response_type") != "json") { + $this->output->enable_profiler(true); + } + + $this->data['title'] = "FileBin"; + + $this->load->model("muser"); + $this->data["user_logged_in"] = $this->muser->logged_in(); + } +} diff --git a/application/errors/error_404.php b/application/errors/error_404.php index 792726a67..cfe923d63 100644 --- a/application/errors/error_404.php +++ b/application/errors/error_404.php @@ -1,62 +1,3 @@ -<!DOCTYPE html> -<html lang="en"> -<head> -<title>404 Page Not Found</title> -<style type="text/css"> - -::selection{ background-color: #E13300; color: white; } -::moz-selection{ background-color: #E13300; color: white; } -::webkit-selection{ background-color: #E13300; color: white; } - -body { - background-color: #fff; - margin: 40px; - font: 13px/20px normal Helvetica, Arial, sans-serif; - color: #4F5155; -} - -a { - color: #003399; - background-color: transparent; - font-weight: normal; -} - -h1 { - color: #444; - background-color: transparent; - border-bottom: 1px solid #D0D0D0; - font-size: 19px; - font-weight: normal; - margin: 0 0 14px 0; - padding: 14px 15px 10px 15px; -} - -code { - font-family: Consolas, Monaco, Courier New, Courier, monospace; - font-size: 12px; - background-color: #f9f9f9; - border: 1px solid #D0D0D0; - color: #002166; - display: block; - margin: 14px 0 14px 0; - padding: 12px 10px 12px 10px; -} - -#container { - margin: 10px; - border: 1px solid #D0D0D0; - -webkit-box-shadow: 0 0 8px #D0D0D0; -} - -p { - margin: 12px 15px 12px 15px; -} -</style> -</head> -<body> - <div id="container"> - <h1><?php echo $heading; ?></h1> - <?php echo $message; ?> - </div> -</body> -</html>
\ No newline at end of file +<?php +$title = "404 Page Not Found"; +include "application/errors/error_general.php"; diff --git a/application/errors/error_db.php b/application/errors/error_db.php index b396cda9f..255513634 100644 --- a/application/errors/error_db.php +++ b/application/errors/error_db.php @@ -1,62 +1,3 @@ -<!DOCTYPE html> -<html lang="en"> -<head> -<title>Database Error</title> -<style type="text/css"> - -::selection{ background-color: #E13300; color: white; } -::moz-selection{ background-color: #E13300; color: white; } -::webkit-selection{ background-color: #E13300; color: white; } - -body { - background-color: #fff; - margin: 40px; - font: 13px/20px normal Helvetica, Arial, sans-serif; - color: #4F5155; -} - -a { - color: #003399; - background-color: transparent; - font-weight: normal; -} - -h1 { - color: #444; - background-color: transparent; - border-bottom: 1px solid #D0D0D0; - font-size: 19px; - font-weight: normal; - margin: 0 0 14px 0; - padding: 14px 15px 10px 15px; -} - -code { - font-family: Consolas, Monaco, Courier New, Courier, monospace; - font-size: 12px; - background-color: #f9f9f9; - border: 1px solid #D0D0D0; - color: #002166; - display: block; - margin: 14px 0 14px 0; - padding: 12px 10px 12px 10px; -} - -#container { - margin: 10px; - border: 1px solid #D0D0D0; - -webkit-box-shadow: 0 0 8px #D0D0D0; -} - -p { - margin: 12px 15px 12px 15px; -} -</style> -</head> -<body> - <div id="container"> - <h1><?php echo $heading; ?></h1> - <?php echo $message; ?> - </div> -</body> -</html>
\ No newline at end of file +<?php +$title = "Database Error"; +include "application/errors/error_general.php"; diff --git a/application/errors/error_general.php b/application/errors/error_general.php index fd63ce2c5..be495e4f6 100644 --- a/application/errors/error_general.php +++ b/application/errors/error_general.php @@ -1,3 +1,51 @@ +<?php + +// fancy error page only works if we can load helpers +if (class_exists("CI_Controller") && !isset($GLOBALS["is_error_page"])) { + if (!isset($title)) { + $title = "Error"; + } + $GLOBALS["is_error_page"] = true; + + $CI =& get_instance(); + $CI->load->helper("filebin"); + $CI->load->helper("url"); + + if ($CI->input->is_cli_request()) { + is_cli_client(true); + } + + if (static_storage("response_type") == "json") { + $message = str_replace("</p>", "</p>\n", $message); + $array = array( + "status" => "error", + "message" => strip_tags($message), + ); + header('Content-type: application/json'); + echo json_encode($array); + exit(); + } + + if (is_cli_client()) { + $message = str_replace("</p>", "</p>\n", $message); + $message = strip_tags($message); + echo "$heading: $message\n"; + exit(); + } + + include 'application/views/header.php'; + + ?> + <div class="error"> + <h1><?php echo $heading; ?></h1> + <?php echo $message; ?> + </div> + + <?php + include 'application/views/footer.php'; +} else { + // default CI error page +?> <!DOCTYPE html> <html lang="en"> <head> @@ -59,4 +107,6 @@ p { <?php echo $message; ?> </div> </body> -</html>
\ No newline at end of file +</html> +<?php +} diff --git a/application/errors/error_php.php b/application/errors/error_php.php index f085c2037..5f91e07a0 100644 --- a/application/errors/error_php.php +++ b/application/errors/error_php.php @@ -7,4 +7,5 @@ <p>Filename: <?php echo $filepath; ?></p> <p>Line Number: <?php echo $line; ?></p> -</div>
\ No newline at end of file +</div> +<?php exit(); diff --git a/application/helpers/filebin_helper.php b/application/helpers/filebin_helper.php new file mode 100644 index 000000000..e5637ce94 --- /dev/null +++ b/application/helpers/filebin_helper.php @@ -0,0 +1,286 @@ +<?php + +function format_bytes($size) +{ + $suffixes = array('B', 'KiB', 'MiB', 'GiB', 'TiB' , 'PiB' , 'EiB', 'ZiB', 'YiB'); + $boundary = 2048.0; + + for ($suffix_pos = 0; $suffix_pos + 1 < count($suffixes); $suffix_pos++) { + if ($size <= $boundary && $size >= -$boundary) { + break; + } + $size /= 1024.0; + } + + # don't print decimals for bytes + if ($suffix_pos != 0) { + return sprintf("%.2f%s", $size, $suffixes[$suffix_pos]); + } else { + return sprintf("%.0f%s", $size, $suffixes[$suffix_pos]); + } +} + +function even_odd($reset = false) +{ + static $counter = 1; + + if ($reset) { + $counter = 1; + } + + if ($counter++%2 == 0) { + return 'even'; + } else { + return 'odd'; + } +} + +// Source: http://hu.php.net/manual/en/function.str-pad.php#71558 +// This is a multibyte enabled str_pad +function mb_str_pad($ps_input, $pn_pad_length, $ps_pad_string = " ", $pn_pad_type = STR_PAD_RIGHT, $ps_encoding = NULL) +{ + $ret = ""; + + if (is_null($ps_encoding)) + $ps_encoding = mb_internal_encoding(); + + $hn_length_of_padding = $pn_pad_length - mb_strlen($ps_input, $ps_encoding); + $hn_psLength = mb_strlen($ps_pad_string, $ps_encoding); // pad string length + + if ($hn_psLength <= 0 || $hn_length_of_padding <= 0) { + // Padding string equal to 0: + // + $ret = $ps_input; + } + else { + $hn_repeatCount = floor($hn_length_of_padding / $hn_psLength); // how many times repeat + + if ($pn_pad_type == STR_PAD_BOTH) { + $hs_lastStrLeft = ""; + $hs_lastStrRight = ""; + $hn_repeatCountLeft = $hn_repeatCountRight = ($hn_repeatCount - $hn_repeatCount % 2) / 2; + + $hs_lastStrLength = $hn_length_of_padding - 2 * $hn_repeatCountLeft * $hn_psLength; // the rest length to pad + $hs_lastStrLeftLength = $hs_lastStrRightLength = floor($hs_lastStrLength / 2); // the rest length divide to 2 parts + $hs_lastStrRightLength += $hs_lastStrLength % 2; // the last char add to right side + + $hs_lastStrLeft = mb_substr($ps_pad_string, 0, $hs_lastStrLeftLength, $ps_encoding); + $hs_lastStrRight = mb_substr($ps_pad_string, 0, $hs_lastStrRightLength, $ps_encoding); + + $ret = str_repeat($ps_pad_string, $hn_repeatCountLeft) . $hs_lastStrLeft; + $ret .= $ps_input; + $ret .= str_repeat($ps_pad_string, $hn_repeatCountRight) . $hs_lastStrRight; + } + else { + $hs_lastStr = mb_substr($ps_pad_string, 0, $hn_length_of_padding % $hn_psLength, $ps_encoding); // last part of pad string + + if ($pn_pad_type == STR_PAD_LEFT) + $ret = str_repeat($ps_pad_string, $hn_repeatCount) . $hs_lastStr . $ps_input; + else + $ret = $ps_input . str_repeat($ps_pad_string, $hn_repeatCount) . $hs_lastStr; + } + } + + return $ret; +} + +function is_cli_client($override = null) +{ + static $is_cli = null; + + if ($override !== null) { + $is_cli = $override; + } + + if ($is_cli === null) { + $is_cli = false; + // official client uses "fb-client/$version" as useragent + $clients = array("fb-client", "libcurl", "pyfb", "curl/"); + foreach ($clients as $client) { + if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], $client) !== false) { + $is_cli = true; + } + } + } + return $is_cli; +} + +function random_alphanum($min_length, $max_length = null) +{ + $random = ''; + $char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + $char_list .= "abcdefghijklmnopqrstuvwxyz"; + $char_list .= "1234567890"; + + if ($max_length === null) { + $max_length = $min_length; + } + $length = mt_rand($min_length, $max_length); + + for($i = 0; $i < $max_length; $i++) { + if (strlen($random) == $length) break; + $random .= substr($char_list, mt_rand(0, strlen($char_list) - 1), 1); + } + return $random; +} + +function link_with_mtime($file) +{ + $link = base_url($file); + + if (file_exists(FCPATH.$file)) { + $link .= "?".filemtime(FCPATH.$file); + } + + return $link; +} + +function include_js($file) +{ + static $included = array(); + if (in_array($file, $included) || $file === null) { + return ""; + } + return "<script src=\"".link_with_mtime($file)."\"></script>\n"; +} + +// kind of hacky, but works well enough for now +function register_js_include($file, $return = false) +{ + static $list = ""; + $list .= include_js($file); + if ($return) { + return $list; + } +} + +function include_registered_js() +{ + return register_js_include(null, true); +} + +function handle_etag($etag) +{ + $etag = strtolower($etag); + $modified = true; + + if(isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + $oldtag = trim(strtolower($_SERVER['HTTP_IF_NONE_MATCH']), '"'); + if($oldtag == $etag) { + $modified = false; + } else { + $modified = true; + } + } + + header('Etag: "'.$etag.'"'); + + if (!$modified) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } +} + +// Reference: http://php.net/manual/en/features.file-upload.multiple.php#109437 +// This is a little different because we don't care about the fieldname +function getNormalizedFILES() +{ + $newfiles = array(); + $ret = array(); + + foreach($_FILES as $fieldname => $fieldvalue) + foreach($fieldvalue as $paramname => $paramvalue) + foreach((array)$paramvalue as $index => $value) + $newfiles[$fieldname][$index][$paramname] = $value; + + $i = 0; + foreach ($newfiles as $fieldname => $field) { + foreach ($field as $file) { + // skip empty fields + if ($file["error"] === 4) { + continue; + } + $ret[$i] = $file; + $ret[$i]["formfield"] = $fieldname; + $i++; + } + } + + return $ret; +} + +// Allow simple checking inside views +function auth_driver_function_implemented($function) +{ + static $result = array(); + if (isset($result[$function])) { + return $result[$function]; + } + + $CI =& get_instance(); + $CI->load->driver("duser"); + $result[$function] = $CI->duser->is_implemented($function);; + + return $result[$function]; +} + +function send_json_reply($array, $status = "success") +{ + $reply = array(); + $reply["status"] = $status; + $reply["data"] = $array; + + $CI =& get_instance(); + $CI->output->set_content_type('application/json'); + $CI->output->set_output(json_encode($reply)); +} + +function static_storage($key, $value = null) +{ + static $storage = array(); + + if ($value !== null) { + $storage[$key] = $value; + } + + if (!isset($storage[$key])) { + $storage[$key] = null; + } + + return $storage[$key]; +} + +function stateful_client() +{ + $CI =& get_instance(); + + if ($CI->input->post("apikey")) { + return false; + } + + if (is_cli_client()) { + return false; + } + + return true; +} + +/** + * Cache the result of the function call + * @param key cache key to use + * @param ttl time to live for the cache entry + * @param function function to call + * @return return value of function (will be cached) + */ +function cache_function($key, $ttl, $function) +{ + $CI =& get_instance(); + $CI->load->driver('cache', array('adapter' => $CI->config->item("cache_backend"))); + if (! $content = $CI->cache->get($key)) { + $content = $function(); + $CI->cache->save($key, $content, $ttl); + } + return $content; +} + +# vim: set noet: diff --git a/application/libraries/Ddownload/Ddownload.php b/application/libraries/Ddownload/Ddownload.php new file mode 100644 index 000000000..808dfe776 --- /dev/null +++ b/application/libraries/Ddownload/Ddownload.php @@ -0,0 +1,34 @@ +<?php +/* + * Copyright 2013 Pierre Schmitz <pierre@archlinux.de> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +abstract class Ddownload_Driver extends CI_Driver { + + abstract public function serveFile($file, $filename, $type); +} + +class Ddownload extends CI_Driver_Library { + + protected $_adapter = null; + + protected $valid_drivers = array( + 'ddownload_php', 'ddownload_nginx', 'ddownload_lighttpd' + ); + + function __construct() + { + $CI =& get_instance(); + + $this->_adapter = $CI->config->item('download_driver'); + } + + public function serveFile($file, $filename, $type) + { + $this->{$this->_adapter}->serveFile($file, $filename, $type); + } +} diff --git a/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php b/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php new file mode 100644 index 000000000..780f60838 --- /dev/null +++ b/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php @@ -0,0 +1,26 @@ +<?php +/* + * Copyright 2013 Pierre Schmitz <pierre@archlinux.de> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Ddownload_lighttpd extends Ddownload_Driver { + + public function serveFile($file, $filename, $type) + { + $CI =& get_instance(); + $upload_path = $CI->config->item('upload_path'); + + if (strpos($file, $upload_path) !== 0) { + show_error('Invalid file path'); + } + + header('Content-disposition: inline; filename="'.$filename."\"\n"); + header('Content-Type: '.$type."\n"); + header('X-Sendfile: '.$file."\n"); + } + +} diff --git a/application/libraries/Ddownload/drivers/Ddownload_nginx.php b/application/libraries/Ddownload/drivers/Ddownload_nginx.php new file mode 100644 index 000000000..2410df4d4 --- /dev/null +++ b/application/libraries/Ddownload/drivers/Ddownload_nginx.php @@ -0,0 +1,29 @@ +<?php +/* + * Copyright 2013 Pierre Schmitz <pierre@archlinux.de> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Ddownload_nginx extends Ddownload_Driver { + + public function serveFile($file, $filename, $type) + { + $CI =& get_instance(); + $upload_path = $CI->config->item('upload_path'); + $download_location = $CI->config->item('download_nginx_location'); + + if (strpos($file, $upload_path) === 0) { + $file_path = substr($file, strlen($upload_path)); + } else { + show_error('Invalid file path'); + } + + header('Content-disposition: inline; filename="'.$filename."\"\n"); + header('Content-Type: '.$type."\n"); + header('X-Accel-Redirect: '.$download_location.$file_path."\n"); + } + +} diff --git a/application/libraries/Ddownload/drivers/Ddownload_php.php b/application/libraries/Ddownload/drivers/Ddownload_php.php new file mode 100644 index 000000000..344db53f0 --- /dev/null +++ b/application/libraries/Ddownload/drivers/Ddownload_php.php @@ -0,0 +1,111 @@ +<?php +/* + * Copyright 2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Ddownload_php extends Ddownload_Driver { + + // Original source: http://www.phpfreaks.com/forums/index.php?topic=198274.msg895468#msg895468 + public function serveFile($file, $filename, $type) + { + $fp = @fopen($file, 'r'); + + $size = filesize($file); // File size + $length = $size; // Content length + $start = 0; // Start byte + $end = $size - 1; // End byte + // Now that we've gotten so far without errors we send the accept range header + /* At the moment we only support single ranges. + * Multiple ranges requires some more work to ensure it works correctly + * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 + * + * Multirange support annouces itself with: + * header('Accept-Ranges: bytes'); + * + * Multirange content must be sent with multipart/byteranges mediatype, + * (mediatype = mimetype) + * as well as a boundry header to indicate the various chunks of data. + */ + header("Accept-Ranges: 0-$length"); + // header('Accept-Ranges: bytes'); + // multipart/byteranges + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 + if (isset($_SERVER['HTTP_RANGE'])) + { + $c_start = $start; + $c_end = $end; + // Extract the range string + list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2); + // Make sure the client hasn't sent us a multibyte range + if (strpos($range, ',') !== false) + { + // (?) Shoud this be issued here, or should the first + // range be used? Or should the header be ignored and + // we output the whole content? + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + // (?) Echo some info to the client? + exit; + } + // If the range starts with an '-' we start from the beginning + // If not, we forward the file pointer + // And make sure to get the end byte if spesified + if ($range{0} == '-') + { + // The n-number of the last bytes is requested + $c_start = $size - substr($range, 1); + } + else + { + $range = explode('-', $range); + $c_start = $range[0]; + $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size; + } + /* Check the range and make sure it's treated according to the specs. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ + // End bytes can not be larger than $end. + $c_end = ($c_end > $end) ? $end : $c_end; + // Validate the requested range and return an error if it's not correct. + if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) + { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + // (?) Echo some info to the client? + exit; + } + $start = $c_start; + $end = $c_end; + $length = $end - $start + 1; // Calculate new content length + fseek($fp, $start); + header('HTTP/1.1 206 Partial Content'); + // Notify the client the byte range we'll be outputting + header("Content-Range: bytes $start-$end/$size"); + } + header("Content-Length: $length"); + header("Content-disposition: inline; filename=\"".$filename."\"\n"); + header("Content-Type: ".$type."\n"); + + // Start buffered download + $buffer = 1024 * 8; + while(!feof($fp) && ($p = ftell($fp)) <= $end) + { + if ($p + $buffer > $end) + { + // In case we're only outputtin a chunk, make sure we don't + // read past the length + $buffer = $end - $p + 1; + } + set_time_limit(0); // Reset time limit for big files + echo fread($fp, $buffer); + flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. + } + + fclose($fp); + } + +} diff --git a/application/libraries/Duser/Duser.php b/application/libraries/Duser/Duser.php new file mode 100644 index 000000000..07a16190c --- /dev/null +++ b/application/libraries/Duser/Duser.php @@ -0,0 +1,112 @@ +<?php +/* + * Copyright 2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +abstract class Duser_Driver extends CI_Driver { + + // List of optional functions that are implemented + // + // Possible values are: + // - can_register_new_users (only supported with the DB driver!) + // - can_reset_password (only supported with the DB driver!) + public $optional_functions = array(); + + /* + * The returned array should contain the following keys: + * - username string + * - userid INT > 0 + * + * @param username + * @param password + * @return mixed array on success, false on failure + */ + abstract public function login($username, $password); + + /* + * @param username + * @return boolean true is username exists, false otherwise + */ + public function username_exists($username) { + return null; + } + + /* + * @param userid + * @return string email address of the user + */ + public function get_email($userid) { + return null; + } +} + +class Duser extends CI_Driver_Library { + + protected $_adapter = null; + + protected $valid_drivers = array( + 'duser_db', 'duser_ldap', 'duser_fluxbb' + ); + + function __construct() + { + $CI =& get_instance(); + + $this->_adapter = $CI->config->item("authentication_driver"); + } + + // require an optional function to be implemented + public function require_implemented($function) { + if (!$this->is_implemented($function)) { + show_error("" + ."Optional function '".$function."' not implemented in user adapter '".$this->_adapter."'. " + ."Requested functionally unavailable."); + } + } + + // check if an optional function is implemented + public function is_implemented($function) { + if (in_array($function, $this->{$this->_adapter}->optional_functions)) { + return true; + } + + return false; + } + + public function login($username, $password) + { + $login_info = $this->{$this->_adapter}->login($username, $password); + if ($login_info === false) { + return false; + } + + $CI =& get_instance(); + + $CI->session->set_userdata(array( + 'logged_in' => true, + 'username' => $login_info["username"], + 'userid' => $login_info["userid"], + 'access_level' => 'full', + )); + + return true; + } + + public function username_exists($username) + { + if ($username === false) { + return false; + } + + return $this->{$this->_adapter}->username_exists($username); + } + + public function get_email($userid) + { + return $this->{$this->_adapter}->get_email($userid); + } +} diff --git a/application/libraries/Duser/drivers/Duser_db.php b/application/libraries/Duser/drivers/Duser_db.php new file mode 100644 index 000000000..a58b5a298 --- /dev/null +++ b/application/libraries/Duser/drivers/Duser_db.php @@ -0,0 +1,79 @@ +<?php +/* + * Copyright 2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Duser_db extends Duser_Driver { + + /* FIXME: If you use this driver as a template, remove can_reset_password + * and can_register_new_users. These features require the DB driver and + * will NOT work with other drivers. + */ + public $optional_functions = array( + 'can_reset_password', + 'can_register_new_users', + ); + + public function login($username, $password) + { + $CI =& get_instance(); + + $query = $CI->db->query(' + SELECT username, id, password + FROM `users` + WHERE `username` = ? + ', array($username))->row_array(); + + if (empty($query)) { + return false; + } + + if (crypt($password, $query["password"]) === $query["password"]) { + return array( + "username" => $username, + "userid" => $query["id"] + ); + } else { + return false; + } + } + + public function username_exists($username) + { + $CI =& get_instance(); + + $query = $CI->db->query(" + SELECT id + FROM users + WHERE username = ? + ", array($username)); + + if ($query->num_rows() > 0) { + return true; + } else { + return false; + } + } + + public function get_email($userid) + { + $CI =& get_instance(); + + $query = $CI->db->query(" + SELECT email + FROM users + WHERE id = ? + ", array($userid))->row_array(); + + if (empty($query)) { + show_error("Failed to get email address from db"); + } + + return $query["email"]; + } + +} diff --git a/application/libraries/Duser/drivers/Duser_fluxbb.php b/application/libraries/Duser/drivers/Duser_fluxbb.php new file mode 100644 index 000000000..1790e830b --- /dev/null +++ b/application/libraries/Duser/drivers/Duser_fluxbb.php @@ -0,0 +1,53 @@ +<?php +/* + * Copyright 2013 Pierre Schmitz <pierre@archlinux.de> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Duser_fluxbb extends Duser_Driver { + + private $CI = null; + private $config = array(); + + function __construct() + { + $this->CI =& get_instance(); + $this->config = $this->CI->config->item('auth_fluxbb'); + } + + public function login($username, $password) + { + $query = $this->CI->db->query(' + SELECT username, id + FROM '.$this->config['database'].'.users + WHERE username = ? AND password = ? + ', array($username, sha1($password)))->row_array(); + + if (!empty($query)) { + return array( + 'username' => $query['username'], + 'userid' => $query['id'] + ); + } else { + return false; + } + } + + public function username_exists($username) + { + $query = $this->CI->db->query(' + SELECT id + FROM '.$this->config['database'].'.users + WHERE username = ? + ', array($username)); + + if ($query->num_rows() > 0) { + return true; + } else { + return false; + } + } +} diff --git a/application/libraries/Duser/drivers/Duser_ldap.php b/application/libraries/Duser/drivers/Duser_ldap.php new file mode 100644 index 000000000..1f1581620 --- /dev/null +++ b/application/libraries/Duser/drivers/Duser_ldap.php @@ -0,0 +1,68 @@ +<?php +/* + * Copyright 2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * Contributions by Hannes Rist + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ +class Duser_ldap extends Duser_Driver { + // none supported + public $optional_functions = array(); + + // Original source: http://code.activestate.com/recipes/101525-ldap-authentication/ + public function login($username, $password) { + $CI =& get_instance(); + + $config = $CI->config->item("auth_ldap"); + + if ($username == "" || $password == "") { + return false; + } + + $ds = ldap_connect($config['host'],$config['port']); + if ($ds === false) { + return false; + } + + switch ($config["scope"]) { + case "base": + $r = ldap_read($ds, $config['basedn'], $config["username_field"].'='.$username); + break; + case "one": + $r = ldap_list($ds, $config['basedn'], $config["username_field"].'='.$username); + break; + case "subtree": + $r = ldap_search($ds, $config['basedn'], $config["username_field"].'='.$username); + break; + default: + show_error("Invalid LDAP scope"); + } + if ($r === false) { + return false; + } + + foreach ($config["options"] as $key => $value) { + if (ldap_set_option($ds, $key, $value) === false) { + return false; + } + } + + $result = ldap_get_entries($ds, $r); + if ($result === false || !isset($result[0])) { + return false; + } + + // ignore errors from ldap_bind as it will throw an error if the password is incorrect + if (@ldap_bind($ds, $result[0]['dn'], $password)) { + ldap_unbind($ds); + return array( + "username" => $result[0][$config["username_field"]][0], + "userid" => $result[0][$config["userid_field"]][0] + ); + } + + return false; + } +} diff --git a/application/libraries/MY_Session.php b/application/libraries/MY_Session.php new file mode 100644 index 000000000..0443bca31 --- /dev/null +++ b/application/libraries/MY_Session.php @@ -0,0 +1,38 @@ +<?php +/* + * Copyright 2013 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class MY_Session extends CI_Session { + private $memory_only = false; + + public function __construct() { + $CI =& get_instance(); + $CI->load->helper("filebin"); + + /* Clients using API keys do not need a persistent session since API keys + * should be sent with each request. This reduces database queries and + * prevents us from sending useless cookies. + */ + if (!stateful_client()) { + $this->memory_only = true; + $CI->config->set_item("sess_use_database", false); + } + + parent::__construct(); + } + + public function _set_cookie($cookie_data = NULL) + { + if ($this->memory_only) { + return; + } + + parent::_set_cookie($cookie_data); + + } +} diff --git a/application/libraries/Output_cache.php b/application/libraries/Output_cache.php new file mode 100644 index 000000000..224e9f95a --- /dev/null +++ b/application/libraries/Output_cache.php @@ -0,0 +1,80 @@ +<?php +/* + * Copyright 2014 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +class Output_cache { + private $output_cache = array(); + + /** + * Combine multiple objects for the same view into one + * @param data data to pass to the view + * @param view view path + */ + public function add_merge($data, $view) + { + assert($view !== NULL); + + // combine multiple objects for the same view into one + $count = count($this->output_cache); + if ($count > 0 && $this->output_cache[$count - 1]["view"] === $view) { + $this->output_cache[$count - 1]["data"] = array_merge_recursive($this->output_cache[$count - 1]["data"], $data); + } else { + $this->add($data, $view); + } + } + + /** + * Add some data that will be output directly if view is NULL or passed + * to the view otherweise. + * + * @param data data to pass to view or output + * @param view view path or NULL + */ + public function add($data, $view = NULL) + { + $this->output_cache[] = array( + "view" => $view, + "data" => $data, + ); + } + + /** + * Add a function that will be excuted when render() is called. + * This function is supposed to use render_now() to output data. + * + * @param data_function + */ + public function add_function($data_function) + { + $this->output_cache[] = array( + "view" => NULL, + "data_function" => $data_function, + ); + } + + public function render_now($data, $view = NULL) + { + if ($view !== NULL) { + echo get_instance()->load->view($view, $data, true); + } else { + echo $data; + } + } + + public function render() + { + while ($output = array_shift($this->output_cache)) { + if (isset($output["data_function"])) { + $output["data_function"](); + } else { + $data = $output["data"]; + $this->render_now($data, $output["view"]); + } + } + } +} diff --git a/application/migrations/001_add_files.php b/application/migrations/001_add_files.php new file mode 100644 index 000000000..f1f16ea3a --- /dev/null +++ b/application/migrations/001_add_files.php @@ -0,0 +1,27 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_files extends CI_Migration { + + public function up() + { + $this->db->query(" + CREATE TABLE IF NOT EXISTS `files` ( + `hash` varchar(32) CHARACTER SET ascii NOT NULL, + `id` varchar(6) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + `filename` varchar(256) COLLATE utf8_bin NOT NULL, + `password` varchar(40) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL, + `date` int(11) unsigned NOT NULL, + `mimetype` varchar(255) CHARACTER SET ascii NOT NULL, + PRIMARY KEY (`id`), + KEY `date` (`date`), + KEY `hash` (`hash`,`id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + "); + } + + public function down() + { + $this->dbforge->drop_table('files'); + } +} diff --git a/application/migrations/002_add_users.php b/application/migrations/002_add_users.php new file mode 100644 index 000000000..5675c77e9 --- /dev/null +++ b/application/migrations/002_add_users.php @@ -0,0 +1,46 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_users extends CI_Migration { + + public function up() + { + $this->db->query(" + CREATE TABLE IF NOT EXISTS `users` ( + `id` int(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `username` varchar(32) COLLATE ascii_general_ci NOT NULL, + `password` varchar(60) COLLATE ascii_general_ci NOT NULL, + `email` varchar(255) COLLATE ascii_general_ci NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + "); + + $this->db->query(" + CREATE TABLE IF NOT EXISTS `ci_sessions` ( + `session_id` varchar(40) NOT NULL DEFAULT '0', + `ip_address` varchar(16) NOT NULL DEFAULT '0', + `user_agent` varchar(120) NOT NULL, + `last_activity` int(10) unsigned NOT NULL DEFAULT '0', + `user_data` text NOT NULL, + PRIMARY KEY (`session_id`), + KEY `last_activity_idx` (`last_activity`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + + $this->db->query(" + ALTER TABLE `files` + ADD `user` INT(8) UNSIGNED NOT NULL DEFAULT '0', + ADD INDEX (`user`) + "); + } + + public function down() + { + $this->dbforge->drop_table('users'); + $this->dbforge->drop_table('ci_sessions'); + $this->db->query(" + ALTER TABLE `files` + DROP `user` + "); + } +} diff --git a/application/migrations/003_add_referrers.php b/application/migrations/003_add_referrers.php new file mode 100644 index 000000000..524e92ff0 --- /dev/null +++ b/application/migrations/003_add_referrers.php @@ -0,0 +1,33 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_referrers extends CI_Migration { + + public function up() + { + $this->db->query(" + CREATE TABLE `invitations` ( + `user` int(8) unsigned NOT NULL, + `key` varchar(16) CHARACTER SET ascii NOT NULL, + `date` int(11) unsigned NOT NULL, + PRIMARY KEY (`key`), + KEY `user` (`user`), + KEY `date` (`date`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin + "); + $this->db->query(" + ALTER TABLE `users` + ADD `referrer` INT(8) UNSIGNED NOT NULL DEFAULT '0' + "); + } + + public function down() + { + $this->db->query(" + ALTER TABLE `users` + DROP `referrer` + "); + $this->dbforge->drop_table('invitations'); + + } +} diff --git a/application/migrations/004_add_filesize.php b/application/migrations/004_add_filesize.php new file mode 100644 index 000000000..d7a70223d --- /dev/null +++ b/application/migrations/004_add_filesize.php @@ -0,0 +1,22 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_filesize extends CI_Migration { + + public function up() + { + $this->db->query(" + ALTER TABLE `files` + ADD `filesize` INT UNSIGNED NOT NULL + "); + } + + public function down() + { + $this->db->query(" + ALTER TABLE `files` + DROP `filesize` + "); + + } +} diff --git a/application/migrations/005_drop_file_password.php b/application/migrations/005_drop_file_password.php new file mode 100644 index 000000000..bf03490a8 --- /dev/null +++ b/application/migrations/005_drop_file_password.php @@ -0,0 +1,21 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Drop_file_password extends CI_Migration { + + public function up() + { + $this->db->query(" + ALTER TABLE `files` + DROP `password`; + "); + } + + public function down() + { + $this->db->query(" + ALTER TABLE `files` + ADD `password` varchar(40) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL; + "); + } +} diff --git a/application/migrations/006_add_username_index.php b/application/migrations/006_add_username_index.php new file mode 100644 index 000000000..ea5e3ebc0 --- /dev/null +++ b/application/migrations/006_add_username_index.php @@ -0,0 +1,21 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_username_index extends CI_Migration { + + public function up() + { + $this->db->query(" + ALTER TABLE `users` + ADD UNIQUE `username` (`username`); + "); + } + + public function down() + { + $this->db->query(" + ALTER TABLE `users` + DROP INDEX `username`; + "); + } +} diff --git a/application/migrations/007_repurpose_invitations.php b/application/migrations/007_repurpose_invitations.php new file mode 100644 index 000000000..d586c2829 --- /dev/null +++ b/application/migrations/007_repurpose_invitations.php @@ -0,0 +1,37 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Repurpose_invitations extends CI_Migration { + + public function up() + { + $this->db->query(" + ALTER TABLE `invitations` + ADD `action` VARCHAR(255) NOT NULL, + ADD `data` TEXT NULL, + ADD INDEX `action` (`action`); + "); + + $this->db->query(" + UPDATE `invitations` SET `action` = 'invitation' WHERE `action` = ''; + "); + + $this->db->query(" + ALTER TABLE `invitations` RENAME `actions`; + "); + + } + + public function down() + { + $this->db->query(" + ALTER TABLE `actions` RENAME `invitations`; + "); + + $this->db->query(" + ALTER TABLE `invitations` + DROP `action`, + DROP `data`; + "); + } +} diff --git a/application/migrations/008_add_profiles.php b/application/migrations/008_add_profiles.php new file mode 100644 index 000000000..3fea33c08 --- /dev/null +++ b/application/migrations/008_add_profiles.php @@ -0,0 +1,31 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_profiles extends CI_Migration { + + public function up() + { + $this->db->query(" + CREATE TABLE `profiles` ( + `user` int(8) unsigned NOT NULL, + `upload_id_limits` varchar(255) COLLATE utf8_bin NOT NULL, + PRIMARY KEY (`user`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin + "); + + $this->db->query(" + ALTER TABLE `files` CHANGE `id` `id` VARCHAR( 255 ); + "); + + } + + public function down() + { + $this->db->query(" + DROP TABLE `profiles`; + "); + $this->db->query(" + ALTER TABLE `files` CHANGE `id` `id` VARCHAR( 6 ); + "); + } +} diff --git a/application/migrations/009_add_apikeys.php b/application/migrations/009_add_apikeys.php new file mode 100644 index 000000000..8e88260a8 --- /dev/null +++ b/application/migrations/009_add_apikeys.php @@ -0,0 +1,24 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_Add_apikeys extends CI_Migration { + + public function up() + { + $this->db->query(" + CREATE TABLE `apikeys` ( + `key` varchar(64) COLLATE utf8_bin NOT NULL, + `user` int(8) unsigned NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `comment` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + PRIMARY KEY (`key`), + KEY `user` (`user`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin + "); + } + + public function down() + { + $this->dbforge->drop_table('apikeys'); + } +} diff --git a/application/migrations/010_files_innodb.php b/application/migrations/010_files_innodb.php new file mode 100644 index 000000000..b32f94724 --- /dev/null +++ b/application/migrations/010_files_innodb.php @@ -0,0 +1,16 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_files_innodb extends CI_Migration { + + public function up() + { + $this->db->query(" + ALTER TABLE `files` ENGINE = InnoDB; + "); + } + + public function down() + { + } +} diff --git a/application/migrations/011_apikeys_add_access_level.php b/application/migrations/011_apikeys_add_access_level.php new file mode 100644 index 000000000..e0f39317b --- /dev/null +++ b/application/migrations/011_apikeys_add_access_level.php @@ -0,0 +1,19 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_apikeys_add_access_level extends CI_Migration { + + public function up() + { + $this->db->query(" + alter table `apikeys` add `access_level` varchar(255) default 'apikey'; + "); + } + + public function down() + { + $this->db->query(" + alter table `apikeys` drop `access_level`; + "); + } +} diff --git a/application/migrations/012_add_constraints.php b/application/migrations/012_add_constraints.php new file mode 100644 index 000000000..2b0764fb0 --- /dev/null +++ b/application/migrations/012_add_constraints.php @@ -0,0 +1,20 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_add_constraints extends CI_Migration { + + public function up() + { + $this->db->query("ALTER TABLE `users` ADD INDEX(`referrer`);"); + $this->db->query("ALTER TABLE `users` CHANGE `referrer` `referrer` + INT(8) UNSIGNED NULL;"); + $this->db->query("UPDATE `users` SET `referrer` = NULL where `referrer` = 0;"); + $this->db->query("ALTER TABLE `users` ADD FOREIGN KEY (`referrer`) + REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;"); + } + + public function down() + { + show_error("downgrade not supported"); + } +} diff --git a/application/migrations/013_add_multipaste.php b/application/migrations/013_add_multipaste.php new file mode 100644 index 000000000..edb4a0748 --- /dev/null +++ b/application/migrations/013_add_multipaste.php @@ -0,0 +1,36 @@ +<?php +defined('BASEPATH') OR exit('No direct script access allowed'); + +class Migration_add_multipaste extends CI_Migration { + + public function up() + { + $this->db->query(' + CREATE TABLE `multipaste` ( + `url_id` varchar(255) NOT NULL, + `multipaste_id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `date` int(11) NOT NULL, + PRIMARY KEY (`url_id`), + UNIQUE KEY `multipaste_id` (`multipaste_id`), + KEY `user_id` (`user_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;'); + + $this->db->query(' + CREATE TABLE `multipaste_file_map` ( + `multipaste_id` int(11) NOT NULL, + `file_url_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + `sort_order` int(10) unsigned NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`sort_order`), + UNIQUE KEY `multipaste_id` (`multipaste_id`,`file_url_id`), + KEY `multipaste_file_map_ibfk_2` (`file_url_id`), + CONSTRAINT `multipaste_file_map_ibfk_1` FOREIGN KEY (`multipaste_id`) REFERENCES `multipaste` (`multipaste_id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `multipaste_file_map_ibfk_2` FOREIGN KEY (`file_url_id`) REFERENCES `files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;'); + } + + public function down() + { + show_error("downgrade not supported"); + } +} 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); + } + +} + diff --git a/application/third_party/PasswordHash.php b/application/third_party/PasswordHash.php new file mode 100644 index 000000000..84447b277 --- /dev/null +++ b/application/third_party/PasswordHash.php @@ -0,0 +1,253 @@ +<?php +# +# Portable PHP password hashing framework. +# +# Version 0.3 / genuine. +# +# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in +# the public domain. Revised in subsequent years, still public domain. +# +# There's absolutely no warranty. +# +# The homepage URL for this framework is: +# +# http://www.openwall.com/phpass/ +# +# Please be sure to update the Version line if you edit this file in any way. +# It is suggested that you leave the main version number intact, but indicate +# your project name (after the slash) and add your own revision information. +# +# Please do not change the "private" password hashing method implemented in +# here, thereby making your hashes incompatible. However, if you must, please +# change the hash type identifier (the "$P$") to something different. +# +# Obviously, since this code is in the public domain, the above are not +# requirements (there can be none), but merely suggestions. +# +class PasswordHash { + var $itoa64; + var $iteration_count_log2; + var $portable_hashes; + var $random_state; + + function PasswordHash($iteration_count_log2, $portable_hashes) + { + $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) + $iteration_count_log2 = 8; + $this->iteration_count_log2 = $iteration_count_log2; + + $this->portable_hashes = $portable_hashes; + + $this->random_state = microtime(); + if (function_exists('getmypid')) + $this->random_state .= getmypid(); + } + + function get_random_bytes($count) + { + $output = ''; + if (@is_readable('/dev/urandom') && + ($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } + + if (strlen($output) < $count) { + $output = ''; + for ($i = 0; $i < $count; $i += 16) { + $this->random_state = + md5(microtime() . $this->random_state); + $output .= + pack('H*', md5($this->random_state)); + } + $output = substr($output, 0, $count); + } + + return $output; + } + + function encode64($input, $count) + { + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) + $value |= ord($input[$i]) << 8; + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) + break; + if ($i < $count) + $value |= ord($input[$i]) << 16; + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) + break; + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + function gensalt_private($input) + { + $output = '$P$'; + $output .= $this->itoa64[min($this->iteration_count_log2 + + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); + + return $output; + } + + function crypt_private($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) == $output) + $output = '*1'; + + $id = substr($setting, 0, 3); + # We use "$P$", phpBB3 uses "$H$" for the same thing + if ($id != '$P$' && $id != '$H$') + return $output; + + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) + return $output; + + $count = 1 << $count_log2; + + $salt = substr($setting, 4, 8); + if (strlen($salt) != 8) + return $output; + + # We're kind of forced to use MD5 here since it's the only + # cryptographic primitive available in all versions of PHP + # currently in use. To implement our own low-level crypto + # in PHP would result in much worse performance and + # consequently in lower iteration counts and hashes that are + # quicker to crack (by non-PHP code). + if (PHP_VERSION >= '5') { + $hash = md5($salt . $password, TRUE); + do { + $hash = md5($hash . $password, TRUE); + } while (--$count); + } else { + $hash = pack('H*', md5($salt . $password)); + do { + $hash = pack('H*', md5($hash . $password)); + } while (--$count); + } + + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); + + return $output; + } + + function gensalt_extended($input) + { + $count_log2 = min($this->iteration_count_log2 + 8, 24); + # This should be odd to not reveal weak DES keys, and the + # maximum valid value is (2**24 - 1) which is odd anyway. + $count = (1 << $count_log2) - 1; + + $output = '_'; + $output .= $this->itoa64[$count & 0x3f]; + $output .= $this->itoa64[($count >> 6) & 0x3f]; + $output .= $this->itoa64[($count >> 12) & 0x3f]; + $output .= $this->itoa64[($count >> 18) & 0x3f]; + + $output .= $this->encode64($input, 3); + + return $output; + } + + function gensalt_blowfish($input) + { + # This one needs to use a different order of characters and a + # different encoding scheme from the one in encode64() above. + # We care because the last character in our encoded string will + # only represent 2 bits. While two known implementations of + # bcrypt will happily accept and correct a salt string which + # has the 4 unused bits set to non-zero, we do not want to take + # chances and we also do not want to waste an additional byte + # of entropy. + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = '$2a$'; + $output .= chr(ord('0') + $this->iteration_count_log2 / 10); + $output .= chr(ord('0') + $this->iteration_count_log2 % 10); + $output .= '$'; + + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } + + function HashPassword($password) + { + $random = ''; + + if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { + $random = $this->get_random_bytes(16); + $hash = + crypt($password, $this->gensalt_blowfish($random)); + if (strlen($hash) == 60) + return $hash; + } + + if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { + if (strlen($random) < 3) + $random = $this->get_random_bytes(3); + $hash = + crypt($password, $this->gensalt_extended($random)); + if (strlen($hash) == 20) + return $hash; + } + + if (strlen($random) < 6) + $random = $this->get_random_bytes(6); + $hash = + $this->crypt_private($password, + $this->gensalt_private($random)); + if (strlen($hash) == 34) + return $hash; + + # Returning '*' on error is safe here, but would _not_ be safe + # in a crypt(3)-like function used _both_ for generating new + # hashes and for validating passwords against existing hashes. + return '*'; + } + + function CheckPassword($password, $stored_hash) + { + $hash = $this->crypt_private($password, $stored_hash); + if ($hash[0] == '*') + $hash = crypt($password, $stored_hash); + + return $hash == $stored_hash; + } +} + +?> diff --git a/application/views/contact.php b/application/views/contact.php new file mode 100644 index 000000000..6497ab6a7 --- /dev/null +++ b/application/views/contact.php @@ -0,0 +1 @@ +<?php echo $contact_info; ?> diff --git a/application/views/file/client.php b/application/views/file/client.php new file mode 100644 index 000000000..1c864437a --- /dev/null +++ b/application/views/file/client.php @@ -0,0 +1,49 @@ +<h1>Client</h1> + +<p> + Development (git): <?php echo anchor("http://git.server-speed.net/users/flo/fb/"); ?><br /> + Latest release: <?php echo $client_link ? anchor($client_link) : "unknown"; ?><br /> + GPG sigs, older versions: <a href="<?php echo $client_link_dir; ?>"><?php echo $client_link_dir; ?></a> +</p> + +<p>To authenticate add the following to your ~/.netrc:</p> + +<pre> +machine <?php echo $domain; ?> login my_username password my_secret_password +</pre> + +<p> + If you are using fb-client ≥1.2 you can + <a href="<?php echo site_url("user/apikeys"); ?>">create an API key</a>, + save it in <code>~/.config/fb-client/apikey</code> and remove + your password from <code>.netrc</code>. Please refer to <code>man + 1 fb</code> for further details. +</p> + +<p> + If you are using fb-client ≥1.1 you can use + <code>~/.config/fb-client/config</code> to upload to a different + pastebin URL (https or you own installation). Please refer to + <code>man 1 fb</code> for further details. +</p> + +<h2>Linux</h2> +<p> + Arch Linux: pacman -S fb-client<br /> + Debian: <?php echo anchor($client_link_deb); ?><br /> + Gentoo: Add <a href="https://publicus.dorado.uberspace.de/cgit/cgit.cgi/holgersson-overlay/tree/README">this overlay</a> and run <code>emerge -a fb-client</code><br /> + Slackware: <?php echo anchor($client_link_slackware); ?> +</p> + +<h2>OS X</h2> +<p> + Get <a href="http://brew.sh">Homebrew</a> and run <code>brew install fb-client</code>. +</p> + +<h1>Shell</h1> + +<pre> +curl -n -F "file=@/home/user/foo" <?php echo site_url("file/do_upload"); ?> (binary safe) +cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url("file/do_upload"); ?> (binary safe) +</pre> + diff --git a/application/views/file/deleted.php b/application/views/file/deleted.php new file mode 100644 index 000000000..8a5818f2d --- /dev/null +++ b/application/views/file/deleted.php @@ -0,0 +1,11 @@ +<div class="center"> + <?php if (!empty($errors)) { + echo "<p>"; + foreach ($errors as $error) { + echo "${error["id"]}: ${error["reason"]}<br>\n"; + } + echo "</p>"; + } ?> + + <p><?php echo $deleted_count; ?> of <?php echo $total_count; ?> deleted.</p> +</div> diff --git a/application/views/file/file_info.php b/application/views/file/file_info.php new file mode 100644 index 000000000..0620ac9bd --- /dev/null +++ b/application/views/file/file_info.php @@ -0,0 +1,32 @@ +<div class="center simple-container"> + <?php if($filedata): ?> + <div class="table-responive"> + <table class="table" style="margin: auto"> + <tr> + <td class="title">ID</td> + <td class="text"><a href="<?php echo site_url($id); ?>/"><?php echo $id; ?></a></td> + </tr> + <tr> + <td class="title">Filename</td> + <td class="text"><?php echo htmlspecialchars($filedata["filename"]); ?></td> + </tr> + <tr> + <td class="title">Date of upload</td> + <td class="text"><?php echo date("r", $filedata["date"]); ?></td> + </tr> + <tr> + <td class="title">Date of removal</td> + <td class="text"><?php echo $timeout; ?></td> + </tr> + <tr> + <td class="title">Size</td> + <td class="text"><?php echo format_bytes($filedata["filesize"]); ?></td> + </tr> + <tr> + <td class="title">Mimetype</td> + <td class="text"><?php echo $filedata["mimetype"]; ?></td> + </tr> + </table> + </div> + <?php endif; ?> +</div> diff --git a/application/views/file/fragments/alert-wide.php b/application/views/file/fragments/alert-wide.php new file mode 100644 index 000000000..ae303e119 --- /dev/null +++ b/application/views/file/fragments/alert-wide.php @@ -0,0 +1,3 @@ +<div class="alert alert-danger alert-wide"> + <?php echo $error_message; ?> +</div> diff --git a/application/views/file/fragments/thumbnail.php b/application/views/file/fragments/thumbnail.php new file mode 100644 index 000000000..6bd82fcb9 --- /dev/null +++ b/application/views/file/fragments/thumbnail.php @@ -0,0 +1,9 @@ +<!-- Comment markers background: http://stackoverflow.com/a/14776780/953022 --> +<div class="container container-wide"> +<div class="upload_thumbnails"><!-- + <?php foreach($items as $key => $item): ?> + --><a href="<?php echo site_url("/".$item["id"])."/"; ?>" title="<?php echo htmlentities($item["filename"]); ?>" data-content="<?php echo htmlentities($item["tooltip"]); ?>" data-id="<?php echo $item["id"]; ?>"><img class="thumb" src="<?php echo site_url("file/thumbnail/".$item["id"]); ?>"></a><!-- + <?php endforeach; ?> + --> +</div> +</div> diff --git a/application/views/file/fragments/uploads_table.php b/application/views/file/fragments/uploads_table.php new file mode 100644 index 000000000..142d19e91 --- /dev/null +++ b/application/views/file/fragments/uploads_table.php @@ -0,0 +1,26 @@ +<?php register_js_include("/data/js/jquery.tablesorter.min.js"); ?> +<div class="table-responsive container-wide"> + <p>Non-previewable file(s):</p> + <table class="table table-striped tablesorter"> + <thead> + <tr> + <th>ID</th> + <th>Filename</th> + <th>Mimetype</th> + <th>Date</th> + <th>Size</th> + </tr> + </thead> + <tbody> + <?php foreach($items as $item): ?> + <tr> + <td><a href="<?php echo site_url("/".$item["id"]) ?>/"><?php echo $item["id"] ?></a></td> + <td class="wrap"><?php echo htmlspecialchars($item["filename"]); ?></td> + <td><?php echo $item["mimetype"] ?></td> + <td class="nowrap" data-sort-value="<?=$item["date"]; ?>"><?php echo date("r", $item["date"]); ?></td> + <td><?php echo format_bytes($item["filesize"]) ?></td> + </tr> + <?php endforeach; ?> + </tbody> + </table> +</div> diff --git a/application/views/file/html_footer.php b/application/views/file/html_footer.php new file mode 100644 index 000000000..bd07b63f9 --- /dev/null +++ b/application/views/file/html_footer.php @@ -0,0 +1,4 @@ +<div class="container"> +<?php +$force_full_html = true; +include(FCPATH."application/views/footer.php"); diff --git a/application/views/file/html_header.php b/application/views/file/html_header.php new file mode 100644 index 000000000..fdce101a2 --- /dev/null +++ b/application/views/file/html_header.php @@ -0,0 +1,15 @@ +<?php +$force_full_html = true; +include(FCPATH."application/views/header.php"); ?> + +</div><!-- .container --> + +<script type="text/javascript"> + /* <![CDATA[ */ + window.lexers = <?php echo json_encode($lexers); ?>; + /* ]]> */ +</script> + +<?php if (isset($error_message)) { + include 'framgents/alert-wide.php'; +} ?> diff --git a/application/views/file/html_paste_footer.php b/application/views/file/html_paste_footer.php new file mode 100644 index 000000000..22bc4dabb --- /dev/null +++ b/application/views/file/html_paste_footer.php @@ -0,0 +1,2 @@ +</div><!-- .container .paste-container --> + diff --git a/application/views/file/html_paste_header.php b/application/views/file/html_paste_header.php new file mode 100644 index 000000000..f4d3021ec --- /dev/null +++ b/application/views/file/html_paste_header.php @@ -0,0 +1,79 @@ +<div class="container paste-container container-wide"> + <div style="border:1px solid #ccc;"> + <div class="navbar navbar-default navbar-static-top navbar-paste"> + <ul class="nav navbar-nav navbar-left dont-float"> + <li><a href="<?=site_url($id)."/"; ?>" class="navbar-brand" data-toggle="modal"><?php echo $title ?></a></li> + <li class="divider"></li> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="language-toggle-<?=$id; ?>"> + Language: <?php echo htmlspecialchars($current_highlight); ?> + <b class="caret"></b> + </a> + <div class="dropdown-menu" style="padding: 15px;"> + <form> + <input data-base-url="<?=site_url($id); ?>" type="text" id="language-<?=$id; ?>" placeholder="Language" class="form-control"> + </form> + </div> + </li> + <li class="divider"></li> + <li> + <a href="#file-info-<?=$id; ?>" role="button" data-toggle="modal">Info</a> + </li> + <?php if (isset($user_logged_in) && $user_logged_in) { ?> + <li class="divider"></li> + <li><a href="<?php echo site_url('file/index?repaste='.$id); ?>" role="button">Repaste</a></li> + <?php } ?> + </ul> + <div class="btn-group navbar-right" style="margin: 8px;"> + <a id="linewrap-<?=$id; ?>" class="btn btn-default" rel="tooltip" title="Toggle wrapping of long lines">Linewrap</a> + <a href="<?php echo site_url($id."/plain") ?>" class="btn btn-default" rel="tooltip" title="View as plain text">Plain</a> + <a href="<?php echo site_url($id) ?>" class="btn btn-default" rel="tooltip" title="View as raw file (org. mime type)">Raw</a> + <?php if ($current_highlight === 'rmd') { ?> + <a href="<?php echo site_url($id)."/" ?>" class="btn btn-default" rel="tooltip" title="Render as Code">Code</a> + <?php } else { ?> + <a href="<?php echo site_url($id."/rmd") ?>" class="btn btn-default" rel="tooltip" title="Render as Markdown">Markdown</a> + <?php } ?> + </div> + </div> <!-- .navbar --> + <div id="file-info-<?=$id; ?>" class="modal fade" role="dialog" aria-labelledby="file-info-<?=$id; ?>" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal">×</button> + <h3 class="modal-title">Paste Information</h3> + </div> + <div class="modal-body"> + <table class="table"> + <tr> + <td style="border:0;">Filename:</td> + <td style="border:0;"><?php echo htmlspecialchars($filedata["filename"]) ?></td> + </tr> + <tr> + <td>Size:</td> + <td><?php echo format_bytes($filedata["filesize"]) ?></td> + </tr> + <tr> + <td>Mimetype:</td> + <td><?php echo $filedata["mimetype"] ?></td> + </tr> + <tr> + <td>Uploaded:</td> + <td><?php echo date("r", $filedata["date"]) ?></td> + </tr> + <tr> + <td>Removal:</td> + <td><?php echo $timeout ?></td> + </tr> + </table> + </div> + <div class="modal-footer"> + <?php echo form_open("file/do_delete/", array("style" => "display: inline")); ?> + <input type="hidden" name="ids[<?php echo $id; ?>]" value="<?php echo $id; ?>"> + <button class="btn btn-danger pull-left" aria-hidden="true">Delete</button> + </form> + <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button> + </div> + </div> + </div> + </div> <!-- .modal --> + </div> diff --git a/application/views/file/multipaste_info.php b/application/views/file/multipaste_info.php new file mode 100644 index 000000000..5baf732a2 --- /dev/null +++ b/application/views/file/multipaste_info.php @@ -0,0 +1,26 @@ +<div class="center simple-container"> + <div class="table-responive"> + <table class="table" style="margin: auto"> + <tr> + <td class="title">ID</td> + <td class="text"><a href="<?=site_url($id); ?>/"><?=$id; ?></a></td> + </tr> + <tr> + <td class="title">Number of files</td> + <td class="text"><?=$file_count; ?></td> + </tr> + <tr> + <td class="title">Date of upload</td> + <td class="text"><?=date("r", $upload_date); ?></td> + </tr> + <tr> + <td class="title">Date of removal</td> + <td class="text"><?=$timeout_string; ?></td> + </tr> + <tr> + <td class="title">Total size (including duplicates)</td> + <td class="text"><?=format_bytes($size); ?></td> + </tr> + </table> + </div> +</div> diff --git a/application/views/file/nav_history.php b/application/views/file/nav_history.php new file mode 100644 index 000000000..bbb695fd6 --- /dev/null +++ b/application/views/file/nav_history.php @@ -0,0 +1,18 @@ +<ul class="nav nav-tabs nav-history"> +<?php +$nav = array( + "List" => "file/upload_history", + "Thumbnails" => "file/upload_history_thumbnails", +); + +$CI =& get_instance(); + +foreach ($nav as $key => $item) { + ?> + <li <?php echo $CI->uri->uri_string() == $item ? 'class="active"' : ''; ?>> + <a href="<?php echo site_url($item); ?>"><?php echo $key; ?></a> + </li> + <?php +} +?> +</ul> diff --git a/application/views/file/non_existent.php b/application/views/file/non_existent.php new file mode 100644 index 000000000..13d8c6950 --- /dev/null +++ b/application/views/file/non_existent.php @@ -0,0 +1,3 @@ +<div class="center"> + <p>I'm sorry, but the requested file doesn't exist.</p> +</div> diff --git a/application/views/file/show_url.php b/application/views/file/show_url.php new file mode 100644 index 000000000..a3d965717 --- /dev/null +++ b/application/views/file/show_url.php @@ -0,0 +1,8 @@ +<div class="center"> + <p>You can get your file(s) here:</p> + <p> + <?php foreach ($urls as $key => $url) { ?> + <a href="<?php echo $url; ?>"><?php echo $url; ?></a><br /> + <?php } ?> + </p> +</div> diff --git a/application/views/file/upload_form.php b/application/views/file/upload_form.php new file mode 100644 index 000000000..5051f689b --- /dev/null +++ b/application/views/file/upload_form.php @@ -0,0 +1,110 @@ +<?php if (isset($user_logged_in) && $user_logged_in) { ?> +<div class="row"> + <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 text-upload-form"> + <?php echo form_open_multipart('file/do_paste'); ?> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Text paste</h3> + </div> + <div class="panel-body"> + <textarea name="content" class="form-control text-upload"><?php + if (isset($textarea_content)) { + echo $textarea_content; + } + ?></textarea><br> + <button type="submit" class="btn btn-primary">Paste it!</button> + </div> + </form> + </div> + </div> +</div> +<div class="row"> + <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12"> + <?php echo form_open_multipart('file/do_upload'); ?> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">File upload</h3> + </div> + <div class="panel-body"> + <div> + <input class="file-upload" type="file" name="file[]" multiple="multiple"><br> + </div> + <label><input type="checkbox" name="multipaste" value="1"> Create multipaste</label><br> + <button type="submit" id="upload_button" class="btn btn-primary">Upload it!</button> + </div> + </div> + </form> + </div> + <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12"> + <div class="panel panel-info"> + <div class="panel-heading"> + <h3 class="panel-title">Notice!</h3> + </div> + <div class="panel-body"> + <p> + Uploads/pastes are <?php if ($upload_max_age > 0) { + echo "deleted after ".$upload_max_age." days"; + if ($small_upload_size > 0) { + echo " unless they are smaller than ".format_bytes($small_upload_size); + } + } else { + echo "stored forever"; + } ?>. Maximum upload size is <?php echo format_bytes($max_upload_size); ?>. + You can upload a maximum of <?php echo ini_get("max_file_uploads"); ?> files at once. + </p> + </div> + </div> + </div> +</div> + +<script type="text/javascript"> + /* <![CDATA[ */ + var max_upload_size = "<?php echo $max_upload_size; ?>"; + var max_files_per_upload = "<?php echo ini_get("max_file_uploads"); ?>"; + /* ]]> */ +</script> + +<?php } else { ?> + <?php echo form_open('user/login', array('class' => 'form-inline')); ?> + <input type="text" name="username" placeholder="Username" autofocus class="form-control inline-input"/> + <input type="password" name="password" placeholder="Password" class="form-control inline-input"/> + <input type="submit" class="btn btn-primary" value="Login" name="process" /> + <?php if(auth_driver_function_implemented("can_reset_password")) { ?> + <p class="help-block"><?php echo anchor("user/reset_password", "Forgot your password?"); ?></p> + <?php } ?> + </form> +<?php } ?> +<div class="row"> + <div class="col-lg-6"> + <div class="page-header"><h1>Features</h1></div> + <p>For shell uploading/pasting and download information for the client go to <a href="<?php echo site_url("file/client"); ?>"><?php echo site_url("file/client"); ?></a></p> + <p>You can use the <?php echo anchor("file/upload_history", "history"); ?> to find old uploads.</p> + <h3>How to link your pastes:</h3> + <dl class="dl-horizontal"> + <dt>/<ID>/</dt><dd>automatically highlight the paste</dd> + <dt>/<ID></dt><dd>set the detected MIME type and let the browser do the rest</dd> + <dt>/<ID>/plain</dt><dd>force the MIME type to be text/plain</dd> + <dt>/<ID>/<file extension></dt><dd>override auto detection and use the supplied file extension or language name for highlighting</dd> + <dt>/<ID>/qr</dt><dd>display a qr code containing a link to <span class="example">/<ID>/</span></dd> + <dt>/<ID>/rmd</dt><dd>convert markdown to HTML</dd> + <dt>/<ID>/ascii</dt><dd>convert text with ANSI (shell) escape codes to HTML</dd> + <dt>/<ID>/info</dt><dd>display some information about the ID</dd> + </dl> + <p>If your upload is not detected as text, only <b>/<ID>/qr</b>, <b>/<ID>/plain</b> and <b>/<ID>/info</b> will work as above and all others will simply return the file with the detected MIME type.</p> + <h3>How to link your multipastes:</h3> + <p>Multipaste IDs begin with <code>m-</code> and only support the following features.</p> + <dl class="dl-horizontal"> + <dt>/<ID>/</dt><dd>automatically display everything in a sensible way</dd> + <dt>/<ID>/qr</dt><dd>display a qr code containing a link to <span class="example">/<ID>/</span></dd> + <dt>/<ID>/info</dt><dd>display some information about the multipaste</dd> + </dl> + </div> + <div class="col-lg-6"> + <div class="page-header"><h1>Information</h1></div> + <p>This website's primary goal is aiding developers, power users, students and alike in solving problems, debugging software, sharing their configuration, etc. It is not intended to distribute confidential or harmful information, scripts or software.</p> + <?php if(auth_driver_function_implemented("can_register_new_users")) { ?> + <p>If you want an account, ask someone who is already using this service to <a href="<?php echo site_url("user/invite"); ?>">invite</a> you.</p> + <p>Invitations are used to control abuse and encourage users to "be nice". They are not intended as a means of exclusivity. In case of abuse reports, involved accounts may be banned and the user who invited them may also be banned. The invitation tree will be followed upwards if necessary.</p> + <?php } ?> + </div> +</div> diff --git a/application/views/file/upload_history.php b/application/views/file/upload_history.php new file mode 100644 index 000000000..10afc53e9 --- /dev/null +++ b/application/views/file/upload_history.php @@ -0,0 +1,34 @@ +<?php register_js_include("/data/js/jquery.tablesorter.min.js"); ?> +<?php register_js_include("/data/js/jquery.metadata.js"); ?> +<?php include 'nav_history.php'; ?> +<?php echo form_open("file/do_delete") ?> + <div class="table-responsive"> + <table id="upload_history" class="table table-striped tablesorter {sortlist: [[4,1]]}"> + <thead> + <tr> + <th class="{sorter: false}"><input type="checkbox" name="all-ids" id="history-all"></th> + <th>ID</th> + <th>Filename</th> + <th>Mimetype + <th>Date</th> + <th>Size</th> + </tr> + </thead> + <tbody> + <?php foreach($items as $key => $item): ?> + <tr> + <td><input type="checkbox" name="ids[<?php echo $item["id"] ?>]" value="<?php echo $item["id"] ?>" class="delete-history"></td> + <td><a href="<?php echo site_url("/".$item["id"]) ?>/"><?php echo $item["id"] ?></a></td> + <td class="wrap"><?php echo htmlspecialchars($item["filename"]); ?></td> + <td><?php echo $item["mimetype"] ?></td> + <td class="nowrap" data-sort-value="<?=$item["date"]; ?>"><?php echo date("r", $item["date"]); ?></td> + <td><?php echo $item["filesize"] ?></td> + </tr> + <?php endforeach; ?> + </tbody> + </table> + </div> + <input class="btn btn-danger" type="submit" value="Delete checked" name="process"> +</form> + +<p>Total sum of your distinct uploads: <?php echo $total_size; ?>.</p> diff --git a/application/views/file/upload_history_thumbnails.php b/application/views/file/upload_history_thumbnails.php new file mode 100644 index 000000000..a061d9676 --- /dev/null +++ b/application/views/file/upload_history_thumbnails.php @@ -0,0 +1,21 @@ +<div class="pull-right"> + <?php echo form_open("file/do_delete/", array("id" => "delete_form", "style" => "display: inline")); ?> + <button class="btn btn-danger" id="delete_button" style="display: none">Delete selected</button> + </form> + <button class="btn btn-default" id="toggle_delete_mode" style="display: inline">Delete mode</button> +</div> + +<?php include 'nav_history.php'; ?> +<?php include 'fragments/thumbnail.php'; ?> + +<div class="row-fluid"> + <div class="span12 alert alert-block alert-info"> + <h4 class="alert-heading">Notice!</h4> + <p> + Currently only jpeg, png and gif images are displayed here. If you are + looking for something else, please switch to the + <a href="<?php echo site_url("file/upload_history"); ?>">list view</a> + which contains your complete history. + </p> + </div> +</div> diff --git a/application/views/file_plaintext/client.php b/application/views/file_plaintext/client.php new file mode 100644 index 000000000..0ab556df2 --- /dev/null +++ b/application/views/file_plaintext/client.php @@ -0,0 +1,12 @@ +Shell (binary safe): + curl -n -F "file=@/home/user/foo" <?php echo site_url("file/do_upload")."\n"; ?> + cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url("file/do_upload")."\n"; ?> + +Client: +Development (git): http://git.server-speed.net/users/flo/fb +Latest release: <?php echo $client_link."\n"; ?> +GPG sigs, older versions: <?php echo $client_link_dir."\n"; ?> + +To authenticate add the following to your ~/.netrc: + machine paste.xinu.at login my_username password my_secret_password + diff --git a/application/views/file_plaintext/deleted.php b/application/views/file_plaintext/deleted.php new file mode 100644 index 000000000..347766092 --- /dev/null +++ b/application/views/file_plaintext/deleted.php @@ -0,0 +1,8 @@ +<?php if (!empty($errors)) { + echo implode("\n", $errors); +} ?> +<?php if (!empty($msgs)) { + echo implode("\n", $msgs); +} ?> + +<?php echo $deleted_count; ?> of <?php echo $total_count; ?> deleted. diff --git a/application/views/file_plaintext/footer.php b/application/views/file_plaintext/footer.php new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/application/views/file_plaintext/footer.php diff --git a/application/views/file_plaintext/header.php b/application/views/file_plaintext/header.php new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/application/views/file_plaintext/header.php diff --git a/application/views/file_plaintext/html_footer.php b/application/views/file_plaintext/html_footer.php new file mode 100644 index 000000000..d46a93ce4 --- /dev/null +++ b/application/views/file_plaintext/html_footer.php @@ -0,0 +1 @@ +<?php include "application/views/file/html_footer.php"; ?> diff --git a/application/views/file_plaintext/html_header.php b/application/views/file_plaintext/html_header.php new file mode 100644 index 000000000..5cc4c40a7 --- /dev/null +++ b/application/views/file_plaintext/html_header.php @@ -0,0 +1 @@ +<?php include "application/views/file/html_header.php"; ?> diff --git a/application/views/file_plaintext/html_paste_footer.php b/application/views/file_plaintext/html_paste_footer.php new file mode 100644 index 000000000..f3cf21e3a --- /dev/null +++ b/application/views/file_plaintext/html_paste_footer.php @@ -0,0 +1 @@ +<?php include "application/views/file/html_paste_footer.php"; ?> diff --git a/application/views/file_plaintext/html_paste_header.php b/application/views/file_plaintext/html_paste_header.php new file mode 100644 index 000000000..2a5db977e --- /dev/null +++ b/application/views/file_plaintext/html_paste_header.php @@ -0,0 +1 @@ +<?php include "application/views/file/html_paste_header.php"; ?> diff --git a/application/views/file_plaintext/non_existent.php b/application/views/file_plaintext/non_existent.php new file mode 100644 index 000000000..7da92e954 --- /dev/null +++ b/application/views/file_plaintext/non_existent.php @@ -0,0 +1 @@ +I'm sorry, but the requested file doesn't exist. diff --git a/application/views/file_plaintext/show_url.php b/application/views/file_plaintext/show_url.php new file mode 100644 index 000000000..64050ddcd --- /dev/null +++ b/application/views/file_plaintext/show_url.php @@ -0,0 +1,3 @@ +<?php +echo implode(" ", $urls)."\n"; + diff --git a/application/views/file_plaintext/upload_form.php b/application/views/file_plaintext/upload_form.php new file mode 100644 index 000000000..a74e5d434 --- /dev/null +++ b/application/views/file_plaintext/upload_form.php @@ -0,0 +1,19 @@ +Uploads/pastes are deleted after <?php echo $upload_max_age; ?> days<?php if($small_upload_size > 0): ?> unless they are smaller than <?php echo format_bytes($small_upload_size); ?><?php endif; ?>. +Maximum upload size is <?php echo format_bytes($max_upload_size); ?>. +You can upload a maximum of <?php echo ini_get("max_file_uploads"); ?> files at once. + +How to link your uploads: + - "/<ID>/" automatically highlight the uploads + - "/<ID>" set the detected MIME type and let the browser do the rest + - "/<ID>/plain" force the MIME type to be text/plain + - "/<ID>/<file extension>" override auto detection and use the supplied + file extension or language name for highlighting + - "/<ID>/qr" display a qr code containing a link to /<ID>/ + - "/<ID>/rmd" convert markdown to HTML + - "/<ID>/ascii" convert text with ANSI (shell) escape codes to HTML + - "/<ID>/info" display some information about the ID + +If your upload is not detected as text, only "/<ID>/qr", "/<ID>/plain" +and "/<ID>/info" will work as above and all others will simply return +the file with the detected MIME type. + diff --git a/application/views/file_plaintext/upload_history.php b/application/views/file_plaintext/upload_history.php new file mode 100644 index 000000000..53801494f --- /dev/null +++ b/application/views/file_plaintext/upload_history.php @@ -0,0 +1,23 @@ +<?php +$dateformat = "r"; +$lengths["date"] = max($lengths["date"], strlen(date($dateformat, time()))); +echo + mb_str_pad($fields["id"], $lengths["id"])." | " + .mb_str_pad($fields["filename"], $lengths["filename"])." | " + .mb_str_pad($fields["mimetype"], $lengths["mimetype"])." | " + .mb_str_pad($fields["date"], $lengths["date"])." | " + .mb_str_pad($fields["hash"], $lengths["hash"])." | " + .mb_str_pad($fields["filesize"], $lengths["filesize"])."\n"; + +foreach($items as $key => $item) { + echo + mb_str_pad($item["id"], $lengths["id"])." | " + .mb_str_pad($item["filename"], $lengths["filename"])." | " + .mb_str_pad($item["mimetype"], $lengths["mimetype"])." | " + .date($dateformat, $item["date"])." | " + .mb_str_pad($item["hash"], $lengths["hash"])." | " + .$item["filesize"]."\n"; +} +?> + +Total sum of your distinct uploads: <?php echo $total_size; ?>. diff --git a/application/views/footer.php b/application/views/footer.php new file mode 100644 index 000000000..ae8d2e575 --- /dev/null +++ b/application/views/footer.php @@ -0,0 +1,33 @@ +<?php +if (is_cli_client() && !isset($force_full_html)) { + return; +} +?> + </div><!-- .container --> +<div id="push"></div> +</div> <!-- #wrap --> +<footer class="footer" id="footer"> + <div class="container muted credits"> + <p>Site code licensed under <a href="http://www.gnu.org/licenses/agpl-3.0.html" target="_blank">AGPL v3</a>.</p> + <p><a href="http://glyphicons.com">Glyphicons Free</a> licensed under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.</p> + <ul class="footer-links"> + <li><a href="http://git.server-speed.net/users/flo/filebin/">Source</a></li> + <li class="muted">·</li> + <li><a href="<?php echo site_url("file/contact"); ?>">Contact</a></li> + </ul> + </div> +</footer> + +<?php +$CI = &get_instance(); +if ($CI->config->item("environment") == "development" && property_exists($CI, "email")) { + echo $CI->email->print_debugger(); +} +?> +<?php echo include_js("/data/js/jquery-2.0.3.min.js"); ?> +<?php echo include_js("/data/js/jquery-ui-1.10.3.custom.min.js"); ?> +<?php echo include_js("/data/js/bootstrap.min.js"); ?> +<?php echo include_js("/data/js/script.js"); ?> +<?php echo include_registered_js(); ?> +</body> +</html> diff --git a/application/views/header.php b/application/views/header.php new file mode 100644 index 000000000..8f246aeb8 --- /dev/null +++ b/application/views/header.php @@ -0,0 +1,105 @@ +<?php +if (is_cli_client() && !isset($force_full_html)) { + return; +} +?><!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <title><?php echo isset($title) ? $title : 'FileBin'; ?></title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="robots" content="noindex,nofollow" /> + <meta name="description" content=""> + <meta name="author" content=""> + + <link href="<?php echo link_with_mtime("/data/css/ui-lightness/jquery-ui-1.10.3.custom.min.css"); ?>" rel="stylesheet"> + <link href="<?php echo link_with_mtime("/data/css/bootstrap.min.css"); ?>" rel="stylesheet"> + <link href="<?php echo link_with_mtime("/data/css/style.css"); ?>" rel="stylesheet"> + <?php + if (file_exists(FCPATH."data/local/style.css")) { + echo '<link href="'.link_with_mtime("/data/local/style.css").'" rel="stylesheet">'; + } + + if (file_exists(FCPATH."data/local/favicon.png")) { + echo '<link href="'.link_with_mtime("/data/local/favicon.png").'" rel="shortcut icon">'; + } + ?> +</head> + +<body> +<div id="wrap"> +<?php if (file_exists(FCPATH."data/local/header.inc.php")) { + include FCPATH."data/local/header.inc.php"; +}?> + <nav class="navbar navbar-fixed-top navbar-inverse" role="navigation"> + <div class="container"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand" href="<?php echo site_url(); ?>"><?php + if (file_exists(FCPATH."data/local/logo.svg")) { + echo '<img class="brand-icon" src="'.link_with_mtime("/data/local/logo.svg").'" style="height: 20px"> FileBin'; + } else { + echo "FileBin"; + } + ?> + </a> + </div> + <div class="collapse navbar-collapse navbar-ex1-collapse"> + <?php if(!isset($GLOBALS["is_error_page"])) { ?> + <ul class="nav navbar-nav navbar-right"> + <?php if (isset($user_logged_in) && $user_logged_in) { ?> + <li><a class="navbar-link" href="<?php echo site_url("/user/logout"); ?>">Logout</a></li> + <?php } else { ?> + <li class="dropdown"> + <a class="dropdown-toggle" href="#" data-toggle="dropdown">Login <b class="caret"></b></a> + <div class="dropdown-menu" style="padding: 5px;"> + <?php if(auth_driver_function_implemented("can_reset_password")) { ?> + <p><?php echo anchor("user/reset_password", "Forgot your password?"); ?></p> + <?php } ?> + <?php echo form_open("user/login", array("class" => "form-signin")); ?> + <input type="text" name="username" placeholder="Username" class="form-control"> + <input type="password" name="password" placeholder="Password" class="form-control"> + <button type="submit" name="process" class="btn btn-default btn-block">Login</button> + </form> + </div> + </li> + <?php } ?> + </ul> + <?php }; ?> + <ul class="nav navbar-nav"> + <?php if (isset($user_logged_in) && $user_logged_in) { ?> + <li><a href="<?php echo site_url("file/index") ?>"><span class="glyphicon glyphicon-pencil"></span> New</a></li> + <li><a href="<?php echo site_url("file/upload_history") ?>"><span class="glyphicon glyphicon-book"></span> History</a></li> + <li class="dropdown"> + <a href="<?php echo site_url("user/index"); ?>" class="dropdown-toggle" data-toggle="dropdown"> + <span class="glyphicon glyphicon-user"></span> Account <b class="caret"></b> + </a> + <ul class="dropdown-menu"> + <?php include "user/nav.php"; ?> + </ul> + </li> + <?php } ?> + </ul> + </div> + </div> + </nav> + <div id="navbar-height"></div> + + <div class="container"> + <?php + if (isset($alerts)) { + foreach ($alerts as $alert) { ?> + <div class="alert alert-dismissable alert-<?php echo $alert["type"]; ?>" style="text-align: center"> + <button type="button" class="close" data-dismiss="alert">×</button> + <?php echo $alert["message"]; ?> + </div> + <?php + } + } + ?> diff --git a/application/views/user/apikeys.php b/application/views/user/apikeys.php new file mode 100644 index 000000000..2b6934c6d --- /dev/null +++ b/application/views/user/apikeys.php @@ -0,0 +1,60 @@ +<h2>API keys</h2> +<div class="table-responsive"> + <table class="table table-striped"> + <thead> + <tr> + <th>#</th> + <th>Key</th> + <th style="width: 30%;">Comment</th> + <th>Created on</th> + <th>Access</th> + <th></th> + </tr> + </thead> + <tbody> + <?php $i = 1; ?> + <?php foreach($query as $key => $item): ?> + <tr> + <td><?php echo $i++; ?></td> + <td><?php echo $item["key"]; ?></td> + <td><?php echo htmlentities($item["comment"]); ?></td> + <td><?php echo date("Y/m/d H:i", $item["created"]); ?></td> + <td> + <?php if ($item["access_level"] == "full"): ?> + <span class="glyphicon glyphicon-warning-sign"></span> + <?php endif; ?> + <?php echo $item["access_level"]; ?> + </td> + <td> + <?php echo form_open("user/delete_apikey", array("style" => "margin-bottom: 0")); ?> + <?php echo form_hidden("key", $item["key"]); ?> + <button class="btn btn-danger btn-xs" type="submit">Delete</input> + </form> + </td> + </tr> + <?php endforeach; ?> + </tbody> + </table> +</div> + +<h3>Access levels:</h3> + +<dl class="dl-horizontal"> + <dt>basic</dt> + <dd>Allows uploading files.</dd> + <dt>apikey</dt> + <dd>Allows removing existing files and viewing the history. Includes <code>basic</code>.</dd> + <dt>full</dt> + <dd>Allows everything, including, but not limited to, creating and removing api keys, changing profile settings and creating invitation keys. Includes <code>apikey</code>.</dd> + +<p> + <?php echo form_open('user/create_apikey', array("class" => "form-inline")); ?> + <input type="text" name="comment" placeholder="Comment" class="form-control" style="width: 200px;"/> + <select name="access_level" class="form-control" style="width: 100px;"> + <option>basic</option> + <option selected="selected">apikey</option> + <option>full</option> + </select> + <input class="btn btn-primary" type="submit" value="Create a new key" name="process" /> +</form> +</p> diff --git a/application/views/user/hash_password.php b/application/views/user/hash_password.php new file mode 100644 index 000000000..98bef9df5 --- /dev/null +++ b/application/views/user/hash_password.php @@ -0,0 +1,38 @@ +<?php +if (!empty($error)) { + echo "<p class='alert alert-danger'>"; + echo implode("<br />\n", $error); + echo "</p>"; +} + +if ($hash) { + echo "<p>Result (this hash uses a random salt, so it will be different each time you submit this form):<br />$hash</p>\n"; +} +?> +<?php echo form_open('user/hash_password'); ?> +<div class="row"> + <div class="form-group col-lg-10 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPassword" name="password" placeholder="Password" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-10 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Confirm password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPasswordConfirm" name="password_confirm" placeholder="Password confirmation" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-10 col-md-10"> + <div class="col-lg-offset-2 col-lg-5 col-md-offset-2 col-md-5"> + <button type="submit" class="btn btn-primary" name="process">Hash it</button> + </div> + </div> +</form> + diff --git a/application/views/user/index.php b/application/views/user/index.php new file mode 100644 index 000000000..9e6f48116 --- /dev/null +++ b/application/views/user/index.php @@ -0,0 +1,3 @@ +<ul class="nav"> +<?php include "nav.php"; ?> +</ul> diff --git a/application/views/user/invite.php b/application/views/user/invite.php new file mode 100644 index 000000000..d3e2fb7a6 --- /dev/null +++ b/application/views/user/invite.php @@ -0,0 +1,39 @@ +<div class="alert alert-warning"> + <p> + <b>Watch out!</b> + </p> + <p> + You are free to invite anyone you want to, but please keep in + mind that if this person violates the rules and is banned, your + account will also be disabled. + </p> +</div> + +<h2>Unused invitation keys</h2> +<div class="table-responsive"> + <table class="table table-striped"> + <thead> + <tr> + <th>#</th> + <th style="width: 70%;">Key</th> + <th>Created on</th> + </tr> + </thead> + <tbody> + <?php $i = 1; ?> + <?php foreach($query as $key => $item): ?> + <tr> + <td><?php echo $i++; ?></td> + <td><?php echo anchor("user/register/".$item["key"], $item["key"]) ?></td> + <td><?php echo date("Y/m/d H:i", $item["date"]) ?></td> + </tr> + <?php endforeach; ?> + </tbody> + </table> +</div> + +<p> + <?php echo form_open('user/create_invitation_key'); ?> + <input class="btn btn-primary btn-large" type="submit" value="Create a new key" name="process" /> + </form> +</p> diff --git a/application/views/user/login.php b/application/views/user/login.php new file mode 100644 index 000000000..3e30d53bd --- /dev/null +++ b/application/views/user/login.php @@ -0,0 +1,26 @@ +<?php +if (isset($login_error)) { ?> + <div class="alert alert-danger">The entered credentials are invalid.</div> +<?php } ?> + +<?php echo form_open('user/login', array("class" => "form-horizontal login-page")); ?> + <div class="form-group"> + <label class="control-label" for="inputUsername">Username</label> + <div class="controls"> + <input type="text" id="inputUsername" name="username" placeholder="Username" class="form-control"> + </div> + </div> + + <div class="form-group"> + <label class="control-label" for="inputPassword">Password</label> + <div class="controls"> + <input type="password" id="inputPassword" name="password" placeholder="Password" class="form-control"> + </div> + </div> + + <div class="form-group"> + <div class="controls"> + <button type="submit" class="btn btn-primary" name="process">Login</button> + </div> + </div> +</form> diff --git a/application/views/user/nav.php b/application/views/user/nav.php new file mode 100644 index 000000000..49c7aa988 --- /dev/null +++ b/application/views/user/nav.php @@ -0,0 +1,11 @@ +<?php if(auth_driver_function_implemented("can_register_new_users")) { ?> +<li><a href="<?php echo site_url("user/invite") ?>"><span class="glyphicon glyphicon-plus"></span> Invite</a></li> +<?php } ?> + +<li><a href="<?php echo site_url("user/profile") ?>"><span class="glyphicon glyphicon-user"></span> Profile</a></li> +<li><a href="<?php echo site_url("user/apikeys") ?>"><span class="glyphicon glyphicon-tags"></span> API keys</a></li> + +<?php if(auth_driver_function_implemented("can_reset_password")) { ?> +<li><a href="<?php echo site_url("user/reset_password") ?>"><span class="glyphicon glyphicon-lock"></span> Change password</a></li> +<?php } ?> + diff --git a/application/views/user/profile.php b/application/views/user/profile.php new file mode 100644 index 000000000..74d786d3f --- /dev/null +++ b/application/views/user/profile.php @@ -0,0 +1,40 @@ +<?php echo form_open("user/profile"); ?> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputUsername">Username</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputUsername" name="username" placeholder="Username" disabled="disabled" value="<?php echo $profile_data["username"]; ?>" class="form-control"> + </div> + </div> +</div> + +<?php if($profile_data["email"] !== null) { ?> +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputEmail">Email</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputEmail" name="email" placeholder="Email" disabled="disabled" value="<?php echo $profile_data["email"]; ?>" class="form-control"> + </div> + </div> +</div> +<?php } ?> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputUploadIDLimits">Upload ID length limits</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputUploadIDLimits" name="upload_id_limits" placeholder="number-number" value="<?php echo $profile_data["upload_id_limits"]; ?>" class="form-control"> + <span class="help-block">Values have to be between 3 and 64 inclusive. Please remember that longer IDs don't protect your pastes from being found if you post the link somewhere a search enginge can see it.</span> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <div class="col-lg-offset-2 col-lg-5 col-md-offset-2 col-md-5"> + <button type="submit" class="btn btn-primary" name="process">Save changes</button> + </div> + </div> +</div> +</form> diff --git a/application/views/user/register.php b/application/views/user/register.php new file mode 100644 index 000000000..af4558ff9 --- /dev/null +++ b/application/views/user/register.php @@ -0,0 +1,51 @@ +<?php if (!empty($error)) { + echo "<p class='alert alert-danger'>"; + echo implode("<br />\n", $error); + echo "</p>"; +} ?> +<?php echo form_open('user/register/'.$key); ?> +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputUsername">Username</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputUsername" name="username" placeholder="Username" value="<?php echo $values["username"]; ?>" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputEmail">Email</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputEmail" name="email" placeholder="Email" value="<?php echo $values["email"]; ?>" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPassword" name="password" placeholder="Password" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Confirm password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPasswordConfirm" name="password_confirm" placeholder="Password confirmation" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <div class="col-lg-offset-2 col-lg-5 col-md-offset-2 col-md-5"> + <button type="submit" class="btn btn-primary" name="process">Register</button> + </div> + </div> +</div> +</form> + diff --git a/application/views/user/registered.php b/application/views/user/registered.php new file mode 100644 index 000000000..f13006aae --- /dev/null +++ b/application/views/user/registered.php @@ -0,0 +1,3 @@ +<div class="center"> + <p>Your account has been created, you may log in now.</p> +</div> diff --git a/application/views/user/reset_password_form.php b/application/views/user/reset_password_form.php new file mode 100644 index 000000000..9c8253189 --- /dev/null +++ b/application/views/user/reset_password_form.php @@ -0,0 +1,33 @@ +<?php if (!empty($error)) { + echo "<p class='alert alert-danger'>"; + echo implode("<br />\n", $error); + echo "</p>"; +} ?> +<?php echo form_open('user/reset_password/'.$key); ?> +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPassword" name="password" placeholder="Password" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputPassword">Confirm password</label> + <div class="col-lg-5 col-md-5"> + <input type="password" id="inputPasswordConfirm" name="password_confirm" placeholder="Password confirmation" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <div class="col-lg-offset-2 col-lg-5 col-md-offset-2 col-md-5"> + <button type="submit" class="btn btn-primary" name="process">Change password</button> + </div> + </div> +</div> +</form> + diff --git a/application/views/user/reset_password_link_sent.php b/application/views/user/reset_password_link_sent.php new file mode 100644 index 000000000..a5b249f89 --- /dev/null +++ b/application/views/user/reset_password_link_sent.php @@ -0,0 +1,3 @@ +<p> + A mail containing your password reset link has been sent to your email address at <?php echo htmlentities($email_domain); ?>. +</p> diff --git a/application/views/user/reset_password_success.php b/application/views/user/reset_password_success.php new file mode 100644 index 000000000..bc7448833 --- /dev/null +++ b/application/views/user/reset_password_success.php @@ -0,0 +1,3 @@ +<div class="center"> + <p>Your password has been changed successfully.</p> +</div> diff --git a/application/views/user/reset_password_username_form.php b/application/views/user/reset_password_username_form.php new file mode 100644 index 000000000..713cd4919 --- /dev/null +++ b/application/views/user/reset_password_username_form.php @@ -0,0 +1,19 @@ +<?php echo form_open('user/reset_password'); ?> +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <label class="control-label col-lg-2 col-md-2" for="inputUsername">Username</label> + <div class="col-lg-5 col-md-5"> + <input type="text" id="inputUsername" name="username" placeholder="Username" value="<?php echo isset($username) ? $username : ""; ?>" class="form-control"> + </div> + </div> +</div> + +<div class="row"> + <div class="form-group col-lg-8 col-md-10"> + <div class="col-lg-offset-2 col-lg-5 col-md-offset-2 col-md-5"> + <button type="submit" class="btn btn-primary" name="process">Send mail</button> + </div> + </div> +</div> +</form> + diff --git a/application/views/welcome_message.php b/application/views/welcome_message.php deleted file mode 100644 index 0bf5a8d2e..000000000 --- a/application/views/welcome_message.php +++ /dev/null @@ -1,88 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>Welcome to CodeIgniter</title> - - <style type="text/css"> - - ::selection{ background-color: #E13300; color: white; } - ::moz-selection{ background-color: #E13300; color: white; } - ::webkit-selection{ background-color: #E13300; color: white; } - - body { - background-color: #fff; - margin: 40px; - font: 13px/20px normal Helvetica, Arial, sans-serif; - color: #4F5155; - } - - a { - color: #003399; - background-color: transparent; - font-weight: normal; - } - - h1 { - color: #444; - background-color: transparent; - border-bottom: 1px solid #D0D0D0; - font-size: 19px; - font-weight: normal; - margin: 0 0 14px 0; - padding: 14px 15px 10px 15px; - } - - code { - font-family: Consolas, Monaco, Courier New, Courier, monospace; - font-size: 12px; - background-color: #f9f9f9; - border: 1px solid #D0D0D0; - color: #002166; - display: block; - margin: 14px 0 14px 0; - padding: 12px 10px 12px 10px; - } - - #body{ - margin: 0 15px 0 15px; - } - - p.footer{ - text-align: right; - font-size: 11px; - border-top: 1px solid #D0D0D0; - line-height: 32px; - padding: 0 10px 0 10px; - margin: 20px 0 0 0; - } - - #container{ - margin: 10px; - border: 1px solid #D0D0D0; - -webkit-box-shadow: 0 0 8px #D0D0D0; - } - </style> -</head> -<body> - -<div id="container"> - <h1>Welcome to CodeIgniter!</h1> - - <div id="body"> - <p>The page you are looking at is being generated dynamically by CodeIgniter.</p> - - <p>If you would like to edit this page you'll find it located at:</p> - <code>application/views/welcome_message.php</code> - - <p>The corresponding controller for this page is found at:</p> - <code>application/controllers/welcome.php</code> - - <p>If you are exploring CodeIgniter for the very first time, you should start by reading the <a href="user_guide/">User Guide</a>.</p> - </div> - - <p class="footer">Page rendered in <strong>{elapsed_time}</strong> seconds</p> -</div> - -</body> -</html>
\ No newline at end of file |