def handle_exception(error): """Send an e-mail to the site admin when an exception occurs.""" if current_app.config.get("DEBUG"): print(traceback.format_exc()) raise import rophako.jsondb as JsonDB # Don't spam too many e-mails in a short time frame. cache = JsonDB.get_cache("exception_catcher") if cache: last_exception = int(cache) if int(time.time()) - last_exception < 120: # Only one e-mail per 2 minutes, minimum logger.error("RAPID EXCEPTIONS, DROPPING") return JsonDB.set_cache("exception_catcher", int(time.time())) username = "******" try: if hasattr( g, "info" ) and "session" in g.info and "username" in g.info["session"]: username = g.info["session"]["username"] except: pass # Get the timestamp. timestamp = time.ctime(time.time()) # Exception's traceback. error = str(error.__class__.__name__) + ": " + str(error) stacktrace = error + "\n\n" \ + "==== Start Traceback ====\n" \ + traceback.format_exc() \ + "==== End Traceback ====\n\n" \ + "Request Information\n" \ + "-------------------\n" \ + "Address: " + remote_addr() + "\n" \ + "User Agent: " + request.user_agent.string + "\n" \ + "Referrer: " + request.referrer # Construct the subject and message subject = "Internal Server Error on {} - {} - {}".format( Config.site.site_name, username, timestamp, ) message = "{} has experienced an exception on the route: {}".format( username, request.path, ) message += "\n\n" + stacktrace # Send the e-mail. send_email( to=Config.site.notify_address, subject=subject, message=message, )
def read_json(path): """Slurp, decode and return the data from a JSON document.""" path = str(path) if not os.path.isfile(path): raise Exception("Can't read JSON file {}: file not found!".format(path)) # Don't allow any fishy looking paths. if ".." in path: logger.error("ERROR: JsonDB tried to read a path with two dots: {}".format(path)) raise Exception() # Open and lock the file. fh = codecs.open(path, 'r', 'utf-8') flock(fh, LOCK_SH) text = fh.read() flock(fh, LOCK_UN) fh.close() # Decode. try: data = json.loads(text) except: logger.error("Couldn't decode JSON data from {}".format(path)) handle_exception(Exception("Couldn't decode JSON from {}\n{}".format( path, text, ))) data = None return data
def catch_exception(error): """Catch unexpected Python exceptions and e-mail them out.""" logger.error("INTERNAL SERVER ERROR: {}".format(str(error))) # E-mail it out. rophako.utils.handle_exception(error) return rophako.utils.template("errors/500.html")
def read_json(path): """Slurp, decode and return the data from a JSON document.""" path = str(path) if not os.path.isfile(path): raise Exception( "Can't read JSON file {}: file not found!".format(path)) # Don't allow any fishy looking paths. if ".." in path: logger.error( "ERROR: JsonDB tried to read a path with two dots: {}".format( path)) raise Exception() # Open and lock the file. fh = codecs.open(path, 'r', 'utf-8') flock(fh, LOCK_SH) text = fh.read() flock(fh, LOCK_UN) fh.close() # Decode. try: data = json.loads(text) except: logger.error("Couldn't decode JSON data from {}".format(path)) handle_exception( Exception("Couldn't decode JSON from {}\n{}".format( path, text, ))) data = None return data
def handle_exception(error): """Send an e-mail to the site admin when an exception occurs.""" if current_app.config.get("DEBUG"): print(traceback.format_exc()) raise import rophako.jsondb as JsonDB # Don't spam too many e-mails in a short time frame. cache = JsonDB.get_cache("exception_catcher") if cache: last_exception = int(cache) if int(time.time()) - last_exception < 120: # Only one e-mail per 2 minutes, minimum logger.error("RAPID EXCEPTIONS, DROPPING") return JsonDB.set_cache("exception_catcher", int(time.time())) username = "******" try: if hasattr(g, "info") and "session" in g.info and "username" in g.info["session"]: username = g.info["session"]["username"] except: pass # Get the timestamp. timestamp = time.ctime(time.time()) # Exception's traceback. error = str(error.__class__.__name__) + ": " + str(error) stacktrace = error + "\n\n" \ + "==== Start Traceback ====\n" \ + traceback.format_exc() \ + "==== End Traceback ====\n\n" \ + "Request Information\n" \ + "-------------------\n" \ + "Address: " + remote_addr() + "\n" \ + "User Agent: " + request.user_agent.string + "\n" \ + "Referrer: " + request.referrer # Construct the subject and message subject = "Internal Server Error on {} - {} - {}".format( Config.site.site_name, username, timestamp, ) message = "{} has experienced an exception on the route: {}".format( username, request.path, ) message += "\n\n" + stacktrace # Send the e-mail. send_email( to=Config.site.notify_address, subject=subject, message=message, )
def edit_album(album, data): """Update an album's settings (description, format, etc.)""" album = sanitize_name(album) index = get_index() if not album in index["albums"]: logger.error("Failed to edit album: not found!") return index["settings"][album].update(data) write_index(index)
def set_album_cover(album, key): """Change the album's cover photo.""" album = sanitize_name(album) index = get_index() logger.info("Changing album cover for {} to {}".format(album, key)) if album in index["albums"] and key in index["albums"][album]: index["covers"][album] = key write_index(index) return logger.error("Failed to change album index! Album or photo not found.")
def get_photo(key): """Look up a photo by key. Returns None if not found.""" index = get_index() photo = None if key in index["map"]: album = index["map"][key] if album in index["albums"] and key in index["albums"][album]: photo = index["albums"][album][key] else: # The map is wrong! logger.error( "Photo album map is wrong; key {} not found in album {}!". format(key, album)) del index["map"][key] write_index(index) if not photo: return None # Inject additional information about the photo: # What position it's at in the album, how many photos total, and the photo # IDs of its siblings. siblings = index["photo-order"][album] i = 0 for pid in siblings: if pid == key: # We found us! Find the siblings. sprev, snext = None, None if i == 0: # We're the first photo. So previous is the last! sprev = siblings[-1] if len(siblings) > i + 1: snext = siblings[i + 1] else: snext = key elif i == len(siblings) - 1: # We're the last. So next is first! sprev = siblings[i - 1] snext = siblings[0] else: # Right in the middle. sprev = siblings[i - 1] snext = siblings[i + 1] # Inject. photo["album"] = album photo["position"] = i + 1 photo["siblings"] = len(siblings) photo["previous"] = sprev photo["next"] = snext i += 1 return photo
def get_photo(key): """Look up a photo by key. Returns None if not found.""" index = get_index() photo = None if key in index["map"]: album = index["map"][key] if album in index["albums"] and key in index["albums"][album]: photo = index["albums"][album][key] else: # The map is wrong! logger.error("Photo album map is wrong; key {} not found in album {}!".format(key, album)) del index["map"][key] write_index(index) if not photo: return None # Inject additional information about the photo: # What position it's at in the album, how many photos total, and the photo # IDs of its siblings. siblings = index["photo-order"][album] i = 0 for pid in siblings: if pid == key: # We found us! Find the siblings. sprev, snext = None, None if i == 0: # We're the first photo. So previous is the last! sprev = siblings[-1] if len(siblings) > i+1: snext = siblings[i+1] else: snext = key elif i == len(siblings)-1: # We're the last. So next is first! sprev = siblings[i-1] snext = siblings[0] else: # Right in the middle. sprev = siblings[i-1] snext = siblings[i+1] # Inject. photo["album"] = album photo["position"] = i+1 photo["siblings"] = len(siblings) photo["previous"] = sprev photo["next"] = snext i += 1 return photo
def set_cache(key, value, expires=None): """Set a key in the Redis cache.""" key = Config.db.redis_prefix + key client = get_redis() if not client: return try: client.set(key, json.dumps(value)) # Expiration date? if expires: client.expire(key, expires) except: logger.error("Redis exception: couldn't set_cache {}".format(key))
def check_auth(username, password): """Check the authentication credentials for the username and password. Returns a boolean true or false. On error, an error is logged.""" # Check if the username exists. if not exists(username=username): logger.error("User authentication failed: username {} not found!".format(username)) return False # Get the user's file. db = get_user(username=username) # Check the password. test = bcrypt.hashpw(str(password).encode("utf-8"), str(db["password"]).encode("utf-8")).decode("utf-8") return test == db["password"]
def get_redis(): """Connect to Redis or return the existing connection.""" global redis_client global disable_redis if not redis_client and not disable_redis: try: redis_client = redis.StrictRedis( host = Config.db.redis_host, port = Config.db.redis_port, db = Config.db.redis_db, ) redis_client.ping() except Exception as e: logger.error("Couldn't connect to Redis; memory caching will be disabled! {}".format(e)) redis_client = None disable_redis = True return redis_client
def resolve_id(fid): """Resolve a friendly ID to the blog ID number.""" index = get_index() # If the ID is all numeric, it's the blog post ID directly. if re.match(r'^\d+$', fid): if fid in index: return int(fid) else: logger.error("Tried resolving blog post ID {} as an EntryID, but it wasn't there!".format(fid)) return None # It's a friendly ID. Scan for it. for post_id, data in index.items(): if data["fid"] == fid: return int(post_id) logger.error("Friendly post ID {} wasn't found!".format(fid)) return None
def get_redis(): """Connect to Redis or return the existing connection.""" global redis_client global disable_redis if not redis_client and not disable_redis: try: redis_client = redis.StrictRedis( host=Config.db.redis_host, port=Config.db.redis_port, db=Config.db.redis_db, ) redis_client.ping() except Exception as e: logger.error( "Couldn't connect to Redis; memory caching will be disabled! {}" .format(e)) redis_client = None disable_redis = True return redis_client
def rename_album(old_name, new_name): """Rename an existing photo album. Returns True on success, False if the new name conflicts with another album's name.""" old_name = sanitize_name(old_name) new_name = sanitize_name(new_name) index = get_index() # New name is unique? if new_name in index["albums"]: logger.error("Can't rename album: new name already exists!") return False def transfer_key(obj, old_key, new_key): # Reusable function to do a simple move on a dict key. obj[new_key] = obj[old_key] del obj[old_key] # Simple moves. transfer_key(index["albums"], old_name, new_name) transfer_key(index["covers"], old_name, new_name) transfer_key(index["photo-order"], old_name, new_name) transfer_key(index["settings"], old_name, new_name) # Update the photo -> album maps. for photo in index["map"]: if index["map"][photo] == old_name: index["map"][photo] = new_name # Fix the album ordering. new_order = list() for name in index["album-order"]: if name == old_name: name = new_name new_order.append(name) index["album-order"] = new_order # And save. write_index(index) return True
def resolve_id(fid): """Resolve a friendly ID to the blog ID number.""" index = get_index() # If the ID is all numeric, it's the blog post ID directly. if re.match(r'^\d+$', fid): if fid in index: return int(fid) else: logger.error( "Tried resolving blog post ID {} as an EntryID, but it wasn't there!" .format(fid)) return None # It's a friendly ID. Scan for it. for post_id, data in index.items(): if data["fid"] == fid: return int(post_id) logger.error("Friendly post ID {} wasn't found!".format(fid)) return None
def write_json(path, data): """Write a JSON document.""" path = str(path) # Don't allow any fishy looking paths. if ".." in path: logger.error("ERROR: JsonDB tried to write a path with two dots: {}".format(path)) raise Exception() logger.debug("JsonDB: WRITE > {}".format(path)) # Open and lock the file. fh = codecs.open(path, 'w', 'utf-8') flock(fh, LOCK_EX) # Write it. fh.write(json.dumps(data, indent=4, separators=(',', ': '))) # Unlock and close. flock(fh, LOCK_UN) fh.close()
def load_theme(): """Pre-load and cache the emoticon theme. This happens on startup.""" theme = Config.emoticons.theme global _cache # Cached? if _cache: return _cache # Only if the theme file exists. settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json") if not os.path.isfile(settings): logger.error("Failed to load smiley theme {}: not found!") # Try the default (tango). theme = "tango" settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json") if os.path.isfile(settings): logger.info("Falling back to default theme: tango") else: # Give up. return {} # Read it. fh = codecs.open(settings, "r", "utf-8") text = fh.read() fh.close() try: data = json.loads(text) except Exception as e: logger.error("Couldn't load JSON from emoticon file: {}".format(e)) data = {} # Cache and return it. _cache = data return data
def write_json(path, data): """Write a JSON document.""" path = str(path) # Don't allow any fishy looking paths. if ".." in path: logger.error( "ERROR: JsonDB tried to write a path with two dots: {}".format( path)) raise Exception() logger.debug("JsonDB: WRITE > {}".format(path)) # Open and lock the file. fh = codecs.open(path, 'w', 'utf-8') flock(fh, LOCK_EX) # Write it. fh.write(json.dumps(data, indent=4, separators=(',', ': '))) # Unlock and close. flock(fh, LOCK_UN) fh.close()
def resolve_id(fid, drafts=False): """Resolve a friendly ID to the blog ID number. Args: drafts (bool): Whether to allow draft IDs to be resolved (for logged-in users only). """ index = get_index(drafts=drafts) # If the ID is all numeric, it's the blog post ID directly. if re.match(r'^\d+$', fid): if fid in index: return int(fid) else: logger.error("Tried resolving blog post ID {} as an EntryID, but it wasn't there!".format(fid)) return None # It's a friendly ID. Scan for it. for post_id, data in index.items(): if data["fid"] == fid: return int(post_id) logger.error("Friendly post ID {} wasn't found!".format(fid)) return None