Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: GDALJP2Metadata: metadata generator
5 : * Author: Even Rouault <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2015, European Union Satellite Centre
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "gdaljp2metadatagenerator.h"
15 :
16 : #include <cstddef>
17 :
18 : #ifdef HAVE_LIBXML2
19 :
20 : #if defined(__GNUC__)
21 : #pragma GCC diagnostic push
22 : #pragma GCC diagnostic ignored "-Wold-style-cast"
23 : #endif
24 :
25 : #ifdef __clang__
26 : #pragma clang diagnostic push
27 : #pragma clang diagnostic ignored "-Wunknown-pragmas"
28 : #pragma clang diagnostic ignored "-Wdocumentation"
29 : #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
30 : #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
31 : #endif
32 :
33 : #include <libxml/parser.h>
34 : #include <libxml/tree.h>
35 : #include <libxml/xpath.h>
36 : #include <libxml/xpathInternals.h>
37 :
38 : #if !(defined(LIBXML_VERSION) && LIBXML_VERSION >= 21400)
39 : // Cf https://gitlab.gnome.org/GNOME/libxml2/-/commit/92d7b0cd909beb61cd90d9746964f303eab36d78
40 : #define xmlXPathValuePush valuePush
41 : #define xmlXPathValuePop valuePop
42 : #endif
43 :
44 : #ifdef __clang__
45 : #pragma clang diagnostic pop
46 : #endif
47 :
48 : #if defined(__GNUC__)
49 : #pragma GCC diagnostic pop
50 : #endif
51 :
52 : // For CHECK_ARITY and clang 5
53 : // CHECK_ARITY: check the number of args passed to an XPath function matches.
54 : #undef NULL
55 : #define NULL nullptr
56 :
57 : // Simplified version of the macro proposed by libxml2
58 : // The reason is when running against filegdbAPI which includes it own copy
59 : // of libxml2, and the check 'ctxt->valueNr < ctxt->valueFrame + (x)'
60 : // done by libxml2 CHECK_ARITY() thus points to garbage
61 : #undef CHECK_ARITY
62 : #define CHECK_ARITY(x) \
63 : if (ctxt == NULL) \
64 : return; \
65 : if (nargs != (x)) \
66 : XP_ERROR(XPATH_INVALID_ARITY);
67 :
68 : /************************************************************************/
69 : /* GDALGMLJP2Expr */
70 : /************************************************************************/
71 :
72 : enum class GDALGMLJP2ExprType
73 : {
74 : GDALGMLJP2Expr_Unknown,
75 : GDALGMLJP2Expr_XPATH,
76 : GDALGMLJP2Expr_STRING_LITERAL,
77 : };
78 :
79 : class GDALGMLJP2Expr
80 : {
81 : static void SkipSpaces(const char *&pszStr);
82 :
83 : public:
84 : GDALGMLJP2ExprType eType = GDALGMLJP2ExprType::GDALGMLJP2Expr_Unknown;
85 : CPLString osValue{};
86 :
87 8 : GDALGMLJP2Expr() = default;
88 :
89 2 : explicit GDALGMLJP2Expr(const char *pszVal)
90 2 : : eType(GDALGMLJP2ExprType::GDALGMLJP2Expr_STRING_LITERAL),
91 2 : osValue(pszVal)
92 : {
93 2 : }
94 :
95 5 : explicit GDALGMLJP2Expr(const CPLString &osVal)
96 5 : : eType(GDALGMLJP2ExprType::GDALGMLJP2Expr_STRING_LITERAL),
97 5 : osValue(osVal)
98 : {
99 5 : }
100 :
101 15 : ~GDALGMLJP2Expr() = default;
102 :
103 : GDALGMLJP2Expr Evaluate(xmlXPathContextPtr pXPathCtx, xmlDocPtr pDoc);
104 :
105 : static GDALGMLJP2Expr *Build(const char *pszOriStr, const char *&pszStr);
106 : static void
107 : ReportError(const char *pszOriStr, const char *pszStr,
108 : const char *pszIntroMessage = "Parsing error at:\n");
109 : };
110 :
111 : /************************************************************************/
112 : /* ReportError() */
113 : /************************************************************************/
114 :
115 5 : void GDALGMLJP2Expr::ReportError(const char *pszOriStr, const char *pszStr,
116 : const char *pszIntroMessage)
117 : {
118 5 : size_t nDist = static_cast<size_t>(pszStr - pszOriStr);
119 5 : if (nDist > 40)
120 0 : nDist = 40;
121 10 : CPLString osErrMsg(pszIntroMessage);
122 15 : CPLString osInvalidExpr = CPLString(pszStr - nDist).substr(0, nDist + 20);
123 60 : for (int i = static_cast<int>(nDist) - 1; i >= 0; --i)
124 : {
125 55 : if (osInvalidExpr[i] == '\n')
126 : {
127 0 : osInvalidExpr = osInvalidExpr.substr(i + 1);
128 0 : nDist -= i + 1;
129 0 : break;
130 : }
131 : }
132 79 : for (size_t i = nDist; i < osInvalidExpr.size(); ++i)
133 : {
134 74 : if (osInvalidExpr[i] == '\n')
135 : {
136 0 : osInvalidExpr.resize(i);
137 0 : break;
138 : }
139 : }
140 5 : osErrMsg += osInvalidExpr;
141 5 : osErrMsg += "\n";
142 60 : for (size_t i = 0; i < nDist; ++i)
143 55 : osErrMsg += " ";
144 5 : osErrMsg += "^";
145 5 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrMsg.c_str());
146 5 : }
147 :
148 : /************************************************************************/
149 : /* SkipSpaces() */
150 : /************************************************************************/
151 :
152 47 : void GDALGMLJP2Expr::SkipSpaces(const char *&pszStr)
153 : {
154 47 : while (*pszStr == ' ' || *pszStr == '\t' || *pszStr == '\r' ||
155 39 : *pszStr == '\n')
156 8 : ++pszStr;
157 39 : }
158 :
159 : /************************************************************************/
160 : /* Build() */
161 : /************************************************************************/
162 :
163 24 : GDALGMLJP2Expr *GDALGMLJP2Expr::Build(const char *pszOriStr,
164 : const char *&pszStr)
165 : {
166 24 : if (STARTS_WITH_CI(pszStr, "{{{"))
167 : {
168 12 : pszStr += strlen("{{{");
169 12 : SkipSpaces(pszStr);
170 12 : GDALGMLJP2Expr *poExpr = Build(pszOriStr, pszStr);
171 12 : if (poExpr == nullptr)
172 4 : return nullptr;
173 8 : SkipSpaces(pszStr);
174 8 : if (!STARTS_WITH_CI(pszStr, "}}}"))
175 : {
176 1 : ReportError(pszOriStr, pszStr);
177 1 : delete poExpr;
178 1 : return nullptr;
179 : }
180 7 : pszStr += strlen("}}}");
181 7 : return poExpr;
182 : }
183 12 : else if (STARTS_WITH_CI(pszStr, "XPATH"))
184 : {
185 10 : pszStr += strlen("XPATH");
186 10 : SkipSpaces(pszStr);
187 10 : if (*pszStr != '(')
188 : {
189 1 : ReportError(pszOriStr, pszStr);
190 1 : return nullptr;
191 : }
192 9 : ++pszStr;
193 9 : SkipSpaces(pszStr);
194 18 : CPLString l_osValue;
195 9 : int nParenthesisIndent = 0;
196 9 : char chLiteralQuote = '\0';
197 138 : while (*pszStr)
198 : {
199 137 : if (chLiteralQuote != '\0')
200 : {
201 38 : if (*pszStr == chLiteralQuote)
202 5 : chLiteralQuote = '\0';
203 38 : l_osValue += *pszStr;
204 38 : ++pszStr;
205 : }
206 99 : else if (*pszStr == '\'' || *pszStr == '"')
207 : {
208 5 : chLiteralQuote = *pszStr;
209 5 : l_osValue += *pszStr;
210 5 : ++pszStr;
211 : }
212 94 : else if (*pszStr == '(')
213 : {
214 7 : ++nParenthesisIndent;
215 7 : l_osValue += *pszStr;
216 7 : ++pszStr;
217 : }
218 87 : else if (*pszStr == ')')
219 : {
220 15 : nParenthesisIndent--;
221 15 : if (nParenthesisIndent < 0)
222 : {
223 8 : pszStr++;
224 : #if DEBUG_VERBOSE
225 : CPLDebug("GMLJP2", "XPath expression '%s'",
226 : l_osValue.c_str());
227 : #endif
228 8 : GDALGMLJP2Expr *poExpr = new GDALGMLJP2Expr();
229 8 : poExpr->eType = GDALGMLJP2ExprType::GDALGMLJP2Expr_XPATH;
230 8 : poExpr->osValue = std::move(l_osValue);
231 8 : return poExpr;
232 : }
233 7 : l_osValue += *pszStr;
234 7 : ++pszStr;
235 : }
236 : else
237 : {
238 72 : l_osValue += *pszStr;
239 72 : pszStr++;
240 : }
241 : }
242 1 : ReportError(pszOriStr, pszStr);
243 1 : return nullptr;
244 : }
245 : else
246 : {
247 2 : ReportError(pszOriStr, pszStr);
248 2 : return nullptr;
249 : }
250 : }
251 :
252 : /************************************************************************/
253 : /* GDALGMLJP2HexFormatter() */
254 : /************************************************************************/
255 :
256 16 : static const char *GDALGMLJP2HexFormatter(GByte nVal)
257 : {
258 16 : return CPLSPrintf("%02X", nVal);
259 : }
260 :
261 : /************************************************************************/
262 : /* Evaluate() */
263 : /************************************************************************/
264 :
265 : static CPLString GDALGMLJP2EvalExpr(const CPLString &osTemplate,
266 : xmlXPathContextPtr pXPathCtx,
267 : xmlDocPtr pDoc);
268 :
269 7 : GDALGMLJP2Expr GDALGMLJP2Expr::Evaluate(xmlXPathContextPtr pXPathCtx,
270 : xmlDocPtr pDoc)
271 : {
272 7 : switch (eType)
273 : {
274 7 : case GDALGMLJP2ExprType::GDALGMLJP2Expr_XPATH:
275 : {
276 7 : xmlXPathObjectPtr pXPathObj = xmlXPathEvalExpression(
277 7 : reinterpret_cast<const xmlChar *>(osValue.c_str()), pXPathCtx);
278 7 : if (pXPathObj == nullptr)
279 2 : return GDALGMLJP2Expr("");
280 :
281 : // Add result of the evaluation.
282 10 : CPLString osXMLRes;
283 5 : if (pXPathObj->type == XPATH_STRING)
284 2 : osXMLRes = reinterpret_cast<const char *>(pXPathObj->stringval);
285 3 : else if (pXPathObj->type == XPATH_BOOLEAN)
286 1 : osXMLRes = pXPathObj->boolval ? "true" : "false";
287 2 : else if (pXPathObj->type == XPATH_NUMBER)
288 1 : osXMLRes = CPLSPrintf("%.16g", pXPathObj->floatval);
289 1 : else if (pXPathObj->type == XPATH_NODESET)
290 : {
291 1 : xmlNodeSetPtr pNodes = pXPathObj->nodesetval;
292 1 : int nNodes = (pNodes) ? pNodes->nodeNr : 0;
293 2 : for (int i = 0; i < nNodes; i++)
294 : {
295 1 : xmlNodePtr pCur = pNodes->nodeTab[i];
296 :
297 1 : xmlBufferPtr pBuf = xmlBufferCreate();
298 1 : xmlNodeDump(pBuf, pDoc, pCur, 2, 1);
299 : osXMLRes +=
300 1 : reinterpret_cast<const char *>(xmlBufferContent(pBuf));
301 1 : xmlBufferFree(pBuf);
302 : }
303 : }
304 :
305 5 : xmlXPathFreeObject(pXPathObj);
306 5 : return GDALGMLJP2Expr(osXMLRes);
307 : }
308 0 : default:
309 0 : CPLAssert(false);
310 : return GDALGMLJP2Expr("");
311 : }
312 : }
313 :
314 : /************************************************************************/
315 : /* GDALGMLJP2EvalExpr() */
316 : /************************************************************************/
317 :
318 8 : static CPLString GDALGMLJP2EvalExpr(const CPLString &osTemplate,
319 : xmlXPathContextPtr pXPathCtx,
320 : xmlDocPtr pDoc)
321 : {
322 8 : CPLString osXMLRes;
323 8 : size_t nPos = 0;
324 : while (true)
325 : {
326 : // Get next expression.
327 15 : size_t nStartPos = osTemplate.find("{{{", nPos);
328 15 : if (nStartPos == std::string::npos)
329 : {
330 : // Add terminating portion of the template.
331 3 : osXMLRes += osTemplate.substr(nPos);
332 3 : break;
333 : }
334 :
335 : // Add portion of template before the expression.
336 12 : osXMLRes += osTemplate.substr(nPos, nStartPos - nPos);
337 :
338 12 : const char *pszExpr = osTemplate.c_str() + nStartPos;
339 12 : GDALGMLJP2Expr *poExpr = GDALGMLJP2Expr::Build(pszExpr, pszExpr);
340 12 : if (poExpr == nullptr)
341 5 : break;
342 7 : nPos = static_cast<size_t>(pszExpr - osTemplate.c_str());
343 7 : osXMLRes += poExpr->Evaluate(pXPathCtx, pDoc).osValue;
344 7 : delete poExpr;
345 7 : }
346 8 : return osXMLRes;
347 : }
348 :
349 : /************************************************************************/
350 : /* GDALGMLJP2XPathErrorHandler() */
351 : /************************************************************************/
352 :
353 2 : static void GDALGMLJP2XPathErrorHandler(void * /* userData */,
354 : #if LIBXML_VERSION >= 21200
355 : const xmlError *error
356 : #else
357 : xmlErrorPtr error
358 : #endif
359 : )
360 : {
361 2 : if (error->domain == XML_FROM_XPATH && error->str1 != nullptr &&
362 2 : error->int1 < static_cast<int>(strlen(error->str1)))
363 : {
364 0 : GDALGMLJP2Expr::ReportError(error->str1, error->str1 + error->int1,
365 : "XPath error:\n");
366 : }
367 : else
368 : {
369 2 : CPLError(CE_Failure, CPLE_AppDefined, "An error occurred in libxml2");
370 : }
371 2 : }
372 :
373 : /************************************************************************/
374 : /* GDALGMLJP2RegisterNamespaces() */
375 : /************************************************************************/
376 :
377 28 : static void GDALGMLJP2RegisterNamespaces(xmlXPathContextPtr pXPathCtx,
378 : xmlNode *pNode)
379 : {
380 28 : for (; pNode; pNode = pNode->next)
381 : {
382 10 : if (pNode->type == XML_ELEMENT_NODE)
383 : {
384 9 : if (pNode->ns != nullptr && pNode->ns->prefix != nullptr)
385 : {
386 : // printf("%s %s\n",pNode->ns->prefix, pNode->ns->href);
387 0 : if (xmlXPathRegisterNs(pXPathCtx, pNode->ns->prefix,
388 0 : pNode->ns->href) != 0)
389 : {
390 0 : CPLError(CE_Warning, CPLE_AppDefined,
391 : "Registration of namespace %s failed",
392 0 : reinterpret_cast<const char *>(pNode->ns->prefix));
393 : }
394 : }
395 : }
396 :
397 10 : GDALGMLJP2RegisterNamespaces(pXPathCtx, pNode->children);
398 : }
399 18 : }
400 :
401 : /************************************************************************/
402 : /* GDALGMLJP2XPathIf() */
403 : /************************************************************************/
404 :
405 2 : static void GDALGMLJP2XPathIf(xmlXPathParserContextPtr ctxt, int nargs)
406 : {
407 : xmlXPathObjectPtr cond_val, then_val, else_val;
408 :
409 2 : CHECK_ARITY(3);
410 2 : else_val = xmlXPathValuePop(ctxt);
411 2 : then_val = xmlXPathValuePop(ctxt);
412 2 : CAST_TO_BOOLEAN
413 2 : cond_val = xmlXPathValuePop(ctxt);
414 :
415 2 : if (cond_val->boolval)
416 : {
417 1 : xmlXPathFreeObject(else_val);
418 1 : xmlXPathValuePush(ctxt, then_val);
419 : }
420 : else
421 : {
422 1 : xmlXPathFreeObject(then_val);
423 1 : xmlXPathValuePush(ctxt, else_val);
424 : }
425 2 : xmlXPathFreeObject(cond_val);
426 : }
427 :
428 : /************************************************************************/
429 : /* GDALGMLJP2XPathUUID() */
430 : /************************************************************************/
431 :
432 1 : static void GDALGMLJP2XPathUUID(xmlXPathParserContextPtr ctxt, int nargs)
433 : {
434 1 : CHECK_ARITY(0);
435 :
436 2 : CPLString osRet;
437 :
438 : // From POSIX.1-2001 as an example of an implementation of rand()
439 16 : const auto fakeRand = []()
440 : {
441 : static uint32_t nCounter =
442 16 : static_cast<unsigned int>(time(nullptr) & UINT_MAX);
443 16 : uint32_t nCounterLocal = static_cast<uint32_t>(
444 16 : (static_cast<uint64_t>(nCounter) * 1103515245U + 12345U) &
445 : UINT32_MAX);
446 16 : nCounter = nCounterLocal;
447 16 : return (nCounterLocal / 65536U) % 32768U;
448 : };
449 :
450 5 : for (int i = 0; i < 4; i++)
451 4 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
452 1 : osRet += "-";
453 1 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
454 1 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
455 1 : osRet += "-";
456 : // Set the version number bits (4 == random).
457 1 : osRet += GDALGMLJP2HexFormatter((fakeRand() & 0x0F) | 0x40);
458 1 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
459 1 : osRet += "-";
460 : // Set the variant bits.
461 1 : osRet += GDALGMLJP2HexFormatter((fakeRand() & 0x3F) | 0x80);
462 1 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
463 1 : osRet += "-";
464 7 : for (int i = 0; i < 6; ++i)
465 : {
466 6 : osRet += GDALGMLJP2HexFormatter(fakeRand() & 0xFF);
467 : }
468 :
469 1 : xmlXPathValuePush(ctxt, xmlXPathNewString(reinterpret_cast<const xmlChar *>(
470 1 : osRet.c_str())));
471 : }
472 :
473 : #endif // LIBXML2
474 :
475 : /************************************************************************/
476 : /* GDALGMLJP2GenerateMetadata() */
477 : /************************************************************************/
478 :
479 : #ifdef HAVE_LIBXML2
480 11 : CPLXMLNode *GDALGMLJP2GenerateMetadata(const CPLString &osTemplateFile,
481 : const CPLString &osSourceFile)
482 : {
483 11 : GByte *pabyStr = nullptr;
484 11 : if (!VSIIngestFile(nullptr, osTemplateFile, &pabyStr, nullptr, -1))
485 1 : return nullptr;
486 20 : CPLString osTemplate(reinterpret_cast<char *>(pabyStr));
487 10 : CPLFree(pabyStr);
488 :
489 10 : if (!VSIIngestFile(nullptr, osSourceFile, &pabyStr, nullptr, -1))
490 1 : return nullptr;
491 18 : CPLString osSource(reinterpret_cast<char *>(pabyStr));
492 9 : CPLFree(pabyStr);
493 :
494 : xmlDocPtr pDoc =
495 9 : xmlParseDoc(reinterpret_cast<const xmlChar *>(osSource.c_str()));
496 9 : if (pDoc == nullptr)
497 : {
498 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse %s",
499 : osSourceFile.c_str());
500 1 : return nullptr;
501 : }
502 :
503 8 : xmlXPathContextPtr pXPathCtx = xmlXPathNewContext(pDoc);
504 8 : if (pXPathCtx == nullptr)
505 : {
506 0 : xmlFreeDoc(pDoc);
507 0 : return nullptr;
508 : }
509 :
510 8 : xmlXPathRegisterFunc(pXPathCtx, reinterpret_cast<const xmlChar *>("if"),
511 : GDALGMLJP2XPathIf);
512 8 : xmlXPathRegisterFunc(pXPathCtx, reinterpret_cast<const xmlChar *>("uuid"),
513 : GDALGMLJP2XPathUUID);
514 :
515 8 : pXPathCtx->error = GDALGMLJP2XPathErrorHandler;
516 :
517 8 : GDALGMLJP2RegisterNamespaces(pXPathCtx, xmlDocGetRootElement(pDoc));
518 :
519 16 : CPLString osXMLRes = GDALGMLJP2EvalExpr(osTemplate, pXPathCtx, pDoc);
520 :
521 8 : xmlXPathFreeContext(pXPathCtx);
522 8 : xmlFreeDoc(pDoc);
523 :
524 8 : return CPLParseXMLString(osXMLRes);
525 : }
526 : #else // !HAVE_LIBXML2
527 : CPLXMLNode *GDALGMLJP2GenerateMetadata(const CPLString & /* osTemplateFile */,
528 : const CPLString & /* osSourceFile */
529 : )
530 : {
531 : return nullptr;
532 : }
533 : #endif // HAVE_LIBXML2
|