initial commit
This commit is contained in:
commit
b5c616ad14
6 changed files with 195 additions and 0 deletions
12
error.html
Normal file
12
error.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>error</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>error</h1>
|
||||
<p>{info}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
10
index.html
Normal file
10
index.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>gemini proxy</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p>welcome to gem.celery.eu.org</p>
|
||||
</body>
|
||||
</html>
|
134
index.js
Normal file
134
index.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
const fs = require("fs");
|
||||
const tls = require("tls");
|
||||
const http = require("http");
|
||||
|
||||
const files = {
|
||||
style: fs.readFileSync("style.css", "utf8"),
|
||||
page: fs.readFileSync("page.html", "utf8"),
|
||||
input: fs.readFileSync("input.html", "utf8"),
|
||||
index: fs.readFileSync("index.html", "utf8"),
|
||||
error: fs.readFileSync("error.html", "utf8"),
|
||||
};
|
||||
|
||||
function parse(data, url) {
|
||||
const clean = (str) => str.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<");
|
||||
let pre = false;
|
||||
let list = false;
|
||||
let html = "";
|
||||
|
||||
for(let line of data.split("\n")) {
|
||||
const header = line.match(/^(#{1,3}) */);
|
||||
const link = line.match(/^=> *(\S+) *(.+)/);
|
||||
|
||||
if(line.startsWith("*")) {
|
||||
if(!list) html += "<ul>\n";
|
||||
list = true;
|
||||
html += `\t<li>${clean(line.replace(/^\* */, ""))}</li>\n`;
|
||||
continue;
|
||||
} else if(list) {
|
||||
html += "</ul>\n";
|
||||
list = false;
|
||||
}
|
||||
|
||||
if(line.startsWith("```")) {
|
||||
const title = line.slice(3).trim();
|
||||
html += `<${pre ? "/" : ""}pre${title ? ` title="${title}"` : ""}>\n`
|
||||
pre = !pre;
|
||||
} else if(pre) {
|
||||
html += clean(line) + "\n";
|
||||
} else if(header) {
|
||||
const n = header[1].length;
|
||||
html += `<h${n}>${clean(line.replace(/^#+ */, ""))}</h${n}>\n`;
|
||||
} else if(line.startsWith(">")) {
|
||||
html += `<blockquote>${clean(line.replace(/^> */, ""))}</blockquote>\n`;
|
||||
} else if(link) {
|
||||
let loc = new URL(link[1], url);
|
||||
if (loc.protocol === "gemini:") {
|
||||
loc = `/${loc.hostname}${loc.pathname}`;
|
||||
}
|
||||
html += `<p><a href="${loc}">${clean(link[2])}</a></p>\n`;
|
||||
} else if(line) {
|
||||
html += `<p>${clean(line)}</p>\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
async function fetch(url, redirects = 5) {
|
||||
const { host, href } = url;
|
||||
const conn = tls.connect({
|
||||
host,
|
||||
port: 1965,
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let data = "";
|
||||
conn.write(`${href}\r\n`);
|
||||
conn.on("data", (d) => {
|
||||
data += d;
|
||||
if (data.length > 100 * 1024 * 1024) {
|
||||
conn.close();
|
||||
rej("response too large (the server's max is 100MiB!)");
|
||||
}
|
||||
});
|
||||
conn.on("end", () => {
|
||||
try {
|
||||
const [_, codeStr, meta] = data.match(/([0-9]{2}) (.+?)\r\n/);
|
||||
const code = parseInt(codeStr, 10);
|
||||
if (code >= 10 && code < 30) return res({ data: data.replace(/.+/, "").trim(), meta, code });
|
||||
if (code >= 30 && code < 40) return res(fetch(new URL(meta, url), redirects - 1));
|
||||
rej(`code ${code}: ${meta}`);
|
||||
} catch {
|
||||
rej("invalid response");
|
||||
}
|
||||
});
|
||||
conn.on("error", ({ code }) => {
|
||||
switch(code) {
|
||||
case "ENOTFOUND": return rej("domain not found");
|
||||
case "ECONNREFUSED": return rej("connection refused");
|
||||
case "ECONNRESET": return rej("connection reset");
|
||||
default: return rej("response errored");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
http.createServer((req, res) => {
|
||||
if (req.method === "POST") {
|
||||
req.on("data", (data) => {
|
||||
const where = req.url + data.toString().replace("input=", "?").replace(/\+/g, "%20");
|
||||
res.writeHead(301, { 'location': where }).end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.url === "/style.css") return res.writeHead(200, { 'content-type': 'text/css' }).end(files.style);
|
||||
if (req.url === "/") return res.end(files.index);
|
||||
const [host, ...path] = req.url.split("/").filter((str, i) => str || i > 0);
|
||||
if (host === "localhost") return res.writeHead(400).end(files.error.replace("{info}", "absolutely not"));
|
||||
const url = new URL(`gemini://${host}/${path.join("/")}`);
|
||||
fetch(url)
|
||||
.then(({ code, meta, data }) => {
|
||||
if (code < 20) {
|
||||
const html = files.input
|
||||
.replace("{title}", `gemini - ${url.hostname}`)
|
||||
.replace("{url}", req.url)
|
||||
.replace("{type}", code === 11 ? "password" : "text")
|
||||
.replace("{meta}", meta);
|
||||
res.writeHead(200, { 'content-type': 'text/html' }).end(html);
|
||||
} else if (meta === "text/gemini") {
|
||||
const charset = meta.replace(/.+?(;|$)/, "").trim() || "utf8";
|
||||
const html = files.page
|
||||
.replace("{title}", `gemini - ${url.hostname}`)
|
||||
.replace("{charset}", charset)
|
||||
.replace("{body}", parse(data, url));
|
||||
res.writeHead(200, { 'content-type': 'text/html' }).end(html);
|
||||
} else {
|
||||
res.writeHead(200, { 'content-type': meta }).end(data);
|
||||
}
|
||||
})
|
||||
.catch((msg) => {
|
||||
res.writeHead(400).end(files.error.replace("{info}", msg));
|
||||
});
|
||||
}).listen(8329);
|
15
input.html
Normal file
15
input.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
</head>
|
||||
<body onload="document.querySelector('input').focus()">
|
||||
<h1>{meta}</h1>
|
||||
<form method="POST" action={url}>
|
||||
<input type="{type}" name="input" style="width:100%" />
|
||||
<br /><br />
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
11
page.html
Normal file
11
page.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<meta charset="{charset}">
|
||||
</head>
|
||||
<body>
|
||||
{body}
|
||||
</body>
|
||||
</html>
|
13
style.css
Normal file
13
style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
body {
|
||||
max-width: 650px;
|
||||
margin: 0 auto;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
margin: 0 -4px;
|
||||
overflow-x: auto;
|
||||
}
|
Loading…
Reference in a new issue