Thứ Hai, 2 tháng 6, 2014

How Drupal Serves Files [Working with Files]


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 ofunctions (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 file or any other electronic resource is named using  a set of standards or “schemes” followed by “://” anthen 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 Interfacclass.
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 owstream 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 providan 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 youve specified in Configuration -> File
system and keeps tracof 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 lets 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 Drupals 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.

Heres 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 alsfile_unmanaged_copy() and  file_

unmanaged_move().

file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)


The file_copy() function copies files into Drupals 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 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 resultinobject 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 callers
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 existsPossible 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  o 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 belo 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 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 mov 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 methoand  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 chec the file system, but rather reports the sum othe 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 tappend 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