Add POST form to shorten link, shortened links to database, configuration file
This commit is contained in:
		
							parent
							
								
									657ee72ecb
								
							
						
					
					
						commit
						513050e131
					
				
					 5 changed files with 150 additions and 12 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -105,3 +105,6 @@ venv.bak/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PyCharm
 | 
					# PyCharm
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Databases
 | 
				
			||||||
 | 
					*.db
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								config.yml
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								config.yml
									
										
									
									
									
								
							| 
						 | 
					@ -1,9 +1,29 @@
 | 
				
			||||||
# Username to make admin API requests
 | 
					# Username to make admin API requests
 | 
				
			||||||
 | 
					# Default: 'admin'
 | 
				
			||||||
admin_username: 'admin'
 | 
					admin_username: 'admin'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Plaintext password to make admin API requests
 | 
					# Plaintext password to make admin API requests
 | 
				
			||||||
# Safe to remove if admin_hashed_password is set
 | 
					# Safe to remove if admin_hashed_password is set
 | 
				
			||||||
 | 
					# Default: commented out, 'password'
 | 
				
			||||||
#admin_password: 'password'
 | 
					#admin_password: 'password'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Hashed password (bcrypt) to make admin API requests - Preferred over plaintext, use securepass.sh to generate
 | 
					# Hashed password (bcrypt) to make admin API requests - Preferred over plaintext, use securepass.sh to generate
 | 
				
			||||||
admin_hashed_password: 'test'
 | 
					# Don't include the <username>: segment, just the hash
 | 
				
			||||||
 | 
					# Default: '$2y$15$Dhll3IY42R.JNOYazarlG.8IndwMjxmHLpFsebJzcGTJd.gbsAwna' (hash for 'password')
 | 
				
			||||||
 | 
					admin_hashed_password: '$2y$15$Dhll3IY42R.JNOYazarlG.8IndwMjxmHLpFsebJzcGTJd.gbsAwna'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Filename of the URL database
 | 
				
			||||||
 | 
					# Default: 'urls'
 | 
				
			||||||
 | 
					database_name: 'urls'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Length of random short URLs by default
 | 
				
			||||||
 | 
					# Default: 4
 | 
				
			||||||
 | 
					random_length: 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Allowed URL characters
 | 
				
			||||||
 | 
					# Default: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
 | 
				
			||||||
 | 
					allowed_chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Amount of time in seconds to spend generating random short URLs until timeout
 | 
				
			||||||
 | 
					# Default: 5
 | 
				
			||||||
 | 
					random_gen_timeout: 5
 | 
				
			||||||
							
								
								
									
										115
									
								
								liteshort.py
									
										
									
									
									
								
							
							
						
						
									
										115
									
								
								liteshort.py
									
										
									
									
									
								
							| 
						 | 
					@ -1,27 +1,122 @@
 | 
				
			||||||
from flask import Flask
 | 
					from flask import Flask, Response, request, current_app, g, send_from_directory
 | 
				
			||||||
 | 
					import bcrypt
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
import yaml
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_config():
 | 
					def load_config():
 | 
				
			||||||
    new_config = yaml.load(open("config.yml"))
 | 
					    new_config = yaml.load(open('config.yml'))
 | 
				
			||||||
    if "admin_hashed_password" in new_config.keys():
 | 
					    new_config = {k.lower(): v for k, v in new_config.items()}  # Make config keys case insensitive
 | 
				
			||||||
        new_config["password"] = new_config["admin_hashed_password"]
 | 
					
 | 
				
			||||||
    elif "admin_password" in new_config.keys():
 | 
					    req_options = {'admin_username': 'admin', 'database_name': "urls", 'random_length': 4,
 | 
				
			||||||
        new_config["password"] = new_config["admin_password"]
 | 
					                   'allowed_chars': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
 | 
				
			||||||
 | 
					                   'random_gen_timeout': 5
 | 
				
			||||||
 | 
					                   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config_types = {'admin_username': str, 'database_name': str, 'random_length': int,
 | 
				
			||||||
 | 
					                    'allowed_chars': str, 'random_gen_timeout': int}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for option in req_options.keys():
 | 
				
			||||||
 | 
					        if option not in new_config.keys():  # Make sure everything in req_options is set in config
 | 
				
			||||||
 | 
					            new_config[option] = req_options[option]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for option in new_config.keys():
 | 
				
			||||||
 | 
					        if option in config_types:
 | 
				
			||||||
 | 
					            if not type(new_config[option]) is config_types[option]:
 | 
				
			||||||
 | 
					                raise TypeError(option + " must be type " + config_types[option].__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if 'admin_hashed_password' in new_config.keys():  # Sets config value to see if bcrypt is required to check password
 | 
				
			||||||
 | 
					        new_config['password_hashed'] = True
 | 
				
			||||||
 | 
					    elif 'admin_password' in new_config.keys():
 | 
				
			||||||
 | 
					        new_config['password_hashed'] = False
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise Exception("admin_password or admin_hashed_password must be set in config.yml")
 | 
					        raise TypeError('admin_password or admin_hashed_password must be set in config.yml')
 | 
				
			||||||
    return new_config
 | 
					    return new_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_password(password, pass_config):
 | 
				
			||||||
 | 
					    if pass_config['password_hashed']:
 | 
				
			||||||
 | 
					        return bcrypt.checkpw(password.encode('utf-8'), pass_config['admin_hashed_password'].encode('utf-8'))
 | 
				
			||||||
 | 
					    elif not pass_config['password_hashed']:
 | 
				
			||||||
 | 
					        return password == pass_config['admin_password']
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise RuntimeError('This should never occur! Bailing...')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_short_exist(database, short):
 | 
				
			||||||
 | 
					    database.cursor().execute("SELECT long FROM urls WHERE short = ?", (short,))
 | 
				
			||||||
 | 
					    result = database.cursor().fetchone()
 | 
				
			||||||
 | 
					    if database.cursor().fetchone():
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_long_exist(database, long):
 | 
				
			||||||
 | 
					    database.cursor().execute("SELECT short FROM urls WHERE long = ?", (long,))
 | 
				
			||||||
 | 
					    result = database.cursor().fetchone()
 | 
				
			||||||
 | 
					    if database.cursor().fetchone():
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_short():
 | 
				
			||||||
 | 
					    return ''.join(random.choice(current_app.config['allowed_chars'])
 | 
				
			||||||
 | 
					                   for i in range(current_app.config['random_length']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_db():
 | 
				
			||||||
 | 
					    if 'db' not in g:
 | 
				
			||||||
 | 
					        g.db = sqlite3.connect(
 | 
				
			||||||
 | 
					            ''.join((current_app.config['database_name'], '.db')),
 | 
				
			||||||
 | 
					            detect_types=sqlite3.PARSE_DECLTYPES
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        g.db.row_factory = sqlite3.Row
 | 
				
			||||||
 | 
					        g.db.cursor().execute('CREATE TABLE IF NOT EXISTS urls (long,short)')
 | 
				
			||||||
 | 
					    return g.db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config = load_config()
 | 
					config = load_config()
 | 
				
			||||||
print(config["password"])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = Flask(__name__)
 | 
					app = Flask(__name__)
 | 
				
			||||||
 | 
					app.config.update(config)  # Add loaded YAML config to Flask config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/')
 | 
					@app.route('/')
 | 
				
			||||||
def hello_world():
 | 
					def main():
 | 
				
			||||||
    return 'Hello World!'
 | 
					    return send_from_directory('static', 'main.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/', methods=['POST'])
 | 
				
			||||||
 | 
					def main_post():
 | 
				
			||||||
 | 
					    if 'long' in request.form and request.form['long']:
 | 
				
			||||||
 | 
					        database = get_db()
 | 
				
			||||||
 | 
					        if 'short' in request.form and request.form['short']:
 | 
				
			||||||
 | 
					            for char in request.form['short']:
 | 
				
			||||||
 | 
					                if char not in current_app.config['allowed_chars']:
 | 
				
			||||||
 | 
					                    return Response('Character ' + char + ' not allowed in short URL.', status=200)
 | 
				
			||||||
 | 
					            short = request.form['short']
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            timeout = time.time() + current_app.config['random_gen_timeout']
 | 
				
			||||||
 | 
					            while True:
 | 
				
			||||||
 | 
					                if time.time() >= timeout:
 | 
				
			||||||
 | 
					                    return Response('Timeout while generating random short URL.', status=200)
 | 
				
			||||||
 | 
					                short = generate_short()
 | 
				
			||||||
 | 
					                if not check_short_exist(database, short):
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					        short_exists = check_short_exist(database, short)
 | 
				
			||||||
 | 
					        long_exists = check_long_exist(database, request.form['long'])
 | 
				
			||||||
 | 
					        if long_exists and 'short' not in request.form:
 | 
				
			||||||
 | 
					            return request.base_url + long_exists
 | 
				
			||||||
 | 
					        if short_exists:
 | 
				
			||||||
 | 
					            return Response('Short URL already exists.', status=200)
 | 
				
			||||||
 | 
					        database.cursor().execute("INSERT INTO urls (long,short) VALUES (?,?)", (request.form['long'], short))
 | 
				
			||||||
 | 
					        database.commit()
 | 
				
			||||||
 | 
					        database.close()
 | 
				
			||||||
 | 
					        return "Your shortened URL is available at " + request.base_url + short
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return "Long URL required!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
## bcrypt passwd generator ##
 | 
					## bcrypt passwd generator ##
 | 
				
			||||||
#############################
 | 
					#############################
 | 
				
			||||||
CMD=$(which htpasswd 2>/dev/null)
 | 
					CMD=$(which htpasswd 2>/dev/null)
 | 
				
			||||||
OPTS="-nBC 15"
 | 
					OPTS="-nBC 12"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
read -p "Username: " USERNAME
 | 
					read -p "Username: " USERNAME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								static/main.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								static/main.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <title>liteshort</title>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <form>
 | 
				
			||||||
 | 
					        Long URL:
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <input name="long" type="url">
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        Short URL:
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <input name="short" type="text">
 | 
				
			||||||
 | 
					        <br> <!-- TODO: Use CSS to do linebreaks -->
 | 
				
			||||||
 | 
					        <input type="submit" value="Shorten" formmethod="post">
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in a new issue