78cb3892dd9543d529c70ace71f775682e482e14
[atutor.git] / docs / include / lib / upload.php
1 <?php
2 /*
3 This is an upload script for SWFUpload that attempts to properly handle uploaded files
4 in a secure way.
5
6 Notes:
7         
8         SWFUpload doesn't send a MIME-TYPE. In my opinion this is ok since MIME-TYPE is no better than
9          file extension and is probably worse because it can vary from OS to OS and browser to browser (for the same file).
10          The best thing to do is content sniff the file but this can be resource intensive, is difficult, and can still be fooled or inaccurate.
11          Accepting uploads can never be 100% secure.
12          
13         You can't guarantee that SWFUpload is really the source of the upload.  A malicious user
14          will probably be uploading from a tool that sends invalid or false metadata about the file.
15          The script should properly handle this.
16          
17         The script should not over-write existing files.
18         
19         The script should strip away invalid characters from the file name or reject the file.
20         
21         The script should not allow files to be saved that could then be executed on the webserver (such as .php files).
22          To keep things simple we will use an extension whitelist for allowed file extensions.  Which files should be allowed
23          depends on your server configuration. The extension white-list is _not_ tied your SWFUpload file_types setting
24         
25         For better security uploaded files should be stored outside the webserver's document root.  Downloaded files
26          should be accessed via a download script that proxies from the file system to the webserver.  This prevents
27          users from executing malicious uploaded files.  It also gives the developer control over the outgoing mime-type,
28          access restrictions, etc.  This, however, is outside the scope of this script.
29         
30         SWFUpload sends each file as a separate POST rather than several files in a single post. This is a better
31          method in my opinions since it better handles file size limits, e.g., if post_max_size is 100 MB and I post two 60 MB files then
32          the post would fail (2x60MB = 120MB). In SWFupload each 60 MB is posted as separate post and we stay within the limits. This
33          also simplifies the upload script since we only have to handle a single file.
34         
35         The script should properly handle situations where the post was too large or the posted file is larger than
36          our defined max.  These values are not tied to your SWFUpload file_size_limit setting.
37         
38 */
39
40 define('AT_INCLUDE_PATH', '../');
41 require(AT_INCLUDE_PATH.'vitals.inc.php');
42
43 if (!authenticate(AT_PRIV_FILES,AT_PRIV_RETURN)) {
44         authenticate(AT_PRIV_CONTENT);
45 }
46
47
48 // Code for Session Cookie workaround
49 /*
50         if (isset($_POST["PHPSESSID"])) {
51                 session_id($_POST["PHPSESSID"]);
52         } else if (isset($_GET["PHPSESSID"])) {
53                 session_id($_GET["PHPSESSID"]);
54         }
55
56         session_start();
57 */
58
59 // Check post_max_size (http://us3.php.net/manual/en/features.file-upload.php#73762)
60         $POST_MAX_SIZE = ini_get('post_max_size');
61         $unit = strtoupper(substr($POST_MAX_SIZE, -1));
62         $multiplier = ($unit == 'M' ? 1048576 : ($unit == 'K' ? 1024 : ($unit == 'G' ? 1073741824 : 1)));
63
64         if ((int)$_SERVER['CONTENT_LENGTH'] > $multiplier*(int)$POST_MAX_SIZE && $POST_MAX_SIZE) {
65                 header("HTTP/1.1 500 Internal Server Error");
66                 echo "POST exceeded maximum allowed size.";
67                 exit(0);
68         }
69
70 // Settings
71 //      $save_path = "/home/elicochr/public_html/sos/uploads/";
72         $save_path = urldecode($_GET['path']);
73         $content_path = AT_CONTENT_DIR . $_SESSION['course_id'];
74         $pos = strpos(realpath($save_path), $content_path);
75         if ($pos === false){
76                 HandleError("Error uploading a file.");
77                 exit(0);
78         }
79         $upload_name = "Filedata";
80         $max_file_size_in_bytes = 2147483647;                           // 2GB in bytes
81         //$extension_whitelist = array("jpg", "gif", "png");    // Allowed file extensions
82         $valid_chars_regex = '.A-Z0-9_ !@#$%^&()+={}\[\]\',~`-';                                // Characters allowed in the file name (in a Regular Expression format)
83
84 // Other variables      
85         $MAX_FILENAME_LENGTH = 260;
86         $file_name = "";
87         $file_extension = "";
88         $uploadErrors = array(
89         0=>"There is no error, the file uploaded with success",
90         1=>"The uploaded file exceeds the upload_max_filesize directive in php.ini",
91         2=>"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
92         3=>"The uploaded file was only partially uploaded",
93         4=>"No file was uploaded",
94         6=>"Missing a temporary folder"
95         );
96
97
98 // Validate the upload
99         if (!isset($_FILES[$upload_name])) {
100                 HandleError("No upload found in \$_FILES for " . $upload_name);
101                 exit(0);
102         } else if (isset($_FILES[$upload_name]["error"]) && $_FILES[$upload_name]["error"] != 0) {
103                 HandleError($uploadErrors[$_FILES[$upload_name]["error"]]);
104                 exit(0);
105         } else if (!isset($_FILES[$upload_name]["tmp_name"]) || !@is_uploaded_file($_FILES[$upload_name]["tmp_name"])) {
106                 HandleError("Upload failed is_uploaded_file test.");
107                 exit(0);
108         } else if (!isset($_FILES[$upload_name]['name'])) {
109                 HandleError("File has no name.");
110                 exit(0);
111         }
112         
113 // Validate the file size (Warning: the largest files supported by this code is 2GB)
114         $file_size = @filesize($_FILES[$upload_name]["tmp_name"]);
115         if (!$file_size || $file_size > $max_file_size_in_bytes) {
116                 HandleError("File exceeds the maximum allowed size");
117                 exit(0);
118         }
119         
120         if ($file_size <= 0) {
121                 HandleError("File size outside allowed lower bound");
122                 exit(0);
123         }
124
125
126 // Validate file name (for our purposes we'll just remove invalid characters)
127         $file_name = preg_replace('/[^'.$valid_chars_regex.']|\.+$/i', "", basename($_FILES[$upload_name]['name']));
128         if (strlen($file_name) == 0 || strlen($file_name) > $MAX_FILENAME_LENGTH) {
129                 HandleError("Invalid file name");
130                 exit(0);
131         }
132
133 // Validate that we won't over-write an existing file
134         if (file_exists($save_path . $file_name)) {
135                 HandleError("File with this name already exists");
136                 exit(0);
137         }
138
139 // Validate file extension
140 $path_parts = pathinfo($_FILES[$upload_name]['name']);
141 $ext = $path_parts['extension'];
142
143 /* check if this file extension is allowed: */
144 /* $IllegalExtentions is defined in ./include/config.inc.php */
145 if (in_array($ext, $IllegalExtentions)) {
146         HandleError("Invalid file extension");
147         exit(0);
148 }
149
150
151 // Validate file contents (extension and mime-type can't be trusted)
152         /*
153                 Validating the file contents is OS and web server configuration dependant.  Also, it may not be reliable.
154                 See the comments on this page: http://us2.php.net/fileinfo
155                 
156                 Also see http://72.14.253.104/search?q=cache:3YGZfcnKDrYJ:www.scanit.be/uploads/php-file-upload.pdf+php+file+command&hl=en&ct=clnk&cd=8&gl=us&client=firefox-a
157                  which describes how a PHP script can be embedded within a GIF image file.
158                 
159                 Therefore, no sample code will be provided here.  Research the issue, decide how much security is
160                  needed, and implement a solution that meets the needs.
161         */
162
163
164 // Process the file
165         /*
166                 At this point we are ready to process the valid file. This sample code shows how to save the file. Other tasks
167                  could be done such as creating an entry in a database or generating a thumbnail.
168                  
169                 Depending on your server OS and needs you may need to set the Security Permissions on the file after it has
170                 been saved.
171         */
172         if (!@move_uploaded_file($_FILES[$upload_name]["tmp_name"], $save_path.$file_name)) {
173                 HandleError("File could not be saved.");
174                 exit(0);
175         }
176
177 // Return output to the browser (only supported by SWFUpload for Flash Player 9)
178
179         echo "File Received";
180         exit(0);
181
182
183 /* Handles the error output.  This function was written for SWFUpload for Flash Player 8 which
184 cannot return data to the server, so it just returns a 500 error. For Flash Player 9 you will
185 want to change this to return the server data you want to indicate an error and then use SWFUpload's
186 uploadSuccess to check the server_data for your error indicator. */
187 function HandleError($message) {
188         header("HTTP/1.1 500 Internal Server Error");
189         echo $message;
190 }
191 ?>