run.py 8.61 KB
Newer Older
1
#!/usr/bin/env python3
2
from flask import Flask, request, redirect, url_for, send_from_directory, abort, render_template
3
from werkzeug import secure_filename
4
from threading import Thread, Timer
5 6
import logging
import os
Dominik V. Salonen's avatar
Dominik V. Salonen committed
7
import random
8 9
import json
import time
10
import magic
11
import secrets
12 13 14 15 16
from random import randint

# Import our configuration
from conf import config

Dominik V. Salonen's avatar
Dominik V. Salonen committed
17
# Import QuadFile stuff
Dominik V. Salonen's avatar
Dominik V. Salonen committed
18 19
from QuadFile import db
from QuadFile.output import print_log, time_to_string
20
from QuadFile import application
21 22 23 24

app = Flask(__name__)


Dominik V. Salonen's avatar
Dominik V. Salonen committed
25
# TODO: Try to turn these into functions or something I dunno
26
print_log('Main', 'Running in "' + os.getcwd() + '"')
27 28
print_log('Main', 'Checking for data folder')
if not os.path.exists(config['UPLOAD_FOLDER']):
29
  print_log('Warning', 'Data folder not found, creating')
30
  os.makedirs(config['UPLOAD_FOLDER'])
31 32 33 34 35 36 37 38
if not os.path.exists('files.db'):
  print_log('Warning', 'Database not found, attempting to create')
  os.system('sqlite3 files.db < schema.sql')
  if not os.path.exists('files.db'):
    print_log('Warning', 'Could not create database. Is sqlite3 available?')
    quit()
  else:
    print_log('Notice', 'Database created')
39 40 41
if config["EXTENDED_DEBUG"] == False:
  log = logging.getLogger('werkzeug')
  log.setLevel(logging.ERROR)
42

43
print_debug = config["DEBUG"]
44

45
def cleaner_thread():
Dominik V. Salonen's avatar
Dominik V. Salonen committed
46 47 48 49
  # Call itself again after the interval
  cleaner = Timer(config["CLEAN_INTERVAL"], cleaner_thread)
  cleaner.daemon = True # Daemons will attempt to exit cleanly along with the main process, which we want
  cleaner.start()
50
  print_log('Thread', 'Cleaner prepared', print_debug)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
51
  # Actual function
52
  delete_old()
53 54


55
def delete_old():
56
  print_log('Thread', 'Cleaner running', print_debug)
57 58 59
  targetTime = time.time() - config["TIME"]
  old = db.get_old_files(targetTime)
  for file in old:
60
    print_log('Thread', 'Removing old file "' + file["file"] + '"')
61 62 63
    try:
      os.remove(os.path.join(config["UPLOAD_FOLDER"], file["file"]))
    except Exception:
64
      print_log('Warning', 'Failed to delete old file "' + file["file"] + '"')
65 66 67
    db.delete_entry(file["file"])


68 69
def error_page(error, code):
  return render_template('error.html', page=config["SITE_DATA"], error=error, code=code)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
70 71


72
def allowed_file(file):
73
  if config["ALLOW_ALL_FILES"]:
74
    print_log('Main', 'All files permitted, no check done', print_debug)
75 76
    return True
  else:
77
    if config["BLACKLIST"]:
78
      if magic.from_buffer(file.read(1024), mime=True) not in config["BANNED_MIMETYPES"]:
79
        file.seek(0) # seek back to start so a valid file could be read
80 81
        return True
      else:
82
        file.seek(0)
83
        return False
84
    else:
85 86 87 88 89 90
      if magic.from_buffer(file.read(1024), mime=True) in config["ALLOWED_MIMETYPES"]:
        file.seek(0)
        return True
      else:
        file.seek(0)
        return False
91 92 93 94

@app.route('/', methods=['GET', 'POST'])
def upload_file():
  if request.method == 'POST':
95
    print_log('Web', 'New file received')
96
    if not application.basicauth(request.headers.get('X-Hyozan-Auth'), config["KEY"]):
97 98 99 100 101
      abort(403)
    data = dict()
    file = request.files['file']

    # Only continue if a file that's allowed gets submitted.
102
    if file and allowed_file(file):
103
      filename = secure_filename(file.filename)
104
      if filename.find(".")!=-1: #check if filename has a .(to check if it should split ext)
105
        filename = secrets.token_urlsafe(5) + '.' + filename.rsplit('.',1)[1]
106
      else:
107
        filename = secrets.token_urlsafe(5)
108

109 110
      deletekey = secrets.token_urlsafe(16)
      thread1 = Thread(target = db.add_file, args = (filename, deletekey))
111
      thread1.start()
112
      print_log('Thread', 'Adding file to DB', print_debug)
113 114 115 116
      file.save(os.path.join(config['UPLOAD_FOLDER'], filename))
      thread1.join()

      data["file"] = filename
117
      data["url"] = request.host_url + filename
118
      if config["GEN_DELETEKEY"]:
119
        data["deletionurl"] = request.host_url + "delete/" + deletekey
120
      print_log('Main', 'New file processed "' + filename + '"')
121

122 123
      try:
        if request.form["source"] == "web":
124
          print_log('Web', 'Returned link page for "' + filename + '"', print_debug)
125
          return render_template('link.html', data=data, page=config["SITE_DATA"])
126
      except Exception:
127
        print_log('Web', 'No web reported in form, returned JSON', print_debug)
128
        return json.dumps(data)
129
    else:
130
      print_log('Notice', 'Forbidden file received')
131
      return error_page(error="This file isn't allowed, sorry!", code=403)
132 133 134

  # Return Web UI if we have a GET request
  elif request.method == 'GET':
135
    print_log('Web', 'Hit upload page', print_debug)
136 137
    return render_template('upload.html', page=config["SITE_DATA"])

Dominik V. Salonen's avatar
Dominik V. Salonen committed
138
# Def all the static pages
139 140
@app.route('/about')
def about():
Dominik V. Salonen's avatar
Dominik V. Salonen committed
141
  return render_template('about.html', page=config["SITE_DATA"])
Dominik V. Salonen's avatar
Dominik V. Salonen committed
142 143 144
@app.route('/faq')
def faq():
  return render_template('faq.html', page=config["SITE_DATA"])
Dominik V. Salonen's avatar
Dominik V. Salonen committed
145 146
@app.route('/dmca')
def dmca():
Dominik V. Salonen's avatar
Dominik V. Salonen committed
147 148
  video = random.choice(os.listdir("static/dmca/"))
  return render_template('dmca.html', page=config["SITE_DATA"], video=video)
149

150 151 152 153 154
# Static resources that browsers spam for
@app.route('/favicon.ico')
def favicon():
  return send_from_directory('static', 'favicon.ico')
@app.route('/apple-touch-icon.png')
155
def appleTouch():
156
  return send_from_directory('static', 'logo/152px.png')
157 158
@app.route('/robots.txt')
def robotsTxt():
159 160
  return send_from_directory('static', 'robots.txt')

Dominik V. Salonen's avatar
Dominik V. Salonen committed
161 162 163
# Custom 404
@app.errorhandler(404)
def page_not_found(e):
164 165
    return error_page(error="We couldn't find that. Are you sure you know what you're looking for?", code=404), 404
@app.errorhandler(500)
166
def internal_error(e):
167
    return error_page(error="Unknown error, try your upload again, or contact the admin.", code=500), 500
168
@app.errorhandler(403)
169
def no_permission(e):
170
    return error_page(error="Permission denied, no snooping.", code=403), 403
Dominik V. Salonen's avatar
Dominik V. Salonen committed
171

172

173 174
@app.route('/<filename>', methods=['GET'])
def get_file(filename):
175
  print_log('Web', 'Hit "' + filename + '"')
176 177 178 179
  try:
    db.update_file(filename)
  except Exception:
    print_log('Warning', 'Unable to update access time. Is the file in the database?')
180 181
  return send_from_directory(config['UPLOAD_FOLDER'], filename)

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
@app.route('/delete', methods=['POST'])
def delete_file_key_api():
  deletekey = (request.form['key'])
  if config["GEN_DELETEKEY"]:
    df = db.get_file_from_key(deletekey)
    if not df:
      print_log('Web', 'Someone used an invalid deletion key')
      return "Error: Invalid deletion key!\n"
    for file in df:
      print_log('Thread', 'Deleting ' + file["file"] + ' with deletion key')
      try:
        os.remove(os.path.join(config["UPLOAD_FOLDER"], file["file"]))
        db.delete_entry(file["file"])
      except Exception:
        print_log('Warning', 'Failed to delete ' + file["file"] + ' : Invalid key')
        return "Error: Failed to delete file, not exactly sure why though\n"
  else:
    return 'Deletion keys are not enabled on this server.\n'
  return 'File deleted!\n'

@app.route('/delete/<deletekey>', methods=['GET'])
def delete_file_key(deletekey):
  if config["GEN_DELETEKEY"]:
    df = db.get_file_from_key(deletekey)
    if not df:
      print_log('Web', 'Someone used an invalid deletion key')
      return error_page(error="Invalid deletion key, double check your key!", code=403), 403
    for file in df:
      print_log('Thread', 'Deleting ' + file["file"] + ' with deletion key')
      try:
        os.remove(os.path.join(config["UPLOAD_FOLDER"], file["file"]))
        db.delete_entry(file["file"])
      except Exception:
        print_log('Warning', 'Failed to delete ' + file["file"] + ' : Invalid key')
        return error_page(error="Failed to delete file, just uhh.. go back to the main page", code=500), 500
  else:
218
    return error_page(error="Deletion keys are not enabled on this server.", code=403), 403
219
  return redirect('/')
220 221 222 223 224

# Configure nginx to use these urls as custom error pages
@app.route('/error/<int:error>')
def nginx_error(error):
  if error == 413:
225
    return error_page(error="The file you uploaded was too large for the server to handle.", code=413), 413
226 227
  elif error == 403: # Block IPs with your web server and return /error/403 for this page
    return error_page(error="Sorry, the IP you're using has been blocked due to excessive abuse", code=403), 403
228 229
  elif error == 500:
    return error_page(error="Internal server error, try again or contact the admin", code=500), 500
230
  else:
231
    return error_page(error="We literally have no idea what just happened, try again or contact the admin", code="Unknown")
232 233


Dominik V. Salonen's avatar
Dominik V. Salonen committed
234 235
if config["DELETE_FILES"]:
  cleaner_thread()
236

237
print_log('Main', 'Launching flask', print_debug)
238 239 240 241
if __name__ == '__main__':
  app.run(
    port=config["PORT"],
    host=config["HOST"],
242
    debug=config["EXTENDED_DEBUG"]
Dominik V. Salonen's avatar
Dominik V. Salonen committed
243
  )