Drupal follows the same mechanism for accessing files that the UNIX operating system does, streams. Streams revolutionized
how UNIX accesses files, treating files as just another resource that can be accessed and interacted with through a common set of functions (system calls). The power of streams is that the concept of a file can be extended to include virtually anything
that is accessible electronically yet the function calls to interact with that electronic resource are the same regardless of whetherit’s a fileresiding on a disk or any other electronic resource.
The concept of streams has permeated nearly every operating system and programming language, including PHP, which
introduced the concept of a “stream wrapper” notation. What this means is that a file or any other electronic resource is named using a set of standards or “schemes” followed by “://” and then a “target”, which is essentially the path in the file system.
Drupal uses php’s stream wrapper notation for file names. public files use “public://filepath” where filepath is the directory
and name of the file. Private files use “private://filepath”, and temporary files use “temporary://filepath”. As a developer you can also write modules that implement other stream wrappers by implementing the Drupal Stream Wrapper Interface class.
the Drupal File Example module (http://drupal.org/project/examples) demon strates how you can create a new stream wrapper
for accessing information stored in $_SESSION as a demonstration of how you can write your own stream wrappers. You can also find other example stream wrappers that are included with PHP, including FILE://, FTP://, and HTTP://,
all of which can beused in your module.
Managed and Unmanaged Drupal APIs
Drupal provides two “layers” of FILE APIs, “managed” and “Unmanaged”. The “Managed” APIs provide an entry into the
file_managed table so that files can be accessed beyond the life of the current user action. Modules that use persistent files
will need to use the managed file a pis.
The Unmanaged functions provide the same functionality as the underlying PHP file APIs, however nothing about the file
being operated on is stored in the database.
To set up the file system paths and specify which download method to use, navigate to Configuration -> File
system page (see figure 14-1).
Figure 14-1. The interface for specifying file-related settings in Drupal.
The directory specified in the public and private file system path must be created and given appropriate permissions.
Public Files
The most straightforward configuration is the public file download method, in which Drupal stays out of the download
process. When files are uploaded, Drupal simply saves them in the directory you’ve specified in Configuration -> File
system and keeps track of the URLs of the files in a database table (so Drupal knows which files are available, who uploaded
them, and so on). When a file is requested, it’s transferred directly by the web server over HTTP as a static file and Drupal
isn’t involved at all. This has theadvantage of being very fast, because no PHP needs to be executed. However, no Drupal user permissions are checked.
When specifying the file system path, the folder must exist and be writable by PHP. Usually the user (on the operating
system) that is running the web server is also the same user running PHP. Thus, giving that user write permission to the
files folder allows Drupal to upload files. With that done, be sure to specify the file system path at Configuration -> File
system. Once these changes are saved, Drupal automatically creates an .htaccess file inside your files folder.
This is necessary to protect your serverfrom a known Apache security exploit allowing users to upload and execute scripts
embedded in uploaded files (see http://drupal.org/node/66763). Check to make sure your files folder contains an
.htaccess file containing the following information:
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
Options None
Options +FollowSymLinks
Private Files
In private download mode, the files folder can be located anywhere PHP may read and write, and need not be (and in most cases
ought not be) directly accessible by the web server itself. The security of private files comes at a performance cost. Rather than
delegating the work of file serving to the web server, Drupal takes on the responsibility of checking access permissions and serving
out the files, and Drupal is fully bootstrapped on every file request.
PHP Settings
A number of settings in php.ini are easy to overlook but are important for file uploads. The first is post_max_size under the Data
Handling section of php.ini. Because files are uploaded by an HTTP POST request, attempts to upload files of a size greater than
post_max_size will fail due to the amount of POST data being sent.
; Maximum size of POST data that PHP will accept.
post_max_size = 8M
The File Uploads section of php.ini contains several more important settings. Here you can determine whether file uploads are allowed
and what the maximum file size for uploaded files should be.
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
; Whether to allow HTTP file uploads.
file_uploads = On
; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
;upload_tmp_dir =
; Maximum allowed size for uploaded files.
upload_max_filesize = 20M
If file uploads seem to be failing, check that these settings are not at fault. Also, note that
upload_max_filesize should be less than post_max_size, which should be less than memory_limit:
upload_max_filesize < post_max_size < memory_limit.
Two final settings that can leave you stumped are max_execution_time and max_input_time. If your script exceeds these limits
while uploading a file, PHP will terminate your script. Check these settings if you see uploads from slow Internetconnections failing.
;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;
max_execution_time = 60
; Maximum execution time of each script, in seconds
; xdebug uses this, so set it very high for debugging max_input_time = 60
; Maximum amount of time each script may spend
; parsing request data
When debugging, you’ll want to have max_execution_time set at a high value (e.g., 1600) so the debugger does not time out.
Bear in mind, however, that if your server is very busy, it is possible to tie up Apache processes for a long time while the files are
uploaded, raising a potential scalability concern.
Media Handling
The file API (found in includes/file.inc) doesn’t provide a generic user interface for uploading files. To fill that gap for most end users,
the field API (see Chapter 4) provides the functionality to address most file upload requirements.
Upload Field
To enable file uploads for a content type, you must first add a field to the content type for uploading files. To add a field to a
content type, navigate to Structure -> Content Types. On the Content types page, click the manage fields link for the content type
that you want to add the file upload capability to and add a new file field to the content type. Once added to the content type, the
file upload field will appear on the content editing screen for that content type, as shown in Figure 14-2.
Figure 14-2. The “File attachments” field added to the node form
After a file has been uploaded on the node edit form, Drupal can add download links to uploaded files underneath the node body.
The links are visible to those who have “view uploaded files” permission, as shown in Figure 14-3.
Figure 14-3. A generic list view of files uploaded to a node
This generic solution probably isn’t robust enough for most people, so let’s see some specific examples in the following section.
Video and Audio
Numerous modules that help to manage media such as video files, Flash content, slideshows, and so on can be found at
File API
The file API lives in includes/file.inc. We’ll cover some of the commonly used functions in this section. For more, the interested
reader is directed to the API documentation to study the API in its current format: http://api.drupal.org/api/group/file/7.
Database Schema
Although Drupal stores files on disk, it still uses the database to store a fair amount of metadata about the files. In addition to
authorship, MIME type, and location, it maintains revision information for uploaded files. The schema for the file_managed tableis
shown in Table 14-1.
Table 14-1. The file_managed Table
|
|||
Field*
|
Type
|
Default
|
Description
|
fid
|
serial
|
|
Primary key
|
uid
|
int
|
0
|
User ID of the user associated with the file
|
filename
|
varchar(255)
|
''
|
Name of
the file
|
uri
|
varchar(255)
|
''
|
The URI to access the file (either local or remote)
|
filemime
|
varchar(255)
|
''
|
The MIME type of the file
|
filesize
|
int
|
0
|
Size of the file in bytes
|
status
|
int
|
0
|
Flag indicating whether file is temporary (1) or permanent (0)
|
timestamp
|
int
|
0
|
Unix timestamp indicating when file was added
|
* Bold indicates a primary key; italics indicate an indexed field.
|
The mechanism for associating uploaded files with the content that they are associated with is handled through a field_data_field_
file_xxxxxx table, where xxxxx represents the unique name assigned to that form field when it was added to the content type.
The schema for all of those tables is identical, as shown in Table 14-2.
Table 14-1. The file_managed Table
|
|||
Field*
|
Type
|
Default
|
Description
|
etid
|
int
|
0
|
The entity type id this data is attached to
|
bundle
|
|
|
The field instance bundle to which this row belongs
|
deleted
|
tinyint
|
0
|
A Boolean indicating
whether this data item
has been deleted
|
entity_id
|
int
|
|
The entity id this data is attached to (e.g.,
the node id)
|
revision_id
|
int
|
NULL
|
The entity revision id this data is attached to
|
language
|
|
|
The language for this data item
|
delta
|
|
|
The sequence number
for this data item
|
field_xxxxxx_fid
|
int
|
NULL
|
The file_managed.id being
referenced in this field, where xxxxx is replaced with the name of the field from the content type
|
field_xxxxxx_display
|
tinyint
|
1
|
Flag to control whether
this file should be displayed when viewing content
|
Field_xxxxxx_description
|
Text
|
NULL
|
A description of the file
|
* Bold indicates a primary key; italics indicate an indexed field.
|
Common Tasks and Functions
If you want to do something with a file, chances are that the File API already has a convenient function for you to use. Let’s look
at some of these.
Finding the Default Files URI
The file_default_scheme() function returns the default scheme (e.g., public or private) and can be used to define the URI where those files exist. For example, file_default_scheme().”:/” represents the default location where files are written to on file upload.
Saving Data to a File
Sometimes you just want to save data in a file. That’s what the following function does.
file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The $data parameter will become the contents of the file. The $dest parameter is the URI of the destination. The $replace parameter determines Drupal’s behavior if a file of the same name already exists at the destination. Possible values are shown in Table14-3.
Table 14-3. Constants That Determine Drupal’s Behavior When a File of the Same Name Exists at the Destination
|
|
Name
|
Meaning
|
FILE_EXISTS_REPLACE
|
Replace the existing file with the current file.
|
FILE_EXISTS_RENAME
|
Append an underscore and
integer to make the new file name unique.
|
FILE_EXISTS_ERROR
|
Abort and return FALSE.
|
Here’s a quick example that puts a short string into a file in Drupal’s file system directory:
<?php
$filename = 'testfile.txt';
$dest = file_build_uri($filename);
file_save_data('My data', $dest, FILE_EXISTS_REPLACE);
The $dest variable must contain a valid stream wrapper URI. The foregoing example utilizes the file_build_uri function to create a
valid stream wrapper URI that points to the destination directory, which in this case is the default public files directory.
Copying and Moving Files
The following functions help you work with files that are already on the file system. See also file_unmanaged_copy() and file_
unmanaged_move().
file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The file_copy() function copies files into Drupal’s file system path (typically sites/default/ files). The $source parameter is a file
object, $destination is a string containing the destination of where the file should be copied to—as a valid stream wrapper URI—and $replace is the action that Drupal should take if the file already exists in the destination directory.
file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The file_move() function works just like the file_copy() function (in fact, it calls file_unmanaged_copy()), but also removes the
original file by calling file_delete().
Checking Directories
The file_prepare_directory(&$directory, $options=FILE_MODIFY_PERMISSIONS) function checks to see whether a directory
exists and is writeable, which is a good thing to do before you attempt to write to that directory. The following example checks to
see if the sites/default/files directory exists and is writeable.
<?php
$directory = 'sites/default/files';
if (file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS)) { echo "The directory exists and is
writeable";
} else {
echo "The file does not exist or it is not writeable";
}
Uploading Files
Although field API and its file field offer a full-fledged implementation of file uploading for nodes, sometimes you just want to be
able to upload a file that is not associated with a node. The following functions can help in that situation.
file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME)
The $source parameter is a string that specifies the filepath or URI of the uploaded file to save. The $validators parameter is an
optional associative array of callback functions used to validate the file. If you don’t specify a validator, then Drupal performs
basic validation that the file extension is one of “jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp”.The $destination parameter
is a string that contains the URI of where the source should be copied to, and the $replace parameter allows you to specify whether
the uploaded file should replace an existing file, rename the file by appending an incrementingnumber to the end of the file name, or
error out. Here is the validation function from the user module that uploads the user’s picture using the file_save_upload function. The function sets three validators: test whether the file is an image, test whether theimage resolution is 85 X 85, and validate the size of the file. The image itself comes from the file upload field on the user form named “picture_upload”
(see figure 14-4) with the resulting object showing in figure 14-5.
function user_validate_picture(&$form, &$form_state) {
// If required, validate the uploaded picture.
$validators = array( 'file_validate_is_image' => array(),
'file_validate_image_resolution' => array(variable_get('user_picture_dimensions','85x85')),
'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),);
// Save the file as a temporary file.
$file = file_save_upload('picture_upload', $validators); if ($file === FALSE) {
form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));}
elseif ($file !== NULL) {
$form_state['values']['picture_upload'] = $file;}}
Figure 14-4. File field for user_picture form element as it appears on the “My account” page
Figure 14-5. Resulting file object after HTTP POST
The return value for file_save_upload() is a fully populated file object (as shown in Figure 14-6), or 0 if something went wrong.
After calling file_save_upload(), a new file exists in Drupal’s temporary directory and a new record is written to the files table.
The record contains the same values as the file object shown in Figure 14-6.
Notice that the status field is set to 0. That means that as far as Drupal is concerned, this is still a temporary file. It is the caller’s
responsibility to make the file permanent. Continuing with our example of uploading a user picture, we see that the usermodule takes
the approach of copying this file to the directory defined in Drupal’s user_picture_path variable and renaming it using the user’s ID:
The $dest parameter in the file_save_upload() function is optional and may contain the directory to which the file will be copied.
For example, when processing files attached to a node, the upload module uses file_directory_path() (which defaults tosites/default/
files) as the value for $dest (see Figure 14-6). If $dest is not provided, the temporary directory will be used.
The $replace parameter defines what Drupal should do if a file with the same name already exists. Possible values are listed in Table14-3.
Figure 14-6. The file object as it exists when passed to file_save_upload() validators
// Process picture uploads.
if (!empty($edit['picture']->fid)) {
$picture = $edit['picture'];
// If the picture is a temporary file move it to its final location and
// make it permanent.
if (($picture->status & FILE_STATUS_PERMANENT) == 0) {
$info = image_get_info($picture->uri);
$picture_directory = variable_get('file_default_scheme', 'public') . '://' .
variable_get('user_picture_path', 'pictures');
// Prepare the pictures directory.
file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
$destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-'
. $account->uid . '-' . REQUEST_TIME . '.' . $info['extension']);
if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
$picture->status |= FILE_STATUS_PERMANENT;
$edit['picture'] = file_save($picture);}}}.
This moves the uploaded image to sites/default/files/pictures/directory and makes the file permanent.
If the $dest parameter was provided and the file was moved to its final destination instead of the temporary directory, the caller can change the status of the record in the files table to permanent by calling file_save($file), with $file set to the full file object (as
shown in Figure 14-7) and the status set to FILE_STATUS_PERMANENT. According toincludes/file.inc,
if you plan to use additional status constants in your own modules, you must start with 256, as 0, 1, 2, 4, 8, 16, 32, 64, and 128 are
reserved for core. Validation functions that may be used with file_save_upload() follow.
file_validate_extensions($file, $extensions)
The $file parameter is a file object. The $extensions parameter is a string of space-delimited file extensions. The function will return an empty array if the file extension is allowed, and an array of error messages like Only files with the following extensionsare allowed: jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp if the file
extension is disallowed. This function is a possible validator for file_save_upload().
file_validate_is_image($file)
This function takes a file object and attempts to pass $file->filepath to image_get_info(). The function will return an
empty array if image_get_info() was able to extract information from the file, or an array containing the error message Only JPEG, PNG and GIF images are allowed if the process failed. This function is a possible validator for file_save_upload().
file_validate_image_resolution($file, $maximum_dimensions = 0, $minimum_ dimensions = 0)
This function takes a file object and uses $file->file path in several operations. If the file is an image, the function will check if the
image exceeds $maximum_dimensions and attempt to resize it if possible. If everything goes well, an empty array will bereturned
and the $file object, which was passed by reference, will have $file->filesize set to the new size if the image was resized.
Otherwise, the array will contain an error message, such as The image is too small; the minimum dimensions are 320x240
pixels. The $maximum_dimensions and $minimum_dimensions parameters are strings made up of width and height in pixels witha lowercase x separating them (e.g., 640x480 or 85x85). The default value of 0 indicates no restriction on size.This function is a
possible validator for file_save_upload().
file_validate_name_length($file)
The $file parameter is a file object. It returns an empty array if $file->filename exceeds 255 characters.
Otherwise, it returns an array containing an error message instructing the user to use a shorter name. This function is a possible
validator forfile_save_upload().
file_validate_size($file, $file_limit = 0, $user_limit = 0)
This function checks that a file is below a maximum limit for the file or a cumulative limit for a user. The $file paramete
is a file object that must contain $file->filesize, which is the size of the file in bytes.
The $file_limit parameter is an integer representing the maximum file size in bytes. The $user_limit parameter is an integer
representing the maximum cumulative number of bytes that the current user is allowed to use. A 0 means “no limit.” If validationpasses, an empty array will be returned; otherwise, an array containing an error will be returned. This function is a
possible validator for file_save_upload().
Getting the URL for a File
If you know the name of a file that has been uploaded and want to tell a client what the URL for that file is, the following function will help.
file_create_url($uri)
This function will return the correct URL for a file no matter whether Drupal is running in public or private download mode.
The $uri parameter is the path to the file (e.g., sites/default/files/ pictures/picture-1.jpg or pictures/picture-1.jpg).
The resulting URLmight be http://example.com/ sites/default/files/pictures/picture-1.jpg. Note that the absolute
path name to the file is not used. This makes it easier to move a Drupal site from one location (or server) to another.
Finding Files in a Directory
Drupal provides a powerful function called file_scan_directory(). It looks through a directory for files that match a given
pattern.
file_scan_directory($dir, $mask, $options = array(), $depth = 0)
Let’s walk through the function signature:
• $dir is the base directory or URI to scan, without trailing slash.
• $mask is the pattern to apply to the files that are contained in the directory. This is a regular expression.
• $options is an associative array of additional options, with the following elements:
• nomask: The preg_match() regular express of the files to ignore. This defaults to “/(\.\?|CVS)$/”.
• callback: The callback function to call for each match
• recurse: When TRUE, the directory scan will recurse the entire tree starting at the provided directory. The default is
TRUE.
• key: The key to be used for the returned associative array of files. Possible values are “uri”, for the file’s URI;
“filename”, for the basename of the file; and “name” for the name of the file without the extension. The default is “uri”.
• min_depth: Minimum depth of directories to return file from. Defaults to 0.
• $depth is the current depth of recursion. This parameter is used only internally and should not be passed in.
The return value is an associative array of objects. The key to the array depends on what is passed in the key parameter,
and defaults to filename. Following are some examples. Scan the themes/seven directory for any files ending with .css:
$found = file_scan_directory('themes/seven, '$css$');
The resulting array of objects is shown in Figure 14-7.
Changing the key parameter to the file name changes the keys of the resulting array, as shown in the following code and
Figure 14-8.
$options = array ('key' => 'filename');
$found = file_scan_directory('themes/seven', '$css$', $options);
Figure 14-8. The result is now keyed by the file name with the full file path omitted.
Finding the Temp Directory
The preferred approach for using the temporary directory is to use the temporary:// scheme. This will always point to the temporary directory that was set up on the system during the installation process.
Neutralizing Dangerous Files
Suppose you are using the public file download method and you have file uploads enabled. What will happen when someone uploads
a file named bad_exploit.php? Will it run when the attacker hits http://example.com/sites/default/files/bad_exploit.php?Hopefully not, for three reasons. The first is that .php should never be in the list of allowed extensions for uploaded files. The second isthe .htaccess file, which should be in sites/default/files/.htaccess (see Chapter 21). However, in several common Apache
configurations, uploading the file exploit.php.txt may result in code execution of the file as PHP code (see http:// drupal.org/files/
sa-2006-007/advisory.txt). That brings us to the third reason: file name munging to render the file harmless. As a defense against
uploaded executable files, the following function is used.
file_munge_filename($filename, $extensions, $alerts = TRUE)
The $filename parameter is the name of the file to modify. The $extensions parameter is a space- separated string containing file
extensions. The $alerts parameter is a Boolean value that defaults to TRUE and results in the user being alerted throughdrupal_set_
message() that the name of the file has been changed. The file name, with underscores inserted to disable potential execution, is returned
$extensions = variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp');
$filename = file_munge_filename($filename, $extensions, FALSE);
$filename is now exploit.php_.txt.
You can prevent file name munging by defining the Drupal variable allow_insecure_uploads to be 1 in settings.php. But this is
usually a bad idea given the security implications.
file_unmunge_filename($filename)
This function attempts to undo the effects of file_munge_filename() by replacing an underscore followed by a dot with a dot:
$original = file_unmunge_filename('exploit.php_.txt);
$original is now exploit.php.txt.
Note that this will also replace any intentional occurrences of _. in the original file name.
Checking Disk Space
The following function reports on space used by files.
file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT)
This function returns total disk space used by files. It does not actually check the file system, but rather reports the sum of the filesize
field in the files table in the database. If a user ID is passed to this function, the query is restricted to files that matchthat user’s ID in
the files table.
Authentication Hooks for Downloading
Module developers can implement hook_file_download() to set access permissions surrounding the download of private files. The hookis used to determine the conditions on which a file will be sent to the browser, and returns additional headers for Drupal to append in
response to the file HTTP request. Figure 14-9 shows an overview of the download process using the implementation of
hook_file_download() found in the user module as an example.
Because Drupal invokes all modules with a hook_file_download() function for each download, it’s important to specify the scope of
your hook. For example, take user_file_download(), which responds to file downloads only if the file to be downloadedis within the pictures directory. If that’s true, it appends headers to the request.
function user_file_download($uri) {
if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
$info = image_get_info($uri);
return array('Content-Type' => $info['mime_type']);
} else {
return -1;}}
Figure 14-9. Life cycle of a private file download request
Implementations of hook_file_download() should return an array of headers if the request should be granted, or -1 to state that access to the file is denied. If no modules respond to the hook, then Drupal will return a 404 Not Found error to the browser.
Summary
In this chapter, you learned
• The difference between public and private files.
• Contributed modules to use for image, video, and audio handling.
• The database schema for file storage.
• Common functions for manipulating files.
• Authentication hooks for private file downloading.
Không có nhận xét nào:
Đăng nhận xét