How to force downloading a file with Drupal 7

New quick post to share a feature.

To force to download a file, as usual with Drupal, there's a module for that. In this case there are even two: Download file and File force. But you may be reticent to install a module for such a basic feature! So here is the solution in a few lines of code:

Here is a snippet based on File API. It is more secure than the second one but it will not work on files not handled by Drupal (whatever file download method you're using) : thus I recommend it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * Implementation of hook_menu()
 */
function mymodule_menu() {
  $items['download/%file'] = array(
    'page callback' => 'mymodule_download_file',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
 
/**
 * Page callback for forcing a file to download
 */
function mymodule_download_file($file) {
  if($file) {
    file_transfer($file->uri, array('Content-disposition' => 'attachment; filename='.$file->filename));
  }
  else {
    return drupal_access_denied();
  }
}

You can download the file with a link like http://mysite.com/download/123 where 123 is the file id (fid) of the file being downloaded.

This second snippet may present a security issue! It is recommended to use it as a last resort when files are not handled by Drupal. To increase security, this feature will be restricted to administrators, and filtered on file extensions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
/**
 * Implementation of hook_menu()
 */
function mymodule_menu() {
  $items['download'] = array(
    'page callback' => 'mymodule_download_file',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
 
/**
 * Page callback for forcing a file to download
 */
function mymodule_download_file() {
  if (!isset($_GET['file']) || !file_exists($_GET['file'])) {
    return drupal_not_found();
  }
  $filepath = $_GET['file'];
  $realpath = realpath($path);
  $filename = basename($filepath);
  $extension = pathinfo($filepath, PATHINFO_EXTENSION);
  // Check extension and restrict to files in DRUPAL_ROOT
  if(in_array($extension, array('jpg', 'png', 'gif', 'mp4')) && substr($path, 0, strlen(DRUPAL_ROOT)) === DRUPAL_ROOT) {
    drupal_add_http_header('Content-disposition', 'attachment; filename=' . $filename);
    readfile($filepath);
  }
  else {
    return drupal_access_denied();
  }
}

Then you can create your file link as in http://mysite.com/download?file=sites/default/files/myimage.png

It's only working for the public file system handling but it is the most famous one.