/* SPDX-License-Identifier: GPL-3.0-or-later
*
* 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 .
*
*
* 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 = '';
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 = "
";
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;
}