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