Compare commits

...

16 Commits

Author SHA1 Message Date
Lee
3f4c542265 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.10.0' (#10) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m48s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
Reviewed-on: #10
2024-06-09 20:07:26 +00:00
3d4dae9515 fix xml?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m10s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-08 01:53:22 +01:00
88d47ef535 fix xml?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-07 22:39:46 +01:00
da32c3bdfa remove the file extension of a paste internally so we only find by the real id
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m17s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
2024-06-07 22:23:51 +01:00
9778a49c64 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.10.0 2024-06-06 14:01:17 +00:00
79e0e5651a add readme 2024-06-05 12:03:10 +01:00
a7e6488e72 fix ignore content type being missing on /api/upload
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m58s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
2024-06-05 11:58:47 +01:00
9c74f41022 move tailwind to be local
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-04 19:35:36 +01:00
98711948b6 fix the fix
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-04 19:31:06 +01:00
f60d5f2114 maybe order matters?
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m14s
2024-06-04 19:22:56 +01:00
c8e91ba949 add hastebin compatibility
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m45s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m14s
2024-06-04 19:16:10 +01:00
78af91e971 use a toast instead of alerting
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 59s
2024-06-02 14:24:05 +01:00
af219e69bb cleanup
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m3s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m13s
2024-06-02 13:01:46 +01:00
f3360b6041 check content type
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-02 12:40:31 +01:00
1e2503ef44 whoops, remove debug
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m2s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 55s
2024-06-02 10:44:06 +01:00
ae718e97f5 fix imports 2024-06-02 10:41:06 +01:00
11 changed files with 195 additions and 56 deletions

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# Paste
A simple pastebin service. Running at [paste.fascinated.cc](https://paste.fascinated.cc).
## Javascript Utility
```js
/**
* Uploads a paste to paste.fascinated.cc
*
* @param content the content of the paste
* @returns the paste key and the URL
*/
async function uploadPaste(content: string) {
const response = await fetch("https://paste.fascinated.cc/api/upload", {
method: "POST",
body: content,
});
const json = await response.json();
if (!response.ok) {
throw new Error(json.message);
}
return {
...json,
url: `https://paste.fascinated.cc/${json.key}`,
};
}
console.log(await uploadPaste("Hello, World!"));
```

View File

@ -87,7 +87,7 @@
<dependency> <dependency>
<groupId>io.sentry</groupId> <groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId> <artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.9.0</version> <version>7.10.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>

View File

@ -63,7 +63,6 @@ public class FileHeaderChecker {
* @return true if the string contains a known file header, false otherwise * @return true if the string contains a known file header, false otherwise
*/ */
public static boolean containsFileHeader(String input) { public static boolean containsFileHeader(String input) {
System.out.println("Checking for file headers in: " + input);
byte[] byteArray = stringToByteArray(input); byte[] byteArray = stringToByteArray(input);
for (byte[] header : FILE_HEADERS.values()) { for (byte[] header : FILE_HEADERS.values()) {

View File

@ -4,10 +4,18 @@ import lombok.NonNull;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration @EnableWebMvc
public class Config { public class Config implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
@Bean @Bean
public WebMvcConfigurer configureCors() { public WebMvcConfigurer configureCors() {

View File

@ -4,11 +4,15 @@ import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
import cc.fascinated.backend.model.Paste; import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService; import cc.fascinated.backend.service.PasteService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import java.awt.datatransfer.MimeTypeParseException;
import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
@ -29,13 +33,20 @@ public class IndexController {
return "index"; return "index";
} }
@GetMapping(value = "/{id}") /**
* This is to allow for Hastebin compatibility.
*/
@PostMapping(value = "/documents")
public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
String id = pasteService.createPaste(content, contentType);
return ResponseEntity.ok(Map.of("key", id));
}
@GetMapping(value = "/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
public String paste(@PathVariable String id, Model model) { public String paste(@PathVariable String id, Model model) {
try { try {
Paste paste = pasteService.getPaste(id); Paste paste = pasteService.getPaste(id);
if (paste == null) { // If the paste does not exist, redirect to the home page
return "redirect:/";
}
model.addAttribute("paste", paste); model.addAttribute("paste", paste);
model.addAttribute("title", "Paste - " + paste.getId()); model.addAttribute("title", "Paste - " + paste.getId());
model.addAttribute("rawUrl", "/raw/" + paste.getId()); model.addAttribute("rawUrl", "/raw/" + paste.getId());
@ -45,7 +56,7 @@ public class IndexController {
} }
} }
@GetMapping(value = "/raw/{id}") @GetMapping(value = "/raw/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
public String pasteRaw(@PathVariable String id, Model model) { public String pasteRaw(@PathVariable String id, Model model) {
try { try {
Paste paste = pasteService.getPaste(id); Paste paste = pasteService.getPaste(id);

View File

@ -3,7 +3,7 @@ package cc.fascinated.backend.controller;
import cc.fascinated.backend.model.Paste; import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService; import cc.fascinated.backend.service.PasteService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -21,9 +21,9 @@ public class PasteController {
} }
@PostMapping(value = "/upload") @PostMapping(value = "/upload")
public ResponseEntity<?> uploadPaste(@RequestBody String content) { public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
String id = pasteService.createPaste(content); String id = pasteService.createPaste(content, contentType);
return ResponseEntity.ok(Map.of("id", id)); return ResponseEntity.ok(Map.of("key", id));
} }
@GetMapping(value = "/paste/{id}") @GetMapping(value = "/paste/{id}")

View File

@ -44,7 +44,7 @@ public class PasteService {
* @param content The content of the paste. * @param content The content of the paste.
* @return The id of the paste. * @return The id of the paste.
*/ */
public String createPaste(String content) { public String createPaste(String content, String contentType) {
int length = content.length(); int length = content.length();
long before = System.currentTimeMillis(); long before = System.currentTimeMillis();
log.info("Creating a new paste. (characters: {})", length); log.info("Creating a new paste. (characters: {})", length);
@ -52,12 +52,19 @@ public class PasteService {
// Check if the content is too large. // Check if the content is too large.
if (length > uploadSizeLimit && uploadSizeLimit != -1) { if (length > uploadSizeLimit && uploadSizeLimit != -1) {
log.info("Paste didn't meet the size requirements. (characters: {})", length); log.info("Paste didn't meet the size requirements. (characters: {})", length);
throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters"); throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters, not uploading...");
}
// Ensure the paste content type is valid.
if (contentType != null && (contentType.contains("image") || contentType.contains("video") || contentType.contains("audio"))) {
log.info("Paste content type is not supported. (content type: {})", contentType);
throw new BadRequestException("The paste content type is not supported, not uploading...");
} }
// Ensure the paste content does not contain a file header. // Ensure the paste content does not contain a file header.
if (FileHeaderChecker.containsFileHeader(content)) { if (FileHeaderChecker.containsFileHeader(content)) {
throw new BadRequestException("The paste content contains a file header"); log.info("Paste content contains a file header, not uploading...");
throw new BadRequestException("The paste content contains a file header, not uploading...");
} }
// Save the paste to the database. // Save the paste to the database.
@ -77,6 +84,7 @@ public class PasteService {
* @return The content of the paste. * @return The content of the paste.
*/ */
public Paste getPaste(String id) { public Paste getPaste(String id) {
id = id.contains(".") ? id.split("\\.")[0] : id; // Remove file extensions (if any)
log.info("Getting paste with the id '{}'", id); log.info("Getting paste with the id '{}'", id);
long before = System.currentTimeMillis(); long before = System.currentTimeMillis();
Optional<Paste> paste = pasteRepository.findById(id); Optional<Paste> paste = pasteRepository.findById(id);

View File

@ -0,0 +1,49 @@
// Handle custom key binds behavior
document.addEventListener("keydown", function (event) {
// Upload the paste when Ctrl + Enter is pressed
if (event.ctrlKey && event.key === "Enter") {
event.preventDefault();
upload();
}
});
// Upload the paste when the paste button is clicked
document.getElementById("paste-button").addEventListener("click", () => upload());
// Upload the paste to the server
const upload = async () => {
var pasteInput = document.getElementById("paste-input");
var paste = pasteInput.value;
if (!paste || paste.trim() === "") {
pasteInput.focus();
toast("Please enter a paste to upload.");
return;
}
console.log("Uploading paste...");
try {
const response = await fetch("/api/upload", {
method: "POST",
body: paste,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
window.location.href = "/" + data.key;
} catch (error) {
console.error("Error:", error);
toast(`${error.message || "An error occurred while uploading the paste."}`);
}
};
const toast = (message, duration = 3000) => {
Toastify({
text: message,
duration: duration,
gravity: "bottom",
position: "right"
}).showToast();
};

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,11 @@
<title>Paste</title> <title>Paste</title>
<!-- TailwindCSS --> <!-- TailwindCSS -->
<script src="https://cdn.tailwindcss.com"></script> <script src="/static/tailwindcss.js"></script>
<!-- Toastify -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
</head> </head>
<body class="bg-neutral-900 text-white"> <body class="bg-neutral-900 text-white">
<div class="p-3 h-screen w-screen relative"> <div class="p-3 h-screen w-screen relative">
@ -29,39 +33,5 @@
</body> </body>
<!-- Paste Script --> <!-- Paste Script -->
<script> <script src="/static/assets/script.js"></script>
// Handle custom key binds behavior
document.addEventListener('keydown', function(event) {
// Upload the paste when Ctrl + Enter is pressed
if (event.ctrlKey && event.key === 'Enter') {
event.preventDefault();
upload();
}
});
// Upload the paste when the paste button is clicked
document.getElementById('paste-button').addEventListener('click', () => upload());
// Upload the paste to the server
const upload = () => {
var pasteInput = document.getElementById('paste-input');
var paste = pasteInput.value;
if (!paste || paste.trim() === ''){
return;
}
fetch('/api/upload', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: paste
}).then(function(response) {
return response.json();
}).then(function(data) {
window.location.href = '/' + data.id;
});
};
</script>
</html> </html>

View File

@ -12,7 +12,7 @@
<meta content="Click to view the Paste." property="og:description" /> <meta content="Click to view the Paste." property="og:description" />
<!-- TailwindCSS --> <!-- TailwindCSS -->
<script src="https://cdn.tailwindcss.com"></script> <script src="/static/tailwindcss.js"></script>
<!-- Highlight.js --> <!-- Highlight.js -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">