Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PDS Translator
4 : * Purpose: Implements OGRPDSDataSource class
5 : * Author: Even Rouault, even dot rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2011, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_conv.h"
14 : #include "cpl_string.h"
15 : #include "ogr_pds.h"
16 :
17 : #include <limits>
18 :
19 : using namespace OGRPDS;
20 :
21 : /************************************************************************/
22 : /* OGRPDSDataSource() */
23 : /************************************************************************/
24 :
25 2 : OGRPDSDataSource::OGRPDSDataSource() : papoLayers(nullptr), nLayers(0)
26 : {
27 2 : }
28 :
29 : /************************************************************************/
30 : /* ~OGRPDSDataSource() */
31 : /************************************************************************/
32 :
33 4 : OGRPDSDataSource::~OGRPDSDataSource()
34 :
35 : {
36 4 : for (int i = 0; i < nLayers; i++)
37 2 : delete papoLayers[i];
38 2 : CPLFree(papoLayers);
39 4 : }
40 :
41 : /************************************************************************/
42 : /* GetLayer() */
43 : /************************************************************************/
44 :
45 2 : const OGRLayer *OGRPDSDataSource::GetLayer(int iLayer) const
46 :
47 : {
48 2 : if (iLayer < 0 || iLayer >= nLayers)
49 0 : return nullptr;
50 :
51 2 : return papoLayers[iLayer];
52 : }
53 :
54 : /************************************************************************/
55 : /* GetKeywordSub() */
56 : /************************************************************************/
57 :
58 2 : const char *OGRPDSDataSource::GetKeywordSub(const char *pszPath, int iSubscript,
59 : const char *pszDefault)
60 :
61 : {
62 2 : const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
63 :
64 2 : if (pszResult == nullptr)
65 0 : return pszDefault;
66 :
67 2 : if (pszResult[0] != '(')
68 0 : return pszDefault;
69 :
70 : char **papszTokens =
71 2 : CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
72 :
73 2 : if (iSubscript <= CSLCount(papszTokens))
74 : {
75 2 : osTempResult = papszTokens[iSubscript - 1];
76 2 : CSLDestroy(papszTokens);
77 2 : return osTempResult.c_str();
78 : }
79 :
80 0 : CSLDestroy(papszTokens);
81 0 : return pszDefault;
82 : }
83 :
84 : /************************************************************************/
85 : /* CleanString() */
86 : /* */
87 : /* Removes single or double quotes, and converts spaces to underscores. */
88 : /* The change is made in-place to CPLString. */
89 : /************************************************************************/
90 :
91 126 : void OGRPDSDataSource::CleanString(CPLString &osInput)
92 :
93 : {
94 252 : if ((osInput.size() < 2) ||
95 126 : ((osInput.at(0) != '"' || osInput.at(osInput.size() - 1) != '"') &&
96 65 : (osInput.at(0) != '\'' || osInput.at(osInput.size() - 1) != '\'')))
97 65 : return;
98 :
99 61 : char *pszWrk = CPLStrdup(osInput.c_str() + 1);
100 :
101 61 : pszWrk[strlen(pszWrk) - 1] = '\0';
102 :
103 956 : for (int i = 0; pszWrk[i] != '\0'; i++)
104 : {
105 895 : if (pszWrk[i] == ' ')
106 0 : pszWrk[i] = '_';
107 : }
108 :
109 61 : osInput = pszWrk;
110 61 : CPLFree(pszWrk);
111 : }
112 :
113 : /************************************************************************/
114 : /* LoadTable() */
115 : /************************************************************************/
116 :
117 8 : static CPLString MakeAttr(CPLString os1, CPLString os2)
118 : {
119 16 : return os1 + "." + os2;
120 : }
121 :
122 2 : bool OGRPDSDataSource::LoadTable(const char *pszFilename, int nRecordSize,
123 : CPLString osTableID)
124 : {
125 4 : CPLString osTableFilename;
126 2 : vsi_l_offset nStartBytes = 0;
127 :
128 4 : CPLString osTableLink = "^";
129 2 : osTableLink += osTableID;
130 :
131 4 : CPLString osTable = oKeywords.GetKeyword(osTableLink, "");
132 2 : if (osTable[0] == '(')
133 : {
134 1 : osTableFilename = GetKeywordSub(osTableLink, 1, "");
135 1 : CPLString osStartRecord = GetKeywordSub(osTableLink, 2, "");
136 1 : nStartBytes = std::strtoull(osStartRecord.c_str(), nullptr, 10);
137 2 : if (nStartBytes == 0 ||
138 1 : (nRecordSize > 0 &&
139 1 : nStartBytes > std::numeric_limits<vsi_l_offset>::max() /
140 1 : static_cast<size_t>(nRecordSize)))
141 : {
142 0 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid StartBytes value");
143 0 : return false;
144 : }
145 1 : nStartBytes--;
146 1 : nStartBytes *= nRecordSize;
147 1 : if (osTableFilename.empty() || osStartRecord.empty())
148 : {
149 0 : CPLError(CE_Failure, CPLE_NotSupported, "Cannot parse %s line",
150 : osTableLink.c_str());
151 0 : return false;
152 : }
153 1 : CPLString osTPath = CPLGetPathSafe(pszFilename);
154 1 : CleanString(osTableFilename);
155 :
156 1 : if (CPLHasPathTraversal(osTableFilename.c_str()))
157 : {
158 0 : CPLError(CE_Failure, CPLE_AppDefined,
159 : "Path traversal detected in %s", osTableFilename.c_str());
160 0 : return false;
161 : }
162 :
163 : osTableFilename =
164 1 : CPLFormCIFilenameSafe(osTPath, osTableFilename, nullptr);
165 : }
166 : else
167 : {
168 1 : osTableFilename = oKeywords.GetKeyword(osTableLink, "");
169 1 : if (!osTableFilename.empty() && osTableFilename[0] >= '0' &&
170 0 : osTableFilename[0] <= '9')
171 : {
172 0 : nStartBytes = std::strtoull(osTableFilename.c_str(), nullptr, 10);
173 0 : if (nStartBytes == 0)
174 : {
175 0 : CPLError(CE_Failure, CPLE_NotSupported, "Cannot parse %s line",
176 : osTableFilename.c_str());
177 0 : return false;
178 : }
179 0 : nStartBytes = nStartBytes - 1;
180 0 : if (strstr(osTableFilename.c_str(), "<BYTES>") == nullptr)
181 : {
182 0 : if (nRecordSize > 0 &&
183 0 : nStartBytes > std::numeric_limits<vsi_l_offset>::max() /
184 0 : static_cast<size_t>(nRecordSize))
185 : {
186 0 : CPLError(CE_Failure, CPLE_NotSupported,
187 : "Too big StartBytes value");
188 0 : return false;
189 : }
190 0 : nStartBytes *= nRecordSize;
191 : }
192 0 : osTableFilename = pszFilename;
193 : }
194 : else
195 : {
196 1 : CPLString osTPath = CPLGetPathSafe(pszFilename);
197 1 : CleanString(osTableFilename);
198 :
199 1 : if (CPLHasPathTraversal(osTableFilename.c_str()))
200 : {
201 0 : CPLError(CE_Failure, CPLE_AppDefined,
202 : "Path traversal detected in %s",
203 : osTableFilename.c_str());
204 0 : return false;
205 : }
206 :
207 : osTableFilename =
208 1 : CPLFormCIFilenameSafe(osTPath, osTableFilename, nullptr);
209 1 : nStartBytes = 0;
210 : }
211 : }
212 :
213 : CPLString osTableName =
214 6 : oKeywords.GetKeyword(MakeAttr(osTableID, "NAME"), "");
215 2 : if (osTableName.empty())
216 : {
217 1 : if (GetLayerByName(osTableID.c_str()) == nullptr)
218 1 : osTableName = osTableID;
219 : else
220 0 : osTableName = CPLSPrintf("Layer_%d", nLayers + 1);
221 : }
222 2 : CleanString(osTableName);
223 : CPLString osTableInterchangeFormat =
224 6 : oKeywords.GetKeyword(MakeAttr(osTableID, "INTERCHANGE_FORMAT"), "");
225 : CPLString osTableRows =
226 6 : oKeywords.GetKeyword(MakeAttr(osTableID, "ROWS"), "");
227 2 : const int nRecords = atoi(osTableRows);
228 2 : if (osTableInterchangeFormat.empty() || osTableRows.empty() || nRecords < 0)
229 : {
230 0 : CPLError(CE_Failure, CPLE_NotSupported,
231 : "One of TABLE.INTERCHANGE_FORMAT or TABLE.ROWS is missing");
232 0 : return false;
233 : }
234 :
235 2 : CleanString(osTableInterchangeFormat);
236 3 : if (osTableInterchangeFormat.compare("ASCII") != 0 &&
237 1 : osTableInterchangeFormat.compare("BINARY") != 0)
238 : {
239 0 : CPLError(CE_Failure, CPLE_NotSupported,
240 : "Only INTERCHANGE_FORMAT=ASCII or BINARY is supported");
241 0 : return false;
242 : }
243 :
244 2 : VSILFILE *fp = VSIFOpenL(osTableFilename, "rb");
245 2 : if (fp == nullptr)
246 : {
247 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
248 : osTableFilename.c_str());
249 0 : return false;
250 : }
251 :
252 : CPLString osTableStructure =
253 6 : oKeywords.GetKeyword(MakeAttr(osTableID, "^STRUCTURE"), "");
254 2 : if (!osTableStructure.empty())
255 : {
256 2 : CPLString osTPath = CPLGetPathSafe(pszFilename);
257 2 : CleanString(osTableStructure);
258 :
259 2 : if (CPLHasPathTraversal(osTableStructure.c_str()))
260 : {
261 0 : CPLError(CE_Failure, CPLE_AppDefined,
262 : "Path traversal detected in %s", osTableStructure.c_str());
263 0 : VSIFCloseL(fp);
264 0 : return false;
265 : }
266 :
267 : osTableStructure =
268 2 : CPLFormCIFilenameSafe(osTPath, osTableStructure, nullptr);
269 : }
270 :
271 2 : GByte *pabyRecord = (GByte *)VSI_MALLOC_VERBOSE(nRecordSize + 1);
272 2 : if (pabyRecord == nullptr)
273 : {
274 0 : VSIFCloseL(fp);
275 0 : return false;
276 : }
277 2 : pabyRecord[nRecordSize] = 0;
278 :
279 2 : papoLayers = static_cast<OGRLayer **>(
280 2 : CPLRealloc(papoLayers, (nLayers + 1) * sizeof(OGRLayer *)));
281 4 : papoLayers[nLayers] = new OGRPDSLayer(
282 : osTableID, osTableName, fp, pszFilename, osTableStructure, nRecords,
283 : nStartBytes, nRecordSize, pabyRecord,
284 4 : osTableInterchangeFormat.compare("ASCII") == 0);
285 2 : nLayers++;
286 :
287 2 : return true;
288 : }
289 :
290 : /************************************************************************/
291 : /* Open() */
292 : /************************************************************************/
293 :
294 2 : int OGRPDSDataSource::Open(const char *pszFilename)
295 :
296 : {
297 : // --------------------------------------------------------------------
298 : // Does this appear to be a .PDS table file?
299 : // --------------------------------------------------------------------
300 :
301 2 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
302 2 : if (fp == nullptr)
303 0 : return FALSE;
304 :
305 : char szBuffer[512];
306 : int nbRead =
307 2 : static_cast<int>(VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fp));
308 2 : szBuffer[nbRead] = '\0';
309 :
310 2 : const char *pszPos = strstr(szBuffer, "PDS_VERSION_ID");
311 2 : const bool bIsPDS = pszPos != nullptr;
312 :
313 2 : if (!bIsPDS)
314 : {
315 0 : VSIFCloseL(fp);
316 0 : return FALSE;
317 : }
318 :
319 2 : if (!oKeywords.Ingest(fp, static_cast<int>(pszPos - szBuffer)))
320 : {
321 0 : VSIFCloseL(fp);
322 0 : return FALSE;
323 : }
324 :
325 2 : VSIFCloseL(fp);
326 4 : CPLString osRecordType = oKeywords.GetKeyword("RECORD_TYPE", "");
327 4 : CPLString osFileRecords = oKeywords.GetKeyword("FILE_RECORDS", "");
328 4 : CPLString osRecordBytes = oKeywords.GetKeyword("RECORD_BYTES", "");
329 2 : int nRecordSize = atoi(osRecordBytes);
330 6 : if (osRecordType.empty() || osFileRecords.empty() ||
331 6 : osRecordBytes.empty() || nRecordSize <= 0 ||
332 : nRecordSize > 10 * 1024 * 1024)
333 : {
334 0 : CPLError(CE_Failure, CPLE_NotSupported,
335 : "One of RECORD_TYPE, FILE_RECORDS or RECORD_BYTES is missing");
336 0 : return FALSE;
337 : }
338 2 : CleanString(osRecordType);
339 2 : if (osRecordType.compare("FIXED_LENGTH") != 0)
340 : {
341 0 : CPLError(CE_Failure, CPLE_NotSupported,
342 : "Only RECORD_TYPE=FIXED_LENGTH is supported");
343 0 : return FALSE;
344 : }
345 :
346 4 : CPLString osTable = oKeywords.GetKeyword("^TABLE", "");
347 2 : if (!osTable.empty())
348 : {
349 2 : LoadTable(pszFilename, nRecordSize, "TABLE");
350 : }
351 : else
352 : {
353 0 : fp = VSIFOpenL(pszFilename, "rb");
354 0 : if (fp == nullptr)
355 0 : return FALSE;
356 :
357 : // To avoid performance issues with datasets generated by oss-fuzz
358 0 : int nErrors = 0;
359 0 : while (nErrors < 10)
360 : {
361 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
362 0 : const char *pszLine = CPLReadLine2L(fp, 256, nullptr);
363 0 : CPLPopErrorHandler();
364 0 : CPLErrorReset();
365 0 : if (pszLine == nullptr)
366 0 : break;
367 : char **papszTokens =
368 0 : CSLTokenizeString2(pszLine, " =", CSLT_HONOURSTRINGS);
369 0 : int nTokens = CSLCount(papszTokens);
370 0 : if (nTokens == 2 && papszTokens[0][0] == '^' &&
371 0 : strstr(papszTokens[0], "TABLE") != nullptr)
372 : {
373 0 : if (!LoadTable(pszFilename, nRecordSize, papszTokens[0] + 1))
374 : {
375 0 : nErrors++;
376 : }
377 : }
378 0 : CSLDestroy(papszTokens);
379 0 : papszTokens = nullptr;
380 : }
381 0 : VSIFCloseL(fp);
382 : }
383 :
384 2 : return nLayers != 0;
385 : }
|