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; }