public function add_photo($id) { $album = ORM::factory("item", $id); access::required("view", $album); access::required("add", $album); access::verify_csrf(); // The Flash uploader not call /start directly, so simulate it here for now. if (!batch::in_progress()) { batch::start(); } $form = $this->_get_add_form($album); // Uploadify adds its own field to the form, so validate that separately. $file_validation = new Validation($_FILES); $file_validation->add_rules("Filedata", "upload::valid", "upload::required", "upload::type[" . implode(",", legal_file::get_extensions()) . "]"); if ($form->validate() && $file_validation->validate()) { $temp_filename = upload::save("Filedata"); Event::add("system.shutdown", create_function("", "unlink(\"{$temp_filename}\");")); try { $item = ORM::factory("item"); $item->name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds $item->title = item::convert_filename_to_title($item->name); $item->parent_id = $album->id; $item->set_data_file($temp_filename); // Remove double extensions from the filename - they'll be disallowed in the model but if // we don't do it here then it'll result in a failed upload. $item->name = legal_file::smash_extensions($item->name); $path_info = @pathinfo($temp_filename); if (array_key_exists("extension", $path_info) && in_array(strtolower($path_info["extension"]), legal_file::get_movie_extensions())) { $item->type = "movie"; $item->save(); log::success("content", t("Added a movie"), html::anchor("movies/{$item->id}", t("view movie"))); } else { $item->type = "photo"; $item->save(); log::success("content", t("Added a photo"), html::anchor("photos/{$item->id}", t("view photo"))); } module::event("add_photos_form_completed", $item, $form); } catch (Exception $e) { // The Flash uploader has no good way of reporting complex errors, so just keep it simple. Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); // Ugh. I hate to use instanceof, But this beats catching the exception separately since // we mostly want to treat it the same way as all other exceptions if ($e instanceof ORM_Validation_Exception) { Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); } header("HTTP/1.1 500 Internal Server Error"); print "ERROR: " . $e->getMessage(); return; } print "FILEID: {$item->id}"; } else { header("HTTP/1.1 400 Bad Request"); print "ERROR: " . t("Invalid upload"); } }
public function smash_extensions_test() { $this->assert_equal("foo_bar.jpg", legal_file::smash_extensions("foo.bar.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("foo.bar.baz.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("foo.bar.baz.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("...foo...bar..baz...jpg")); $this->assert_equal("/path/to/foo_bar.jpg", legal_file::smash_extensions("/path/to/foo.bar.jpg")); $this->assert_equal("/path/to.to/foo_bar.jpg", legal_file::smash_extensions("/path/to.to/foo.bar.jpg")); $this->assert_equal("foo_bar-12345678.jpg", legal_file::smash_extensions("foo.bar-12345678.jpg")); }
static function upgrade($version) { $db = Database::instance(); if ($version == 1) { module::set_var("gallery", "date_format", "Y-M-d"); module::set_var("gallery", "date_time_format", "Y-M-d H:i:s"); module::set_var("gallery", "time_format", "H:i:s"); module::set_version("gallery", $version = 2); } if ($version == 2) { module::set_var("gallery", "show_credits", 1); module::set_version("gallery", $version = 3); } if ($version == 3) { $db->query("CREATE TABLE {caches} (\n `id` varchar(255) NOT NULL,\n `tags` varchar(255),\n `expiration` int(9) NOT NULL,\n `cache` text,\n PRIMARY KEY (`id`),\n KEY (`tags`))\n DEFAULT CHARSET=utf8;"); module::set_version("gallery", $version = 4); } if ($version == 4) { Cache::instance()->delete_all(); $db->query("ALTER TABLE {caches} MODIFY COLUMN `cache` LONGBLOB"); module::set_version("gallery", $version = 5); } if ($version == 5) { Cache::instance()->delete_all(); $db->query("ALTER TABLE {caches} DROP COLUMN `id`"); $db->query("ALTER TABLE {caches} ADD COLUMN `key` varchar(255) NOT NULL"); $db->query("ALTER TABLE {caches} ADD COLUMN `id` int(9) NOT NULL auto_increment PRIMARY KEY"); module::set_version("gallery", $version = 6); } if ($version == 6) { module::clear_var("gallery", "version"); module::set_version("gallery", $version = 7); } if ($version == 7) { $groups = identity::groups(); $permissions = ORM::factory("permission")->find_all(); foreach ($groups as $group) { foreach ($permissions as $permission) { // Update access intents $db->query("ALTER TABLE {access_intents} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT NULL"); // Update access cache if ($permission->name === "view") { $db->query("ALTER TABLE {items} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT FALSE"); } else { $db->query("ALTER TABLE {access_caches} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) NOT NULL DEFAULT FALSE"); } } } module::set_version("gallery", $version = 8); } if ($version == 8) { $db->query("ALTER TABLE {items} CHANGE COLUMN `left` `left_ptr` INT(9) NOT NULL;"); $db->query("ALTER TABLE {items} CHANGE COLUMN `right` `right_ptr` INT(9) NOT NULL;"); module::set_version("gallery", $version = 9); } if ($version == 9) { $db->query("ALTER TABLE {items} ADD KEY `weight` (`weight` DESC);"); module::set_version("gallery", $version = 10); } if ($version == 10) { module::set_var("gallery", "image_sharpen", 15); module::set_version("gallery", $version = 11); } if ($version == 11) { $db->query("ALTER TABLE {items} ADD COLUMN `relative_url_cache` varchar(255) DEFAULT NULL"); $db->query("ALTER TABLE {items} ADD COLUMN `slug` varchar(255) DEFAULT NULL"); // This is imperfect since some of the slugs may contain invalid characters, but it'll do // for now because we don't want a lengthy operation here. $db->query("UPDATE {items} SET `slug` = `name`"); // Flush all path caches because we're going to start urlencoding them. $db->query("UPDATE {items} SET `relative_url_cache` = NULL, `relative_path_cache` = NULL"); module::set_version("gallery", $version = 12); } if ($version == 12) { if (module::get_var("gallery", "active_site_theme") == "default") { module::set_var("gallery", "active_site_theme", "wind"); } if (module::get_var("gallery", "active_admin_theme") == "admin_default") { module::set_var("gallery", "active_admin_theme", "admin_wind"); } module::set_version("gallery", $version = 13); } if ($version == 13) { // Add rules for generating our thumbnails and resizes Database::instance()->query("UPDATE {graphics_rules} SET `operation` = CONCAT('gallery_graphics::', `operation`);"); module::set_version("gallery", $version = 14); } if ($version == 14) { $sidebar_blocks = block_manager::get_active("site_sidebar"); if (empty($sidebar_blocks)) { $available_blocks = block_manager::get_available_site_blocks(); foreach (array_keys(block_manager::get_available_site_blocks()) as $id) { $sidebar_blocks[] = explode(":", $id); } block_manager::set_active("site_sidebar", $sidebar_blocks); } module::set_version("gallery", $version = 15); } if ($version == 15) { module::set_var("gallery", "identity_provider", "user"); module::set_version("gallery", $version = 16); } // Convert block keys to an md5 hash of the module and block name if ($version == 16) { foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { $blocks = block_manager::get_active($location); $new_blocks = array(); foreach ($blocks as $block) { $new_blocks[md5("{$block[0]}:{$block[1]}")] = $block; } block_manager::set_active($location, $new_blocks); } module::set_version("gallery", $version = 17); } // We didn't like md5 hashes so convert block keys back to random keys to allow duplicates. if ($version == 17) { foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { $blocks = block_manager::get_active($location); $new_blocks = array(); foreach ($blocks as $block) { $new_blocks[random::int()] = $block; } block_manager::set_active($location, $new_blocks); } module::set_version("gallery", $version = 18); } // Rename blocks_site.sidebar to blocks_site_sidebar if ($version == 18) { $blocks = block_manager::get_active("site.sidebar"); block_manager::set_active("site_sidebar", $blocks); module::clear_var("gallery", "blocks_site.sidebar"); module::set_version("gallery", $version = 19); } // Set a default for the number of simultaneous uploads // Version 20 was reverted in 57adefc5baa7a2b0dfcd3e736e80c2fa86d3bfa2, so skip it. if ($version == 19 || $version == 20) { module::set_var("gallery", "simultaneous_upload_limit", 5); module::set_version("gallery", $version = 21); } // Update the graphics rules table so that the maximum height for resizes is 640 not 480. // Fixes ticket #671 if ($version == 21) { $resize_rule = ORM::factory("graphics_rule")->where("id", "=", "2")->find(); // make sure it hasn't been changed already $args = unserialize($resize_rule->args); if ($args["height"] == 480 && $args["width"] == 640) { $args["height"] = 640; $resize_rule->args = serialize($args); $resize_rule->save(); } module::set_version("gallery", $version = 22); } // Update slug values to be legal. We should have done this in the 11->12 upgrader, but I was // lazy. Mea culpa! if ($version == 22) { foreach (db::build()->from("items")->select("id", "slug")->where(db::expr("`slug` REGEXP '[^_A-Za-z0-9-]'"), "=", 1)->execute() as $row) { $new_slug = item::convert_filename_to_slug($row->slug); if (empty($new_slug)) { $new_slug = random::int(); } db::build()->update("items")->set("slug", $new_slug)->set("relative_url_cache", null)->where("id", "=", $row->id)->execute(); } module::set_version("gallery", $version = 23); } if ($version == 23) { $db->query("CREATE TABLE {failed_logins} (\n `id` int(9) NOT NULL auto_increment,\n `count` int(9) NOT NULL,\n `name` varchar(255) NOT NULL,\n `time` int(9) NOT NULL,\n PRIMARY KEY (`id`))\n DEFAULT CHARSET=utf8;"); module::set_version("gallery", $version = 24); } if ($version == 24) { foreach (array("logs", "tmp", "uploads") as $dir) { self::_protect_directory(VARPATH . $dir); } module::set_version("gallery", $version = 25); } if ($version == 25) { db::build()->update("items")->set("title", db::expr("`name`"))->and_open()->where("title", "IS", null)->or_where("title", "=", "")->close()->execute(); module::set_version("gallery", $version = 26); } if ($version == 26) { if (in_array("failed_logins", Database::instance()->list_tables())) { $db->query("RENAME TABLE {failed_logins} TO {failed_auths}"); } module::set_version("gallery", $version = 27); } if ($version == 27) { // Set the admin area timeout to 90 minutes module::set_var("gallery", "admin_area_timeout", 90 * 60); module::set_version("gallery", $version = 28); } if ($version == 28) { module::set_var("gallery", "credits", "Powered by <a href=\"%url\">%gallery_version</a>"); module::set_version("gallery", $version = 29); } if ($version == 29) { $db->query("ALTER TABLE {caches} ADD KEY (`key`);"); module::set_version("gallery", $version = 30); } if ($version == 30) { module::set_var("gallery", "maintenance_mode", 0); module::set_version("gallery", $version = 31); } if ($version == 31) { $db->query("ALTER TABLE {modules} ADD COLUMN `weight` int(9) DEFAULT NULL"); $db->query("ALTER TABLE {modules} ADD KEY (`weight`)"); db::update("modules")->set("weight", db::expr("`id`"))->execute(); module::set_version("gallery", $version = 32); } if ($version == 32) { $db->query("ALTER TABLE {items} ADD KEY (`left_ptr`)"); module::set_version("gallery", $version = 33); } if ($version == 33) { $db->query("ALTER TABLE {access_caches} ADD KEY (`item_id`)"); module::set_version("gallery", $version = 34); } if ($version == 34) { module::set_var("gallery", "visible_title_length", 15); module::set_version("gallery", $version = 35); } if ($version == 35) { module::set_var("gallery", "favicon_url", "lib/images/favicon.ico"); module::set_version("gallery", $version = 36); } if ($version == 36) { module::set_var("gallery", "email_from", "*****@*****.**"); module::set_var("gallery", "email_reply_to", "*****@*****.**"); module::set_var("gallery", "email_line_length", 70); module::set_var("gallery", "email_header_separator", serialize("\n")); module::set_version("gallery", $version = 37); } // Changed our minds and decided that the initial value should be empty // But don't just reset it blindly, only do it if the value is version 37 default if ($version == 37) { $email = module::get_var("gallery", "email_from", ""); if ($email == "*****@*****.**") { module::set_var("gallery", "email_from", ""); } $email = module::get_var("gallery", "email_reply_to", ""); if ($email == "*****@*****.**") { module::set_var("gallery", "email_reply_to", ""); } module::set_version("gallery", $version = 38); } if ($version == 38) { module::set_var("gallery", "show_user_profiles_to", "registered_users"); module::set_version("gallery", $version = 39); } if ($version == 39) { module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin"); module::set_version("gallery", $version = 40); } if ($version == 40) { module::clear_var("gallery", "_cache"); module::set_version("gallery", $version = 41); } if ($version == 41) { $db->query("TRUNCATE TABLE {caches}"); $db->query("ALTER TABLE {caches} DROP INDEX `key`, ADD UNIQUE `key` (`key`)"); module::set_version("gallery", $version = 42); } if ($version == 42) { $db->query("ALTER TABLE {items} CHANGE `description` `description` text DEFAULT NULL"); module::set_version("gallery", $version = 43); } if ($version == 43) { $db->query("ALTER TABLE {items} CHANGE `rand_key` `rand_key` DECIMAL(11, 10)"); module::set_version("gallery", $version = 44); } if ($version == 44) { $db->query("ALTER TABLE {messages} CHANGE `value` `value` text default NULL"); module::set_version("gallery", $version = 45); } if ($version == 45) { // Splice the upgrade_checker block into the admin dashboard at the top // of the page, but under the welcome block if it's in the first position. $blocks = block_manager::get_active("dashboard_center"); $index = count($blocks) && current($blocks) == array("gallery", "welcome") ? 1 : 0; array_splice($blocks, $index, 0, array(random::int() => array("gallery", "upgrade_checker"))); block_manager::set_active("dashboard_center", $blocks); module::set_var("gallery", "upgrade_checker_auto_enabled", true); module::set_version("gallery", $version = 46); } if ($version == 46) { module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png"); module::set_version("gallery", $version = 47); } if ($version == 47 || $version == 48) { // Add configuration variable to set timezone. Defaults to the currently // used timezone (from PHP configuration). Note that in v48 we were // setting this value incorrectly, so we're going to stomp this value for v49. module::set_var("gallery", "timezone", null); module::set_version("gallery", $version = 49); } if ($version == 49) { // In v49 we changed the Item_Model validation code to disallow files with two dots in them, // but we didn't rename any files which fail to validate, so as soon as you do anything to // change those files (eg. as a side effect of getting the url or file path) it fails to // validate. Fix those here. This might be slow, but if it times out it can just pick up // where it left off. foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where(db::expr("`name` REGEXP '\\\\..*\\\\.'"), "=", 1)->order_by("id", "asc")->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = legal_file::smash_extensions($item->name); $item->save(); } module::set_version("gallery", $version = 50); } if ($version == 50) { // In v51, we added a lock_timeout variable so that administrators could edit the time out // from 1 second to a higher variable if their system runs concurrent parallel uploads for // instance. module::set_var("gallery", "lock_timeout", 1); module::set_version("gallery", $version = 51); } if ($version == 51) { // In v52, we added functions to the legal_file helper that map photo and movie file // extensions to their mime types (and allow extension of the list by other modules). During // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error // where they were mapped to video/mp4. This corrects the existing items. db::build()->update("items")->set("mime_type", "video/x-m4v")->where("name", "REGEXP", "\\.m4v\$")->execute(); module::set_version("gallery", $version = 52); } if ($version == 52) { // In v53, we added the ability to change the default time used when extracting frames from // movies. Previously we hard-coded this at 3 seconds, so we use that as the default. module::set_var("gallery", "movie_extract_frame_time", 3); module::set_version("gallery", $version = 53); } if ($version == 53) { // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously, // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered // conflicting if their slugs were different (a rare case in practice since server_add and // uploader would give them both the same slug "foo"). Now, we check the filename without its // extension. This upgrade stanza fixes any conflicts where they were previously allowed. // This might be slow, but if it times out it can just pick up where it left off. // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one // conflict; "bar.jpg", "bar.png", and "bar.flv" are another) foreach (db::build()->select_distinct(array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))")))->select(array("C" => "COUNT(\"*\")"))->from("items")->where("type", "<>", "album")->having("C", ">", 1)->group_by("parent_base_name")->execute() as $conflict) { list($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2); $base_name_escaped = Database::escape_for_like($base_name); // Loop through the items for each conflict foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where("parent_id", "=", $parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->limit(1000000)->offset(1)->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = $item->name; // this will force Item_Model to check for conflicts on save $item->save(); } } module::set_version("gallery", $version = 54); } if ($version == 54) { $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)"); module::set_version("gallery", $version = 55); } if ($version == 55) { // In v56, we added the ability to change the default behavior regarding movie uploads. It // can be set to "always", "never", or "autodetect" to match the previous behavior where they // are allowed only if FFmpeg is found. module::set_var("gallery", "movie_allow_uploads", "autodetect"); module::set_version("gallery", $version = 56); } if ($version == 56) { // Cleanup possible instances where resize_dirty of albums or movies was set to 0. This is // unlikely to have occurred, and doesn't currently matter much since albums and movies don't // have resize images anyway. However, it may be useful to be consistent here going forward. db::build()->update("items")->set("resize_dirty", 1)->where("type", "<>", "photo")->execute(); module::set_version("gallery", $version = 57); } if ($version == 57) { // In v58 we changed the Item_Model validation code to disallow files or directories with // backslashes in them, and we need to fix any existing items that have them. This is // pretty unlikely, as having backslashes would have probably already caused other issues for // users, but we should check anyway. This might be slow, but if it times out it can just // pick up where it left off. foreach (db::build()->from("items")->select("id")->where(db::expr("`name` REGEXP '\\\\\\\\'"), "=", 1)->order_by("id", "asc")->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = str_replace("\\", "_", $item->name); $item->save(); } module::set_version("gallery", $version = 58); } }
public function add() { access::verify_csrf(); $form = watermark::get_add_form(); if ($form->validate()) { $file = $_POST["file"]; $pathinfo = pathinfo($file); // Forge prefixes files with "uploadfile-xxxxxxx" for uniqueness $name = preg_replace("/uploadfile-[^-]+-(.*)/", '$1', $pathinfo["basename"]); $name = legal_file::smash_extensions($name); if (!($image_info = getimagesize($file)) || !in_array($image_info[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) { message::error(t("Unable to identify this image file")); @unlink($file); return; } if (!in_array($pathinfo["extension"], legal_file::get_photo_extensions())) { switch ($image_info[2]) { case IMAGETYPE_GIF: $name = legal_file::change_extension($name, "gif"); break; case IMAGETYPE_JPEG: $name = legal_file::change_extension($name, "jpg"); break; case IMAGETYPE_PNG: $name = legal_file::change_extension($name, "png"); break; } } rename($file, VARPATH . "modules/watermark/{$name}"); module::set_var("watermark", "name", $name); module::set_var("watermark", "width", $image_info[0]); module::set_var("watermark", "height", $image_info[1]); module::set_var("watermark", "mime_type", $image_info["mime"]); module::set_var("watermark", "position", $form->add_watermark->position->value); module::set_var("watermark", "transparency", $form->add_watermark->transparency->value); $this->_update_graphics_rules(); @unlink($file); message::success(t("Watermark saved")); log::success("watermark", t("Watermark saved")); json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); } else { // rawurlencode the results because the JS code that uploads the file buffers it in an // iframe which entitizes the HTML and makes it difficult for the JS to process. If we url // encode it now, it passes through cleanly. See ticket #797. json::reply(array("result" => "error", "html" => rawurlencode((string) $form))); } // Override the application/json mime type. The dialog based HTML uploader uses an iframe to // buffer the reply, and on some browsers (Firefox 3.6) it does not know what to do with the // JSON that it gets back so it puts up a dialog asking the user what to do with it. So force // the encoding type back to HTML for the iframe. // See: http://jquery.malsup.com/form/#file-upload header("Content-Type: text/html; charset=" . Kohana::CHARSET); }
/** * Sanitize a filename for a given type (given as "photo" or "movie") and a target file format * (given as an extension). This returns a completely legal and valid filename, * or throws an exception if the type or extension given is invalid or illegal. It tries to * maintain the filename's original extension even if it's not identical to the given extension * (e.g. don't change "JPG" or "jpeg" to "jpg"). * * Note: it is not okay if the extension given is legal but does not match the type (e.g. if * extension is "mp4" and type is "photo", it will throw an exception) * * @param string $filename (with no directory) * @param string $extension (can be uppercase or lowercase) * @param string $type (as "photo" or "movie") * @return string sanitized filename (or null if bad extension argument) */ static function sanitize_filename($filename, $extension, $type) { // Check if the type is valid - if so, get the mime types of the // original and target extensions; if not, throw an exception. $original_extension = pathinfo($filename, PATHINFO_EXTENSION); switch ($type) { case "photo": $mime_type = legal_file::get_photo_types_by_extension($extension); $original_mime_type = legal_file::get_photo_types_by_extension($original_extension); break; case "movie": $mime_type = legal_file::get_movie_types_by_extension($extension); $original_mime_type = legal_file::get_movie_types_by_extension($original_extension); break; default: throw new Exception("@todo INVALID_TYPE"); } // Check if the target extension is blank or invalid - if so, throw an exception. if (!$extension || !$mime_type) { throw new Exception("@todo ILLEGAL_EXTENSION"); } // Check if the mime types of the original and target extensions match - if not, fix it. if (!$original_extension || $mime_type != $original_mime_type) { $filename = legal_file::change_extension($filename, $extension); } // It should be a filename without a directory - remove all slashes (and backslashes). $filename = str_replace("/", "_", $filename); $filename = str_replace("\\", "_", $filename); // Remove extra dots from the filename. This will also remove extraneous underscores. $filename = legal_file::smash_extensions($filename); // It's possible that the filename has no base (e.g. ".jpg") - if so, give it a generic one. if (empty($filename) || substr($filename, 0, 1) == ".") { $filename = $type . $filename; // e.g. "photo.jpg" or "movie.mp4" } return $filename; }
public function smash_extensions_pass_thru_names_without_extensions_test() { $this->assert_equal("foo", legal_file::smash_extensions("foo")); $this->assert_equal("foo.", legal_file::smash_extensions("foo.")); $this->assert_equal(".foo", legal_file::smash_extensions(".foo")); $this->assert_equal(".", legal_file::smash_extensions(".")); $this->assert_equal("", legal_file::smash_extensions("")); $this->assert_equal(null, legal_file::smash_extensions(null)); }