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