diff --git a/build/index.php b/build/index.php
index 595d60c..f4f8836 100644
--- a/build/index.php
+++ b/build/index.php
@@ -174,6 +174,25 @@ $settings->footer_message = "All content is under <a href='?page=License' target
 // page. May contain HTML.
 $settings->editing_message = "By submitting your edit, you are agreeing to release your changes under <a href='?action=view&page=License' target='_blank'>this license</a>. Also note that if you don't want your work to be edited by other users of this site, please don't submit it here!";
 
+// Whether to allow image uploads to the server. Currently disabled temporarily
+// for security reasons while I finish writing the file uploader.
+$settings->upload_enabled = true;
+
+// An array of mime types that are allowed to be uploaded.
+$settings->upload_allowed_file_types = [
+	"image/jpeg",
+	"image/png",
+	"image/gif",
+	"image/webp"
+];
+
+// The location of a file that maps mime types onto file extensions and vice
+// versa. Used to generate the file extension for an uploaded file. Set to the
+// default location of the mime.types file on Linux. If you aren't using linux,
+// download this pastebin and point this setting at it instead:
+// http://pastebin.com/mjM3zKjz
+$settings->mime_extension_mappings_location = "/etc/mime.types";
+
 // A string of css to include. Will be included in the <head> of every page
 // inside a <style> tag. This may also be a url - urls will be referenced via a
 // <link rel='stylesheet' /> tag.
@@ -504,6 +523,49 @@ function hide_email($str)
 	
 	return $hidden_email;
 }
+/*
+ * @summary Checks to see if $haystack starts with $needle.
+ * 
+ * @param $haystack {string} The string to search.
+ * @param $needle {string} The string to search for at the beginning of $haystack.
+ * 
+ * @returns {boolean} Whether $needle can be found at the beginning of $haystack.
+ */
+function starts_with($haystack, $needle)
+{
+     $length = strlen($needle);
+     return (substr($haystack, 0, $length) === $needle);
+}
+
+function system_extension_mime_types() {
+	global $settings;
+    # Returns the system MIME type mapping of extensions to MIME types, as defined in /etc/mime.types.
+    $out = array();
+    $file = fopen($settings->mime_extension_mappings_location, 'r');
+    while(($line = fgets($file)) !== false) {
+        $line = trim(preg_replace('/#.*/', '', $line));
+        if(!$line)
+            continue;
+        $parts = preg_split('/\s+/', $line);
+        if(count($parts) == 1)
+            continue;
+        $type = array_shift($parts);
+        foreach($parts as $part)
+            $out[$part] = $type;
+    }
+    fclose($file);
+    return $out;
+}
+function system_mime_type_extension($type) {
+    # Returns the canonical file extension for the MIME type specified, as defined in /etc/mime.types (considering the first
+    # extension listed to be canonical).
+    #
+    # $type - the MIME type
+    static $exts;
+    if(!isset($exts))
+        $exts = system_mime_type_extensions();
+    return isset($exts[$type]) ? $exts[$type] : null;
+}
 
 ///////////////////////////////////////////////////////////////////////////////////////////
 
@@ -1154,6 +1216,186 @@ register_module([
 
 
 
+
+register_module([
+	"name" => "Uploader",
+	"version" => "0.1",
+	"author" => "Starbeamrainbowlabs",
+	"description" => "Adds the ability to upload files to Pepperminty Wiki. Uploaded files act as pages and have the special 'File:' prefix.",
+	"id" => "feature-upload",
+	"code" => function() {
+		add_action("upload", function() {
+			global $settings, $env, $pageindex;
+			
+			
+			switch($_SERVER["REQUEST_METHOD"])
+			{
+				case "GET":
+					// Send upload page
+					
+					if(!$settings->upload_enabled)
+						exit(page_renderer::render("Upload Disabled - $setting->sitename", "<p>You can't upload anything at the moment because $settings->sitename has uploads disabled. Try contacting " . $settings->admindetails["name"] . ", your site Administrator. <a href='javascript:history.back();'>Go back</a>.</p>"));
+					if(!$env->is_logged_in)
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You are not currently logged in, so you can't upload anything.</p>
+		<p>Try <a href='?action=login&returnto=" . rawurlencode("?action=upload") . "'>logging in</a> first.</p>"));
+					
+					exit(page_renderer::render("Upload - $settings->sitename", "<p>Select an image below, and then type a name for it in the box. This server currently supports uploads up to " . get_max_upload_size() . " in size.</p>
+		<p>$settings->sitename currently supports uploading of the following file types: " . implode(", ", $settings->upload_allowed_file_types) . ".</p>
+		<form method='post' action='?action=upload' enctype='multipart/form-data'>
+			<label for='file'>Select a file to upload.</label>
+			<input type='file' name='file' />
+			<br />
+			<label for='name'>Name:</label>
+			<input type='text' name='name'  />
+			<br />
+			<label for='description'>Description:</label>
+			<textarea name='description'></textarea>
+			<br />
+			<input type='submit' value='Upload' />
+		</form>"));
+					
+					break;
+				
+				case "POST":
+					// Recieve file
+					
+					// Make sure uploads are enabled
+					if(!$settings->upload_enabled)
+					{
+						unlink($_FILES["file"]["tmp_name"]);
+						http_response_code(412);
+						exit(page_renderer::render("Upload failed - $settings->sitename", "<p>Your upload couldn't be processed because uploads are currently disabled on $settings->sitename. <a href='index.php'>Go back to the main page</a>.</p>"));
+					}
+					
+					// Make sure that the user is logged in
+					if(!$env->is_logged_in)
+					{
+						unlink($_FILES["file"]["tmp_name"]);
+						http_response_code(401);
+						exit(page_renderer::render("Upload failed - $settings->sitename", "<p>Your upload couldn't be processed because you are not logged in.</p><p>Try <a href='?action=login&returnto=" . rawurlencode("?action=upload") . "'>logging in</a> first."));
+					}
+					
+					// Calculate the target ename, removing any characters we
+					// are unsure about.
+					$target_name = makepathsafe($_POST["name"]);
+					$temp_filename = $_FILES["file"]["tmp_name"];
+					
+					$mimechecker = new finfo(FILEINFO_MIME_TYPE);
+					$mime_type = finfo_file($mimechecker, $temp_filename);
+					
+					// Perform appropriate checks based on the *real* filetype
+					switch(substr($mime_type, 0, strpos($mime_type, "/")))
+					{
+						case "image":
+							$extra_data = [];
+							$imagesize = getimagesize($temp_filename, $extra_data);
+							
+							// Make sure that the image size is defined
+							if(!is_int($imagesize[0]) or !is_int($imagesize))
+								exit(page_renderer::render("Upload Error - $settings->sitename", "<p>The file that you uploaded doesn't appear to be an image. $settings->sitename currently only supports uploading images (videos coming soon). <a href='?action=upload'>Go back to try again</a>.</p>"));
+							
+							break;
+						
+						case "video":
+							exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You uploaded a video, but $settings->sitename doesn't support them yet. Please try again later.</p>"));
+						
+						default:
+							exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You uploaded an unnknown file type which couldn't be processed. $settings->sitename thinks that the file you uploaded was a(n) $mime_type, which isn't supported.</p>"));
+					}
+					
+					$file_extension = system_mime_type_extension($mime_type);
+					
+					$new_filename = "Files/$target_name.$file_extension";
+					$new_description_filename = "Files/$target_name.md";
+					
+					if(isset($pageindex->$new_filename))
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>A page or file has already been uploaded with the name '$new_filename'. Try deleting it first. If you do not have permission to delete things, try contacting one of the moderators.</p>"));
+					
+					if(!file_exists("Files"))
+						mkdir("Files", 0664);
+					
+					if(!move_uploaded_file($temp_filename, $new_filename))
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>The file you uploaded was valid, but $settings->sitename couldn't verify that it was tampered with during the upload process. This probably means that $settings->sitename has been attacked. Please contact " . $settings->admindetails . ", your $settings->sitename Administrator.</p>"));
+					
+					file_put_contents($new_description_filename, $_POST["description"]);
+					
+					$description = $_POST["description"];
+					
+					if($settings->clean_raw_html)
+						$description = htmlentities($description, ENT_QUOTES);
+					
+					file_put_contents($new_description_filename, $description);
+					
+					// Construct a new entry for the pageindex
+					$entry = new stdClass();
+					$entry->filename = $new_description_filename;
+					$entry->size = strlen($description);
+					$entry->lastmodified = time();
+					$entry->lasteditor = $env->user;
+					$entry->uploadedfile = true;
+					$entry->uploadedfilepath = $new_filename;
+					// Add the new entry to the pageindex
+					$pageindex->$new_filename = $entry;
+					
+					// Save the pageindex
+					file_put_contents("pageindex.json", json_encode($pageindex, JSON_PRETTY_PRINT));
+					
+					break;
+			}
+		});
+		add_action("preview", function() {
+			global $settings;
+			
+			// todo render a preview here
+			
+			/*
+			 * size (image outputs only, possibly width / height)
+				 * 1-2048 (configurable)
+			 * filetype
+				 * either a mime type or 'native'
+			 */
+		});
+		
+		page_renderer::register_part_preprocessor(function(&$parts) {
+			// Todo add the preview to the top o fthe page here, but onyl if the current action is view and we are on a page prefixed with file:
+		});
+	}
+]);
+
+//// Pair of functions to calculate the actual maximum upload size supported by the server
+//// Lifted from Drupal by @meustrus from  Stackoverflow. Link to answer:
+//// http://stackoverflow.com/a/25370978/1460422
+// Returns a file size limit in bytes based on the PHP upload_max_filesize
+// and post_max_size
+function get_max_upload_size()
+{
+	static $max_size = -1;
+	if ($max_size < 0) {
+		// Start with post_max_size.
+		$max_size = parse_size(ini_get('post_max_size'));
+		// If upload_max_size is less, then reduce. Except if upload_max_size is
+		// zero, which indicates no limit.
+		$upload_max = parse_size(ini_get('upload_max_filesize'));
+		if ($upload_max > 0 && $upload_max < $max_size) {
+			$max_size = $upload_max;
+		}
+	}
+	return $max_size;
+}
+
+function parse_size($size) {
+	$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
+	$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
+	if ($unit) {
+		// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
+		return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
+	} else {
+		return round($size);
+	}
+}
+
+
+
 
 register_module([
 	"name" => "Credits",
diff --git a/core.php b/core.php
index 3029635..a37456f 100644
--- a/core.php
+++ b/core.php
@@ -239,6 +239,49 @@ function hide_email($str)
 	
 	return $hidden_email;
 }
+/*
+ * @summary Checks to see if $haystack starts with $needle.
+ * 
+ * @param $haystack {string} The string to search.
+ * @param $needle {string} The string to search for at the beginning of $haystack.
+ * 
+ * @returns {boolean} Whether $needle can be found at the beginning of $haystack.
+ */
+function starts_with($haystack, $needle)
+{
+     $length = strlen($needle);
+     return (substr($haystack, 0, $length) === $needle);
+}
+
+function system_extension_mime_types() {
+	global $settings;
+    # Returns the system MIME type mapping of extensions to MIME types, as defined in /etc/mime.types.
+    $out = array();
+    $file = fopen($settings->mime_extension_mappings_location, 'r');
+    while(($line = fgets($file)) !== false) {
+        $line = trim(preg_replace('/#.*/', '', $line));
+        if(!$line)
+            continue;
+        $parts = preg_split('/\s+/', $line);
+        if(count($parts) == 1)
+            continue;
+        $type = array_shift($parts);
+        foreach($parts as $part)
+            $out[$part] = $type;
+    }
+    fclose($file);
+    return $out;
+}
+function system_mime_type_extension($type) {
+    # Returns the canonical file extension for the MIME type specified, as defined in /etc/mime.types (considering the first
+    # extension listed to be canonical).
+    #
+    # $type - the MIME type
+    static $exts;
+    if(!isset($exts))
+        $exts = system_mime_type_extensions();
+    return isset($exts[$type]) ? $exts[$type] : null;
+}
 
 ///////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/module_index.json b/module_index.json
index a8b4952..c5b3b97 100644
--- a/module_index.json
+++ b/module_index.json
@@ -5,7 +5,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a utility action (that anyone can use) called hash that hashes a given string. Useful when changing a user's password.",
         "id": "action-hash",
-        "lastupdate": 1444478036,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -14,7 +14,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Exposes Pepperminty Wiki's new page protection mechanism and makes the protect button in the 'More...' menu on the top bar work.",
         "id": "action-protect",
-        "lastupdate": 1443593234,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -23,7 +23,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a 'raw' action that shows you the raw source of a page.",
         "id": "action-raw",
-        "lastupdate": 1442903519,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -32,7 +32,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a sidebar to the left hand side of every page. Add '$settings->sidebar_show = true;' to your configuration, or append '&sidebar=yes' to the url to enable. Adding to the url sets a cookie to remember your setting.",
         "id": "extra-sidebar",
-        "lastupdate": 1438776654,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -41,7 +41,16 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds support for redirect pages. Uses the same syntax that Mediawiki does.",
         "id": "feature-redirect",
-        "lastupdate": 1444295544,
+        "lastupdate": 1445170746,
+        "optional": false
+    },
+    {
+        "name": "Uploader",
+        "version": "0.1",
+        "author": "Starbeamrainbowlabs",
+        "description": "Adds the ability to upload files to Pepperminty Wiki. Uploaded files act as pages and have the special 'File:' prefix.",
+        "id": "feature-upload",
+        "lastupdate": 1445499493,
         "optional": false
     },
     {
@@ -50,7 +59,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds the credits page. You *must* have this module :D",
         "id": "page-credits",
-        "lastupdate": 1444323484,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -59,7 +68,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds an action to allow administrators to delete pages.",
         "id": "page-delete",
-        "lastupdate": 1442929182,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -68,7 +77,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Allows you to edit pages by adding the edit and save actions. You should probably include this one.",
         "id": "page-edit",
-        "lastupdate": 1443958991,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -77,7 +86,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a page that you can use to export your wiki as a .zip file. Uses $settings->export_only_allow_admins, which controls whether only admins are allowed to export the wiki.",
         "id": "page-export",
-        "lastupdate": 1442927946,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -86,7 +95,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds the help action. You really want this one.",
         "id": "page-help",
-        "lastupdate": 1432661123,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -95,7 +104,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a page that lists all the pages in the index along with their metadata.",
         "id": "page-list",
-        "lastupdate": 1444295525,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -104,7 +113,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds a pair of actions (login and checklogin) that allow users to login. You need this one if you want your users to be able to login.",
         "id": "page-login",
-        "lastupdate": 1444477827,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -113,7 +122,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds an action to let users user out. For security reasons it is wise to add this module since logging in automatically opens a session that is valid for 30 days.",
         "id": "page-logout",
-        "lastupdate": 1442928225,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -122,7 +131,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds an action to allow administrators to move pages.",
         "id": "page-move",
-        "lastupdate": 1442928409,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -131,7 +140,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Adds an update page that downloads the latest stable version of Pepperminty Wiki. This module is currently outdated as it doesn't save your module preferences.",
         "id": "page-update",
-        "lastupdate": 1442928402,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -140,7 +149,7 @@
         "author": "Starbeamrainbowlabs",
         "description": "Allows you to view pages. You reallyshould include this one.",
         "id": "page-view",
-        "lastupdate": 1443946169,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -149,7 +158,7 @@
         "author": "Johnny Broadway & Starbeamrainbowlabs",
         "description": "The default parser for Pepperminty Wiki. Based on Johnny Broadway's Slimdown (with more than a few modifications). This parser's features are documented in the help page.",
         "id": "parser-default",
-        "lastupdate": 1443964067,
+        "lastupdate": 1445170746,
         "optional": false
     },
     {
@@ -158,7 +167,7 @@
         "author": "Johnny Broadway, Emanuil Rusev & Starbeamrainbowlabs",
         "description": "An upgraded parser based on Emanuil Rusev's Parsedown Extra PHP library (https:\/\/github.com\/erusev\/parsedown-extra), which is licensed MIT. Also uses a modified Slimdown engine by Johnny Broadway in order to add support for internal links etc. Please be careful, as this module adds a _ton_ of weight to your installation.",
         "id": "parser-parsedown",
-        "lastupdate": 1443968417,
+        "lastupdate": 1445170746,
         "optional": true
     }
 ]
\ No newline at end of file
diff --git a/modules/feature-upload.php b/modules/feature-upload.php
index f299cb6..075ce9d 100644
--- a/modules/feature-upload.php
+++ b/modules/feature-upload.php
@@ -7,7 +7,7 @@ register_module([
 	"id" => "feature-upload",
 	"code" => function() {
 		add_action("upload", function() {
-			global $settings;
+			global $settings, $env, $pageindex;
 			
 			
 			switch($_SERVER["REQUEST_METHOD"])
@@ -15,33 +15,41 @@ register_module([
 				case "GET":
 					// Send upload page
 					
-					if($settings->upload_enabled && $env->is_logged_in)
-						exit(page_renderer::render("Upload - $settings->sitename", "<p>Select an image below, and then type a name for it in the box. This server currently supports uploads up to " . get_max_upload_size() . " in size.</p>
+					if(!$settings->upload_enabled)
+						exit(page_renderer::render("Upload Disabled - $setting->sitename", "<p>You can't upload anything at the moment because $settings->sitename has uploads disabled. Try contacting " . $settings->admindetails["name"] . ", your site Administrator. <a href='javascript:history.back();'>Go back</a>.</p>"));
+					if(!$env->is_logged_in)
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You are not currently logged in, so you can't upload anything.</p>
+		<p>Try <a href='?action=login&returnto=" . rawurlencode("?action=upload") . "'>logging in</a> first.</p>"));
+					
+					exit(page_renderer::render("Upload - $settings->sitename", "<p>Select an image below, and then type a name for it in the box. This server currently supports uploads up to " . get_max_upload_size() . " in size.</p>
 		<p>$settings->sitename currently supports uploading of the following file types: " . implode(", ", $settings->upload_allowed_file_types) . ".</p>
 		<form method='post' action='?action=upload' enctype='multipart/form-data'>
 			<label for='file'>Select a file to upload.</label>
 			<input type='file' name='file' />
 			<br />
-			<label for='filename'>File Name:</label>
-			<input type='text' name='filename'  />
+			<label for='name'>Name:</label>
+			<input type='text' name='name'  />
+			<br />
+			<label for='description'>Description:</label>
+			<textarea name='description'></textarea>
 			<br />
 			<input type='submit' value='Upload' />
 		</form>"));
-					else
-						exit(page_renderer::render("Error - Upload - $settings->sitename", "<p>$settings->sitename does not currently have uploads enabled, or you do not currently have permission to upload files because you are not logged in. <a href='javascript:history.back();'>Go back</a>.</p>"));
 					
 					break;
 				
 				case "POST":
 					// Recieve file
 					
-					if(!$settings->allow_uploads)
+					// Make sure uploads are enabled
+					if(!$settings->upload_enabled)
 					{
 						unlink($_FILES["file"]["tmp_name"]);
 						http_response_code(412);
 						exit(page_renderer::render("Upload failed - $settings->sitename", "<p>Your upload couldn't be processed because uploads are currently disabled on $settings->sitename. <a href='index.php'>Go back to the main page</a>.</p>"));
 					}
 					
+					// Make sure that the user is logged in
 					if(!$env->is_logged_in)
 					{
 						unlink($_FILES["file"]["tmp_name"]);
@@ -49,20 +57,70 @@ register_module([
 						exit(page_renderer::render("Upload failed - $settings->sitename", "<p>Your upload couldn't be processed because you are not logged in.</p><p>Try <a href='?action=login&returnto=" . rawurlencode("?action=upload") . "'>logging in</a> first."));
 					}
 					
-					// Calculate the target filename, removing any characters we
+					// Calculate the target ename, removing any characters we
 					// are unsure about.
-					$target_filename = preg_replace("/[^a-z0-9\-_]/i", "", $_POST["filename"]);
+					$target_name = makepathsafe($_POST["name"]);
+					$temp_filename = $_FILES["file"]["tmp_name"];
 					
-					$extra_data = [];
-					$imagesize = getimagesize($_FILES["file"]["tmp_name"], $extra_data);
-					echo("Raw file information: ");
-					var_dump($_FILES);
-					echo("Image sizing information: ");
-					var_dump($imagesize);
-					echo("Extra embedded information: ");
-					var_dump($extra_data);
+					$mimechecker = new finfo(FILEINFO_MIME_TYPE);
+					$mime_type = finfo_file($mimechecker, $temp_filename);
 					
-					unlink($_FILES["file"]["tmp_name"]);
+					// Perform appropriate checks based on the *real* filetype
+					switch(substr($mime_type, 0, strpos($mime_type, "/")))
+					{
+						case "image":
+							$extra_data = [];
+							$imagesize = getimagesize($temp_filename, $extra_data);
+							
+							// Make sure that the image size is defined
+							if(!is_int($imagesize[0]) or !is_int($imagesize))
+								exit(page_renderer::render("Upload Error - $settings->sitename", "<p>The file that you uploaded doesn't appear to be an image. $settings->sitename currently only supports uploading images (videos coming soon). <a href='?action=upload'>Go back to try again</a>.</p>"));
+							
+							break;
+						
+						case "video":
+							exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You uploaded a video, but $settings->sitename doesn't support them yet. Please try again later.</p>"));
+						
+						default:
+							exit(page_renderer::render("Upload Error - $settings->sitename", "<p>You uploaded an unnknown file type which couldn't be processed. $settings->sitename thinks that the file you uploaded was a(n) $mime_type, which isn't supported.</p>"));
+					}
+					
+					$file_extension = system_mime_type_extension($mime_type);
+					
+					$new_filename = "Files/$target_name.$file_extension";
+					$new_description_filename = "Files/$target_name.md";
+					
+					if(isset($pageindex->$new_filename))
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>A page or file has already been uploaded with the name '$new_filename'. Try deleting it first. If you do not have permission to delete things, try contacting one of the moderators.</p>"));
+					
+					if(!file_exists("Files"))
+						mkdir("Files", 0664);
+					
+					if(!move_uploaded_file($temp_filename, $new_filename))
+						exit(page_renderer::render("Upload Error - $settings->sitename", "<p>The file you uploaded was valid, but $settings->sitename couldn't verify that it was tampered with during the upload process. This probably means that $settings->sitename has been attacked. Please contact " . $settings->admindetails . ", your $settings->sitename Administrator.</p>"));
+					
+					file_put_contents($new_description_filename, $_POST["description"]);
+					
+					$description = $_POST["description"];
+					
+					if($settings->clean_raw_html)
+						$description = htmlentities($description, ENT_QUOTES);
+					
+					file_put_contents($new_description_filename, $description);
+					
+					// Construct a new entry for the pageindex
+					$entry = new stdClass();
+					$entry->filename = $new_description_filename;
+					$entry->size = strlen($description);
+					$entry->lastmodified = time();
+					$entry->lasteditor = $env->user;
+					$entry->uploadedfile = true;
+					$entry->uploadedfilepath = $new_filename;
+					// Add the new entry to the pageindex
+					$pageindex->$new_filename = $entry;
+					
+					// Save the pageindex
+					file_put_contents("pageindex.json", json_encode($pageindex, JSON_PRETTY_PRINT));
 					
 					break;
 			}
diff --git a/settings.fragment.php b/settings.fragment.php
index 981e7c6..7c6d196 100644
--- a/settings.fragment.php
+++ b/settings.fragment.php
@@ -176,13 +176,20 @@ $settings->editing_message = "By submitting your edit, you are agreeing to relea
 $settings->upload_enabled = true;
 
 // An array of mime types that are allowed to be uploaded.
-$settings->upload_allowed_types = [
+$settings->upload_allowed_file_types = [
 	"image/jpeg",
 	"image/png",
 	"image/gif",
 	"image/webp"
 ];
 
+// The location of a file that maps mime types onto file extensions and vice
+// versa. Used to generate the file extension for an uploaded file. Set to the
+// default location of the mime.types file on Linux. If you aren't using linux,
+// download this pastebin and point this setting at it instead:
+// http://pastebin.com/mjM3zKjz
+$settings->mime_extension_mappings_location = "/etc/mime.types";
+
 // A string of css to include. Will be included in the <head> of every page
 // inside a <style> tag. This may also be a url - urls will be referenced via a
 // <link rel='stylesheet' /> tag.
