105 lines
2.3 KiB
TypeScript
105 lines
2.3 KiB
TypeScript
// Gemini v0.11.0 client
|
|
|
|
import {
|
|
BufReader,
|
|
readLines,
|
|
} from "https://deno.land/std@0.74.0/io/bufio.ts";
|
|
|
|
const content_buffer_size = 512;
|
|
const gemini_port = "1965";
|
|
|
|
interface Response {
|
|
status: string;
|
|
meta: string;
|
|
content?: Uint8Array;
|
|
}
|
|
|
|
async function fetchGemini(
|
|
url: URL,
|
|
redirect_count: number = 5,
|
|
): Promise<Response> {
|
|
if (url.protocol != "gemini:") {
|
|
throw new Error("Unsupported protocol");
|
|
}
|
|
|
|
const conn = await Deno.connectTls({
|
|
hostname: url.hostname,
|
|
port: parseInt(url.port != "" ? url.port : gemini_port),
|
|
});
|
|
|
|
try {
|
|
const request = new TextEncoder().encode(url.href + "\r\n");
|
|
if ((await conn.write(request)) != request.length) {
|
|
throw new Error("Write failure");
|
|
}
|
|
|
|
const reader = new BufReader(conn);
|
|
const header = await reader.readString("\n");
|
|
|
|
if (header == null) {
|
|
throw new Error("Unexpected end of stream");
|
|
}
|
|
|
|
const [_, status, meta] = header.match(/(\d+)\s+(.*)/) ?? ["", "", ""];
|
|
|
|
if (
|
|
status == undefined ||
|
|
status.length != 2 ||
|
|
(meta != undefined && meta.length > 1024)
|
|
) {
|
|
throw new Error("Received malformed response");
|
|
}
|
|
|
|
switch (status[0]) {
|
|
case "2":
|
|
break;
|
|
case "3":
|
|
if (redirect_count == 0) {
|
|
throw new Error("Too many redirects");
|
|
}
|
|
|
|
return fetchGemini(new URL(meta, url), redirect_count - 1);
|
|
default:
|
|
return {
|
|
status,
|
|
meta,
|
|
};
|
|
}
|
|
|
|
// Read remaining stream
|
|
|
|
let total = new Uint8Array(0);
|
|
let current = new Uint8Array(content_buffer_size);
|
|
for (
|
|
let count = await reader.read(current);
|
|
count != null;
|
|
count = await reader.read(current)
|
|
) {
|
|
let new_total = new Uint8Array(total.length + count);
|
|
new_total.set(total, 0);
|
|
new_total.set(current.slice(0, count), total.length);
|
|
total = new_total;
|
|
}
|
|
|
|
return {
|
|
status,
|
|
meta,
|
|
content: total,
|
|
};
|
|
} finally {
|
|
conn.close();
|
|
}
|
|
}
|
|
|
|
if (Deno.args.length != 1) {
|
|
console.error("Invalid number of arguments");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const res = await fetchGemini(new URL(Deno.args[0]));
|
|
|
|
console.log("Status " + res.status + " " + res.meta);
|
|
if (res.content != undefined) {
|
|
console.log(new TextDecoder().decode(res.content));
|
|
}
|