1 package net.sumaris.server.http.rest;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.base.Joiner;
27 import com.google.common.base.Preconditions;
28 import net.sumaris.core.util.Files;
29 import net.sumaris.core.util.StringUtils;
30 import net.sumaris.server.config.SumarisServerConfiguration;
31 import net.sumaris.server.http.MediaTypes;
32 import org.apache.commons.io.FileUtils;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.springframework.beans.factory.annotation.Autowired;
36 import org.springframework.core.io.InputStreamResource;
37 import org.springframework.http.HttpHeaders;
38 import org.springframework.http.HttpStatus;
39 import org.springframework.http.MediaType;
40 import org.springframework.http.ResponseEntity;
41 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
42 import org.springframework.security.core.Authentication;
43 import org.springframework.security.core.context.SecurityContextHolder;
44 import org.springframework.security.core.userdetails.UserDetails;
45 import org.springframework.stereotype.Controller;
46 import org.springframework.web.bind.annotation.PathVariable;
47 import org.springframework.web.bind.annotation.RequestMapping;
48 import org.springframework.web.bind.annotation.RequestParam;
49
50 import javax.servlet.ServletContext;
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.IOException;
54
55 @Controller
56 public class DownloadController {
57
58
59 private static final Logger log = LoggerFactory.getLogger(DownloadController.class);
60
61 @Autowired
62 private ServletContext servletContext;
63
64 @Autowired
65 private SumarisServerConfiguration configuration;
66
67 @RequestMapping({RestPaths.DOWNLOAD_PATH + "/{username}/{filename}", RestPaths.DOWNLOAD_PATH + "/{filename}"})
68 public ResponseEntity<InputStreamResource> downloadFileAsPath(
69 @PathVariable(name="username", required = false) String username,
70 @PathVariable(name="filename") String filename
71 ) throws IOException {
72 if (StringUtils.isNotBlank(username)) {
73 return doDownloadFile(username + "/" + filename);
74 }
75 else {
76 return doDownloadFile(filename);
77 }
78 }
79
80 @RequestMapping(RestPaths.DOWNLOAD_PATH)
81 public ResponseEntity<InputStreamResource> downloadFileAsQuery(
82 @RequestParam(name = "username", required = false) String username,
83 @RequestParam(name = "filename") String filename
84 ) throws IOException{
85 if (StringUtils.isNotBlank(username)) {
86 return doDownloadFile(username + "/" + filename);
87 }
88 else {
89 return doDownloadFile(filename);
90 }
91 }
92
93 public String registerFile(File sourceFile, boolean moveSourceFile) throws IOException {
94
95 String username = getAuthenticatedUsername();
96
97
98 String userPath = asSecuredPath(username);
99 if (!username.equals(userPath)) {
100 throw new AuthenticationCredentialsNotFoundException("Bad authentication token");
101 }
102
103 File userDirectory = new File(configuration.getDownloadDirectory(), userPath);
104 FileUtils.forceMkdir(userDirectory);
105 File targetFile = new File(userDirectory, sourceFile.getName());
106
107 if (targetFile.exists()) {
108 int counter = 1;
109 String baseName = Files.getNameWithoutExtension(sourceFile);
110 String extension = Files.getExtension(sourceFile);
111 do {
112 targetFile = new File(userDirectory, String.format("%s-%s.%s",
113 baseName,
114 counter++,
115 extension));
116 } while (targetFile.exists());
117 }
118
119 if (moveSourceFile) {
120 FileUtils.moveFile(sourceFile, targetFile);
121 }
122 else {
123 FileUtils.copyFile(sourceFile, targetFile);
124 }
125
126 return Joiner.on('/').join(
127 configuration.getServerUrl() + RestPaths.DOWNLOAD_PATH,
128 userPath,
129 targetFile.getName());
130 }
131
132
133
134 protected ResponseEntity<InputStreamResource> doDownloadFile(String filename) throws IOException {
135 if (StringUtils.isBlank(filename)) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
136
137
138 String securedFilename = asSecuredPath(filename);
139 if (!filename.equals(securedFilename)) {
140 log.warn(String.format("Reject download request: invalid path {%s}", filename));
141 return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
142 }
143
144 MediaType mediaType = MediaTypes.getMediaTypeForFileName(this.servletContext, filename);
145
146 File file = new File(configuration.getDownloadDirectory(), filename);
147 if (!file.exists()) {
148 log.warn(String.format("Reject download request: file {%s} not found, or invalid path", filename));
149 return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
150 }
151 if (!file.canRead()) {
152 log.warn(String.format("Reject download request: file {%s} not readable", filename));
153 return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
154 }
155
156 log.debug(String.format("Download request to file {%s} of type {%s}", filename, mediaType));
157 InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
158
159 return ResponseEntity.ok()
160
161 .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
162
163 .contentType(mediaType)
164
165 .contentLength(file.length())
166 .body(resource);
167 }
168
169 protected String getAuthenticatedUsername() {
170 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
171 if (auth != null) {
172 Object principal = auth.getPrincipal();
173 if (principal instanceof UserDetails) {
174 return ((UserDetails) principal).getUsername();
175 }
176 }
177 return null;
178 }
179
180 protected String asSecuredPath(String path) {
181 Preconditions.checkNotNull(path);
182 Preconditions.checkArgument(path.trim().length() > 0);
183
184 return path != null ? path.trim().replaceAll("[.][.]/?", "") : null;
185 }
186 }