From 0ce09ceb66c5e6f195f3c5db53959d86aefda005 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 23 Jul 2026 14:55:27 +0200 Subject: [PATCH] Fix fasta preview rendering for sequences larger than 100 KB Sequence.display_data set the text/plain content-type only on the non-truncated preview branch. Sequences larger than the 100 KB peek limit were returned without a content-type, so the browser fell back to text/html: newlines collapsed and the header line ran into the sequence (the "horrible" fasta preview). Serve text/plain for both branches. Also stop dropping the final byte of small previews -- the read-ahead byte that chunk[:-1] removes is only present when the content is truncated. Fixes #22719 --- lib/galaxy/datatypes/sequence.py | 11 ++++++----- lib/galaxy_test/api/test_datasets.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py index fed04fa3d0f..9a43ec55de5 100644 --- a/lib/galaxy/datatypes/sequence.py +++ b/lib/galaxy/datatypes/sequence.py @@ -336,12 +336,13 @@ class Sequence(data.Text): chunk = fh.read(max_peek_size + 1) except UnicodeDecodeError: raise InvalidFileFormatError("Dataset appears to contain binary data, cannot display.") + # Always serve as text/plain so the browser preserves whitespace/newlines + # and does not interpret the content as HTML. + self._clean_and_set_mime_type(trans, "text/plain", headers) if len(chunk) <= max_peek_size: - mime = "text/plain" - self._clean_and_set_mime_type(trans, mime, headers) - return chunk[:-1], headers - headers["x-content-truncated"] = max_peek_size - return util.unicodify(chunk[:-1]), headers + return chunk, headers + headers["x-content-truncated"] = str(max_peek_size) + return util.unicodify(chunk[:max_peek_size]), headers else: return super().display_data(trans, dataset, preview, filename, to_ext, **kwd) diff --git a/lib/galaxy_test/api/test_datasets.py b/lib/galaxy_test/api/test_datasets.py index 579361d9f45..72543fb01b5 100644 --- a/lib/galaxy_test/api/test_datasets.py +++ b/lib/galaxy_test/api/test_datasets.py @@ -355,6 +355,22 @@ class TestDatasetsApi(ApiTestCase): assert content_type.startswith("text/plain"), content_type assert display_response.text == contents + def test_display_preview_large_fasta_uses_text_plain(self, history_id): + # Regression test for https://github.com/galaxyproject/galaxy/issues/22719 + # A fasta larger than the 100 KB preview limit must still be served as + # text/plain so the browser preserves newlines; otherwise the header line + # runs into the sequence and the content is rendered as HTML. + header = ">seq0 large fasta regression test\n" + contents = header + "".join(f"{'ACGT' * 20}\n" for _ in range(2000)) + hda1 = self.dataset_populator.new_dataset(history_id, content=contents, file_type="fasta", wait=True) + display_response = self._get(f"histories/{history_id}/contents/{hda1['id']}/display", {"preview": "True"}) + self._assert_status_code_is(display_response, 200) + content_type = display_response.headers.get("content-type", "") + assert content_type.startswith("text/plain"), content_type + assert display_response.headers.get("x-content-truncated") == "100000" + assert display_response.text.startswith(header) + assert len(display_response.text) <= 100000 + def test_display_extra_paths(self, history_id: str): test_data_resolver = TestDataResolver() with open(test_data_resolver.get_filename("1.fasta")) as fh: