Skip to content
Snippets Groups Projects
Commit 100137ca authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Improved validation of list of links upload

parent d93836d2
No related branches found
No related tags found
No related merge requests found
Pipeline #8800 passed
...@@ -10,6 +10,8 @@ import it.inaf.ia2.vospace.ui.data.CreateLinkRequest; ...@@ -10,6 +10,8 @@ import it.inaf.ia2.vospace.ui.data.CreateLinkRequest;
import it.inaf.ia2.vospace.ui.exception.BadRequestException; import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import it.inaf.ia2.vospace.ui.exception.VOSpaceStatusException; import it.inaf.ia2.vospace.ui.exception.VOSpaceStatusException;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
...@@ -18,6 +20,7 @@ import net.ivoa.xml.vospace.v2.LinkNode; ...@@ -18,6 +20,7 @@ import net.ivoa.xml.vospace.v2.LinkNode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
...@@ -31,6 +34,9 @@ public class CreateLinksController extends BaseController { ...@@ -31,6 +34,9 @@ public class CreateLinksController extends BaseController {
private static final Logger LOG = LoggerFactory.getLogger(CreateLinksController.class); private static final Logger LOG = LoggerFactory.getLogger(CreateLinksController.class);
@Value("${list-of-links.limit:1000}")
private int listOfLinksSizeLimit;
@Autowired @Autowired
private VOSpaceClient client; private VOSpaceClient client;
...@@ -63,11 +69,17 @@ public class CreateLinksController extends BaseController { ...@@ -63,11 +69,17 @@ public class CreateLinksController extends BaseController {
List<CompletableFuture<?>> currentHttpCallsGroup = new ArrayList<>(); List<CompletableFuture<?>> currentHttpCallsGroup = new ArrayList<>();
httpCallsGroups.add(currentHttpCallsGroup); httpCallsGroups.add(currentHttpCallsGroup);
for (String url : fileContent.replaceAll("\\r\\n?", "\n").split("\n")) { // normalize newlines and split on them // normalize newlines and split on them
String[] urls = fileContent.replaceAll("\\r\\n?", "\n").split("\n");
if (urls.length > listOfLinksSizeLimit) {
throw new BadRequestException("List is too large: " + urls.length + " lines detected, limit is " + listOfLinksSizeLimit);
}
for (String url : urls) {
if (!url.isBlank()) { if (!url.isBlank()) {
String fileName = url.substring(url.lastIndexOf("/") + 1); url = url.trim();
String uri = parent.getUri() + "/" + fileName; String uri = parent.getUri() + "/" + getFileNameFromUrl(url);
LinkNode link = new LinkNode(); LinkNode link = new LinkNode();
link.setUri(uri); link.setUri(uri);
...@@ -89,6 +101,28 @@ public class CreateLinksController extends BaseController { ...@@ -89,6 +101,28 @@ public class CreateLinksController extends BaseController {
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }
private String getFileNameFromUrl(String url) {
try {
// parse URL and remove the query string
String urlPath = new URL(url).getPath();
if (urlPath.endsWith("/")) {
// remove last char if it is a slash
urlPath = urlPath.substring(0, urlPath.length() - 1);
}
if (urlPath.isEmpty() || !urlPath.contains("/")) {
throw new BadRequestException("Unable to extract file name from URL " + url);
}
String fileName = urlPath.substring(urlPath.lastIndexOf("/") + 1);
if (fileName.isEmpty()) {
throw new BadRequestException("Unable to extract file name from URL " + url);
}
return fileName;
} catch (MalformedURLException ex) {
throw new BadRequestException("Invalid URL: " + url);
}
}
private ContainerNode getFolder(String folderPath) { private ContainerNode getFolder(String folderPath) {
try { try {
return (ContainerNode) client.getNode("/" + folderPath); return (ContainerNode) client.getNode("/" + folderPath);
......
...@@ -11,6 +11,7 @@ cors.allowed.origin=http://localhost:8080 ...@@ -11,6 +11,7 @@ cors.allowed.origin=http://localhost:8080
logging.level.it.inaf=TRACE logging.level.it.inaf=TRACE
trusted.eppn.scope=inaf.it trusted.eppn.scope=inaf.it
list-of-links.limit=1000
support.contact.label=IA2 team support.contact.label=IA2 team
support.contact.email=ia2@inaf.it support.contact.email=ia2@inaf.it
...@@ -10,6 +10,7 @@ import it.inaf.ia2.aa.data.User; ...@@ -10,6 +10,7 @@ import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.CreateLinkRequest; import it.inaf.ia2.vospace.ui.data.CreateLinkRequest;
import it.inaf.ia2.vospace.ui.exception.VOSpaceStatusException; import it.inaf.ia2.vospace.ui.exception.VOSpaceStatusException;
import java.util.Collections;
import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -109,6 +110,41 @@ public class CreateLinksControllerTest { ...@@ -109,6 +110,41 @@ public class CreateLinksControllerTest {
verify(client, times(75)).createNode(any()); verify(client, times(75)).createNode(any());
} }
@Test
public void testInvalidUrl() throws Exception {
testInvalidContent("foo");
}
@Test
public void testInvalidUrlNoFile() throws Exception {
testInvalidContent("http://archives.ia2.inaf.it/");
}
@Test
public void testInvalidUrlNoFileNoSlash() throws Exception {
testInvalidContent("http://archives.ia2.inaf.it");
}
@Test
public void testTooManyLinks() throws Exception {
testInvalidContent(String.join("\n", Collections.nCopies(1500, "http://archives.ia2.inaf.it/files/aao/SC182172.fits.gz")));
}
private void testInvalidContent(String fileContent) throws Exception {
ContainerNode myFolder = new ContainerNode();
when(client.getNode("/path/to/myfolder")).thenReturn(myFolder);
MockMultipartFile file = new MockMultipartFile("file", fileContent.getBytes());
mockMvc.perform(multipart("/uploadLinks")
.file(file)
.param("folder", "path/to/myfolder")
.sessionAttr("user_data", user))
.andDo(print())
.andExpect(status().isBadRequest());
}
private MockMultipartFile getListOfLinksMockMultipartFile() throws Exception { private MockMultipartFile getListOfLinksMockMultipartFile() throws Exception {
return new MockMultipartFile("file", UploadControllerTest.class.getClassLoader().getResourceAsStream("list-of-links.txt")); return new MockMultipartFile("file", UploadControllerTest.class.getClassLoader().getResourceAsStream("list-of-links.txt"));
} }
......
http://archives.ia2.inaf.it/files/aao/SC182159.fits.gz http://archives.ia2.inaf.it/files/aao/SC182159.fits.gz
http://archives.ia2.inaf.it/files/aao/SC182160.fits.gz http://archives.ia2.inaf.it/files/aao/SC182160.fits.gz/
http://archives.ia2.inaf.it/files/aao/SC182161.fits.gz http://archives.ia2.inaf.it/files/aao/SC182161.fits.gz?query=xxx
http://archives.ia2.inaf.it/files/aao/SC182169.fits.gz http://archives.ia2.inaf.it/files/aao/SC182169.fits.gz
http://archives.ia2.inaf.it/files/aao/SC182170.fits.gz http://archives.ia2.inaf.it/files/aao/SC182170.fits.gz
http://archives.ia2.inaf.it/files/aao/SC182171.fits.gz http://archives.ia2.inaf.it/files/aao/SC182171.fits.gz
http://archives.ia2.inaf.it/files/aao/SC182172.fits.gz http://archives.ia2.inaf.it/files/aao/SC182172.fits.gz
......
...@@ -24,7 +24,10 @@ ...@@ -24,7 +24,10 @@
<b-form-invalid-feedback id="node-name-input-feedback" class="text-right">{{nodeNameError}}</b-form-invalid-feedback> <b-form-invalid-feedback id="node-name-input-feedback" class="text-right">{{nodeNameError}}</b-form-invalid-feedback>
</b-form> </b-form>
<b-form inline v-if="mode === 'multiple'"> <b-form inline v-if="mode === 'multiple'">
<p>Upload list of links (separated by newlines)</p> <p>
Upload list of links (separated by newlines)<br />
<em>Maximum 1000 links per file!</em>
</p>
<b-form-file class="text-left" v-model="file" :multiple="false" placeholder="Choose your file or drop it here..." drop-placeholder="Drop file here..." :state="fileState"></b-form-file> <b-form-file class="text-left" v-model="file" :multiple="false" placeholder="Choose your file or drop it here..." drop-placeholder="Drop file here..." :state="fileState"></b-form-file>
<b-form-invalid-feedback id="file-input-feedback" class="text-right">{{fileError}}</b-form-invalid-feedback> <b-form-invalid-feedback id="file-input-feedback" class="text-right">{{fileError}}</b-form-invalid-feedback>
</b-form> </b-form>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment