Skip to content

Commit efe54bf

Browse files
authored
Merge commit from fork
1 parent da291a5 commit efe54bf

5 files changed

Lines changed: 97 additions & 71 deletions

File tree

src/modules/xmp_metadata.js

Lines changed: 75 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -26,83 +26,88 @@
2626

2727
import { jsPDF } from "../jspdf.js";
2828

29-
/**
30-
* @name xmp_metadata
31-
* @module
32-
*/
33-
(function(jsPDFAPI) {
34-
"use strict";
29+
function postPutResources() {
30+
const metadata = this.internal.__metadata__.metadata;
31+
const utf8Metadata = unescape(encodeURIComponent(metadata));
3532

36-
var postPutResources = function() {
37-
var xmpmeta_beginning = '<x:xmpmeta xmlns:x="adobe:ns:meta/">';
38-
var rdf_beginning =
33+
const rawXml = this.internal.__metadata__.rawXml;
34+
let content;
35+
if (rawXml) {
36+
content = utf8Metadata;
37+
} else {
38+
const xmpmetaBeginning = '<x:xmpmeta xmlns:x="adobe:ns:meta/">';
39+
const rdfBeginning =
3940
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:jspdf="' +
40-
this.internal.__metadata__.namespaceuri +
41+
this.internal.__metadata__.namespaceUri +
4142
'"><jspdf:metadata>';
42-
var rdf_ending = "</jspdf:metadata></rdf:Description></rdf:RDF>";
43-
var xmpmeta_ending = "</x:xmpmeta>";
44-
var utf8_xmpmeta_beginning = unescape(
45-
encodeURIComponent(xmpmeta_beginning)
46-
);
47-
var utf8_rdf_beginning = unescape(encodeURIComponent(rdf_beginning));
48-
var utf8_metadata = unescape(
49-
encodeURIComponent(this.internal.__metadata__.metadata)
50-
);
51-
var utf8_rdf_ending = unescape(encodeURIComponent(rdf_ending));
52-
var utf8_xmpmeta_ending = unescape(encodeURIComponent(xmpmeta_ending));
43+
const rdfEnding = "</jspdf:metadata></rdf:Description></rdf:RDF>";
44+
const xmpmetaEnding = "</x:xmpmeta>";
5345

54-
var total_len =
55-
utf8_rdf_beginning.length +
56-
utf8_metadata.length +
57-
utf8_rdf_ending.length +
58-
utf8_xmpmeta_beginning.length +
59-
utf8_xmpmeta_ending.length;
46+
content =
47+
xmpmetaBeginning +
48+
rdfBeginning +
49+
escapeXml(utf8Metadata) +
50+
rdfEnding +
51+
xmpmetaEnding;
52+
}
6053

61-
this.internal.__metadata__.metadata_object_number = this.internal.newObject();
62-
this.internal.write(
63-
"<< /Type /Metadata /Subtype /XML /Length " + total_len + " >>"
64-
);
65-
this.internal.write("stream");
54+
this.internal.__metadata__.metadataObjectNumber = this.internal.newObject();
55+
this.internal.write(
56+
"<< /Type /Metadata /Subtype /XML /Length " + content.length + " >>"
57+
);
58+
this.internal.write("stream");
59+
this.internal.write(content);
60+
this.internal.write("endstream");
61+
this.internal.write("endobj");
62+
}
63+
64+
function putCatalog() {
65+
if (this.internal.__metadata__.metadataObjectNumber) {
6666
this.internal.write(
67-
utf8_xmpmeta_beginning +
68-
utf8_rdf_beginning +
69-
utf8_metadata +
70-
utf8_rdf_ending +
71-
utf8_xmpmeta_ending
67+
"/Metadata " + this.internal.__metadata__.metadataObjectNumber + " 0 R"
7268
);
73-
this.internal.write("endstream");
74-
this.internal.write("endobj");
75-
};
69+
}
70+
}
7671

77-
var putCatalog = function() {
78-
if (this.internal.__metadata__.metadata_object_number) {
79-
this.internal.write(
80-
"/Metadata " +
81-
this.internal.__metadata__.metadata_object_number +
82-
" 0 R"
83-
);
84-
}
85-
};
72+
function escapeXml(str) {
73+
return str
74+
.replace(/&/g, "&amp;")
75+
.replace(/</g, "&lt;")
76+
.replace(/>/g, "&gt;")
77+
.replace(/"/g, "&quot;")
78+
.replace(/'/g, "&apos;");
79+
}
8680

87-
/**
88-
* Adds XMP formatted metadata to PDF
89-
*
90-
* @name addMetadata
91-
* @function
92-
* @param {String} metadata The actual metadata to be added. The metadata shall be stored as XMP simple value. Note that if the metadata string contains XML markup characters "<", ">" or "&", those characters should be written using XML entities.
93-
* @param {String} namespaceuri Sets the namespace URI for the metadata. Last character should be slash or hash.
94-
* @returns {jsPDF} jsPDF-instance
95-
*/
96-
jsPDFAPI.addMetadata = function(metadata, namespaceuri) {
97-
if (typeof this.internal.__metadata__ === "undefined") {
98-
this.internal.__metadata__ = {
99-
metadata: metadata,
100-
namespaceuri: namespaceuri || "http://jspdf.default.namespaceuri/"
101-
};
102-
this.internal.events.subscribe("putCatalog", putCatalog);
81+
/**
82+
* Adds XMP formatted metadata to PDF.
83+
*
84+
* WARNING: Passing raw XML is potentially insecure! Always sanitize user input before passing it to this function!
85+
* @name addMetadata
86+
* @function
87+
* @param {string} metadata The actual metadata to be added. The interpretation of this parameter depends on the
88+
* second parameter.
89+
* @param {boolean|string|undefined} rawXmlOrNamespaceUri If a string is passed it sets the namespace URI for the
90+
* metadata and the metadata shall be stored as XMP simple value. The last character should be a slash or hash.
91+
*
92+
* If this argument is omitted, a string is passed, or `false` is passed, the `metadata` argument will be
93+
* XML-escaped before including it in the PDF.
94+
*
95+
* If `true` is passed, the `metadata` argument will be interpreted as raw XMP and will be included verbatim
96+
* in the PDF. The passed metadata must be complete (including surrounding `xmpmeta` and `RDF` tags).
97+
* @returns {jsPDF} jsPDF-instance
98+
*/
99+
jsPDF.API.addMetadata = function(metadata, rawXmlOrNamespaceUri) {
100+
if (typeof this.internal.__metadata__ === "undefined") {
101+
this.internal.__metadata__ = {
102+
metadata: metadata,
103+
namespaceUri:
104+
rawXmlOrNamespaceUri ?? "http://jspdf.default.namespaceuri/",
105+
rawXml:
106+
typeof rawXmlOrNamespaceUri === "boolean" ? rawXmlOrNamespaceUri : false
107+
};
108+
this.internal.events.subscribe("putCatalog", putCatalog);
103109

104-
this.internal.events.subscribe("postPutResources", postPutResources);
105-
}
106-
return this;
107-
};
108-
})(jsPDF.API);
110+
this.internal.events.subscribe("postPutResources", postPutResources);
111+
}
112+
return this;
113+
};
3.42 KB
Binary file not shown.
3.34 KB
Binary file not shown.

test/specs/xmpmetadata.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@
22

33
describe("Module: xmp_metadata", () => {
44
beforeAll(loadGlobals);
5+
56
it("make some metadata var. 1", () => {
67
var doc = new jsPDF({ putOnlyUsedFonts: true, floatPrecision: 2 });
78
doc.addMetadata("My metadata as a string.", "http://my.namespace.uri/");
89
comparePdf(doc.output(), "xmpmetadata.pdf");
910
});
11+
1012
it("make some metadata var. 2", () => {
1113
var doc = new jsPDF({ putOnlyUsedFonts: true, floatPrecision: 2 });
1214
doc.addMetadata("My metadata as a string.");
1315
comparePdf(doc.output(), "xmpmetadata-defaultNS.pdf");
1416
});
17+
18+
it("should support rawXml overload", () => {
19+
const doc = new jsPDF();
20+
const rawXml =
21+
'<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:custom="http://custom.ns/"><custom:data>Raw XML Data</custom:data></rdf:Description></rdf:RDF></x:xmpmeta>';
22+
doc.addMetadata(rawXml, true);
23+
comparePdf(doc.output(), "xmpmetadata-rawXml.pdf");
24+
});
25+
26+
it("should escape XML content when rawXml is not used", () => {
27+
const doc = new jsPDF();
28+
const metadataWithXml = 'Some metadata with <xml> & "special" characters';
29+
doc.addMetadata(metadataWithXml);
30+
comparePdf(doc.output(), "xmpmetadata-escaped.pdf");
31+
});
1532
});

types/index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,11 @@ declare module "jspdf" {
13401340
getFileFromVFS(filename: string): string;
13411341

13421342
// jsPDF plugin: xmp_metadata
1343-
addMetadata(metadata: string, namespaceuri?: string): jsPDF;
1343+
addMetadata(metadata: string, namespaceUri?: string): jsPDF;
1344+
/**
1345+
* WARNING: Passing raw XML is potentially insecure! Always sanitize user input before passing it to this function!
1346+
*/
1347+
addMetadata(metadata: string, rawXml?: boolean): jsPDF;
13441348

13451349
Matrix(
13461350
a: number,

0 commit comments

Comments
 (0)