Settings and checks for maximum artifact size
Different archive backends may not cope well with large file sizes. Additionally, Fling admins may want to restrict the maximum file size to protect their server.
This commit is contained in:
parent
d4b7f1db30
commit
e424c9ef09
13 changed files with 69 additions and 12 deletions
|
@ -16,7 +16,6 @@ import net.friedl.fling.model.json.PathSerializer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class FlingConfiguration {
|
public class FlingConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -32,6 +33,8 @@ import net.friedl.fling.service.archive.ArchiveService;
|
||||||
@SecurityRequirement(name = "bearer")
|
@SecurityRequirement(name = "bearer")
|
||||||
@Validated
|
@Validated
|
||||||
public class ArtifactController {
|
public class ArtifactController {
|
||||||
|
@Value("${fling.max-artifact-size:-1}")
|
||||||
|
private Long maxArtifactSize;
|
||||||
|
|
||||||
private ArtifactService artifactService;
|
private ArtifactService artifactService;
|
||||||
private ArchiveService archiveService;
|
private ArchiveService archiveService;
|
||||||
|
@ -58,6 +61,10 @@ public class ArtifactController {
|
||||||
@PostMapping(path = "/{id}/data")
|
@PostMapping(path = "/{id}/data")
|
||||||
public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request)
|
public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
if(maxArtifactSize >= 0 && maxArtifactSize < request.getContentLengthLong()) {
|
||||||
|
throw new IOException("Maximum artifact size exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
archiveService.storeArtifact(id, request.getInputStream());
|
archiveService.storeArtifact(id, request.getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,12 @@
|
||||||
"type": "java.util.Long",
|
"type": "java.util.Long",
|
||||||
"description": "Time until JWT tokens expire",
|
"description": "Time until JWT tokens expire",
|
||||||
"sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration"
|
"sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fling.max-artifact-size",
|
||||||
|
"type": "java.util.Long",
|
||||||
|
"description": "Maximum artifact size in bytes. -1 to disable.",
|
||||||
|
"sourceType": "net.friedl.fling.controller.ArtifactController"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ spring:
|
||||||
servlet:
|
servlet:
|
||||||
multipart.max-file-size: -1
|
multipart.max-file-size: -1
|
||||||
multipart.max-request-size: -1
|
multipart.max-request-size: -1
|
||||||
|
|
||||||
logging.level:
|
logging.level:
|
||||||
root: WARN
|
root: WARN
|
||||||
net.friedl: TRACE
|
net.friedl: TRACE
|
||||||
|
@ -21,6 +21,7 @@ logging.level:
|
||||||
# spring.http.log-request-details: true
|
# spring.http.log-request-details: true
|
||||||
|
|
||||||
fling:
|
fling:
|
||||||
|
max-artifact-size: 209715200 # 200 MB
|
||||||
archive.filesystem.archive-path: /home/armin/Desktop/fling
|
archive.filesystem.archive-path: /home/armin/Desktop/fling
|
||||||
security:
|
security:
|
||||||
allowed-origins:
|
allowed-origins:
|
||||||
|
|
|
@ -16,6 +16,7 @@ logging.level:
|
||||||
root: WARN
|
root: WARN
|
||||||
|
|
||||||
fling:
|
fling:
|
||||||
|
max-artifact-size: 209715200 # 200 MB
|
||||||
archive.filesystem.archive-path: "/var/fling/files"
|
archive.filesystem.archive-path: "/var/fling/files"
|
||||||
security:
|
security:
|
||||||
allowed-origins:
|
allowed-origins:
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
REACT_APP_API=https://fling.friedl.net
|
REACT_APP_API=https://fling.friedl.net
|
||||||
REACT_APP_LOGLEVEL=warn
|
REACT_APP_LOGLEVEL=warn
|
||||||
|
REACT_APP_FILESIZE=209715200
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
REACT_APP_API=http://localhost:8080/
|
REACT_APP_API=http://localhost:8080/
|
||||||
REACT_APP_LOGLEVEL=trace
|
REACT_APP_LOGLEVEL=trace
|
||||||
|
REACT_APP_FILESIZE=209715200
|
||||||
|
|
||||||
|
|
5
web/fling/package-lock.json
generated
5
web/fling/package-lock.json
generated
|
@ -13376,6 +13376,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
},
|
},
|
||||||
|
"vanillatoasts": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vanillatoasts/-/vanillatoasts-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-DVMPPWVVt/x1B2f4iVpCw3rYDceu1PGRV8ECc3OfUC8By6O14OdXWjKryfaz8UorBuNe8btDIsujp6YP6gDNGA=="
|
||||||
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"spectre.css": "^0.5.8"
|
"spectre.css": "^0.5.8",
|
||||||
|
"vanillatoasts": "^1.4.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
@ -100,7 +100,9 @@ export default function FlingArtifacts() {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
log.debug(`Got ${result.length} artifacts`);
|
log.debug(`Got ${result.length} artifacts`);
|
||||||
for (let artifact of result) {
|
for (let artifact of result) {
|
||||||
artifacts.push(<FlingArtifactRow key={artifact.id} artifact={artifact} reloadArtifactsFn={getArtifacts} />);
|
if(artifact.archived) {
|
||||||
|
artifacts.push(<FlingArtifactRow key={artifact.id} artifact={artifact} reloadArtifactsFn={getArtifacts} />);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setArtifacts(artifacts);
|
setArtifacts(artifacts);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
import VanillaToasts from 'vanillatoasts';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
@ -81,9 +82,21 @@ export default function Upload() {
|
||||||
stopEvent(ev);
|
stopEvent(ev);
|
||||||
ev.persist();
|
ev.persist();
|
||||||
|
|
||||||
let evFiles = ev.dataTransfer.files;
|
let maxSize = process.env.REACT_APP_FILESIZE;
|
||||||
|
let evFiles = fileListToArray(ev.dataTransfer.files);
|
||||||
|
|
||||||
if (!evFiles) {
|
for (let i = evFiles.length - 1; i >= 0; i--) {
|
||||||
|
if (maxSize && maxSize >= 0 && evFiles[i].size > maxSize) {
|
||||||
|
VanillaToasts.create({
|
||||||
|
title: "Maximum file size exceeded",
|
||||||
|
text: `${evFiles[i].name} exceeds the maximum file size of ${prettifyBytes(maxSize)}`,
|
||||||
|
type: "warning"
|
||||||
|
});
|
||||||
|
evFiles.splice(i, 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evFiles.lenght === 0) {
|
||||||
console.warn("Dropzone triggered without files");
|
console.warn("Dropzone triggered without files");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -198,6 +211,7 @@ export default function Upload() {
|
||||||
</div>
|
</div>
|
||||||
<div className="upload-command-line m-2">
|
<div className="upload-command-line m-2">
|
||||||
<span className="total-upload">Total Size: {totalSize()}</span>
|
<span className="total-upload">Total Size: {totalSize()}</span>
|
||||||
|
<span className="total-upload">{`Max: ${prettifyBytes(process.env.REACT_APP_FILESIZE)}`}</span>
|
||||||
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
import VanillaToasts from 'vanillatoasts';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { Switch, Route, useLocation, Link } from "react-router-dom";
|
import { Switch, Route, useLocation, Link } from "react-router-dom";
|
||||||
|
@ -17,7 +18,10 @@ function Artifacts(props) {
|
||||||
|
|
||||||
let flingClient = new FlingClient();
|
let flingClient = new FlingClient();
|
||||||
flingClient.getArtifacts(props.fling.id)
|
flingClient.getArtifacts(props.fling.id)
|
||||||
.then(artifacts => setArtifacts(artifacts));
|
.then(artifacts => {
|
||||||
|
artifacts = artifacts.filter(a => a.archived)
|
||||||
|
setArtifacts(artifacts)
|
||||||
|
});
|
||||||
}, [props.fling]);
|
}, [props.fling]);
|
||||||
|
|
||||||
function renderArtifact(artifact) {
|
function renderArtifact(artifact) {
|
||||||
|
@ -119,9 +123,21 @@ function Upload(props) {
|
||||||
stopEvent(ev);
|
stopEvent(ev);
|
||||||
ev.persist();
|
ev.persist();
|
||||||
|
|
||||||
let evFiles = ev.dataTransfer.files;
|
let maxSize = process.env.REACT_APP_FILESIZE;
|
||||||
|
let evFiles = fileListToArray(ev.dataTransfer.files);
|
||||||
|
|
||||||
if (!evFiles) {
|
for (let i = evFiles.length - 1; i >= 0; i--) {
|
||||||
|
if (maxSize && maxSize >= 0 && evFiles[i].size > maxSize) {
|
||||||
|
VanillaToasts.create({
|
||||||
|
title: "Maximum file size exceeded",
|
||||||
|
text: `${evFiles[i].name} exceeds the maximum file size of ${prettifyBytes(maxSize)}`,
|
||||||
|
type: "warning"
|
||||||
|
});
|
||||||
|
evFiles.splice(i, 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evFiles.length === 0) {
|
||||||
console.warn("Dropzone triggered without files");
|
console.warn("Dropzone triggered without files");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -236,6 +252,7 @@ function Upload(props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="upload-command-line m-2">
|
<div className="upload-command-line m-2">
|
||||||
<span className="total-upload">Total Size: {totalSize()}</span>
|
<span className="total-upload">Total Size: {totalSize()}</span>
|
||||||
|
<span className="total-upload">{`Max: ${prettifyBytes(process.env.REACT_APP_FILESIZE)}`}</span>
|
||||||
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -303,10 +320,10 @@ export default function FlingUserList(props) {
|
||||||
<li className={`tab-item ${location.pathname !== path("upload") ? "active" : ""}`}>
|
<li className={`tab-item ${location.pathname !== path("upload") ? "active" : ""}`}>
|
||||||
<Link to={path("files")}>Files</Link>
|
<Link to={path("files")}>Files</Link>
|
||||||
</li>
|
</li>
|
||||||
{ props.fling.allowUpload
|
{props.fling.allowUpload
|
||||||
? <li className={`tab-item ${location.pathname === path("upload") ? "active" : ""}`}>
|
? <li className={`tab-item ${location.pathname === path("upload") ? "active" : ""}`}>
|
||||||
<Link to={path("upload")}>Upload</Link>
|
<Link to={path("upload")}>Upload</Link>
|
||||||
</li>
|
</li>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
|
import 'vanillatoasts/vanillatoasts.css';
|
||||||
import "./style/fling.scss";
|
import "./style/fling.scss";
|
||||||
|
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
Loading…
Reference in a new issue