Skip to content

Commit

Permalink
MapUpload: Add validation endpoint / form for map name and scenario.lua
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Aug 18, 2019
1 parent d5e2bdb commit 6e92ec3
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -91,7 +90,7 @@ public boolean matches(HttpServletRequest request) {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/faforever/api/error/ApiException.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ public ApiException(Error error) {
this(new Error[]{error});
}

public ApiException(Error[] errors) {
public ApiException(Error... errors) {
super(Arrays.toString(errors));
this.errors = errors;
}

public static ApiException of(ErrorCode errorCode, Object... args) {
return new ApiException(new Error(errorCode, args));
}
}
7 changes: 4 additions & 3 deletions src/main/java/com/faforever/api/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ public enum ErrorCode {
VOTING_SUBJECT_DOES_NOT_EXIST(180, "Voting subject does not exist", "There is no voting subject with the ID ''{0}''."),
VOTING_CHOICE_DOES_NOT_EXIST(181, "Invalid choice", "There is no voting choice with the ID ''{0}''."),
STEAM_ID_ALREADY_LINKED(182, " Steam account already linked to a FAF account", "You linked this account already to user with name ''{0}''."),
MAP_NAME_INVALID(183, "Map name invalid", "The name of the map in the scenario file can only contain printable ASCII characters and blanks."),
MOD_NAME_INVALID(184, "Mod name invalid", "The name of the mod in the scenario file can only contain printable ASCII characters and blanks.");

MAP_NAME_INVALID_CHARACTER(183, "Map name invalid", "Only latin characters, numbers, blanks and the minus are allowed."),
MOD_NAME_INVALID(184, "Mod name invalid", "The name of the mod in the scenario file can only contain printable ASCII characters and blanks."),
MAP_NAME_INVALID_MINUS_OCCURENCE(185, "Map name invalid", "Only a maximum of {0} minus characters are allowed."),
MAP_SCRIPT_LINE_MISSING(186, "Missing scenario.lua line", "The scenario.lua has to contain the following line: {0}");

private final int code;
private final String title;
Expand Down
81 changes: 76 additions & 5 deletions src/main/java/com/faforever/api/map/MapService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.faforever.commons.io.Zipper;
import com.faforever.commons.lua.LuaLoader;
import com.faforever.commons.map.PreviewGenerator;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
Expand All @@ -26,6 +28,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
Expand All @@ -37,29 +40,97 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.github.nocatch.NoCatch.noCatch;
import static java.text.MessageFormat.format;

@Service
@Slf4j
@AllArgsConstructor
public class MapService {
private static final Pattern MAP_NAME_VALIDATION_PATTERN = Pattern.compile("[a-zA-Z0-9\\- ]+");
private static final Pattern ADAPTIVE_MAP_PATTERN = Pattern.compile("AdaptiveMap\\w=\\wtrue");
private static final int MAP_NAME_MINUS_MAX_OCCURENCE = 3;

private static final String[] REQUIRED_FILES = new String[]{
".scmap",
"_save.lua",
"_scenario.lua",
"_script.lua"};
"_script.lua",
};

private static final String[] ADAPTIVE_REQUIRED_FILES = new String[]{
"_options.lua",
"_tables.lua",
};

private static final Charset MAP_CHARSET = StandardCharsets.ISO_8859_1;
private static final String STUPID_MAP_FOLDER_PREFIX = "maps/";
private static final int MAP_DISPLAY_NAME_MAX_LENGTH = 100;
private final FafApiProperties fafApiProperties;
private final MapRepository mapRepository;
private final ContentService contentService;

@VisibleForTesting
void validate(MapValidationRequest mapValidationRequest) {
String mapName = mapValidationRequest.getName();
Assert.notNull(mapName, "The map name is mandatory.");
String scenarioLua = mapValidationRequest.getScenarioLua();

Collection<Error> mapNameErrors = validateMapName(mapName);
if (!mapNameErrors.isEmpty()) {
throw new ApiException(mapNameErrors.toArray(new Error[0]));
}

if (scenarioLua != null) {
Collection<Error> scenarioLuaErrors = validateScenarioLua(mapName, scenarioLua);
if (!scenarioLuaErrors.isEmpty()) {
throw new ApiException(scenarioLuaErrors.toArray(new Error[0]));
}
}
}

private Collection<Error> validateMapName(String mapName) {
List<Error> errors = new ArrayList<>();

if (!MAP_NAME_VALIDATION_PATTERN.matcher(mapName).matches()) {
errors.add(new Error(ErrorCode.MAP_NAME_INVALID_CHARACTER));
}

if (StringUtils.countOccurrencesOf(mapName, "-") > MAP_NAME_MINUS_MAX_OCCURENCE) {
errors.add(new Error(ErrorCode.MAP_NAME_INVALID_MINUS_OCCURENCE, MAP_NAME_MINUS_MAX_OCCURENCE));
}

return errors;
}

private Collection<Error> validateScenarioLua(String validMapName, String scenarioLua) {
List<Error> errors = new ArrayList<>();

java.util.Map<String, String> declarations = ImmutableMap.<String, String>builder()
.put("map", ".scmap")
.put("save", "_save.lua")
.put("script", "_script.lua")
.build();

for (Entry entry : declarations.entrySet()) {
if (!scenarioLua.matches(format("{0}\\w=\\w'\\/maps\\/{0}(\\.v\\d{4})?/{0}{1}'", entry.getKey(), entry.getValue()))) {
errors.add(new Error(ErrorCode.MAP_SCRIPT_LINE_MISSING, format(
"{0} = ''/maps/{2}/{2}{1}'',", entry.getKey(), entry.getValue(), validMapName
)));
}
}

return errors;
}

@Transactional
@SneakyThrows
@CacheEvict(value = {Map.TYPE_NAME, MapVersion.TYPE_NAME}, allEntries = true)
Expand Down Expand Up @@ -100,9 +171,9 @@ private Path copyToTemporaryDirectory(byte[] mapData, MapUploadData progressData
}

private void unzipFile(MapUploadData mapData) throws IOException, ArchiveException {
Unzipper.from(mapData.getUploadedFile())
.to(mapData.getBaseDir())
.unzip();
Unzipper.from(mapData.getUploadedFile())
.to(mapData.getBaseDir())
.unzip();
}

private void postProcessZipFiles(MapUploadData mapUploadData) throws IOException {
Expand Down Expand Up @@ -161,7 +232,7 @@ private void checkLua(MapUploadData progressData) {
throw new ApiException(new Error(ErrorCode.MAP_NAME_TOO_LONG, MAP_DISPLAY_NAME_MAX_LENGTH, displayName.length()));
}
if (!NameUtil.isPrintableAsciiString(displayName)) {
throw new ApiException(new Error(ErrorCode.MAP_NAME_INVALID));
throw new ApiException(new Error(ErrorCode.MAP_NAME_INVALID_CHARACTER));
}

if (scenarioInfo.get(ScenarioMapInfo.DESCRIPTION) == LuaValue.NIL) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/faforever/api/map/MapValidationRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.faforever.api.map;

import lombok.Value;

@Value
public class MapValidationRequest {
String name;
String scenarioLua;
}
31 changes: 24 additions & 7 deletions src/main/java/com/faforever/api/map/MapsController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,51 @@
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.inject.Inject;
import java.io.IOException;
import java.util.Map;

import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;

@RestController
@RequestMapping(path = "/maps")
@Slf4j
@AllArgsConstructor
public class MapsController {
private final MapService mapService;
private final FafApiProperties fafApiProperties;
private final ObjectMapper objectMapper;
private final PlayerService playerService;

@Inject
public MapsController(MapService mapService, FafApiProperties fafApiProperties, ObjectMapper objectMapper, PlayerService playerService) {
this.mapService = mapService;
this.fafApiProperties = fafApiProperties;
this.objectMapper = objectMapper;
this.playerService = playerService;

@RequestMapping(path = "/validate", method = RequestMethod.GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ModelAndView showValidationForm(Map<String, Object> model) {
return new ModelAndView("validate_map_metadata.html");
}

@ApiOperation("Validate map metadata")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Success"),
@ApiResponse(code = 422, message = "Containing information about the errors in the payload")})
@RequestMapping(
path = "/validate",
method = RequestMethod.POST,
produces = APPLICATION_JSON_UTF8_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
public void validateMapMetadata(@RequestBody MapValidationRequest mapValidationRequest) {
mapService.validate(mapValidationRequest);
}

@ApiOperation("Upload a map")
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/config/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ spring:
password: ${ADMIN_CLIENT_PASSWORD:banana}
logging:
level:
com.faforever.api: trace
com.faforever.api: debug

4 changes: 1 addition & 3 deletions src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
<meta charset="UTF-8"/>
<title>Log-in</title>

<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<style type="text/css">
body {
center: fixed;
Expand Down
4 changes: 1 addition & 3 deletions src/main/resources/templates/oauth_confirm_access.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<head>
<meta charset="UTF-8"/>
<title>Authorizing</title>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz" rel="stylesheet" type="text/css"/>
<style type="text/css">
body {
Expand Down

0 comments on commit 6e92ec3

Please sign in to comment.