Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/pom.xml
### 代码内容：
<?xml version="1.0" encoding="UTF-8"?>

<!--
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.xwiki.platform</groupId>
    <artifactId>xwiki-platform-rest</artifactId>
    <version>14.7-SNAPSHOT</version>
  </parent>
  <artifactId>xwiki-platform-rest-server</artifactId>
  <packaging>jar</packaging>
  <name>XWiki Platform - REST - Server</name>
  <description>Service for accessing XWiki through a RESTful API</description>
  <properties>
    <xwiki.jacoco.instructionRatio>0.26</xwiki.jacoco.instructionRatio>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-component-api</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-context</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-job-api</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-repository-model</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-script</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.rendering</groupId>
      <artifactId>xwiki-rendering-api</artifactId>
      <version>${rendering.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rest-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rest-model</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-localization-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-model-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-query-manager</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-configuration-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-security-authorization-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-wiki-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <!-- Needed to resolve the user serializer component -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-user-default</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.restlet.jse</groupId>
      <artifactId>org.restlet</artifactId>
    </dependency>
    <dependency>
      <groupId>org.restlet.jee</groupId>
      <artifactId>org.restlet.ext.servlet</artifactId>
    </dependency>
    <dependency>
      <groupId>org.restlet.jse</groupId>
      <artifactId>org.restlet.ext.jaxrs</artifactId>
    </dependency>
    <!-- Replace the version triggered by restlet by a more accurate and up to date one -->
    <dependency>
      <groupId>com.tdunning</groupId>
      <artifactId>json</artifactId>
    </dependency>
    <dependency>
      <!-- We need Jackson for a proper JSON representation of our REST resources.
        See XWIKI-9136: Rest API JSON does not retrieve inherited fields -->
      <groupId>org.restlet.jse</groupId>
      <artifactId>org.restlet.ext.jackson</artifactId>
    </dependency>
    <!-- We need a JAXB converter for XML representation because we model our REST resources using JAXB. Jackson (see
        above) has a module that understands JAXB annotations but the output of the Restlet Jackson converter is not very
        good (missing XML declaration and namespace, attributes serialized as elements) so we prefer to use a dedicated
        JAXB converter for XML representation. -->
    <dependency>
      <groupId>org.restlet.jse</groupId>
      <artifactId>org.restlet.ext.jaxb</artifactId>
    </dependency>
    <!-- Replace the version triggered by Restlet by a more accurate one -->
    <dependency>
      <groupId>org.glassfish.jaxb</groupId>
      <artifactId>jaxb-runtime</artifactId>
    </dependency>

    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-oldcore</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-wiki-user-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.jvnet.jaxb2_commons</groupId>
      <artifactId>jaxb2-fluent-api</artifactId>
      <version>3.0</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-icon-api</artifactId>
      <version>${project.version}</version>
    </dependency>

    <!-- Test dependencies -->
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-tool-test-component</artifactId>
      <version>${commons.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-test-oldcore</artifactId>
      <version>${project.version}</version>
      <type>pom</type>
      <scope>test</scope>
    </dependency>
    <!-- Required to test the templates. -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-test-page</artifactId>
      <version>${project.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-web-templates</artifactId>
      <version>${project.version}</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <!-- Apply the Checkstyle configurations defined in the top level pom.xml file -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <executions>
          <execution>
            <!-- Specify the "default" execution id so that the "blocker" one is always executed -->
            <id>default</id>
            <configuration>
              <failsOnError>true</failsOnError>
              <suppressionsLocation>${basedir}/src/checkstyle/checkstyle-suppressions.xml</suppressionsLocation>
              <excludes>
                org/xwiki/rest/Constants.java,
                org/xwiki/rest/XWikiResource.java,
                org/xwiki/rest/internal/DomainObjectFactory.java,
                org/xwiki/rest/internal/ModelFactory.java,
                org/xwiki/rest/internal/RangeIterable.java,
                org/xwiki/rest/internal/Utils.java,
                org/xwiki/rest/internal/XWikiAuthentication.java,
                org/xwiki/rest/internal/resources/BaseAttachmentsResource.java,
                org/xwiki/rest/internal/resources/BaseSearchResult.java,
                org/xwiki/rest/internal/resources/BrowserAuthenticationResource.java,
                org/xwiki/rest/internal/resources/pages/PagesResourceImpl.java,
                org/xwiki/rest/internal/resources/search/AbstractDatabaseSearchSource.java,
                org/xwiki/rest/internal/resources/search/SearchSource.java,
                org/xwiki/rest/internal/resources/wikis/WikiPagesResourceImpl.java
              </excludes>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>fr.inria.gforge.spoon</groupId>
        <artifactId>spoon-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>spoon-main</id>
            <configuration>
              <processorProperties combine.children="append">
                <processorProperty>
                  <name>org.xwiki.tool.spoon.InjectAnnotationProcessor</name>
                  <properties>
                    <property>
                      <!-- Exclude javax.ws.rs.core.Application & org.restlet.ext.jaxrs.ObjectFactory since we use an
                           Inject on them. These are special cases when we don't inject on an interface with a @Role
                           and there's no way for the Spoon checker to find out about this, since only the Component
                           Manager knows what has been registered in it. -->
                      <name>excludedFieldTypes</name>
                      <value><![CDATA[
                        ["javax.ws.rs.core.Application", "org.restlet.ext.jaxrs.ObjectFactory"]
                      ]]></value>
                    </property>
                  </properties>
                </processorProperty>
              </processorProperties>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

## 内容2
### 文件路径：
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/ModificationsResourceImpl.java
### 代码内容：
/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.rest.internal.resources;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;

import javax.inject.Named;

import org.xwiki.component.annotation.Component;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.rest.XWikiResource;
import org.xwiki.rest.XWikiRestException;
import org.xwiki.rest.internal.DomainObjectFactory;
import org.xwiki.rest.internal.Utils;
import org.xwiki.rest.model.jaxb.History;
import org.xwiki.rest.model.jaxb.HistorySummary;
import org.xwiki.rest.resources.ModificationsResource;

import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId;

/**
 * @version $Id$
 */
@Component
@Named("org.xwiki.rest.internal.resources.ModificationsResourceImpl")
public class ModificationsResourceImpl extends XWikiResource implements ModificationsResource
{
    @Override
    public History getModifications(String wikiName, Integer start, Integer number, String order, Long ts,
            Boolean withPrettyNames) throws XWikiRestException
    {
        try {
            History history = new History();

            String query = String.format("select doc.space, doc.name, doc.language, rcs.id, rcs.date, rcs.author,"
                + " rcs.comment from XWikiRCSNodeInfo as rcs, XWikiDocument as doc where rcs.id.docId = doc.id and"
                + " rcs.date > :date order by rcs.date %s, rcs.id.version1 %s, rcs.id.version2 %s",
                    order, order, order);

            List<Object> queryResult = null;
            queryResult = queryManager.createQuery(query, Query.XWQL).bindValue("date", new Date(ts)).setLimit(number)
                    .setOffset(start).setWiki(wikiName).execute();

            for (Object object : queryResult) {
                Object[] fields = (Object[]) object;

                String spaceId = (String) fields[0];
                List<String> spaces = Utils.getSpacesFromSpaceId(spaceId);
                String pageName = (String) fields[1];
                String language = (String) fields[2];
                if (language.equals("")) {
                    language = null;
                }
                XWikiRCSNodeId nodeId = (XWikiRCSNodeId) fields[3];
                Timestamp timestamp = (Timestamp) fields[4];
                Date modified = new Date(timestamp.getTime());
                String modifier = (String) fields[5];
                String comment = (String) fields[6];

                HistorySummary historySummary = DomainObjectFactory.createHistorySummary(objectFactory,
                        uriInfo.getBaseUri(), wikiName, spaces, pageName, language, nodeId.getVersion(), modifier,
                        modified, comment, Utils.getXWikiApi(componentManager), withPrettyNames);

                history.getHistorySummaries().add(historySummary);
            }

            return history;
        } catch (QueryException e) {
            throw new XWikiRestException(e);
        }
    }
}

Output:
【漏洞类型】权限检查缺失漏洞
【严重性】Medium
【漏洞摘要】原始代码中`ModificationsResourceImpl`类的`getModifications`方法可能存在权限检查缺失的问题。该方法直接从数据库中检索文档的历史修改记录并返回给调用者，但没有检查用户是否有查看这些文档的权限。这可能导致未授权用户获取不应该访问的文档信息。
【关键代码模式】`getModifications`方法，`queryManager.createQuery`，`query.execute`，`Utils.getSpacesFromSpaceId`，`DomainObjectFactory.createHistorySummary`。
【修复方法说明】修复方法在返回历史修改记录之前，增加了对每个文档的权限检查，确保只有具有查看权限的用户才能获取这些信息。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/TriggerUtils.java
### 代码内容：
package com.appsmith.external.helpers.restApiUtils.helpers;

import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.helpers.SSLHelper;
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
import com.appsmith.external.helpers.restApiUtils.constants.ResponseDataType;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Property;
import com.appsmith.util.WebClientUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.NoArgsConstructor;
import org.bson.internal.Base64;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Set;

import static com.appsmith.external.helpers.restApiUtils.helpers.URIUtils.DISALLOWED_HOSTS;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

@NoArgsConstructor
public class TriggerUtils {

    public static String SIGNATURE_HEADER_NAME = "X-APPSMITH-SIGNATURE";
    public static String RESPONSE_DATA_TYPE = "X-APPSMITH-DATATYPE";
    public static int MAX_REDIRECTS = 5;
    public static Set BINARY_DATA_TYPES = Set.of("application/zip", "application/octet-stream", "application/pdf",
            "application/pkcs8", "application/x-binary");

    public static HeaderUtils headerUtils = new HeaderUtils();

    public Mono<ActionExecutionResult> triggerApiCall(WebClient client, HttpMethod httpMethod, URI uri,
                                                             Object requestBody,
                                                             ActionExecutionRequest actionExecutionRequest,
                                                             ObjectMapper objectMapper, Set<String> hintMessages,
                                                             ActionExecutionResult errorResult,
                                                             RequestCaptureFilter requestCaptureFilter) {
        return httpCall(client, httpMethod, uri, requestBody, 0)
                .flatMap(clientResponse -> clientResponse.toEntity(byte[].class))
                .map(stringResponseEntity -> {
                    HttpHeaders headers = stringResponseEntity.getHeaders();
                        /*
                            Find the media type of the response to parse the body as required. In case the content-type
                            header is not present in the response then set it to our default i.e. "text/plain" although
                            the RFC 7231 standard suggests assuming "application/octet-stream" content-type in case
                            it's not present in response header.
                         */
                    MediaType contentType = headers.getContentType();
                    if (contentType == null) {
                        contentType = MediaType.TEXT_PLAIN;
                    }
                    byte[] body = stringResponseEntity.getBody();
                    HttpStatus statusCode = stringResponseEntity.getStatusCode();

                    ActionExecutionResult result = new ActionExecutionResult();

                    // Set the request fields
                    result.setRequest(requestCaptureFilter.populateRequestFields(actionExecutionRequest));

                    result.setStatusCode(statusCode.toString());
                    result.setIsExecutionSuccess(statusCode.is2xxSuccessful());

                    // Convert the headers into json tree to store in the results
                    String headerInJsonString;
                    try {
                        headerInJsonString = objectMapper.writeValueAsString(headers);
                    } catch (JsonProcessingException e) {
                        throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
                    }

                    // Set headers in the result now
                    try {
                        result.setHeaders(objectMapper.readTree(headerInJsonString));
                    } catch (IOException e) {
                        throw Exceptions.propagate(
                                new AppsmithPluginException(
                                        AppsmithPluginError.PLUGIN_JSON_PARSE_ERROR,
                                        headerInJsonString,
                                        e.getMessage()
                                )
                        );
                    }

                    if (body != null) {

                        ResponseDataType responseDataType = ResponseDataType.UNDEFINED;

                        /**TODO
                         * Handle XML response. Currently we only handle JSON & Image responses. The other kind of responses
                         * are kept as is and returned as a string.
                         */
                        if (contentType.includes(MediaType.APPLICATION_JSON)) {
                            try {
                                String jsonBody = new String(body, StandardCharsets.UTF_8);
                                result.setBody(objectMapper.readTree(jsonBody));
                                responseDataType = ResponseDataType.JSON;
                            } catch (IOException e) {
                                System.out.println("Unable to parse response JSON. Setting response body as string.");
                                String bodyString = new String(body, StandardCharsets.UTF_8);
                                result.setBody(bodyString.trim());

                                // Warn user that the API response is not a valid JSON.
                                hintMessages.add("The response returned by this API is not a valid JSON. Please " +
                                        "be careful when using the API response anywhere a valid JSON is required" +
                                        ". You may resolve this issue either by modifying the 'Content-Type' " +
                                        "Header to indicate a non-JSON response or by modifying the API response " +
                                        "to return a valid JSON.");
                            }
                        } else if (MediaType.IMAGE_GIF.equals(contentType) ||
                                MediaType.IMAGE_JPEG.equals(contentType) ||
                                MediaType.IMAGE_PNG.equals(contentType)) {
                            String encode = Base64.encode(body);
                            result.setBody(encode);
                            responseDataType = ResponseDataType.IMAGE;

                        } else if (BINARY_DATA_TYPES.contains(contentType.toString())) {
                            String encode = Base64.encode(body);
                            result.setBody(encode);
                            responseDataType = ResponseDataType.BINARY;
                        } else {
                            // If the body is not of JSON type, just set it as is.
                            String bodyString = new String(body, StandardCharsets.UTF_8);
                            result.setBody(bodyString.trim());
                            responseDataType = ResponseDataType.TEXT;
                        }

                        // Now add a new header which specifies the data type of the response as per Appsmith
                        JsonNode headersJsonNode = result.getHeaders();
                        ObjectNode headersObjectNode = (ObjectNode) headersJsonNode;
                        headersObjectNode.putArray(RESPONSE_DATA_TYPE)
                                .add(String.valueOf(responseDataType));
                        result.setHeaders(headersObjectNode);

                    }

                    result.setMessages(hintMessages);
                    return result;
                })
                .onErrorResume(error -> {
                    errorResult.setRequest(requestCaptureFilter.populateRequestFields(actionExecutionRequest));
                    errorResult.setIsExecutionSuccess(false);
                    errorResult.setErrorInfo(error);
                    return Mono.just(errorResult);
                });

    }

    protected Mono<ClientResponse> httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, Object requestBody,
                                          int iteration) {
        if (iteration == MAX_REDIRECTS) {
            return Mono.error(new AppsmithPluginException(
                    AppsmithPluginError.PLUGIN_ERROR,
                    "Exceeded the HTTP redirect limits of " + MAX_REDIRECTS
            ));
        }

        assert requestBody instanceof BodyInserter<?, ?>;
        BodyInserter<?, ?> finalRequestBody = (BodyInserter<?, ?>) requestBody;

        return webClient
                .method(httpMethod)
                .uri(uri)
                .body((BodyInserter<?, ? super ClientHttpRequest>) finalRequestBody)
                .exchange()
                .doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)))
                .flatMap(response -> {
                    if (response.statusCode().is3xxRedirection()) {
                        String redirectUrl = response.headers().header("Location").get(0);
                        /**
                         * TODO
                         * In case the redirected URL is not absolute (complete), create the new URL using the relative path
                         * This particular scenario is seen in the URL : https://rickandmortyapi.com/api/character
                         * It redirects to partial URI : /api/character/
                         * In this scenario we should convert the partial URI to complete URI
                         */
                        URI redirectUri;
                        try {
                            redirectUri = new URI(redirectUrl);
                            if (DISALLOWED_HOSTS.contains(redirectUri.getHost())
                                    || DISALLOWED_HOSTS.contains(InetAddress.getByName(redirectUri.getHost()).getHostAddress())) {
                                return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Host not allowed."));
                            }
                        } catch (URISyntaxException | UnknownHostException e) {
                            return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
                        }

                        return httpCall(webClient, httpMethod, redirectUri, finalRequestBody, iteration + 1);
                    }
                    return Mono.just(response);
                });
    }

    public WebClient getWebClient(WebClient.Builder webClientBuilder, APIConnection apiConnection,
                                         String reqContentType, ObjectMapper objectMapper,
                                         ExchangeStrategies EXCHANGE_STRATEGIES, RequestCaptureFilter requestCaptureFilter) {
        // Right before building the webclient object, we populate it with whatever mutation the APIConnection object demands
        if (apiConnection != null) {
            webClientBuilder.filter(apiConnection);
        }

        if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(reqContentType)) {
            webClientBuilder.filter(new BufferingFilter());
        }

        webClientBuilder.filter(requestCaptureFilter);

        return webClientBuilder.exchangeStrategies(EXCHANGE_STRATEGIES).build();
    }

    public WebClient.Builder getWebClientBuilder(ActionConfiguration actionConfiguration,
                                                        DatasourceConfiguration datasourceConfiguration) {
        HttpClient httpClient = getHttpClient(datasourceConfiguration);
        WebClient.Builder webClientBuilder = WebClientUtils.builder(httpClient);
        addAllHeaders(webClientBuilder, actionConfiguration, datasourceConfiguration);
        addSecretKey(webClientBuilder, datasourceConfiguration);

        return webClientBuilder;
    }

    protected void addSecretKey(WebClient.Builder webClientBuilder,
                                     DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException {
        // If users have chosen to share the Appsmith signature in the header, calculate and add that
        String secretKey;
        secretKey = headerUtils.getSignatureKey(datasourceConfiguration);

        if (secretKey != null) {
            final SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
            final Instant now = Instant.now();
            final String token = Jwts.builder()
                    .setIssuer("Appsmith")
                    .setIssuedAt(new Date(now.toEpochMilli()))
                    .setExpiration(new Date(now.plusSeconds(600).toEpochMilli()))
                    .signWith(key)
                    .compact();

            webClientBuilder.defaultHeader(SIGNATURE_HEADER_NAME, token);
        }
    }

    protected void addAllHeaders(WebClient.Builder webClientBuilder, ActionConfiguration actionConfiguration,
                                      DatasourceConfiguration datasourceConfiguration) {
        /**
         * First, check if headers are defined in API datasource and add them.
         */
        if (datasourceConfiguration.getHeaders() != null) {
            addHeaders(webClientBuilder, datasourceConfiguration.getHeaders());
        }

        /**
         * If headers are defined in API action config, then add them too.
         * In case there is a conflict with the datasource headers then the header defined in the API action config
         * will override it.
         */
        if (actionConfiguration.getHeaders() != null) {
            addHeaders(webClientBuilder, actionConfiguration.getHeaders());
        }
    }

    protected void addHeaders(WebClient.Builder webClientBuilder, List<Property> headers) {
        headers.stream()
                .filter(header -> isNotEmpty(header.getKey()))
                .forEach(header -> webClientBuilder.defaultHeader(header.getKey(), (String) header.getValue()));
    }

    protected HttpClient getHttpClient(DatasourceConfiguration datasourceConfiguration) {
        // Initializing webClient to be used for http call
        final ConnectionProvider provider = ConnectionProvider
                .builder("rest-api-provider")
                .maxIdleTime(Duration.ofSeconds(600))
                .maxLifeTime(Duration.ofSeconds(600))
                .build();

        HttpClient httpClient = HttpClient.create(provider)
                .secure(SSLHelper.sslCheckForHttpClient(datasourceConfiguration))
                .compress(true);

        return httpClient;
    }
}

## 内容2
### 文件路径：
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/URIUtils.java
### 代码内容：
package com.appsmith.external.helpers.restApiUtils.helpers;

import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Property;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

@NoArgsConstructor
public class URIUtils {
    public static final Set<String> DISALLOWED_HOSTS = Set.of(
            "169.254.169.254",
            "metadata.google.internal"
    );

    public URI createUriWithQueryParams(ActionConfiguration actionConfiguration,
                                        DatasourceConfiguration datasourceConfiguration, String url,
                                        boolean encodeParamsToggle) throws URISyntaxException {
        String httpUrl = addHttpToUrlWhenPrefixNotPresent(url);

        ArrayList<Property> allQueryParams = new ArrayList<>();
        if (!isEmpty(actionConfiguration.getQueryParameters())) {
            allQueryParams.addAll(actionConfiguration.getQueryParameters());
        }

        if (!isEmpty(datasourceConfiguration.getQueryParameters())) {
            allQueryParams.addAll(datasourceConfiguration.getQueryParameters());
        }

        return addQueryParamsToURI(new URI(httpUrl), allQueryParams, encodeParamsToggle);
    }

    public URI addQueryParamsToURI(URI uri, List<Property> allQueryParams, boolean encodeParamsToggle) {
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance();
        uriBuilder.uri(uri);

        if (allQueryParams != null) {
            for (Property queryParam : allQueryParams) {
                String key = queryParam.getKey();
                if (isNotEmpty(key)) {
                    if (encodeParamsToggle == true) {
                        uriBuilder.queryParam(
                                URLEncoder.encode(key, StandardCharsets.UTF_8),
                                URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8)
                        );
                    } else {
                        uriBuilder.queryParam(
                                key,
                                queryParam.getValue()
                        );
                    }
                }
            }
        }

        return uriBuilder.build(true).toUri();
    }

    protected String addHttpToUrlWhenPrefixNotPresent(String url) {
        if (url == null || url.toLowerCase().startsWith("http") || url.contains("://")) {
            return url;
        }
        return "http://" + url;
    }

    public boolean isHostDisallowed(URI uri) throws UnknownHostException {
        String host = uri.getHost();
        return StringUtils.isEmpty(host) || DISALLOWED_HOSTS.contains(host)
                || DISALLOWED_HOSTS.contains(InetAddress.getByName(host).getHostAddress());
    }
}

## 内容3
### 文件路径：
app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java
### 代码内容：
package com.appsmith.util;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

public class WebClientUtils {

    private WebClientUtils() {
    }

    public static WebClient create() {
        return builder()
                .build();
    }

    public static WebClient create(String baseUrl) {
        return builder()
                .baseUrl(baseUrl)
                .build();
    }

    private static boolean shouldUseSystemProxy() {
        return "true".equals(System.getProperty("java.net.useSystemProxies"))
                && (!System.getProperty("http.proxyHost", "").isEmpty() || !System.getProperty("https.proxyHost", "").isEmpty());
    }

    public static WebClient.Builder builder() {
        return builder(HttpClient.create());
    }

    public static WebClient.Builder builder(HttpClient httpClient) {
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(applyProxyIfConfigured(httpClient)));
    }

    private static HttpClient applyProxyIfConfigured(HttpClient httpClient) {
        if (shouldUseSystemProxy()) {
            httpClient = httpClient.proxyWithSystemProperties();
        }

        return httpClient;
    }

}

## 内容4
### 文件路径：
app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java
### 代码内容：
package com.external.plugins;

import com.appsmith.external.dtos.ExecuteActionDTO;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.helpers.DataTypeStringUtils;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.ApiContentType;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.PaginationType;
import com.appsmith.external.models.Property;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.BaseRestApiPluginExecutor;
import com.appsmith.external.services.SharedConfig;
import com.external.utils.GraphQLHintMessageUtils;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromPropertyList;
import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInPropertyList;
import static com.external.utils.GraphQLBodyUtils.PAGINATION_DATA_INDEX;
import static com.external.utils.GraphQLDataTypeUtils.smartlyReplaceGraphQLQueryBodyPlaceholderWithValue;
import static com.external.utils.GraphQLPaginationUtils.updateVariablesWithPaginationValues;
import static com.external.utils.GraphQLBodyUtils.QUERY_VARIABLES_INDEX;
import static com.external.utils.GraphQLBodyUtils.convertToGraphQLPOSTBodyFormat;
import static com.external.utils.GraphQLBodyUtils.getGraphQLQueryParamsForBodyAndVariables;
import static com.external.utils.GraphQLBodyUtils.validateBodyAndVariablesSyntax;
import static java.lang.Boolean.TRUE;
import static org.apache.commons.lang3.StringUtils.isBlank;

public class GraphQLPlugin extends BasePlugin {

    public GraphQLPlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    @Slf4j
    @Extension
    public static class GraphQLPluginExecutor extends BaseRestApiPluginExecutor {

        public GraphQLPluginExecutor(SharedConfig sharedConfig) {
            super(sharedConfig);

            GraphQLHintMessageUtils hintMessageUtils = new GraphQLHintMessageUtils();
            super.setHintMessageUtils(hintMessageUtils);
        }

        /**
         * Instead of using the default executeParametrized provided by pluginExecutor, this implementation affords an opportunity
         * also update the datasource and action configuration for pagination and some minor cleanup of the configuration before execution
         *
         * @param connection              : This is the connection that is established to the data source. This connection is according
         *                                to the parameters in Datasource Configuration
         * @param executeActionDTO        : This is the data structure sent by the client during execute. This contains the params
         *                                which would be used for substitution
         * @param datasourceConfiguration : These are the configurations which have been used to create a Datasource from a Plugin
         * @param actionConfiguration     : These are the configurations which have been used to create an Action from a Datasource.
         * @return
         */
        @Override
        public Mono<ActionExecutionResult> executeParameterized(APIConnection connection,
                                                                ExecuteActionDTO executeActionDTO,
                                                                DatasourceConfiguration datasourceConfiguration,
                                                                ActionConfiguration actionConfiguration) {

            final List<Property> properties = actionConfiguration.getPluginSpecifiedTemplates();
            List<Map.Entry<String, String>> parameters = new ArrayList<>();

            String variables = getValueSafelyFromPropertyList(properties, QUERY_VARIABLES_INDEX, String.class);
            Boolean smartSubstitution = this.smartSubstitutionUtils.isSmartSubstitutionEnabled(properties);
            if (TRUE.equals(smartSubstitution)) {
                /* Apply smart JSON substitution logic to mustache binding values in query variables */
                if (!isBlank(variables)) {
                    List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(variables);
                    // Replace all the bindings with a ? as expected in a prepared statement.
                    String updatedVariables = MustacheHelper.replaceMustacheWithPlaceholder(variables, mustacheKeysInOrder);

                    try {
                        updatedVariables = (String) smartSubstitutionOfBindings(updatedVariables,
                                mustacheKeysInOrder,
                                executeActionDTO.getParams(),
                                parameters,
                                false);
                        setValueSafelyInPropertyList(properties, QUERY_VARIABLES_INDEX, updatedVariables);
                    } catch (AppsmithPluginException e) {
                        ActionExecutionResult errorResult = new ActionExecutionResult();
                        errorResult.setIsExecutionSuccess(false);
                        errorResult.setErrorInfo(e);
                        errorResult.setStatusCode(AppsmithPluginError.PLUGIN_ERROR.getAppErrorCode().toString());
                        return Mono.just(errorResult);
                    }
                }

                /* Apply smart substitution logic to query body */
                String query = actionConfiguration.getBody();
                if (!isBlank(query)) {
                    List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
                    // Replace all the bindings with a ? as expected in a prepared statement.
                    String updatedQuery = MustacheHelper.replaceMustacheWithPlaceholder(query, mustacheKeysInOrder);

                    try {
                        updatedQuery = (String) smartSubstitutionOfBindings(updatedQuery,
                                mustacheKeysInOrder,
                                executeActionDTO.getParams(),
                                parameters,
                                true);
                        actionConfiguration.setBody(updatedQuery);
                    } catch (AppsmithPluginException e) {
                        ActionExecutionResult errorResult = new ActionExecutionResult();
                        errorResult.setIsExecutionSuccess(false);
                        errorResult.setErrorInfo(e);
                        errorResult.setStatusCode(AppsmithPluginError.PLUGIN_ERROR.getAppErrorCode().toString());
                        return Mono.just(errorResult);
                    }
                }
            }

            prepareConfigurationsForExecution(executeActionDTO, actionConfiguration, datasourceConfiguration);

            if (isBlank(variables)) {
                setValueSafelyInPropertyList(properties, QUERY_VARIABLES_INDEX, "{}");
            }

            /* Check if the query body and query variables have the correct GraphQL syntax. */
            try {
                validateBodyAndVariablesSyntax(actionConfiguration);
            } catch (AppsmithPluginException e) {
                return Mono.error(e);
            }

            if (actionConfiguration.getPaginationType() != null && !PaginationType.NONE.equals(actionConfiguration.getPaginationType())) {
                updateVariablesWithPaginationValues(actionConfiguration, executeActionDTO);
            }

            // Filter out any empty headers
            headerUtils.removeEmptyHeaders(actionConfiguration);

            return this.executeCommon(connection, datasourceConfiguration, actionConfiguration, parameters);
        }

        public Mono<ActionExecutionResult> executeCommon(APIConnection apiConnection,
                                                         DatasourceConfiguration datasourceConfiguration,
                                                         ActionConfiguration actionConfiguration,
                                                         List<Map.Entry<String, String>> insertedParams) {

            // Initializing object for error condition
            ActionExecutionResult errorResult = new ActionExecutionResult();
            initUtils.initializeResponseWithError(errorResult);

            // Initializing request URL
            String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration);

            Boolean encodeParamsToggle = headerUtils.isEncodeParamsToggleEnabled(actionConfiguration);

            URI uri;
            try {
                uri = uriUtils.createUriWithQueryParams(actionConfiguration, datasourceConfiguration, url,
                        encodeParamsToggle);
            } catch (URISyntaxException e) {
                ActionExecutionRequest actionExecutionRequest =
                        RequestCaptureFilter.populateRequestFields(actionConfiguration, null, insertedParams, objectMapper);
                actionExecutionRequest.setUrl(url);
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage(e));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            ActionExecutionRequest actionExecutionRequest =
                    RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);

            try {
                if (uriUtils.isHostDisallowed(uri)) {
                    errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed."));
                    errorResult.setRequest(actionExecutionRequest);
                    return Mono.just(errorResult);
                }
            } catch (UnknownHostException e) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
                    datasourceConfiguration);

            String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration);

            /* Check for content type */
            final String contentTypeError = headerUtils.verifyContentType(actionConfiguration.getHeaders());
            if (contentTypeError != null) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Invalid value for Content-Type."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            HttpMethod httpMethod = actionConfiguration.getHttpMethod();
            if (httpMethod == null) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("HTTPMethod must be set."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            if (HttpMethod.POST.equals(httpMethod)) {
                /**
                 * For content-type=application/json re-formatting is required.
                 * Ref: https://graphql.org/learn/serving-over-http/#post-request
                 *
                 * Graphql reference doc also mentions that content-type=application/graphql does not require any
                 * re-formatting.
                 * Ref: https://graphql.org/learn/serving-over-http/#post-request
                 *
                 * On searching over the web I also found that there are some custom content-type in use like
                 * `application/graphql+json` or `application/graphql-json` that expect the data in the same format
                 * as is for `application/json`. Hence, the current check assumes that any content type that differs
                 * from `application/graphql` would expect the data in the same format as for `application/json`
                 */
                if (!ApiContentType.GRAPHQL.getValue().equals(reqContentType)) {
                    /**
                     * When a GraphQL request is sent using HTTP POST method, then the request body needs to be in the
                     * following format:
                     * {
                     *     "query": "... graphql query body ...",
                     *     "variables": {"var1": val1, "var2": val2 ...},
                     *     "operationName": "name of operation" // only required if multiple operations are defined in a
                     *     single query body
                     * }
                     * Ref: https://graphql.org/learn/serving-over-http/
                     */
                    try {
                        actionConfiguration.setBody(convertToGraphQLPOSTBodyFormat(actionConfiguration));
                    } catch (AppsmithPluginException e) {
                        return Mono.error(e);
                    }
                }
            }
            else if (HttpMethod.GET.equals(httpMethod)) {
                /**
                 * When a GraphQL request is sent using GET method, the GraphQL body and variables are sent as part of
                 * query parameters in the URL.
                 * Ref: https://graphql.org/learn/serving-over-http/
                 */
                List<Property> additionalQueryParams = getGraphQLQueryParamsForBodyAndVariables(actionConfiguration);
                uri = uriUtils.addQueryParamsToURI(uri, additionalQueryParams, encodeParamsToggle);
            }
            else {
                /**
                 * Only POST and GET HTTP methods are supported by GraphQL specifications.
                 * Ref: https://graphql.org/learn/serving-over-http/
                 */
                return Mono.error(
                        new AppsmithPluginException(
                                AppsmithPluginError.PLUGIN_ERROR,
                                "Appsmith server has found an unexpected HTTP method configured with the GraphQL " +
                                        "plugin query: " + httpMethod
                        )
                );
            }

            final RequestCaptureFilter requestCaptureFilter = new RequestCaptureFilter(objectMapper);
            Object requestBodyObj = dataUtils.getRequestBodyObject(actionConfiguration, reqContentType,
                    encodeParamsToggle,
                    httpMethod);
            WebClient client = triggerUtils.getWebClient(webClientBuilder, apiConnection, reqContentType, objectMapper,
                    EXCHANGE_STRATEGIES, requestCaptureFilter);

            /* Triggering the actual REST API call */
            Set<String> hintMessages = new HashSet<String>();
            return triggerUtils.triggerApiCall(client, httpMethod, uri, requestBodyObj, actionExecutionRequest,
                    objectMapper,
                    hintMessages, errorResult, requestCaptureFilter);
        }

        @Override
        public Object substituteValueInInput(int index,
                                             String binding,
                                             String value,
                                             Object input,
                                             List<Map.Entry<String, String>> insertedParams,
                                             Object... args) {
            boolean isInputQueryBody = (boolean) args[0];
            if (!isInputQueryBody) {
                String queryVariables = (String) input;
                return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(queryVariables, value, null, insertedParams, null);
            }
            else {
                String queryBody = (String) input;
                return smartlyReplaceGraphQLQueryBodyPlaceholderWithValue(queryBody, value, insertedParams);
            }
        }

        /**
         * This method returns a set of paths that are expected to contain bindings that refer to the
         * same action object i.e. a cyclic reference. e.g. A GraphQL API response can contain pagination
         * cursors that are required to be configured in the pagination tab of the same API. We don't want to treat
         * these cyclic references as cyclic dependency errors.
         */
        @Override
        public Set<String> getSelfReferencingDataPaths() {
            return Set.of("pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.limitBased.limit.value",
                    "pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.limitBased.offset.value",
                    "pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.cursorBased.next.limit.value",
                    "pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.cursorBased.next.cursor.value",
                    "pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.cursorBased.previous.limit.value",
                    "pluginSpecifiedTemplates[" + PAGINATION_DATA_INDEX + "].value.cursorBased.previous.cursor.value");
        }
    }
}

## 内容5
### 文件路径：
app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java
### 代码内容：
package com.external.plugins;

import com.appsmith.external.dtos.ExecuteActionDTO;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.helpers.DataTypeStringUtils;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.PaginationType;
import com.appsmith.external.models.Property;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.BaseRestApiPluginExecutor;
import com.appsmith.external.services.SharedConfig;
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.lang.Boolean.TRUE;

@Slf4j
public class RestApiPlugin extends BasePlugin {

    public RestApiPlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    @Extension
    public static class RestApiPluginExecutor extends BaseRestApiPluginExecutor {

        public RestApiPluginExecutor(SharedConfig sharedConfig) {
            super(sharedConfig);
        }

        /**
         * Instead of using the default executeParametrized provided by pluginExecutor, this implementation affords an opportunity
         * also update the datasource and action configuration for pagination and some minor cleanup of the configuration before execution
         *
         * @param connection              : This is the connection that is established to the data source. This connection is according
         *                                to the parameters in Datasource Configuration
         * @param executeActionDTO        : This is the data structure sent by the client during execute. This contains the params
         *                                which would be used for substitution
         * @param datasourceConfiguration : These are the configurations which have been used to create a Datasource from a Plugin
         * @param actionConfiguration     : These are the configurations which have been used to create an Action from a Datasource.
         * @return
         */
        @Override
        public Mono<ActionExecutionResult> executeParameterized(APIConnection connection,
                                                                ExecuteActionDTO executeActionDTO,
                                                                DatasourceConfiguration datasourceConfiguration,
                                                                ActionConfiguration actionConfiguration) {

            final List<Property> properties = actionConfiguration.getPluginSpecifiedTemplates();
            List<Map.Entry<String, String>> parameters = new ArrayList<>();

            // Smartly substitute in actionConfiguration.body and replace all the bindings with values.
            Boolean smartJsonSubstitution = this.smartSubstitutionUtils.isSmartSubstitutionEnabled(properties);
            if (TRUE.equals(smartJsonSubstitution)) {
                // Do smart replacements in JSON body
                if (actionConfiguration.getBody() != null) {

                    // First extract all the bindings in order
                    List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(actionConfiguration.getBody());
                    // Replace all the bindings with a ? as expected in a prepared statement.
                    String updatedBody = MustacheHelper.replaceMustacheWithPlaceholder(actionConfiguration.getBody(), mustacheKeysInOrder);

                    try {
                        updatedBody = (String) smartSubstitutionOfBindings(updatedBody,
                                mustacheKeysInOrder,
                                executeActionDTO.getParams(),
                                parameters);
                    } catch (AppsmithPluginException e) {
                        ActionExecutionResult errorResult = new ActionExecutionResult();
                        errorResult.setIsExecutionSuccess(false);
                        errorResult.setErrorInfo(e);
                        errorResult.setStatusCode(AppsmithPluginError.PLUGIN_ERROR.getAppErrorCode().toString());
                        return Mono.just(errorResult);
                    }

                    actionConfiguration.setBody(updatedBody);
                }
            }

            prepareConfigurationsForExecution(executeActionDTO, actionConfiguration, datasourceConfiguration);

            // If the action is paginated, update the configurations to update the correct URL.
            if (actionConfiguration.getPaginationType() != null &&
                    PaginationType.URL.equals(actionConfiguration.getPaginationType()) &&
                    executeActionDTO.getPaginationField() != null) {
                updateDatasourceConfigurationForPagination(actionConfiguration, datasourceConfiguration, executeActionDTO.getPaginationField());
                updateActionConfigurationForPagination(actionConfiguration, executeActionDTO.getPaginationField());
            }

            // Filter out any empty headers
            headerUtils.removeEmptyHeaders(actionConfiguration);

            return this.executeCommon(connection, datasourceConfiguration, actionConfiguration, parameters);
        }

        public Mono<ActionExecutionResult> executeCommon(APIConnection apiConnection,
                                                         DatasourceConfiguration datasourceConfiguration,
                                                         ActionConfiguration actionConfiguration,
                                                         List<Map.Entry<String, String>> insertedParams) {

            // Initializing object for error condition
            ActionExecutionResult errorResult = new ActionExecutionResult();
            initUtils.initializeResponseWithError(errorResult);

            // Set of hint messages that can be returned to the user.
            Set<String> hintMessages = new HashSet();

            // Initializing request URL
            String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration);

            Boolean encodeParamsToggle = headerUtils.isEncodeParamsToggleEnabled(actionConfiguration);

            URI uri;
            try {
                uri = uriUtils.createUriWithQueryParams(actionConfiguration, datasourceConfiguration, url,
                        encodeParamsToggle);
            } catch (URISyntaxException e) {
                ActionExecutionRequest actionExecutionRequest =
                        RequestCaptureFilter.populateRequestFields(actionConfiguration, null, insertedParams, objectMapper);
                actionExecutionRequest.setUrl(url);
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage(e));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            ActionExecutionRequest actionExecutionRequest =
                    RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);

            try {
                if (uriUtils.isHostDisallowed(uri)) {
                    errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed."));
                    errorResult.setRequest(actionExecutionRequest);
                    return Mono.just(errorResult);
                }
            } catch (UnknownHostException e) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
                    datasourceConfiguration);
            String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration);

            /* Check for content type */
            final String contentTypeError = headerUtils.verifyContentType(actionConfiguration.getHeaders());
            if (contentTypeError != null) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Invalid value for Content-Type."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            HttpMethod httpMethod = actionConfiguration.getHttpMethod();
            if (httpMethod == null) {
                errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("HTTPMethod must be set."));
                errorResult.setRequest(actionExecutionRequest);
                return Mono.just(errorResult);
            }

            final RequestCaptureFilter requestCaptureFilter = new RequestCaptureFilter(objectMapper);
            Object requestBodyObj = dataUtils.getRequestBodyObject(actionConfiguration, reqContentType,
                    encodeParamsToggle,
                    httpMethod);
            WebClient client = triggerUtils.getWebClient(webClientBuilder, apiConnection, reqContentType, objectMapper,
                    EXCHANGE_STRATEGIES, requestCaptureFilter);

            /* Triggering the actual REST API call */
            return triggerUtils.triggerApiCall(client, httpMethod, uri, requestBodyObj, actionExecutionRequest,
                    objectMapper, hintMessages, errorResult, requestCaptureFilter);
        }

        private ActionConfiguration updateActionConfigurationForPagination(ActionConfiguration actionConfiguration,
                                                                           PaginationField paginationField) {
            if (PaginationField.NEXT.equals(paginationField) || PaginationField.PREV.equals(paginationField)) {
                actionConfiguration.setPath("");
                actionConfiguration.setQueryParameters(null);
            }
            return actionConfiguration;
        }

        private DatasourceConfiguration updateDatasourceConfigurationForPagination(ActionConfiguration actionConfiguration,
                                                                                DatasourceConfiguration datasourceConfiguration,
                                                                                PaginationField paginationField) {
            if (PaginationField.NEXT.equals(paginationField)) {
                if (actionConfiguration.getNext() == null) {
                    datasourceConfiguration.setUrl(null);
                } else {
                    datasourceConfiguration.setUrl(URLDecoder.decode(actionConfiguration.getNext(), StandardCharsets.UTF_8));
                }
            } else if (PaginationField.PREV.equals(paginationField)) {
                datasourceConfiguration.setUrl(actionConfiguration.getPrev());
            }
            return datasourceConfiguration;
        }

        @Override
        public Object substituteValueInInput(int index,
                                             String binding,
                                             String value,
                                             Object input,
                                             List<Map.Entry<String, String>> insertedParams,
                                             Object... args) {
            String jsonBody = (String) input;
            return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null);
        }
    }
}

Output:
【漏洞类型】主机拒绝列表绕过漏洞
【严重性】High
【漏洞摘要】原始代码在处理重定向和查询参数时，依赖于`URIUtils.isHostDisallowed`方法来检查主机是否允许。然而，这个方法在`TriggerUtils.httpCall`中被移除了，并且在`RestApiPlugin`和`GraphQLPlugin`中也被删除了。这导致了原始的主机拒绝列表检查被绕过，攻击者可能利用重定向机制访问被禁止的主机。
【关键代码模式】`URIUtils.isHostDisallowed`、`TriggerUtils.httpCall`、`RestApiPlugin`、`GraphQLPlugin`
【修复方法说明】修复方法通过在WebClient的构建过程中添加自定义的NameResolver来检查主机是否在禁止列表中，从而防止重定向到被禁止的主机。这种方法遵循了安全编码的最佳实践，即在所有可能的代码路径中都进行必要的主机验证。