michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "mar_private.h" michael@0: #include "mar.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #else michael@0: #include michael@0: #endif michael@0: michael@0: michael@0: /* this is the same hash algorithm used by nsZipArchive.cpp */ michael@0: static uint32_t mar_hash_name(const char *name) { michael@0: uint32_t val = 0; michael@0: unsigned char* c; michael@0: michael@0: for (c = (unsigned char *) name; *c; ++c) michael@0: val = val*37 + *c; michael@0: michael@0: return val % TABLESIZE; michael@0: } michael@0: michael@0: static int mar_insert_item(MarFile *mar, const char *name, int namelen, michael@0: uint32_t offset, uint32_t length, uint32_t flags) { michael@0: MarItem *item, *root; michael@0: uint32_t hash; michael@0: michael@0: item = (MarItem *) malloc(sizeof(MarItem) + namelen); michael@0: if (!item) michael@0: return -1; michael@0: item->next = NULL; michael@0: item->offset = offset; michael@0: item->length = length; michael@0: item->flags = flags; michael@0: memcpy(item->name, name, namelen + 1); michael@0: michael@0: hash = mar_hash_name(name); michael@0: michael@0: root = mar->item_table[hash]; michael@0: if (!root) { michael@0: mar->item_table[hash] = item; michael@0: } else { michael@0: /* append item */ michael@0: while (root->next) michael@0: root = root->next; michael@0: root->next = item; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static int mar_consume_index(MarFile *mar, char **buf, const char *buf_end) { michael@0: /* michael@0: * Each item has the following structure: michael@0: * uint32_t offset (network byte order) michael@0: * uint32_t length (network byte order) michael@0: * uint32_t flags (network byte order) michael@0: * char name[N] (where N >= 1) michael@0: * char null_byte; michael@0: */ michael@0: uint32_t offset; michael@0: uint32_t length; michael@0: uint32_t flags; michael@0: const char *name; michael@0: int namelen; michael@0: michael@0: if ((buf_end - *buf) < (int)(3*sizeof(uint32_t) + 2)) michael@0: return -1; michael@0: michael@0: memcpy(&offset, *buf, sizeof(offset)); michael@0: *buf += sizeof(offset); michael@0: michael@0: memcpy(&length, *buf, sizeof(length)); michael@0: *buf += sizeof(length); michael@0: michael@0: memcpy(&flags, *buf, sizeof(flags)); michael@0: *buf += sizeof(flags); michael@0: michael@0: offset = ntohl(offset); michael@0: length = ntohl(length); michael@0: flags = ntohl(flags); michael@0: michael@0: name = *buf; michael@0: /* find namelen; must take care not to read beyond buf_end */ michael@0: while (**buf) { michael@0: if (*buf == buf_end) michael@0: return -1; michael@0: ++(*buf); michael@0: } michael@0: namelen = (*buf - name); michael@0: /* consume null byte */ michael@0: if (*buf == buf_end) michael@0: return -1; michael@0: ++(*buf); michael@0: michael@0: return mar_insert_item(mar, name, namelen, offset, length, flags); michael@0: } michael@0: michael@0: static int mar_read_index(MarFile *mar) { michael@0: char id[MAR_ID_SIZE], *buf, *bufptr, *bufend; michael@0: uint32_t offset_to_index, size_of_index; michael@0: michael@0: /* verify MAR ID */ michael@0: if (fread(id, MAR_ID_SIZE, 1, mar->fp) != 1) michael@0: return -1; michael@0: if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) michael@0: return -1; michael@0: michael@0: if (fread(&offset_to_index, sizeof(uint32_t), 1, mar->fp) != 1) michael@0: return -1; michael@0: offset_to_index = ntohl(offset_to_index); michael@0: michael@0: if (fseek(mar->fp, offset_to_index, SEEK_SET)) michael@0: return -1; michael@0: if (fread(&size_of_index, sizeof(uint32_t), 1, mar->fp) != 1) michael@0: return -1; michael@0: size_of_index = ntohl(size_of_index); michael@0: michael@0: buf = (char *) malloc(size_of_index); michael@0: if (!buf) michael@0: return -1; michael@0: if (fread(buf, size_of_index, 1, mar->fp) != 1) { michael@0: free(buf); michael@0: return -1; michael@0: } michael@0: michael@0: bufptr = buf; michael@0: bufend = buf + size_of_index; michael@0: while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0); michael@0: michael@0: free(buf); michael@0: return (bufptr == bufend) ? 0 : -1; michael@0: } michael@0: michael@0: /** michael@0: * Internal shared code for mar_open and mar_wopen. michael@0: * On failure, will fclose(fp). michael@0: */ michael@0: static MarFile *mar_fpopen(FILE *fp) michael@0: { michael@0: MarFile *mar; michael@0: michael@0: mar = (MarFile *) malloc(sizeof(*mar)); michael@0: if (!mar) { michael@0: fclose(fp); michael@0: return NULL; michael@0: } michael@0: michael@0: mar->fp = fp; michael@0: memset(mar->item_table, 0, sizeof(mar->item_table)); michael@0: if (mar_read_index(mar)) { michael@0: mar_close(mar); michael@0: return NULL; michael@0: } michael@0: michael@0: return mar; michael@0: } michael@0: michael@0: MarFile *mar_open(const char *path) { michael@0: FILE *fp; michael@0: michael@0: fp = fopen(path, "rb"); michael@0: if (!fp) { michael@0: fprintf(stderr, "ERROR: could not open file in mar_open()\n"); michael@0: perror(path); michael@0: return NULL; michael@0: } michael@0: michael@0: return mar_fpopen(fp); michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: MarFile *mar_wopen(const wchar_t *path) { michael@0: FILE *fp; michael@0: michael@0: _wfopen_s(&fp, path, L"rb"); michael@0: if (!fp) { michael@0: fprintf(stderr, "ERROR: could not open file in mar_wopen()\n"); michael@0: _wperror(path); michael@0: return NULL; michael@0: } michael@0: michael@0: return mar_fpopen(fp); michael@0: } michael@0: #endif michael@0: michael@0: void mar_close(MarFile *mar) { michael@0: MarItem *item; michael@0: int i; michael@0: michael@0: fclose(mar->fp); michael@0: michael@0: for (i = 0; i < TABLESIZE; ++i) { michael@0: item = mar->item_table[i]; michael@0: while (item) { michael@0: MarItem *temp = item; michael@0: item = item->next; michael@0: free(temp); michael@0: } michael@0: } michael@0: michael@0: free(mar); michael@0: } michael@0: michael@0: /** michael@0: * Determines the MAR file information. michael@0: * michael@0: * @param fp An opened MAR file in read mode. michael@0: * @param hasSignatureBlock Optional out parameter specifying if the MAR michael@0: * file has a signature block or not. michael@0: * @param numSignatures Optional out parameter for storing the number michael@0: * of signatures in the MAR file. michael@0: * @param hasAdditionalBlocks Optional out parameter specifying if the MAR michael@0: * file has additional blocks or not. michael@0: * @param offsetAdditionalBlocks Optional out parameter for the offset to the michael@0: * first additional block. Value is only valid if michael@0: * hasAdditionalBlocks is not equal to 0. michael@0: * @param numAdditionalBlocks Optional out parameter for the number of michael@0: * additional blocks. Value is only valid if michael@0: * hasAdditionalBlocks is not equal to 0. michael@0: * @return 0 on success and non-zero on failure. michael@0: */ michael@0: int get_mar_file_info_fp(FILE *fp, michael@0: int *hasSignatureBlock, michael@0: uint32_t *numSignatures, michael@0: int *hasAdditionalBlocks, michael@0: uint32_t *offsetAdditionalBlocks, michael@0: uint32_t *numAdditionalBlocks) michael@0: { michael@0: uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i; michael@0: michael@0: /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ michael@0: if (!hasSignatureBlock && !hasAdditionalBlocks) { michael@0: return -1; michael@0: } michael@0: michael@0: michael@0: /* Skip to the start of the offset index */ michael@0: if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Read the offset to the index. */ michael@0: if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: offsetToIndex = ntohl(offsetToIndex); michael@0: michael@0: if (numSignatures) { michael@0: /* Skip past the MAR file size field */ michael@0: if (fseek(fp, sizeof(uint64_t), SEEK_CUR)) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Read the number of signatures field */ michael@0: if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: *numSignatures = ntohl(*numSignatures); michael@0: } michael@0: michael@0: /* Skip to the first index entry past the index size field michael@0: We do it in 2 calls because offsetToIndex + sizeof(uint32_t) michael@0: could oerflow in theory. */ michael@0: if (fseek(fp, offsetToIndex, SEEK_SET)) { michael@0: return -1; michael@0: } michael@0: michael@0: if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Read the first offset to content field. */ michael@0: if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: offsetToContent = ntohl(offsetToContent); michael@0: michael@0: /* Check if we have a new or old MAR file */ michael@0: if (hasSignatureBlock) { michael@0: if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) { michael@0: *hasSignatureBlock = 0; michael@0: } else { michael@0: *hasSignatureBlock = 1; michael@0: } michael@0: } michael@0: michael@0: /* If the caller doesn't care about the product info block michael@0: value, then just return */ michael@0: if (!hasAdditionalBlocks) { michael@0: return 0; michael@0: } michael@0: michael@0: /* Skip to the start of the signature block */ michael@0: if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Get the number of signatures */ michael@0: if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: signatureCount = ntohl(signatureCount); michael@0: michael@0: /* Check that we have less than the max amount of signatures so we don't michael@0: waste too much of either updater's or signmar's time. */ michael@0: if (signatureCount > MAX_SIGNATURES) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Skip past the whole signature block */ michael@0: for (i = 0; i < signatureCount; i++) { michael@0: /* Skip past the signature algorithm ID */ michael@0: if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Read the signature length and skip past the signature */ michael@0: if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: signatureLen = ntohl(signatureLen); michael@0: if (fseek(fp, signatureLen, SEEK_CUR)) { michael@0: return -1; michael@0: } michael@0: } michael@0: michael@0: if (ftell(fp) == offsetToContent) { michael@0: *hasAdditionalBlocks = 0; michael@0: } else { michael@0: if (numAdditionalBlocks) { michael@0: /* We have an additional block, so read in the number of additional blocks michael@0: and set the offset. */ michael@0: *hasAdditionalBlocks = 1; michael@0: if (fread(numAdditionalBlocks, sizeof(uint32_t), 1, fp) != 1) { michael@0: return -1; michael@0: } michael@0: *numAdditionalBlocks = ntohl(*numAdditionalBlocks); michael@0: if (offsetAdditionalBlocks) { michael@0: *offsetAdditionalBlocks = ftell(fp); michael@0: } michael@0: } else if (offsetAdditionalBlocks) { michael@0: /* numAdditionalBlocks is not specified but offsetAdditionalBlocks michael@0: is, so fill it! */ michael@0: *offsetAdditionalBlocks = ftell(fp) + sizeof(uint32_t); michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /** michael@0: * Reads the product info block from the MAR file's additional block section. michael@0: * The caller is responsible for freeing the fields in infoBlock michael@0: * if the return is successful. michael@0: * michael@0: * @param infoBlock Out parameter for where to store the result to michael@0: * @return 0 on success, -1 on failure michael@0: */ michael@0: int michael@0: read_product_info_block(char *path, michael@0: struct ProductInformationBlock *infoBlock) michael@0: { michael@0: int rv; michael@0: MarFile mar; michael@0: mar.fp = fopen(path, "rb"); michael@0: if (!mar.fp) { michael@0: fprintf(stderr, "ERROR: could not open file in read_product_info_block()\n"); michael@0: perror(path); michael@0: return -1; michael@0: } michael@0: rv = mar_read_product_info_block(&mar, infoBlock); michael@0: fclose(mar.fp); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Reads the product info block from the MAR file's additional block section. michael@0: * The caller is responsible for freeing the fields in infoBlock michael@0: * if the return is successful. michael@0: * michael@0: * @param infoBlock Out parameter for where to store the result to michael@0: * @return 0 on success, -1 on failure michael@0: */ michael@0: int michael@0: mar_read_product_info_block(MarFile *mar, michael@0: struct ProductInformationBlock *infoBlock) michael@0: { michael@0: uint32_t i, offsetAdditionalBlocks, numAdditionalBlocks, michael@0: additionalBlockSize, additionalBlockID; michael@0: int hasAdditionalBlocks; michael@0: michael@0: /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and michael@0: product version < 32 bytes + 3 NULL terminator bytes. */ michael@0: char buf[97] = { '\0' }; michael@0: int ret = get_mar_file_info_fp(mar->fp, NULL, NULL, michael@0: &hasAdditionalBlocks, michael@0: &offsetAdditionalBlocks, michael@0: &numAdditionalBlocks); michael@0: for (i = 0; i < numAdditionalBlocks; ++i) { michael@0: /* Read the additional block size */ michael@0: if (fread(&additionalBlockSize, michael@0: sizeof(additionalBlockSize), michael@0: 1, mar->fp) != 1) { michael@0: return -1; michael@0: } michael@0: additionalBlockSize = ntohl(additionalBlockSize) - michael@0: sizeof(additionalBlockSize) - michael@0: sizeof(additionalBlockID); michael@0: michael@0: /* Read the additional block ID */ michael@0: if (fread(&additionalBlockID, michael@0: sizeof(additionalBlockID), michael@0: 1, mar->fp) != 1) { michael@0: return -1; michael@0: } michael@0: additionalBlockID = ntohl(additionalBlockID); michael@0: michael@0: if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { michael@0: const char *location; michael@0: int len; michael@0: michael@0: /* This block must be at most 104 bytes. michael@0: MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL michael@0: terminator bytes. We only check for 96 though because we remove 8 michael@0: bytes above from the additionalBlockSize: We subtract michael@0: sizeof(additionalBlockSize) and sizeof(additionalBlockID) */ michael@0: if (additionalBlockSize > 96) { michael@0: return -1; michael@0: } michael@0: michael@0: if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) { michael@0: return -1; michael@0: } michael@0: michael@0: /* Extract the MAR channel name from the buffer. For now we michael@0: point to the stack allocated buffer but we strdup this michael@0: if we are within bounds of each field's max length. */ michael@0: location = buf; michael@0: len = strlen(location); michael@0: infoBlock->MARChannelID = location; michael@0: location += len + 1; michael@0: if (len >= 64) { michael@0: infoBlock->MARChannelID = NULL; michael@0: return -1; michael@0: } michael@0: michael@0: /* Extract the version from the buffer */ michael@0: len = strlen(location); michael@0: infoBlock->productVersion = location; michael@0: location += len + 1; michael@0: if (len >= 32) { michael@0: infoBlock->MARChannelID = NULL; michael@0: infoBlock->productVersion = NULL; michael@0: return -1; michael@0: } michael@0: infoBlock->MARChannelID = michael@0: strdup(infoBlock->MARChannelID); michael@0: infoBlock->productVersion = michael@0: strdup(infoBlock->productVersion); michael@0: return 0; michael@0: } else { michael@0: /* This is not the additional block you're looking for. Move along. */ michael@0: if (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) { michael@0: return -1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* If we had a product info block we would have already returned */ michael@0: return -1; michael@0: } michael@0: michael@0: const MarItem *mar_find_item(MarFile *mar, const char *name) { michael@0: uint32_t hash; michael@0: const MarItem *item; michael@0: michael@0: hash = mar_hash_name(name); michael@0: michael@0: item = mar->item_table[hash]; michael@0: while (item && strcmp(item->name, name) != 0) michael@0: item = item->next; michael@0: michael@0: return item; michael@0: } michael@0: michael@0: int mar_enum_items(MarFile *mar, MarItemCallback callback, void *closure) { michael@0: MarItem *item; michael@0: int i; michael@0: michael@0: for (i = 0; i < TABLESIZE; ++i) { michael@0: item = mar->item_table[i]; michael@0: while (item) { michael@0: int rv = callback(mar, item, closure); michael@0: if (rv) michael@0: return rv; michael@0: item = item->next; michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, michael@0: int bufsize) { michael@0: int nr; michael@0: michael@0: if (offset == (int) item->length) michael@0: return 0; michael@0: if (offset > (int) item->length) michael@0: return -1; michael@0: michael@0: nr = item->length - offset; michael@0: if (nr > bufsize) michael@0: nr = bufsize; michael@0: michael@0: if (fseek(mar->fp, item->offset + offset, SEEK_SET)) michael@0: return -1; michael@0: michael@0: return fread(buf, 1, nr, mar->fp); michael@0: } michael@0: michael@0: /** michael@0: * Determines the MAR file information. michael@0: * michael@0: * @param path The path of the MAR file to check. michael@0: * @param hasSignatureBlock Optional out parameter specifying if the MAR michael@0: * file has a signature block or not. michael@0: * @param numSignatures Optional out parameter for storing the number michael@0: * of signatures in the MAR file. michael@0: * @param hasAdditionalBlocks Optional out parameter specifying if the MAR michael@0: * file has additional blocks or not. michael@0: * @param offsetAdditionalBlocks Optional out parameter for the offset to the michael@0: * first additional block. Value is only valid if michael@0: * hasAdditionalBlocks is not equal to 0. michael@0: * @param numAdditionalBlocks Optional out parameter for the number of michael@0: * additional blocks. Value is only valid if michael@0: * has_additional_blocks is not equal to 0. michael@0: * @return 0 on success and non-zero on failure. michael@0: */ michael@0: int get_mar_file_info(const char *path, michael@0: int *hasSignatureBlock, michael@0: uint32_t *numSignatures, michael@0: int *hasAdditionalBlocks, michael@0: uint32_t *offsetAdditionalBlocks, michael@0: uint32_t *numAdditionalBlocks) michael@0: { michael@0: int rv; michael@0: FILE *fp = fopen(path, "rb"); michael@0: if (!fp) { michael@0: fprintf(stderr, "ERROR: could not open file in get_mar_file_info()\n"); michael@0: perror(path); michael@0: return -1; michael@0: } michael@0: michael@0: rv = get_mar_file_info_fp(fp, hasSignatureBlock, michael@0: numSignatures, hasAdditionalBlocks, michael@0: offsetAdditionalBlocks, numAdditionalBlocks); michael@0: michael@0: fclose(fp); michael@0: return rv; michael@0: }