@mablhq/mabl-cli 1.50.5 → 1.52.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,13 +48,69 @@ mablEmbeddedPdfDetector = () => {
48
48
  }
49
49
  }
50
50
 
51
+ /**
52
+ * Returns the URL string of the element which should in some way be embedding a PDF.
53
+ * @param {HTMLElement} originalPdfElement The element embedding the PDF, should have embed tag, object tag,
54
+ * @returns {(string|undefined)} The URL which should host the PDF, or undefined if none was found
55
+ */
56
+ function getPdfUrlFromElement(originalPdfElement) {
57
+ return (
58
+ originalPdfElement.src ||
59
+ originalPdfElement.data || // embed has @src, object has @data
60
+ undefined
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Checks that the URL embedding the PDF leads to a valid location and/or
66
+ * has a valid protocol
67
+ * @param {HTMLElement} element The element on the page whose URL to check
68
+ * @returns {boolean} Whether or not the element is valid
69
+ */
70
+ function checkUrlProtocol(element) {
71
+ if (element.src === 'about:blank') {
72
+ // this is likely the chrome viewer for a POST
73
+ // don't do anything, we might get a manual update later
74
+ // if the trainer finds a PDF
75
+ return false;
76
+ }
77
+ // Validate that the URL is an allowed protocol to prevent XSS, see IST-561
78
+ const pdfUrlString = getPdfUrlFromElement(element);
79
+ try {
80
+ const pdfUrl = new URL(pdfUrlString);
81
+ // data: is allowed to support embedding the PDF inline using a data URI scheme
82
+ const allowedProtocols = ['http:', 'https:', 'data:'];
83
+ if (!allowedProtocols.includes(pdfUrl?.protocol)) {
84
+ console.warn(
85
+ `PDF Handler ignoring element with URL ${pdfUrlString} because protocol ${pdfUrl?.protocol} does not match one of the allowed protocols ${allowedProtocols}`,
86
+ );
87
+ return false;
88
+ } else if (
89
+ pdfUrl?.protocol === 'data:' &&
90
+ !/^data:application\/pdf[,;]/.test(pdfUrl?.href ?? '') // starts with data:application/pdf and either , or ; separator
91
+ ) {
92
+ console.warn(
93
+ `PDF Handler ignoring element with URL ${pdfUrlString} because it does not use application/pdf MIME type`,
94
+ );
95
+ return false;
96
+ }
97
+ } catch (e) {
98
+ console.warn(
99
+ `PDF Handler ignoring element with URL ${pdfUrlString}; error ${e} was thrown while converting to URL`,
100
+ );
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+
51
106
  async function handle(event) {
52
107
  const element = event.target;
53
108
  if (
54
109
  checkAnimationEvent(event) &&
55
110
  checkElement(element) &&
56
111
  checkMimeType(element) &&
57
- !isMablMarked(element)
112
+ !isMablMarked(element) &&
113
+ checkUrlProtocol(element)
58
114
  ) {
59
115
  const baseUri = document.baseURI;
60
116
  const pdfElement = element;
@@ -100,7 +156,7 @@ mablEmbeddedPdfDetector = () => {
100
156
 
101
157
  function checkMimeType(element) {
102
158
  const mimeType = element.type;
103
- const path = element.src || element.data; // src for embed, data for object
159
+ const path = getPdfUrlFromElement(element);
104
160
  const isAPdf =
105
161
  (mimeType && mimeType.toLowerCase() === 'application/pdf') || // mime type is PDF
106
162
  (!mimeType && path && /\.pdf($|[?#])/i.test(path)) || // or if we don't have a mime type test the path up to query or hash
@@ -130,6 +186,7 @@ const MABL_DEFAULT_PDF_NAME = 'mablPdf';
130
186
  function documentHasDocType() {
131
187
  return document.doctype && document.doctype.name;
132
188
  }
189
+
133
190
  function runEmbeddedPdfDetector() {
134
191
  if (documentHasDocType()) {
135
192
  mablEmbeddedPdfDetector();