plint_web.py (7114B)
1 #!/usr/bin/python3 -Ou 2 # encoding: utf8 3 4 from plint import localization, error, template, diaeresis 5 import re 6 from plint.bottle import run, Bottle, request, static_file, redirect, response 7 from jinja2 import Environment, PackageLoader 8 from json import dumps 9 import time 10 11 env = Environment(loader=PackageLoader('plint_web', 'views')) 12 13 14 # force HTTPS usage 15 # http://bottlepy.org/docs/dev/faq.html#problems-with-reverse-proxies 16 # because bottle makes absolute redirects 17 # https://github.com/bottlepy/bottle/blob/9fe68c89e465004a5e6babed0955bc1eeba88002/bottle.py#L2637 18 # even though relative Location: is now allowed 19 # http://stackoverflow.com/a/25643550 20 def fix_https(app): 21 def fixed_app(environ, start_response): 22 environ['wsgi.url_scheme'] = 'https' 23 return app(environ, start_response) 24 25 return fixed_app 26 27 28 app = Bottle() 29 app.wsgi = fix_https(app.wsgi) 30 31 THROTTLE_DELAY = 2 32 throttle = set() 33 34 35 def best_match(matches, header): 36 # inspired by http://www.xml.com/pub/a/2005/06/08/restful.html 37 38 def parse_one(t): 39 parts = t.split(";") 40 d = {} 41 for param in parts[1:]: 42 spl = param.split("=") 43 if (len(spl) != 2): 44 # this should be formatted as key=value 45 # so ignore it 46 continue 47 k, v = spl 48 d[k.strip().lower()] = v.strip() 49 if 'q' not in d.keys(): 50 d['q'] = "1" 51 return (parts[0], d) 52 53 parts = [] 54 for p in header.split(","): 55 parsed = parse_one(p) 56 try: 57 value = float(parsed[1]['q']) 58 except ValueError: 59 # q value should be a float; set it to 0 60 value = 0 61 parts.append((value, parsed[0].split("-"))) 62 for lang in [x[1] for x in sorted(parts, reverse=True)]: 63 for match in matches: 64 if match in lang: 65 return match 66 return matches[0] 67 68 69 def get_locale(): 70 header = request.headers.get('Accept-Language') 71 print(header) 72 try: 73 return best_match(['fr', 'en'], header) 74 except AttributeError: 75 return 'en' 76 77 78 def get_title(lang): 79 if lang == 'fr': 80 return "plint -- vérification formelle de poèmes" 81 else: 82 return "plint -- French poetry checker" 83 84 85 @app.route('/static/tpl/<filename>') 86 def server_static(filename): 87 return static_file(filename, root="./static/tpl", mimetype="text/plain") 88 89 90 @app.route('/<lang>/static/img/<filename>') 91 def server_static(filename, lang=None): 92 return static_file(filename, root="./static/img") 93 94 95 @app.route('/<lang>/static/tpl/<filename>') 96 def server_static(filename, lang=None): 97 return static_file(filename, root="./static/tpl", mimetype="text/plain") 98 99 100 @app.route('/static/<filename>') 101 def server_static(filename): 102 return static_file(filename, root="./static") 103 104 105 @app.route('/<lang>/static/<filename>') 106 def server_static(filename, lang=None): 107 return static_file(filename, root="./static") 108 109 110 @app.route('/') 111 def root(): 112 redirect('/' + get_locale() + '/') 113 114 115 @app.route('/<page>') 116 def paged(page): 117 redirect('/' + get_locale() + '/' + page) 118 119 120 @app.route('/<lang>/') 121 def root(lang): 122 if lang not in ['fr', 'en']: 123 return paged(lang) 124 return env.get_template('index.html').render(title=get_title(lang), 125 lang=lang, path="") 126 127 128 @app.route('/<lang>/about') 129 def about(lang): 130 return env.get_template('about.html').render(title=get_title(lang), 131 lang=lang, path="about") 132 133 134 MAX_POEM_LEN = 8192 135 MAX_LINE_LEN = 512 136 137 138 class TooBigException(Exception): 139 pass 140 141 142 class TooLongLinesException(Exception): 143 pass 144 145 146 def check(poem): 147 if len(poem) > MAX_POEM_LEN: 148 raise TooBigException 149 s = poem.split("\n") 150 for x in range(len(s)): 151 if len(s[x]) > MAX_LINE_LEN: 152 raise TooLongLinesException 153 s[x] = s[x].strip() 154 return s 155 156 157 @app.route('/<lang>/checkjs', method='POST') 158 def q(lang): 159 global throttle 160 # necessary when serving with lighttpd proxy-core 161 ip = request.environ.get('HTTP_X_FORWARDED_FOR') 162 if not ip: 163 # fallback; this is 127.0.0.1 with proxy-core 164 ip = request.environ.get('REMOTE_ADDR') 165 t = time.time() 166 print("== %s %s ==" % (ip, t)) 167 response.content_type = 'application/json' 168 localization.init_locale(lang) 169 throttle = set(x for x in throttle if t - x[1] < THROTTLE_DELAY) 170 if ip in (x[0] for x in throttle): 171 if lang == 'fr': 172 msg = (("Trop de requêtes pour vérifier le poème," 173 + " veuillez réessayer dans %d secondes") % 174 THROTTLE_DELAY) 175 else: 176 msg = (("Too many requests to check poem," 177 + " please try again in %d seconds") % 178 THROTTLE_DELAY) 179 return dumps({'error': msg}) 180 throttle.add((ip, t)) 181 poem = re.sub(r'<>&', '', request.forms.get('poem')) 182 print(poem) 183 184 # default message 185 if lang == 'fr': 186 msg = "Le poème est vide" 187 else: 188 msg = "Poem is empty" 189 190 try: 191 poem = check(poem) 192 except TooBigException: 193 poem = None 194 if lang == 'fr': 195 msg = "Le poème est trop long (maximum %d caractères)" % MAX_POEM_LEN 196 else: 197 msg = "Poem is too long (maximum %d characters)" % MAX_POEM_LEN 198 except TooLongLinesException: 199 poem = None 200 if lang == 'fr': 201 msg = "Certaines lignes du poème sont trop longues (maximum %d caractères)" % MAX_LINE_LEN 202 else: 203 msg = "Some lines of the poem are too long (maximum %d characters)" % MAX_LINE_LEN 204 if not poem or len(poem) == 0 or (len(poem) == 1 and len(poem[0]) == 0): 205 return dumps({'error': msg}) 206 templateName = re.sub(r'[^a-z_]', '', request.forms.get('template')) 207 print(templateName) 208 if templateName == 'custom': 209 x = request.forms.get('custom_template') 210 else: 211 try: 212 f = open("static/tpl/" + templateName + ".tpl") 213 x = f.read() 214 f.close() 215 except IOError: 216 if lang == 'fr': 217 msg = "Modèle inexistant" 218 else: 219 msg = "No such template" 220 return dumps({'error': msg}) 221 print(x) 222 try: 223 templ = template.Template(x) 224 except error.TemplateLoadError as e: 225 if lang == 'fr': 226 msg = "Erreur à la lecture du modèle : " + e.msg 227 else: 228 msg = "Error when reading template: " + e.msg 229 return dumps({'error': msg}) 230 poem.append(None) 231 r = [] 232 i = 0 233 d = {} 234 for line in poem: 235 i += 1 236 last = False 237 if line == None: 238 line = "" 239 last = True 240 errors = templ.check(line, last=last) 241 if errors: 242 r.append({ 243 'line': line, 244 'num': i, 245 'errors': sum(errors.lines(short=True), []) 246 }) 247 d['result'] = r 248 return dumps(d) 249 250 251 if __name__ == '__main__': 252 run(app, port='5000', server="cherrypy", host="::")