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