From b81ba7d5e1c02b99e34be1698120e54b94753d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Sexenian?= <99925035+tomas-sexenian@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:42:53 -0300 Subject: [PATCH] Encode non UTF-8 characters only once --- .../src/main/java/com/genexus/CommonUtil.java | 31 ++++++++++++++++ .../db/driver/ExternalProviderS3V2.java | 35 +++++++++++++------ 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/com/genexus/CommonUtil.java b/common/src/main/java/com/genexus/CommonUtil.java index 42af5b2e8..69421d980 100644 --- a/common/src/main/java/com/genexus/CommonUtil.java +++ b/common/src/main/java/com/genexus/CommonUtil.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; import java.io.*; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.text.*; import java.util.*; @@ -3490,4 +3491,34 @@ public static String Sanitize(String input, HashMap whiteL } return sanitizedInput.toString(); } + + public static String encodeUtf8Once(String input) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c == '%' && i + 2 < input.length() && + (((input.charAt(i+1) >= '0' && input.charAt(i+1) <= '9') || + (input.charAt(i+1) >= 'A' && input.charAt(i+1) <= 'F') || + (input.charAt(i+1) >= 'a' && input.charAt(i+1) <= 'f')) && + ((input.charAt(i+2) >= '0' && input.charAt(i+2) <= '9') || + (input.charAt(i+2) >= 'A' && input.charAt(i+2) <= 'F') || + (input.charAt(i+2) >= 'a' && input.charAt(i+2) <= 'f')))) { + sb.append('%').append(input.charAt(i+1)).append(input.charAt(i+2)); + i += 2; + } + else if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || c == '_' || c == '.' || c == '~') { + sb.append(c); + } + else { + byte[] bytes = String.valueOf(c).getBytes(StandardCharsets.UTF_8); + for (byte b : bytes) { + sb.append(String.format("%%%02X", b)); + } + } + } + return sb.toString(); + } } diff --git a/gxcloudstorage-awss3-v2/src/main/java/com/genexus/db/driver/ExternalProviderS3V2.java b/gxcloudstorage-awss3-v2/src/main/java/com/genexus/db/driver/ExternalProviderS3V2.java index 878f4947a..27e709a6c 100644 --- a/gxcloudstorage-awss3-v2/src/main/java/com/genexus/db/driver/ExternalProviderS3V2.java +++ b/gxcloudstorage-awss3-v2/src/main/java/com/genexus/db/driver/ExternalProviderS3V2.java @@ -1,5 +1,6 @@ package com.genexus.db.driver; +import com.genexus.CommonUtil; import software.amazon.awssdk.auth.credentials.*; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.ResponseInputStream; @@ -25,7 +26,6 @@ import java.io.*; import java.net.URI; -import java.net.URL; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -251,13 +251,26 @@ public String upload(String externalFileName, InputStream input, ResourceAccessC public String get(String externalFileName, ResourceAccessControlList acl, int expirationMinutes) { // Send a request to AWS S3 to retrieve the metadata for the specified object to see if // the object exists and is accessible under the provided credentials and permissions. - HeadObjectRequest headObjectRequest = HeadObjectRequest.builder() - .bucket(bucket) - .key(externalFileName) - .build(); - client.headObject(headObjectRequest); - - return getResourceUrl(externalFileName, acl, expirationMinutes); + try { + HeadObjectRequest headObjectRequest = HeadObjectRequest.builder() + .bucket(bucket) + .key(externalFileName) + .build(); + client.headObject(headObjectRequest); + return getResourceUrl(externalFileName, acl, expirationMinutes); + } catch (Exception e) { + int lastIndex = Math.max(externalFileName.lastIndexOf('/'), externalFileName.lastIndexOf('\\')); + String path = lastIndex >= 0 ? externalFileName.substring(0, lastIndex + 1) : ""; + String fileName = lastIndex >= 0 ? externalFileName.substring(lastIndex + 1) : externalFileName; + String encodedFileName = CommonUtil.encodeUtf8Once(fileName); + String encodedExternalFileName = path + encodedFileName; + HeadObjectRequest headObjectRequest = HeadObjectRequest.builder() + .bucket(bucket) + .key(encodedExternalFileName) + .build(); + client.headObject(headObjectRequest); + return getResourceUrl(encodedExternalFileName, acl, expirationMinutes); + } } private String getResourceUrl(String externalFileName, ResourceAccessControlList acl, int expirationMinutes) { @@ -645,7 +658,7 @@ private String getResourceUrlWithACL(String externalFileName, ResourceAccessCont int lastIndex = Math.max(externalFileName.lastIndexOf('/'), externalFileName.lastIndexOf('\\')); String path = externalFileName.substring(0, lastIndex + 1); String fileName = externalFileName.substring(lastIndex + 1); - String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()); + String encodedFileName = CommonUtil.encodeUtf8Once(fileName); String url = String.format( "https://%s.s3.%s.amazonaws.com/%s%s", @@ -656,8 +669,8 @@ private String getResourceUrlWithACL(String externalFileName, ResourceAccessCont ); return url; - } catch (UnsupportedEncodingException uee) { - logger.error("Failed to encode resource URL for " + externalFileName, uee); + } catch (Exception e) { + logger.error("Failed to encode resource URL for " + externalFileName, e); return ""; } }