Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/gcore/gdaljp2box.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  GDALJP2Box Implementation - Low level JP2 box reader.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2010-2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "gdaljp2metadata.h"
16
17
#include <cstddef>
18
#include <cstdio>
19
#include <cstring>
20
21
#include <algorithm>
22
23
#include "cpl_conv.h"
24
#include "cpl_error.h"
25
#include "cpl_string.h"
26
#include "cpl_vsi.h"
27
28
/*! @cond Doxygen_Suppress */
29
30
/************************************************************************/
31
/*                             GDALJP2Box()                             */
32
/************************************************************************/
33
34
// GDALJP2Box does *not* take ownership of fpIn
35
25.9k
GDALJP2Box::GDALJP2Box(VSILFILE *fpIn) : fpVSIL(fpIn)
36
25.9k
{
37
25.9k
}
38
39
/************************************************************************/
40
/*                            ~GDALJP2Box()                             */
41
/************************************************************************/
42
43
GDALJP2Box::~GDALJP2Box()
44
45
25.9k
{
46
    // Do not close fpVSIL. Ownership remains to the caller of GDALJP2Box
47
    // constructor
48
25.9k
}
49
50
/************************************************************************/
51
/*                             SetOffset()                              */
52
/************************************************************************/
53
54
int GDALJP2Box::SetOffset(vsi_l_offset nNewOffset)
55
56
228k
{
57
228k
    szBoxType[0] = '\0';
58
228k
    return VSIFSeekL(fpVSIL, nNewOffset, SEEK_SET) == 0;
59
228k
}
60
61
/************************************************************************/
62
/*                             ReadFirst()                              */
63
/************************************************************************/
64
65
int GDALJP2Box::ReadFirst()
66
67
19.5k
{
68
19.5k
    return SetOffset(0) && ReadBox();
69
19.5k
}
70
71
/************************************************************************/
72
/*                              ReadNext()                              */
73
/************************************************************************/
74
75
int GDALJP2Box::ReadNext()
76
77
202k
{
78
202k
    return SetOffset(nBoxOffset + nBoxLength) && ReadBox();
79
202k
}
80
81
/************************************************************************/
82
/*                           ReadFirstChild()                           */
83
/************************************************************************/
84
85
int GDALJP2Box::ReadFirstChild(GDALJP2Box *poSuperBox)
86
87
25.9k
{
88
25.9k
    if (poSuperBox == nullptr)
89
19.5k
        return ReadFirst();
90
91
6.41k
    szBoxType[0] = '\0';
92
6.41k
    if (!poSuperBox->IsSuperBox())
93
0
        return FALSE;
94
95
6.41k
    return SetOffset(poSuperBox->nDataOffset) && ReadBox();
96
6.41k
}
97
98
/************************************************************************/
99
/*                           ReadNextChild()                            */
100
/************************************************************************/
101
102
int GDALJP2Box::ReadNextChild(GDALJP2Box *poSuperBox)
103
104
202k
{
105
202k
    if (poSuperBox == nullptr)
106
192k
        return ReadNext();
107
108
9.83k
    if (!ReadNext())
109
155
        return FALSE;
110
111
9.67k
    if (nBoxOffset >= poSuperBox->nBoxOffset + poSuperBox->nBoxLength)
112
3.32k
    {
113
3.32k
        szBoxType[0] = '\0';
114
3.32k
        return FALSE;
115
3.32k
    }
116
117
6.35k
    return TRUE;
118
9.67k
}
119
120
/************************************************************************/
121
/*                              ReadBox()                               */
122
/************************************************************************/
123
124
int GDALJP2Box::ReadBox()
125
126
228k
{
127
228k
    GUInt32 nLBox = 0;
128
228k
    GUInt32 nTBox = 0;
129
130
228k
    nBoxOffset = VSIFTellL(fpVSIL);
131
132
228k
    if (VSIFReadL(&nLBox, 4, 1, fpVSIL) != 1 ||
133
213k
        VSIFReadL(&nTBox, 4, 1, fpVSIL) != 1)
134
16.1k
    {
135
16.1k
        return FALSE;
136
16.1k
    }
137
138
212k
    memcpy(szBoxType, &nTBox, 4);
139
212k
    szBoxType[4] = '\0';
140
141
212k
    nLBox = CPL_MSBWORD32(nLBox);
142
143
212k
    if (nLBox != 1)
144
212k
    {
145
212k
        nBoxLength = nLBox;
146
212k
        nDataOffset = nBoxOffset + 8;
147
212k
    }
148
280
    else
149
280
    {
150
280
        GByte abyXLBox[8] = {0};
151
280
        if (VSIFReadL(abyXLBox, 8, 1, fpVSIL) != 1)
152
19
            return FALSE;
153
154
261
        CPL_MSBPTR64(abyXLBox);
155
261
        memcpy(&nBoxLength, abyXLBox, 8);
156
157
261
        if (nBoxLength < 0)
158
73
        {
159
73
            CPLDebug("GDALJP2", "Invalid length for box %s", szBoxType);
160
73
            return FALSE;
161
73
        }
162
188
        nDataOffset = nBoxOffset + 16;
163
188
    }
164
165
212k
    if (nBoxLength == 0 && m_bAllowGetFileSize)
166
11.0k
    {
167
11.0k
        if (VSIFSeekL(fpVSIL, 0, SEEK_END) != 0)
168
0
            return FALSE;
169
11.0k
        nBoxLength = VSIFTellL(fpVSIL) - nBoxOffset;
170
11.0k
        if (VSIFSeekL(fpVSIL, static_cast<vsi_l_offset>(nDataOffset),
171
11.0k
                      SEEK_SET) != 0)
172
0
            return FALSE;
173
11.0k
    }
174
175
212k
    if (EQUAL(szBoxType, "uuid"))
176
94.8k
    {
177
94.8k
        if (VSIFReadL(abyUUID, 16, 1, fpVSIL) != 1)
178
105
            return FALSE;
179
94.7k
        nDataOffset += 16;
180
94.7k
    }
181
182
212k
    if (m_bAllowGetFileSize && GetDataLength() < 0)
183
50
    {
184
50
        CPLDebug("GDALJP2", "Invalid length for box %s", szBoxType);
185
50
        return FALSE;
186
50
    }
187
188
212k
    return TRUE;
189
212k
}
190
191
/************************************************************************/
192
/*                             IsSuperBox()                             */
193
/************************************************************************/
194
195
int GDALJP2Box::IsSuperBox()
196
197
209k
{
198
209k
    if (EQUAL(GetType(), "asoc") || EQUAL(GetType(), "jp2h") ||
199
197k
        EQUAL(GetType(), "res ") || EQUAL(GetType(), "jumb"))
200
12.9k
        return TRUE;
201
202
196k
    return FALSE;
203
209k
}
204
205
/************************************************************************/
206
/*                            ReadBoxData()                             */
207
/************************************************************************/
208
209
GByte *GDALJP2Box::ReadBoxData()
210
211
483k
{
212
483k
    GIntBig nDataLength = GetDataLength();
213
483k
    if (nDataLength > 100 * 1024 * 1024)
214
0
    {
215
0
        CPLError(CE_Failure, CPLE_AppDefined,
216
0
                 "Too big box : " CPL_FRMT_GIB " bytes", nDataLength);
217
0
        return nullptr;
218
0
    }
219
220
483k
    if (VSIFSeekL(fpVSIL, static_cast<vsi_l_offset>(nDataOffset), SEEK_SET) !=
221
483k
        0)
222
0
        return nullptr;
223
224
483k
    char *pszData = static_cast<char *>(
225
483k
        VSI_MALLOC_VERBOSE(static_cast<int>(nDataLength) + 1));
226
483k
    if (pszData == nullptr)
227
0
        return nullptr;
228
229
483k
    if (static_cast<GIntBig>(VSIFReadL(
230
483k
            pszData, 1, static_cast<int>(nDataLength), fpVSIL)) != nDataLength)
231
2.54k
    {
232
2.54k
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot read box content");
233
2.54k
        CPLFree(pszData);
234
2.54k
        return nullptr;
235
2.54k
    }
236
237
480k
    pszData[nDataLength] = '\0';
238
239
480k
    return reinterpret_cast<GByte *>(pszData);
240
483k
}
241
242
/************************************************************************/
243
/*                           GetDataLength()                            */
244
/************************************************************************/
245
246
GIntBig GDALJP2Box::GetDataLength() const
247
1.02M
{
248
1.02M
    return nBoxLength - (nDataOffset - nBoxOffset);
249
1.02M
}
250
251
/************************************************************************/
252
/*                            DumpReadable()                            */
253
/************************************************************************/
254
255
int GDALJP2Box::DumpReadable(FILE *fpOut, int nIndentLevel)
256
257
0
{
258
0
    if (fpOut == nullptr)
259
0
        fpOut = stdout;
260
261
0
    for (int i = 0; i < nIndentLevel; ++i)
262
0
        fprintf(fpOut, "  ");
263
264
0
    char szBuffer[128];
265
0
    CPLsnprintf(szBuffer, sizeof(szBuffer),
266
0
                "  Type=%s, Offset=" CPL_FRMT_GIB "/" CPL_FRMT_GIB
267
0
                ", Data Size=" CPL_FRMT_GIB,
268
0
                szBoxType, nBoxOffset, nDataOffset, GetDataLength());
269
0
    fprintf(fpOut, "%s", szBuffer);
270
271
0
    if (IsSuperBox())
272
0
    {
273
0
        fprintf(fpOut, " (super)");
274
0
    }
275
276
0
    fprintf(fpOut, "\n");
277
278
0
    if (IsSuperBox())
279
0
    {
280
0
        GDALJP2Box oSubBox(GetFILE());
281
282
0
        for (oSubBox.ReadFirstChild(this); strlen(oSubBox.GetType()) > 0;
283
0
             oSubBox.ReadNextChild(this))
284
0
        {
285
0
            oSubBox.DumpReadable(fpOut, nIndentLevel + 1);
286
0
        }
287
0
    }
288
289
0
    if (EQUAL(GetType(), "uuid"))
290
0
    {
291
0
        char *pszHex = CPLBinaryToHex(16, GetUUID());
292
0
        for (int i = 0; i < nIndentLevel; ++i)
293
0
            fprintf(fpOut, "  ");
294
295
0
        fprintf(fpOut, "    UUID=%s", pszHex);
296
297
0
        if (EQUAL(pszHex, "B14BF8BD083D4B43A5AE8CD7D5A6CE03"))
298
0
            fprintf(fpOut, " (GeoTIFF)");
299
0
        if (EQUAL(pszHex, "96A9F1F1DC98402DA7AED68E34451809"))
300
0
            fprintf(fpOut, " (MSI Worldfile)");
301
0
        if (EQUAL(pszHex, "BE7ACFCB97A942E89C71999491E3AFAC"))
302
0
            fprintf(fpOut, " (XMP)");
303
0
        CPLFree(pszHex);
304
305
0
        fprintf(fpOut, "\n");
306
0
    }
307
308
0
    return 0;
309
0
}
310
311
/************************************************************************/
312
/*                              SetType()                               */
313
/************************************************************************/
314
315
void GDALJP2Box::SetType(const char *pszType)
316
317
0
{
318
0
    CPLAssert(strlen(pszType) == 4);
319
320
0
    memcpy(szBoxType, pszType, 4);
321
0
    szBoxType[4] = '\0';
322
0
}
323
324
/************************************************************************/
325
/*                         GetWritableBoxData()                         */
326
/************************************************************************/
327
328
GByte *GDALJP2Box::GetWritableBoxData() const
329
0
{
330
0
    CPLAssert(static_cast<GUInt32>(nBoxLength) == 8 + abyData.size());
331
0
    GByte *pabyRet =
332
0
        static_cast<GByte *>(CPLMalloc(static_cast<GUInt32>(nBoxLength)));
333
0
    const GUInt32 nLBox = CPL_MSBWORD32(static_cast<GUInt32>(nBoxLength));
334
0
    memcpy(pabyRet, &nLBox, sizeof(GUInt32));
335
0
    memcpy(pabyRet + 4, szBoxType, 4);
336
0
    memcpy(pabyRet + 8, abyData.data(), abyData.size());
337
0
    return pabyRet;
338
0
}
339
340
/************************************************************************/
341
/*                          SetWritableData()                           */
342
/************************************************************************/
343
344
void GDALJP2Box::SetWritableData(int nLength, const GByte *pabyDataIn)
345
346
0
{
347
0
    abyData.assign(pabyDataIn, pabyDataIn + nLength);
348
349
0
    nBoxOffset = -9;  // Virtual offsets for data length computation.
350
0
    nDataOffset = -1;
351
352
0
    nBoxLength = 8 + nLength;
353
0
}
354
355
/************************************************************************/
356
/*                         AppendWritableData()                         */
357
/************************************************************************/
358
359
void GDALJP2Box::AppendWritableData(int nLength, const void *pabyDataIn)
360
361
0
{
362
0
    if (abyData.empty())
363
0
    {
364
0
        nBoxOffset = -9;  // Virtual offsets for data length computation.
365
0
        nDataOffset = -1;
366
0
        nBoxLength = 8;
367
0
    }
368
369
0
    abyData.insert(abyData.end(), static_cast<const GByte *>(pabyDataIn),
370
0
                   static_cast<const GByte *>(pabyDataIn) + nLength);
371
372
0
    nBoxLength += nLength;
373
0
}
374
375
/************************************************************************/
376
/*                            AppendUInt32()                            */
377
/************************************************************************/
378
379
void GDALJP2Box::AppendUInt32(GUInt32 nVal)
380
0
{
381
0
    CPL_MSBPTR32(&nVal);
382
0
    AppendWritableData(4, &nVal);
383
0
}
384
385
/************************************************************************/
386
/*                            AppendUInt16()                            */
387
/************************************************************************/
388
389
void GDALJP2Box::AppendUInt16(GUInt16 nVal)
390
0
{
391
0
    CPL_MSBPTR16(&nVal);
392
0
    AppendWritableData(2, &nVal);
393
0
}
394
395
/************************************************************************/
396
/*                            AppendUInt8()                             */
397
/************************************************************************/
398
399
void GDALJP2Box::AppendUInt8(GByte nVal)
400
0
{
401
0
    AppendWritableData(1, &nVal);
402
0
}
403
404
/************************************************************************/
405
/*                           CreateUUIDBox()                            */
406
/************************************************************************/
407
408
GDALJP2Box *GDALJP2Box::CreateUUIDBox(const GByte *pabyUUID, int nDataSize,
409
                                      const GByte *pabyDataIn)
410
411
0
{
412
0
    GDALJP2Box *const poBox = new GDALJP2Box();
413
0
    poBox->SetType("uuid");
414
415
0
    poBox->AppendWritableData(16, pabyUUID);
416
0
    poBox->AppendWritableData(nDataSize, pabyDataIn);
417
418
0
    return poBox;
419
0
}
420
421
/************************************************************************/
422
/*                           CreateAsocBox()                            */
423
/************************************************************************/
424
425
GDALJP2Box *GDALJP2Box::CreateAsocBox(int nCount,
426
                                      const GDALJP2Box *const *papoBoxes)
427
0
{
428
0
    return CreateSuperBox("asoc", nCount, papoBoxes);
429
0
}
430
431
/************************************************************************/
432
/*                           CreateAsocBox()                            */
433
/************************************************************************/
434
435
GDALJP2Box *GDALJP2Box::CreateSuperBox(const char *pszType, int nCount,
436
                                       const GDALJP2Box *const *papoBoxes)
437
0
{
438
0
    int nDataSize = 0;
439
440
    /* -------------------------------------------------------------------- */
441
    /*      Compute size of data area of asoc box.                          */
442
    /* -------------------------------------------------------------------- */
443
0
    for (int iBox = 0; iBox < nCount; ++iBox)
444
0
        nDataSize += 8 + static_cast<int>(papoBoxes[iBox]->GetDataLength());
445
446
0
    GByte *pabyNext = static_cast<GByte *>(CPLMalloc(nDataSize));
447
0
    GByte *pabyCompositeData = pabyNext;
448
449
    /* -------------------------------------------------------------------- */
450
    /*      Copy subboxes headers and data into buffer.                     */
451
    /* -------------------------------------------------------------------- */
452
0
    for (int iBox = 0; iBox < nCount; ++iBox)
453
0
    {
454
0
        GUInt32 nLBox =
455
0
            CPL_MSBWORD32(static_cast<GUInt32>(papoBoxes[iBox]->nBoxLength));
456
0
        memcpy(pabyNext, &nLBox, 4);
457
0
        pabyNext += 4;
458
459
0
        memcpy(pabyNext, papoBoxes[iBox]->szBoxType, 4);
460
0
        pabyNext += 4;
461
462
0
        memcpy(pabyNext, papoBoxes[iBox]->abyData.data(),
463
0
               papoBoxes[iBox]->abyData.size());
464
0
        pabyNext += papoBoxes[iBox]->GetDataLength();
465
0
    }
466
467
    /* -------------------------------------------------------------------- */
468
    /*      Create asoc box.                                                */
469
    /* -------------------------------------------------------------------- */
470
0
    GDALJP2Box *const poAsoc = new GDALJP2Box();
471
472
0
    poAsoc->SetType(pszType);
473
0
    poAsoc->SetWritableData(nDataSize, pabyCompositeData);
474
475
0
    CPLFree(pabyCompositeData);
476
477
0
    return poAsoc;
478
0
}
479
480
/************************************************************************/
481
/*                            CreateLblBox()                            */
482
/************************************************************************/
483
484
GDALJP2Box *GDALJP2Box::CreateLblBox(const char *pszLabel)
485
486
0
{
487
0
    GDALJP2Box *const poBox = new GDALJP2Box();
488
0
    poBox->SetType("lbl ");
489
0
    poBox->SetWritableData(static_cast<int>(strlen(pszLabel)),
490
0
                           reinterpret_cast<const GByte *>(pszLabel));
491
492
0
    return poBox;
493
0
}
494
495
/************************************************************************/
496
/*                       CreateLabelledXMLAssoc()                       */
497
/************************************************************************/
498
499
GDALJP2Box *GDALJP2Box::CreateLabelledXMLAssoc(const char *pszLabel,
500
                                               const char *pszXML)
501
502
0
{
503
0
    GDALJP2Box oLabel;
504
0
    oLabel.SetType("lbl ");
505
0
    oLabel.SetWritableData(static_cast<int>(strlen(pszLabel)),
506
0
                           reinterpret_cast<const GByte *>(pszLabel));
507
508
0
    GDALJP2Box oXML;
509
0
    oXML.SetType("xml ");
510
0
    oXML.SetWritableData(static_cast<int>(strlen(pszXML)),
511
0
                         reinterpret_cast<const GByte *>(pszXML));
512
513
0
    GDALJP2Box *aoList[2] = {&oLabel, &oXML};
514
515
0
    return CreateAsocBox(2, aoList);
516
0
}
517
518
/************************************************************************/
519
/*                     CreateJUMBFDescriptionBox()                      */
520
/************************************************************************/
521
522
GDALJP2Box *GDALJP2Box::CreateJUMBFDescriptionBox(const GByte *pabyUUIDType,
523
                                                  const char *pszLabel)
524
525
0
{
526
0
    GDALJP2Box *const poBox = new GDALJP2Box();
527
0
    poBox->SetType("jumd");
528
529
0
    poBox->AppendWritableData(16, pabyUUIDType);
530
0
    poBox->AppendUInt8(3);  // requestable field
531
    // +1 since NUL terminated byte required in the JUMBF spec
532
    // cf other implementation at https://gitlab.com/wg1/jpeg-systems/reference-software/jumbf-reference-implementation-2/-/blame/main/dbench_jumbf/src/db_jumbf_desc_box.cpp?ref_type=heads#L169
533
0
    const size_t nLabelLen = strlen(pszLabel) + 1;
534
0
    poBox->AppendWritableData(static_cast<int>(nLabelLen), pszLabel);
535
536
0
    return poBox;
537
0
}
538
539
/************************************************************************/
540
/*                           CreateJUMBFBox()                           */
541
/************************************************************************/
542
543
GDALJP2Box *GDALJP2Box::CreateJUMBFBox(const GDALJP2Box *poJUMBFDescriptionBox,
544
                                       int nCount,
545
                                       const GDALJP2Box *const *papoBoxes)
546
0
{
547
0
    std::vector<const GDALJP2Box *> apoBoxes;
548
0
    apoBoxes.push_back(poJUMBFDescriptionBox);
549
0
    apoBoxes.insert(apoBoxes.end(), papoBoxes, papoBoxes + nCount);
550
0
    return CreateSuperBox("jumb", static_cast<int>(apoBoxes.size()),
551
0
                          apoBoxes.data());
552
0
}
553
554
/*! @endcond */