#include #include #include "website.h" struct ZIP_Headers { struct iovec *cur_iv; u8 *cur_data; int top_dir; u32 cur_offset; }; struct ZIP_Stats { size_t total_file_size; int total_name_size; int n_entries; int top_dir; }; static void enum_dirs(Filesystem *fs, int didx, void *data) { auto stats = (ZIP_Stats*)data; stats->n_entries++; int top_name = fs->dirs[stats->top_dir].name_idx; int path_len = top_name < 0 ? 1 : strlen(fs->name_pool.at(top_name)) + 1; int d = didx; while (d >= 0 && d != stats->top_dir) { path_len += strlen(fs->name_pool.at(fs->dirs[d].name_idx)) + 1; d = fs->dirs[d].parent; } stats->total_name_size += path_len; } static void enum_files(Filesystem *fs, int fidx, void *data) { auto stats = (ZIP_Stats*)data; stats->n_entries++; // n_entries stats->total_file_size += (size_t)fs->files[fidx].size; // total_size int top_name = fs->dirs[stats->top_dir].name_idx; int path_len = top_name < 0 ? 1 : strlen(fs->name_pool.at(top_name)) + 1; path_len += strlen(fs->name_pool.at(fs->files[fidx].name_idx)); int parent = fs->files[fidx].parent; while (parent >= 0 && parent != stats->top_dir) { path_len += strlen(fs->name_pool.at(fs->dirs[parent].name_idx)) + 1; parent = fs->dirs[parent].parent; } stats->total_name_size += path_len; } static void write_dir_header(Filesystem *fs, int didx, void *data) { auto hdr = (ZIP_Headers*)data; u8 *p = hdr->cur_data; *p++ = 'P'; *p++ = 'K'; *p++ = 3; *p++ = 4; *p++ = 10; p += 25; char *name = fs->name_pool.at(fs->dirs[didx].name_idx); int name_len = 0; if (didx == hdr->top_dir) { name_len = strlen(name); memcpy(p, name, name_len); } else { FS_Directory *dir = &fs->dirs[didx]; name_len = fs->get_path((char*)p, hdr->top_dir, dir->parent, name); } name_len++; // add trailing slash p[-4] = (u8)name_len; p[-3] = (u8)(name_len >> 8); p += name_len; p[-1] = '/'; *hdr->cur_iv++ = { hdr->cur_data, (size_t)(p - hdr->cur_data) }; hdr->cur_data = p; } static void write_file_header(Filesystem *fs, int fidx, void *data) { auto hdr = (ZIP_Headers*)data; FS_File *file = &fs->files[fidx]; u8 *p = hdr->cur_data; *p++ = 'P'; *p++ = 'K'; *p++ = 3; *p++ = 4; *p++ = 10; p += 9; *p++ = (u8)file->crc; *p++ = (u8)(file->crc >> 8); *p++ = (u8)(file->crc >> 16); *p++ = (u8)(file->crc >> 24); p[0] = p[4] = (u8)file->size; p[1] = p[5] = (u8)(file->size >> 8); p[2] = p[6] = (u8)(file->size >> 16); p[3] = p[7] = (u8)(file->size >> 24); p += 12; int name_len = fs->get_path((char*)p, hdr->top_dir, file->parent, fs->name_pool.at(fs->files[fidx].name_idx)); p[-4] = (u8)name_len; p[-3] = (u8)(name_len >> 8); p += name_len; *hdr->cur_iv++ = { hdr->cur_data, (size_t)(p - hdr->cur_data) }; hdr->cur_data = p; if (file->size) *hdr->cur_iv++ = { file->buffer, (size_t)file->size }; } static void write_dir_central(Filesystem *fs, int didx, void *data) { auto hdr = (ZIP_Headers*)data; u8 *p = hdr->cur_data; *p++ = 'P'; *p++ = 'K'; *p++ = 1; *p++ = 2; *p++ = 30; *p++ = 3; *p++ = 10; p += 21; char *name = fs->name_pool.at(fs->dirs[didx].name_idx); int name_len = 0; if (didx == hdr->top_dir) { name_len = strlen(name); memcpy(&p[18], name, name_len); } else { FS_Directory *dir = &fs->dirs[didx]; name_len = fs->get_path((char*)&p[18], hdr->top_dir, dir->parent, name); } name_len++; // add trailing slash *p++ = (u8)name_len; *p++ = (u8)(name_len >> 8); p += 8; *p++ = 0x10; *p++ = 0; *p++ = 0xb6; // permissions 1: w-rw-rw- *p++ = 0x41; // permissions 2: dr *p++ = (u8)hdr->cur_offset; *p++ = (u8)(hdr->cur_offset >> 8); *p++ = (u8)(hdr->cur_offset >> 16); *p++ = (u8)(hdr->cur_offset >> 24); p += name_len; p[-1] = '/'; *hdr->cur_iv++ = { hdr->cur_data, (size_t)(p - hdr->cur_data) }; hdr->cur_data = p; hdr->cur_offset += 30 + name_len; } static void write_file_central(Filesystem *fs, int fidx, void *data) { auto hdr = (ZIP_Headers*)data; FS_File *file = &fs->files[fidx]; u8 *p = hdr->cur_data; *p++ = 'P'; *p++ = 'K'; *p++ = 1; *p++ = 2; *p++ = 30; *p++ = 3; *p++ = 10; p += 9; *p++ = (u8)file->crc; *p++ = (u8)(file->crc >> 8); *p++ = (u8)(file->crc >> 16); *p++ = (u8)(file->crc >> 24); p[0] = p[4] = (u8)file->size; p[1] = p[5] = (u8)(file->size >> 8); p[2] = p[6] = (u8)(file->size >> 16); p[3] = p[7] = (u8)(file->size >> 24); p += 8; int name_len = fs->get_path((char*)&p[18], hdr->top_dir, file->parent, fs->name_pool.at(fs->files[fidx].name_idx)); *p++ = (u8)name_len; *p++ = (u8)(name_len >> 8); p += 10; *p++ = 0xb6; // permissions 1: w-rw-rw- *p++ = 0x81; // permissions 2: -r *p++ = (u8)hdr->cur_offset; *p++ = (u8)(hdr->cur_offset >> 8); *p++ = (u8)(hdr->cur_offset >> 16); *p++ = (u8)(hdr->cur_offset >> 24); p += name_len; *hdr->cur_iv++ = { hdr->cur_data, (size_t)(p - hdr->cur_data) }; hdr->cur_data = p; hdr->cur_offset += 30 + name_len + file->size; } // Creates an uncompressed .zip file from a directory by creating ZIP headers for each file, // then writes the output as a scattered set of messages to the given socket void write_zip_as_response(Filesystem& fs, int dir_idx, Response& response) { response.format = RESPONSE_MULTI; ZIP_Stats stats = {0}; stats.top_dir = dir_idx; fs.walk(dir_idx, FS_ORDER_ALPHA, enum_dirs, enum_files, &stats); int zip_headers_size = 22 + stats.n_entries * (30 + 46) + stats.total_name_size * 2; // combined file size + combined zip header size + size of ZIP "end of central directory record" size_t response_size = stats.total_file_size + zip_headers_size; char datetime[128]; char http_buf[512]; String http((char*)http_buf, 512); get_datetime(datetime); http.reformat( "HTTP/1.1 200 OK\r\n" "Date: {s} GMT\r\n" "Server: jackbendtsen.com.au\r\n" "Content-Disposition: attachment\r\n" "Content-Type: application/zip\r\n" "Content-Length: {d}\r\n\r\n", datetime, response_size ); int n_msgs = 3 * stats.n_entries + 2; int buf_size = sizeof(struct msghdr) + n_msgs * sizeof(struct iovec) + http.len + zip_headers_size; response.html.resize(buf_size + 4); // +4 just in case u8 *buf = (u8*)response.html.data(); memset(buf, 0, buf_size); auto msg = (struct msghdr*)buf; msg->msg_iov = (struct iovec*)&buf[sizeof(struct msghdr)]; memcpy(&msg->msg_iov[n_msgs], http.data(), http.len); msg->msg_iov[0] = { &msg->msg_iov[n_msgs], (size_t)http.len }; ZIP_Headers zip = { &msg->msg_iov[1], &buf[buf_size - zip_headers_size], dir_idx, 0 }; fs.walk(dir_idx, FS_ORDER_ALPHA, write_dir_header, write_file_header, &zip); u8 *zip_local_end = zip.cur_data; fs.walk(dir_idx, FS_ORDER_ALPHA, write_dir_central, write_file_central, &zip); u8 *zip_end = &buf[buf_size - 22]; memset(zip_end, 0, 22); { u8 *p = zip_end; *p++ = 'P'; *p++ = 'K'; *p++ = 5; *p++ = 6; *(u32*)p = 0; p += 4; p[0] = p[2] = (u8)stats.n_entries; p[1] = p[3] = (u8)(stats.n_entries >> 8); p += 4; u32 central_offset = zip.cur_offset; u32 central_size = (u32)(zip.cur_data - zip_local_end); *p++ = (u8)central_size; *p++ = (u8)(central_size >> 8); *p++ = (u8)(central_size >> 16); *p++ = (u8)(central_size >> 24); *p++ = (u8)central_offset; *p++ = (u8)(central_offset >> 8); *p++ = (u8)(central_offset >> 16); *p++ = (u8)(central_offset >> 24); } *zip.cur_iv++ = { zip_end, 22 }; msg->msg_iovlen = (size_t)((u8*)zip.cur_iv - &buf[sizeof(struct msghdr)]) / sizeof(struct iovec); }