Подтвердить что ты не робот

Извлечь файл из строки ZIP

У меня есть строка BASE64 zip файла, содержащего один файл XML.

Любые идеи о том, как я могу получить содержимое файла XML без необходимости иметь дело с файлами на диске?

Мне бы очень хотелось сохранить весь процесс в памяти, поскольку XML имеет только 1-5k.

Было бы неприятно писать zip, извлекать XML, а затем загружать его и удалять все.

4b9b3361

Ответ 1

После нескольких часов исследований я думаю, что удивительно невозможно обработать zip без временного файла:

  • Первая попытка с php://memory не будет работать, вывести поток, который не может быть прочитан функциями типа file_get_contents() или ZipArchive::open(). В комментариях есть ссылка на php-bugtracker из-за отсутствия документации по этой проблеме.
  • Существует поддержка потока ZipArchive с ::getStream(), но, как указано в руководстве, она поддерживает только операцию чтения в открытом файле. Таким образом, вы не можете создавать архив на лету с этим.
  • Обертка zip:// также доступна только для чтения: Создать ZIP файл с помощью обертки fopen()
  • Я также сделал некоторые попытки с другими php-обертками/протоколлами, например

     file_get_contents("zip://data://text/plain;base64,{$base64_string}#test.txt")
     $zip->open("php://filter/read=convert.base64-decode/resource={$base64_string}")
     $zip->open("php://filter/read=/resource=php://memory")
    

    но для меня они вообще не работают, даже если в руководстве есть примеры. Поэтому вам нужно усвоить таблетку и создать временный файл.


Исходный ответ:

Это просто способ временного хранения. Надеюсь, вы сами справитесь с обработкой zip и анализом xml.

Используйте php php://memory (doc) wrapper. Имейте в виду, что это полезно только для небольших файлов, потому что оно хранится в памяти - очевидно. В противном случае используйте php://temp.

<?php

// the decoded content of your zip file
$text = 'base64 _decoded_ zip content';

// this will empty the memory and appen your zip content
$written = file_put_contents('php://memory', $text);

// bytes written to memory
var_dump($written);

// new instance of the ZipArchive
$zip = new ZipArchive;

// success of the archive reading
var_dump(true === $zip->open('php://memory'));

Ответ 2

У меня была аналогичная проблема, я закончил ее вручную.
https://www.pkware.com/documents/casestudies/APPNOTE.TXT

Это извлекает один файл (только первый), без ошибок /crc, предполагает, что используется deflate.

// zip in a string
$data = file_get_contents('test.zip');

// magic
$head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,0,30));
$filename = substr($data,30,$head['namelen']);
$raw = gzinflate(substr($data,30+$head['namelen']+$head['exlen'],$head['csize']));

// first file uncompressed and ready to use
file_put_contents($filename,$raw);

Ответ 3

toster-cx правильно, вы должны наградить его очками, это пример, когда zip происходит от ответа на мыло в виде байтового массива (двоичного), содержимое представляет собой XML файл:

$objResponse = $objClient->__soapCall("sendBill",array(parameters));
$fileData=unzipByteArray($objResponse->applicationResponse);
header("Content-type: text/xml");
echo $fileData;
function unzipByteArray($data){
  /*this firts is a directory*/
  $head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,0,30));
  $filename = substr($data,30,$head['namelen']);
  $if=30+$head['namelen']+$head['exlen']+$head['csize'];
 /*this second is the actua file*/
  $head = unpack("Vsig/vver/vflag/vmeth/vmodt/vmodd/Vcrc/Vcsize/Vsize/vnamelen/vexlen", substr($data,$if,30));
  $raw = gzinflate(substr($data,$if+$head['namelen']+$head['exlen']+30,$head['csize']));
  /*you can create a loop and continue decompressing more files if the were*/
  return $raw;
}

Ответ 4

Если вы знаете имя файла внутри .zip, просто выполните следующее:

<?php
$xml = file_get_contents('zip://./your-zip.zip#your-file.xml');

Если у вас простая строка, просто выполните следующее:

<?php
$xml = file_get_contents('compress.zlib://data://text/plain;base64,'.$base64_encoded_string);

[править] Документация есть: http://www.php.net/manual/en/wrappers.php

Из комментариев: если у вас нет кодированной в base64 строки, вам нужно выполнить urlencode() перед использованием оболочки data://.

<?php
$xml = file_get_contents('compress.zlib://data://text/plain,'.urlencode($text));

[edit 2] Даже если вы уже нашли решение с файлом, есть решение (для проверки), которое я не видел в вашем ответе:

<?php
$zip = new ZipArchive;
$zip->open('data::text/plain,'.urlencode($base64_decoded_string));
$zip2 = new ZipArchive;
$zip2->open('data::text/plain;base64,'.urlencode($base64_string));

Ответ 5

Если вы работаете в Linux и у вас есть администрация системы. Вы можете смонтировать небольшой виртуальный диск с помощью tmpfs, тогда будут работать стандартные функции file_get/put и ZipArchive, за исключением того, что он не записывает на диск, он записывает в память. Чтобы он был постоянно готов, fstab - это что-то вроде:

/media/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=2M 0 0

Установите свой размер и местоположение соответственно, чтобы он подходил вам. Использование php для монтирования виртуального диска и его удаления после использования (если оно даже имеет привилегии), вероятно, менее эффективно, чем просто запись на диск, если только у вас нет большого количества файлов для обработки за один раз. Хотя это не чисто php-решение и не переносимо. Вам все равно нужно будет удалить "файлы" после использования или попросить ОС очистить старые файлы. Они не будут сохраняться после перезагрузки или перемонтирования виртуального диска.

Ответ 6

если вы хотите прочитать содержимое файла из zip и xml внутри вас, посмотрите на это, я использую его для подсчета слов из docx (это zip)

if (!function_exists('docx_word_count')) {
    function docx_word_count($filename)
    {
        $zip = new ZipArchive();
        if ($zip->open($filename) === true) {
            if (($index = $zip->locateName('docProps/app.xml')) !== false) {
                $data = $zip->getFromIndex($index);
                $zip->close();
                $xml = new SimpleXMLElement($data);
                return $xml->Words;
            }
            $zip->close();
        }
        return 0;
    }
}