|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include <sys/types.h> |
|
8 #include <fcntl.h> |
|
9 #include <stdlib.h> |
|
10 #include <string.h> |
|
11 #include "mar_private.h" |
|
12 #include "mar.h" |
|
13 |
|
14 #ifdef XP_WIN |
|
15 #include <winsock2.h> |
|
16 #else |
|
17 #include <netinet/in.h> |
|
18 #endif |
|
19 |
|
20 |
|
21 /* this is the same hash algorithm used by nsZipArchive.cpp */ |
|
22 static uint32_t mar_hash_name(const char *name) { |
|
23 uint32_t val = 0; |
|
24 unsigned char* c; |
|
25 |
|
26 for (c = (unsigned char *) name; *c; ++c) |
|
27 val = val*37 + *c; |
|
28 |
|
29 return val % TABLESIZE; |
|
30 } |
|
31 |
|
32 static int mar_insert_item(MarFile *mar, const char *name, int namelen, |
|
33 uint32_t offset, uint32_t length, uint32_t flags) { |
|
34 MarItem *item, *root; |
|
35 uint32_t hash; |
|
36 |
|
37 item = (MarItem *) malloc(sizeof(MarItem) + namelen); |
|
38 if (!item) |
|
39 return -1; |
|
40 item->next = NULL; |
|
41 item->offset = offset; |
|
42 item->length = length; |
|
43 item->flags = flags; |
|
44 memcpy(item->name, name, namelen + 1); |
|
45 |
|
46 hash = mar_hash_name(name); |
|
47 |
|
48 root = mar->item_table[hash]; |
|
49 if (!root) { |
|
50 mar->item_table[hash] = item; |
|
51 } else { |
|
52 /* append item */ |
|
53 while (root->next) |
|
54 root = root->next; |
|
55 root->next = item; |
|
56 } |
|
57 return 0; |
|
58 } |
|
59 |
|
60 static int mar_consume_index(MarFile *mar, char **buf, const char *buf_end) { |
|
61 /* |
|
62 * Each item has the following structure: |
|
63 * uint32_t offset (network byte order) |
|
64 * uint32_t length (network byte order) |
|
65 * uint32_t flags (network byte order) |
|
66 * char name[N] (where N >= 1) |
|
67 * char null_byte; |
|
68 */ |
|
69 uint32_t offset; |
|
70 uint32_t length; |
|
71 uint32_t flags; |
|
72 const char *name; |
|
73 int namelen; |
|
74 |
|
75 if ((buf_end - *buf) < (int)(3*sizeof(uint32_t) + 2)) |
|
76 return -1; |
|
77 |
|
78 memcpy(&offset, *buf, sizeof(offset)); |
|
79 *buf += sizeof(offset); |
|
80 |
|
81 memcpy(&length, *buf, sizeof(length)); |
|
82 *buf += sizeof(length); |
|
83 |
|
84 memcpy(&flags, *buf, sizeof(flags)); |
|
85 *buf += sizeof(flags); |
|
86 |
|
87 offset = ntohl(offset); |
|
88 length = ntohl(length); |
|
89 flags = ntohl(flags); |
|
90 |
|
91 name = *buf; |
|
92 /* find namelen; must take care not to read beyond buf_end */ |
|
93 while (**buf) { |
|
94 if (*buf == buf_end) |
|
95 return -1; |
|
96 ++(*buf); |
|
97 } |
|
98 namelen = (*buf - name); |
|
99 /* consume null byte */ |
|
100 if (*buf == buf_end) |
|
101 return -1; |
|
102 ++(*buf); |
|
103 |
|
104 return mar_insert_item(mar, name, namelen, offset, length, flags); |
|
105 } |
|
106 |
|
107 static int mar_read_index(MarFile *mar) { |
|
108 char id[MAR_ID_SIZE], *buf, *bufptr, *bufend; |
|
109 uint32_t offset_to_index, size_of_index; |
|
110 |
|
111 /* verify MAR ID */ |
|
112 if (fread(id, MAR_ID_SIZE, 1, mar->fp) != 1) |
|
113 return -1; |
|
114 if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) |
|
115 return -1; |
|
116 |
|
117 if (fread(&offset_to_index, sizeof(uint32_t), 1, mar->fp) != 1) |
|
118 return -1; |
|
119 offset_to_index = ntohl(offset_to_index); |
|
120 |
|
121 if (fseek(mar->fp, offset_to_index, SEEK_SET)) |
|
122 return -1; |
|
123 if (fread(&size_of_index, sizeof(uint32_t), 1, mar->fp) != 1) |
|
124 return -1; |
|
125 size_of_index = ntohl(size_of_index); |
|
126 |
|
127 buf = (char *) malloc(size_of_index); |
|
128 if (!buf) |
|
129 return -1; |
|
130 if (fread(buf, size_of_index, 1, mar->fp) != 1) { |
|
131 free(buf); |
|
132 return -1; |
|
133 } |
|
134 |
|
135 bufptr = buf; |
|
136 bufend = buf + size_of_index; |
|
137 while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0); |
|
138 |
|
139 free(buf); |
|
140 return (bufptr == bufend) ? 0 : -1; |
|
141 } |
|
142 |
|
143 /** |
|
144 * Internal shared code for mar_open and mar_wopen. |
|
145 * On failure, will fclose(fp). |
|
146 */ |
|
147 static MarFile *mar_fpopen(FILE *fp) |
|
148 { |
|
149 MarFile *mar; |
|
150 |
|
151 mar = (MarFile *) malloc(sizeof(*mar)); |
|
152 if (!mar) { |
|
153 fclose(fp); |
|
154 return NULL; |
|
155 } |
|
156 |
|
157 mar->fp = fp; |
|
158 memset(mar->item_table, 0, sizeof(mar->item_table)); |
|
159 if (mar_read_index(mar)) { |
|
160 mar_close(mar); |
|
161 return NULL; |
|
162 } |
|
163 |
|
164 return mar; |
|
165 } |
|
166 |
|
167 MarFile *mar_open(const char *path) { |
|
168 FILE *fp; |
|
169 |
|
170 fp = fopen(path, "rb"); |
|
171 if (!fp) { |
|
172 fprintf(stderr, "ERROR: could not open file in mar_open()\n"); |
|
173 perror(path); |
|
174 return NULL; |
|
175 } |
|
176 |
|
177 return mar_fpopen(fp); |
|
178 } |
|
179 |
|
180 #ifdef XP_WIN |
|
181 MarFile *mar_wopen(const wchar_t *path) { |
|
182 FILE *fp; |
|
183 |
|
184 _wfopen_s(&fp, path, L"rb"); |
|
185 if (!fp) { |
|
186 fprintf(stderr, "ERROR: could not open file in mar_wopen()\n"); |
|
187 _wperror(path); |
|
188 return NULL; |
|
189 } |
|
190 |
|
191 return mar_fpopen(fp); |
|
192 } |
|
193 #endif |
|
194 |
|
195 void mar_close(MarFile *mar) { |
|
196 MarItem *item; |
|
197 int i; |
|
198 |
|
199 fclose(mar->fp); |
|
200 |
|
201 for (i = 0; i < TABLESIZE; ++i) { |
|
202 item = mar->item_table[i]; |
|
203 while (item) { |
|
204 MarItem *temp = item; |
|
205 item = item->next; |
|
206 free(temp); |
|
207 } |
|
208 } |
|
209 |
|
210 free(mar); |
|
211 } |
|
212 |
|
213 /** |
|
214 * Determines the MAR file information. |
|
215 * |
|
216 * @param fp An opened MAR file in read mode. |
|
217 * @param hasSignatureBlock Optional out parameter specifying if the MAR |
|
218 * file has a signature block or not. |
|
219 * @param numSignatures Optional out parameter for storing the number |
|
220 * of signatures in the MAR file. |
|
221 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR |
|
222 * file has additional blocks or not. |
|
223 * @param offsetAdditionalBlocks Optional out parameter for the offset to the |
|
224 * first additional block. Value is only valid if |
|
225 * hasAdditionalBlocks is not equal to 0. |
|
226 * @param numAdditionalBlocks Optional out parameter for the number of |
|
227 * additional blocks. Value is only valid if |
|
228 * hasAdditionalBlocks is not equal to 0. |
|
229 * @return 0 on success and non-zero on failure. |
|
230 */ |
|
231 int get_mar_file_info_fp(FILE *fp, |
|
232 int *hasSignatureBlock, |
|
233 uint32_t *numSignatures, |
|
234 int *hasAdditionalBlocks, |
|
235 uint32_t *offsetAdditionalBlocks, |
|
236 uint32_t *numAdditionalBlocks) |
|
237 { |
|
238 uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i; |
|
239 |
|
240 /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ |
|
241 if (!hasSignatureBlock && !hasAdditionalBlocks) { |
|
242 return -1; |
|
243 } |
|
244 |
|
245 |
|
246 /* Skip to the start of the offset index */ |
|
247 if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { |
|
248 return -1; |
|
249 } |
|
250 |
|
251 /* Read the offset to the index. */ |
|
252 if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) { |
|
253 return -1; |
|
254 } |
|
255 offsetToIndex = ntohl(offsetToIndex); |
|
256 |
|
257 if (numSignatures) { |
|
258 /* Skip past the MAR file size field */ |
|
259 if (fseek(fp, sizeof(uint64_t), SEEK_CUR)) { |
|
260 return -1; |
|
261 } |
|
262 |
|
263 /* Read the number of signatures field */ |
|
264 if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) { |
|
265 return -1; |
|
266 } |
|
267 *numSignatures = ntohl(*numSignatures); |
|
268 } |
|
269 |
|
270 /* Skip to the first index entry past the index size field |
|
271 We do it in 2 calls because offsetToIndex + sizeof(uint32_t) |
|
272 could oerflow in theory. */ |
|
273 if (fseek(fp, offsetToIndex, SEEK_SET)) { |
|
274 return -1; |
|
275 } |
|
276 |
|
277 if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { |
|
278 return -1; |
|
279 } |
|
280 |
|
281 /* Read the first offset to content field. */ |
|
282 if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) { |
|
283 return -1; |
|
284 } |
|
285 offsetToContent = ntohl(offsetToContent); |
|
286 |
|
287 /* Check if we have a new or old MAR file */ |
|
288 if (hasSignatureBlock) { |
|
289 if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) { |
|
290 *hasSignatureBlock = 0; |
|
291 } else { |
|
292 *hasSignatureBlock = 1; |
|
293 } |
|
294 } |
|
295 |
|
296 /* If the caller doesn't care about the product info block |
|
297 value, then just return */ |
|
298 if (!hasAdditionalBlocks) { |
|
299 return 0; |
|
300 } |
|
301 |
|
302 /* Skip to the start of the signature block */ |
|
303 if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { |
|
304 return -1; |
|
305 } |
|
306 |
|
307 /* Get the number of signatures */ |
|
308 if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { |
|
309 return -1; |
|
310 } |
|
311 signatureCount = ntohl(signatureCount); |
|
312 |
|
313 /* Check that we have less than the max amount of signatures so we don't |
|
314 waste too much of either updater's or signmar's time. */ |
|
315 if (signatureCount > MAX_SIGNATURES) { |
|
316 return -1; |
|
317 } |
|
318 |
|
319 /* Skip past the whole signature block */ |
|
320 for (i = 0; i < signatureCount; i++) { |
|
321 /* Skip past the signature algorithm ID */ |
|
322 if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { |
|
323 return -1; |
|
324 } |
|
325 |
|
326 /* Read the signature length and skip past the signature */ |
|
327 if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { |
|
328 return -1; |
|
329 } |
|
330 signatureLen = ntohl(signatureLen); |
|
331 if (fseek(fp, signatureLen, SEEK_CUR)) { |
|
332 return -1; |
|
333 } |
|
334 } |
|
335 |
|
336 if (ftell(fp) == offsetToContent) { |
|
337 *hasAdditionalBlocks = 0; |
|
338 } else { |
|
339 if (numAdditionalBlocks) { |
|
340 /* We have an additional block, so read in the number of additional blocks |
|
341 and set the offset. */ |
|
342 *hasAdditionalBlocks = 1; |
|
343 if (fread(numAdditionalBlocks, sizeof(uint32_t), 1, fp) != 1) { |
|
344 return -1; |
|
345 } |
|
346 *numAdditionalBlocks = ntohl(*numAdditionalBlocks); |
|
347 if (offsetAdditionalBlocks) { |
|
348 *offsetAdditionalBlocks = ftell(fp); |
|
349 } |
|
350 } else if (offsetAdditionalBlocks) { |
|
351 /* numAdditionalBlocks is not specified but offsetAdditionalBlocks |
|
352 is, so fill it! */ |
|
353 *offsetAdditionalBlocks = ftell(fp) + sizeof(uint32_t); |
|
354 } |
|
355 } |
|
356 |
|
357 return 0; |
|
358 } |
|
359 |
|
360 /** |
|
361 * Reads the product info block from the MAR file's additional block section. |
|
362 * The caller is responsible for freeing the fields in infoBlock |
|
363 * if the return is successful. |
|
364 * |
|
365 * @param infoBlock Out parameter for where to store the result to |
|
366 * @return 0 on success, -1 on failure |
|
367 */ |
|
368 int |
|
369 read_product_info_block(char *path, |
|
370 struct ProductInformationBlock *infoBlock) |
|
371 { |
|
372 int rv; |
|
373 MarFile mar; |
|
374 mar.fp = fopen(path, "rb"); |
|
375 if (!mar.fp) { |
|
376 fprintf(stderr, "ERROR: could not open file in read_product_info_block()\n"); |
|
377 perror(path); |
|
378 return -1; |
|
379 } |
|
380 rv = mar_read_product_info_block(&mar, infoBlock); |
|
381 fclose(mar.fp); |
|
382 return rv; |
|
383 } |
|
384 |
|
385 /** |
|
386 * Reads the product info block from the MAR file's additional block section. |
|
387 * The caller is responsible for freeing the fields in infoBlock |
|
388 * if the return is successful. |
|
389 * |
|
390 * @param infoBlock Out parameter for where to store the result to |
|
391 * @return 0 on success, -1 on failure |
|
392 */ |
|
393 int |
|
394 mar_read_product_info_block(MarFile *mar, |
|
395 struct ProductInformationBlock *infoBlock) |
|
396 { |
|
397 uint32_t i, offsetAdditionalBlocks, numAdditionalBlocks, |
|
398 additionalBlockSize, additionalBlockID; |
|
399 int hasAdditionalBlocks; |
|
400 |
|
401 /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and |
|
402 product version < 32 bytes + 3 NULL terminator bytes. */ |
|
403 char buf[97] = { '\0' }; |
|
404 int ret = get_mar_file_info_fp(mar->fp, NULL, NULL, |
|
405 &hasAdditionalBlocks, |
|
406 &offsetAdditionalBlocks, |
|
407 &numAdditionalBlocks); |
|
408 for (i = 0; i < numAdditionalBlocks; ++i) { |
|
409 /* Read the additional block size */ |
|
410 if (fread(&additionalBlockSize, |
|
411 sizeof(additionalBlockSize), |
|
412 1, mar->fp) != 1) { |
|
413 return -1; |
|
414 } |
|
415 additionalBlockSize = ntohl(additionalBlockSize) - |
|
416 sizeof(additionalBlockSize) - |
|
417 sizeof(additionalBlockID); |
|
418 |
|
419 /* Read the additional block ID */ |
|
420 if (fread(&additionalBlockID, |
|
421 sizeof(additionalBlockID), |
|
422 1, mar->fp) != 1) { |
|
423 return -1; |
|
424 } |
|
425 additionalBlockID = ntohl(additionalBlockID); |
|
426 |
|
427 if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { |
|
428 const char *location; |
|
429 int len; |
|
430 |
|
431 /* This block must be at most 104 bytes. |
|
432 MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL |
|
433 terminator bytes. We only check for 96 though because we remove 8 |
|
434 bytes above from the additionalBlockSize: We subtract |
|
435 sizeof(additionalBlockSize) and sizeof(additionalBlockID) */ |
|
436 if (additionalBlockSize > 96) { |
|
437 return -1; |
|
438 } |
|
439 |
|
440 if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) { |
|
441 return -1; |
|
442 } |
|
443 |
|
444 /* Extract the MAR channel name from the buffer. For now we |
|
445 point to the stack allocated buffer but we strdup this |
|
446 if we are within bounds of each field's max length. */ |
|
447 location = buf; |
|
448 len = strlen(location); |
|
449 infoBlock->MARChannelID = location; |
|
450 location += len + 1; |
|
451 if (len >= 64) { |
|
452 infoBlock->MARChannelID = NULL; |
|
453 return -1; |
|
454 } |
|
455 |
|
456 /* Extract the version from the buffer */ |
|
457 len = strlen(location); |
|
458 infoBlock->productVersion = location; |
|
459 location += len + 1; |
|
460 if (len >= 32) { |
|
461 infoBlock->MARChannelID = NULL; |
|
462 infoBlock->productVersion = NULL; |
|
463 return -1; |
|
464 } |
|
465 infoBlock->MARChannelID = |
|
466 strdup(infoBlock->MARChannelID); |
|
467 infoBlock->productVersion = |
|
468 strdup(infoBlock->productVersion); |
|
469 return 0; |
|
470 } else { |
|
471 /* This is not the additional block you're looking for. Move along. */ |
|
472 if (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) { |
|
473 return -1; |
|
474 } |
|
475 } |
|
476 } |
|
477 |
|
478 /* If we had a product info block we would have already returned */ |
|
479 return -1; |
|
480 } |
|
481 |
|
482 const MarItem *mar_find_item(MarFile *mar, const char *name) { |
|
483 uint32_t hash; |
|
484 const MarItem *item; |
|
485 |
|
486 hash = mar_hash_name(name); |
|
487 |
|
488 item = mar->item_table[hash]; |
|
489 while (item && strcmp(item->name, name) != 0) |
|
490 item = item->next; |
|
491 |
|
492 return item; |
|
493 } |
|
494 |
|
495 int mar_enum_items(MarFile *mar, MarItemCallback callback, void *closure) { |
|
496 MarItem *item; |
|
497 int i; |
|
498 |
|
499 for (i = 0; i < TABLESIZE; ++i) { |
|
500 item = mar->item_table[i]; |
|
501 while (item) { |
|
502 int rv = callback(mar, item, closure); |
|
503 if (rv) |
|
504 return rv; |
|
505 item = item->next; |
|
506 } |
|
507 } |
|
508 |
|
509 return 0; |
|
510 } |
|
511 |
|
512 int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, |
|
513 int bufsize) { |
|
514 int nr; |
|
515 |
|
516 if (offset == (int) item->length) |
|
517 return 0; |
|
518 if (offset > (int) item->length) |
|
519 return -1; |
|
520 |
|
521 nr = item->length - offset; |
|
522 if (nr > bufsize) |
|
523 nr = bufsize; |
|
524 |
|
525 if (fseek(mar->fp, item->offset + offset, SEEK_SET)) |
|
526 return -1; |
|
527 |
|
528 return fread(buf, 1, nr, mar->fp); |
|
529 } |
|
530 |
|
531 /** |
|
532 * Determines the MAR file information. |
|
533 * |
|
534 * @param path The path of the MAR file to check. |
|
535 * @param hasSignatureBlock Optional out parameter specifying if the MAR |
|
536 * file has a signature block or not. |
|
537 * @param numSignatures Optional out parameter for storing the number |
|
538 * of signatures in the MAR file. |
|
539 * @param hasAdditionalBlocks Optional out parameter specifying if the MAR |
|
540 * file has additional blocks or not. |
|
541 * @param offsetAdditionalBlocks Optional out parameter for the offset to the |
|
542 * first additional block. Value is only valid if |
|
543 * hasAdditionalBlocks is not equal to 0. |
|
544 * @param numAdditionalBlocks Optional out parameter for the number of |
|
545 * additional blocks. Value is only valid if |
|
546 * has_additional_blocks is not equal to 0. |
|
547 * @return 0 on success and non-zero on failure. |
|
548 */ |
|
549 int get_mar_file_info(const char *path, |
|
550 int *hasSignatureBlock, |
|
551 uint32_t *numSignatures, |
|
552 int *hasAdditionalBlocks, |
|
553 uint32_t *offsetAdditionalBlocks, |
|
554 uint32_t *numAdditionalBlocks) |
|
555 { |
|
556 int rv; |
|
557 FILE *fp = fopen(path, "rb"); |
|
558 if (!fp) { |
|
559 fprintf(stderr, "ERROR: could not open file in get_mar_file_info()\n"); |
|
560 perror(path); |
|
561 return -1; |
|
562 } |
|
563 |
|
564 rv = get_mar_file_info_fp(fp, hasSignatureBlock, |
|
565 numSignatures, hasAdditionalBlocks, |
|
566 offsetAdditionalBlocks, numAdditionalBlocks); |
|
567 |
|
568 fclose(fp); |
|
569 return rv; |
|
570 } |