diff options
author | Chris Xiong <chirs241097@gmail.com> | 2018-10-11 18:39:34 +0800 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2018-10-11 18:39:34 +0800 |
commit | 779aaa764cb26f02da56e60bb903afc9442f5f1c (patch) | |
tree | 33b6a23f1e0fd2516bd16d5cb54c9619a7186d4d /generator | |
download | sbs-779aaa764cb26f02da56e60bb903afc9442f5f1c.tar.xz |
Initial commit.
Diffstat (limited to 'generator')
-rw-r--r-- | generator/listrenderer.js | 68 | ||||
-rw-r--r-- | generator/main.js | 86 | ||||
-rw-r--r-- | generator/postrenderer.js | 161 | ||||
-rw-r--r-- | generator/scanner.js | 53 | ||||
-rw-r--r-- | generator/sha256.js | 174 |
5 files changed, 542 insertions, 0 deletions
diff --git a/generator/listrenderer.js b/generator/listrenderer.js new file mode 100644 index 0000000..fd58b3a --- /dev/null +++ b/generator/listrenderer.js @@ -0,0 +1,68 @@ +module.exports={ + render:function(outf,pginfo,posts){return _render(outf,pginfo,posts);}, + set_template:function(tmplf){return _set_template(tmplf);} +}; +const fs=require('fs'); +const jsdom=require('jsdom'); +const path=require('path'); +let template_file=''; +let tmplcont=''; + +function _set_template(tmplf) +{ + template_file=tmplf; + tmplcont=fs.readFileSync(template_file,'utf8'); +} + +async function _render(outf,pginfo,posts) +{ + const tr=new jsdom.JSDOM(tmplcont); + const trd=tr.window.document; + + const tags_list=trd.getElementById('tagslist'); + const posts_list=trd.getElementById('postslist'); + const atag_template=trd.getElementById('active_taglist_item_template'); + const tag_template=trd.getElementById('taglist_item_template'); + const pitm_template=trd.getElementById('post_item_template'); + + for(let i of pginfo.tags) + { + const newnode=(i==pginfo.atag?atag_template:tag_template).cloneNode(true); + newnode.id=''; + const a=newnode.querySelector('a'); + a.innerHTML=i; + a.href=`/blog/list/${i}`; + tags_list.appendChild(newnode); + } + + for(let i of posts) + { + let newnode=pitm_template.cloneNode(true); + newnode.id=''; + const mdate=new Date(i.mdate); + const mdstr=`${mdate.getFullYear()}-${(mdate.getMonth()+1+'').padStart(2,'0')}-${(mdate.getDate()+'').padStart(2,'0')}` + newnode.querySelector('#title').innerHTML=i.title; + newnode.querySelector('#title').href=`/blog/post/${i.file}.html`; + newnode.querySelector('#tags').innerHTML='#'+i.tags.split(',').join(' #'); + newnode.querySelector('#time').innerHTML=`${i.date}/${mdstr}`; + newnode.querySelector('#title').id= + newnode.querySelector('#tags').id= + newnode.querySelector('#time').id=''; + posts_list.appendChild(newnode); + } + + trd.getElementById('page').innerHTML=`${pginfo.cp+1}/${pginfo.pc}`; + trd.getElementById('prepage').href=pginfo.cp>0? + `/blog/list/${pginfo.atag?pginfo.atag+'/':''}${pginfo.cp-1}.html` + :'#'; + trd.getElementById('nexpage').href=pginfo.cp<pginfo.pc-1? + `/blog/list/${pginfo.atag?pginfo.atag+'/':''}${pginfo.cp+1}.html` + :'#'; + + atag_template.parentNode.removeChild(atag_template); + tag_template.parentNode.removeChild(tag_template); + pitm_template.parentNode.removeChild(pitm_template); + + fs.writeFileSync(outf,trd.documentElement.outerHTML,'utf8'); + return outf; +} diff --git a/generator/main.js b/generator/main.js new file mode 100644 index 0000000..0d86ca5 --- /dev/null +++ b/generator/main.js @@ -0,0 +1,86 @@ +const content_dir='../content'; +const template_dir='../templates'; +const dest_dir='../generated'; +const posts_per_listpage=5; + +const fs=require('fs'); +const path=require('path'); + +const scanner=require('./scanner'); +const list=scanner.scan(content_dir,dest_dir); +const tags=scanner.build_list_index(); +const taglist=Object.keys(tags).sort(); +const force=process.argv.indexOf('--force')!=-1; + +const postrenderer=require('./postrenderer'); +postrenderer.set_template(path.join(template_dir,'post_template')); + +function ensure_dir(p) +{ + let needcreation=false; + try{ + if(!fs.statSync(p).isDirectory()) + { + needcreation=true; + fs.unlinkSync(p); + } + }catch(e){needcreation=true;} + if(needcreation)fs.mkdirSync(p); + if(!fs.statSync(p).isDirectory())throw 'shit'; +} + +const post_dir=path.join(dest_dir,'post'); +ensure_dir(post_dir); +for(let j=0;j<list.length;++j){ +const i=list[j]; +if(i.needsupdate||force) + postrenderer.render( + path.join(content_dir,`${i.file}.txt`), + path.join(post_dir,`${i.file}.html`), + j?list[j-1].file:undefined, + j<list.length-1?list[j+1].file:undefined + ) + .then((r)=>{console.log(`rendered: ${r}`);}) +} + +const listrenderer=require('./listrenderer'); +listrenderer.set_template(path.join(template_dir,'list_template')); + +const list_dir=path.join(dest_dir,'list'); +const ppp=posts_per_listpage; +ensure_dir(list_dir); +let pc=Math.floor(list.length/ppp)+(list.length%ppp!=0); +for(let i=0;i<pc;++i) +{ + const cl=list.slice(i*ppp,Math.min((i+1)*ppp,list.length)); + listrenderer.render( + path.join(list_dir,`${i}.html`), + {atag:'',tags:taglist,cp:i,pc:pc}, + cl + ); +} +try{ +fs.unlinkSync(path.join(list_dir,'index.html')); +}catch(e){}; +fs.symlinkSync('0.html',path.join(list_dir,'index.html')); + +for(let i of taglist) +if(tags[i].needsupdate||force) +{ + const tl_dir=path.join(list_dir,i); + ensure_dir(tl_dir); + let pc=Math.floor(tags[i].posts.length/ppp)+(tags[i].posts.length%ppp!=0); + for(let j=0;j<pc;++j) + { + const cl=tags[i].posts.slice(j*ppp,Math.min((j+1)*ppp,tags[i].posts.length)); + listrenderer.render( + path.join(tl_dir,`${j}.html`), + {atag:i,tags:taglist,cp:j,pc:pc}, + cl + ); + } + try{ + fs.unlinkSync(path.join(tl_dir,'index.html')); + }catch(e){}; + fs.symlinkSync('0.html',path.join(tl_dir,'index.html')); +} diff --git a/generator/postrenderer.js b/generator/postrenderer.js new file mode 100644 index 0000000..729c4df --- /dev/null +++ b/generator/postrenderer.js @@ -0,0 +1,161 @@ +module.exports={ + render:function(inf,outf,np,pp){return _render(inf,outf,np,pp);}, + set_template:function(tmplf){return _set_template(tmplf);} +}; +const fs=require('fs'); +const jsdom=require('jsdom'); +const path=require('path'); +const aes=require('aes-js'); +const scrypt=require('scrypt-js'); +const btoa=require('btoa'); +const sha256=require('./sha256').sha256; +const spawn=require('child_process').spawn; +let template_file=''; +let tmplcont=''; +let tocid; +let headerlist; + +function _dfs(doc,el,le,p) +{ + var e=doc.createElement('li'); + e.innerHTML=`<a class="toctarg" href="#tocanch${tocid}">${el.innerHTML}</a>`; + le.appendChild(e); + el.id='tocanch'+(tocid++); + el.classList.add('tvis'); + var che=null,i; + for(i=p+1;i<headerlist.length;) + { + if(headerlist[i].tagName<=el.tagName)break; + if(headerlist[i].classList.contains('notoc'))continue; + if(che===null) + { + var te=doc.createElement('li'); + che=doc.createElement('ul'); + che.classList.add('tocnode'); + te.appendChild(che); + le.appendChild(te); + } + i=_dfs(doc,headerlist[i],che,i); + } + return i; +} +function footnoter(doc) +{ + var footnotes=doc.body.getElementsByTagName("footnote"); + let starting=doc.getElementById("notediv").children.length; + for(var i=0;i<footnotes.length;++i) + { + var s=footnotes[i].innerHTML; + footnotes[i].innerHTML=""; + var a=doc.createElement("a"); + a.setAttribute("id","n"+(starting+i+1)); + a.setAttribute("href","#note"+(starting+i+1)); + a.setAttribute("class","note"); + a.innerHTML="["+(starting+i+1)+"]"; + footnotes[i].parentNode.insertBefore(a,footnotes[i]); + var span=doc.createElement("span"); + span.setAttribute("class","TText"); + span.innerHTML="<a id=\"note"+(starting+i+1)+"\" href=\"#n"+(starting+i+1)+"\">["+(starting+i+1)+"]</a>: "+s+"<br>"; + doc.getElementById("notediv").appendChild(span); + } + starting+=footnotes.length; + while(footnotes.length)footnotes[0].remove(); +} +async function encrypt(doc) +{ + let enid=0; + for(let i of doc.querySelectorAll('encrypted')) + { + const key=i.getAttribute('key'); + i.removeAttribute('key'); + const contu8=aes.utils.utf8.toBytes(i.innerHTML); + const conths=sha256.hash(aes.utils.hex.fromBytes(contu8),{msgFormat:'hex-bytes'}); + const keyu8=aes.utils.utf8.toBytes(key); + const saltu8=aes.utils.utf8.toBytes('hellwhymustiaddsalttothiscrap'); + const enckey=await new Promise( + (resolv,rej)=> + {scrypt(keyu8,saltu8,1024,16,2,32,(e,p,k)=>{e?rej(e):k?resolv(k):undefined;});} + ); + const ctr=new aes.ModeOfOperation.ctr(enckey); + const enccont=ctr.encrypt(contu8); + let encconts='';for(let i=0;i<enccont.length;++i)encconts+=String.fromCharCode(enccont[i]); + encconts=btoa(encconts); + i.setAttribute('hash',conths); + i.setAttribute('encont',encconts); + i.setAttribute('enid',enid); + i.innerHTML= +`This section is encrypted. Click <a href="javascript:void(0)" onclick="decryptui(${enid})">here</a> +to decrypt. <noscript>Note that you need JavaScript enabled for decryption.</noscript>`; + ++enid; + } +} +function _set_template(tmplf) +{ + template_file=tmplf; + tmplcont=fs.readFileSync(template_file,'utf8'); +} +async function _render(inf,outf,np,pp) +{ + const postcont=fs.readFileSync(inf,'utf8'); + const tr=new jsdom.JSDOM(tmplcont); + const trd=tr.window.document; + const cont=fs.readFileSync(inf,'utf8'); + const contsplit=cont.split('\n'); + const meta=contsplit.splice(0,4); + const preproc=meta[3].trim(); + trd.getElementById('title').innerHTML= + trd.getElementById('titleh').innerHTML=meta[0].trim(); + trd.getElementById('datetags').innerHTML=`${meta[1].trim()}<br>#${meta[2].split(',').join(' #')}`; + if(preproc.length) + { + const ppargs=preproc.split(' '); + const ppcmd=ppargs.splice(0,1); + const pp=spawn(ppcmd[0],ppargs,{maxBuffer:1048576}); + let out=''; + pp.stdout.setEncoding('utf8'); + pp.stdout.on('data',(d)=>{out+=d;}); + pp.stdin.write(contsplit.join('\n'),'utf8'); + const ppp=new Promise( + (resolv,rej)=> + { + pp.on('close',(r)=>{if(r)rej(r);resolv(out);}); + } + ); + pp.stdin.end(); + trd.getElementById('article').innerHTML=await ppp; + } + else + trd.getElementById('article').innerHTML=contsplit.join('\n'); + + //Encryption + await encrypt(trd); + + //TOC + const l=trd.getElementById('article').querySelectorAll('h2,h3,h4,h5,h6'); + const tocroot=trd.getElementById('tocroot'); + tocid=0; + headerlist=[]; + for(let i of l) + if(!i.classList.contains('notoc'))headerlist.push(i); + for(let i=0;i<headerlist.length;)i=_dfs(trd,headerlist[i],tocroot,i); + if(!tocroot.children.length)trd.getElementById('tocouter').style.display='none'; + + //Footnotes + footnoter(trd); + + //Tag list + const tgs=meta[2].split(','); + for(let i=0;i<tgs.length;++i) + { + let l=trd.createElement('li'); + l.innerHTML=`<a href="/blog/list/${tgs[i]}/">${tgs[i]}</a>`; + trd.getElementById('tagslist').appendChild(l); + } + + //neighboring posts + if(pp)trd.getElementById('prevp').href=`${pp}.html`; + if(np)trd.getElementById('nextp').href=`${np}.html`; + + fs.writeFileSync(outf,trd.documentElement.outerHTML,'utf8'); + return outf; +} diff --git a/generator/scanner.js b/generator/scanner.js new file mode 100644 index 0000000..0817cdb --- /dev/null +++ b/generator/scanner.js @@ -0,0 +1,53 @@ +module.exports={ + scan:function(s,d){return _scan(s,d);}, + build_list_index:function(){return _build_list_index();} +}; +const fs=require('fs'); +const path=require('path'); +const list=[]; +const tags=[]; +function _scan(s,dst) +{ + list.splice(0); + d=fs.readdirSync(s).reverse(); + pdst=path.join(dst,'post'); + let poste=true; + try{ + st=fs.statSync(pdst); + if(!st.isDirectory)throw 'shit'; + }catch(e){poste=false;} + for(let i of d) + if(i.endsWith('.txt')) + { + const cont=fs.readFileSync(path.join(s,i),'utf8'); + const smodt=fs.statSync(path.join(s,i)).mtimeMs; + let dmodt=0; + try{ + dmodt=fs.statSync(path.join(pdst,i.substring(0,i.length-4)+'.html')).mtimeMs; + }catch(e){}; + contsplit=cont.split('\n'); + if(contsplit.length<4)continue; + if(contsplit[1].indexOf('WIP')!=-1)continue; + list.push({ + file:i.substring(0,i.length-4), + title:contsplit[0].trim(), + date:contsplit[1].trim(), + tags:contsplit[2].trim(), + mdate:smodt, + needsupdate:dmodt<smodt + }); + + } + return list; +} +function _build_list_index() +{ + for(let i of list) + for(let tg of i.tags.split(',')) + { + if(!tags[tg])tags[tg]={posts:[],needsupdate:false}; + tags[tg].posts.push(i); + tags[tg].needsupdate|=i.needsupdate; + } + return tags; +} diff --git a/generator/sha256.js b/generator/sha256.js new file mode 100644 index 0000000..f6c59d8 --- /dev/null +++ b/generator/sha256.js @@ -0,0 +1,174 @@ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* SHA-256 (FIPS 180-4) implementation in JavaScript (c) Chris Veness 2002-2017 */ +/* MIT Licence */ +/* www.movable-type.co.uk/scripts/sha256.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +'use strict'; + +/** + * SHA-256 hash function reference implementation. + * + * This is an annotated direct implementation of FIPS 180-4, without any optimisations. It is + * intended to aid understanding of the algorithm rather than for production use. + * + * While it could be used where performance is not critical, I would recommend using the ‘Web + * Cryptography API’ (developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest) for the browser, + * or the ‘crypto’ library (nodejs.org/api/crypto.html#crypto_class_hash) in Node.js. + * + * See csrc.nist.gov/groups/ST/toolkit/secure_hashing.html + * csrc.nist.gov/groups/ST/toolkit/examples.html + */ +class Sha256 { + + /** + * Generates SHA-256 hash of string. + * + * @param {string} msg - (Unicode) string to be hashed. + * @param {Object} [options] + * @param {string} [options.msgFormat=string] - Message format: 'string' for JavaScript string + * (gets converted to UTF-8 for hashing); 'hex-bytes' for string of hex bytes ('616263' ≡ 'abc') . + * @param {string} [options.outFormat=hex] - Output format: 'hex' for string of contiguous + * hex bytes; 'hex-w' for grouping hex bytes into groups of (4 byte / 8 character) words. + * @returns {string} Hash of msg as hex character string. + */ + static hash(msg, options) { + const defaults = { msgFormat: 'string', outFormat: 'hex' }; + const opt = Object.assign(defaults, options); + + // note use throughout this routine of 'n >>> 0' to coerce Number 'n' to unsigned 32-bit integer + + switch (opt.msgFormat) { + default: // default is to convert string to UTF-8, as SHA only deals with byte-streams + case 'string': msg = utf8Encode(msg); break; + case 'hex-bytes':msg = hexBytesToString(msg); break; // mostly for running tests + } + + // constants [§4.2.2] + const K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; + + // initial hash value [§5.3.3] + const H = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; + + // PREPROCESSING [§6.2.1] + + msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1] + + // convert string msg into 512-bit blocks (array of 16 32-bit integers) [§5.2.1] + const l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length + const N = Math.ceil(l/16); // number of 16-integer (512-bit) blocks required to hold 'l' ints + const M = new Array(N); // message M is N×16 array of 32-bit integers + + for (let i=0; i<N; i++) { + M[i] = new Array(16); + for (let j=0; j<16; j++) { // encode 4 chars per integer (64 per block), big-endian encoding + M[i][j] = (msg.charCodeAt(i*64+j*4+0)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) + | (msg.charCodeAt(i*64+j*4+2)<< 8) | (msg.charCodeAt(i*64+j*4+3)<< 0); + } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0 + } + // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1] + // note: most significant word would be (len-1)*8 >>> 32, but since JS converts + // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators + const lenHi = ((msg.length-1)*8) / Math.pow(2, 32); + const lenLo = ((msg.length-1)*8) >>> 0; + M[N-1][14] = Math.floor(lenHi); + M[N-1][15] = lenLo; + + + // HASH COMPUTATION [§6.2.2] + + for (let i=0; i<N; i++) { + const W = new Array(64); + + // 1 - prepare message schedule 'W' + for (let t=0; t<16; t++) W[t] = M[i][t]; + for (let t=16; t<64; t++) { + W[t] = (Sha256.σ1(W[t-2]) + W[t-7] + Sha256.σ0(W[t-15]) + W[t-16]) >>> 0; + } + + // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value + let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7]; + + // 3 - main loop (note '>>> 0' for 'addition modulo 2^32') + for (let t=0; t<64; t++) { + const T1 = h + Sha256.Σ1(e) + Sha256.Ch(e, f, g) + K[t] + W[t]; + const T2 = Sha256.Σ0(a) + Sha256.Maj(a, b, c); + h = g; + g = f; + f = e; + e = (d + T1) >>> 0; + d = c; + c = b; + b = a; + a = (T1 + T2) >>> 0; + } + + // 4 - compute the new intermediate hash value (note '>>> 0' for 'addition modulo 2^32') + H[0] = (H[0]+a) >>> 0; + H[1] = (H[1]+b) >>> 0; + H[2] = (H[2]+c) >>> 0; + H[3] = (H[3]+d) >>> 0; + H[4] = (H[4]+e) >>> 0; + H[5] = (H[5]+f) >>> 0; + H[6] = (H[6]+g) >>> 0; + H[7] = (H[7]+h) >>> 0; + } + + // convert H0..H7 to hex strings (with leading zeros) + for (let h=0; h<H.length; h++) H[h] = ('00000000'+H[h].toString(16)).slice(-8); + + // concatenate H0..H7, with separator if required + const separator = opt.outFormat=='hex-w' ? ' ' : ''; + + return H.join(separator); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + function utf8Encode(str) { + try { + return new TextEncoder().encode(str, 'utf-8').reduce((prev, curr) => prev + String.fromCharCode(curr), ''); + } catch (e) { // no TextEncoder available? + return unescape(encodeURIComponent(str)); // monsur.hossa.in/2012/07/20/utf-8-in-javascript.html + } + } + + function hexBytesToString(hexStr) { // convert string of hex numbers to a string of chars (eg '616263' -> 'abc'). + const str = hexStr.replace(' ', ''); // allow space-separated groups + return str=='' ? '' : str.match(/.{2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join(''); + } + } + + + + /** + * Rotates right (circular right shift) value x by n positions [§3.2.4]. + * @private + */ + static ROTR(n, x) { + return (x >>> n) | (x << (32-n)); + } + + + /** + * Logical functions [§4.1.2]. + * @private + */ + static Σ0(x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); } + static Σ1(x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); } + static σ0(x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x>>>3); } + static σ1(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); } + static Ch(x, y, z) { return (x & y) ^ (~x & z); } // 'choice' + static Maj(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); } // 'majority' + +} + +module.exports.sha256 = Sha256; |