query($query, [(int)$_GET['fileId']]); if ($result) { $info = $result->fetchRow(); $skip = true; } else { $info = []; } } if (! $skip) { $filegallib = TikiLib::lib('filegal'); $access->check_feature('feature_file_galleries'); } if ($prefs["user_store_file_gallery_picture"] == 'y' && isset($_REQUEST["avatar"])) { $userprefslib = TikiLib::lib('userprefs'); if ($user_picture_id = $userprefslib->get_user_picture_id($_REQUEST["avatar"])) { $_REQUEST['fileId'] = $user_picture_id; } elseif (! empty($prefs['user_default_picture_id'])) { $_REQUEST['fileId'] = $prefs['user_default_picture_id']; } } @set_time_limit(0); $zip = false; $error = ''; if (! $skip) { if (isset($_REQUEST['fileId']) && ! is_array($_REQUEST['fileId'])) { if (isset($_GET['draft'])) { $info = \Tiki\FileGallery\FileDraft::id($_REQUEST['fileId'])->getParams(); } else { $info = $filegallib->get_file($_REQUEST['fileId']); } } elseif (isset($_REQUEST['galleryId']) && isset($_REQUEST['name'])) { $info = $filegallib->get_file_by_name($_REQUEST['galleryId'], $_REQUEST['name']); } elseif (isset($_REQUEST['fileId']) && is_array($_REQUEST['fileId'])) { $info = $filegallib->zip($_REQUEST['fileId'], $error); $zip = true; } elseif (! empty($_REQUEST['randomGalleryId'])) { $info = $filegallib->get_file(0, $_REQUEST['randomGalleryId']); } elseif (! empty($_GET['data'])) { global $base_url; $data = Tiki_Security::get()->decode($_GET['data']); if (! $data || ! isset($data['url'])) { $access->display_error('', tra('Invalid request'), 400); } $src = $data['url']; $info = unserialize(TikiLib::lib('cache')->getCached($src, 'external_downloaded_files')); if (! $info || (($info['expires'] ?? 0) < time() )) { $info = $filegallib->get_info_from_url($src); if (! $info) { $access->display_error(null, tr('Could not access to %0', $src), 404); } // If there is no expire set or equals to current time, cache it for 1 day if (($info['expires'] ?? 0) <= time()) { $info['expires'] = strtotime('+1 day'); } TikiLib::lib('cache')->cacheItem($src, serialize($info), 'external_downloaded_files'); } } else { $access->display_error('', tra('Incorrect param'), 400); } if (! is_array($info)) { $access->display_error(null, tra('File has been deleted'), 404); } if ($prefs['auth_token_access'] != 'y' || ! $is_token_access) { // Check permissions except if the user comes with a valid Token if ($tiki_p_admin_file_galleries != 'y' && $info['backlinkPerms'] == 'y' && $filegallib->hasOnlyPrivateBacklinks($info['fileId'])) { if (! $user && $prefs['permission_denied_login_box'] === 'y' && empty($_SESSION['loginfrom'])) { $_SESSION['loginfrom'] = $_SERVER['HTTP_REFERER']; } $access->display_error('', tra('Permission denied'), 401); } if (! $zip && $tiki_p_admin_file_galleries != 'y' && ! $userlib->user_has_perm_on_object($user, $info['fileId'], 'file', 'tiki_p_download_files') && ! $filegallib->isBacklinkedFromAViewableTrackerItem($info['fileId'])) { if (! $user && $prefs['permission_denied_login_box'] === 'y' && empty($_SESSION['loginfrom'])) { $_SESSION['loginfrom'] = $_SERVER['HTTP_REFERER']; } $access->display_error('', tra('Permission denied'), 401); } if (isset($_GET['thumbnail']) && is_numeric($_GET['thumbnail'])) { //check also perms on thumb $info_thumb = $filegallib->get_file($_GET['thumbnail']); if (! $zip && $tiki_p_admin_file_galleries != 'y' && ! $userlib->user_has_perm_on_object($user, $info_thumb['fileId'], 'file', 'tiki_p_download_files')) { if (! $user && $prefs['permission_denied_login_box'] === 'y' && empty($_SESSION['loginfrom'])) { $_SESSION['loginfrom'] = $_SERVER['HTTP_REFERER']; } $access->display_error('', tra('Permission denied'), 401); } } if ($prefs['feature_use_fgal_for_user_files'] === 'y' && $tiki_p_admin_file_galleries !== 'y' && $prefs['userfiles_private'] === 'y') { $gal_info = $filegallib->get_file_gallery_info($info['galleryId']); if ($gal_info['type'] === 'user' && $gal_info['visible'] !== 'y' && $gal_info['user'] !== $user) { $access->display_error('', tra('Permission denied'), 401); } } } } //if the file is remote, display, and don't cache $attributelib = TikiLib::lib('attribute'); $attributes = $attributelib->get_attributes('file', $info['fileId']); if (isset($attributes['tiki.content.url'])) { $smarty->loadPlugin('smarty_modifier_sefurl'); $src = smarty_modifier_sefurl($info['fileId'], 'file'); session_write_close(); $client = $tikilib->get_http_client($src); $response = $client->send(); header('Content-Type: ' . $response->getHeaders()->get('Content-Type')->getFieldValue()); echo $response->getBody(); exit(); } // Add hits ( if download or display only ) + lock if set if (! isset($_GET['thumbnail']) && ! isset($_GET['icon'])) { $statslib = TikiLib::lib('stats'); $filegallib = TikiLib::lib('filegal'); if (! $filegallib->add_file_hit($info['fileId'])) { $access->display_error('', tra('You cannot download this file right now. Your score is low or file limit was reached.'), 401); } $statslib->stats_hit($info['filename'], 'file', $info['fileId']); if ($prefs['feature_actionlog'] == 'y') { $logslib = TikiLib::lib('logs'); $logslib->add_action('Downloaded', $info['galleryId'], 'file gallery', 'fileId=' . $info['fileId']); } if (! empty($_REQUEST['lock']) && $access->checkCsrf(true)) { if (! empty($info['lockedby']) && $info['lockedby'] != $user) { Feedback::error(tr(sprintf('The file has been locked by %s', $info['lockedby']))); } $result = $filegallib->lock_file($info['fileId'], $user); if ($result && $result->numRows()) { Feedback::success(tr('File locked')); } else { Feedback::error(tr('File not locked')); } } } session_write_close(); // close the session in case of large downloads to enable further browsing error_reporting(E_ALL); while (ob_get_level() > 1) { ob_end_clean(); }// Be sure output buffering is turned off $file = new \Tiki\FileGallery\File($info); $wrapper = $file->getWrapper(); $content_changed = false; $md5 = $wrapper->getChecksum(); $last_modified = $file->lastModif; // local files can be read and served in chunks if ($wrapper->isFileLocal()) { $filepath = $wrapper->getReadableFile(); $content = ''; } else { $filepath = ''; $content = $wrapper->getContents(); } $scale = 0; if (isset($_GET['scale'])) { $scale = (float)$_GET['scale']; } if ($scale >= 1) { $scale = 0; } // ETag: Entity Tag used for strong cache validation. if (! isset($_GET['display']) || isset($_GET['x']) || isset($_GET['y']) || $scale || isset($_GET['max']) || isset($_GET['format'])) { // if image will be modified, emit a different ETag for modifications. $str = isset($_GET['x']) ? $_GET['x'] . 'x' : ''; $str .= isset($_GET['y']) ? $_GET['y'] . 'y' : ''; $str .= $scale ? $scale . 's' : ''; $str .= isset($_GET['max']) ? $_GET['max'] . 'm' : ''; $str .= isset($_GET['format']) ? $_GET['format'] . 'f' : ''; $etag = '"' . $md5 . '-' . crc32($md5) . '-' . crc32($str) . '"'; } else { $etag = '"' . $md5 . '-' . crc32($md5) . '"'; } header('ETag: ' . $etag); $use_client_cache = false; if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified == strtotime(current($a = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) { $use_client_cache = true; } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $tmp = array_map('trim', explode(',', $_SERVER['HTTP_IF_NONE_MATCH'])); foreach ($tmp as $v) { if ($v == '*' || $v == $etag) { $use_client_cache = true; break; } } unset($tmp); } header("Pragma: "); header('Expires: '); header('Cache-Control: ' . ( ! empty($user) ? 'private' : 'public' ) . ',must-revalidate,post-check=0,pre-check=0'); if ($use_client_cache) { header('Status: 304 Not Modified', true, 304); exit; } else { if (! empty($last_modified)) { header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $last_modified) . ' GMT'); } } // Indicates if a 'office' document should be converted to pdf for download or display in browser. $convertToPdf = (isset($_GET['display']) || isset($_GET['pdf'])) && PDFHelper::canConvertToPDF($info['filetype']) && $prefs['fgal_convert_documents_pdf'] == 'y'; // Handle images display, files thumbnails and icons if (isset($_GET['preview']) || isset($_GET['thumbnail']) || isset($_GET['display']) || isset($_GET['icon']) || $convertToPdf) { $use_cache = false; // Cache only thumbnails to avoid DOS attacks $cacheName = ''; $cacheType = ''; $cachelib = TikiLib::lib('cache'); if (( isset($_GET['thumbnail']) || isset($_GET['preview']) || $convertToPdf) && ! isset($_GET['display']) && ! isset($_GET['icon']) && ! $scale && ! isset($_GET['x']) && ! isset($_GET['y']) && ! isset($_GET['format']) && ! isset($_GET['max'])) { $use_cache = true; $cacheName = $md5; if ($convertToPdf) { $cacheTypePrefix = 'pdf_'; } elseif (isset($_GET['thumbnail'])) { $cacheTypePrefix = 'thumbnail_'; } else { $cacheTypePrefix = 'preview_'; } $cacheType = $cacheTypePrefix . ((int)$_REQUEST['fileId']) . '_'; } $build_content = true; $content_temp = $cachelib->getCached($cacheName, $cacheType); if ($use_cache && $content_temp) { if ($content_temp !== serialize(false) and $content_temp != "") { $build_content = false; $content = $content_temp; } $content_changed = true; } unset($content_temp); if ($build_content) { if ($convertToPdf) { $pdfFile = PDFHelper::convertToPDF($_REQUEST['fileId']); $content = file_get_contents($pdfFile); unlink($pdfFile); $content_changed = true; } elseif (! isset($_GET['display']) || isset($_GET['x']) || isset($_GET['y']) || $scale || isset($_GET['max']) || isset($_GET['format']) || isset($_GET['thumbnail'])) { // Modify the original image if needed if (! Image::isAvailable()) { die(); } $content_changed = true; $format = substr($info['filename'], strrpos($info['filename'], '.') + 1); // Fallback to an icon if the format is not supported $tmp = Image::create('img/trans.png', true, 'png'); // needed to call non-static Image functions non-statically if (! $tmp->isSupported($format)) { // Is the filename correct? Maybe it doesn't have an extenstion? // Try to determine the format from the filetype too $format = substr($info['filetype'], strrpos($info['filetype'], '/') + 1); if (! $tmp->isSupported($format)) { $_GET['icon'] = 'y'; $_GET['max'] = 32; } } do { $tryIconFallback = false; if (isset($_GET['icon'])) { unset($filepath); $content = null; // Explicitely free memory before generating icon if (isset($_GET['max'])) { $icon_x = $_GET['max']; $icon_y = $_GET['max']; } else { $icon_x = isset($_GET['x']) ? $_GET['x'] : 0; $icon_y = isset($_GET['y']) ? $_GET['y'] : 0; } $ext = pathinfo($info['filename']); // TODO replace with mimelib functions $format = isset($ext['extension']) ? $ext['extension'] : $format; $content = $tmp->icon($format, $icon_x, $icon_y); $format = $tmp->getIconDefaultFormat(); $info['filetype'] = 'image/' . $format; $info['lastModif'] = 0; } if (! isset($_GET['icon']) || ( isset($_GET['format']) && $_GET['format'] != $format )) { if (! empty($filepath)) { $image = Image::create($filepath, true, $format); } else { $image = Image::create($content, false, $format); $content = null; // Explicitely free memory before getting cache } if ($image->isEmpty()) { die; } $resize = false; // We resize if needed if (isset($_GET['x']) || isset($_GET['y'])) { $image->resize(isset($_GET['x']) ? (int) $_GET['x'] : 0, isset($_GET['y']) ? (int) $_GET['y'] : 0); $resize = true; } elseif ($scale) { // We scale if needed $image->scale($scale); $resize = true; } elseif (isset($_GET['max'])) { // We reduce size if length or width is greater that $_GET['max'] if needed $image->resizeMax($_GET['max'] + 0); $resize = true; } elseif (isset($_GET['thumbnail'])) { // We resize to a thumbnail size if needed if (is_numeric($_GET['thumbnail'])) { if (empty($info_thumb)) { $info_thumb = $filegallib->get_file($_GET['thumbnail']); } $file_thumb = new \Tiki\TikiFile\File($info_thumb); $image = Image::create($file_thumb->getContents()); $content = null; // Explicitely free memory before getting cache if ($image->isEmpty()) { die; } } $image->resizeThumb(); } elseif (isset($_GET['preview'])) { // We resize to a preview size if needed $image->resizeMax('800'); $resize = true; } // We change the image format if needed if (isset($_GET['format']) && $image->isSupported($_GET['format'])) { if (isset($_GET['quality'])) { $image->setQuality($_GET['quality']); } $image->convert($_GET['format']); } elseif (isset($_GET['thumbnail']) && $image->getFormat() != 'svg') { // Or, if no format is explicitely specified and a thumbnail has to be created, we convert the image to the $thumbnail_format if ($image->getFormat()) { $thumbnail_format = $image->getFormat(); // preserves transparency if png or gif } $image->convert($thumbnail_format); } $content = $image->display(); // If the new image creating has failed, fallback to an icon if (! isset($_GET['icon']) && ( $content === null || $content === false )) { $tryIconFallback = true; $_GET['icon'] = 'y'; $_GET['max'] = 32; } else { $info['filetype'] = $image->getMimeType(); } } } while ($tryIconFallback); } if (strpos($info['filetype'], 'image/svg') !== false) { $info['filetype'] = 'image/svg+xml'; $content = '' . "\n" . $content; } if ($info['filetype'] === 'text/plain') { $content = nl2br($content); } if ($use_cache && ! empty($content)) { // Remove all existing thumbnails for this file, to avoid taking too much disk space // (only one thumbnail size is handled at the same time) $cachelib->empty_type_cache($cacheType); // Cache Thumbnail $cachelib->cacheItem($cacheName, $content, $cacheType); } } } if ($convertToPdf) { // Replace file metadata to output in response headers $info['filetype'] = 'application/pdf'; $info['filename'] = pathinfo($info['filename'], PATHINFO_FILENAME) . '.pdf'; } $mimelib = TikiLib::lib('mime'); if ( empty($info['filetype']) || $info['filetype'] == 'application/x-octetstream' || $info['filetype'] == 'application/octet-stream' || $info['filetype'] == 'unknown' ) { $info['filetype'] = $mimelib->from_path($info['filename'], $filepath); } elseif (isset($_GET['thumbnail']) && (strpos($info['filetype'], 'image') === false || ($content_changed && strpos($info['filetype'], 'image/svg') === false))) { // use thumb format $info['filetype'] = $mimelib->from_content($info['filename'], $content); } header('Content-type: ' . $info['filetype']); // IE6 can not download file with / in the name (the / can be there from a previous bug) $file = basename($info['filename']); // If the content has not changed, ask the browser to download it (instead of displaying it) if ((! $content_changed and ! isset($_GET['display'])) || isset($_GET['pdf'])) { header("Content-Disposition: attachment; filename=\"$file\""); } else { header("Content-Disposition: filename=\"$file\""); } if (! empty($filepath) and ! $content_changed) { $filesize = filesize($filepath); header("Accept-Ranges: bytes"); if (empty($_SERVER['HTTP_RANGE'])) { header('Content-Length: ' . $filesize); if (strpos($info['filetype'], 'text/') === false) { readfile($filepath); } else { $content = file_get_contents($filepath); echo htmlspecialchars($content); } } else { // support media range requests here, e.g. bytes=524288-524288 bytes=0- or bytes=0-1 etc $range = preg_split('/[=-]/', $_SERVER['HTTP_RANGE']); $start = strlen($range[1]) ? (int) $range[1] : 0; if ($start >= $filesize) { $start = $filesize - 1; } $end = strlen($range[2]) ? (int) $range[2] : $filesize - 1; if ($end >= $filesize) { $end = $filesize - 1; } header('HTTP/1.1 206 Partial Content'); // FIXME Safari seems to fail when getting range 0-1 with Content-Length: 2 so leave it out for now //header('Content-Length: ' . ($end - $start + 1)); header("Content-Range: bytes $start-$end/$filesize"); header("Content-Disposition: inline; filename=\"$file\""); $fp = fopen($filepath, 'r'); fseek($fp, $start); $chunkSize = 8192; // should be a pref? while ($end) { $read = ($end > $chunkSize) ? $chunkSize : $end; $end -= $read; echo fread($fp, $read); } } } else { if (function_exists('mb_strlen')) { header('Content-Length: ' . mb_strlen($content, '8bit')); } else { header('Content-Length: ' . strlen($content)); } if (strpos($info['filetype'], 'text/') === false) { echo "$content"; } else { echo htmlspecialchars($content); } }