1 | /* |
2 | * ISO9660 support routines for PhysicsFS. |
3 | * |
4 | * Please see the file LICENSE.txt in the source's root directory. |
5 | * |
6 | * This file originally written by Christoph Nelles, but was largely |
7 | * rewritten by Ryan C. Gordon (so please harass Ryan about bugs and not |
8 | * Christoph). |
9 | */ |
10 | |
11 | /* |
12 | * Handles CD-ROM disk images (and raw CD-ROM devices). |
13 | * |
14 | * Not supported: |
15 | * - Rock Ridge (needed for sparse files, device nodes and symlinks, etc). |
16 | * - Non 2048 Sectors |
17 | * - TRANS.TBL (maps 8.3 filenames on old discs to long filenames). |
18 | * - Multiextents (4gb max file size without it). |
19 | * - UDF |
20 | * |
21 | * Deviations from the standard |
22 | * - Ignores mandatory sort order |
23 | * - Allows various invalid file names |
24 | * |
25 | * Problems |
26 | * - Ambiguities in the standard |
27 | */ |
28 | |
29 | #define __PHYSICSFS_INTERNAL__ |
30 | #include "physfs_internal.h" |
31 | |
32 | #if PHYSFS_SUPPORTS_ISO9660 |
33 | |
34 | #include <time.h> |
35 | |
36 | /* ISO9660 often stores values in both big and little endian formats: little |
37 | first, followed by big. While technically there might be different values |
38 | in each, we just always use the littleendian ones and swap ourselves. The |
39 | fields aren't aligned anyhow, so you have to serialize them in any case |
40 | to avoid crashes on many CPU archs in any case. */ |
41 | |
42 | static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet, |
43 | const char *base, const PHYSFS_uint64 dirstart, |
44 | const PHYSFS_uint64 dirend, void *unpkarc); |
45 | |
46 | static int iso9660AddEntry(PHYSFS_Io *io, const int joliet, const int isdir, |
47 | const char *base, PHYSFS_uint8 *fname, |
48 | const int fnamelen, const PHYSFS_sint64 ts, |
49 | const PHYSFS_uint64 pos, const PHYSFS_uint64 len, |
50 | void *unpkarc) |
51 | { |
52 | char *fullpath; |
53 | char *fnamecpy; |
54 | size_t baselen; |
55 | size_t fullpathlen; |
56 | void *entry; |
57 | int i; |
58 | |
59 | if (fnamelen == 1 && ((fname[0] == 0) || (fname[0] == 1))) |
60 | return 1; /* Magic that represents "." and "..", ignore */ |
61 | |
62 | BAIL_IF(fnamelen == 0, PHYSFS_ERR_CORRUPT, 0); |
63 | assert(fnamelen > 0); |
64 | assert(fnamelen <= 255); |
65 | BAIL_IF(joliet && (fnamelen % 2), PHYSFS_ERR_CORRUPT, 0); |
66 | |
67 | /* Joliet is UCS-2, so at most UTF-8 will double the byte size */ |
68 | baselen = strlen(base); |
69 | fullpathlen = baselen + (fnamelen * (joliet ? 2 : 1)) + 2; |
70 | fullpath = (char *) __PHYSFS_smallAlloc(fullpathlen); |
71 | BAIL_IF(!fullpath, PHYSFS_ERR_OUT_OF_MEMORY, 0); |
72 | fnamecpy = fullpath; |
73 | if (baselen > 0) |
74 | { |
75 | snprintf(fullpath, fullpathlen, "%s/" , base); |
76 | fnamecpy += baselen + 1; |
77 | fullpathlen -= baselen - 1; |
78 | } /* if */ |
79 | |
80 | if (joliet) |
81 | { |
82 | PHYSFS_uint16 *ucs2 = (PHYSFS_uint16 *) fname; |
83 | int total = fnamelen / 2; |
84 | for (i = 0; i < total; i++) |
85 | ucs2[i] = PHYSFS_swapUBE16(ucs2[i]); |
86 | ucs2[total] = '\0'; |
87 | PHYSFS_utf8FromUcs2(ucs2, fnamecpy, fullpathlen); |
88 | } /* if */ |
89 | else |
90 | { |
91 | for (i = 0; i < fnamelen; i++) |
92 | { |
93 | /* We assume the filenames are low-ASCII; consider the archive |
94 | corrupt if we see something above 127, since we don't know the |
95 | encoding. (We can change this later if we find out these exist |
96 | and are intended to be, say, latin-1 or UTF-8 encoding). */ |
97 | BAIL_IF(fname[i] > 127, PHYSFS_ERR_CORRUPT, 0); |
98 | fnamecpy[i] = fname[i]; |
99 | } /* for */ |
100 | fnamecpy[fnamelen] = '\0'; |
101 | |
102 | if (!isdir) |
103 | { |
104 | /* find last SEPARATOR2 */ |
105 | char *ptr = strrchr(fnamecpy, ';'); |
106 | if (ptr && (ptr != fnamecpy)) |
107 | *(ptr--) = '\0'; |
108 | else |
109 | ptr = fnamecpy + (fnamelen - 1); |
110 | |
111 | /* chop out any trailing '.', as done in all implementations */ |
112 | if (*ptr == '.') |
113 | *ptr = '\0'; |
114 | } /* if */ |
115 | } /* else */ |
116 | |
117 | entry = UNPK_addEntry(unpkarc, fullpath, isdir, ts, ts, pos, len); |
118 | if ((entry) && (isdir)) |
119 | { |
120 | if (!iso9660LoadEntries(io, joliet, fullpath, pos, pos + len, unpkarc)) |
121 | entry = NULL; /* so we report a failure later. */ |
122 | } /* if */ |
123 | |
124 | __PHYSFS_smallFree(fullpath); |
125 | return entry != NULL; |
126 | } /* iso9660AddEntry */ |
127 | |
128 | static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet, |
129 | const char *base, const PHYSFS_uint64 dirstart, |
130 | const PHYSFS_uint64 dirend, void *unpkarc) |
131 | { |
132 | PHYSFS_uint64 readpos = dirstart; |
133 | |
134 | while (1) |
135 | { |
136 | PHYSFS_uint8 recordlen; |
137 | PHYSFS_uint8 extattrlen; |
138 | PHYSFS_uint32 extent; |
139 | PHYSFS_uint32 datalen; |
140 | PHYSFS_uint8 ignore[4]; |
141 | PHYSFS_uint8 year, month, day, hour, minute, second, offset; |
142 | PHYSFS_uint8 flags; |
143 | PHYSFS_uint8 fnamelen; |
144 | PHYSFS_uint8 fname[256]; |
145 | PHYSFS_sint64 timestamp; |
146 | struct tm t; |
147 | int isdir; |
148 | int multiextent; |
149 | |
150 | BAIL_IF_ERRPASS(!io->seek(io, readpos), 0); |
151 | |
152 | /* recordlen = 0 -> no more entries or fill entry */ |
153 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &recordlen, 1), 0); |
154 | if (recordlen > 0) |
155 | readpos += recordlen; /* ready to seek to next record. */ |
156 | else |
157 | { |
158 | PHYSFS_uint64 nextpos; |
159 | |
160 | /* if we are in the last sector of the directory & it's 0 -> end */ |
161 | if ((dirend - 2048) <= (readpos - 1)) |
162 | break; /* finished */ |
163 | |
164 | /* else skip to the next sector & continue; */ |
165 | nextpos = (((readpos - 1) / 2048) + 1) * 2048; |
166 | |
167 | /* whoops, can't make forward progress! */ |
168 | BAIL_IF(nextpos == readpos, PHYSFS_ERR_CORRUPT, 0); |
169 | |
170 | readpos = nextpos; |
171 | continue; /* start back at upper loop. */ |
172 | } /* else */ |
173 | |
174 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extattrlen, 1), 0); |
175 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0); |
176 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */ |
177 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0); |
178 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */ |
179 | |
180 | /* record timestamp */ |
181 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &year, 1), 0); |
182 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &month, 1), 0); |
183 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &day, 1), 0); |
184 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &hour, 1), 0); |
185 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &minute, 1), 0); |
186 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &second, 1), 0); |
187 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &offset, 1), 0); |
188 | |
189 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0); |
190 | isdir = (flags & (1 << 1)) != 0; |
191 | multiextent = (flags & (1 << 7)) != 0; |
192 | BAIL_IF(multiextent, PHYSFS_ERR_UNSUPPORTED, 0); /* !!! FIXME */ |
193 | |
194 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* unit size */ |
195 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* interleave gap */ |
196 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum le */ |
197 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum be */ |
198 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &fnamelen, 1), 0); |
199 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, fname, fnamelen), 0); |
200 | |
201 | t.tm_sec = second; |
202 | t.tm_min = minute; |
203 | t.tm_hour = hour; |
204 | t.tm_mday = day; |
205 | t.tm_mon = month - 1; |
206 | t.tm_year = year; |
207 | t.tm_wday = 0; |
208 | t.tm_yday = 0; |
209 | t.tm_isdst = -1; |
210 | timestamp = (PHYSFS_sint64) mktime(&t); |
211 | |
212 | extent += extattrlen; /* skip extended attribute record. */ |
213 | |
214 | /* infinite loop, corrupt file? */ |
215 | BAIL_IF((extent * 2048) == dirstart, PHYSFS_ERR_CORRUPT, 0); |
216 | |
217 | if (!iso9660AddEntry(io, joliet, isdir, base, fname, fnamelen, |
218 | timestamp, extent * 2048, datalen, unpkarc)) |
219 | { |
220 | return 0; |
221 | } /* if */ |
222 | } /* while */ |
223 | |
224 | return 1; |
225 | } /* iso9660LoadEntries */ |
226 | |
227 | |
228 | static int parseVolumeDescriptor(PHYSFS_Io *io, PHYSFS_uint64 *_rootpos, |
229 | PHYSFS_uint64 *_rootlen, int *_joliet, |
230 | int *_claimed) |
231 | { |
232 | PHYSFS_uint64 pos = 32768; /* start at the Primary Volume Descriptor */ |
233 | int found = 0; |
234 | int done = 0; |
235 | |
236 | *_joliet = 0; |
237 | |
238 | while (!done) |
239 | { |
240 | PHYSFS_uint8 type; |
241 | PHYSFS_uint8 identifier[5]; |
242 | PHYSFS_uint8 version; |
243 | PHYSFS_uint8 flags; |
244 | PHYSFS_uint8 escapeseqs[32]; |
245 | PHYSFS_uint8 ignore[32]; |
246 | PHYSFS_uint16 blocksize; |
247 | PHYSFS_uint32 extent; |
248 | PHYSFS_uint32 datalen; |
249 | |
250 | BAIL_IF_ERRPASS(!io->seek(io, pos), 0); |
251 | pos += 2048; /* each volume descriptor is 2048 bytes */ |
252 | |
253 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0); |
254 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, identifier, 5), 0); |
255 | |
256 | if (memcmp(identifier, "CD001" , 5) != 0) /* maybe not an iso? */ |
257 | { |
258 | BAIL_IF(!*_claimed, PHYSFS_ERR_UNSUPPORTED, 0); |
259 | continue; /* just skip this one */ |
260 | } /* if */ |
261 | |
262 | *_claimed = 1; /* okay, this is probably an iso. */ |
263 | |
264 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, 1), 0); /* version */ |
265 | BAIL_IF(version != 1, PHYSFS_ERR_UNSUPPORTED, 0); |
266 | |
267 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0); |
268 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* system id */ |
269 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* volume id */ |
270 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 8), 0); /* reserved */ |
271 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space le */ |
272 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space be */ |
273 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, escapeseqs, 32), 0); |
274 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize le */ |
275 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize be */ |
276 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num le */ |
277 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num be */ |
278 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &blocksize, 2), 0); |
279 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* blocklen be */ |
280 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen le */ |
281 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen be */ |
282 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos le */ |
283 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos le */ |
284 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos be */ |
285 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos be */ |
286 | |
287 | /* root directory record... */ |
288 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* len */ |
289 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* attr len */ |
290 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0); |
291 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */ |
292 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0); |
293 | BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */ |
294 | |
295 | /* !!! FIXME: deal with this properly. */ |
296 | blocksize = PHYSFS_swapULE32(blocksize); |
297 | BAIL_IF(blocksize && (blocksize != 2048), PHYSFS_ERR_UNSUPPORTED, 0); |
298 | |
299 | switch (type) |
300 | { |
301 | case 1: /* Primary Volume Descriptor */ |
302 | case 2: /* Supplementary Volume Descriptor */ |
303 | if (found < type) |
304 | { |
305 | *_rootpos = PHYSFS_swapULE32(extent) * 2048; |
306 | *_rootlen = PHYSFS_swapULE32(datalen); |
307 | found = type; |
308 | |
309 | if (found == 2) /* possible Joliet volume */ |
310 | { |
311 | const PHYSFS_uint8 *s = escapeseqs; |
312 | *_joliet = !(flags & 1) && |
313 | (s[0] == 0x25) && (s[1] == 0x2F) && |
314 | ((s[2] == 0x40) || (s[2] == 0x43) || (s[2] == 0x45)); |
315 | } /* if */ |
316 | } /* if */ |
317 | break; |
318 | |
319 | case 255: /* type 255 terminates the volume descriptor list */ |
320 | done = 1; |
321 | break; |
322 | |
323 | default: |
324 | break; /* skip unknown types. */ |
325 | } /* switch */ |
326 | } /* while */ |
327 | |
328 | BAIL_IF(!found, PHYSFS_ERR_CORRUPT, 0); |
329 | |
330 | return 1; |
331 | } /* parseVolumeDescriptor */ |
332 | |
333 | |
334 | static void *ISO9660_openArchive(PHYSFS_Io *io, const char *filename, |
335 | int forWriting, int *claimed) |
336 | { |
337 | PHYSFS_uint64 rootpos = 0; |
338 | PHYSFS_uint64 len = 0; |
339 | int joliet = 0; |
340 | void *unpkarc = NULL; |
341 | |
342 | assert(io != NULL); /* shouldn't ever happen. */ |
343 | |
344 | BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); |
345 | |
346 | if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed)) |
347 | return NULL; |
348 | |
349 | /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */ |
350 | unpkarc = UNPK_openArchive(io, 1, 0); |
351 | BAIL_IF_ERRPASS(!unpkarc, NULL); |
352 | |
353 | if (!iso9660LoadEntries(io, joliet, "" , rootpos, rootpos + len, unpkarc)) |
354 | { |
355 | UNPK_abandonArchive(unpkarc); |
356 | return NULL; |
357 | } /* if */ |
358 | |
359 | return unpkarc; |
360 | } /* ISO9660_openArchive */ |
361 | |
362 | |
363 | const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660 = |
364 | { |
365 | CURRENT_PHYSFS_ARCHIVER_API_VERSION, |
366 | { |
367 | "ISO" , |
368 | "ISO9660 image file" , |
369 | "Ryan C. Gordon <icculus@icculus.org>" , |
370 | "https://icculus.org/physfs/" , |
371 | 0, /* supportsSymlinks */ |
372 | }, |
373 | ISO9660_openArchive, |
374 | UNPK_enumerate, |
375 | UNPK_openRead, |
376 | UNPK_openWrite, |
377 | UNPK_openAppend, |
378 | UNPK_remove, |
379 | UNPK_mkdir, |
380 | UNPK_stat, |
381 | UNPK_closeArchive |
382 | }; |
383 | |
384 | #endif /* defined PHYSFS_SUPPORTS_ISO9660 */ |
385 | |
386 | /* end of physfs_archiver_iso9660.c ... */ |
387 | |
388 | |