diff -r 5c24555aba2d filebrowser.install
--- a/filebrowser.install	Thu Mar 20 12:05:08 2008 +0300
+++ b/filebrowser.install	Thu Mar 20 15:14:54 2008 +0300
@@ -4,11 +4,13 @@ function filebrowser_schema() {
 function filebrowser_schema() {
   $schema['filebrowser'] = array(
     'fields' => array(
+      'lid' => array('type' => 'serial', 'not null' => TRUE, 'unsigned' => TRUE),
       'path' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE),
       'location' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE),
-      'can_explore' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'disp-width' => '1')
+      'can_explore' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'disp-width' => '1'),
+      'private' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0)
     ),
-    'primary key' => array('path')
+    'primary key' => array('lid')
   );
   
   return $schema;
@@ -20,4 +22,17 @@ function filebrowser_install() {
 
 function filebrowser_uninstall() {
   drupal_uninstall_schema('filebrowser');
+}
+
+/**
+ * Change PRIMARY KEY to 'lid' and add private field option.
+ */
+function filebrowser_update_1() {
+  $ret = array();
+
+  db_drop_primary_key($ret, 'filebrowser');
+  db_add_field($ret, 'filebrowser', 'lid', array('type' => 'serial', 'not null' => TRUE, 'unsigned' => TRUE), array('primary key' => array('lid')));
+  db_add_field($ret, 'filebrowser', 'private', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
+
+  return $ret;
 }

diff -r 5c24555aba2d filebrowser.module
--- a/filebrowser.module	Thu Mar 20 12:05:08 2008 +0300
+++ b/filebrowser.module	Thu Mar 20 15:14:54 2008 +0300
@@ -22,6 +22,12 @@ function filebrowser_menu() {
     'description' => t('Expose file system directories to users'),
     'type' => MENU_NORMAL_ITEM
   );
+  $items['filebrowser/download'] = array(
+    'title' => 'File download',
+    'page callback' => 'filebrowser_download',
+    'access arguments' => array('access directory files'),
+    'type' => MENU_CALLBACK
+  );
   
   return $items;
 }
@@ -30,13 +36,14 @@ function filebrowser_page($path) {
   global $user;
   
   // Grab info for the listing we're viewing
-  $listing = db_fetch_object(db_query('SELECT location, can_explore FROM {filebrowser} WHERE path = "%s"', $path));
+  $listing = db_fetch_object(db_query('SELECT lid, location, can_explore, private FROM {filebrowser} WHERE path = "%s"', $path));
   $listing->can_explore = (bool)(int)$listing->can_explore;
+  $listing->private = (bool)(int)$listing->private;
   $listing->path = $path;
   
   // Grab full Drupal path
-  $curr_dir = substr($_GET['q'], strpos($full_path, $root_dir));
-  $curr_dir = str_replace($listing->path, $listing->location, $curr_dir);
+  $curr_dir = str_replace($listing->path, $listing->location, $_GET['q']);
+  $subdir_name = str_replace($path, '', $_GET['q']);
   
   // Are we in a subdirectory?
   $is_subdir = ($listing->location != $curr_dir);
@@ -58,16 +65,20 @@ function filebrowser_page($path) {
       $full_path = $dir .'/'. $file;
       if (is_file($full_path)) {
         $f_stats = stat($full_path);
+        // change URL for private downloads
+        if ($listing->private) {
+          $full_path = 'filebrowser/download/' .$listing->lid . $subdir_name .'/'. $file;
+        }
         if ($f_stats !== false) {
           $total_size += $f_stats['size'];
           
           // Mark this file new or updated
-        	$mark_value = MARK_READ;
+          $mark_value = MARK_READ;
           if ($user->access < $f_stats['ctime']) {
-          	$mark_value = MARK_NEW;
+            $mark_value = MARK_NEW;
           }
           else if ($user->access < $f_stats['mtime']) {
-          	$mark_value = MARK_UPDATED;
+            $mark_value = MARK_UPDATED;
           }
           
           $files[] = array(l($file, $full_path) . theme('mark', $mark_value), format_size($f_stats['size']));
@@ -131,7 +142,7 @@ function filebrowser_admin_settings() {
   );
   
   // Insert listings table markup
-  $qry = db_query('SELECT path, location, can_explore FROM {filebrowser} WHERE 1');
+  $qry = db_query('SELECT path, location, can_explore, private FROM {filebrowser} WHERE 1');
   while ($o = db_fetch_object($qry)) {
     $form['paths'][$o->path] = array(
       '#type' => 'value',
@@ -144,6 +155,10 @@ function filebrowser_admin_settings() {
     $form['can_explore'][$o->path] = array(
       '#type' => 'value',
       '#value' => (bool)(int)$o->can_explore
+    );
+    $form['private'][$o->path] = array(
+      '#type' => 'value',
+      '#value' => (bool)(int)$o->private
     );
     $paths[$o->path] = '';
   }
@@ -183,6 +198,10 @@ function filebrowser_admin_settings() {
   $form['new_listing']['can_explore'] = array(
     '#type' => 'checkbox',
     '#title' => t('Do you want to allow exploring subdirectories?')
+  );
+  $form['new_listing']['private'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Private file downloads?')
   );
   
   $form['new_listing']['submit'] = array(
@@ -228,8 +247,8 @@ function filebrowser_admin_settings_vali
 }
 
 function filebrowser_admin_settings_submit_add($form, &$form_state) {
-  db_query('INSERT INTO {filebrowser} (path, location, can_explore) VALUES ("%s", "%s", %d)',
-    $form_state['values']['path'], $form_state['values']['location'], $form_state['values']['can_explore']);
+  db_query('INSERT INTO {filebrowser} (path, location, can_explore, private) VALUES ("%s", "%s", %d, %d)',
+    $form_state['values']['path'], $form_state['values']['location'], $form_state['values']['can_explore'], $form_state['values']['private']);
   
   // Make sure our entries are deleted from the menu system also
   // TODO: See if there's a lighter-weight function to update the menu
@@ -263,6 +282,7 @@ function theme_filebrowser_admin_setting
     $row[] = $stats['file_count'];
     $row[] = format_size($stats['total_size']);
     $row[] = $form['can_explore'][$key]['#value']?t('Yes'):t('No');
+    $row[] = $form['private'][$key]['#value']?t('Yes'):t('No');
     $row[] = drupal_render($form['listing_checks'][$key]);
     $rows[] = $row;
   }
@@ -273,6 +293,7 @@ function theme_filebrowser_admin_setting
     t('Files'),
     t('Size'),
     t('Explorable'),
+    t('Private'),
     t('Delete')
   );
   
@@ -306,7 +327,7 @@ function filebrowser_theme() {
  *
  */
 function filebrowser_perm() {
-  return array('view directory listing');
+  return array('view directory listing', 'access directory files');
 }
 
 // TODO: Include support for subdirectories
@@ -342,4 +363,116 @@ function theme_filebrowser_page(&$files,
   else {
     return '';
   }
+}
+
+/**
+ * Private downloading files via script.
+ */
+function filebrowser_download() {
+  $args = func_get_args();
+  $lid = (int) array_shift($args); // first argument = Listing ID
+  $filepath = implode('/', $args);
+  $mime = filebrowser_detect_mime(basename($filepath));
+  $headers = array('Content-type:' . $mime);
+  filebrowser_transfer($lid, $filepath, $headers);
+}
+
+/**
+ * Transfer file using http to client. Pipes a file through Drupal to the
+ * client. Analog of the file_transfer() core function.
+ *
+ * @param $lid List ID.
+ * @param $source File to transfer.
+ * @param $headers An array of http headers to send along with file.
+ */
+function filebrowser_transfer($lid, $source, $headers) {
+  ob_end_clean();
+
+  foreach ($headers as $header) {
+    // To prevent HTTP header injection, we delete new lines that are
+    // not followed by a space or a tab.
+    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+    $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
+    drupal_set_header($header);
+  }
+
+  $source = filebrowser_create_path($lid, $source);
+
+  // Transfer file in 1024 byte chunks to save memory usage.
+  if (is_file($source) && $fd = fopen($source, 'rb')) {
+    while (!feof($fd)) {
+      print fread($fd, 1024);
+    }
+    fclose($fd);
+  }
+  else {
+    drupal_not_found();
+  }
+  exit();
+}
+
+/**
+ * Make sure the destination is a complete path and resides in the filebrowser
+ * directory, if it is not prepend the filebrowser directory.
+ * Analog of the file_create_path() core function.
+ *
+ * @param $lid ID of the list.
+ * @param $dest A string containing the path to verify.
+ * @return A string containing the path to file, with file system directory
+ *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
+ *   configured 'files' or temp directories).
+ */
+function filebrowser_create_path($lid, $dest) {
+  if ($lid && $dest) {
+    $listing = db_fetch_object(db_query('SELECT location FROM {filebrowser} WHERE lid = %d', $lid));
+    // file_check_location() checks whether the file is inside the right directory
+    if (file_check_location($listing->location .'/'. $dest, $listing->location)) {
+      return $listing->location .'/'. $dest;
+    }
+  }
+  // File not found.
+  return FALSE;
+}
+
+/**
+ * Detect mime-type of the file.
+ */
+function filebrowser_detect_mime($filename) {
+  $filename = pathinfo($filename);
+
+  $ret = 'application/octet-stream';
+  // all other files = 'application/octet-stream'
+  $mime_types = array(
+    'text/html' => array('html', 'htm'),
+    'text/css' => array('css'),
+    'text/plain' => array('txt', 'php', 'pl', 'py', 'cgi', 'asp', 'js'),
+    'application/pdf' => array('pdf'),
+    'application/xml' => array('xml', 'xsl'),
+    'audio/mpeg' => array('mpga', 'mp2', 'mp3'),
+    'audio/x-mpegurl' => array('m3u'),
+    'audio/x-wav' => array('wav'),
+    'application/ogg' => array('ogg'),
+    'video/mpeg' => array('mpeg', 'mpg', 'mpe'),
+    'video/x-msvideo' => array('avi'),
+    'video/quicktime' => array('qt', 'mov'),
+    'video/vnd.mpegurl' => array('mxu', 'm4u'),
+    'application/zip' => array('zip', 'rar', 'tar'),
+    'application/postscript' => array('ai', 'eps', 'ps'),
+    'image/jpeg' => array('jpeg', 'jpg', 'jpe'),
+    'image/gif' => array('gif'),
+    'image/bmp' => array('bmp'),
+    'image/png' => array('png'),
+    'image/tiff' => array('tiff', 'tif'),
+    'application/msword' => array('doc', 'docx'),
+    'application/vnd.ms-excel' => array('xls', 'xlsx'),
+    'application/vnd.ms-powerpoint' => array('ppt', 'pptx')
+  );
+
+  foreach ($mime_types as $key => $val) {
+    if (in_array($filename['extension'], $val)) {
+      $ret = $key;
+    }
+  }
+
+  return $ret;
 }
