]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15118 Reading DevOps response header should be case-insensitive
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 30 Jun 2021 17:47:02 +0000 (13:47 -0400)
committersonartech <sonartech@sonarsource.com>
Thu, 1 Jul 2021 20:03:19 +0000 (20:03 +0000)
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationHttpClientImpl.java
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java
server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationHttpClientImplTest.java
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java
server/sonar-auth-common/src/main/java/org/sonar/auth/OAuthRestClient.java
server/sonar-auth-common/src/test/java/org/sonar/auth/OAuthRestClientTest.java

index 0f56ffc5c52e758ff5bf9cbd32f2580a721a4c23..71b654655dd7bb8cedb506304a3a6c42719638b0 100644 (file)
@@ -203,7 +203,7 @@ public class GithubApplicationHttpClientImpl implements GithubApplicationHttpCli
 
   @CheckForNull
   private static String readNextEndPoint(okhttp3.Response response) {
-    String links = response.header("link");
+    String links = response.headers().get("link");
     if (links == null || links.isEmpty() || !links.contains("rel=\"next\"")) {
       return null;
     }
index ffe6e647de61fd39386f7de04c37408eb35794f5..4932e2db87e98ab8eb44f24dfae728067940f5bc 100644 (file)
@@ -29,6 +29,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nullable;
+import okhttp3.Headers;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
 import okhttp3.RequestBody;
@@ -302,11 +303,12 @@ public class GitlabHttpClient {
       .build();
 
     try (Response response = client.newCall(request).execute()) {
+      Headers headers = response.headers();
       checkResponseIsSuccessful(response, "Could not get projects from GitLab instance");
       List<Project> projectList = Project.parseJsonArray(response.body().string());
-      int returnedPageNumber = parseAndGetIntegerHeader(response.header("X-Page"));
-      int returnedPageSize = parseAndGetIntegerHeader(response.header("X-Per-Page"));
-      int totalProjects = parseAndGetIntegerHeader(response.header("X-Total"));
+      int returnedPageNumber = parseAndGetIntegerHeader(headers.get("X-Page"));
+      int returnedPageSize = parseAndGetIntegerHeader(headers.get("X-Per-Page"));
+      int totalProjects = parseAndGetIntegerHeader(headers.get("X-Total"));
       return new ProjectList(projectList, returnedPageNumber, returnedPageSize, totalProjects);
     } catch (JsonSyntaxException e) {
       throw new IllegalArgumentException("Could not parse GitLab answer to search projects. Got a non-json payload as result.");
index 5922ececdb235908c91a67e02094ac8bcf85db49..8bbf5da3a706c35ca2fb6a0883ea195cfa907241 100644 (file)
@@ -162,6 +162,17 @@ public class GithubApplicationHttpClientImplTest {
     assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
   }
 
+  @Test
+  public void get_returns_endPoint_when_link_header_has_next_rel_different_case() throws IOException {
+    String linkHeader = "<https://api.github.com/installation/repositories?per_page=5&page=2>; rel=\"next\"";
+    server.enqueue(new MockResponse().setBody(randomBody)
+      .setHeader("Link", linkHeader));
+
+    GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
+
+    assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
+  }
+
   @DataProvider
   public static Object[][] linkHeadersWithNextRel() {
     String expected = "https://api.github.com/installation/repositories?per_page=5&page=2";
index 283371f2cb447d3e9f917647a4a73f13b8c48bc7..229f26f6ebebc8b24f96b1e56222e19474ea417a 100644 (file)
@@ -243,6 +243,43 @@ public class GitlabHttpClientTest {
     assertThat(projectGitlabRequest.getMethod()).isEqualTo("GET");
   }
 
+  @Test
+  public void search_projects_with_case_insensitive_pagination_headers() throws InterruptedException {
+    MockResponse projects1 = new MockResponse()
+      .setResponseCode(200)
+      .setBody("[\n"
+        + "  {\n"
+        + "    \"id\": 1,\n"
+        + "    \"name\": \"SonarQube example 1\",\n"
+        + "    \"name_with_namespace\": \"SonarSource / SonarQube / SonarQube example 1\",\n"
+        + "    \"path\": \"sonarqube-example-1\",\n"
+        + "    \"path_with_namespace\": \"sonarsource/sonarqube/sonarqube-example-1\",\n"
+        + "    \"web_url\": \"https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-1\"\n"
+        + "  }"
+        + "]");
+    projects1.addHeader("x-page", 1);
+    projects1.addHeader("x-Per-page", 1);
+    projects1.addHeader("X-Total", 2);
+    server.enqueue(projects1);
+
+    ProjectList projectList = underTest.searchProjects(gitlabUrl, "pat", "example", 1, 10);
+
+    assertThat(projectList.getPageNumber()).isEqualTo(1);
+    assertThat(projectList.getPageSize()).isEqualTo(1);
+    assertThat(projectList.getTotal()).isEqualTo(2);
+
+    assertThat(projectList.getProjects()).hasSize(1);
+    assertThat(projectList.getProjects()).extracting(
+      Project::getId, Project::getName, Project::getNameWithNamespace, Project::getPath, Project::getPathWithNamespace, Project::getWebUrl).containsExactly(
+      tuple(1L, "SonarQube example 1", "SonarSource / SonarQube / SonarQube example 1", "sonarqube-example-1", "sonarsource/sonarqube/sonarqube-example-1",
+        "https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-1"));
+
+    RecordedRequest projectGitlabRequest = server.takeRequest(10, TimeUnit.SECONDS);
+    String gitlabUrlCall = projectGitlabRequest.getRequestUrl().toString();
+    assertThat(gitlabUrlCall).isEqualTo(server.url("") + "projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=example&page=1&per_page=10");
+    assertThat(projectGitlabRequest.getMethod()).isEqualTo("GET");
+  }
+
   @Test
   public void search_projects_projectName_param_should_be_encoded() throws InterruptedException {
     MockResponse projects = new MockResponse()
index 8a95fba3c956ef68b91aec3b1251c2a40840aca2..c21795d5aefc834e7d42171e7d5b86e676633317 100644 (file)
@@ -27,6 +27,7 @@ import com.github.scribejava.core.oauth.OAuth20Service;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
@@ -81,11 +82,14 @@ public class OAuthRestClient {
   }
 
   private static Optional<String> readNextEndPoint(Response response) {
-    String link = response.getHeader("Link");
-    if (link == null || link.isEmpty() || !link.contains("rel=\"next\"")) {
+    Optional<String> link = response.getHeaders().entrySet().stream()
+      .filter(e -> "Link".equalsIgnoreCase(e.getKey()))
+      .map(Map.Entry::getValue)
+      .findAny();
+    if (link.isEmpty() || link.get().isEmpty() || !link.get().contains("rel=\"next\"")) {
       return Optional.empty();
     }
-    Matcher nextLinkMatcher = NEXT_LINK_PATTERN.matcher(link);
+    Matcher nextLinkMatcher = NEXT_LINK_PATTERN.matcher(link.get());
     if (!nextLinkMatcher.find()) {
       return Optional.empty();
     }
index 60be82738b0d1fa84c063a738e56712a029f3b6f..3f29fd2597d9106d9b585953edf3e1ac926df074 100644 (file)
@@ -97,6 +97,20 @@ public class OAuthRestClientTest {
     assertThat(response).contains("A", "B");
   }
 
+  @Test
+  public void execute_paginated_request_case_insensitive_headers() {
+    mockWebServer.enqueue(new MockResponse()
+      .setHeader("link", "<" + serverUrl + "/test?per_page=100&page=2>; rel=\"next\", <" + serverUrl + "/test?per_page=100&page=2>; rel=\"last\"")
+      .setBody("A"));
+    mockWebServer.enqueue(new MockResponse()
+      .setHeader("link", "<" + serverUrl + "/test?per_page=100&page=1>; rel=\"prev\", <" + serverUrl + "/test?per_page=100&page=1>; rel=\"first\"")
+      .setBody("B"));
+
+    List<String> response = executePaginatedRequest(serverUrl + "/test", oAuth20Service, auth2AccessToken, Arrays::asList);
+
+    assertThat(response).contains("A", "B");
+  }
+
   @Test
   public void fail_to_executed_paginated_request() {
     mockWebServer.enqueue(new MockResponse()