diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GetStructureMethod.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GetStructureMethod.java index ca6925c9b59f..1eef17abf6f0 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GetStructureMethod.java +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GetStructureMethod.java @@ -6,6 +6,7 @@ import com.external.constants.ErrorMessages; import com.external.domains.RowObject; import com.external.plugins.exceptions.GSheetsPluginError; +import com.external.utils.GoogleSheetsApiEncoding; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -23,6 +24,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Slf4j public class GetStructureMethod implements ExecutionMethod, TriggerMethod { @@ -63,19 +65,26 @@ public boolean validateExecutionMethodRequest(MethodConfig methodConfig) { return true; } + /** + * Builds the GET request for {@code spreadsheets.values.batchGet} used to infer sheet structure. + * Range strings are encoded with {@link GoogleSheetsApiEncoding#encodeQueryParameter(String)} + * so sheet names containing characters such as '+' are sent correctly in the query string. + */ @Override public WebClient.RequestHeadersSpec getExecutionClient(WebClient webClient, MethodConfig methodConfig) { final List ranges = validateInputs(methodConfig); + final List encodedRanges = + ranges.stream().map(GoogleSheetsApiEncoding::encodeQueryParameter).collect(Collectors.toList()); UriComponentsBuilder uriBuilder = getBaseUriBuilder( this.BASE_SHEETS_API_URL, methodConfig.getSpreadsheetId() /* spreadsheet Id */ + "/values:batchGet"); uriBuilder.queryParam("majorDimension", "ROWS"); - uriBuilder.queryParam("ranges", ranges); + uriBuilder.queryParam("ranges", encodedRanges); return webClient .method(HttpMethod.GET) - .uri(uriBuilder.build(false).toUri()) + .uri(uriBuilder.build(true).toUri()) .body(BodyInserters.empty()); } diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/RowsGetMethod.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/RowsGetMethod.java index 155fbe5c2efc..e7d87c945d37 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/RowsGetMethod.java +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/RowsGetMethod.java @@ -8,6 +8,7 @@ import com.external.constants.ErrorMessages; import com.external.domains.RowObject; import com.external.plugins.exceptions.GSheetsPluginError; +import com.external.utils.GoogleSheetsApiEncoding; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -24,6 +25,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * API reference: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get @@ -93,19 +95,26 @@ public boolean validateExecutionMethodRequest(MethodConfig methodConfig) { return true; } + /** + * Builds the GET request for {@code spreadsheets.values.batchGet} to fetch row data. + * Range strings are encoded with {@link GoogleSheetsApiEncoding#encodeQueryParameter(String)} + * so sheet names containing characters such as '+' are sent correctly in the query string. + */ @Override public WebClient.RequestHeadersSpec getExecutionClient(WebClient webClient, MethodConfig methodConfig) { final List ranges = validateInputs(methodConfig); + final List encodedRanges = + ranges.stream().map(GoogleSheetsApiEncoding::encodeQueryParameter).collect(Collectors.toList()); UriComponentsBuilder uriBuilder = getBaseUriBuilder( this.BASE_SHEETS_API_URL, methodConfig.getSpreadsheetId() /* spreadsheet Id */ + "/values:batchGet"); uriBuilder.queryParam("majorDimension", "ROWS"); - uriBuilder.queryParam("ranges", ranges); + uriBuilder.queryParam("ranges", encodedRanges); return webClient .method(HttpMethod.GET) - .uri(uriBuilder.build(false).toUri()) + .uri(uriBuilder.build(true).toUri()) .body(BodyInserters.empty()); } diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/utils/GoogleSheetsApiEncoding.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/utils/GoogleSheetsApiEncoding.java new file mode 100644 index 000000000000..fb3327209440 --- /dev/null +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/utils/GoogleSheetsApiEncoding.java @@ -0,0 +1,42 @@ +package com.external.utils; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * Utility class providing URL encoding helpers for Google Sheets API query parameters. + *

+ * Google Sheets API range parameters (e.g., A1 notation with sheet names containing + * special characters like '+') must be percent-encoded before being appended as query + * parameters. Standard URI builders may leave '+' unencoded, causing the API to + * misinterpret it as a space. + *

+ */ +public final class GoogleSheetsApiEncoding { + + /** + * Private constructor to prevent instantiation of this utility class. + * + * @throws UnsupportedOperationException always, since this class should not be instantiated + */ + private GoogleSheetsApiEncoding() { + throw new UnsupportedOperationException("Utility class — do not instantiate"); + } + + /** + * Encodes a string for safe use as a Google Sheets API query parameter value. + *

+ * Uses {@link URLEncoder#encode(String, java.nio.charset.Charset)} with UTF-8 + * and additionally replaces '+' (which URLEncoder uses for spaces) with '%20' + * so the value can be placed inside a URI that is already considered pre-encoded + * (i.e., built with {@code UriComponentsBuilder.build(true)}). + *

+ * + * @param value the raw query parameter value to encode; must not be {@code null} + * @return the percent-encoded value safe for URI query parameters + */ + public static String encodeQueryParameter(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8) + .replace("+", "%20"); + } +} diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/utils/GoogleSheetsApiEncodingTest.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/utils/GoogleSheetsApiEncodingTest.java new file mode 100644 index 000000000000..20b338c55984 --- /dev/null +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/utils/GoogleSheetsApiEncodingTest.java @@ -0,0 +1,16 @@ +package com.external.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GoogleSheetsApiEncodingTest { + + @Test + public void encodeQueryParameter_plusInSheetName_usesPercent2BNotLiteralPlus() { + String encoded = GoogleSheetsApiEncoding.encodeQueryParameter("'Data+A'!1:1"); + assertTrue(encoded.contains("%2B"), encoded); + assertFalse(encoded.contains("+"), encoded); + } +}