UNPKG

fibjs-captcha

Version:

A Lightweight Pure JavaScript Captcha for Fibjs. No C/C++, No ImageMagick, No canvas.

208 lines (173 loc) 4.48 kB
const util = require('util') const { randomBytes } = require('crypto') const colors = require('./colors') const lt = require('./font') const SW = require('./sw') const SIZE = 5 const NDOTS = 100 const GIF_SIZE = 17646 const LETTERS = 'abcdafahijklmnopqrstuvwxyz' function urandom(size) { return new Promise((resolve, reject) => { randomBytes(size, (err, buf) => { err ? reject(err) : resolve(buf) }) }) } function random(min, max) { return Math.floor(Math.random() * (max - min)) + min } function letter(n, pos, im, swr, s1, s2) { let l = im.length let t = lt[n] let r = 200 * 16 + pos let i = r let sk1 = s1 + pos let sk2 = s2 + pos let mpos = pos let row = 0 for (let j = 0, k = t.length; j < k; j++) { let p = t[j] if (p === -101) continue if (p < 0) { if (p === -100) { r += 200 i = r sk1 = s1 + pos row++ continue } i += -p continue } if (sk1 >= 200) sk1 = sk1 % 200 let skew = Math.floor(SW[sk1] / 16) sk1 += (swr[pos + i - r] & 0x1) + 1 if (sk2 >= 200) sk2 %= 200 let skewh = Math.floor(SW[sk2] / 70) sk2 += swr[row] & 0x1 let x = i + skew * 200 + skewh mpos = Math.max(mpos, pos + i - r) if (x - l < 70 * 200) im[x] = p << 4 i++ } return mpos } function line(im, swr, s1) { for (let x = 0, sk1 = s1; x < 199; x++) { if (sk1 >= 200) sk1 %= 200 let skew = Math.floor(SW[sk1] / 16) sk1 += (swr[x] & 0x3) + 1 let i = 200 * (45 + skew) + x im[i + 0] = 0 im[i + 1] = 0 im[i + 200] = 0 im[i + 201] = 0 } } function dots(im, dr) { for (let n = 0; n < NDOTS; n++) { let v = Number(dr.readUInt32BE(n)) let i = v % (200 * 67) im[i + 0] = 0xff im[i + 1] = 0xff im[i + 2] = 0xff im[i + 200] = 0xff im[i + 201] = 0xff im[i + 202] = 0xff } } function blur(im) { for (let i = 0, y = 0; y < 68; y++) { for (let x = 0; x < 198; x++) { let c11 = im[i + 0] let c12 = im[i + 1] let c21 = im[i + 200] let c22 = im[i + 201] im[i++] = Math.floor((c11 + c12 + c21 + c22) / 4) } } } function filter(im) { const om = Buffer.alloc(70 * 200).fill(0xff) let i = 0 let o = 0 for (let y = 0; y < 70; y++) { for (let x = 4; x < 200 - 4; x++) { if (im[i] > 0xf0 && im[i + 1] < 0xf0) { om[o] = 0 om[o + 1] = 0 } else if (im[i] < 0xf0 && im[i + 1] > 0xf0) { om[o] = 0 om[o + 1] = 0 } i++ o++ } } om.copy(im) } async function captcha(size) { const rb = await urandom(size + 200 + 100 * 4 + 1 + 1) const l = Buffer.alloc(size) const swr = Buffer.alloc(200) const dr = Buffer.alloc(100 * 4) let s1 let s2 rb.copy(l, 0, 0, size) rb.copy(swr, 0, size, size + 200) rb.copy(dr, 0, size + 200, size + 200 + 100 * 4) s1 = rb.readUInt8(size + 200 + 100 * 4) s2 = rb.readUInt8(size + 200 + 100 * 4 + 1) const im = Buffer.alloc(200 * 70).fill(0xff) s1 &= 0x7f s2 &= 0x3f let p = 30 for (let n = 0; n < size; n++) { l[n] %= 25 p = letter(l[n], p, im, swr, s1, s2) l[n] = LETTERS.charCodeAt(l[n]) } line(im, swr, s1) dots(im, dr) // blur(im) // filter(im) return { im, l } } // http://brokestream.com/captcha.html function makegif(im, gif, style) { // tag ; widthxheight ; GCT:0:0:7 ; bgcolor + aspect // GCT // Image Separator // left x top // widthxheight // Flags // LZW code size let r = style === -1 ? random(0, colors.length) : style gif.fill(Buffer.from(colors[r].replace(/\n/g, ''), 'latin1'), 0, 13 + 48 + 10 + 1) let i = 0 let p = 13 + 48 + 10 + 1 for (let y = 0; y < 70; y++) { gif[p++] = 250 // Data length 5*50=250 for (let x = 0; x < 50; x++) { let a = im[i + 0] >> 4 let b = im[i + 1] >> 4 let c = im[i + 2] >> 4 let d = im[i + 3] >> 4 gif[p + 0] = 16 | (a << 5) // bbb10000 gif[p + 1] = (a >> 3) | 64 | (b << 7) // b10000xb gif[p + 2] = b >> 1 // 0000xbbb gif[p + 3] = 1 | (c << 1) // 00xbbbb1 gif[p + 4] = 4 | (d << 3) // xbbbb100 i += 4 p += 5 } } gif.fill('\x01\x11\x00;', GIF_SIZE - 4) } async function generate({ size = SIZE, style = -1 } = {}) { const gif = Buffer.alloc(GIF_SIZE) const { im, l } = await captcha(size) makegif(im, gif, style) return { buffer: gif, token: l.toString() } } module.exports = util.sync(generate, true)