From f8b8986960d6d93da95d7f0b5877fe30d71e69e0 Mon Sep 17 00:00:00 2001 From: Jonas Bardino Date: Wed, 11 Jun 2025 12:49:35 +0200 Subject: [PATCH] Rework showvgridprivatefile to emulate a simplified version of cat.py to render html and txt files directly but force download of anything else. --- .../functionality/showvgridprivatefile.py | 56 +++++++++++++------ mig/shared/output.py | 9 ++- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/mig/shared/functionality/showvgridprivatefile.py b/mig/shared/functionality/showvgridprivatefile.py index 2787fc42b..00a2d80f2 100644 --- a/mig/shared/functionality/showvgridprivatefile.py +++ b/mig/shared/functionality/showvgridprivatefile.py @@ -4,7 +4,7 @@ # --- BEGIN_HEADER --- # # showvgridprivatefile - View VGrid private files for owners and members -# Copyright (C) 2003-2020 The MiG Project lead by Brian Vinter +# Copyright (C) 2003-2025 The MiG Project by the Science HPC Center at UCPH # # This file is part of MiG. # @@ -36,8 +36,10 @@ import os from mig.shared import returnvalues +from mig.shared.fileio import read_file, read_file_lines from mig.shared.functional import validate_input_and_cert, REJECT_UNSET -from mig.shared.init import initialize_main_variables, find_entry +from mig.shared.init import initialize_main_variables, find_entry, \ + start_download from mig.shared.validstring import valid_user_path from mig.shared.vgrid import vgrid_is_owner_or_member @@ -53,7 +55,8 @@ def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ - initialize_main_variables(client_id, op_header=False) + initialize_main_variables(client_id, op_title=False, op_header=False, + op_menu=False) defaults = signature()[1] label = configuration.site_vgrid_label # NOTE: no title or header here since output is usually raw @@ -86,6 +89,8 @@ def main(client_id, user_arguments_dict): base_dir = os.path.abspath(os.path.join(configuration.vgrid_private_base, vgrid_name)) + os.sep + start_entry = find_entry(output_objects, 'start') + # Strip leading slashes to avoid join() throwing away prefix rel_path = path.lstrip(os.sep) @@ -97,22 +102,26 @@ def main(client_id, user_arguments_dict): private files dir.''' % label}) return (output_objects, returnvalues.CLIENT_ERROR) + # NOTE: we use a simplified version of the cat.py handling here and simply + # return anything but html and txt as downloads. + if abs_path.endswith('.html') or abs_path.endswith('.txt'): + force_file = False + src_mode = "r" + else: + force_file = True + src_mode = "rb" + + output_lines = [] try: - # TODO: port to read_file - private_fd = open(abs_path, 'rb') - entry = {'object_type': 'binary', - 'data': private_fd.read()} - # Serve web pages directly with default html content type but cut away - # all the usual web page formatting to show only contents otherwise - if abs_path.endswith('.html'): - headers = [] + if force_file: + content = read_file(abs_path, logger, mode=src_mode) + lines = [content] else: - headers = [('Content-Disposition', 'attachment; filename="%s";' % - os.path.basename(abs_path))] - output_objects = [{'object_type': 'start', 'headers': headers}, entry, - {'object_type': 'script_status'}, - {'object_type': 'end'}] - private_fd.close() + content = lines = read_file_lines(abs_path, logger, + mode=src_mode) + if content is None: + raise Exception("could not read file") + output_lines += lines except Exception as exc: logger.error("reading private file %s failed: %s" % (abs_path, exc)) output_objects.append({'object_type': 'error_text', 'text': @@ -120,4 +129,17 @@ def main(client_id, user_arguments_dict): path)}) return (output_objects, returnvalues.SYSTEM_ERROR) + entry = {'object_type': 'file_output', + 'lines': output_lines, + 'wrap_binary': force_file, + 'verbatim': not force_file, + 'wrap_targets': ['lines']} + # Inject download marker when force_file is set + if force_file: + download_marker = start_download(configuration, abs_path, + output_lines) + start_entry.update(download_marker) + output_objects.append(entry) + # Don't append status or timing info + output_objects.append({'object_type': 'script_status'}) return (output_objects, returnvalues.OK) diff --git a/mig/shared/output.py b/mig/shared/output.py index bfff854d0..d54f5ccde 100644 --- a/mig/shared/output.py +++ b/mig/shared/output.py @@ -1292,9 +1292,12 @@ def html_format(configuration, ret_val, ret_msg, out_obj): elif i['object_type'] == 'file_output': if 'path' in i: lines.append('File: %s
' % i['path']) - # NOTE: we shouldn't expect user file contents to be safe here - lines.append('
%s

' % - html_escape(''.join(i['lines']))) + if i.get('verbatim', False): + lines.append(''.join(i['lines'])) + else: + # NOTE: we shouldn't expect user file contents to be safe here + lines.append('
%s

' % + html_escape(''.join(i['lines']))) elif i['object_type'] == 'list': lines.append('