Skip to content

Commit

Permalink
feat: add Long press to download file instead of show content in (#2)
Browse files Browse the repository at this point in the history
- add 500 error page
- long press `Download` button to Save file into disk
- allow drag-drop file to entire webpage
  • Loading branch information
datlt4 authored Mar 9, 2024
1 parent 23ecd7f commit 71750ed
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 47 deletions.
115 changes: 75 additions & 40 deletions fhost.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
and limitations under the License.
"""

from flask import Flask, abort, make_response, redirect, request, send_from_directory, url_for, jsonify, Response, render_template
from flask import Flask, abort, make_response, redirect, request, send_from_directory, url_for, jsonify, Response, render_template, send_file
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from sqlalchemy import and_, or_
Expand All @@ -31,6 +31,7 @@
import click
import re
import os
import io
import sys
import time
import datetime
Expand All @@ -43,6 +44,7 @@
from pathlib import Path
from slugify import slugify
from flask_cors import CORS
from inspect import currentframe, getframeinfo

HTTP_URL_PATTERN = re.compile(r"(http[s]*://[\w.:]+)/?.*")

Expand Down Expand Up @@ -463,56 +465,60 @@ def manage_file(f):
@app.route("/<path:path>", methods=["GET", "POST"])
@app.route("/s/<secret>/<path:path>", methods=["GET", "POST"])
def get(path, secret=None):
p = Path(path.split("/", 1)[0])
sufs = "".join(p.suffixes[-2:])
name = p.name[:-len(sufs) or None]
try:
p = Path(path.split("/", 1)[0])
sufs = "".join(p.suffixes[-2:])
name = p.name[:-len(sufs) or None]

if "." in name:
abort(404)
if "." in name:
abort(404)

id = su.debase(name)
id = su.debase(name)

if sufs:
f = File.query.get(id)
if sufs:
f = File.query.get(id)

if f and f.ext == sufs:
if f.secret != secret:
abort(404)
if f and f.ext == sufs:
if f.secret != secret:
abort(404)

if f.removed:
abort(451)
if f.removed:
abort(451)

fpath = f.getpath()
fpath = f.getpath()

if not fpath.is_file():
abort(404)
if not fpath.is_file():
abort(404)

if request.method == "POST":
return manage_file(f)
if request.method == "POST":
return manage_file(f)

if app.config["FHOST_USE_X_ACCEL_REDIRECT"]:
response = make_response()
response.headers["Content-Type"] = f.mime
response.headers["Content-Length"] = f.size
response.headers["X-Accel-Redirect"] = "/" + str(fpath)
else:
response = send_from_directory(app.config["FHOST_STORAGE_PATH"], f.sha256, mimetype = f.mime, download_name=path)
if app.config["FHOST_USE_X_ACCEL_REDIRECT"]:
response = make_response()
response.headers["Content-Type"] = f.mime
response.headers["Content-Length"] = f.size
response.headers["X-Accel-Redirect"] = "/" + str(fpath)
else:
response = send_from_directory(app.config["FHOST_STORAGE_PATH"], f.sha256, mimetype = f.mime, download_name=path)

response.headers["X-Expires"] = f.expiration
return response
else:
if request.method == "POST":
abort(405)
response.headers["X-Expires"] = f.expiration
return response
else:
if request.method == "POST":
abort(405)

if "/" in path:
abort(404)
if "/" in path:
abort(404)

u = URL.query.get(id)
u = URL.query.get(id)

if u:
return redirect(u.url)
if u:
return redirect(u.url)

abort(404)
abort(404)
except OverflowError as e:
print(e)
abort(500)

@app.route("/", methods=["GET", "POST"])
def fhost():
Expand Down Expand Up @@ -565,8 +571,8 @@ def fhost():
else:
return render_template("index.html")

@app.route('/fetch-file', methods=["POST"])
def fetch_file():
@app.route('/fetch-content', methods=["POST"])
def fetch_content():
if request.method == "POST":
url = request.form["url"]
if len(HTTP_URL_PATTERN.findall(url))==0:
Expand All @@ -579,7 +585,7 @@ def fetch_file():

if not request.url:
return jsonify({"error": "URL parameter is required"}), 400

print("fetch-content:", url)
try:
response = requests.get(url)
if response.status_code != 200:
Expand All @@ -590,6 +596,35 @@ def fetch_file():
except Exception as e:
return jsonify({"error": str(e)}), 500

@app.route('/fetch-file', methods=["POST"])
def fetch_file():
if request.method == "POST":
url = request.form["url"]
if len(HTTP_URL_PATTERN.findall(url))==0:
url = os.path.join("http://localhost:5000", url)
else:
base_url = HTTP_URL_PATTERN.findall(request.url)
if len(base_url) > 0:
base_url = base_url[0]
url = url.replace(base_url, "http://localhost:5000")

if not request.url:
return jsonify({"error": "URL parameter is required"}), 400
print("fetch-file:", url)
try:
response = requests.get(url)
if response.status_code != 200:
return "Failed to fetch file", response.status_code
# Return the file content
# return response.text, 200
res_f = make_response(send_file(io.BytesIO(response.content), as_attachment=True, download_name=os.path.basename(url)))
res_f.headers["Content-Type"] = response.headers["Content-Type"]
return res_f
except Exception as e:
print(e)
return jsonify({"error": str(e)}), 500


@app.route("/robots.txt")
def robots():
return """User-agent: *
Expand Down
125 changes: 119 additions & 6 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ let checkmarkSuccessTimer;
let hideCheckmarkTimer;
var editor;

var longPressTimer;
var fetchDataLongPressed = false;

$(document).ready(function () {
editor = CodeMirror.fromTextArea(document.getElementById('text-editor'), {
lineNumbers: true,
Expand Down Expand Up @@ -104,6 +107,85 @@ document.addEventListener("DOMContentLoaded", function() {
}
}, 5000);

document.getElementById("get-data-from-url").addEventListener("mousedown", function(event) {
longPressTimer = setTimeout(function() {
// Long press detected, initiate the file download
getFileFromUrl(true);
fetchDataLongPressed = true;
}, 700); // Adjust the duration for long press as needed (in milliseconds)
});

document.getElementById("get-data-from-url").addEventListener("mouseup", function(event) {
clearTimeout(longPressTimer);
if (!fetchDataLongPressed) {
getFileFromUrl(false);
}
fetchDataLongPressed = false;
});

document.getElementById("get-data-from-url").addEventListener("mouseleave", function(event) {
clearTimeout(longPressTimer);
fetchDataLongPressed = false;
});

// Add event listeners for drag and drop to the document
document.addEventListener("dragover", function(event) {
// Prevent the default behavior to allow dropping
event.preventDefault();
});

document.addEventListener("drop", function(event) {
// Prevent the default behavior to allow dropping
event.preventDefault();
// Get the dropped files from the event data
var files = event.dataTransfer.files;
// Process the dropped files
if (files.length > 0) {
checkmarkTimerMaxTime = Date.now();
clearTimeout(checkmarkCompleteTimer);
clearTimeout(checkmarkSuccessTimer);
clearTimeout(hideCheckmarkTimer);
$("#check-pasting").attr("class", "check");
$("#fill-pasting").attr("class", "fill");
$("#path-pasting").attr("class", "path");
var checkmarkElement = document.getElementById("checkmark-pasting");
checkmarkElement.classList.remove("hide");
checkmarkElement.classList.add("show");

try {
var webUploadSection = document.getElementById("web-upload");
window.scrollTo({ top: webUploadSection.offsetTop, behavior: "smooth" /* Smooth scroll behavior */ });
var fileInput = document.getElementById("file");
// Assuming you want to upload the first dropped file
var file = files[0];
// Update the file input with the dropped file
var fileInput = document.getElementById("file");
fileInput.files = files;
// console.log("Dropped file:", file.name);

if (validateFileSelection()) {
clearResponseDiv("response");
checkmarkCompleteTimer = setTimeout(function (timestamp) {
$("#check-pasting").attr("class", "check check-complete");
$("#fill-pasting").attr("class", "fill fill-complete");
}, 200, checkmarkTimerMaxTime);
checkmarkSuccessTimer = setTimeout(function (timestamp) {
$("#check-pasting").attr("class", "check check-complete success");
$("#fill-pasting").attr("class", "fill fill-complete success");
$("#path-pasting").attr("class", "path path-complete");
}, 500, checkmarkTimerMaxTime);
hideCheckmarkTimer = setTimeout(function (timestamp) {
checkmarkElement.classList.remove("show");
checkmarkElement.classList.add("hide");
}, 3500, checkmarkTimerMaxTime);
}
} catch (error) {
checkmarkElement.classList.remove("show");
checkmarkElement.classList.add("hide");
}
}
});

document.querySelector('a[href="#the-null-pointer"]').addEventListener("click", scrollIntoView);
document.querySelector('a[href="#terms-of-service"]').addEventListener("click", scrollIntoView);
document.querySelector('a[href="#web-upload"]').addEventListener("click", scrollIntoView);
Expand Down Expand Up @@ -439,10 +521,27 @@ let checkmarkDownloadFromUrlCompleteTimer; // Define a global variable to hold t
let checkmarkDownloadFromUrlSuccessTimer;
let hideCheckmarkDownloadFromUrlTimer;

function getFileFromUrl() {
function getFilenameFromXhr(xhr) {
var filename = "";
var disposition = xhr.getResponseHeader("Content-Disposition");
if (disposition && (disposition.indexOf("attachment") !== -1 || disposition.indexOf("inline") !== -1)) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
return filename === "" ? "untitled" : filename;
}

function getFileFromUrl(fetch_file) {
var downloadLink = document.getElementById("download-url").value;
downloadLink.disabled = true;

if (downloadLink === "" || downloadLink === null) {
alert("Enter valide URL");
return;
}
try {
// new URL(downloadLink);
downloadFromUrlTimerMaxTime = Date.now();
Expand All @@ -460,12 +559,24 @@ function getFileFromUrl() {
formData.append("url", downloadLink);

var xhr = new XMLHttpRequest();
xhr.open("POST", "/fetch-file", true);
if (fetch_file) {
xhr.open("POST", "/fetch-file", true);
xhr.responseType = "blob";
} else {
xhr.open("POST", "/fetch-content", true);
}
xhr.onreadystatechange = function () {
if (xhr.status === 200) {
if (xhr.readyState === 4 && xhr.status === 200) {
// Request was successful, handle the response
editor.setValue(xhr.responseText);
clearResponseDiv("response-script");
if (fetch_file) {
// Create a Blob object from the response
var blob = new Blob([xhr.response], { type: xhr.getResponseHeader("Content-Type") });
// Use FileSaver.js to save the Blob as a file
saveAs(blob, getFilenameFromXhr(xhr));
} else {
editor.setValue(xhr.responseText);
clearResponseDiv("response-script");
}
checkmarkDownloadFromUrlCompleteTimer = setTimeout(function (timestamp) {
if (timestamp < downloadFromUrlTimerMaxTime) {
return;
Expand Down Expand Up @@ -533,7 +644,9 @@ function checkHttpStatus(xhr, responseDivId) {
showResponse("415 Unsupported Media Type", xhr.status, responseDivId, xhr);
} else if (xhr.status === 451) {
showResponse("451 Unavailable For Legal Reasons", xhr.status, responseDivId, xhr);
} else {
} else if (xhr.status === 500) {
showResponse("500 Internal Server Error", xhr.status, responseDivId, xhr);
} else {
showResponse("ERROR CODE" + xhr.status, xhr.status, responseDivId, xhr);
}
return false;
Expand Down
16 changes: 16 additions & 0 deletions templates/500.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "layout.html" %}

{% block title %}500 Internal Server Error{% endblock %}

{% block content %}
<div class="col-md-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">500 Internal Server Error</h5>
<pre class="card-text">
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
</pre>
</div>
</div>
</div>
{% endblock %}
4 changes: 3 additions & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ <h2 id="text-editor-sect">
<div class="col-md-12 indented-content">
<div class="input-group mb-3">
<input type="text" id="download-url" class="form-control" placeholder="Download link" aria-label="Download link" aria-describedby="get-data-from-url">
<button class="btn btn-outline-primary" type="button" id="get-data-from-url" onclick="getFileFromUrl()">Download</button>
<button class="btn btn-outline-primary" type="button" id="get-data-from-url">Download</button>
</div>
<div id="download-from-url-feedback" class="invalid-feedback">Please enter an valid URL.</div>
<form id="scriptForm" enctype="multipart/form-data">
Expand Down Expand Up @@ -306,6 +306,8 @@ <h2 id="file-retention-period">
<!-- CodeMirror -->
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"></link>
<!-- FileSaver.js -->
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>
<!-- Customize JS -->
<script src="{{ url_for('static', filename='app.js') }}"></script>
<script type="text/javascript">
Expand Down

0 comments on commit 71750ed

Please sign in to comment.