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