Compare commits

..

57 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
2c1cccf25a block file some common headers
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 1m3s
2024-06-02 10:40:46 +01:00
288c127520 make the background less dark
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m11s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 56s
2024-06-02 10:25:06 +01:00
93d491445a add html compression
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m59s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-02 10:20:10 +01:00
2f008730f9 check if the paste contents is empty
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 1m0s
2024-06-01 22:07:17 +01:00
5b95ce2e33 add Ctrl + Enter keybind to upload paste
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 59s
2024-06-01 22:04:55 +01:00
6cd275994e fix selecting the >
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 59s
2024-06-01 22:01:19 +01:00
f046d9cc68 add time taken to paste api and fixed text size on the creation page
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 1m4s
2024-06-01 21:55:58 +01:00
0e585ed1cd redirect on invalid paste
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m19s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
2024-06-01 21:21:55 +01:00
b2c034df5f Merge remote-tracking branch 'origin/master'
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 57s
2024-06-01 18:49:58 +01:00
c2c8b348e6 add embeds 2024-06-01 18:49:44 +01:00
Lee
09fdb97b6f Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.0' (#8) from renovate/spring-boot into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m47s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 57s
Reviewed-on: #8
2024-06-01 17:34:56 +00:00
Lee
acc129e9da Merge pull request 'Update maven Docker tag to v3.9.7' (#9) from renovate/maven-3.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #9
2024-06-01 17:34:52 +00:00
Lee
50f370cde2 Merge pull request 'Update s4u/setup-maven-action action to v1.13.0' (#7) from renovate/s4u-setup-maven-action-1.x into master
Some checks failed
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
Reviewed-on: #7
2024-06-01 17:34:40 +00:00
Lee
0888cb1ac1 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.9.0' (#6) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #6
2024-06-01 17:34:31 +00:00
Lee
fe13824d04 Merge pull request 'Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.13.0' (#5) from renovate/de.flapdoodle.embed-de.flapdoodle.embed.mongo.spring3x-4.x into master
Some checks are pending
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Reviewed-on: #5
2024-06-01 17:34:20 +00:00
1ab282772e re-enable the cache and make the buttons smaller
Some checks failed
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Waiting to run
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
2024-06-01 18:33:40 +01:00
779460689a fix text size for the code block
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m0s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-01 18:27:58 +01:00
4a2d3d4fb7 custom select all only for the code
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-01 18:25:46 +01:00
3c4f5089ef fix test
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m19s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 58s
2024-06-01 18:18:41 +01:00
4c4fcc884d move to backend as the frontend (easier to manage and deploy)
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 1m5s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 28s
2024-06-01 18:16:54 +01:00
d6a6b45ec7 Update maven Docker tag to v3.9.7 2024-05-28 21:01:17 +00:00
7e678be372 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.0 2024-05-23 14:01:20 +00:00
64f33e1c3c Update s4u/setup-maven-action action to v1.13.0 2024-05-17 19:01:14 +00:00
93192d6b5f Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.9.0 2024-05-08 14:01:13 +00:00
0150be17e7 Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.13.0 2024-04-30 22:01:07 +00:00
f2783d14da add Sentry
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m53s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m7s
2024-04-27 08:45:57 +01:00
5332370c32 Merge remote-tracking branch 'origin/master' 2024-04-24 21:28:12 +01:00
5c97e33500 add license 2024-04-24 21:28:01 +01:00
Lee
c26b1b38cd Merge pull request 'Update eclipse-temurin Docker tag to v17.0.11_9-jre-focal' (#4) from renovate/eclipse-temurin-17.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m12s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m4s
Reviewed-on: #4
2024-04-24 20:18:19 +00:00
77b58dd330 whoopsie
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m33s
Publish Docker Image / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m1s
2024-04-24 20:10:32 +01:00
821284463e run tests when building the dockerhub image
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m26s
Publish Docker Image / docker (ubuntu-latest) (push) Failing after 17s
2024-04-24 20:06:14 +01:00
76c5b48237 Update eclipse-temurin Docker tag to v17.0.11_9-jre-focal 2024-04-24 19:01:30 +00:00
0fb69e8d13 fix tests
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m8s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 47s
2024-04-24 19:55:44 +01:00
4c95983c47 fix uploading non string values
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 28s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 36s
2024-04-24 19:06:00 +01:00
38848349fd add logging
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m43s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 35s
2024-04-24 17:17:28 +01:00
6ae8929d91 cleanup
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m17s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 54s
2024-04-23 21:18:15 +01:00
2ca9b6ce5f allow -1 for the paste limit to disable it 2024-04-23 21:18:02 +01:00
5a0286412f Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m7s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 36s
2024-04-23 21:16:48 +01:00
7d1f81c3a2 impl a char limit for pastes 2024-04-23 21:16:42 +01:00
Lee
075877748e Merge pull request 'Update eclipse-temurin Docker tag to v17.0.10_7-jre-focal' (#2) from renovate/eclipse-temurin-17.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m33s
Publish Docker Image / docker (ubuntu-latest) (push) Successful in 43s
Reviewed-on: #2
2024-04-23 20:01:36 +00:00
5ba0c5d034 Update eclipse-temurin Docker tag to v17.0.10_7-jre-focal 2024-04-23 16:00:43 +00:00
22 changed files with 662 additions and 29 deletions

View File

@ -27,7 +27,7 @@ jobs:
# Setup Java and Maven
- name: Set up JDK and Maven
uses: s4u/setup-maven-action@v1.12.0
uses: s4u/setup-maven-action@v1.13.0
with:
java-version: ${{ matrix.java-version }}
distribution: "zulu"

View File

@ -13,6 +13,9 @@ jobs:
strategy:
matrix:
arch: [ "ubuntu-latest" ]
git-version: [ "2.44.0" ]
java-version: [ "17" ]
maven-version: [ "3.8.5" ]
runs-on: ${{ matrix.arch }}
# Steps to run
@ -20,8 +23,18 @@ jobs:
# Checkout the repo
- name: Checkout
uses: actions/checkout@v4
# Setup Java and Maven
- name: Set up JDK and Maven
uses: s4u/setup-maven-action@v1.13.0
with:
fetch-depth: 0
java-version: ${{ matrix.java-version }}
distribution: "zulu"
maven-version: ${{ matrix.maven-version }}
# Run JUnit Tests
- name: Run Tests
run: mvn --batch-mode test -q
# Login to Docker Hub
- name: Login to Repo

View File

@ -1,5 +1,5 @@
# Stage 1: Build the application
FROM maven:3.9.6-eclipse-temurin-17-alpine AS builder
FROM maven:3.9.7-eclipse-temurin-17-alpine AS builder
# Set the working directory
WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# Stage 2: Create the final lightweight image
FROM eclipse-temurin:17-jre-focal
FROM eclipse-temurin:17.0.11_9-jre-focal
# Set the working directory
WORKDIR /home/container

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Liam (Fascinated)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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!"));
```

22
pom.xml
View File

@ -17,7 +17,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -52,6 +52,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.3.0</version>
</dependency>
<!-- MongoDB for data storage -->
<dependency>
@ -72,6 +77,19 @@
<version>3.14.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.googlecode.htmlcompressor</groupId>
<artifactId>htmlcompressor</artifactId>
<version>1.5.2</version>
</dependency>
<!-- Sentry -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.10.0</version>
<scope>compile</scope>
</dependency>
<!-- Tests -->
<dependency>
@ -82,7 +100,7 @@
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
<version>4.12.6</version>
<version>4.13.0</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,76 @@
package cc.fascinated.backend.common;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
public class FileHeaderChecker {
// Define known file headers
private static final Map<String, byte[]> FILE_HEADERS = new HashMap<>();
static {
FILE_HEADERS.put("PNG", new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
FILE_HEADERS.put("JPEG", new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
FILE_HEADERS.put("GIF", new byte[]{0x47, 0x49, 0x46, 0x38});
FILE_HEADERS.put("PDF", new byte[]{0x25, 0x50, 0x44, 0x46});
FILE_HEADERS.put("ZIP", new byte[]{0x50, 0x4B, 0x03, 0x04});
FILE_HEADERS.put("RAR", new byte[]{0x52, 0x61, 0x72, 0x21});
FILE_HEADERS.put("7Z", new byte[]{0x37, 0x7A, (byte) 0xBC, (byte) 0xAF, (byte) 0x27, 0x1C});
FILE_HEADERS.put("TAR", new byte[]{0x75, 0x73, 0x74, 0x61, 0x72});
FILE_HEADERS.put("BZ2", new byte[]{0x42, 0x5A, 0x68});
FILE_HEADERS.put("GZ", new byte[]{0x1F, (byte) 0x8B});
FILE_HEADERS.put("EXE", new byte[]{0x4D, 0x5A});
}
/**
* Converts a string to a byte array using UTF-8 encoding.
*
* @param input the string to convert
* @return the byte array representation of the string
*/
private static byte[] stringToByteArray(String input) {
return input.getBytes(StandardCharsets.UTF_8);
}
/**
* Checks if the byte array starts with the specified header.
*
* @param byteArray the byte array to check
* @param header the header to look for
* @return true if the byte array starts with the header, false otherwise
*/
private static boolean startsWith(byte[] byteArray, byte[] header) {
if (byteArray.length < header.length) {
return false;
}
for (int i = 0; i < header.length; i++) {
if (byteArray[i] != header[i]) {
return false;
}
}
return true;
}
/**
* Checks if the given string contains a known file header.
*
* @param input the string to check
* @return true if the string contains a known file header, false otherwise
*/
public static boolean containsFileHeader(String input) {
byte[] byteArray = stringToByteArray(input);
for (byte[] header : FILE_HEADERS.values()) {
if (startsWith(byteArray, header)) {
return true;
}
}
return false;
}
}

View File

@ -4,10 +4,18 @@ import lombok.NonNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
@Configuration
public class Config {
@Configuration @EnableWebMvc
public class Config implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
@Bean
public WebMvcConfigurer configureCors() {

View File

@ -0,0 +1,40 @@
package cc.fascinated.backend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
/**
* @author Fascinated (fascinated7)
*/
@Configuration
@EnableWebMvc
public class ThymeleafConfiguration {
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
SpringResourceTemplateResolver templateResolver
= new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
return templateResolver;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
}

View File

@ -0,0 +1,70 @@
package cc.fascinated.backend.controller;
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService;
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.ui.Model;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.*;
import java.awt.datatransfer.MimeTypeParseException;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Controller
@RequestMapping(value = "/")
public class IndexController {
private final PasteService pasteService;
@Autowired
public IndexController(PasteService pasteService) {
this.pasteService = pasteService;
}
@GetMapping(value = "/")
public String home() {
return "index";
}
/**
* 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) {
try {
Paste paste = pasteService.getPaste(id);
model.addAttribute("paste", paste);
model.addAttribute("title", "Paste - " + paste.getId());
model.addAttribute("rawUrl", "/raw/" + paste.getId());
return "paste";
} catch (ResourceNotFoundException ex) {
return "redirect:/";
}
}
@GetMapping(value = "/raw/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
public String pasteRaw(@PathVariable String id, Model model) {
try {
Paste paste = pasteService.getPaste(id);
model.addAttribute("paste", paste);
model.addAttribute("title", "Paste - " + paste.getId() + " (Raw)");
return "paste-raw";
} catch (ResourceNotFoundException ex) {
return "redirect:/";
}
}
}

View File

@ -3,13 +3,14 @@ package cc.fascinated.backend.controller;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.service.PasteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping(value = "/")
@RequestMapping(value = "/api")
public class PasteController {
private final PasteService pasteService;
@ -19,20 +20,13 @@ public class PasteController {
this.pasteService = pasteService;
}
@GetMapping(value = "/")
public ResponseEntity<?> home() {
return ResponseEntity.ok(Map.of(
"status", "OK"
));
}
@PostMapping(value = "/upload")
public ResponseEntity<?> uploadPaste(@RequestBody String content) {
String id = pasteService.createPaste(content);
return ResponseEntity.ok(Map.of("id", id));
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}")
@GetMapping(value = "/paste/{id}")
public ResponseEntity<Paste> getPaste(@PathVariable String id) {
Paste paste = pasteService.getPaste(id);
return ResponseEntity.ok(paste);

View File

@ -2,6 +2,7 @@ package cc.fascinated.backend.exception;
import cc.fascinated.backend.model.response.ErrorResponse;
import io.micrometer.common.lang.NonNull;
import io.sentry.Sentry;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -39,6 +40,7 @@ public final class ExceptionControllerAdvice {
}
if (status == null) { // Fallback to 500
status = HttpStatus.INTERNAL_SERVER_ERROR;
Sentry.captureException(ex); // Capture the exception
}
return new ResponseEntity<>(new ErrorResponse(status, message), status);
}

View File

@ -0,0 +1,9 @@
package cc.fascinated.backend.exception.impl;
import lombok.experimental.StandardException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@StandardException
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException { }

View File

@ -0,0 +1,98 @@
package cc.fascinated.backend.filter;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.springframework.stereotype.Component;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Filter to compress HTML responses using HtmlCompressor.
* Author: Fascinated (fascinated7)
*/
@Component
public class HtmlCompressionFilter implements Filter {
private final HtmlCompressor htmlCompressor = new HtmlCompressor();
/**
* Applies the HTML compression filter.
*
* @param request the ServletRequest
* @param response the ServletResponse
* @param chain the FilterChain
* @throws ServletException if an error occurs during filtering
* @throws IOException if an input or output error occurs
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
ServletResponse newResponse = response;
// Wrap the response to capture the output
if (request instanceof HttpServletRequest) {
newResponse = new CharResponseWrapper((HttpServletResponse) response);
}
// Pass the request and response along the filter chain
chain.doFilter(request, newResponse);
// Compress the captured response if it was wrapped
if (newResponse instanceof CharResponseWrapper) {
String responseContent = newResponse.toString();
if (responseContent != null) {
response.getWriter().write(htmlCompressor.compress(responseContent));
}
}
}
/**
* Wrapper for HttpServletResponse to capture the output for compression.
*/
private static class CharResponseWrapper extends HttpServletResponseWrapper {
private final CharArrayWriter charWriter = new CharArrayWriter();
private PrintWriter writer;
private boolean getOutputStreamCalled;
private boolean getWriterCalled;
/**
* Constructor to wrap the response.
* @param response the original HttpServletResponse
*/
public CharResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (getWriterCalled) {
throw new IllegalStateException("getWriter already called");
}
getOutputStreamCalled = true;
return super.getOutputStream();
}
@Override
public PrintWriter getWriter() {
if (writer != null) {
return writer;
}
if (getOutputStreamCalled) {
throw new IllegalStateException("getOutputStream already called");
}
getWriterCalled = true;
writer = new PrintWriter(charWriter);
return writer;
}
@Override
public String toString() {
return (writer != null) ? charWriter.toString() : null;
}
}
}

View File

@ -1,8 +1,11 @@
package cc.fascinated.backend.service;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.common.FileHeaderChecker;
import cc.fascinated.backend.exception.impl.BadRequestException;
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
import cc.fascinated.backend.model.Paste;
import cc.fascinated.backend.repository.PasteRepository;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -10,7 +13,7 @@ import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@Service @Log4j2(topic = "Paste Service")
public class PasteService {
/**
@ -24,6 +27,12 @@ public class PasteService {
@Value("${paste.id-length}")
private int idLength;
/**
* The maximum size of the paste content.
*/
@Value("${paste.upload-size-limit}")
private int uploadSizeLimit;
@Autowired
public PasteService(PasteRepository pasteRepository) {
this.pasteRepository = pasteRepository;
@ -35,12 +44,37 @@ public class PasteService {
* @param content The content of the paste.
* @return The id of the paste.
*/
public String createPaste(String content) {
return pasteRepository.save(new Paste(
public String createPaste(String content, String contentType) {
int length = content.length();
long before = System.currentTimeMillis();
log.info("Creating a new paste. (characters: {})", length);
// Check if the content is too large.
if (length > uploadSizeLimit && uploadSizeLimit != -1) {
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, 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.
if (FileHeaderChecker.containsFileHeader(content)) {
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.
Paste paste = pasteRepository.save(new Paste(
RandomStringUtils.randomAlphabetic(idLength),
System.currentTimeMillis(),
content
)).getId();
));
log.info("Created a new paste with the id '{}' (took: {}ms)", paste.getId(), System.currentTimeMillis() - before);
return paste.getId();
}
/**
@ -50,12 +84,17 @@ public class PasteService {
* @return The content of the paste.
*/
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);
long before = System.currentTimeMillis();
Optional<Paste> paste = pasteRepository.findById(id);
// The paste does not exist.
if (paste.isEmpty()) {
throw new ResourceNotFoundException("Paste with id '%s' not found".formatted(id));
log.info("Paste with the id '{}' not found", id);
throw new ResourceNotFoundException("Paste '%s' not found".formatted(id));
}
log.info("Got paste with the id '{}' (took: {}ms)", id, System.currentTimeMillis() - before);
return paste.get();
}
}

View File

@ -19,10 +19,17 @@ spring:
database: test
port: 27017
# Sentry Configuration
sentry:
dsn: ""
tracesSampleRate: 1.0
# Paste Configuration
paste:
# The length of the ID for the paste
id-length: 12
# The limit of the paste size (in characters) - You can set it to "-1" to disable the limit
upload-size-limit: 400000
# Set the embedded MongoDB version
de:

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

@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Paste</title>
<!-- TailwindCSS -->
<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>
<body class="bg-neutral-900 text-white">
<div class="p-3 h-screen w-screen relative">
<div class="flex gap-2 h-full w-full text-sm">
<p class="hidden md:block select-none pointer-events-none">&gt;</p>
<textarea
id="paste-input"
class="w-full h-full bg-background outline-none resize-none bg-transparent"
placeholder="Paste your code here..."></textarea>
</div>
<div class="absolute top-0 right-0 p-3">
<button id="paste-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
Paste
</button>
</div>
</div>
</body>
<!-- Paste Script -->
<script src="/static/assets/script.js"></script>
</html>

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title th:text="${title}"></title>
</head>
<body>
<pre><code th:text="${paste.content}"></code></pre>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title th:text="${title}"></title>
<!-- Meta tags for social media sharing -->
<meta th:content="${title}" property="og:title" />
<meta content="Click to view the Paste." property="og:description" />
<!-- TailwindCSS -->
<script src="/static/tailwindcss.js"></script>
<!-- Highlight.js -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
</head>
<body class="bg-neutral-900 text-white h-screen relative">
<pre><code class="bg-transparent text-sm" th:text="${paste.content}"></code></pre>
<div class="absolute top-0 right-0 p-3">
<button id="raw-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
<a th:href="${rawUrl}">
Raw
</a>
</button>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">
<a href="/">
New
</a>
</button>
</div>
</body>
<script>
// Highlight the code block
hljs.highlightAll();
// Custom select all behavior to select all the content inside the <pre> block
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.key === 'a') {
event.preventDefault(); // Prevent the default select all behavior
// Select all the content inside the <pre> block
const preBlock = document.querySelector('pre');
const range = document.createRange();
range.selectNodeContents(preBlock);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
});
</script>
</html>

View File

@ -20,9 +20,9 @@ class PasteControllerTests {
@Test
public void ensureUploadSuccess() throws Exception {
ResultActions result = mockMvc.perform(post("/upload")
ResultActions result = mockMvc.perform(post("/api/upload")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.contentType(MediaType.TEXT_PLAIN)
.content("joe"))
.andExpect(status().isOk());
}