Package base :: Package includes :: Module file
[hide private]

Source Code for Module base.includes.file

   1  #!/usr/bin/env python 
   2  # $Id: file.inc,v 1.127 2008/07/05 18:34:29 dries Exp $ 
   3   
   4  """ 
   5    API for handling file uploads and server file management. 
   6   
   7    @package includes 
   8    @see <a href='http://drupy.net'>Drupy Homepage</a> 
   9    @see <a href='http://drupal.org'>Drupal Homepage</a> 
  10    @note Drupy is a port of the Drupal project. 
  11    @note This file was ported from Drupal's includes/file.inc 
  12    @author Brendon Crawford 
  13    @copyright 2008 Brendon Crawford 
  14    @contact message144 at users dot sourceforge dot net 
  15    @created 2008-01-10 
  16    @version 0.1 
  17    @note License: 
  18   
  19      This program is free software; you can redistribute it and/or 
  20      modify it under the terms of the GNU General Public License 
  21      as published by the Free Software Foundation; either version 2 
  22      of the License, or (at your option) any later version. 
  23   
  24      This program is distributed in the hope that it will be useful, 
  25      but WITHOUT ANY WARRANTY; without even the implied warranty of 
  26      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  27      GNU General Public License for more details. 
  28   
  29      You should have received a copy of the GNU General Public License 
  30      along with this program; if not, write to: 
  31       
  32      The Free Software Foundation, Inc., 
  33      51 Franklin Street, Fifth Floor, 
  34      Boston, MA  02110-1301, 
  35      USA 
  36  """ 
  37   
  38  __version__ = "$Revision: 1 $" 
  39   
  40  from lib.drupy import DrupyPHP as php 
  41   
  42  # 
  43  # Common file handling functions. 
  44  # 
  45   
  46  # 
  47  # Flag to indicate that the 'public' file download method is enabled. 
  48  # 
  49  # When using this method, files are available from a regular HTTP request, 
  50  # which provides no additional access restrictions. 
  51  # 
  52  FILE_DOWNLOADS_PUBLIC = 1 
  53   
  54  # 
  55  # Flag to indicate that the 'private' file download method is enabled. 
  56  # 
  57  # When using this method, all file requests are served by Drupal, during which 
  58  # access-control checking can be performed. 
  59  # 
  60  FILE_DOWNLOADS_PRIVATE = 2 
  61   
  62  # 
  63  # Flag used by file_create_directory() -- create directory if not present. 
  64  # 
  65  FILE_CREATE_DIRECTORY = 1 
  66   
  67  # 
  68  # Flag used by file_create_directory() -- file permissions may be changed. 
  69  # 
  70  FILE_MODIFY_PERMISSIONS = 2 
  71   
  72  # 
  73  # Flag for dealing with existing files: Append number until filename is unique. 
  74  # 
  75  FILE_EXISTS_RENAME = 0 
  76   
  77  # 
  78  # Flag for dealing with existing files: Replace the existing file. 
  79  # 
  80  FILE_EXISTS_REPLACE = 1 
  81   
  82  # 
  83  # Flag for dealing with existing files: Do nothing and return FALSE. 
  84  # 
  85  FILE_EXISTS_ERROR = 2 
  86   
  87  # 
  88  # File status -- File has been temporarily saved to the {files} tables. 
  89  # 
  90  # Drupal's file garbage collection will delete the file and remove it from the 
  91  # files table after a set period of time. 
  92  # 
  93  FILE_STATUS_TEMPORARY = 0 
  94   
  95  # 
  96  # File status -- File has been permanently saved to the {files} tables. 
  97  # 
  98  # If you wish to add custom statuses for use by contrib plugins please expand 
  99  # as binary flags and consider the first 8 bits reserved. 
 100  # (0,1,2,4,8,16,32,64,128). 
 101  # 
 102  FILE_STATUS_PERMANENT = 1 
 103   
104 -def create_url(path):
105 """ 106 Create the download path to a file. 107 108 @param path A string containing the path of the file to generate URL for. 109 @return A string containing a URL that can be used to download the file. 110 """ 111 # Strip file_directory_path from path + We only include relative paths in urls. 112 if (php.strpos(path, file_directory_path() + '/') == 0): 113 path = php.trim(php.substr(path, php.strlen(file_directory_path())), '\\/') 114 dls = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC); 115 if dls == FILE_DOWNLOADS_PUBLIC: 116 return settings.base_url + '/' + file_directory_path() + '/' + \ 117 php.str_replace('\\', '/', path) 118 elif dls == FILE_DOWNLOADS_PRIVATE: 119 return url('system/files/' + path, {'absolute' : True})
120 121 122
123 -def create_path(dest = 0):
124 """ 125 Make sure the destination is a complete path and resides in the file system 126 directory, if it is not prepend the file system directory. 127 128 @param dest A string containing the path to verify + If this value is 129 omitted, Drupal's 'files' directory will be used. 130 @return A string containing the path to file, with file system directory 131 appended if necessary, or False if the path is invalid (i.e + outside the 132 configured 'files' or temp directories). 133 """ 134 file_path = file_directory_path() 135 if (dest == 0): 136 return file_path 137 # file_check_location() checks whether the destination is inside the 138 # Drupal files directory. 139 if (file_check_location(dest, file_path)): 140 return dest 141 # check if the destination is instead inside the Drupal temporary 142 # files directory. 143 elif (file_check_location(dest, file_directory_temp())): 144 return dest 145 # Not found, try again with prefixed directory path. 146 elif (file_check_location(file_path + '/' + dest, file_path)): 147 return file_path + '/' + dest 148 # File not found. 149 return False
150 151
152 -def check_directory(directory, mode = 0, form_item = None):
153 """ 154 Check that the directory exists and is writable + Directories need to 155 have execute permissions to be considered a directory by FTP servers, etc. 156 157 @param directory A string containing the name of a directory path. 158 @param mode A Boolean value to indicate if the directory should be created 159 if it does not exist or made writable if it is read-only. 160 @param form_item An optional string containing the name of a form item that 161 any errors will be attached to + This is useful for settings forms that 162 require the user to specify a writable directory + If it can't be made to 163 work, a form error will be set preventing them from saving the settings. 164 @return False when directory not found, or True when directory exists. 165 """ 166 php.Reference.check(directory); 167 directory._ = php.rtrim(directory._, '/\\') 168 # Check if directory exists. 169 if (not php.is_dir(directory._)): 170 if ((mode & FILE_CREATE_DIRECTORY) and mkdir(directory._) != False): 171 chmod(directory._, 0775); # Necessary for non-webserver users. 172 else: 173 if (form_item): 174 form_set_error(form_item, \ 175 t('The directory %directory does not exist.', \ 176 {'%directory' : directory._})) 177 watchdog('file system', 'The directory %directory does not exist.', \ 178 {'%directory' : directory}, WATCHDOG_ERROR); 179 return False 180 # Check to see if the directory is writable. 181 if (not php.is_writable(directory._)): 182 if ((mode & FILE_MODIFY_PERMISSIONS) and not php.chmod(directory, 0775)): 183 form_set_error(form_item, t('The directory %directory is not writable', \ 184 {'%directory' : directory._})) 185 watchdog('file system', 'The directory %directory is not writable, ' + \ 186 'because it does not have the correct permissions set.', \ 187 {'%directory' : directory._}, WATCHDOG_ERROR) 188 return False 189 if ((file_directory_path() == directory._ or \ 190 file_directory_temp() == directory._) and \ 191 not php.is_file("directory/.htaccess")): 192 htaccess_lines = \ 193 "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\n" + \ 194 "Options None\nOptions +FollowSymLinks" 195 fp = fopen("directory/.htaccess", 'w') 196 if (fp and fputs(fp, htaccess_lines)): 197 fclose(fp) 198 chmod(directory._ + '/.htaccess', 0664) 199 else: 200 variables = {'%directory' : directory._, \ 201 '!htaccess' : '<br />' + php.nl2br(check_plain(htaccess_lines))} 202 form_set_error(form_item, t("Security warning: " + \ 203 "Couldn't write + htaccess file. " + \ 204 "Please create a .htaccess file in your " + \ 205 "%directory directory which contains the following lines: " + \ 206 "<code>!htaccess</code>", variables)) 207 watchdog('security', "Security warning: Couldn't write " + \ 208 ".htaccess file. Please create a .htaccess file in " + \ 209 "your %directory directory which contains the " + \ 210 "following lines: <code>not htaccess</code>", \ 211 variables, WATCHDOG_ERROR) 212 return True
213 214 215
216 -def check_path(path):
217 """ 218 Checks path to see if it is a directory, or a dir/file. 219 220 @param path A string containing a file path + This will be set to the 221 directory's path. 222 @return If the directory is not in a Drupal writable directory, False is 223 returned + Otherwise, the base name of the path is returned. 224 """ 225 php.Reference.check(path) 226 # Check if path is a directory. 227 if (file_check_directory(path)): 228 return '' 229 # Check if path is a possible dir/file. 230 filename = basename(path) 231 path = php.dirname(path) 232 if (file_check_directory(path)): 233 return filename 234 return False
235 236 237
238 -def check_location(source, directory = ''):
239 """ 240 Check if a file is really located inside directory + Should be used to make 241 sure a file specified is really located within the directory to prevent 242 exploits. 243 244 @code 245 // Returns False: 246 file_check_location('/www/example.com/files/../../../etc/passwd', \ 247 '/www/example.com/files') 248 @endcode 249 250 @param source A string set to the file to check. 251 @param directory A string where the file should be located. 252 @return FALSE for invalid path or the real path of the source. 253 """ 254 check = realpath(source) 255 if (check): 256 source = check 257 else: 258 # This file does not yet exist 259 source = realpath(php.dirname(source)) + '/' + basename(source) 260 directory = realpath(directory) 261 if (directory and php.strpos(source, directory) != 0): 262 return False 263 return source
264 265 266
267 -def copy(source, dest = 0, replace = FILE_EXISTS_RENAME):
268 """ 269 Copies a file to a new location. 270 This is a powerful function that in many ways 271 performs like an advanced version of copy(). 272 - Checks if source and dest are valid and readable/writable. 273 - Performs a file copy if source is not equal to dest. 274 - If file already exists in dest either the call will 275 error out, replace the 276 file or rename the file based on the replace parameter. 277 278 @param source A string specifying the file location of the original file. 279 This parameter will contain the resulting destination filename in case of 280 success. 281 @param dest A string containing the directory source should be copied to. 282 If this value is omitted, Drupal's 'files' directory will be used. 283 @param replace Replace behavior when the destination file already exists. 284 - FILE_EXISTS_REPLACE - Replace the existing file 285 - FILE_EXISTS_RENAME - Append _{incrementing number} until 286 the filename is unique 287 - FILE_EXISTS_ERROR - Do nothing and return False. 288 @return True for success, False for failure. 289 """ 290 php.Reference.check(source) 291 dest = file_create_path(dest) 292 directory = dest 293 basename = file_check_path(directory) 294 # Make sure we at least have a valid directory. 295 if (basename == False): 296 if hasattr(source, 'filepath'): 297 source._ = source.filepath 298 drupal_set_message(t('The selected file %file could not be ' + \ 299 'uploaded, because the destination %directory is not ' + \ 300 'properly configured.', \ 301 {'%file' : source._, '%directory' : dest}), 'error') 302 watchdog('file system', 'The selected file %file could not ' + \ 303 'be uploaded, because the destination %directory could ' + \ 304 'not be found, or because its permissions do ' + \ 305 'not allow the file to be written.', \ 306 {'%file' : source._, '%directory' : dest}, WATCHDOG_ERROR) 307 return False 308 # Process a file upload object. 309 if (php.is_object(source._)): 310 file = source._ 311 source._ = file.filepath 312 if (not basename): 313 basename = file.filename 314 source._ = php.realpath(source._) 315 if (not php.file_exists(source._)): 316 drupal_set_message(t('The selected file %file could not be copied, ' + \ 317 'because no file by that name exists. ' + \ 318 'Please check that you supplied the correct filename.', \ 319 {'%file' : source._}), 'error') 320 return False 321 # If the destination file is not specified then use the filename 322 # of the source file. 323 basename = (basename if basename else basename(source._)) 324 dest._ = directory + '/' + basename 325 # Make sure source and destination filenames are not the same, makes no sense 326 # to copy it if they are + In fact copying the file will most 327 # likely result in 328 # a 0 byte file + Which is bad. Real bad. 329 if (source._ != php.realpath(dest._)): 330 dest._ = file_destination(dest._, replace) 331 if (not dest._): 332 drupal_set_message(t('The selected file %file could not be copied, ' + \ 333 'because a file by that name already exists in the destination.', \ 334 {'%file' : source._}), 'error') 335 return False 336 if (not copy(source._, dest._)): 337 drupal_set_message(t('The selected file %file could not be copied.', \ 338 {'%file' : source._}), 'error') 339 return False 340 # Give everyone read access so that FTP'd users or 341 # non-webserver users can see/read these files, 342 # and give group write permissions so group members 343 # can alter files uploaded by the webserver. 344 chmod(dest._, 0664) 345 if (php.isset(file) and php.is_object(file)): 346 file.filename = basename 347 file.filepath = dest._ 348 source._ = file 349 else: 350 source._ = dest 351 return True # Everything went ok.
352 353 354
355 -def destination(destination, replace):
356 """ 357 Determines the destination path for a file depending on how replacement of 358 existing files should be handled. 359 360 @param destination A string specifying the desired path. 361 @param replace Replace behavior when the destination file already exists. 362 - FILE_EXISTS_REPLACE - Replace the existing file 363 - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is 364 unique 365 - FILE_EXISTS_ERROR - Do nothing and return False. 366 @return The destination file path or False if the file already exists and 367 FILE_EXISTS_ERROR was specified. 368 """ 369 if (php.file_exists(destination)): 370 if replace == FILE_EXISTS_RENAME: 371 basename = basename(destination) 372 directory = php.dirname(destination) 373 destination = file_create_filename(basename, directory) 374 elif replace == FILE_EXISTS_ERROR: 375 drupal_set_message(t('The selected file %file could not be copied, \ 376 because a file by that name already exists in the destination.', \ 377 {'%file' : destination}), 'error') 378 return False 379 return destination
380 381
382 -def move(source, dest = 0, replace = FILE_EXISTS_RENAME):
383 """ 384 Moves a file to a new location. 385 - Checks if source and dest are valid and readable/writable. 386 - Performs a file move if source is not equal to dest. 387 - If file already exists in dest either the call will error out, replace the 388 file or rename the file based on the replace parameter. 389 390 @param source A string specifying the file location of the original file. 391 This parameter will contain the resulting destination filename in case of 392 success. 393 @param dest A string containing the directory source should be copied to. 394 If this value is omitted, Drupal's 'files' directory will be used. 395 @param replace Replace behavior when the destination file already exists. 396 - FILE_EXISTS_REPLACE - Replace the existing file 397 - FILE_EXISTS_RENAME - Append _{incrementing number} until the 398 filename is unique 399 - FILE_EXISTS_ERROR - Do nothing and return False. 400 @return True for success, False for failure. 401 """ 402 php.Reference.check(source) 403 if hasattr(source, 'filepath'): 404 source._ = source.filepath 405 path_original = source._ 406 if (file_copy(source._, dest, replace)): 407 path_current = (source.filepath if hasattr(source, 'filepath') else \ 408 source._) 409 if (path_original == path_current or file_delete(path_original)): 410 return True 411 drupal_set_message(t('The removal of the original file %file ' + \ 412 'has failed.', {'%file' : path_original}), 'error') 413 return False
414 415 416
417 -def munge_filename(filename, extensions, alerts = True):
418 """ 419 Munge the filename as needed for security purposes + For instance the file 420 name "exploit.php.pps" would become "exploit.php_.pps". 421 422 @param filename The name of a file to modify. 423 @param extensions A space separated list of extensions that should not 424 be altered. 425 @param alerts Whether alerts (watchdog, drupal_set_message()) should be 426 displayed. 427 @return filename The potentially modified filename. 428 """ 429 original = filename 430 # Allow potentially insecure uploads for very savvy users and admin 431 if (not variable_get('allow_insecure_uploads', 0)): 432 whitelist = array_unique(php.explode(' ', php.trim(extensions))) 433 # Split the filename up by periods + The first part becomes the basename 434 # the last part the final extension. 435 filename_parts = php.explode('.', filename) 436 new_filename = php.array_shift(filename_parts); # Remove file basename. 437 final_extension = php.array_pop(filename_parts); # Remove final extension. 438 # Loop through the middle parts of the name and add an underscore to the 439 # end of each section that could be a file extension but isn't in the list 440 # of allowed extensions. 441 for filename_part in filename_parts: 442 new_filename += '.' + filename_part 443 if (not php.in_array(filename_part, whitelist) and \ 444 php.preg_match("/^[a-zA-Z]{2,5}\d?$/", filename_part)): 445 new_filename += '_' 446 filename = new_filename + '.' + final_extension 447 if (alerts and original != filename): 448 drupal_set_message(t('For security reasons, your upload has ' + \ 449 'been renamed to %filename.', {'%filename' : filename})) 450 return filename
451 452 453
454 -def unmunge_filename(filename):
455 """ 456 Undo the effect of upload_munge_filename(). 457 458 @param filename string filename 459 @return string 460 """ 461 return php.str_replace('_.', '.', filename)
462 463 464
465 -def create_filename(basename, directory):
466 """ 467 Create a full file path from a directory and filename + If a file with the 468 specified name already exists, an alternative will be used. 469 470 @param basename string filename 471 @param directory string directory 472 @return 473 """ 474 dest = directory + '/' + basename 475 if (php.file_exists(dest)): 476 # Destination file already exists, generate an alternative. 477 pos = strrpos(basename, '.') 478 if (pos): 479 name = php.substr(basename, 0, pos) 480 ext = php.substr(basename, pos) 481 else: 482 name = basename 483 counter = 0 484 while True: 485 dest = directory + '/' + name + '_' + counter + ext 486 counter += 1 487 if (not php.file_exists(dest)): 488 break 489 return dest
490 491 492
493 -def delete(path):
494 """ 495 Delete a file. 496 497 @param path A string containing a file path. 498 @return True for success, False for failure. 499 """ 500 if (php.is_file(path)): 501 return p.unlink(path)
502 503
504 -def space_used(uid = None):
505 """ 506 Determine total disk space used by a single user or the whole filesystem. 507 508 @param uid 509 An optional user id + A None value returns the total space used 510 by all files. 511 """ 512 if (uid != None): 513 return drupy_int(db_result(db_query(\ 514 'SELECT SUM(filesize) FROM {files} WHERE uid = %d', uid))) 515 return drupy_int(db_result(db_query('SELECT SUM(filesize) FROM {files}')))
516 517
518 -def save_upload(source, validators = {}, dest = False, \ 519 replace = FILE_EXISTS_RENAME):
520 """ 521 Saves a file upload to a new location + The source file is validated as a 522 proper upload and handled as such. 523 524 The file will be added to the files table as a temporary file. 525 Temporary files 526 are periodically cleaned + To make the file permanent file call 527 file_set_status() to change its status. 528 529 @param source 530 A string specifying the name of the upload field to save. 531 @param validators 532 An optional, associative array of callback functions used to validate the 533 file + The keys are function names and the values arrays of callback 534 parameters which will be passed in after the user and file objects + The 535 functions should return an array of error messages, an empty array 536 indicates that the file passed validation. 537 The functions will be called in 538 the order specified. 539 @param dest 540 A string containing the directory source should be copied to + If this is 541 not provided or is not writable, the temporary directory will be used. 542 @param replace 543 A boolean indicating whether an existing file of the same name in the 544 destination directory should overwritten + A False value will generate a 545 new, unique filename in the destination directory. 546 @return 547 An object containing the file information, or False 548 in the event of an error. 549 """ 550 php.static(file_save_upload, 'upload_cache', {}) 551 # Add in our check of the the file name length. 552 validators['file_validate_name_length'] = {} 553 # Return cached objects without processing since the file will have 554 # already been processed and the paths in FILES will be invalid. 555 if (php.isset(file_save_upload.uploadcache, source)): 556 return file_save_upload.uploadcache[source] 557 # If a file was uploaded, process it. 558 if (php.isset(p.FILES, 'files') and p.FILES['files']['name'][source] and \ 559 php.is_uploaded_file(p.FILES['files']['tmp_name'][source])): 560 # Check for file upload errors and return False if a 561 # lower level system error occurred. 562 # @see http://php.net/manual/en/features.file-upload.errors.php 563 if p.FILES['files']['error'][source] == UPLOAD_ERR_OK: 564 pass 565 elif p.FILES['files']['error'][source] == UPLOAD_ERR_INI_SIZE or \ 566 p.FILES['files']['error'][source] == UPLOAD_ERR_FORM_SIZE: 567 drupal_set_message(t(\ 568 'The file %file could not be saved, because it exceeds %maxsize, ' + \ 569 'the maximum allowed size for uploads.', \ 570 {'%file' : source, '%maxsize' : \ 571 format_size(file_upload_max_size())}), 'error') 572 return False 573 elif p.FILES['files']['error'][source] == UPLOAD_ERR_PARTIAL or \ 574 p.FILES['files']['error'][source] == UPLOAD_ERR_NO_FILE: 575 drupal_set_message(t('The file %file could not be saved, ' + \ 576 'because the upload did not complete.', {'%file' : source}), 'error') 577 return False 578 # Unknown error 579 else: 580 drupal_set_message(t('The file %file could not be saved. ' + \ 581 'An unknown error has occurred.', {'%file' : source}), 'error') 582 return False 583 # Build the list of non-munged extensions. 584 # @todo: this should not be here + we need to figure out the right place. 585 extensions = '' 586 for rid,name in lib_appglobals.user.roles.items(): 587 extensions += ' ' + variable_get("upload_extensions_rid", 588 variable_get('upload_extensions_default', \ 589 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp')) 590 # Begin building file object. 591 file = php.stdClass() 592 file.filename = file_munge_filename(php.trim(\ 593 basename(p.FILES['files']['name'][source]), '.'), extensions) 594 file.filepath = p.FILES['files']['tmp_name'][source] 595 file.filemime = p.FILES['files']['type'][source] 596 # Rename potentially executable files, to help prevent exploits. 597 if (php.preg_match('/\.(php|pl|py|cgi|asp|js)$/i', file.filename) and \ 598 (php.substr(file.filename, -4) != '.txt')): 599 file.filemime = 'text/plain' 600 file.filepath += '.txt' 601 file.filename += '.txt' 602 # If the destination is not provided, or is not writable, then use the 603 # temporary directory. 604 if (php.empty(dest) or file_check_path(dest) == False): 605 dest = file_directory_temp() 606 file.source = source 607 file.destination = file_destination(file_create_path(dest + '/' + \ 608 file.filename), replace) 609 file.filesize = FILES['files']['size'][source] 610 # Call the validation functions. 611 errors = {} 612 for function,args in validators.items(): 613 array_unshift(args, file) 614 errors = php.array_merge(errors, function(*args)) 615 # Check for validation errors. 616 if (not php.empty(errors)): 617 message = t('The selected file %name could not be uploaded.', \ 618 {'%name' : file.filename}) 619 if (php.count(errors) > 1): 620 message += '<ul><li>' + php.implode('</li><li>', errors) + '</li></ul>' 621 else: 622 message += ' ' + php.array_pop(errors) 623 form_set_error(source, message) 624 return False 625 # Move uploaded files from PHP's upload_tmp_dir to 626 # Drupal's temporary directory. 627 # This overcomes open_basedir restrictions for future file operations. 628 file.filepath = file.destination 629 if (not move_uploaded_file(p.FILES['files']['tmp_name'][source], \ 630 file.filepath)): 631 form_set_error(source, t('File upload error. ' + \ 632 'Could not move uploaded file.')) 633 watchdog('file', 'Upload error + Could not move uploaded file ' + \ 634 '%file to destination %destination.', \ 635 {'%file' : file.filename, '%destination' : file.filepath}) 636 return False 637 # If we made it this far it's safe to record this file in the database. 638 file.uid = lib_appglobals.user.uid 639 file.status = FILE_STATUS_TEMPORARY 640 file.timestamp = time() 641 drupal_write_record('files', file) 642 # Add file to the cache. 643 file_save_upload.upload_cache[source] = file 644 return file 645 return False
646 647 648
649 -def validate_name_length(file):
650 """ 651 Check for files with names longer than we can store in the database. 652 653 @param file 654 A Drupal file object. 655 @return 656 An array + If the file name is too long, it will contain an error message. 657 """ 658 errors = [] 659 if (php.strlen(file.filename) > 255): 660 errors.append( t('Its name exceeds the 255 characters limit. ' + \ 661 'Please rename the file and try again.') ) 662 return errors
663 664 665
666 -def validate_extensions(file, extensions):
667 """ 668 Check that the filename ends with an allowed extension + This check is not 669 enforced for the user #1. 670 671 @param file 672 A Drupal file object. 673 @param extensions 674 A string with a space separated 675 @return 676 An array + If the file extension is not allowed, 677 it will contain an error message. 678 """ 679 errors = [] 680 # Bypass validation for uid = 1. 681 if (lib_appglobals.user.uid != 1): 682 regex = '/\.(' + ereg_replace(' +', '|', \ 683 php.preg_quote(extensions)) + ')$/i' 684 if (not php.preg_match(regex, file.filename)): 685 errors.append( t('Only files with the following extensions ' + \ 686 'are allowed: %files-allowed.', {'%files-allowed' : extensions}) ) 687 return errors
688 689 690
691 -def validate_size(file, file_limit = 0, user_limit = 0):
692 """ 693 Check that the file's size is below certain limits + This check is not 694 enforced for the user #1. 695 696 @param file 697 A Drupal file object. 698 @param file_limit 699 An integer specifying the maximum file size in bytes. 700 Zero indicates that 701 no limit should be enforced. 702 @param $user_limit 703 An integer specifying the maximum number of bytes the user is allowed . 704 Zero indicates that no limit should be enforced. 705 @return 706 An array + If the file size exceeds limits, 707 it will contain an error message. 708 """ 709 errors = [] 710 # Bypass validation for uid = 1. 711 if (lib_appglobals.user.uid != 1): 712 if (file_limit and file.filesize > file_limit): 713 errors.append( t('The file is %filesize exceeding the ' + \ 714 'maximum file size of %maxsize.', \ 715 {'%filesize' : format_size(file.filesize), \ 716 '%maxsize' : format_size(file_limit)}) ) 717 total_size = file_space_used(lib_appglobals.uid) + file.filesize 718 if (user_limit and total_size > user_limit): 719 errors.append( t('The file is %filesize which would exceed ' + \ 720 'your disk quota of %quota.', \ 721 {'%filesize' : format_size(file.filesize), \ 722 '%quota' : format_size(user_limit)}) ) 723 return errors
724 725 726 727
728 -def validate_is_image(file):
729 """ 730 Check that the file is recognized by image_get_info() as an image. 731 732 @param file 733 A Drupal file object. 734 @return 735 An array + If the file is not an image, it will contain an error message. 736 """ 737 errors = [] 738 info = image_get_info(file.filepath) 739 if (not info or not php.isset(info, 'extension') or \ 740 php.empty(info['extension'])): 741 errors.append( t('Only JPEG, PNG and GIF images are allowed.') ) 742 return errors
743 744 745 746
747 -def validate_image_resolution(file, maximum_dimensions = 0, \ 748 minimum_dimensions = 0):
749 """ 750 If the file is an image verify that its dimensions are within the specified 751 maximum and minimum dimensions + Non-image files will be ignored. 752 753 @param file 754 A Drupal file object + This function may resize the file 755 affecting its size. 756 @param maximum_dimensions 757 An optional string in the form WIDTHxHEIGHT e.g + '640x480' or '85x85'. If 758 an image toolkit is installed the image will be resized down to these 759 dimensions + A value of 0 indicates no restriction on size, so resizing 760 will be attempted. 761 @param minimum_dimensions 762 An optional string in the form WIDTHxHEIGHT. 763 This will check that the image 764 meets a minimum size + A value of 0 indicates no restriction. 765 @return 766 An array + If the file is an image and did not meet the requirements, it 767 will contain an error message. 768 """ 769 php.Reference.check(file) 770 errors = [] 771 # Check first that the file is an image. 772 info = image_get_info(file.filepath) 773 if (info): 774 if (maximum_dimensions): 775 # Check that it is smaller than the given dimensions. 776 width, height = php.explode('x', maximum_dimensions) 777 if (info['width'] > width or info['height'] > height): 778 # Try to resize the image to fit the dimensions. 779 if (image_get_toolkit() and image_scale(file.filepath, \ 780 file.filepath, width, height)): 781 drupal_set_message(t('The image was resized to fit within ' + \ 782 'the maximum allowed dimensions of %dimensions pixels.', \ 783 {'%dimensions' : maximum_dimensions})) 784 # Clear the cached filesize and refresh the image information. 785 clearstatcache() 786 info = image_get_info(file.filepath) 787 file.filesize = info['file_size'] 788 else: 789 errors.append( t('The image is too large; the maximum ' + \ 790 'dimensions are %dimensions pixels.', \ 791 {'%dimensions' : maximum_dimensions}) ) 792 if (minimum_dimensions): 793 # Check that it is larger than the given dimensions. 794 width, height = php.explode('x', minimum_dimensions) 795 if (info['width'] < width or info['height'] < height): 796 errors.append( t('The image is too small; the ' + \ 797 'minimum dimensions are %dimensions pixels.', \ 798 {'%dimensions' : minimum_dimensions}) ) 799 return errors
800 801 802
803 -def save_data(data, dest, replace = FILE_EXISTS_RENAME):
804 """ 805 Save a string to the specified destination. 806 807 @param data A string containing the contents of the file. 808 @param dest A string containing the destination location. 809 @param replace Replace behavior when the destination file already exists. 810 - FILE_EXISTS_REPLACE - Replace the existing file 811 - FILE_EXISTS_RENAME - Append _{incrementing number} 812 until the filename is unique 813 - FILE_EXISTS_ERROR - Do nothing and return False. 814 815 @return A string containing the resulting filename or False on error 816 """ 817 temp = file_directory_temp() 818 # On Windows, tempnam() requires an absolute path, so we use realpath(). 819 file = tempnam(realpath(temp), 'file') 820 fp = p.fopen(file, 'wb') 821 if (not fp): 822 drupal_set_message(t('The file could not be created.'), 'error') 823 return False 824 p.fwrite(fp, data) 825 p.fclose(fp) 826 if (not file_move(file, dest, replace)): 827 return False 828 return file
829 830 831
832 -def set_status(file, status):
833 """ 834 Set the status of a file. 835 836 @param file A Drupal file object 837 @param status A status value to set the file to. 838 @return False on failure, True on success and file.status will contain the 839 status. 840 """ 841 php.Reference.check(file) 842 if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', \ 843 status, file.fid)): 844 file.status = status 845 return True 846 return False
847 848
849 -def transfer(source, headers):
850 """ 851 Transfer file using http to client + Pipes a file through Drupal to the 852 client. 853 854 @param source File to transfer. 855 @param headers An array of http headers to send along with file. 856 """ 857 ob_end_clean() 858 for php.header in headers: 859 # To prevent HTTP php.header injection, we delete new lines that are 860 # not followed by a space or a tab. 861 # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 862 php.header = php.preg_replace('/\r?\n(?not \t| )/', '', php.header) 863 drupal_set_header(php.header) 864 source = file_create_path(source) 865 # Transfer file in 1024 byte chunks to save memory usage. 866 fd = fopen(source, 'rb') 867 if (fd): 868 while (not feof(fd)): 869 print fread(fd, 1024) 870 fclose(fd) 871 else: 872 drupal_not_found() 873 exit()
874 875 876
877 -def download():
878 """ 879 Call plugins that implement hook_file_download() to find out if a file is 880 accessible and what headers it should be transferred with + If a plugin 881 returns -1 drupal_access_denied() will be returned + If one or more plugins 882 returned headers the download will start with the returned headers + If no 883 plugins respond drupal_not_found() will be returned. 884 """ 885 # Merge remainder of arguments from php.GET['q'], into relative file path. 886 args = func_get_args() 887 filepath = php.implode('/', args) 888 # Maintain compatibility with old ?file=paths saved in node bodies. 889 if (php.isset(php.GET, 'file')): 890 filepath = php.GET['file'] 891 if (php.file_exists(file_create_path(filepath))): 892 headers = plugin_invoke_all('file_download', filepath) 893 if (php.in_array(-1, headers)): 894 return drupal_access_denied() 895 if (php.count(headers)): 896 file_transfer(filepath, headers) 897 return drupal_not_found()
898 899 900
901 -def scan_directory(dir, mask, nomask = ['.', '..', 'CVS'], \ 902 callback = 0, recurse = True, key = 'filename', min_depth = 0, depth = 0):
903 """ 904 Finds all files that match a given mask in a given directory. 905 Directories and files beginning with a period are excluded; this 906 prevents hidden files and directories (such as SVN working directories) 907 from being scanned. 908 909 @param dir 910 The base directory for the scan, without trailing slash. 911 @param mask 912 The regular expression of the files to find. 913 @param nomask 914 An array of files/directories to ignore. 915 @param callback 916 The callback function to call for each match. 917 @param recurse 918 When True, the directory scan will recurse the entire tree 919 starting at the provided directory. 920 @param key 921 The key to be used for the returned array of files + Possible 922 values are "filename", for the path starting with dir, 923 "basename", for the basename of the file, and "name" for the name 924 of the file without an extension. 925 @param min_depth 926 Minimum depth of directories to return files from. 927 @param depth 928 Current depth of recursion + This parameter is only used 929 internally and should not be passed. 930 931 @return 932 An associative array (keyed on the provided key) of objects with 933 "path", "basename", and "name" members corresponding to the 934 matching files. 935 """ 936 key = (key if php.in_array(key, \ 937 ('filename', 'basename', 'name')) else 'filename') 938 files = [] 939 if php.is_dir(dir): 940 dir_files = php.scandir(dir) 941 for file in dir_files: 942 if (not php.in_array(file, nomask) and file[0] != '.'): 943 if (php.is_dir("%s/%s" % (dir, file)) and recurse): 944 # Give priority to files in this folder by 945 # merging them in after any subdirectory files. 946 files = php.array_merge(file_scan_directory("%s/%s" % (dir, file), \ 947 mask, nomask, callback, recurse, key, min_depth, depth + 1), files) 948 elif (depth >= min_depth and ereg(mask, file)): 949 # Always use this match over anything already 950 # set in files with the same $key. 951 filename = "%s/%s" % (dir, file) 952 basename_ = php.basename(file) 953 name = php.substr(basename_, 0, php.strrpos(basename_, '.')) 954 files[key] = php.stdClass() 955 files[key].filename = filename 956 files[key].basename = basename_ 957 files[key].name = name 958 if (callback): 959 callback(filename) 960 return files
961 962 963
964 -def directory_temp():
965 """ 966 Determine the default temporary directory. 967 968 @return A string containing a temp directory. 969 """ 970 temporary_directory = variable_get('file_directory_temp', None) 971 if (is_None(temporary_directory)): 972 directories = [] 973 # Has PHP been set with an upload_tmp_dir? 974 if (ini_get('upload_tmp_dir')): 975 directories.append( ini_get('upload_tmp_dir') ) 976 # Operating system specific dirs. 977 if (php.substr(PHP_OS, 0, 3) == 'WIN'): 978 directories.append( 'c:\\windows\\temp' ) 979 directories.append( 'c:\\winnt\\temp' ) 980 path_delimiter = '\\' 981 else: 982 directories.append( '/tmp' ) 983 path_delimiter = '/' 984 for directory in directories: 985 if (not temporary_directory and php.is_dir(directory)): 986 temporary_directory = directory 987 # if a directory has been found, use it, 988 # otherwise default to 'files/tmp' or 'files\\tmp' 989 temporary_directory = (temporary_directory if \ 990 (temporary_directory != None) else \ 991 (file_directory_path() + path_delimiter + 'tmp')) 992 variable_set('file_directory_temp', temporary_directory) 993 return temporary_directory
994 995 996
997 -def directory_path():
998 """ 999 Determine the default 'files' directory. 1000 1001 @return A string containing the path to Drupal's 'files' directory. 1002 """ 1003 return variable_get('file_directory_path', conf_path() + '/files')
1004 1005
1006 -def upload_max_size():
1007 """ 1008 Determine the maximum file upload size by querying the PHP settings. 1009 1010 @return 1011 A file size limit in bytes based on the PHP 1012 upload_max_filesize and post_max_size 1013 """ 1014 php.static(file_upload_max_size, 'max_size', -1) 1015 if (file_upload_max_size.max_size < 0): 1016 upload_max = parse_size(ini_get('upload_max_filesize')) 1017 post_max = parse_size(ini_get('post_max_size')) 1018 file_upload_max_size.max_size = (upload_max if \ 1019 (upload_max < post_max) else post_max) 1020 return max_size
1021