Can embed any file in a PNG/WebM/GIF/JPEG and upload it to a third-party host through 4chan
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

171 lines
5.3 KiB

import { Buffer } from "buffer";
import type { EmbeddedFile } from "./main";
import type { ImageProcessor } from "./processor.worker";
import { BufferWriteStream } from "./png";
import { decodeCoom3Payload, uploadFiles } from "./utils";
const netscape = Buffer.from("!\xFF\x0BNETSCAPE2.0", 'ascii');
const magic = Buffer.from("!\xFF\x0B" + "DOOMTECH1.1", 'ascii');
const magic2 = Buffer.from("!\xFF\x0B" + "VOOMTECH1.1", 'ascii');
const magic3 = Buffer.from("!\xFF\x0B" + "ANIMEXTS1.0", 'ascii');
const read_section = (gif: Buffer, pos: number) => {
const begin = pos;
pos += 3 + gif[pos + 2];
let buf = Buffer.alloc(0);
while (pos < gif.byteLength) {
const v = gif[pos++];
buf = Buffer.concat([buf, gif.slice(pos, pos + v)]);
if (v == 0)
break;
pos += v;
}
const appname = gif.slice(begin + 3, begin + 11).toString('ascii');
return {
appname,
data: buf,
end: pos
};
};
const password = Buffer.from("NOA");
const xor = (a: Buffer, p: Buffer) => {
let n = 0;
for (let i = 0; i < a.byteLength; ++i) {
a[i] ^= p[n];
n++;
n %= p.byteLength;
}
};
const extractBuff = (gif: Buffer) => {
const field = gif.readUInt8(10);
const gcte = !!(field & (1 << 7));
let end = 13;
if (gcte) {
end += 3 * (1 << ((field & 7) + 1));
}
// skip beeg blocks
while (gif[end] == '!'.charCodeAt(0)) {
let sec = read_section(gif, end); // this section contains the size to more easily preallocate a buffer size, but you don't need to care care
if (sec.appname == "DOOMTECH") {
const ret = Buffer.alloc(sec.data.readInt32LE(0));
let ptr = 0;
do {
sec = read_section(gif, sec.end);
sec.data.copy(ret, ptr);
ptr += sec.data.byteLength;
end = sec.end;
// v wtf did i mean by this
} while (sec.appname == "DOOMTECH" && gif[end] == '!'.charCodeAt(0));
return decodeCoom3Payload(ret);
}
const cond = () => sec.appname == "VOOMTECH" || sec.appname == 'ANIMEXTS';
if (cond()) {
const ret = Buffer.alloc(sec.data.readInt32LE(0));
let ptr = 0;
do {
sec = read_section(gif, sec.end);
sec.data.copy(ret, ptr);
ptr += sec.data.byteLength;
end = sec.end;
} while (cond() && gif[end] == '!'.charCodeAt(0));
xor(ret, password);
return decodeCoom3Payload(ret);
}
end = sec.end;
}
throw new Error("Shouldn't happen");
// metadata ended, nothing...
};
const extract = extractBuff;
const write_data = async (writer: WritableStreamDefaultWriter<Buffer>, inj: Buffer) => {
await writer.write(magic3);
const byte = Buffer.from([0]);
let size = inj.byteLength;
let ws;
let offset = 0;
while (size != 0) {
ws = size >= 255 ? 255 : size;
byte.writeUInt8(ws, 0);
await writer.write(byte);
await writer.write(inj.slice(offset, offset + ws));
size -= ws;
offset += ws;
}
byte.writeUInt8(0, 0);
await writer.write(byte);
};
const write_embedding = async (writer: WritableStreamDefaultWriter<Buffer>, inj: Buffer) => {
const b = Buffer.alloc(4);
b.writeInt32LE(inj.byteLength, 0);
await write_data(writer, b);
let size = inj.byteLength;
let offset = 0;
while (size != 0) {
const ws = size >= (3 << 13) ? (3 << 13) : size;
await write_data(writer, inj.slice(offset, offset + ws));
offset += ws;
size -= ws;
}
};
const inject = async (container: File, links: string[]) => {
const [writestream, extract] = BufferWriteStream();
const writer = writestream.getWriter();
const inj = Buffer.from(links.join(' '));
xor(inj, password);
const contbuff = Buffer.from(await container.arrayBuffer());
const field = contbuff.readUInt8(10);
const gcte = !!(field & (1 << 0x7));
let endo = 13;
if (gcte)
endo += 3 * (1 << ((field & 7) + 1));
if (netscape.compare(contbuff, endo, endo + netscape.byteLength) == 0)
endo += 19;
await writer.write(contbuff.slice(0, endo));
await write_embedding(writer, Buffer.from(inj));
await writer.write(contbuff.slice(endo));
return extract();
};
const has_embed = (gif: Buffer) => {
const field = gif.readUInt8(10);
const gcte = !!(field & (1 << 7));
let end = 13;
if (gcte) {
end += 3 * (1 << ((field & 7) + 1));
}
// skip beeg blocks
while (end < gif.byteLength && gif.readUInt8(end) == '!'.charCodeAt(0)) {
if ([magic, magic2, magic3].every(m => m.compare(gif, end, end + m.byteLength) != 0)) {
end += 3 + gif.readUInt8(end + 2);
// eslint-disable-next-line no-constant-condition
while (true) { // skip sub blocks
const v = gif.readUInt8(end++);
if (!v)
break;
end += v;
}
} else {
return true;
}
}
if (end >= gif.byteLength)
return; // Don't know yet, need more to decide.
return false; // no more extension blocks, so definite no
};
export default {
extract,
has_embed,
inject,
match: fn => !!fn.match(/\.gif$/)
} as ImageProcessor;