#include #include #include #include #include #include #include #include #include #include #include #include #include "applefile.h" #include "mapped_file.h" void damaged_file() { throw std::runtime_error("File is damaged."); } void throw_errno() { throw std::system_error(errno, std::generic_category()); } void throw_errno(const std::string &what) { throw std::system_error(errno, std::generic_category(), what); } class defer { public: typedef std::function FX; defer() = default; defer(FX fx) : _fx(fx) {} defer(const defer &) = delete; defer(defer &&) = default; defer & operator=(const defer &) = delete; defer & operator=(defer &&) = default; void cancel() { _fx = nullptr; } ~defer() { if (_fx) _fx(); } private: FX _fx; }; void unfork(const char *in, const char *out) { static_assert(sizeof(ASHeader) == 26, "ASHeader size is wrong."); static_assert(sizeof(ASEntry) == 12, "ASEntry size is wrong."); mapped_file mf(in, mapped_file::priv); if (mf.size() < sizeof(ASHeader)) damaged_file(); ASHeader *header = (ASHeader *)mf.data(); header->magicNum = ntohl(header->magicNum); header->versionNum = ntohl(header->versionNum); header->numEntries = ntohs(header->numEntries); if (header->magicNum != 0x00051600 || header->versionNum != 0x00020000 || header->numEntries == 0) throw std::runtime_error("Not an AppleSingle File"); if (header->numEntries * sizeof(ASEntry) + sizeof(ASHeader) > mf.size()) damaged_file(); ASEntry *begin = (ASEntry *)(mf.data() + sizeof(ASHeader)); ASEntry *end = &begin[header->numEntries]; std::for_each(begin, end, [](ASEntry &e){ e.entryID = ntohl(e.entryID); e.entryOffset = ntohl(e.entryOffset); e.entryLength = ntohl(e.entryLength); }); // check for truncation.... for (auto iter = begin; iter != end; ++iter) { const auto &e = *iter; if (!e.entryLength) continue; if (e.entryOffset > mf.size()) damaged_file(); if (e.entryOffset + e.entryLength > mf.size()) damaged_file(); } std::string outname = out ? out : ""; // if no name, pull it from the name record. if (!out) { auto iter = std::find_if(begin, end, [](const ASEntry &e) { return e.entryID == AS_REALNAME; }); if (iter != end) { outname.assign((const char *)mf.data() + iter->entryOffset, iter->entryLength); } } if (outname.empty()) throw std::runtime_error("No filename"); int fd = open(outname.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fd < 1) throw_errno(); defer close_fd([fd](){ close(fd); }); for (auto iter = begin; iter != end; ++iter) { const auto &e = *iter; if (e.entryLength == 0) continue; switch(e.entryID) { case AS_DATA: { ssize_t ok = write(fd, mf.data() + e.entryOffset, e.entryLength); if (ok < 0) throw_errno(); //if (ok != e.entryLength) return -1; break; } case AS_RESOURCE: { int rfd = openat(fd, "com.apple.ResourceFork", O_XATTR | O_CREAT | O_TRUNC | O_WRONLY, 0666); if (rfds < 0) throw_errno("com.apple.ResourceFork"); defer close_fd([rfd](){ close(rfd); }); ssize_t ok = write(rfd, mf.data() + e.entryOffset, e.entryLength); if (ok < 0) throw_errno("com.apple.ResourceFork"); //if (ok != e.entryLength) return -1; break; } case AS_FINDERINFO: { if (e.entryLength != 32) { fputs("Warning: Invalid Finder Info size.\n", stderr); break; } int rfd = openat(fd, "com.apple.FinderInfo", O_XATTR | O_CREAT | O_TRUNC | O_WRONLY, 0666); if (rfd < 0) throw_errno("com.apple.ResourceFork"); defer close_fd([rfd](){ close(rfd); }); ssize_t ok = write(rfd, mf.data() + e.entryOffset, e.entryLength); if (ok < 0) throw_errno("com.apple.FinderInfo"); //if (ok != e.entryLength) return -1; break; } } } } void usage() { fputs("Usage: unfork [-o file] file...\n", stderr); exit(EX_USAGE); } int main(int argc, char **argv) { int c; char *_o; while ((c = getopt(argc, argv, "o:")) != -1) { switch(c) { case 'o': _o = optarg; break; case ':': case '?': default: usage(); break; } } argv += optind; argc -= optind; int rv = 0; for (int i = 0; i < argc; ++i) { try { unfork(argv[i], _o); } catch (std::exception &ex) { fprintf(stderr, "%s : %s\n", argv[i], ex.what()); rv = 1; } _o = nullptr; } return rv; }