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