Skip to content

Commit

Permalink
added redirect and public file groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Andcool-Systems committed May 14, 2024
1 parent 118bf05 commit 4cc8031
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 96 deletions.
3 changes: 2 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
default = "application/x-msdownload"

accessLifeTime = 432_000
accessLifeTimeBot = 15_552_000
accessLifeTimeBot = 15_552_000
pattern = r'^https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
148 changes: 79 additions & 69 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"""

from fastapi import FastAPI, UploadFile, Request, Header
from fastapi.responses import JSONResponse, FileResponse, Response
from fastapi.responses import JSONResponse, FileResponse, Response, RedirectResponse
from typing import Annotated, Union
import uvicorn
from config import filetypes, default, accessLifeTime, accessLifeTimeBot
from config import filetypes, default, accessLifeTime, accessLifeTimeBot, pattern
import aiohttp
import utils
from slowapi.errors import RateLimitExceeded
Expand All @@ -24,6 +24,7 @@
import bcrypt
import random
import json
import re


def custom_key_func(request: Request):
Expand Down Expand Up @@ -193,28 +194,34 @@ async def upload_file(
else:
group_id = -1

file_data = file.file.read()
try:
is_url = re.match(pattern, file_data.decode("utf-8"))
except Exception:
is_url = False

key = str(uuid.uuid4()) # Generate unique delete key
ext = ("." + file.filename.split(".")[-1].lower()) if file.filename.find(".") != -1 else "" # Get file extension
fid = utils.generate_token(10) + (ext if include_ext else "") # Generate file url
fn = str(uuid.uuid4()) + ext # Generate file name

async with aiofiles.open(f"uploads/{fn}", "wb") as f: # Save file locally
await f.write(file.file.read())
if not is_url:
async with aiofiles.open(f"uploads/{fn}", "wb") as f: # Save file locally
await f.write(file_data)

now = datetime.now()
filetype = filetypes.get(ext[1:], default) if ext and ext.lower()[1:] in filetypes else "download"
created = await db.file.create(
{ # Creating a file record
"user_id": user_id,
"group_id": int(group_id),
"created_date": f"{now.day}.{now.month}.{now.year} {now.hour}:{now.minute}:{now.second}",
"url": fid,
"filename": f"uploads/{fn}",
"filename": f"uploads/{fn}" if not is_url else file_data.decode("utf-8"),
"craeted_at": time.time(),
"last_watched": time.time(),
"key": key,
"type": (
filetypes.get(ext[1:], default) if ext and ext.lower()[1:] in filetypes else "download"
),
"type": filetype if not is_url else "redirect",
"ext": ext,
"size": file.size,
"user_filename": file.filename,
Expand Down Expand Up @@ -249,11 +256,15 @@ async def upload_file(
@limiter.limit(dynamic_limit_provider)
async def send_file(url: str, request: Request):
result = await db.file.find_first(where={"url": url}) # Get file by url
if not result or not os.path.isfile(result.filename):

if not result or (not os.path.isfile(result.filename) and result.type != "redirect"):
async with aiofiles.open("404.html", mode="rb") as f:
return Response(
await f.read(), media_type="text/html", status_code=404
) # if file does'n exists

if result.type == "redirect":
return RedirectResponse(result.filename, status_code=301)

print(request.headers.get("CF-IPCountry"))

Expand All @@ -276,16 +287,16 @@ async def send_file(url: str, request: Request):
await delete_file(result.url, result.key)
return JSONResponse(content="File not found!", status_code=404)

if result.type != "download": # If File extension recognized
async with aiofiles.open(result.filename, mode="rb") as f:
return Response(
await f.read(), media_type=result.type
) # Send file with "Content-type" header
else: # If file extension doesn't recognized
if result.type == "download": # If File extension doesn't recognized
return FileResponse(
path=result.filename, filename=result.user_filename, media_type=result.type
) # Send file as FileResponse

async with aiofiles.open(result.filename, mode="rb") as f:
return Response(
await f.read(), media_type=result.type
) # Send file with "Content-type" header


@app.get("/api/delete/{url}") # File delete handler
async def delete_file(url: str, key: str = ""):
Expand All @@ -294,31 +305,34 @@ async def delete_file(url: str, key: str = ""):
return JSONResponse(
content={"status": "error", "message": "File not found"}, status_code=404
) # if file does'n exists
try:
if result.key == key: # If provided key matched with key from database record
await db.file.delete(
where={"id": result.id}
) # Delete file record from database

async with aiohttp.ClientSession(
"https://api.cloudflare.com"
) as session: # Clear file cache from CloudFlare
async with session.post(
f"/client/v4/zones/{os.getenv('ZONE_ID')}/purge_cache",
json={"files": ["https://fu.andcool.ru/file/" + result.url]},
headers={"Authorization": "Bearer " + os.getenv("KEY")}):
pass
os.remove(result.filename) # Delete file

if result.key == key: # If provided key matched with key from database record
os.remove(result.filename) # Delete file
await db.file.delete(
where={"id": result.id}
) # Delete file record from database

async with aiohttp.ClientSession(
"https://api.cloudflare.com"
) as session: # Clear file cache from CloudFlare
async with session.post(
f"/client/v4/zones/{os.getenv('ZONE_ID')}/purge_cache",
json={"files": ["https://fu.andcool.ru/file/" + result.url]},
headers={"Authorization": "Bearer " + os.getenv("KEY")},
):
pass

return JSONResponse(
content={"status": "success", "message": "deleted"}, status_code=200
)
else: # If provided key doesn't matched with key from database record
return JSONResponse(
content={"status": "error", "message": "invalid unique key"},
status_code=400,
)
except Exception:
return JSONResponse(
content={"status": "success", "message": "deleted"}, status_code=200
)
else: # If provided key doesn't matched with key from database record
return JSONResponse(
content={"status": "error", "message": "invalid unique key"},
status_code=400,
)


@app.get("/api/getFiles/{group_id}") # get files handler
Expand All @@ -330,59 +344,55 @@ async def getFiles(
Authorization: Annotated[Union[str, None], Header(convert_underscores=False)] = None,
user_agent: Union[str, None] = Header(default=None)
):
token_db, auth_error = await check_token(Authorization, user_agent) # Check token validity
if not token_db: # If token is not valid
return JSONResponse(
content={
"status": "error",
"message": "Auth error",
"auth_error": auth_error,
},
status_code=401,
)

user = await db.user.find_first(
where={"id": token_db.user_id}
) # Get user files from db

username = None
if group_id != "private":
token_db, auth_error = await check_token(Authorization, user_agent) # Check token validity
if token_db:
user = await db.user.find_first(where={"id": token_db.user_id})
username = user.username

if group_id == "private":
files = await db.file.find_many(
where={"user_id": user.id, "group_id": -1}
) # Get all user files from db
else:
if not group_id.isnumeric():
return JSONResponse(
content={"status": "error", "message": "Invalid group id"},
status_code=400,
)

group = await db.group.find_first(
where={"group_id": group_id}, include={"members": True}
)
group = await db.group.find_first(where={"group_id": group_id}, include={"members": True})
if not group:
return JSONResponse(
content={"status": "error", "message": "Group not found"},
status_code=404,
)
if user not in group.members:

files = await db.file.find_many(where={"group_id": group_id})

else:
token_db, auth_error = await check_token(Authorization, user_agent) # Check token validity
if not token_db: # If token is not valid
return JSONResponse(
{"status": "error", "message": "You are not in the group"},
status_code=400,
content={
"status": "error",
"message": "Auth error",
"auth_error": auth_error,
},
status_code=401,
)

files = await db.file.find_many(where={"group_id": group_id})
user = await db.user.find_first(where={"id": token_db.user_id}) # Get user files from db
files = await db.file.find_many(where={"user_id": user.id, "group_id": -1}) # Get all user files from db
username = user.username


files_response = []
for file in files:
user_filename = file.user_filename[:50] + (
"..." if len(file.user_filename) > 50 else ""
)
usr = (await db.user.find_first(where={"id": file.user_id})).username if group_id != "private" else None
user_filename = file.user_filename[:50] + ("..." if len(file.user_filename) > 50 else "")
usr = (await db.user.find_first(where={"id": file.user_id})).username if username else None
files_response.append(
{
"file_url": file.url,
"file_url_full": "https://fu.andcool.ru/file/" + file.url,
"key": file.key,
"key": file.key if username else None,
"ext": file.ext,
"user_filename": user_filename,
"creation_date": file.created_date,
Expand All @@ -396,9 +406,9 @@ async def getFiles(
content={
"status": "success",
"message": "messages got successfully",
"username": user.username,
"username": username,
"is_group_owner": (
None if group_id == "private" else group.admin_id == token_db.user_id
None if group_id == "private" or not username else group.admin_id == token_db.user_id
),
"data": files_response,
},
Expand Down
6 changes: 3 additions & 3 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<main>
<h1>File uploader</h1>
<nav>
<a class="selected">Home</a>
<a href="https://fu.andcool.ru/" class="selected">Home</a>
<a href="https://fu.andcool.ru/login/" id="login_page_a">Login</a>
<a href="https://fu.andcool.ru/tos/" id="login_page_a">ToS</a>
<a id="login_page_a" href="https://fu.andcool.ru/uploaders/">Uploaders</a>
Expand All @@ -73,14 +73,14 @@ <h1>File uploader</h1>
</div>
</div>
<form method="post" enctype="multipart/form-data">
<label class="input-file">
<label class="input-file" id="label_input">
<input type="file" name="file" id="input_file">
<span>Choose file</span>
</label>
</form>

<div id="groups_selector" style="display: none;">
<p style="margin-bottom: 5px; margin-left: 0;">Select file group:</p>
<p style="margin-bottom: 5px; margin-left: 0;" id="select_group">Select file group:</p>

<div style="display: flex; align-items: center; flex-direction: row; justify-content: flex-start;">
<select id="groups">
Expand Down
7 changes: 7 additions & 0 deletions web/res/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4cc8031

Please sign in to comment.