From 922e5bcf7ecc3b7f9dfb828f1fa73923ba6c1051 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Fri, 14 Mar 2025 14:56:19 +0000 Subject: [PATCH 1/4] Updated MSYS2 API endpoints to expose more types of installers and archives for download --- src/murfey/server/api/bootstrap.py | 62 ++++++++---------------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/src/murfey/server/api/bootstrap.py b/src/murfey/server/api/bootstrap.py index 5a9661b34..2ec94d40d 100644 --- a/src/murfey/server/api/bootstrap.py +++ b/src/murfey/server/api/bootstrap.py @@ -256,6 +256,7 @@ def parse_cygwin_request(request_path: str): # Variables used by the MSYS2 functions below msys2_url = "https://repo.msys2.org" msys2_setup_file = "msys2-x86_64-latest.exe" +msys2_file_ext = (".exe", ".sig", ".tar.xz", "tar.zst") valid_envs = ( # Tuple of systems and supported libraries/compilers/architectures within ( @@ -289,15 +290,19 @@ def parse_cygwin_request(request_path: str): ) -@msys2.get("/setup-x86_64.exe", response_class=Response) -def get_msys2_setup(): +@msys2.get("/distrib/{setup_file}", response_class=Response) +def get_msys2_setup(setup_file: str): """ Obtain and pass through an MSYS2 installer from an official source. This is used during client bootstrapping, and can download and install the MSYS2 distribution that then remains on the client machines. """ - installer = requests.get(f"{msys2_url}/distrib/{msys2_setup_file}") + # Allow only '.exe', 'tar.xz', 'tar.zst', or '.sig' files + if not any(setup_file.endswith(suffix) for suffix in (msys2_file_ext)): + raise ValueError(f"{setup_file!r} is not a valid executable") + + installer = requests.get(f"{msys2_url}/distrib/{setup_file}") return Response( content=installer.content, media_type=installer.headers.get("Content-Type"), @@ -314,24 +319,6 @@ def get_msys2_main_index( from the main MSYS2 repository. """ - def get_msys2_setup_html(): - """ - Returns the HTML line for the latest MSYS2 installer for Windows from an official - source. - """ - url = f"{msys2_url}/distrib" - index = requests.get(url) - content: bytes = index.content - content_text: str = content.decode("latin1") - - for line in content_text.splitlines(): - if line.startswith("]*)">([^<]*)', # Regex search criteria _rewrite_url, # Function to apply search criteria to @@ -367,26 +354,6 @@ def _rewrite_url(match): ) content_text_list.append(line_new) - # Replace the "distrib/" hyperlink with one to the setup file - elif "distrib" in line: - # Set up URL to be requested on the Murfey server - mirror_file_name = "setup-x86_64.exe" - setup_url = f"{base_path}/{mirror_file_name}" - - # Get request from the "distrib" page and rewrite it - setup_html = get_msys2_setup_html() - if setup_html is None: - # Skip showing the setup file link if it fails to find it - continue - - line_new = " ".join( # Adjust spaces to align columns - re.sub( - '^]*)">([^"<]*)', - f'{mirror_file_name}', - setup_html, - ).split(" ", 1) - ) - content_text_list.append(line_new) # Other URLs don't need to be mirrored else: pass @@ -430,8 +397,8 @@ def _rewrite_url(match): path = request.url.path.strip("/") base_path = f"{base_url}/{path}" - # Validate provided system - if any(system in env[0] for env in valid_envs) is False: + # Validate provided system; use this endpoint to display 'distrib' folder too + if not (any(system in env[0] for env in valid_envs) or system == "distrib"): raise ValueError(f"{system!r} is not a valid msys2 environment") # Construct URL to main MSYS repo and get response @@ -444,6 +411,11 @@ def _rewrite_url(match): content_text_list = [] for line in content_text.splitlines(): if line.startswith("]*)">([^<]*)', # Regex search criteria From 801cdb1d57d8039510692cd6a63aaee39ed8506f Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Fri, 14 Mar 2025 14:58:58 +0000 Subject: [PATCH 2/4] Deleted deprecated variable --- src/murfey/server/api/bootstrap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/murfey/server/api/bootstrap.py b/src/murfey/server/api/bootstrap.py index 2ec94d40d..92f3a3e8b 100644 --- a/src/murfey/server/api/bootstrap.py +++ b/src/murfey/server/api/bootstrap.py @@ -255,7 +255,6 @@ def parse_cygwin_request(request_path: str): # Variables used by the MSYS2 functions below msys2_url = "https://repo.msys2.org" -msys2_setup_file = "msys2-x86_64-latest.exe" msys2_file_ext = (".exe", ".sig", ".tar.xz", "tar.zst") valid_envs = ( # Tuple of systems and supported libraries/compilers/architectures within From 2a8761d011d742141202e04930c43be1e8be8483 Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Fri, 14 Mar 2025 15:06:51 +0000 Subject: [PATCH 3/4] Validate setup file path inputted --- src/murfey/server/api/bootstrap.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/api/bootstrap.py b/src/murfey/server/api/bootstrap.py index 92f3a3e8b..ae84033d6 100644 --- a/src/murfey/server/api/bootstrap.py +++ b/src/murfey/server/api/bootstrap.py @@ -297,8 +297,12 @@ def get_msys2_setup(setup_file: str): MSYS2 distribution that then remains on the client machines. """ + # Validate characters in sent path + if not bool(re.fullmatch(r"^[\w\.\-]+$", setup_file)): + raise ValueError("Unallowed characters present in requested setup file") + # Allow only '.exe', 'tar.xz', 'tar.zst', or '.sig' files - if not any(setup_file.endswith(suffix) for suffix in (msys2_file_ext)): + if not any(setup_file.endswith(ext) for ext in (msys2_file_ext)): raise ValueError(f"{setup_file!r} is not a valid executable") installer = requests.get(f"{msys2_url}/distrib/{setup_file}") From 929924144a51a4926fa6b4c819b4d945df9e447d Mon Sep 17 00:00:00 2001 From: Eu Pin Tien Date: Fri, 14 Mar 2025 15:17:13 +0000 Subject: [PATCH 4/4] Stricter conditions --- src/murfey/server/api/bootstrap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/api/bootstrap.py b/src/murfey/server/api/bootstrap.py index ae84033d6..5558305d6 100644 --- a/src/murfey/server/api/bootstrap.py +++ b/src/murfey/server/api/bootstrap.py @@ -302,7 +302,9 @@ def get_msys2_setup(setup_file: str): raise ValueError("Unallowed characters present in requested setup file") # Allow only '.exe', 'tar.xz', 'tar.zst', or '.sig' files - if not any(setup_file.endswith(ext) for ext in (msys2_file_ext)): + if not setup_file.startswith("msys2") and not any( + setup_file.endswith(ext) for ext in (msys2_file_ext) + ): raise ValueError(f"{setup_file!r} is not a valid executable") installer = requests.get(f"{msys2_url}/distrib/{setup_file}")