run.py 8.61 KB
Newer Older
Dominik V. Salonen's avatar
Dominik V. Salonen committed
1
#!/usr/bin/env python3
2
from flask import Flask, request, redirect, url_for, send_from_directory, abort, render_template
Dominik V. Salonen's avatar
Dominik V. Salonen committed
3
from werkzeug import secure_filename
4
from threading import Thread, Timer
Dominik V. Salonen's avatar
Dominik V. Salonen committed
5 6
import logging
import os
Dominik V. Salonen's avatar
Dominik V. Salonen committed
7
import random
Dominik V. Salonen's avatar
Dominik V. Salonen committed
8 9
import json
import time
10
import magic
11
import secrets
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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() + '"')
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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')
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
42

43
print_debug = config["DEBUG"]
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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"])


Dominik V. Salonen's avatar
Dominik V. Salonen committed
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:
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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"]):
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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):
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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))
Dominik V. Salonen's avatar
Dominik V. Salonen committed
111
      thread1.start()
112
      print_log('Thread', 'Adding file to DB', print_debug)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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 + '"')
Dominik V. Salonen's avatar
Dominik V. Salonen committed
121

Dominik V. Salonen's avatar
Dominik V. Salonen committed
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"])
Dominik V. Salonen's avatar
Dominik V. Salonen committed
126
      except Exception:
127
        print_log('Web', 'No web reported in form, returned JSON', print_debug)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
128
        return json.dumps(data)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
129
    else:
130
      print_log('Notice', 'Forbidden file received')
Dominik V. Salonen's avatar
Dominik V. Salonen committed
131
      return error_page(error="This file isn't allowed, sorry!", code=403)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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):
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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

Dominik V. Salonen's avatar
Dominik V. Salonen committed
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?')
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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)
Dominik V. Salonen's avatar
Dominik V. Salonen committed
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
  )