From 8a0e70b19c6e347a226878ef5902a4baf14a4953 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 2 Apr 2012 11:58:30 +0200 Subject: [PATCH] We CORS now. At least on sys.4chan.org, so ban detection on non Chrom* browsers is still inaccurate. Close #368 --- 4chan_x.user.js | 303 ++++++++++-------------------------------------- Cakefile | 1 - changelog | 2 + script.coffee | 240 ++++++++------------------------------ 4 files changed, 109 insertions(+), 437 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index d34578e03..dbe75ca8b 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -9,7 +9,6 @@ // @include http*://boards.4chan.org/* // @include http*://images.4chan.org/* // @include http*://sys.4chan.org/* -// @include http*://www.4chan.org/* // @run-at document-start // @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js // @icon http://mayhemydg.github.com/4chan-x/favicon.gif @@ -1226,46 +1225,17 @@ return setTimeout(this.asyncInit); }, asyncInit: function() { - var form, iframe, link, loadChecking, script, src; - form = $('form[name=post]'); + var link, script; if (Conf['Hide Original Post Form']) { link = $.el('h1', { innerHTML: "" + (g.REPLY ? 'Quick Reply' : 'New Thread') + "" }); - $.on($('a', link), 'click', function() { + $.on(link.firstChild, 'click', function() { QR.open(); if (!g.REPLY) $('select', QR.el).value = 'new'; return $('textarea', QR.el).focus(); }); - $.before(form, link); - } - if (/chrome/i.test(navigator.userAgent)) { - QR.status({ - ready: true - }); - } else { - src = "http" + (/^https/.test(form.action) ? 's' : '') + "://sys.4chan.org/robots.txt"; - iframe = $.el('iframe', { - id: 'iframe', - src: src - }); - $.on(iframe, 'error', function() { - return this.src = this.src; - }); - loadChecking = function(iframe) { - if (!QR.status.ready) { - iframe.src = 'about:blank'; - return setTimeout((function() { - return iframe.src = src; - }), 100); - } - }; - $.on(iframe, 'load', function() { - if (this.src !== 'about:blank') { - return setTimeout(loadChecking, 500, this); - } - }); - $.add(d.head, iframe); + $.before($('form[name=post]'), link); } script = $.el('script', { textContent: 'Recaptcha.focus_response_field=function(){}' @@ -1294,9 +1264,7 @@ close: function() { var i, spoiler, _i, _len, _ref; QR.el.hidden = true; - QR.message.send({ - req: 'abort' - }); + QR.abort(); d.activeElement.blur(); $.removeClass(QR.el, 'dump'); _ref = QR.replies; @@ -1341,23 +1309,12 @@ status: function(data) { var disabled, input, value; if (data == null) data = {}; - if (data.ready) { - QR.status.ready = true; - QR.status.banned = data.banned; - } else if (!QR.status.ready) { - value = 'Loading'; - disabled = true; - } if (g.dead) { value = 404; disabled = true; QR.cooldown.auto = false; - } else if (QR.status.banned) { - value = 'Banned'; - disabled = true; - } else { - value = QR.cooldown.seconds || data.progress || value; } + value = QR.cooldown.seconds || data.progress || value; if (!QR.el) return; input = QR.status.input; input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit'; @@ -1823,16 +1780,14 @@ return QR.el.dispatchEvent(e); }, submit: function(e) { - var captcha, captchas, challenge, err, file, m, post, reader, reply, response, threadID; + var callbacks, captcha, captchas, challenge, err, form, m, name, opts, post, reply, response, threadID, val; if (e != null) e.preventDefault(); if (QR.cooldown.seconds) { QR.cooldown.auto = !QR.cooldown.auto; QR.status(); return; } - QR.message.send({ - req: 'abort' - }); + QR.abort(); reply = QR.replies[0]; if (!(reply.com || reply.file)) { err = 'No file selected.'; @@ -1865,8 +1820,10 @@ if (Conf['Thread Watcher'] && Conf['Auto Watch Reply'] && threadID !== 'new') { Watcher.watch(threadID); } + QR.status({ + progress: '...' + }); post = { - postURL: $('form[name=post]').action, resto: threadID, name: reply.name, email: reply.email, @@ -1879,37 +1836,51 @@ recaptcha_challenge_field: challenge, recaptcha_response_field: response + ' ' }; - QR.status({ - progress: '...' - }); - if ($.engine === 'gecko' && reply.file) { - file = {}; - reader = new FileReader(); - reader.onload = function() { - file.buffer = this.result; - file.name = reply.file.name; - file.type = reply.file.type; - post.upfile = file; - return QR.message.send(post); - }; - reader.readAsBinaryString(reply.file); - return; + form = new FormData(); + for (name in post) { + val = post[name]; + if (val) form.append(name, val); } - if (/chrome/i.test(navigator.userAgent)) { - QR.message.post(post); - return; - } - return QR.message.send(post); + callbacks = { + onload: function() { + return QR.response(this.response); + }, + onerror: function() { + return QR.error('_', $.el('a', { + href: '//www.4chan.org/banned', + target: '_blank', + textContent: 'Connection error, or you are banned.' + })); + } + }; + opts = { + form: form, + type: 'POST', + upCallbacks: { + onload: function() { + return QR.status({ + progress: '...' + }); + }, + onprogress: function(e) { + return QR.status({ + progress: "" + (Math.round(e.loaded / e.total * 100)) + "%" + }); + } + } + }; + return QR.ajax = $.ajax($('form[name=post]').action, callbacks, opts); }, response: function(html) { var b, doc, err, node, persona, postNumber, reply, thread, _, _ref; - doc = $.el('a', { - innerHTML: html - }); - if ($('title', doc).textContent === '4chan - Banned') { - QR.message.receive({ - req: 'banned' - }); + doc = d.implementation.createHTMLDocument(null); + doc.documentElement.innerHTML = html; + if (doc.title === '4chan - Banned') { + QR.error('_', $.el('a', { + href: '//www.4chan.org/banned', + target: '_blank', + textContent: 'You are banned.' + })); return; } if (!(b = $('td b', doc))) { @@ -1967,146 +1938,10 @@ QR.status(); return QR.resetFileInput(); }, - message: { - send: function(data) { - var host, window; - if (/chrome/i.test(navigator.userAgent)) { - QR.message.receive(data); - return; - } - data.QR = true; - host = location.hostname; - window = host === 'boards.4chan.org' ? $.id('iframe').contentWindow : parent; - return window.postMessage(data, '*'); - }, - receive: function(data) { - var req, _ref; - req = data.req; - delete data.req; - delete data.QR; - switch (req) { - case 'abort': - if ((_ref = QR.ajax) != null) _ref.abort(); - return QR.message.send({ - req: 'status' - }); - case 'response': - return QR.response(data.html); - case 'status': - return QR.status(data); - case 'connection error': - return QR.error('_', $.el('a', { - href: '//www.4chan.org/banned', - target: '_blank', - textContent: 'Connection error, or you are banned.' - })); - case 'banned': - QR.error('_', $.el('a', { - href: '//www.4chan.org/banned', - target: '_blank', - textContent: 'You are banned.' - })); - return QR.status({ - ready: true, - banned: true - }); - default: - return QR.message.post(data); - } - }, - post: function(data) { - var boundary, callbacks, form, i, name, opts, parts, toBin, url, val; - url = data.postURL; - delete data.postURL; - if ($.engine === 'gecko' && data.upfile) { - if (!data.binary) { - toBin = function(data, name, val) { - var bb, r; - bb = new MozBlobBuilder(); - bb.append(val); - r = new FileReader(); - r.onload = function() { - data[name] = r.result; - if (!--i) return QR.message.post(data); - }; - return r.readAsBinaryString(bb.getBlob('text/plain')); - }; - i = Object.keys(data).length; - for (name in data) { - val = data[name]; - if (typeof val === 'object') { - toBin(data.upfile, 'name', data.upfile.name); - } else if (typeof val === 'boolean') { - if (val) { - toBin(data, name, String(val)); - } else { - i--; - } - } else { - toBin(data, name, val); - } - } - data.postURL = url; - data.binary = true; - return; - } - delete data.binary; - boundary = '-------------SMCD' + Date.now(); - parts = []; - parts.push('Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n'); - delete data.upfile; - for (name in data) { - val = data[name]; - if (val) { - parts.push('Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n'); - } - } - form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n'; - } else { - form = new FormData(); - for (name in data) { - val = data[name]; - if (val) form.append(name, val); - } - } - callbacks = { - onload: function() { - return QR.message.send({ - req: 'response', - html: this.response - }); - }, - onerror: function() { - return QR.message.send({ - req: 'connection error' - }); - } - }; - opts = { - form: form, - type: 'post', - upCallbacks: { - onload: function() { - return QR.message.send({ - req: 'status', - progress: '...' - }); - }, - onprogress: function(e) { - return QR.message.send({ - req: 'status', - progress: "" + (Math.round(e.loaded / e.total * 100)) + "%" - }); - } - } - }; - if (boundary) { - opts.headers = { - 'Content-Type': 'multipart/form-data;boundary=' + boundary - }; - } - return QR.ajax = $.ajax(url, callbacks, opts); - } + abort: function() { + var _ref; + if ((_ref = QR.ajax) != null) _ref.abort(); + return QR.status(); } }; @@ -2566,10 +2401,7 @@ d.title = d.title.match(/^.+-/)[0] + ' 404'; } Unread.update(true); - QR.message.send({ - req: 'abort' - }); - QR.status(); + QR.abort(); return; } Updater.retryCoef = 10; @@ -3811,12 +3643,7 @@ $.on(window, 'message', Main.message); switch (location.hostname) { case 'sys.4chan.org': - if (path === '/robots.txt') { - QR.message.send({ - req: 'status', - ready: true - }); - } else if (/report/.test(location.search)) { + if (/report/.test(location.search)) { $.ready(function() { return $.on($.id('recaptcha_response_field'), 'keydown', function(e) { if (e.keyCode === 8 && !e.target.value) { @@ -3826,13 +3653,6 @@ }); } return; - case 'www.4chan.org': - if (path === '/banned') { - QR.message.send({ - req: 'banned' - }); - } - return; case 'images.4chan.org': $.ready(function() { if (d.title === '4chan - 404') return Redirect.init(); @@ -3993,13 +3813,8 @@ } }, message: function(e) { - var data, version; - data = e.data; - if (data.QR) { - QR.message.receive(data); - return; - } - version = data.version; + var version; + version = e.data.version; if (version && version !== Main.version && confirm('An updated version of 4chan X is available, would you like to install it now?')) { return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + version + "/4chan_x.user.js"; } diff --git a/Cakefile b/Cakefile index 8fb5a2a42..139742e0c 100644 --- a/Cakefile +++ b/Cakefile @@ -16,7 +16,6 @@ HEADER = """ // @include http*://boards.4chan.org/* // @include http*://images.4chan.org/* // @include http*://sys.4chan.org/* -// @include http*://www.4chan.org/* // @run-at document-start // @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js // @icon http://mayhemydg.github.com/4chan-x/favicon.gif diff --git a/changelog b/changelog index a6f7f9e6a..2fd72e603 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,6 @@ master +- Mayhem + Update posting method. 2.29.2 - Mayhem diff --git a/script.coffee b/script.coffee index fd50fadd0..e10dc9695 100644 --- a/script.coffee +++ b/script.coffee @@ -1010,34 +1010,17 @@ QR = setTimeout @asyncInit asyncInit: -> - form = $ 'form[name=post]' if Conf['Hide Original Post Form'] link = $.el 'h1', innerHTML: "#{if g.REPLY then 'Quick Reply' else 'New Thread'}" - $.on $('a', link), 'click', -> + $.on link.firstChild, 'click', -> QR.open() $('select', QR.el).value = 'new' unless g.REPLY $('textarea', QR.el).focus() - $.before form, link - - # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. - if /chrome/i.test navigator.userAgent - QR.status ready: true - else - src = "http#{if /^https/.test form.action then 's' else ''}://sys.4chan.org/robots.txt" - iframe = $.el 'iframe', - id: 'iframe' - src: src - $.on iframe, 'error', -> @src = @src - # Greasemonkey ghetto fix - loadChecking = (iframe) -> - unless QR.status.ready - iframe.src = 'about:blank' - setTimeout (-> iframe.src = src), 100 - $.on iframe, 'load', -> if @src isnt 'about:blank' then setTimeout loadChecking, 500, @ - $.add d.head, iframe + $.before $('form[name=post]'), link # Prevent original captcha input from being focused on reload. - script = $.el 'script', textContent: 'Recaptcha.focus_response_field=function(){}' + script = $.el 'script', + textContent: 'Recaptcha.focus_response_field=function(){}' $.add d.head, script $.rm script @@ -1059,7 +1042,7 @@ QR = QR.dialog() close: -> QR.el.hidden = true - QR.message.send req: 'abort' + QR.abort() d.activeElement.blur() $.removeClass QR.el, 'dump' for i in QR.replies @@ -1093,22 +1076,11 @@ QR = $('.warning', QR.el).textContent = null status: (data={}) -> - if data.ready - QR.status.ready = true - QR.status.banned = data.banned - else unless QR.status.ready - value = 'Loading' - disabled = true if g.dead value = 404 disabled = true QR.cooldown.auto = false - else if QR.status.banned - value = 'Banned' - disabled = true - else - # do not cancel `value = 'Loading'` once the cooldown is over - value = QR.cooldown.seconds or data.progress or value + value = QR.cooldown.seconds or data.progress or value return unless QR.el {input} = QR.status input.value = @@ -1509,7 +1481,7 @@ QR = QR.cooldown.auto = !QR.cooldown.auto QR.status() return - QR.message.send req: 'abort' + QR.abort() reply = QR.replies[0] # prevent errors @@ -1549,8 +1521,11 @@ QR = if Conf['Thread Watcher'] and Conf['Auto Watch Reply'] and threadID isnt 'new' Watcher.watch threadID + # Starting to upload might take some time. + # Provide some feedback that we're starting to submit. + QR.status progress: '...' + post = - postURL: $('form[name=post]').action resto: threadID name: reply.name email: reply.email @@ -1563,36 +1538,42 @@ QR = recaptcha_challenge_field: challenge recaptcha_response_field: response + ' ' - # Starting to upload might take some time. - # Provide some feedback that we're starting to submit. - QR.status progress: '...' + form = new FormData() + for name, val of post + form.append name, val if val - if $.engine is 'gecko' and reply.file - # https://bugzilla.mozilla.org/show_bug.cgi?id=673742 - # We plan to allow postMessaging Files and FileLists across origins, - # that just needs a more in depth security review. - file = {} - reader = new FileReader() - reader.onload = -> - file.buffer = @result - file.name = reply.file.name - file.type = reply.file.type - post.upfile = file - QR.message.send post - reader.readAsBinaryString reply.file - return + callbacks = + onload: -> + QR.response @response + onerror: -> + # Connection error, or + # CORS disabled error on www.4chan.org/banned + QR.error '_', $.el 'a', + href: '//www.4chan.org/banned' + target: '_blank' + textContent: 'Connection error, or you are banned.' + opts = + form: form + type: 'POST' + upCallbacks: + onload: -> + # Upload done, waiting for response. + QR.status progress: '...' + onprogress: (e) -> + # Uploading... + QR.status progress: "#{Math.round e.loaded / e.total * 100}%" - # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. - if /chrome/i.test navigator.userAgent - QR.message.post post - return - QR.message.send post + QR.ajax = $.ajax $('form[name=post]').action, callbacks, opts response: (html) -> - doc = $.el 'a', innerHTML: html + doc = d.implementation.createHTMLDocument null + doc.documentElement.innerHTML = html # Check for ban. - if $('title', doc).textContent is '4chan - Banned' - QR.message.receive req: 'banned' + if doc.title is '4chan - Banned' + QR.error '_', $.el 'a', + href: '//www.4chan.org/banned' + target: '_blank' + textContent: 'You are banned.' return unless b = $ 'td b', doc err = 'Connection error with sys.4chan.org.' @@ -1650,123 +1631,9 @@ QR = QR.status() QR.resetFileInput() - message: - send: (data) -> - # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. - if /chrome/i.test navigator.userAgent - QR.message.receive data - return - data.QR = true - host = location.hostname - window = - if host is 'boards.4chan.org' - $.id('iframe').contentWindow - else - parent - window.postMessage data, '*' - receive: (data) -> - req = data.req - delete data.req - delete data.QR - switch req - when 'abort' - QR.ajax?.abort() - QR.message.send req: 'status' - when 'response' # xhr response - QR.response data.html - when 'status' - QR.status data - when 'connection error' - QR.error '_', $.el 'a', - href: '//www.4chan.org/banned' - target: '_blank' - textContent: 'Connection error, or you are banned.' - when 'banned' - QR.error '_', $.el 'a', - href: '//www.4chan.org/banned' - target: '_blank' - textContent: 'You are banned.' - # Disable iframe reloading - QR.status ready: true, banned: true - else - QR.message.post data # Reply object: we're posting - - post: (data) -> - - url = data.postURL - # Do not append these values to the form. - delete data.postURL - - # File with filename upload fix from desuwa - if $.engine is 'gecko' and data.upfile - # All of this is fucking retarded. - unless data.binary - toBin = (data, name, val) -> - bb = new MozBlobBuilder() - bb.append val - r = new FileReader() - r.onload = -> - data[name] = r.result - unless --i - QR.message.post data - r.readAsBinaryString bb.getBlob 'text/plain' - i = Object.keys(data).length - for name, val of data - if typeof val is 'object' # File. toBin the filename. - toBin data.upfile, 'name', data.upfile.name - else if typeof val is 'boolean' - if val - toBin data, name, String val - else - i-- - else - toBin data, name, val - data.postURL = url - data.binary = true - return - - delete data.binary - - boundary = '-------------SMCD' + Date.now(); - parts = [] - parts.push 'Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n' - delete data.upfile - - for name, val of data - parts.push 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n' if val - form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n' - - else - form = new FormData() - for name, val of data - form.append name, val if val - - callbacks = - onload: -> - QR.message.send - req: 'response' - html: @response - onerror: -> - # Connection error, or - # CORS disabled error: redirecting to banned page ;_; - QR.message.send req: 'connection error' - opts = - form: form - type: 'post' - upCallbacks: - onload: -> - QR.message.send - req: 'status' - progress: '...' - onprogress: (e) -> - QR.message.send - req: 'status' - progress: "#{Math.round e.loaded / e.total * 100}%" - if boundary - opts.headers = - 'Content-Type': 'multipart/form-data;boundary=' + boundary - - QR.ajax = $.ajax url, callbacks, opts + abort: -> + QR.ajax?.abort() + QR.status() Options = init: -> @@ -2179,8 +2046,7 @@ Updater = else d.title = d.title.match(/^.+-/)[0] + ' 404' Unread.update true - QR.message.send req: 'abort' - QR.status() + QR.abort() return Updater.retryCoef = 10 @@ -3178,17 +3044,11 @@ Main = switch location.hostname when 'sys.4chan.org' - if path is '/robots.txt' - QR.message.send req: 'status', ready: true - else if /report/.test location.search + if /report/.test location.search $.ready -> $.on $.id('recaptcha_response_field'), 'keydown', (e) -> window.location = 'javascript:Recaptcha.reload()' if e.keyCode is 8 and not e.target.value return - when 'www.4chan.org' - if path is '/banned' - QR.message.send req: 'banned' - return when 'images.4chan.org' $.ready -> Redirect.init() if d.title is '4chan - 404' return @@ -3365,11 +3225,7 @@ Main = $.on d, 'DOMNodeInserted', Main.addStyle message: (e) -> - {data} = e - if data.QR - QR.message.receive data - return - {version} = data + {version} = e.data if version and version isnt Main.version and confirm 'An updated version of 4chan X is available, would you like to install it now?' window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js"