Back to index

avfs  1.0.1
uzip.c
Go to the documentation of this file.
00001 /*
00002     AVFS: A Virtual File System Library
00003     Copyright (C) 1998-1999  Miklos Szeredi <miklos@szeredi.hu>
00004     
00005     This program can be distributed under the terms of the GNU GPL.
00006     See the file COPYING.
00007 
00008     ZIP module
00009 */
00010 
00011 #include "archive.h"
00012 #include "zipconst.h"
00013 #include "zfile.h"
00014 #include "cache.h"
00015 #include "oper.h"
00016 #include "version.h"
00017 
00018 struct ecrec {
00019     avushort this_disk;
00020     avushort cdir_disk;
00021     avushort this_entries;
00022     avushort total_entries;
00023     avuint cdir_size;
00024     avuint cdir_off;
00025     avushort comment_len;
00026 };
00027 
00028 #define ECREC_THIS_DISK     4
00029 #define ECREC_CDIR_DISK     6
00030 #define ECREC_THIS_ENTRIES  8
00031 #define ECREC_TOTAL_ENTRIES 10
00032 #define ECREC_CDIR_SIZE     12
00033 #define ECREC_CDIR_OFF      16
00034 #define ECREC_COMMENT_LEN   20
00035 
00036 #define ECREC_SIZE          22
00037 
00038 struct cdirentry {
00039     avushort version;
00040     avushort need_version;
00041     avushort flag;
00042     avushort method;
00043     avuint mod_time;
00044     avuint crc;
00045     avuint comp_size;
00046     avuint file_size;
00047     avushort fname_len;
00048     avushort extra_len;
00049     avushort comment_len;
00050     avushort start_disk;
00051     avushort int_attr;
00052     avuint attr;
00053     avuint file_off;         
00054 };
00055 
00056 #define CDIRENT_VERSION       4
00057 #define CDIRENT_NEED_VERSION  6
00058 #define CDIRENT_FLAG          8
00059 #define CDIRENT_METHOD        10
00060 #define CDIRENT_MOD_TIME      12
00061 #define CDIRENT_CRC           16
00062 #define CDIRENT_COMP_SIZE     20
00063 #define CDIRENT_FILE_SIZE     24
00064 #define CDIRENT_FNAME_LEN     28
00065 #define CDIRENT_EXTRA_LEN     30
00066 #define CDIRENT_COMMENT_LEN   32
00067 #define CDIRENT_START_DISK    34
00068 #define CDIRENT_INT_ATTR      36
00069 #define CDIRENT_ATTR          38
00070 #define CDIRENT_FILE_OFF      42
00071 
00072 #define CDIRENT_SIZE          46
00073 
00074 struct ldirentry {
00075     avushort need_version;
00076     avushort flag;
00077     avushort method;
00078     avuint mod_time;
00079     avuint crc;
00080     avuint comp_size;
00081     avuint file_size;
00082     avushort fname_len;
00083     avushort extra_len;
00084 };
00085 
00086 #define LDIRENT_NEED_VERSION  4
00087 #define LDIRENT_FLAG          6
00088 #define LDIRENT_METHOD        8
00089 #define LDIRENT_MOD_TIME      10
00090 #define LDIRENT_CRC           14
00091 #define LDIRENT_COMP_SIZE     18
00092 #define LDIRENT_FILE_SIZE     22
00093 #define LDIRENT_FNAME_LEN     26
00094 #define LDIRENT_EXTRA_LEN     28
00095 
00096 #define LDIRENT_SIZE          30
00097 
00098 #define dos_ftsec(ft)   (int)( 2 * ((ft >>  0) & 0x1F))
00099 #define dos_ftmin(ft)   (int)(     ((ft >>  5) & 0x3F))
00100 #define dos_fthour(ft)  (int)(     ((ft >> 11) & 0x1F))
00101 #define dos_ftday(ft)   (int)(     ((ft >> 16) & 0x1F))
00102 #define dos_ftmonth(ft) (int)(-1 + ((ft >> 21) & 0x0F))
00103 #define dos_ftyear(ft)  (int)(80 + ((ft >> 25) & 0x7F))
00104 
00105 #define BUFSIZE 512
00106 
00107 #define SEARCHLEN 66000
00108 
00109 #define BI(ptr, i)  ((avbyte) (ptr)[i])
00110 #define DBYTE(ptr) (BI(ptr,0) | (BI(ptr,1)<<8))
00111 #define QBYTE(ptr) (BI(ptr,0) | (BI(ptr,1)<<8) | \
00112                    (BI(ptr,2)<<16) | (BI(ptr,3)<<24))
00113 
00114 struct zipnode {
00115     avuint crc;
00116     avushort method;
00117     avoff_t headeroff;
00118     struct cacheobj *cache;
00119 };
00120 
00121 static void conv_tolower(char *s)
00122 {
00123     for(; *s; s++) *s = tolower(*s);
00124 }
00125 
00126 static avoff_t find_ecrec(vfile *vf, long searchlen, struct ecrec *ecrec)
00127 {
00128     int res;
00129     avoff_t bufstart;
00130     int pos;
00131     char buf[BUFSIZE+3];
00132     avoff_t sres;
00133     int found;
00134   
00135     sres = av_lseek(vf, 0, AVSEEK_END);
00136     if(sres < 0)
00137         return sres;
00138     if(sres < ECREC_SIZE) {
00139         av_log(AVLOG_ERROR, "UZIP: Broken archive");
00140         return -EIO;
00141     }
00142   
00143     pos = 0;
00144     bufstart = sres - (ECREC_SIZE - 4);
00145     buf[0] = buf[1] = buf[2] = 0;
00146     found = 0;
00147 
00148     for(;searchlen && (bufstart || pos); searchlen--) {
00149        if(!pos) {
00150            pos = BUFSIZE;
00151            if(bufstart < pos) pos = bufstart;
00152            bufstart -= pos;
00153            buf[pos]   = buf[0];
00154            buf[pos+1] = buf[1];
00155            buf[pos+2] = buf[2];
00156             res = av_pread_all(vf, buf, pos, bufstart);
00157             if(res < 0)
00158                 return res;
00159        }
00160        pos--;
00161        if(buf[pos] == 'P' && buf[pos+1] == 'K' && 
00162           buf[pos+2] == 5 && buf[pos+3] == 6) {
00163            found = 1;
00164            break;
00165        }
00166     } 
00167   
00168     if(!found) {
00169         av_log(AVLOG_ERROR, 
00170                "UZIP: Couldn't find End of Central Directory Record");
00171         return -EIO;
00172     }
00173 
00174     bufstart += pos;
00175     res = av_pread_all(vf, buf, ECREC_SIZE, bufstart);
00176     if(res < 0)
00177         return res;
00178   
00179     ecrec->this_disk =     DBYTE(buf+ECREC_THIS_DISK);
00180     ecrec->cdir_disk =     DBYTE(buf+ECREC_CDIR_DISK);
00181     ecrec->this_entries =  DBYTE(buf+ECREC_THIS_ENTRIES);
00182     ecrec->total_entries = DBYTE(buf+ECREC_TOTAL_ENTRIES);
00183     ecrec->cdir_size =     QBYTE(buf+ECREC_CDIR_SIZE);
00184     ecrec->cdir_off =      QBYTE(buf+ECREC_CDIR_OFF);
00185     ecrec->comment_len =   DBYTE(buf+ECREC_COMMENT_LEN);
00186 
00187     return bufstart;
00188 }
00189 
00190 static avtime_t dos2unix_time(avuint dt)
00191 {
00192     struct avtm ut;
00193 
00194     ut.sec = dos_ftsec(dt);
00195     ut.min = dos_ftmin(dt);
00196     ut.hour = dos_fthour(dt);
00197     ut.day = dos_ftday(dt);
00198     ut.mon = dos_ftmonth(dt);
00199     ut.year = dos_ftyear(dt);
00200 
00201     return av_mktime(&ut);
00202 }
00203 
00204 static avmode_t dos2unix_attr(avuint da, avmode_t archmode)
00205 {
00206     avmode_t mode = (archmode & 0666);
00207     if (da & 0x01) mode = mode & ~0222;
00208     if (da & 0x10) mode = mode | ((mode & 0444) >> 2) | AV_IFDIR;
00209     else mode |= AV_IFREG;
00210 
00211     return mode;
00212 }
00213 
00214 static avmode_t zip_get_mode(struct cdirentry *cent, const char *path,
00215                              avmode_t origmode)
00216 {
00217     avmode_t mode;
00218 
00219     /* FIXME: Handle other architectures */
00220     if((cent->version & 0xFF00) >> 8 == OS_UNIX) 
00221        mode = (cent->attr >> 16) & 0xFFFF;
00222     else
00223        mode = dos2unix_attr(cent->attr & 0xFF, origmode);
00224 
00225     if(path[0] && path[strlen(path)-1] == '/')
00226         mode = (mode & 07777) | AV_IFDIR;
00227 
00228     return mode;
00229 }
00230 
00231 static void zipnode_delete(struct zipnode *nod)
00232 {
00233     av_unref_obj(nod->cache);
00234 }
00235 
00236 static void fill_zipentry(struct archive *arch, const char *path, 
00237                           struct entry *ent, struct cdirentry *cent,
00238                           struct ecrec *ecrec)
00239 {
00240     struct archnode *nod;
00241     struct zipnode *info;
00242     int isdir = AV_ISDIR(zip_get_mode(cent, path, 0));
00243 
00244     nod = av_arch_new_node(arch, ent, isdir);
00245     
00246     nod->st.mode = zip_get_mode(cent, path, nod->st.mode);
00247     nod->st.size = cent->file_size;
00248     nod->st.blocks = AV_BLOCKS(cent->comp_size);
00249     nod->st.blksize = 4096;
00250     nod->st.mtime.sec = dos2unix_time(cent->mod_time);
00251     nod->st.mtime.nsec = 0;
00252     nod->st.atime = nod->st.mtime;
00253     nod->st.ctime = nod->st.mtime;
00254     nod->realsize = cent->comp_size;
00255 
00256     AV_NEW_OBJ(info, zipnode_delete);
00257     nod->data = info;
00258 
00259     info->cache = NULL;
00260     info->crc = cent->crc;
00261     info->method = 0;
00262 
00263     /* FIXME: multivolume archives */
00264     if(cent->start_disk != 0 || ecrec->cdir_disk != 0)
00265         info->headeroff = -1;
00266     else
00267         info->headeroff = cent->file_off;
00268 
00269 }
00270 
00271 static void insert_zipentry(struct archive *arch, char *path, 
00272                             struct cdirentry *cent, struct ecrec *ecrec)
00273 {
00274     struct entry *ent;
00275     int entflags = 0;
00276 
00277     /* FIXME: option for uzip, not to convert filenames to lowercase */
00278     switch((cent->version & 0xFF00) >> 8) {
00279     case OS_CPM:
00280     case OS_VM_CMS:
00281     case OS_MVS:
00282     case OS_TANDEM:
00283     case OS_TOPS20:
00284     case OS_VMS:
00285        conv_tolower(path);
00286 
00287        /* fall through */
00288     case OS_MSDOS: /* some shitty windows zipper produces zipfiles in this
00289                       type */
00290     case OS_NT:
00291     case OS_WIN95:
00292  
00293        entflags |= NSF_NOCASE;
00294     }
00295 
00296     ent = av_arch_create(arch, path, entflags);
00297     if(ent == NULL)
00298         return;
00299 
00300     fill_zipentry(arch, path, ent, cent, ecrec);
00301     av_unref_obj(ent);
00302 }
00303 
00304 static avoff_t read_entry(vfile *vf, struct archive *arch, avoff_t pos,
00305                           struct ecrec *ecrec)
00306 {
00307     int res;
00308     char buf[CDIRENT_SIZE];
00309     struct cdirentry ent;
00310     char *filename;
00311 
00312     res = av_pread_all(vf, buf, CDIRENT_SIZE, pos);
00313     if(res < 0)
00314         return res;
00315   
00316     if(buf[0] != 'P' || buf[1] != 'K' || buf[2] != 1 || buf[3] != 2) {
00317         av_log(AVLOG_ERROR, "UZIP: Broken archive");
00318         return -EIO;
00319     }
00320 
00321     ent.version      = DBYTE(buf+CDIRENT_VERSION);
00322     ent.need_version = DBYTE(buf+CDIRENT_NEED_VERSION);
00323     ent.flag         = DBYTE(buf+CDIRENT_FLAG);
00324     ent.method       = DBYTE(buf+CDIRENT_METHOD);
00325     ent.mod_time     = QBYTE(buf+CDIRENT_MOD_TIME);
00326     ent.crc          = QBYTE(buf+CDIRENT_CRC);
00327     ent.comp_size    = QBYTE(buf+CDIRENT_COMP_SIZE);
00328     ent.file_size    = QBYTE(buf+CDIRENT_FILE_SIZE);
00329     ent.fname_len    = DBYTE(buf+CDIRENT_FNAME_LEN);
00330     ent.extra_len    = DBYTE(buf+CDIRENT_EXTRA_LEN);
00331     ent.comment_len  = DBYTE(buf+CDIRENT_COMMENT_LEN);
00332     ent.start_disk   = DBYTE(buf+CDIRENT_START_DISK);
00333     ent.int_attr     = DBYTE(buf+CDIRENT_INT_ATTR);
00334     ent.attr         = QBYTE(buf+CDIRENT_ATTR);
00335     ent.file_off     = QBYTE(buf+CDIRENT_FILE_OFF);
00336 
00337     filename = av_malloc(ent.fname_len + 1);
00338     res = av_pread_all(vf, filename, ent.fname_len, pos + CDIRENT_SIZE);
00339     if(res < 0) {
00340         av_free(filename);
00341         return res;
00342     }
00343     filename[ent.fname_len] = '\0';
00344 
00345     insert_zipentry(arch, filename, &ent, ecrec);
00346     av_free(filename);
00347 
00348     return pos + CDIRENT_SIZE + ent.fname_len + ent.extra_len +
00349         ent.comment_len;
00350 }
00351 
00352 
00353 static int read_zipfile(vfile *vf, struct archive *arch)
00354 {
00355     avoff_t ecrec_pos;
00356     struct ecrec ecrec;
00357     avoff_t extra_bytes;
00358     avoff_t cdir_end;
00359     avoff_t cdir_pos;
00360     int nument;
00361 
00362     ecrec_pos = find_ecrec(vf, SEARCHLEN, &ecrec);
00363     if(ecrec_pos < 0)
00364         return ecrec_pos;
00365 
00366     cdir_end = ecrec.cdir_size+ecrec.cdir_off;
00367 
00368     if(ecrec.this_disk != ecrec.cdir_disk) {
00369         av_log(AVLOG_ERROR, "UZIP: Cannot handle multivolume archives");
00370         return -EIO;
00371     }
00372   
00373     extra_bytes = ecrec_pos - cdir_end;
00374     if(extra_bytes < 0) {
00375         av_log(AVLOG_ERROR, "UZIP: Broken archive");
00376         return -EIO;
00377     }
00378   
00379     if(ecrec.cdir_off == 0 && ecrec.cdir_size == 0) {
00380        /* Empty zipfile */
00381        return 0;
00382     }
00383   
00384     cdir_pos = ecrec.cdir_off + extra_bytes;
00385   
00386     for(nument = 0; nument < ecrec.total_entries; nument++) {
00387        if(cdir_pos >= ecrec_pos) {
00388             av_log(AVLOG_ERROR, "UZIP: Broken archive");
00389             return -EIO;
00390        }
00391        cdir_pos = read_entry(vf, arch, cdir_pos, &ecrec);
00392        if(cdir_pos < 0) 
00393             return cdir_pos;
00394     }
00395   
00396     return 0;
00397 }
00398 
00399 static int parse_zipfile(void *data, ventry *ve, struct archive *arch)
00400 {
00401     int res;
00402     vfile *vf;
00403 
00404     res = av_open(ve->mnt->base, AVO_RDONLY, 0, &vf);
00405     if(res < 0)
00406         return res;
00407 
00408     res = read_zipfile(vf, arch);
00409     av_close(vf);
00410     
00411     return res;  
00412 }
00413 
00414 static int zip_close(struct archfile *fil)
00415 {
00416     struct zfile *zfil = fil->data;
00417 
00418     av_unref_obj(zfil);
00419     return 0;
00420 }
00421 
00422 static int zip_open(ventry *ve, struct archfile *fil)
00423 {
00424     int res;
00425     char buf[LDIRENT_SIZE];
00426     struct ldirentry ent;
00427     int headersize;
00428     struct zipnode *info = (struct zipnode *) fil->nod->data;
00429     avoff_t offset;
00430 
00431     if(info == NULL) {
00432         /* no info means accessing base zip directory without any filename */
00433         return -EISDIR;
00434     }
00435   
00436     offset = info->headeroff;
00437     if(offset == -1) {
00438         av_log(AVLOG_ERROR, "UZIP: Cannot handle multivolume archives");
00439         return -ENOENT;
00440     }
00441 
00442     res = av_pread_all(fil->basefile, buf, LDIRENT_SIZE, offset);
00443     if(res < 0)
00444         return res;
00445 
00446     if(buf[0] != 'P' || buf[1] != 'K' || buf[2] != 3 || buf[3] != 4) {
00447         av_log(AVLOG_ERROR, "UZIP: Broken archive");
00448         return -EIO;
00449     }
00450 
00451     ent.need_version = DBYTE(buf+LDIRENT_NEED_VERSION);
00452     ent.flag         = DBYTE(buf+LDIRENT_FLAG);
00453     ent.method       = DBYTE(buf+LDIRENT_METHOD);
00454     ent.mod_time     = QBYTE(buf+LDIRENT_MOD_TIME);
00455     ent.crc          = QBYTE(buf+LDIRENT_CRC);
00456     ent.comp_size    = QBYTE(buf+LDIRENT_COMP_SIZE);
00457     ent.file_size    = QBYTE(buf+LDIRENT_FILE_SIZE);
00458     ent.fname_len    = DBYTE(buf+LDIRENT_FNAME_LEN);
00459     ent.extra_len    = DBYTE(buf+LDIRENT_EXTRA_LEN);
00460 
00461     if(ent.method != METHOD_STORE && ent.method != METHOD_DEFLATE) {
00462         av_log(AVLOG_ERROR, "UZIP: Cannot handle compression method %i",
00463                ent.method);
00464         return -ENOENT;
00465     }
00466 
00467     if((ent.flag & 0x08) != 0) {
00468        /* can't trust local header, use central directory: */
00469     
00470        ent.comp_size = fil->nod->realsize;
00471        ent.file_size = fil->nod->st.size;
00472        ent.crc = info->crc;
00473     }
00474 
00475     info->method = ent.method;
00476     headersize = LDIRENT_SIZE + ent.fname_len + ent.extra_len;
00477     fil->nod->offset = offset + headersize;
00478 
00479     if(ent.method == METHOD_DEFLATE) {
00480         struct zfile *zfil;
00481 
00482         zfil = av_zfile_new(fil->basefile, fil->nod->offset, ent.crc, 1);
00483         fil->data = zfil;
00484     }
00485 
00486     return 0;
00487 }
00488 
00489 
00490 static avssize_t zip_deflate_read(vfile *vf, char *buf, avsize_t nbyte)
00491 {
00492     avssize_t res;
00493     struct archfile *fil = arch_vfile_file(vf);
00494     struct zfile *zfil = (struct zfile *) fil->data;
00495     struct zipnode *info = (struct zipnode *) fil->nod->data;
00496     struct zcache *zc;
00497 
00498     zc = (struct zcache *) av_cacheobj_get(info->cache);
00499     if(zc == NULL) {
00500         av_unref_obj(info->cache);
00501         info->cache = NULL;
00502         zc = av_zcache_new();
00503     }
00504     
00505     res = av_zfile_pread(zfil, zc, buf, nbyte, vf->ptr);
00506     if(res >= 0) {
00507         avoff_t cachesize;
00508 
00509         vf->ptr += res;
00510         cachesize = av_zcache_size(zc);
00511         if(cachesize != 0) {
00512             /* FIXME: name of this cacheobj? */
00513             if(info->cache == NULL)
00514                 info->cache = av_cacheobj_new(zc, "(uzip:index)");
00515             av_cacheobj_setsize(info->cache, cachesize);
00516         }
00517     }
00518     else {
00519         av_unref_obj(info->cache);
00520         info->cache = NULL;
00521     }
00522     av_unref_obj(zc);
00523 
00524     return res;
00525 }
00526 
00527 static avssize_t zip_read(vfile *vf, char *buf, avsize_t nbyte)
00528 {
00529     avssize_t res;
00530     struct archfile *fil = arch_vfile_file(vf);
00531     struct zfile *zfil = (struct zfile *) fil->data;
00532 
00533     if(zfil != NULL)
00534         res = zip_deflate_read(vf, buf, nbyte);
00535     else
00536         res = av_arch_read(vf, buf, nbyte);
00537 
00538     return res;
00539 }
00540 
00541 extern int av_init_module_uzip(struct vmodule *module);
00542 
00543 int av_init_module_uzip(struct vmodule *module)
00544 {
00545     int res;
00546     struct avfs *avfs;
00547     struct ext_info zipexts[5];
00548     struct archparams *ap;
00549 
00550     zipexts[0].from = ".zip",   zipexts[0].to = NULL;
00551     zipexts[1].from = ".jar",   zipexts[1].to = NULL;
00552     zipexts[2].from = ".ear",   zipexts[2].to = NULL;
00553     zipexts[3].from = ".war",   zipexts[3].to = NULL;
00554     zipexts[4].from = NULL;
00555 
00556     res = av_archive_init("uzip", zipexts, AV_VER, module, &avfs);
00557     if(res < 0)
00558         return res;
00559 
00560     ap = (struct archparams *) avfs->data;
00561     ap->parse = parse_zipfile;
00562     ap->open = zip_open;
00563     ap->close = zip_close;
00564     ap->read = zip_read;
00565 
00566     av_add_avfs(avfs);
00567 
00568     return 0;
00569 }
00570