| 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 | |