Changed to allow TSA feature in web browser

main 2.7.3
zboris12 2024-08-24 12:17:43 +09:00
parent e5372e2652
commit 0d39f569ae
7 changed files with 168 additions and 87 deletions

View File

@ -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) * 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). * 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 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) * 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) * 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) * 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: * Set password protection to a pdf. Supported algorithms:
* 40bit RC4 Encryption * 40bit RC4 Encryption
* 128bit 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) ## 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, 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 ## The Dependencies

View File

@ -9,6 +9,8 @@ else
mkdir ${OUTFLDR} mkdir ${OUTFLDR}
fi fi
VER=$(sed -n -r "s/^.*\"version\": ?\"([0-9.]+)\".*$/\1/p" package.json)
GCCOPT="--charset UTF-8 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE" 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" GCCEXT="--externs closure/google-ext.js --externs closure/forge-ext.js --externs closure/pdflib-ext.js --externs closure/zb-externs.js"
jss="" jss=""
@ -20,7 +22,12 @@ do
if [ "$c" != "#" ] if [ "$c" != "#" ]
then then
outf="${OUTFLDR}/_${js}" 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 ] if [ $? -eq 0 ]
then then
echo "Created js file: ${outf}" echo "Created js file: ${outf}"
@ -32,6 +39,7 @@ do
fi fi
fi fi
done <<EOF done <<EOF
zgafetch.js
zgacertsutil.js zgacertsutil.js
zgapdfcryptor.js zgapdfcryptor.js
zgapdfsigner.js zgapdfsigner.js

140
lib/zgafetch.js Normal file
View File

@ -0,0 +1,140 @@
/**
* @param {Object<string, *>} 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<Uint8Array>}
*/
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<Buffer>}
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<Uint8Array>}
*/
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//

View File

@ -5,7 +5,9 @@
*/ */
function genZga(){ function genZga(){
/** @const {Object<string, *>} */ /** @const {Object<string, *>} */
const z = {}; const z = {
ver: "",
};
/** /**
* @param {...string} msg * @param {...string} msg
@ -42,22 +44,6 @@ function genZga(){
return arr; return arr;
}; };
// Google Apps Script
if(globalThis.UrlFetchApp){
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
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; return z;
} }
@ -68,6 +54,7 @@ if(typeof exports === "object" && typeof module !== "undefined"){
//Only for nodejs End// //Only for nodejs End//
if(!globalThis.Zga){ if(!globalThis.Zga){
globalThis.Zga = genZga(); globalThis.Zga = genZga();
supplyZgaUrlFetch(globalThis.Zga);
supplyZgaCertsChain(globalThis.Zga); supplyZgaCertsChain(globalThis.Zga);
supplyZgaCryptor(globalThis.Zga); supplyZgaCryptor(globalThis.Zga);
supplyZgaSigner(globalThis.Zga); supplyZgaSigner(globalThis.Zga);

View File

@ -9,69 +9,8 @@ z.PDFLib = require("pdf-lib");
// z.fontkit = require("@pdf-lib/fontkit"); // z.fontkit = require("@pdf-lib/fontkit");
z.fontkit = require("pdf-fontkit"); z.fontkit = require("pdf-fontkit");
z.pako = require("pako"); z.pako = require("pako");
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
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<Buffer>} */
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("./zgacertsutil.js")(z);
require("./zgapdfcryptor.js")(z); require("./zgapdfcryptor.js")(z);
require("./zgapdfsigner.js")(z); require("./zgapdfsigner.js")(z);

View File

@ -260,6 +260,9 @@ z.PdfSigner = class{
if(!(globalThis.forge || forge)){ if(!(globalThis.forge || forge)){
throw new Error("node-forge is not imported."); throw new Error("node-forge is not imported.");
} }
if(z.ver){
z.log("ZgaPdfSigner Version:", z.ver);
}
/** @type {?TsaServiceInfo} */ /** @type {?TsaServiceInfo} */
var tsainf = null; var tsainf = null;
if(signopt.signdate){ if(signopt.signdate){
@ -273,11 +276,13 @@ z.PdfSigner = class{
} }
if(tsainf){ if(tsainf){
if(!z.urlFetch){ 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]){ if(z.TSAURLS[tsainf.url]){
Object.assign(tsainf, 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)); throw new Error("Unknown tsa data. " + JSON.stringify(tsainf));
} }
if(!tsainf.len){ if(!tsainf.len){

View File

@ -1,6 +1,6 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.7.2", "version": "2.7.3",
"author": "zboris12", "author": "zboris12",
"description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.", "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", "homepage": "https://github.com/zboris12/zgapdfsigner",