diff --git a/agent/ajax.php b/agent/ajax.php index 799c08f9c..558a8daa9 100644 --- a/agent/ajax.php +++ b/agent/ajax.php @@ -413,6 +413,54 @@ if (isset($_GET['get_canned_response'])) { } +/* + * Returns a document's rendered content for the file previewer on agent/files.php. + * + * The previewer shows files by pointing an iframe at file.php, but a document is + * rows in a table, not bytes on disk. Rather than embedding every document's + * HTML in the page payload - a folder of long documents would be megabytes of + * JSON on a page that shows two dozen tiles - it is fetched when the document is + * actually opened. + * + * Purified here rather than at save time, the same way canned responses above + * and agent/document.php do it. + */ +if (isset($_GET['get_document_content'])) { + enforceUserPermission('module_support'); + + $document_id = intval($_GET['get_document_content']); + + $document_sql = mysqli_query($mysqli, "SELECT document_client_id, document_content, document_name + FROM documents WHERE document_id = $document_id LIMIT 1"); + + $document_row = mysqli_fetch_assoc($document_sql); + + $response = []; + + if ($document_row) { + // Scope the fetch to the client the document belongs to - this endpoint + // takes an id straight from the query string + $client_id = intval($document_row['document_client_id']); + enforceClientAccess(); + + require_once "../libs/htmlpurifier/HTMLPurifier.standalone.php"; + + $document_purifier_config = HTMLPurifier_Config::createDefault(); + $document_purifier_config->set('Cache.DefinitionImpl', null); + $document_purifier_config->set('URI.AllowedSchemes', ['data' => true, 'src' => true, 'http' => true, 'https' => true]); + $document_purifier = new HTMLPurifier($document_purifier_config); + + $response['name'] = $document_row['document_name']; + $response['content'] = $document_purifier->purify($document_row['document_content']); + } else { + $response['name'] = ''; + $response['content'] = ''; + } + + echo json_encode($response); + +} + /* * Returns ordered list of active contacts for a specified client */ diff --git a/agent/file.php b/agent/file.php index 455a64f7f..edc7fc452 100644 --- a/agent/file.php +++ b/agent/file.php @@ -54,14 +54,7 @@ enforceClientAccess(); // MIME types that are safe to render inline in the browser // Everything else (esp. HTML/SVG - stored XSS risk) falls back to download -$inline_allowed_mime_types = array( - "application/pdf", - "image/png", - "image/jpeg", - "image/gif", - "image/webp", - "text/plain" -); +$inline_allowed_mime_types = getInlineViewableMimeTypes(); if ($disposition == "inline" && !in_array($file_mime_type, $inline_allowed_mime_types, true)) { $disposition = "attachment"; diff --git a/agent/files.php b/agent/files.php index 7265c98d5..8d7340cba 100644 --- a/agent/files.php +++ b/agent/files.php @@ -16,7 +16,40 @@ if (!empty($_GET['folder_id'])) { // Folder ID (used in forms/etc) $get_folder_id = $folder_id; -// View Mode -- 0 List, 1 Thumbnail (thumbnail = files only) +/* + * Type filter -- '' all, 'file' uploads only, 'document' created documents only. + * + * Whitelisted rather than trusted: it decides which of the two queries below run + * at all, so an unexpected value should mean "everything", not an empty page. + * + * Like the view mode, it rides along on every folder link - a filter that + * silently clears itself the moment you open a folder is worse than no filter. + */ +$type_filter = $_GET['type'] ?? ''; +if (!in_array($type_filter, ['file', 'document'], true)) { + $type_filter = ''; +} + +/* + * Links for the filter buttons. Built from a copy with 'type' removed so the + * new value does not land next to the old one, and with 'page' removed because + * switching filter while on page 4 of the files would otherwise open page 4 of + * a shorter list and show nothing. + */ +$type_get_copy = $_GET; +unset($type_get_copy['type'], $type_get_copy['page'], $type_get_copy['sort'], $type_get_copy['order']); +$url_query_strings_type = http_build_query($type_get_copy); +if ($url_query_strings_type !== '') { + $url_query_strings_type .= '&'; +} + +/* + * View Mode -- 0 List, 1 Grid + * + * Carried on every folder link below. Without it, opening a folder dropped the + * parameter and the page fell back to list view, so picking grid only lasted + * until the next click. + */ if (!empty($_GET['view'])) { $view = intval($_GET['view']); } else { @@ -80,13 +113,13 @@ function isAncestorFolder($folder_id, $current_folder_id, $client_id) { } function displayFolders($parent_folder_id, $client_id, $indent = 0, $render_root = false) { - global $mysqli, $get_folder_id, $session_user_role, $archive_query, $archived, $num_root_items, $folders_expanded; + global $mysqli, $get_folder_id, $session_user_role, $archive_query, $archived, $num_root_items, $folders_expanded, $view, $type_filter; // Always render root (only once) if ($parent_folder_id == 0 && $indent == 0) { echo '