Uploading {{ fileToUpload.name }}...
-
-

-
File uploaded successfully!
-
+
+
+

+
+
+
File uploaded successfully!
+
+
+
+
+
File Name:
+
{{uploadData.fileName}}
+
+
Is password protected:
+
+
+
Is single download:
+
+
Download Password:
+
{{uploadData.password || 'N/A'}}
+
+
File ID:
+
{{uploadData.fileId}}
+
+
Download URL:
+
+
+
File Status URL:
+
+
+
+
+
+
+
+
+
+
Your file is now securely stored on our servers.
+
+
+
+
+
+
+
diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts
index f7818c7..fdc2c96 100644
--- a/frontend/src/app/upload/upload.component.ts
+++ b/frontend/src/app/upload/upload.component.ts
@@ -1,9 +1,10 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
-import {DecimalPipe, NgIf} from "@angular/common";
+import {DecimalPipe, NgClass, NgIf} from "@angular/common";
import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe";
import {FormsModule} from "@angular/forms";
import axios, {AxiosProgressEvent} from "axios";
import { DevelopmentStore } from '../../store/DevelopmentStore';
+import {RouterLink} from "@angular/router";
@Component({
selector: 'app-upload',
@@ -12,7 +13,9 @@ import { DevelopmentStore } from '../../store/DevelopmentStore';
NgIf,
FormatFileSizePipePipe,
FormsModule,
- DecimalPipe
+ DecimalPipe,
+ NgClass,
+ RouterLink
],
templateUrl: './upload.component.html',
styleUrl: './upload.component.scss'
@@ -26,7 +29,6 @@ export class UploadComponent {
singleDownload: boolean = false;
fileDescription: string = '';
passwordProtected: boolean = false;
- password: string = ''; // Generated by the server for only this file
uploadStarted: boolean = false;
uploadProgress = 0; // Real progress
@@ -34,6 +36,9 @@ export class UploadComponent {
uploadFinished = false;
uploadSpeedBps: number = 0;
+ uploadData: FileDetails | null = null;
+ fileUrls: { downloadUrl: string, statusUrl: string, deleteUrl: string } | null = null;
+ urlCopied: boolean = false;
constructor(private developmentStore: DevelopmentStore) {
@@ -97,6 +102,8 @@ export class UploadComponent {
.then(response => {
console.log('Upload completed successfully!');
console.log(response.data);
+ this.fileUrls = this.buildFileUrls(response.data);
+ this.uploadData = response.data;
this.uploadFinished = true;
})
.catch(error => {
@@ -105,13 +112,26 @@ export class UploadComponent {
}
}
+ buildFileUrls(fileDetails: FileDetails) {
+ const baseUrl = this.developmentStore.getBaseUrl();
+ const fileId = fileDetails.fileId;
+ const downloadUrl = `${baseUrl}download?fileId=${fileId}`;
+ const deleteUrl = `${baseUrl}api/v1/deletefile?fileId=${fileId}`;
+ const statusUrl = `${baseUrl}status?fileId=${fileId}`;
+ return {
+ downloadUrl,
+ statusUrl,
+ deleteUrl,
+ }; }
+
buildFormDataObject(): FormData {
const formData = new FormData();
formData.append('file', this.fileToUpload as Blob);
- formData.append('password', this.passwordProtected ? this.password : '');
+ formData.append('passwordProtected', this.passwordProtected ? 'true' : 'false');
formData.append('singleDownload', this.singleDownload.toString());
formData.append('shortStorage', this.shortStorage.toString());
formData.append('fileDescription', this.fileDescription);
+ console.log(formData.get("passwordProtected"));
return formData;
}
@@ -167,4 +187,24 @@ export class UploadComponent {
this.uploadProgress = this.targetUploadProgress;
}
}
+
+ copyUrlToClipboard(url: string | undefined) {
+ if(url) {
+ navigator.clipboard.writeText(url).then(() => {
+ this.urlCopied = true;
+ console.log('Text successfully copied to clipboard');
+ }).catch(err => {
+ console.error('Failed to copy text to clipboard', err);
+ });
+ }
+ }
+}
+interface FileDetails {
+ fileId: string;
+ fileName: string;
+ message: string;
+ passwordProtected: boolean;
+ singleDownload: boolean;
+ uploadDate: string;
+ password: string;
}
diff --git a/frontend/src/assets/check-solid.svg b/frontend/src/assets/check-solid.svg
new file mode 100644
index 0000000..10da547
--- /dev/null
+++ b/frontend/src/assets/check-solid.svg
@@ -0,0 +1 @@
+
diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java
index 3515dfb..803c3be 100644
--- a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java
+++ b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java
@@ -29,7 +29,7 @@ public class Upload extends ApiRestController {
// Currently testing
@PostMapping("/upload")
public ResponseEntity
getUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request,
- @RequestParam(value = "password", required = false) String password,
+ @RequestParam(value = "passwordProtected", required = false) Boolean passwordProtected,
@RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload,
@RequestParam(value = "fileDescription", required = false) String fileDescription) {
@@ -40,15 +40,17 @@ public class Upload extends ApiRestController {
return new ResponseEntity<>("Please select a file to upload.", HttpStatus.NOT_ACCEPTABLE);
}
- FileUpload fileUpload = fileService.processUploadedFile(file, request.getRemoteAddr(), password, singleDownload, fileDescription);
+ FileUpload fileUpload = fileService.processUploadedFile(file, request.getRemoteAddr(), passwordProtected, singleDownload, fileDescription);
log.debug("User uploaded file " + file.getOriginalFilename() + " from IP " + request.getRemoteAddr() + " successfully.");
JsonObject response = new JsonObject();
response.addProperty("fileId", fileUpload.getFileId());
+ response.addProperty("fileName", fileUpload.getFileName());
response.addProperty("message", "File " + file.getOriginalFilename() + " uploaded successfully!");
response.addProperty("passwordProtected", fileUpload.isPasswordProtected());
response.addProperty("singleDownload", fileUpload.isSingleDownload());
response.addProperty("uploadDate", fileUpload.getUploadDate().toString());
+ response.addProperty("password", fileUpload.getDownloadPassword());
return new ResponseEntity<>(response.toString(), HttpStatus.OK);
}
diff --git a/src/main/java/de/w665/sharepulse/service/FileSecurityService.java b/src/main/java/de/w665/sharepulse/service/FileSecurityService.java
index f55903e..4d57dd2 100644
--- a/src/main/java/de/w665/sharepulse/service/FileSecurityService.java
+++ b/src/main/java/de/w665/sharepulse/service/FileSecurityService.java
@@ -3,14 +3,20 @@ package de.w665.sharepulse.service;
import de.w665.sharepulse.exception.NoDownloadPermissionException;
import de.w665.sharepulse.model.FileUpload;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
-
-import java.io.File;
+import java.util.Random;
@Slf4j
@Service
public class FileSecurityService {
+ @Value("${sharepulse.filepassword-length}")
+ private int passwordLength;
+ @Value("${sharepulse.filepassword-charset}")
+ private String passwordCharset;
+ private final Random random = new Random();
+
public boolean verifyDownloadPermission(FileUpload file, String password) throws NoDownloadPermissionException {
@@ -34,4 +40,16 @@ public class FileSecurityService {
throw new NoDownloadPermissionException("Password protected file can only be downloaded with correct password.");
}
+
+ public String generateFilePassword() {
+ StringBuilder password = new StringBuilder(passwordLength);
+
+ for (int i = 0; i < passwordLength; i++) {
+ int randomIndex = random.nextInt(passwordCharset.length());
+ char randomChar = passwordCharset.charAt(randomIndex);
+ password.append(randomChar);
+ }
+
+ return password.toString();
+ }
}
diff --git a/src/main/java/de/w665/sharepulse/service/FileService.java b/src/main/java/de/w665/sharepulse/service/FileService.java
index 044a629..57207e2 100644
--- a/src/main/java/de/w665/sharepulse/service/FileService.java
+++ b/src/main/java/de/w665/sharepulse/service/FileService.java
@@ -23,15 +23,16 @@ import java.util.Date;
public class FileService {
private final FileIdService fileIdService;
-
+ private final FileSecurityService fileSecurityService;
private final FileUploadRepository fileUploadRepository;
@Value("${sharepulse.temp-filestore-path}")
private String tempDirPath;
@Autowired
- public FileService(FileIdService fileIdService, FileUploadRepository fileUploadRepository) {
+ public FileService(FileIdService fileIdService, FileSecurityService fileSecurityService, FileUploadRepository fileUploadRepository) {
this.fileIdService = fileIdService;
+ this.fileSecurityService = fileSecurityService;
this.fileUploadRepository = fileUploadRepository;
}
@@ -53,10 +54,15 @@ public class FileService {
}
}
- public FileUpload processUploadedFile(MultipartFile file, String uploaderIp, String password, boolean singleDownload, String fileDescription) {
+ public FileUpload processUploadedFile(MultipartFile file, String uploaderIp, boolean passwordProtected, boolean singleDownload, String fileDescription) {
String fileId = fileIdService.generateNewUniqueId();
+ String password = "";
+ if (passwordProtected) {
+ password = fileSecurityService.generateFilePassword();
+ }
+
FileUpload fileUpload = FileUpload.builder()
.fileId(fileId)
.fileName(file.getOriginalFilename())
@@ -66,7 +72,7 @@ public class FileService {
.uploadedByIpAddress(uploaderIp)
.downloadCount(0)
.fileDescription(fileDescription)
- .passwordProtected(password != null && !password.isEmpty())
+ .passwordProtected(passwordProtected)
.downloadPassword(password)
.build();
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ce36a92..99e3d04 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,5 +1,7 @@
# Application config
sharepulse.temp-filestore-path=/temp-filestore
+sharepulse.filepassword-length=6
+sharepulse.filepassword-charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
# Static path
spring.web.resources.static-locations=classpath:/static/browser/
@@ -11,9 +13,6 @@ spring.data.rest.base-path=/api/v1
spring.servlet.multipart.max-file-size=1GB
spring.servlet.multipart.max-request-size=1GB
-# Logging
-logging.level.de.w665.sharepulse=DEBUG
-
# Database
rethinkdb.host=localhost
rethinkdb.port=28015
@@ -24,4 +23,7 @@ server.port=80
spring.application.name=sharepulse
# Spring profiles (Options: development, production) (Controls cors)
-spring.profiles.active=development
\ No newline at end of file
+spring.profiles.active=development
+
+# Logging
+logging.level.de.w665.sharepulse=DEBUG
\ No newline at end of file