miércoles, 5 de octubre de 2011

Como hacer que la librería java.util.zip.ZipInputStream extraiga correctamente ficheros cuyos nombres contengan caracteres especiales del idioma castellano (tíldes, ñ, ...)

1. DETALLES DEL ENTORNO
java version:
    J2RE 1.5.0 IBM J9 2.3 Linux x86-32 j9vmxi3223ifx-20101008 (JIT enabled) J9VM - 20101007_66049_lHdSMr JIT - 20100623_16197ifx1_r8 GC - 20100211_AA
charset : UTF-8
Sistema operativo: Linux RedHat 4.1.2-50

Sistema Operativo donde se generó el .zip: Widows 7 Professional.
2. DESCRIPCIÓN GENERAL DEL PROBLEMA

Al ir a extraer las carpetas y/o documentos cuyos nombres contienen caracteres especiales del español (tildes, ñ, etc...), esos caracteres especiales no se reconocen correctamente. También sucede con caracteres especiales de otros idiomas como el alemán o el chino. (http://bugs.sun.com/view_bug.do?bug_id=4820807).

Los ficheros zip son contenido binario, y por tanto no tiene sentido de hablar del charset enconding del contenido de éstos, sin embargo, cuando se descomprimen estos ficheros, los nombres de los ficheros contenidos en ellos sí que deben estar codificados atendiendo a una determinada codificación.

Parece que la clase ZipInputStream no gestiona bien la extracción de los nombres de los contenidos para los distintos charset encondings.

3. SOLUCIÓN ADOPTADA (PARA IDIOMA ESPAÑOL).

La solución adoptada consiste en "parchear" la clase ZipInputStream para que intente en primer lugar obtener los nombres de los contenidos utilizando como charset encoding "utf-8". Si esta obtención falla, se intentará realizar la misma operación, pero esta vez utilizando como charset "Cp850" (ms-dos latin1).
Además realizaremos otra pequeña modificación, consistente en reinicializar el array de bytes que almacena temporalmente el nombre de cada fichero extraído del zip para evitar arrastrar caracteres de nombres de ficheros almacenados anteriormente en este array de bytes.
A continuación se expone la solución en pasos:

1- Crear un paquete en nuestro proyecto con tres clases:
         - ZipInputStream.java
         - ZipConstants.java
         - ZipEntry.java
2- Copiar dentro de estas clases el correspondiente código fuente  de las clases homónimas del paquete java.util.zip de las librerías de java.


3- Modificar el método readLOC() de la clase ZipInputStream.java y dejarlo de la siguiente forma:


    private ZipEntry readLOC() throws IOException {
        tmpbuf = new byte[512];
        b = new byte[256];

        try {
            readFully(tmpbuf, 0, LOCHDR);
        } catch (EOFException e) {
            return null;
        }
        if (get32(tmpbuf, 0) != LOCSIG) {
            return null;
        }
        // get the entry name and create the ZipEntry first
        int len = get16(tmpbuf, LOCNAM);
        if (len == 0) {
            throw new ZipException("missing entry name");
        }
        int blen = b.length;
        if (len > blen) {
            do
                blen = blen * 2;
            while (len > blen);
            b = new byte[blen];
        }
        readFully(b, 0, len);

        // TODO
        // ZipEntry e = createZipEntry(getUTF8String(b, 0, len));
        String fileName = null;
        try {
            fileName = getUTF8String(b, 0, len);
        } catch (Exception ex) {

            fileName = new String(b, "Cp850").trim();
           

        }
        ZipEntry e = createZipEntry(fileName);

        // now get the remaining fields for the entry
        e.version = get16(tmpbuf, LOCVER);
        e.flag = get16(tmpbuf, LOCFLG);
        if ((e.flag & 1) == 1) {
            throw new ZipException("encrypted ZIP entry not supported");
        }
        e.method = get16(tmpbuf, LOCHOW);
        e.time = get32(tmpbuf, LOCTIM);
        if ((e.flag & 8) == 8) {
            /* EXT descriptor present */
            if (e.method != DEFLATED) {
                throw new ZipException(
                        "only DEFLATED entries can have EXT descriptor");
            }
        } else {
            e.crc = get32(tmpbuf, LOCCRC);
            e.csize = get32(tmpbuf, LOCSIZ);
            e.size = get32(tmpbuf, LOCLEN);
        }
        len = get16(tmpbuf, LOCEXT);
        if (len > 0) {
            byte[] bb = new byte[len];
            readFully(bb, 0, len);
            e.extra = bb;
        }
        return e;
    }



4- Editar la clase ZipEntry.java y borrar el siguiente código:


static {
    /* Zip library is loaded from System.initializeSystemClass */
    /*
    initIDs();



4. PROBLEMAS


Lógicamente solo se obtendrán correctamente los ficheros cuyo nombre con caracteres especiales estén codificados con "utf-8" o "Cp850".

No hay comentarios:

Publicar un comentario