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); } /**