0.9.2: Fix thumbnail folder generation, better chatterino config generation and better error hadling
All checks were successful
File-uploader-crystal CI / build (push) Successful in 2m15s

This commit is contained in:
Fijxu 2024-10-21 13:54:51 -03:00
parent 4803700cab
commit b51513339c
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
7 changed files with 86 additions and 26 deletions

View file

@ -152,13 +152,13 @@ module Handling::Admin
# /api/admin/whitelist # /api/admin/whitelist
# curl -X GET -H "X-Api-Key: asd" http://localhost:8080/api/admin/torexitnodes | jq # curl -X GET -H "X-Api-Key: asd" http://localhost:8080/api/admin/torexitnodes | jq
# def add_ip_to_whitelist(env, nodes) # def add_ip_to_whitelist(env, nodes)
# json = JSON.build do |j| # json = JSON.build do |j|
# j.object do # j.object do
# j.field "ips", nodes # j.field "ips", nodes
# end # end
# end # end
# end # end
# /api/admin/blacklist # /api/admin/blacklist
# curl -X GET -H "X-Api-Key: asd" http://localhost:8080/api/admin/torexitnodes | jq # curl -X GET -H "X-Api-Key: asd" http://localhost:8080/api/admin/torexitnodes | jq

View file

@ -1,6 +1,7 @@
require "../http-errors" require "../http-errors"
require "http/client" require "http/client"
require "benchmark" require "benchmark"
# require "../filters"
module Handling module Handling
extend self extend self
@ -10,6 +11,7 @@ module Handling
ip_address = Utils.ip_address(env) ip_address = Utils.ip_address(env)
protocol = Utils.protocol(env) protocol = Utils.protocol(env)
host = Utils.host(env) host = Utils.host(env)
# filter = env.params.query["filter"]?
# You can modify this if you want to allow files smaller than 1MiB. # You can modify this if you want to allow files smaller than 1MiB.
# This is generally a good way to check the filesize but there is a better way to do it # This is generally a good way to check the filesize but there is a better way to do it
# which is inspecting the file directly (If I'm not wrong). # which is inspecting the file directly (If I'm not wrong).
@ -46,6 +48,10 @@ module Handling
original_filename = upload.filename original_filename = upload.filename
uploaded_at = Time.utc uploaded_at = Time.utc
checksum = Utils.hash_file(file_path) checksum = Utils.hash_file(file_path)
# TODO: Apply filters
# if filter
# Filters.apply_filter(file_path, filter)
# end
end end
# X-Forwarded-For if behind a reverse proxy and the header is set in the reverse # X-Forwarded-For if behind a reverse proxy and the header is set in the reverse
# proxy configuration. # proxy configuration.
@ -88,13 +94,19 @@ module Handling
ip_address = Utils.ip_address(env) ip_address = Utils.ip_address(env)
protocol = Utils.protocol(env) protocol = Utils.protocol(env)
host = Utils.host(env) host = Utils.host(env)
files = env.params.json["files"].as((Array(JSON::Any))) begin
files = env.params.json["files"].as((Array(JSON::Any)))
rescue ex : JSON::ParseException
LOGGER.error "Body malformed: #{ex.message}"
return error400 "Body malformed: #{ex.message}"
rescue ex
LOGGER.error "Unknown error: #{ex.message}"
return error500 "Unknown error"
end
successfull_files = [] of NamedTuple(filename: String, extension: String, original_filename: String, checksum: String, delete_key: String | Nil) successfull_files = [] of NamedTuple(filename: String, extension: String, original_filename: String, checksum: String, delete_key: String | Nil)
failed_files = [] of String failed_files = [] of String
# X-Forwarded-For if behind a reverse proxy and the header is set in the reverse # X-Forwarded-For if behind a reverse proxy and the header is set in the reverse
# proxy configuration. # proxy configuration.
if files.empty?
end
files.each do |url| files.each do |url|
url = url.to_s url = url.to_s
filename = Utils.generate_filename filename = Utils.generate_filename
@ -103,7 +115,9 @@ module Handling
checksum = "" checksum = ""
uploaded_at = Time.utc uploaded_at = Time.utc
extension = File.extname(URI.parse(url).path) extension = File.extname(URI.parse(url).path)
delete_key = nil if CONFIG.deleteKeyLength > 0
delete_key = Random.base58(CONFIG.deleteKeyLength)
end
file_path = ::File.join ["#{CONFIG.files}", filename + extension] file_path = ::File.join ["#{CONFIG.files}", filename + extension]
File.open(file_path, "w") do |output| File.open(file_path, "w") do |output|
begin begin
@ -168,7 +182,6 @@ module Handling
json json
end end
# TODO: Add delete url, same for upload_url_bulk
def upload_url(env) def upload_url(env)
env.response.content_type = "application/json" env.response.content_type = "application/json"
ip_address = Utils.ip_address(env) ip_address = Utils.ip_address(env)
@ -179,31 +192,29 @@ module Handling
failed_files = [] of String failed_files = [] of String
# X-Forwarded-For if behind a reverse proxy and the header is set in the reverse # X-Forwarded-For if behind a reverse proxy and the header is set in the reverse
# proxy configuration. # proxy configuration.
if url.empty?
end
# files.each do |url|
url = url.to_s
filename = Utils.generate_filename filename = Utils.generate_filename
original_filename = "" original_filename = ""
extension = "" extension = ""
checksum = "" checksum = ""
uploaded_at = Time.utc uploaded_at = Time.utc
extension = File.extname(URI.parse(url).path) extension = File.extname(URI.parse(url).path)
delete_key = nil if CONFIG.deleteKeyLength > 0
delete_key = Random.base58(CONFIG.deleteKeyLength)
end
file_path = ::File.join ["#{CONFIG.files}", filename + extension] file_path = ::File.join ["#{CONFIG.files}", filename + extension]
File.open(file_path, "w") do |output| File.open(file_path, "w") do |output|
begin begin
# TODO: Connect timeout to prevent possible Denial of Service to the external website spamming requests
# https://crystal-lang.org/api/1.13.2/HTTP/Client.html#connect_timeout
HTTP::Client.get(url) do |res| HTTP::Client.get(url) do |res|
IO.copy(res.body_io, output) IO.copy(res.body_io, output)
end end
rescue ex rescue ex
LOGGER.debug "Failed to download file '#{url}': #{ex.message}" LOGGER.debug "Failed to download file '#{url}': #{ex.message}"
return error403("Failed to download file '#{url}'") return error403("Failed to download file '#{url}': #{ex.message}")
failed_files << url failed_files << url
end end
end end
# successfull_files << url
# end
if extension.empty? if extension.empty?
extension = Utils.detect_extension(file_path) extension = Utils.detect_extension(file_path)
File.rename(file_path, file_path + extension) File.rename(file_path, file_path + extension)
@ -231,7 +242,6 @@ module Handling
LOGGER.error "An error ocurred when trying to insert the data into the DB: #{ex.message}" LOGGER.error "An error ocurred when trying to insert the data into the DB: #{ex.message}"
return error500("An error ocurred when trying to insert the data into the DB") return error500("An error ocurred when trying to insert the data into the DB")
end end
# end
json = JSON.build do |j| json = JSON.build do |j|
j.array do j.array do
successfull_files.each do |fileinfo| successfull_files.each do |fileinfo|
@ -383,4 +393,16 @@ module Handling
"ErrorMessage": "{json:error}" "ErrorMessage": "{json:error}"
}) })
end end
def chatterino_config(env)
host = Utils.host(env)
protocol = Utils.protocol(env)
env.response.content_type = "application/json"
return %({
"requestUrl": "#{protocol}://#{host}/upload",
"formField": "data",
"imageLink": "{link}",
"deleteLink": "{deleteLink}"
})
end
end end

View file

@ -1,3 +1,10 @@
macro error400(message)
env.response.content_type = "application/json"
env.response.status_code = 400
error_message = {"error" => {{message}}}.to_json
error_message
end
macro error401(message) macro error401(message)
env.response.content_type = "application/json" env.response.content_type = "application/json"
env.response.status_code = 401 env.response.status_code = 401

View file

@ -50,6 +50,13 @@ module Routing
render "src/views/index.ecr" render "src/views/index.ecr"
end end
get "/chatterino" do |env|
host = Utils.host(env)
protocol = Utils.protocol(env)
files_hosted = SQL.query_one "SELECT COUNT (filename) FROM #{CONFIG.dbTableName}", as: Int32
render "src/views/chatterino.ecr"
end
post "/upload" do |env| post "/upload" do |env|
Handling.upload(env) Handling.upload(env)
end end
@ -82,6 +89,10 @@ module Routing
Handling.sharex_config(env) Handling.sharex_config(env)
end end
get "/chatterinoconfig" do |env|
Handling.chatterino_config(env)
end
if CONFIG.adminEnabled if CONFIG.adminEnabled
self.register_admin self.register_admin
end end

View file

@ -30,7 +30,7 @@ module Utils
end end
def create_thumbnails_dir def create_thumbnails_dir
if !CONFIG.thumbnails if CONFIG.thumbnails
if !Dir.exists?("#{CONFIG.thumbnails}") if !Dir.exists?("#{CONFIG.thumbnails}")
LOGGER.info "Creating thumbnails folder under '#{CONFIG.thumbnails}'" LOGGER.info "Creating thumbnails folder under '#{CONFIG.thumbnails}'"
begin begin
@ -69,7 +69,7 @@ module Utils
dependencies.each do |dep| dependencies.each do |dep|
next if !CONFIG.generateThumbnails next if !CONFIG.generateThumbnails
if !Process.find_executable(dep) if !Process.find_executable(dep)
LOGGER.fatal("'#{dep}' was not found") LOGGER.fatal("'#{dep}' was not found.")
exit(1) exit(1)
end end
end end
@ -113,7 +113,7 @@ module Utils
def generate_thumbnail(filename, extension) def generate_thumbnail(filename, extension)
# Disable generation if false # Disable generation if false
return if !CONFIG.generateThumbnails return if !CONFIG.generateThumbnails || !CONFIG.thumbnails
LOGGER.debug "Generating thumbnail for #{filename + extension} in background" LOGGER.debug "Generating thumbnail for #{filename + extension} in background"
process = Process.run("ffmpeg", process = Process.run("ffmpeg",
[ [
@ -128,7 +128,7 @@ module Utils
"-update", "1", "-update", "1",
"#{CONFIG.thumbnails}/#{filename}.jpg", "#{CONFIG.thumbnails}/#{filename}.jpg",
]) ])
if process.normal_exit? if process.exit_code == 0
LOGGER.debug "Thumbnail for #{filename + extension} generated successfully" LOGGER.debug "Thumbnail for #{filename + extension} generated successfully"
SQL.exec "UPDATE #{CONFIG.dbTableName} SET thumbnail = ? WHERE filename = ?", filename + ".jpg", filename SQL.exec "UPDATE #{CONFIG.dbTableName} SET thumbnail = ? WHERE filename = ?", filename + ".jpg", filename
else else

20
src/views/chatterino.ecr Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> <%= host %> </title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="./favicon.gif" type="image/gif" />
<script src="script.js"></script>
</head>
<body>
<div class="container">
<h1 style="font-size: 68px; text-align: center; margin: 20px;">Chatterino config</h1>
<p>Request URL: <a style="color: #2cca00"><%= protocol %>://<%= host %>/upload</a></p>
<p>Form field: <a style="color: #2cca00">data</a></p>
<p>Image link: <a style="color: #2cca00">link</a></p>
<p>Delete link: <a style="color: #2cca00">deleteLink</a></p>
</div>
</body>
</html>

View file

@ -22,7 +22,7 @@
<div> <div>
<div style="text-align:center;"> <div style="text-align:center;">
<p> <p>
<a href='./chatterino.png'>Chatterino Config</a> | <a href='./chatterino'>Chatterino Config</a> |
<a href='./sharex.sxcu'>ShareX Config</a> | <a href='./sharex.sxcu'>ShareX Config</a> |
<a href='https://codeberg.org/Fijxu/file-uploader-crystal'> <a href='https://codeberg.org/Fijxu/file-uploader-crystal'>
file-uploader-crystal (BETA <%= CURRENT_TAG %> - <%= CURRENT_VERSION %> @ <%= CURRENT_BRANCH %>) file-uploader-crystal (BETA <%= CURRENT_TAG %> - <%= CURRENT_VERSION %> @ <%= CURRENT_BRANCH %>)