diff --git a/README.md b/README.md index 37863ea..feac18f 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ And I use this name to hope the merits from this application will be dedicated t * A visible signature can be placed on multiple pages. (In the same position) * Sign a pdf and set [DocMDP](https://github.com/zboris12/zgapdfsigner/wiki/API#note). * Add a new signature to a pdf if it has been signed already. (An incremental update) -* Add a document timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser) -* Sign a pdf with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser) -* Enable signature's [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser) +* Add a document timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:) +* Sign a pdf with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:) +* Enable signature's [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:) * Set password protection to a pdf. Supported algorithms: * 40bit RC4 Encryption * 128bit RC4 Encryption @@ -32,7 +32,9 @@ And I use this name to hope the merits from this application will be dedicated t ## About signing with [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note) and [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note) Because of the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions in web browser, -signing with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note) or enabling [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note) can only be used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/). +signing with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note) or enabling [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note) can only be used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/). +:sunflower: However, if you can avoid the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions +by creating your own service or providing a reverse proxy server, these features are also available in web browser. ## The Dependencies diff --git a/build.sh b/build.sh index 4e1e7bb..7026e21 100755 --- a/build.sh +++ b/build.sh @@ -9,6 +9,8 @@ else mkdir ${OUTFLDR} fi +VER=$(sed -n -r "s/^.*\"version\": ?\"([0-9.]+)\".*$/\1/p" package.json) + GCCOPT="--charset UTF-8 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE" GCCEXT="--externs closure/google-ext.js --externs closure/forge-ext.js --externs closure/pdflib-ext.js --externs closure/zb-externs.js" jss="" @@ -20,7 +22,12 @@ do if [ "$c" != "#" ] then outf="${OUTFLDR}/_${js}" - sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" "lib/${js}" > "${outf}" + if [ "${js}" = "zgaindex.js" ] + then + sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" -e "s/ver: \"\"/ver: \"${VER}\"/" "lib/${js}" > "${outf}" + else + sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" "lib/${js}" > "${outf}" + fi if [ $? -eq 0 ] then echo "Created js file: ${outf}" @@ -32,6 +39,7 @@ do fi fi done <} z + */ +function supplyZgaUrlFetch(z){ + +//Only for nodejs Start// +const m_urlparser = require("url"); +const m_h = { + "http:": require('follow-redirects').http, + "https:": require('follow-redirects').https, +}; +// @type {boolean} +z.isNode = function(){return this === globalThis.global;}(); +//Only for nodejs End// + +/** @type {boolean} */ +z.isBrowser = function(){return this === globalThis.self;}(); + +/** + * @param {string} url + * @param {UrlFetchParams} params + * @return {Promise} + */ +z.urlFetch = function(url, params){ + +//Only for nodejs Start// + if(z.isNode){ + return new Promise(function(resolve, reject){ + // @type {URL} + var opts = m_urlparser.parse(url); + var http = m_h[opts.protocol]; + // @type {string|Buffer} + var dat = null; + var encoding = undefined; + opts.method = "GET"; + if(params){ + if(params.payload instanceof Buffer){ + dat = params.payload; + }else if(params.payload instanceof Uint8Array){ + dat = Buffer.from(params.payload.buffer); + }else if(params.payload instanceof ArrayBuffer){ + dat = Buffer.from(params.payload); + }else{ + dat = params.payload; + encoding = "binary"; + } + if(params.headers){ + opts.headers = params.headers; + } + if(params.method){ + opts.method = params.method; + } + if(params.validateHttpsCertificates === false){ + opts.rejectUnauthorized = false; + } + } + + // @type {http.ClientRequest} + var hreq = http.request(opts, function(a_res){ // @type {http.IncomingMessage} a_res + if(a_res.statusCode !== 200){ + var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode); + a_res.resume(); + throw a_err; + } + // @type {Array} + var a_bufs = []; + var a_bufs_len = 0; + a_res.on("data", function(b_chunk){ // @type {Buffer} b_chunk + a_bufs.push(b_chunk); + a_bufs_len += b_chunk.length; + }); + a_res.on("end", function(){ + // @type {Buffer} + var b_bdat = Buffer.concat(a_bufs, a_bufs_len); + resolve(b_bdat); + }); + }); + hreq.on("error", function(a_err){ + throw a_err; + }); + hreq.end(dat, encoding); + }); + } +//Only for nodejs End// + + // Google Apps Script + if(globalThis.UrlFetchApp){ + return new Promise(function(resolve){ + /** @type {GBlob} */ + var tblob = UrlFetchApp.fetch(url, params).getBlob(); + resolve(new Uint8Array(tblob.getBytes())); + }); + } + + // browser + if(z.isBrowser && globalThis.self.fetch){ + /** + * @return {Promise} + */ + var func = async function(){ + /** @type {!RequestInit} */ + var reqinf = { + method: "GET", + redirect: "follow", + }; + if(params){ + if(params.payload){ + reqinf.body = params.payload; + } + if(params.headers){ + reqinf.headers = params.headers; + } + if(params.method){ + reqinf.method = params.method; + } + } + /** @type {Response} */ + var resp = await fetch(url, reqinf); + if(resp.ok){ + /** @type {ArrayBuffer} */ + var abdat = await resp.arrayBuffer(); + return new Uint8Array(abdat); + }else{ + /** @type {string} */ + var msg = await resp.text(); + throw new Error("Fetch failed." + resp.status + ": " + msg); + } + }; + return func(); + } + return null; +}; + +} + +//Only for nodejs Start// +if(typeof exports === "object" && typeof module !== "undefined"){ + module.exports = supplyZgaUrlFetch; +} +//Only for nodejs End// diff --git a/lib/zgaindex.js b/lib/zgaindex.js index c7743fe..5961079 100644 --- a/lib/zgaindex.js +++ b/lib/zgaindex.js @@ -5,7 +5,9 @@ */ function genZga(){ /** @const {Object} */ - const z = {}; + const z = { + ver: "", + }; /** * @param {...string} msg @@ -42,22 +44,6 @@ function genZga(){ return arr; }; - // Google Apps Script - if(globalThis.UrlFetchApp){ - /** - * @param {string} url - * @param {UrlFetchParams} params - * @return {Promise} - */ - z.urlFetch = function(url, params){ - return new Promise(function(resolve){ - /** @type {GBlob} */ - var tblob = UrlFetchApp.fetch(url, params).getBlob(); - resolve(new Uint8Array(tblob.getBytes())); - }); - }; - } - return z; } @@ -68,6 +54,7 @@ if(typeof exports === "object" && typeof module !== "undefined"){ //Only for nodejs End// if(!globalThis.Zga){ globalThis.Zga = genZga(); + supplyZgaUrlFetch(globalThis.Zga); supplyZgaCertsChain(globalThis.Zga); supplyZgaCryptor(globalThis.Zga); supplyZgaSigner(globalThis.Zga); diff --git a/lib/zganode.js b/lib/zganode.js index 8ebd290..4bd9024 100644 --- a/lib/zganode.js +++ b/lib/zganode.js @@ -9,69 +9,8 @@ z.PDFLib = require("pdf-lib"); // z.fontkit = require("@pdf-lib/fontkit"); z.fontkit = require("pdf-fontkit"); z.pako = require("pako"); -/** - * @param {string} url - * @param {UrlFetchParams} params - * @return {Promise} - */ -z.urlFetch = function(url, params){ - return new Promise(function(resolve, reject){ - /** @type {URL} */ - var opts = m_urlparser.parse(url); - var http = m_h[opts.protocol]; - /** @type {string|Buffer} */ - var dat = null; - var encoding = undefined; - opts.method = "GET"; - if(params){ - if(params.payload instanceof Buffer){ - dat = params.payload; - }else if(params.payload instanceof Uint8Array){ - dat = Buffer.from(params.payload.buffer); - }else if(params.payload instanceof ArrayBuffer){ - dat = Buffer.from(params.payload); - }else{ - dat = params.payload; - encoding = "binary"; - } - if(params.headers){ - opts.headers = params.headers; - } - if(params.method){ - opts.method = params.method; - } - if(params.validateHttpsCertificates === false){ - opts.rejectUnauthorized = false; - } - } - - /** @type {http.ClientRequest} */ - var hreq = http.request(opts, function(/** @type {http.IncomingMessage} */a_res){ - if(a_res.statusCode !== 200){ - var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode); - a_res.resume(); - throw a_err; - } - /** @type {Array} */ - var a_bufs = []; - var a_bufs_len = 0; - a_res.on("data", function(/** @type {Buffer} */b_chunk){ - a_bufs.push(b_chunk); - a_bufs_len += b_chunk.length; - }); - a_res.on("end", function(){ - /** @type {Buffer} */ - var b_bdat = Buffer.concat(a_bufs, a_bufs_len); - resolve(b_bdat); - }); - }); - hreq.on("error", function(a_err){ - throw a_err; - }); - hreq.end(dat, encoding); - }); -}; +require("./zgafetch.js")(z); require("./zgacertsutil.js")(z); require("./zgapdfcryptor.js")(z); require("./zgapdfsigner.js")(z); diff --git a/lib/zgapdfsigner.js b/lib/zgapdfsigner.js index 909421a..7a14dbc 100644 --- a/lib/zgapdfsigner.js +++ b/lib/zgapdfsigner.js @@ -260,6 +260,9 @@ z.PdfSigner = class{ if(!(globalThis.forge || forge)){ throw new Error("node-forge is not imported."); } + if(z.ver){ + z.log("ZgaPdfSigner Version:", z.ver); + } /** @type {?TsaServiceInfo} */ var tsainf = null; if(signopt.signdate){ @@ -273,11 +276,13 @@ z.PdfSigner = class{ } if(tsainf){ if(!z.urlFetch){ - throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser."); + // throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser."); + throw new Error("No fetch method found in this environment."); } if(z.TSAURLS[tsainf.url]){ Object.assign(tsainf, z.TSAURLS[tsainf.url]); - }else if(!(new RegExp("^https?://")).test(tsainf.url)){ + }else if(!tsainf.url || (!z.isBrowser && !(new RegExp("^https?://")).test(tsainf.url))){ + // It may be a relative path in browser environment, so only check in non-browser environment throw new Error("Unknown tsa data. " + JSON.stringify(tsainf)); } if(!tsainf.len){ diff --git a/package.json b/package.json index d1e5b60..24648f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zgapdfsigner", - "version": "2.7.2", + "version": "2.7.3", "author": "zboris12", "description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.", "homepage": "https://github.com/zboris12/zgapdfsigner",