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