diff --git a/cps.py b/cps.py index f4012293..e4b1ede8 100755 --- a/cps.py +++ b/cps.py @@ -72,7 +72,6 @@ def main(): app.register_blueprint(admi) app.register_blueprint(remotelogin) app.register_blueprint(meta) - # if config.config_use_google_drive: app.register_blueprint(gdrive) app.register_blueprint(editbook) if kobo_available: diff --git a/cps/__init__.py b/cps/__init__.py index 70c53b85..517358c5 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -37,6 +37,11 @@ from . import config_sql, logger, cache_buster, cli, ub, db from .reverseproxy import ReverseProxied from .server import WebServer +try: + import lxml + lxml_present = True +except ImportError: + lxml_present = False mimetypes.init() mimetypes.add_type('application/xhtml+xml', '.xhtml') @@ -90,6 +95,16 @@ db.CalibreDB.setup_db(config.config_calibre_dir, cli.settingspath) calibre_db = db.CalibreDB() def create_app(): + if sys.version_info < (3, 0): + log.info( + '*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') + print( + '*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') + sys.exit(5) + if not lxml_present: + log.info('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***') + print('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***') + sys.exit(6) app.wsgi_app = ReverseProxied(app.wsgi_app) # For python2 convert path to unicode if sys.version_info < (3, 0): @@ -99,12 +114,8 @@ def create_app(): if os.environ.get('FLASK_DEBUG'): cache_buster.init_cache_busting(app) - log.info('Starting Calibre Web...') - if sys.version_info < (3, 0): - log.info('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') - print('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') - sys.exit(5) + Principal(app) lm.init_app(app) app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session)) diff --git a/cps/admin.py b/cps/admin.py index 93c742df..fac151b1 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -888,7 +888,7 @@ def list_restriction(res_type, user_id): else: json_dumps = "" js = json.dumps(json_dumps) - response = make_response(js.replace("'", '"')) + response = make_response(js) #.replace("'", '"') response.headers["Content-Type"] = "application/json; charset=utf-8" return response diff --git a/cps/editbooks.py b/cps/editbooks.py index fbfc4483..d8e16e4c 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -26,15 +26,18 @@ from datetime import datetime import json from shutil import copyfile from uuid import uuid4 +try: + from lxml.html.clean import clean_html +except ImportError: + pass + # Improve this to check if scholarly is available in a global way, like other pythonic libraries -have_scholar = True try: from scholarly import scholarly + have_scholar = True except ImportError: have_scholar = False - pass - from babel import Locale as LC from babel.core import UnknownLocaleError @@ -57,6 +60,8 @@ except ImportError: pass # We're not using Python 3 + + editbook = Blueprint('editbook', __name__) log = logger.create() @@ -459,9 +464,11 @@ def edit_book_series_index(series_index, book): # Handle book comments/description def edit_book_comments(comments, book): modif_date = False + if comments: + comments = clean_html(comments) if len(book.comments): if book.comments[0].text != comments: - book.comments[0].text = comments + book.comments[0].text = clean_html(comments) modif_date = True else: if comments: @@ -515,6 +522,8 @@ def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string): to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0 elif c.datatype == 'comments': to_save[cc_string] = Markup(to_save[cc_string]).unescape() + if to_save[cc_string]: + to_save[cc_string] = clean_html(to_save[cc_string]) elif c.datatype == 'datetime': try: to_save[cc_string] = datetime.strptime(to_save[cc_string], "%Y-%m-%d") diff --git a/cps/helper.py b/cps/helper.py index 3f91ca86..860b5bf1 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -38,6 +38,7 @@ from flask_login import current_user from sqlalchemy.sql.expression import true, false, and_, text, func from werkzeug.datastructures import Headers from werkzeug.security import generate_password_hash +from markupsafe import escape try: from urllib.parse import quote @@ -97,10 +98,11 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format, settings['body'] = _(u'This e-mail has been sent via Calibre-Web.') else: settings = dict() - txt = (u"%s -> %s: %s" % ( + link = '{}"'.format(url_for('web.show_book', book_id=book.id), escape(book.title)) # prevent xss + txt = u"{} -> {}: {}".format( old_book_format, new_book_format, - "" + book.title + "")) + link) settings['old_book_format'] = old_book_format settings['new_book_format'] = new_book_format WorkerThread.add(user_id, TaskConvert(file_path, book.id, txt, settings, kindle_mail, user_id)) @@ -773,7 +775,7 @@ def render_task_status(tasklist): ret['taskMessage'] = "{}: {}".format(_(task.name), task.message) ret['progress'] = "{} %".format(int(task.progress * 100)) - ret['user'] = user + ret['user'] = escape(user) # prevent xss renderedtasklist.append(ret) return renderedtasklist diff --git a/cps/jinjia.py b/cps/jinjia.py index 70a6090e..554bc791 100644 --- a/cps/jinjia.py +++ b/cps/jinjia.py @@ -31,7 +31,7 @@ from babel.dates import format_date from flask import Blueprint, request, url_for from flask_babel import get_locale from flask_login import current_user - +from markupsafe import escape from . import logger @@ -129,6 +129,10 @@ def formatseriesindex_filter(series_index): return series_index return 0 +@jinjia.app_template_filter('escapedlink') +def escapedlink_filter(url, text): + return "{}".format(url, escape(text)) + @jinjia.app_template_filter('uuidfilter') def uuidfilter(var): return uuid4() diff --git a/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js b/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js index 27f5fdb8..a154f702 100644 --- a/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js +++ b/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js @@ -1,10 +1,10 @@ /** * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) * - * @version v1.18.3 + * @version v1.18.3 -> Modified to eliminate some bugs by Ozzieisaacs (don't update without appling patches again) * @homepage https://bootstrap-table.com * @author wenzhixin (http://wenzhixin.net.cn/) * @license MIT */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).jQuery)}(this,(function(t){"use strict";function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var n=e(t);function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n0?gt:bt)(t)},xt=Math.min,Et=function(t){return t>0?xt(mt(t),9007199254740991):0},wt=Math.max,Ot=Math.min,St=function(t){return function(e,n,r){var o,i=I(e),a=Et(i.length),u=function(t,e){var n=mt(t);return n<0?wt(n+e,0):Ot(n,e)}(r,a);if(t&&n!=n){for(;a>u;)if((o=i[u++])!=o)return!0}else for(;a>u;u++)if((t||u in i)&&i[u]===n)return t||u||0;return!t&&-1}},jt={includes:St(!0),indexOf:St(!1)},Tt=jt.indexOf,At=function(t,e){var n,r=I(t),o=0,i=[];for(n in r)!C(it,n)&&C(r,n)&&i.push(n);for(;e.length>o;)C(r,n=e[o++])&&(~Tt(i,n)||i.push(n));return i},It=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Pt=It.concat("length","prototype"),Rt={f:Object.getOwnPropertyNames||function(t){return At(t,Pt)}},_t={f:Object.getOwnPropertySymbols},Ct=yt("Reflect","ownKeys")||function(t){var e=Rt.f(N(t)),n=_t.f;return n?e.concat(n(t)):e},kt=function(t,e){for(var n=Ct(e),r=L.f,o=U.f,i=0;i0&&(!i.multiline||i.multiline&&"\n"!==t[i.lastIndex-1])&&(c="(?: "+c+")",l=" "+l,f++),n=new RegExp("^(?:"+c+")",u)),Jt&&(n=new RegExp("^"+c+"$(?!\\s)",u)),Qt&&(e=i.lastIndex),r=zt.call(a?n:i,l),a?r?(r.input=r.input.slice(f),r[0]=r[0].slice(f),r.index=i.lastIndex,i.lastIndex+=r[0].length):i.lastIndex=0:Qt&&r&&(i.lastIndex=i.global?r.index+r[0].length:e),Jt&&r&&r.length>1&&Xt.call(r[0],n,(function(){for(o=1;o=74)&&(ee=oe.match(/Chrome\/(\d+)/))&&(ne=ee[1]);var ce=ne&&+ne,fe=!!Object.getOwnPropertySymbols&&!b((function(){return!Symbol.sham&&(re?38===ce:ce>37&&ce<41)})),le=fe&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,se=J("wks"),de=y.Symbol,pe=le?de:de&&de.withoutSetter||nt,ve=function(t){return C(se,t)&&(fe||"string"==typeof se[t])||(fe&&C(de,t)?se[t]=de[t]:se[t]=pe("Symbol."+t)),se[t]},he=ve("species"),ye=!b((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),be="$0"==="a".replace(/./,"$0"),ge=ve("replace"),me=!!/./[ge]&&""===/./[ge]("a","$0"),xe=!b((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),Ee=function(t){return function(e,n){var r,o,i=String(A(e)),a=mt(n),u=i.length;return a<0||a>=u?t?"":void 0:(r=i.charCodeAt(a))<55296||r>56319||a+1===u||(o=i.charCodeAt(a+1))<56320||o>57343?t?i.charAt(a):r:t?i.slice(a,a+2):o-56320+(r-55296<<10)+65536}},we={codeAt:Ee(!1),charAt:Ee(!0)}.charAt,Oe=function(t,e,n){return e+(n?we(t,e).length:1)},Se=function(t){return Object(A(t))},je=Math.floor,Te="".replace,Ae=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,Ie=/\$([$&'`]|\d{1,2})/g,Pe=function(t,e,n,r,o,i){var a=n+t.length,u=r.length,c=Ie;return void 0!==o&&(o=Se(o),c=Ae),Te.call(i,c,(function(i,c){var f;switch(c.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,n);case"'":return e.slice(a);case"<":f=o[c.slice(1,-1)];break;default:var l=+c;if(0===l)return i;if(l>u){var s=je(l/10);return 0===s?i:s<=u?void 0===r[s-1]?c.charAt(1):r[s-1]+c.charAt(1):i}f=r[l-1]}return void 0===f?"":f}))},Re=function(t,e){var n=t.exec;if("function"==typeof n){var r=n.call(t,e);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==S(t))throw TypeError("RegExp#exec called on incompatible receiver");return te.call(t,e)},_e=Math.max,Ce=Math.min;!function(t,e,n,r){var o=ve(t),i=!b((function(){var e={};return e[o]=function(){return 7},7!=""[t](e)})),a=i&&!b((function(){var e=!1,n=/a/;return"split"===t&&((n={}).constructor={},n.constructor[he]=function(){return n},n.flags="",n[o]=/./[o]),n.exec=function(){return e=!0,null},n[o](""),!e}));if(!i||!a||"replace"===t&&(!ye||!be||me)||"split"===t&&!xe){var u=/./[o],c=n(o,""[t],(function(t,e,n,r,o){return e.exec===te?i&&!o?{done:!0,value:u.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}),{REPLACE_KEEPS_$0:be,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:me}),f=c[0],l=c[1];pt(String.prototype,t,f),pt(RegExp.prototype,o,2==e?function(t,e){return l.call(t,this,e)}:function(t){return l.call(t,this)})}r&&q(RegExp.prototype[o],"sham",!0)}("replace",2,(function(t,e,n,r){var o=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,i=r.REPLACE_KEEPS_$0,a=o?"$":"$0";return[function(n,r){var o=A(this),i=null==n?void 0:n[t];return void 0!==i?i.call(n,o,r):e.call(String(o),n,r)},function(t,r){if(!o&&i||"string"==typeof r&&-1===r.indexOf(a)){var u=n(e,t,this,r);if(u.done)return u.value}var c=N(t),f=String(this),l="function"==typeof r;l||(r=String(r));var s=c.global;if(s){var d=c.unicode;c.lastIndex=0}for(var p=[];;){var v=Re(c,f);if(null===v)break;if(p.push(v),!s)break;""===String(v[0])&&(c.lastIndex=Oe(f,Et(c.lastIndex),d))}for(var h,y="",b=0,g=0;g=b&&(y+=f.slice(b,x)+j,b=x+m.length)}return y+f.slice(b)}]}));var ke=function(t,e){var n=[][t];return!!n&&b((function(){n.call(null,e||function(){throw 1},1)}))},De=jt.indexOf,Fe=[].indexOf,Me=!!Fe&&1/[1].indexOf(1,-0)<0,$e=ke("indexOf");qt({target:"Array",proto:!0,forced:Me||!$e},{indexOf:function(t){return Me?Fe.apply(this,arguments)||0:De(this,t,arguments.length>1?arguments[1]:void 0)}});var Ue,Ne=Array.isArray||function(t){return"Array"==S(t)},Be=function(t,e,n){var r=R(e);r in t?L.f(t,r,w(0,n)):t[r]=n},Le=ve("species"),qe=function(t,e){var n;return Ne(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!Ne(n.prototype)?P(n)&&null===(n=n[Le])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===e?0:e)},Ve=ve("species"),Ke=ve("isConcatSpreadable"),Ge=9007199254740991,He="Maximum allowed index exceeded",We=ce>=51||!b((function(){var t=[];return t[Ke]=!1,t.concat()[0]!==t})),ze=(Ue="concat",ce>=51||!b((function(){var t=[];return(t.constructor={})[Ve]=function(){return{foo:1}},1!==t[Ue](Boolean).foo}))),Xe=function(t){if(!P(t))return!1;var e=t[Ke];return void 0!==e?!!e:Ne(t)};qt({target:"Array",proto:!0,forced:!We||!ze},{concat:function(t){var e,n,r,o,i,a=Se(this),u=qe(a,0),c=0;for(e=-1,r=arguments.length;eGe)throw TypeError(He);for(n=0;n=Ge)throw TypeError(He);Be(u,c++,i)}return u.length=c,u}});var Ye=[].join,Qe=T!=Object,Ze=ke("join",",");qt({target:"Array",proto:!0,forced:Qe||!Ze},{join:function(t){return Ye.call(I(this),void 0===t?",":t)}});var Je,tn=function(t,e,n){if(function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function")}(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}},en=[].push,nn=function(t){var e=1==t,n=2==t,r=3==t,o=4==t,i=6==t,a=7==t,u=5==t||i;return function(c,f,l,s){for(var d,p,v=Se(c),h=T(v),y=tn(f,l,3),b=Et(h.length),g=0,m=s||qe,x=e?m(c,b):n||a?m(c,0):void 0;b>g;g++)if((u||g in h)&&(p=y(d=h[g],g,v),t))if(e)x[g]=p;else if(p)switch(t){case 3:return!0;case 5:return d;case 6:return g;case 2:en.call(x,d)}else switch(t){case 4:return!1;case 7:en.call(x,d)}return i?-1:r||o?o:x}},rn={forEach:nn(0),map:nn(1),filter:nn(2),some:nn(3),every:nn(4),find:nn(5),findIndex:nn(6),filterOut:nn(7)},on=Object.keys||function(t){return At(t,It)},an=g?Object.defineProperties:function(t,e){N(t);for(var n,r=on(e),o=r.length,i=0;o>i;)L.f(t,n=r[i++],e[n]);return t},un=yt("document","documentElement"),cn=ot("IE_PROTO"),fn=function(){},ln=function(t){return" - + + {% endblock %} diff --git a/cps/ub.py b/cps/ub.py index a5a6763b..f1a33d75 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -91,9 +91,9 @@ def store_user_session(): user_session = User_Sessions(flask_session.get('_user_id', ""), flask_session.get('_id', "")) session.add(user_session) session.commit() - log.info("Login and store session : " + flask_session.get('_id', "")) + log.debug("Login and store session : " + flask_session.get('_id', "")) else: - log.info("Found stored session : " + flask_session.get('_id', "")) + log.debug("Found stored session: " + flask_session.get('_id', "")) except (exc.OperationalError, exc.InvalidRequestError) as e: session.rollback() log.exception(e) @@ -102,7 +102,7 @@ def store_user_session(): def delete_user_session(user_id, session_key): try: - log.info("Deleted session_key : " + session_key) + log.debug("Deleted session_key: " + session_key) session.query(User_Sessions).filter(User_Sessions.user_id==user_id, User_Sessions.session_key==session_key).delete() session.commit() diff --git a/optional-requirements.txt b/optional-requirements.txt index 1fa84d50..af068a51 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -30,7 +30,6 @@ Flask-Dance>=2.0.0,<5.1.0 SQLAlchemy-Utils>=0.33.5,<0.38.0 # extracting metadata -lxml>=3.8.0,<4.7.0 rarfile>=2.7 scholarly>=1.2.0, <1.3 diff --git a/requirements.txt b/requirements.txt index b29d3b99..e7f67593 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ SQLAlchemy>=1.3.0,<1.5.0 tornado>=4.1,<6.2 Wand>=0.4.4,<0.7.0 unidecode>=0.04.19,<1.3.0 +lxml>=3.8.0,<4.7.0