Index: webfm.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webfm/webfm.module,v
retrieving revision 1.48
diff -u -r1.48 webfm.module
--- webfm.module	24 Jun 2010 06:09:09 -0000	1.48
+++ webfm.module	14 Jul 2010 08:58:41 -0000
@@ -2329,153 +2331,210 @@
 
 
 /**
- * webfm_send_file - streams a file privately for download
- *
- * If $fid arg is numeric then file path is referenced via db, otherwise the arg
- * ...is a urlencoded path that must be converted and concatenated to base file dir
- *
- * @param int $fid - file id
- * @param bool $download - true=download / false=stream
- * @param bool $bypass_invoke true=do no invoke webfm_send hook  / false=invoke
+ * Implement hook_file_download
+ * 
+ * @param String of the file's path.
+ * 
+ * @return NULL|-1|array file not handeled by webfm|access denied|headers
+ */
+function webfm_file_download($filepath) {
+  if (!_webfm_managed_by_webfm($filepath)) {
+    //file is not within webfm tree
+    return NULL;
+  }
+  
+  if (_webfm_check_access($filepath)) {
+      $filename=_webfm_basename($filepath);
+      //filenames in IE containing dots will screw up the
+      //filename unless we add this
+      if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
+        $filename = preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1);
+      }
+    
+    $header = array();
+    if (_webfm_attach_new_window()) {
+      $header[] = 'Pragma: public'; // required
+      $header[] = 'Expires: 0';
+      $header[] = 'Cache-Control: must-revalidate, post-check=0, pre-check=0';
+      $header[] = 'Content-Transfer-Encoding: binary';
+      $header[] = 'Content-Disposition: inline; filename="'.$filename.'";' ;
+      
+      // consider an app run inside the browser as a 'download'
+      //TODO
+      if (!empty($f->fid) && (strpos($type, "application") === 0)) {
+        $cnt = array();
+        $cnt['dl_cnt'] = (int)$f->dl_cnt + 1;
+        webfm_dbupdate_file($f->fid, '', $cnt);
+      }   
+    } 
+    else {
+      $header[] = 'Pragma: no-cache';
+      $header[] = 'Cache-Control: no-cache, must-revalidate';
+      $header[] = 'Content-Disposition: attachment; filename="'.$filename.'";' ;
+       //TODO
+      if (!empty($f->fid)) {
+        $cnt = array();
+        $cnt['dl_cnt'] = (int)$f->dl_cnt + 1;
+        webfm_dbupdate_file($f->fid, '', $cnt);
+      }
+    }
+    
+    $header[] = 'Content-Type: '.file_get_mimetype($filename);
+    $header[] = 'Content-Length: '.(string)(@filesize($filepath));
+    $header[] = 'Connection: close';  
+    return $header;
+  }
+  else {
+    //access denied
+    return -1;
+  }
+}
+
+/**
+ * Returns or sets status whether or not the attachment should be opend in a new window 
+ * 
+ * @param boolean $status set status to true - open in a new window or to false - open inline
+ * @return boolen true - open in a new window|false - open inline
  */
-function webfm_send_file($fid, $download = false, $bypass_invoke = false) {
-  global $user;
+function _webfm_attach_new_window($status = NULL) {
+  static $webfm_attach_new_window = NULL;
 
-  $match = FALSE;
-  $f = false;
-  // User has either admin access, webfm access or view attach access
-  if(($user->uid == 1) || user_access('administer webfm')) {
-    // Admins have total access
-    $webfm_perm = WEBFM_ADMIN;
-    $match = TRUE;
-  } else if(user_access('access webfm')) {
-    $webfm_perm = WEBFM_USER;
-  } else if(user_access('view webfm attachments')) {
-    $webfm_perm = WEBFM_ATTACH_VIEW;
-  } else {
-    $webfm_perm = 0;
+  if (is_null($webfm_attach_new_window)) {
+    $webfm_attach_new_window = variable_get('webfm_attach_new_window', false);
   }
-
-  if(is_numeric($fid)) {
-    if(($f = webfm_get_file_record($fid)) === FALSE) {
-      return drupal_not_found();
-    } else {
-      if($f->uid == $user->uid)
-        // Even if file has been moved to an inaccessible dir this works
-        $match = TRUE;
-    }
-  } else if($webfm_perm == WEBFM_ADMIN) {
-    // Only allow admins to download files without fid
-    $f = new stdClass();
-    $str_arr = array();
-    $str_arr = split('[~]', rawurldecode($fid));
-    $f->fpath = file_directory_path()."/".implode('/', $str_arr);
-    if(!(is_file($f->fpath))) {
-      return drupal_not_found();
-    } else {
-      $match = TRUE;
+  
+  if (!is_null($status)) {
+    if (!status) {
+       $webfm_attach_new_window = false; 
     }
-  } else {
-    return drupal_not_found();
+    else {
+      $webfm_attach_new_window = true;
+    }
+   
   }
+  
+  return $webfm_attach_new_window;
+}
 
-  // Files that have been attached are always considered public to whoever can
-  // access that node/comment (nodeaccess/commentaccess security).
-  if($match == FALSE && $webfm_perm != WEBFM_ADMIN) {
-    if($f->perm & WEBFM_FILE_ACCESS_PUBLIC_VIEW) {
-      $match = TRUE;
-    } else if($webfm_perm == WEBFM_USER || $webfm_perm == WEBFM_ATTACH_VIEW){
-      //Check if the file is attached to a node or comment.
-      $query = 'SELECT nid,cid FROM {webfm_attach} WHERE fid = %d';
-      $result = db_query($query, $f->fid);
-      if($result !== FALSE) {
-        while ($dbfid = db_fetch_array($result)) {
-          if ($dbfid['cid'] != 0 ) {
-            // For a comment, a user must be able to view the parent node and have "access_comments".
-            if (!user_access('access comments')) {
-              continue;
-            }
-            $comment = _comment_load($dbfid['cid']);
-            $dbfid['nid'] = $comment->nid;
-          }
-          $node = node_load($dbfid['nid']);
-          if (node_access('view', $node)) {
-            $match = TRUE;
-            // Modules might use their own method of node restriction, other than node_access.
-            drupal_alter('webfm_file_access', $match, $node, $f->$fid);
-            if ($match) {
-              break;
-            }
-          }
+/**
+ * Check if the user has access to that file in webfm terms
+ * 
+ * Does different checks introduced by webfms access rules.
+ * 
+ * @param $filepath string Full path of file in question.
+ * 
+ * @return boolean  
+ */
+function _webfm_check_access($filepath) {
+  //got root? go ahead
+  if (user_access('administer webfm')) {
+    return true;
+  }
+  
+  //at first: get the file object
+  $f = webfm_get_file_record(NULL,$filepath);
+  if (!is_object($f)) {
+    return false;
+  }
+  
+  if ($f->perm & WEBFM_FILE_ACCESS_PUBLIC_VIEW) {
+    //file is available to the public
+    // $f->perm is set to Public Download and WEBFM_FILE_ACCESS_PUBLIC_VIEW is still true
+    return true;
+  }
+  
+  if ( user_access('access webfm') AND (webfm_file_view_access($f) OR webfm_file_mod_access($f)) ) {
+    return true;
+  }
+
+  if ( user_access('access webfm') OR user_access('view webfm attachments') ) {
+    $webfm_attach_result = db_query('SELECT nid,cid FROM {webfm_attach} WHERE fid = %d', $f->fid);
+    while ($webfm_attach_data = db_fetch_array($webfm_attach_result)) {
+      
+      //handle files attached to comments
+      if ($webfm_attach_data['cid'] != 0) {
+        // For a comment, a user must be able to view the parent node and have "access_comments".
+        if ( !user_access('access comments') ) {
+          continue;
         }
+        $comment = _comment_load($dbfid['cid']);
+        $webfm_attach_data['nid'] = $comment->nid;
+      }
+      
+      //handle files attached to nodes
+      $node = node_load($dbfid['nid']);
+      if (node_access('view', $node)) {
+        return true;
       }
     }
   }
+  
+  return false;
+}
 
-  // Files that are viewable via the filebrowser UI are downloadable
-  if($match == FALSE &&
-     $webfm_perm == WEBFM_USER &&
-     (webfm_file_view_access($f) || webfm_file_mod_access($f))) {
-    $match = TRUE;
-  }
-
-  if(!$match) {
-    drupal_access_denied();
-    return;
-  }
-
-  // Hook to allow alternative file streaming
-  // Bypass allows 2nd pass of file through this function after work done
-  // in module using this hook
-  if($bypass_invoke === FALSE) {
-    $ret = module_invoke_all('webfm_send', $f);
-    if(array_key_exists('sent', $ret)) {
-      return;
-    }
+/**
+ * Check if file is within webfm managed tree
+ * 
+ * @param $fullfilepath string Full path of file in question.
+ * 
+ * @return  bool
+ */
+function _webfm_managed_by_webfm($fullfilepath) {
+  $webfm_root_path = webfm_get_root_path();
+  //Webfm root_path not set?
+  if (empty($webfm_root_path)) {
+    return false;
+  }
+  
+  //create fullfilepath for webfm root
+  $webfmstoragepath = file_directory_path().$webfm_root_path;
+
+  //if the webfm storage path is not part of the filepath we don't manage this file
+  if (stripos($fullfilepath,$webfmstoragepath) !== false) {
+    return true;
+  }
+  else {
+    return false;
   }
+}
 
-  $name=_webfm_basename($f->fpath);
-
-  //filenames in IE containing dots will screw up the
-  //filename unless we add this
-  if(strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
-    $name = preg_replace('/\./', '%2e', $name, substr_count($name, '.') - 1);
-
-  // Get file extension
-  $type = file_get_mimetype($name);
-
-  //download headers:
-  $header = array();
-  if($download === '1') {
-    // prompt for download file or view
-    $header[] = 'Pragma: no-cache';
-    $header[] = 'Cache-Control: no-cache, must-revalidate';
-    $header[] = 'Content-Disposition: attachment; filename="'.$name.'";' ;
-    if(!empty($f->fid)) {
-      $cnt = array();
-      $cnt['dl_cnt'] = (int)$f->dl_cnt + 1;
-      webfm_dbupdate_file($f->fid, '', $cnt);
-    }
-  } else {
-    // view file via browser
-    $header[] = 'Pragma: public'; // required
-    $header[] = 'Expires: 0';
-    $header[] = 'Cache-Control: must-revalidate, post-check=0, pre-check=0';
-    $header[] = 'Content-Transfer-Encoding: binary';
-    $header[] = 'Content-Disposition: inline; filename="'.$name.'";' ;
-    // consider an app run inside the browser as a 'download'
-    if(!empty($f->fid) && (strpos($type, "application") === 0)) {
-      $cnt = array();
-      $cnt['dl_cnt'] = (int)$f->dl_cnt + 1;
-      webfm_dbupdate_file($f->fid, '', $cnt);
-    }
-  }
-  $header[] = 'Content-Type: '.$type;
-  $header[] = 'Content-Length: '.(string)(@filesize($f->fpath));
-  $header[] = 'Connection: close';
 
-  //drupal file_transfer will fail if file is not inside file system directory
-  file_transfer($f->fpath, $header);
+/**
+ * Gets a file managed by webfm.
+ *
+ * If $fid arg is numeric then file path is referenced via db, otherwise the arg
+ * ...is a urlencoded path that must be converted and concatenated to base file dir
+ *
+ * @param int $fid - file id
+ * @param bool $download - true=download / false=stream
+ * @param bool $bypass_invoke true=do no invoke webfm_send hook  / false=invoke
+ */
+function webfm_send_file($fid, $download = false, $bypass_invoke = false) {
+  //at first we try to get our file record
+  $f = webfm_get_file_record($fid,NULL);
+
+  //no file record, but administrator?
+  if ($f === FALSE and user_access('administer webfm')) {
+    //this is so admin users can still access non-indexed files all files
+      $f = new stdClass();
+      $str_arr = array();
+      $str_arr = preg_split('/~/',rawurldecode($fid),-1,PREG_SPLIT_NO_EMPTY);
+      $f->fpath = file_directory_path()."/".implode('/', $str_arr);
+   }
+
+   //is this file within drupals allowed path?
+   if(!file_create_path($f->fpath)) {
+     return drupal_not_found();
+   }
+
+  //keep in mind if we need to open this in a new window
+  _webfm_attach_new_window($download);
+
+  //make sure no injection happen, because file_download handles $_GET['file']
+  unset($_GET['file']);
+  
+  //use Drupals function to present file download {@see webfm_file_download}
+  return file_download($f->fpath);
 }
 
 /**
