/* SPDX-License-Identifier: LicenseRef-GPL-3.0-or-later-WITH-js-exceptions * * Part of Hacktcha, a free/libre front-end for reCAPTCHA for use with Haketilo. * * Copyright (C) 2022 Wojtek Kosior * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * As additional permission under GNU GPL version 3 section 7, you * may distribute forms of that code without the copy of the GNU * GPL normally required by section 4, provided you include this * license notice and, in case of non-source distribution, a URL * through which recipients can access the Corresponding Source. * If you modify file(s) with this exception, you may extend this * exception to your version of the file(s), but you are not * obligated to do so. If you do not wish to do so, delete this * exception statement from your version. * * As a special exception to the GPL, any HTML file which merely * makes function calls to this code, and for that purpose * includes it by reference shall be deemed a separate work for * copyright law purposes. If you modify this code, you may extend * this exception to your version of the code, but you are not * obligated to do so. If you do not wish to do so, delete this * exception statement from your version. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * * I, Wojtek Kosior, thereby promise not to sue for violation of this file's * license. Although I request that you do not make use of this code in a * proprietary program, I am not going to enforce this in court. */ /* Load CORS bypass library first. */ "use strict"; var HCHA = {}; /* First, define some helper functions under the "HCHA" napesmace. */ /* * reCAPTCHA uses a less common variant of base64 which we produce by slightly * modifying the output of JavaScript's native base64 encoding routine. */ HCHA._rc_base64 = function(data) { return btoa(data) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", "."); } HCHA._get_rc_version = async function() { const url = "https://google.com/recaptcha/api.js"; const resp = await HKT.fetch(url); const text = await resp.text(); const reg = /https:\/\/www.gstatic.com\/recaptcha\/releases\/([^/]*)/; const match = reg.exec(text); if (!match) throw new Error(`URL '${url}' returned invalid response.`); return match[1]; } HCHA._make_default_site_url = function() { const url_obj = new URL(window.location.href); let port = url_obj.port; if (url_obj.protocol === "http:" && port === "80") port = ""; else if (url_obj.protocol === "https:" && port === "") port = "443"; return url_obj.origin + (port && `:${port}`); } HCHA._make_anchor_frame_url = function(site_key, site_url, rc_version) { const url = new URL("https://google.com/recaptcha/api2/anchor"); url.search = new URLSearchParams({ "ar": "1", "k": site_key, "co": HCHA._rc_base64(site_url), "hl": "en", "v": rc_version, "size": "normal", "sa": "action" }); return url; } HCHA._style_contained_element = function(elem) { elem.style.width = "302px"; if (elem.tagName === "IFRAME") { elem.style.borderStyle = "none"; elem.style.height = "422px"; } else { elem.style.textAlign = "center"; elem.style.padding = "10px"; } } HCHA._spawn_frame = function(container, site_key, site_url, rc_version) { container.innerHTML = '<iframe scrolling="no" frameborder="0"></iframe>'; HCHA._style_contained_element(container.firstElementChild); container.firstElementChild.src = HCHA._make_anchor_frame_url(site_key, site_url, rc_version); } HCHA._show_msg_and_wait = async function(container, button_text, paragraph_text=null) { container.innerHTML = "<div><p></p><button></button></div>"; HCHA._style_contained_element(container.firstElementChild); const paragraph = container.querySelector("p"); if (paragraph_text === null) paragraph.remove(); else paragraph.textContent = paragraph_text; const button = container.querySelector("button"); button.textContent = button_text; await new Promise(cb => button.onclick = e => { e.preventDefault(); cb(); }); } HCHA._show_success_info = async function(container) { const msg = "CAPTCHA completed successfully. The token shall be valid for 2 minutes."; await HCHA._show_msg_and_wait(container, "OK", msg); container.innerHTML = ""; } HCHA._main_loop = async function*(container, site_key, site_url) { const rc_version = await HCHA._get_rc_version(); while (true) { HCHA._spawn_frame(container, site_key, site_url, rc_version); let event, resolve; const prom = new Promise(cb => resolve = cb); window.addEventListener("message", resolve, {once: true}); event = await prom; if (event.origin !== "https://google.com") { console.warn("Received message from unexepected origin!", event); } else if (event.data === "recreate_frame") { HCHA._spawn_frame(container, site_key, site_url, rc_version); } else if (typeof event.data !== "string") { console.error("Received token is not a string!", event); } else { HCHA._show_success_info(container); yield event.data; await new Promise(cb => setTimeout(cb, 120000)); yield null; await HCHA._show_msg_and_wait(container, "Solve reCAPTCHA again", "Reached token timeout."); } } } /* This library's external API consists of the run() function defined below. */ HCHA.run = async function*(container, site_key, site_url, start_immediately=false) { site_url = site_url || HCHA._make_default_site_url(); if (!start_immediately) await HCHA._show_msg_and_wait(container, "Start solving reCAPTCHA"); for await (const value of HCHA._main_loop(container, site_key, site_url)) yield value; }