diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index 932e77ad9..56789be3d 100755 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -54,8 +54,8 @@ Redirect = # For fuuka-based archives: # https://github.com/eksopl/fuuka/issues/27 protocol = Redirect.protocol archive - return '' unless protocol is 'https://' or location.protocol is 'http:' URL = new String "#{protocol}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}" + return '' unless Redirect.securityCheck URL URL.archive = archive URL @@ -76,10 +76,14 @@ Redirect = "#{boardID}/?task=search2&search_#{if type is 'image' then 'media_hash' else type}=#{value}" "#{Redirect.protocol archive}#{archive.domain}/#{path}" + securityCheck: (URL) -> + /^https:\/\//.test(URL) or + location.protocol is 'http:' or + Conf['Allow Mixed Content from Archives'] + navigate: (URL, alternative) -> if URL and ( - /^https:\/\//.test(URL) or - location.protocol is 'http:' or + Redirect.securityCheck(URL) or confirm "Redirect to #{URL}?\n\nYour connection will not be encrypted." ) location.replace URL diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 60e4f04c0..dcb9c564a 100755 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -29,6 +29,10 @@ Config = true 'Redirect dead threads and images.' ] + 'Allow Mixed Content from Archives': [ + false + 'Permit warningless access to HTTP-only archives from HTTPS pages.' + ] 'Keybinds': [ true 'Bind actions to keyboard shortcuts.' diff --git a/src/General/CrossOrigin.coffee b/src/General/CrossOrigin.coffee index 99c8ce479..1e5d49729 100644 --- a/src/General/CrossOrigin.coffee +++ b/src/General/CrossOrigin.coffee @@ -1,46 +1,73 @@ -CrossOrigin = do -> +CrossOrigin = + file: do -> + makeBlob = (urlBlob, contentType, contentDisposition, url) -> + name = url.match(/([^\/]+)\/*$/)?[1] + mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream' + match = + contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or + contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1] + if match + name = match.replace /\\"/g, '"' + blob = new Blob([urlBlob], {type: mime}) + blob.name = name + blob - makeBlob = (urlBlob, contentType, contentDisposition, url) -> - name = url.match(/([^\/]+)\/*$/)?[1] - mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream' - match = - contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or - contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1] - if match - name = match.replace /\\"/g, '"' - blob = new Blob([urlBlob], {type: mime}) - blob.name = name - blob + (url, cb) -> + <% if (type === 'crx') { %> + $.ajax url, + responseType: 'blob' + onload: -> + return cb null unless @readyState is @DONE and @status is 200 + contentType = @getResponseHeader 'Content-Type' + contentDisposition = @getResponseHeader 'Content-Disposition' + cb (makeBlob @response, contentType, contentDisposition, url) + onerror: -> + cb null + <% } %> + <% if (type === 'userscript') { %> + GM_xmlhttpRequest + method: "GET" + url: url + overrideMimeType: "text/plain; charset=x-user-defined" + onload: (xhr) -> + r = xhr.responseText + data = new Uint8Array r.length + i = 0 + while i < r.length + data[i] = r.charCodeAt i + i++ + contentType = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)?[1] + contentDisposition = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)?[1] + cb (makeBlob data, contentType, contentDisposition, url) + onerror: -> + cb null + <% } %> - file = (url, cb) -> - <% if (type === 'crx') { %> - $.ajax url, - responseType: 'blob' - onload: -> - return cb null unless @readyState is @DONE and @status is 200 - contentType = @getResponseHeader 'Content-Type' - contentDisposition = @getResponseHeader 'Content-Disposition' - cb (makeBlob @response, contentType, contentDisposition, url) - onerror: -> - cb null - <% } %> - <% if (type === 'userscript') { %> - GM_xmlhttpRequest - method: "GET" - url: url - overrideMimeType: "text/plain; charset=x-user-defined" - onload: (xhr) -> - r = xhr.responseText - data = new Uint8Array r.length - i = 0 - while i < r.length - data[i] = r.charCodeAt i - i++ - contentType = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)?[1] - contentDisposition = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)?[1] - cb (makeBlob data, contentType, contentDisposition, url) - onerror: -> - cb null - <% } %> - - {file} + json: do -> + callbacks = {} + responses = {} + (url, cb) -> + <% if (type === 'crx') { %> + $.cache url, (-> cb @response), responseType: 'json' + <% } %> + <% if (type === 'userscript') { %> + if responses[url] + cb responses[url] + return + if callbacks[url] + callbacks[url].push cb + return + callbacks[url] = [cb] + GM_xmlhttpRequest + method: "GET" + url: url + onload: (xhr) -> + response = JSON.parse xhr.responseText + cb response for cb in callbacks[url] + delete callbacks[url] + responses[url] = response + onerror: -> + delete callbacks[url] + onabort: -> + delete callbacks[url] + <% } %> diff --git a/src/General/Get.coffee b/src/General/Get.coffee index de10edafb..f983e5b9b 100755 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -132,20 +132,25 @@ Get = Get.insert post, root, context archivedPost: (boardID, postID, root, context) -> return false unless url = Redirect.to 'post', {boardID, postID} - $.cache url, - -> Get.parseArchivedPost @, boardID, postID, root, context - , - responseType: 'json' - withCredentials: url.archive.withCredentials - return true - parseArchivedPost: (req, boardID, postID, root, context) -> + if /^https:\/\//.test(URL) or location.protocol is 'http:' + $.cache url, + -> Get.parseArchivedPost @response, boardID, postID, root, context + , + responseType: 'json' + withCredentials: url.archive.withCredentials + return true + else if Conf['Allow Mixed Content from Archives'] + CrossOrigin.json url, (response) -> + Get.parseArchivedPost response, boardID, postID, root, context + return true + return false + parseArchivedPost: (data, boardID, postID, root, context) -> # In case of multiple callbacks for the same request, # don't parse the same original post more than once. if post = g.posts["#{boardID}.#{postID}"] Get.insert post, root, context return - data = req.response if data.error $.addClass root, 'warning' root.textContent = data.error diff --git a/src/Images/ImageCommon.coffee b/src/Images/ImageCommon.coffee index e5bec60b4..091e86161 100644 --- a/src/Images/ImageCommon.coffee +++ b/src/Images/ImageCommon.coffee @@ -12,7 +12,7 @@ ImageCommon = URL = Redirect.to 'file', boardID: post.board.ID filename: src[src.length - 1] - unless URL and (/^https:\/\//.test(URL) or location.protocol is 'http:') + unless URL and Redirect.securityCheck URL URL = null return cb URL if (post.isDead or post.file.isDead) and file.src.split('/')[2] is 'i.4cdn.org'