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