org.codehaus.mojo.signature
@@ -768,6 +776,10 @@
26.0-jre
2.2.3
+
+ 8.11.4
+ 1.9.4
+
1.8
UTF-8
diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java
index 94b31ac844..61466c0e2c 100644
--- a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java
+++ b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java
@@ -53,9 +53,11 @@ public interface WebResourceLoader
* Returns a {@link URL} for the given path. The method will return null if no
* resources could be found for the given path.
*
+ * Note: The path is a web path and uses "/" as path separator
+ *
* @param path resource path
*
* @return url object for the given path or null
*/
- public URL getResource(String path);
+ URL getResource(String path);
}
diff --git a/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java b/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java
new file mode 100644
index 0000000000..16dca8e910
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java
@@ -0,0 +1,8 @@
+package sonia.scm.util;
+
+public class CRLFInjectionException extends IllegalArgumentException{
+
+ public CRLFInjectionException(String message) {
+ super(message);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
index bc3f4e74cd..2744addc62 100644
--- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
+++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
@@ -344,8 +344,7 @@ public final class HttpUtil
"parameter \"{}\" contains a character which could be an indicator for a crlf injection",
parameter);
- throw new IllegalArgumentException(
- "parameter contains an illegal character");
+ throw new CRLFInjectionException("parameter contains an illegal character");
}
}
diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
index ecab36969c..ddcce9f4e8 100644
--- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
+++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
@@ -12,6 +12,8 @@ public class VndMediaType {
private static final String SUBTYPE_PREFIX = "vnd.scmm-";
public static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX;
public static final String SUFFIX = "+json;v=" + VERSION;
+ public static final String PLAIN_TEXT_PREFIX = "text/" + SUBTYPE_PREFIX;
+ public static final String PLAIN_TEXT_SUFFIX = "+plain;v=" + VERSION;
public static final String USER = PREFIX + "user" + SUFFIX;
public static final String GROUP = PREFIX + "group" + SUFFIX;
@@ -22,6 +24,7 @@ public class VndMediaType {
public static final String TAG = PREFIX + "tag" + SUFFIX;
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
+ public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX;
public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX;
public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX;
public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX;
diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
index 85764291a7..fc095828b3 100644
--- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
+++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
@@ -37,6 +37,7 @@ public class RepositoryAccessITCase {
private final String repositoryType;
private File folder;
+ private RepositoryRequests.AppliedRepositoryGetRequest repositoryGetRequest;
public RepositoryAccessITCase(String repositoryType) {
this.repositoryType = repositoryType;
@@ -48,9 +49,15 @@ public class RepositoryAccessITCase {
}
@Before
- public void initClient() {
+ public void init() {
TestData.createDefault();
folder = tempFolder.getRoot();
+ repositoryGetRequest = RepositoryRequests.start()
+ .given()
+ .url(TestData.getDefaultRepositoryUrl(repositoryType))
+ .usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
+ .get()
+ .assertStatusCode(HttpStatus.SC_OK);
}
@Test
@@ -246,5 +253,64 @@ public class RepositoryAccessITCase {
assertThat(changesets).size().isBetween(2, 3); // svn has an implicit root revision '0' that is extra to the two commits
}
+
+ @Test
+ public void shouldFindDiffs() throws IOException {
+ RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
+
+ RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "a.txt", "a");
+ RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "b.txt", "b");
+
+ String changesetsUrl = given()
+ .when()
+ .get(TestData.getDefaultRepositoryUrl(repositoryType))
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .extract()
+ .path("_links.changesets.href");
+
+ String diffUrl = given()
+ .when()
+ .get(changesetsUrl)
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .extract()
+ .path("_embedded.changesets[0]._links.diff.href");
+
+ given()
+ .when()
+ .get(diffUrl)
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .extract()
+ .body()
+ .asString()
+ .contains("diff");
+
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void shouldFindFileHistory() throws IOException {
+ RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
+ Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "folder/subfolder/a.txt", "a");
+ repositoryGetRequest
+ .usingRepositoryResponse()
+ .requestSources()
+ .usingSourcesResponse()
+ .requestSelf("folder")
+ .usingSourcesResponse()
+ .requestSelf("subfolder")
+ .usingSourcesResponse()
+ .requestFileHistory("a.txt")
+ .assertStatusCode(HttpStatus.SC_OK)
+ .usingChangesetsResponse()
+ .assertChangesets(changesets -> {
+ assertThat(changesets).hasSize(1);
+ assertThat(changesets.get(0)).containsEntry("id", changeset.getId());
+ assertThat(changesets.get(0)).containsEntry("description", changeset.getDescription());
+ }
+ );
+ }
}
diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java
new file mode 100644
index 0000000000..41fa2a6a3d
--- /dev/null
+++ b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java
@@ -0,0 +1,249 @@
+package sonia.scm.it;
+
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+
+/**
+ * Encapsulate rest requests of a repository in builder pattern
+ *
+ * A Get Request can be applied with the methods request*()
+ * These methods return a AppliedGet*Request object
+ * This object can be used to apply general assertions over the rest Assured response
+ * In the AppliedGet*Request classes there is a using*Response() method
+ * that return the *Response class containing specific operations related to the specific response
+ * the *Response class contains also the request*() method to apply the next GET request from a link in the response.
+ */
+public class RepositoryRequests {
+
+ private String url;
+ private String username;
+ private String password;
+
+ static RepositoryRequests start() {
+ return new RepositoryRequests();
+ }
+
+ Given given() {
+ return new Given();
+ }
+
+
+ /**
+ * Apply a GET Request to the extracted url from the given link
+ *
+ * @param linkPropertyName the property name of link
+ * @param response the response containing the link
+ * @return the response of the GET request using the given link
+ */
+ private Response getResponseFromLink(Response response, String linkPropertyName) {
+ return getResponse(response
+ .then()
+ .extract()
+ .path(linkPropertyName));
+ }
+
+
+ /**
+ * Apply a GET Request to the given url and return the response.
+ *
+ * @param url the url of the GET request
+ * @return the response of the GET request using the given url
+ */
+ private Response getResponse(String url) {
+ return RestAssured.given()
+ .auth().preemptive().basic(username, password)
+ .when()
+ .get(url);
+ }
+
+ private void setUrl(String url) {
+ this.url = url;
+ }
+
+ private void setUsername(String username) {
+ this.username = username;
+ }
+
+ private void setPassword(String password) {
+ this.password = password;
+ }
+
+ private String getUrl() {
+ return url;
+ }
+
+ private String getUsername() {
+ return username;
+ }
+
+ private String getPassword() {
+ return password;
+ }
+
+ class Given {
+
+ GivenUrl url(String url) {
+ setUrl(url);
+ return new GivenUrl();
+ }
+
+ }
+
+ class GivenWithUrlAndAuth {
+ AppliedRepositoryGetRequest get() {
+ return new AppliedRepositoryGetRequest(
+ getResponse(url)
+ );
+ }
+ }
+
+ class AppliedGetRequest {
+ private Response response;
+
+ public AppliedGetRequest(Response response) {
+ this.response = response;
+ }
+
+ /**
+ * apply custom assertions to the actual response
+ *
+ * @param consumer consume the response in order to assert the content. the header, the payload etc..
+ * @return the self object
+ */
+ SELF assertResponse(Consumer consumer) {
+ consumer.accept(response);
+ return (SELF) this;
+ }
+
+ /**
+ * special assertion of the status code
+ *
+ * @param expectedStatusCode the expected status code
+ * @return the self object
+ */
+ SELF assertStatusCode(int expectedStatusCode) {
+ this.response.then().assertThat().statusCode(expectedStatusCode);
+ return (SELF) this;
+ }
+
+ }
+
+ class AppliedRepositoryGetRequest extends AppliedGetRequest {
+
+ AppliedRepositoryGetRequest(Response response) {
+ super(response);
+ }
+
+ RepositoryResponse usingRepositoryResponse() {
+ return new RepositoryResponse(super.response);
+ }
+ }
+
+ class RepositoryResponse {
+
+ private Response repositoryResponse;
+
+ public RepositoryResponse(Response repositoryResponse) {
+ this.repositoryResponse = repositoryResponse;
+ }
+
+ AppliedGetSourcesRequest requestSources() {
+ return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href"));
+ }
+
+ AppliedGetChangesetsRequest requestChangesets(String fileName) {
+ return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href"));
+ }
+ }
+
+ class AppliedGetChangesetsRequest extends AppliedGetRequest {
+
+ AppliedGetChangesetsRequest(Response response) {
+ super(response);
+ }
+
+ ChangesetsResponse usingChangesetsResponse() {
+ return new ChangesetsResponse(super.response);
+ }
+ }
+
+ class ChangesetsResponse {
+ private Response changesetsResponse;
+
+ public ChangesetsResponse(Response changesetsResponse) {
+ this.changesetsResponse = changesetsResponse;
+ }
+
+ ChangesetsResponse assertChangesets(Consumer> changesetsConsumer) {
+ List