From 6a21d7690a40ea4678ad0908a6a1846c289cc943 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 20 Feb 2019 18:35:04 -0800 Subject: [PATCH] Improve data sanitation (Fixes Tautulli/Tautulli-Issues#161) --- plexpy/datatables.py | 4 ---- plexpy/helpers.py | 25 +++++++++++++++++++++---- plexpy/plextv.py | 12 ++++++------ plexpy/webserve.py | 13 ++++++++++++- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/plexpy/datatables.py b/plexpy/datatables.py index 1559ec19..47d02503 100644 --- a/plexpy/datatables.py +++ b/plexpy/datatables.py @@ -101,10 +101,6 @@ class DataTables(object): # Paginate results result = filtered[parameters['start']:(parameters['start'] + parameters['length'])] - # Sanitize on the way out - result = [{k: helpers.sanitize(v) if isinstance(v, basestring) else v for k, v in row.iteritems()} - for row in result] - output = {'result': result, 'draw': draw_counter, 'filteredCount': len(filtered), diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 65fe61b6..ba92e658 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -522,11 +522,28 @@ def process_json_kwargs(json_kwargs): return params -def sanitize(string): - if string: - return unicode(string).replace('<','<').replace('>','>') +def sanitize_out(*dargs, **dkwargs): + """ Helper decorator that sanitized the output + """ + def rd(function): + @wraps(function) + def wrapper(*args, **kwargs): + return sanitize(function(*args, **kwargs)) + return wrapper + return rd + + +def sanitize(obj): + if isinstance(obj, basestring): + return unicode(obj).replace('<', '<').replace('>', '>') + elif isinstance(obj, list): + return [sanitize(o) for o in obj] + elif isinstance(obj, dict): + return {k: sanitize(v) for k, v in obj.iteritems()} + elif isinstance(obj, tuple): + return tuple(sanitize(list(obj))) else: - return '' + return obj def is_public_ip(host): diff --git a/plexpy/plextv.py b/plexpy/plextv.py index b57d039d..433f86bd 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -566,13 +566,13 @@ class PlexTV(object): settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution') - sync_details = {"device_name": helpers.sanitize(device_name), - "platform": helpers.sanitize(device_platform), + sync_details = {"device_name": device_name, + "platform": device_platform, "user_id": device_user_id, - "user": helpers.sanitize(device_friendly_name), - "username": helpers.sanitize(device_username), - "root_title": helpers.sanitize(sync_root_title), - "sync_title": helpers.sanitize(sync_title), + "user": device_friendly_name, + "username": device_username, + "root_title": sync_root_title, + "sync_title": sync_title, "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 452f68fb..614cb34d 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -55,7 +55,7 @@ import users import versioncheck import web_socket from plexpy.api2 import API2 -from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json +from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json, sanitize_out from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library from plexpy.webauth import AuthController, requireAuth, member_of @@ -349,6 +349,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi("get_libraries_table") def get_library_list(self, **kwargs): """ Get the data on the Tautulli libraries table. @@ -427,6 +428,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) + @sanitize_out() @addtoapi("get_library_names") def get_library_sections(self, **kwargs): """ Get a list of library sections and ids on the PMS. @@ -1014,6 +1016,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi("get_users_table") def get_user_list(self, **kwargs): """ Get the data on Tautulli users table. @@ -1228,6 +1231,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi() def get_user_ips(self, user_id=None, **kwargs): """ Get the data on Tautulli users IP table. @@ -1294,6 +1298,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi() def get_user_logins(self, user_id=None, **kwargs): """ Get the data on Tautulli user login table. @@ -1575,6 +1580,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi() def get_history(self, user=None, user_id=None, grouping=None, **kwargs): """ Get the Tautulli history. @@ -1821,6 +1827,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() + @sanitize_out() @addtoapi() def get_user_names(self, **kwargs): """ Get a list of all user and user ids. @@ -2293,6 +2300,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() + @sanitize_out() @requireAuth() def get_sync(self, machine_id=None, user_id=None, **kwargs): if user_id == 'null': @@ -2434,6 +2442,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) + @sanitize_out() @addtoapi() def get_notification_log(self, **kwargs): """ Get the data on the Tautulli notification logs table. @@ -2495,6 +2504,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) + @sanitize_out() @addtoapi() def get_newsletter_log(self, **kwargs): """ Get the data on the Tautulli newsletter logs table. @@ -5228,6 +5238,7 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) + @sanitize_out() @addtoapi() def get_synced_items(self, machine_id='', user_id='', **kwargs): """ Get a list of synced items on the PMS.