From c4b80ff70291edea08fe38a1688d08dfc32c5263 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Tue, 4 Mar 2014 15:40:21 +0100 Subject: [PATCH 1/2] make CORS middleware more generic to use --- isso/__init__.py | 3 ++- isso/wsgi.py | 14 ++++++++++---- specs/test_cors.py | 21 ++++++++++++--------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/isso/__init__.py b/isso/__init__.py index 6d48af4..7122ff7 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -182,7 +182,8 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False): '/css': join(dirname(__file__), 'css/')})) wrapper.append(partial(wsgi.CORSMiddleware, - origin=origin(isso.conf.getiter("general", "host")))) + origin=origin(isso.conf.getiter("general", "host")), + allowed=("Origin", "Content-Type"), exposed=("X-Set-Cookie", ))) wrapper.extend([wsgi.SubURI, ProxyFix]) diff --git a/isso/wsgi.py b/isso/wsgi.py index da7e836..66e1f06 100644 --- a/isso/wsgi.py +++ b/isso/wsgi.py @@ -60,19 +60,25 @@ class SubURI(object): class CORSMiddleware(object): """Add Cross-origin resource sharing headers to every request.""" - def __init__(self, app, origin): + methods = ("HEAD", "GET", "POST", "PUT", "DELETE") + + def __init__(self, app, origin, allowed=[], exposed=[]): self.app = app self.origin = origin + self.allowed = allowed + self.exposed = exposed def __call__(self, environ, start_response): def add_cors_headers(status, headers, exc_info=None): headers = Headers(headers) headers.add("Access-Control-Allow-Origin", self.origin(environ)) - headers.add("Access-Control-Allow-Headers", "Origin, Content-Type") headers.add("Access-Control-Allow-Credentials", "true") - headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") - headers.add("Access-Control-Expose-Headers", "X-Set-Cookie") + headers.add("Access-Control-Allow-Methods", ", ".join(self.methods)) + if self.allowed: + headers.add("Access-Control-Allow-Headers", ", ".join(self.allowed)) + if self.exposed: + headers.add("Access-Control-Expose-Headers", ", ".join(self.exposed)) return start_response(status, headers.to_list(), exc_info) if environ.get("REQUEST_METHOD") == "OPTIONS": diff --git a/specs/test_cors.py b/specs/test_cors.py index 93b9036..0479abe 100644 --- a/specs/test_cors.py +++ b/specs/test_cors.py @@ -22,21 +22,23 @@ class CORSTest(unittest.TestCase): def test_simple(self): - app = CORSMiddleware(hello_world, origin=origin([ - "https://example.tld/", - "http://example.tld/", - "http://example.tld", - ])) + app = CORSMiddleware(hello_world, + origin=origin([ + "https://example.tld/", + "http://example.tld/", + "http://example.tld", + ]), + allowed=("Foo", "Bar"), exposed=("Spam", )) client = Client(app, Response) rv = client.get("/", headers={"ORIGIN": "https://example.tld"}) self.assertEqual(rv.headers["Access-Control-Allow-Origin"], "https://example.tld") - self.assertEqual(rv.headers["Access-Control-Allow-Headers"], "Origin, Content-Type") self.assertEqual(rv.headers["Access-Control-Allow-Credentials"], "true") - self.assertEqual(rv.headers["Access-Control-Allow-Methods"], "GET, POST, PUT, DELETE") - self.assertEqual(rv.headers["Access-Control-Expose-Headers"], "X-Set-Cookie") + self.assertEqual(rv.headers["Access-Control-Allow-Methods"], "HEAD, GET, POST, PUT, DELETE") + self.assertEqual(rv.headers["Access-Control-Allow-Headers"], "Foo, Bar") + self.assertEqual(rv.headers["Access-Control-Expose-Headers"], "Spam") a = client.get("/", headers={"ORIGIN": "http://example.tld"}) self.assertEqual(a.headers["Access-Control-Allow-Origin"], "http://example.tld") @@ -50,7 +52,8 @@ class CORSTest(unittest.TestCase): def test_preflight(self): - app = CORSMiddleware(hello_world, origin=origin(["http://example.tld"])) + app = CORSMiddleware(hello_world, origin=origin(["http://example.tld"]), + allowed=("Foo", ), exposed=("Bar", )) client = Client(app, Response) rv = client.open(method="OPTIONS", path="/", headers={"ORIGIN": "http://example.tld"}) From 9a678e46910fa6f692a8a61aa9fc9af187cfb078 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Tue, 4 Mar 2014 15:48:44 +0100 Subject: [PATCH 2/2] an attempt to address #69 Add a global-like object that stores the delta from server time and client time in a list and use the resulting average to "correct" utils.ago diffs. --- isso/__init__.py | 2 +- isso/js/app/api.js | 5 +++-- isso/js/app/globals.js | 21 +++++++++++++++++++++ isso/js/app/isso.js | 6 +++--- isso/js/app/utils.js | 4 ++-- 5 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 isso/js/app/globals.js diff --git a/isso/__init__.py b/isso/__init__.py index 7122ff7..1f70390 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -183,7 +183,7 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False): wrapper.append(partial(wsgi.CORSMiddleware, origin=origin(isso.conf.getiter("general", "host")), - allowed=("Origin", "Content-Type"), exposed=("X-Set-Cookie", ))) + allowed=("Origin", "Content-Type"), exposed=("X-Set-Cookie", "Date"))) wrapper.extend([wsgi.SubURI, ProxyFix]) diff --git a/isso/js/app/api.js b/isso/js/app/api.js index 24de165..f0bc3a8 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -1,4 +1,4 @@ -define(["app/lib/promise"], function(Q) { +define(["app/lib/promise", "app/globals"], function(Q, globals) { "use strict"; @@ -41,8 +41,9 @@ define(["app/lib/promise"], function(Q) { function onload() { - var cookie = xhr.getResponseHeader("X-Set-Cookie"); + globals.offset.update(new Date(xhr.getResponseHeader("Date"))); + var cookie = xhr.getResponseHeader("X-Set-Cookie"); if (cookie && cookie.match(/^isso-/)) { document.cookie = cookie; } diff --git a/isso/js/app/globals.js b/isso/js/app/globals.js new file mode 100644 index 0000000..b3efe9d --- /dev/null +++ b/isso/js/app/globals.js @@ -0,0 +1,21 @@ +define(function() { + "use strict"; + + var Offset = function() { + this.values = []; + }; + + Offset.prototype.update = function(remoteTime) { + this.values.push((new Date()).getTime() - remoteTime); + }; + + Offset.prototype.localTime = function() { + return new Date((new Date()).getTime() + this.values.reduce( + function(a, b) { return a + b; }) / this.values.length); + }; + + return { + offset: new Offset() + }; + +}); \ No newline at end of file diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index d2b2077..fff7552 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -1,7 +1,7 @@ /* Isso – Ich schrei sonst! */ -define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/markup", "app/i18n", "app/lib"], - function(templates, $, utils, config, api, Mark, i18n, lib) { +define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/markup", "app/i18n", "app/lib", "app/globals"], + function(templates, $, utils, config, api, Mark, i18n, lib, globals) { "use strict"; @@ -100,7 +100,7 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m // update datetime every 60 seconds var refresh = function() { $(".permalink > date", el).textContent = utils.ago( - new Date(parseInt(comment.created, 10) * 1000)); + globals.offset.localTime(), new Date(parseInt(comment.created, 10) * 1000)); setTimeout(refresh, 60*1000); }; diff --git a/isso/js/app/utils.js b/isso/js/app/utils.js index dfe737c..7c8f1bf 100644 --- a/isso/js/app/utils.js +++ b/isso/js/app/utils.js @@ -5,9 +5,9 @@ define(["app/markup"], function(Mark) { return (document.cookie.match('(^|; )' + cookie + '=([^;]*)') || 0)[2]; }; - var ago = function(date) { + var ago = function(localTime, date) { - var diff = (((new Date()).getTime() - date.getTime()) / 1000), + var diff = ((localTime.getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if (isNaN(day_diff) || day_diff < 0) {