如何确定一个字符串是否被压缩过?

11

除了比较使用gzuncompress函数前后字符串的大小,我该如何确定一个字符串是否已经被gzcompress压缩?或者说,那种方法是正确的?

4个回答

28

前置条件:
我猜,如果您发送一个请求,您可以立即查看$http_response_header,以查看数组中的某个项目是否是Content-Encoding: gzip的变体。但这并不理想!
有一种更好的方法。


以下是如何操作...

像老板一样检查是否为GZIP!

根据GZIP RFC

GZIP内容的头部如下所示

+---+---+---+---+---+---+---+---+---+---+
|ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
+---+---+---+---+---+---+---+---+---+---+

这段代码中,ID1ID2 用于标识内容为GZIP格式。而CM 表示ZLIB_ENCODING(压缩方法)为ZLIB_ENCODING_DEFLATE - 这是所有Web服务器上通常与GZIP一起使用的压缩方法。

哦!并且它们有固定的值:

  • ID1 的值为"\x1f"
  • ID2 的值为"\x8b"
  • CM 的值为"\x08" (或者只是8...)

接近成功:


`$is_gzip = 0 === mb_strpos($mystery_string , "\x1f" . "\x8b" . "\x08");`

工作示例

<?php
/** @link https://gist.github.com/eladkarako/d8f3addf4e3be92bae96#file-checking_gzip_like_a_boss-php */

date_default_timezone_set("Asia/Jerusalem");

while (ob_get_level() > 0) ob_end_flush();
mb_language("uni");
@mb_internal_encoding('UTF-8');
setlocale(LC_ALL, 'en_US.UTF-8');

header('Time-Zone: Asia/Jerusalem');
header('Charset: UTF-8');
header('Content-Encoding: UTF-8');
header('Content-Type: text/plain; charset=UTF-8');
header('Access-Control-Allow-Origin: *');

function get($url, $cookie = '') {
  $html = @file_get_contents($url, false, stream_context_create([
    'http' => [
      'method' => "GET",
      'header' => implode("\r\n", [''
        , 'Pragma: no-cache'
        , 'Cache-Control: no-cache'
        , 'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2310.0 Safari/537.36'
        , 'DNT: 1'
        , 'Accept-Language: en-US,en;q=0.8'
        , 'Accept: text/plain'
        , 'X-Forwarded-For: ' . implode(', ', array_unique(array_filter(array_map(function ($item) { return filter_input(INPUT_SERVER, $item, FILTER_SANITIZE_SPECIAL_CHARS); }, ['HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR', 'HTTP_CLIENT_IP', 'SERVER_ADDR', 'REMOTE_ADDR']), function ($item) { return null !== $item; })))
        , 'Referer: http://eladkarako.com'
        , 'Connection: close'
        , 'Cookie: ' . $cookie
        , 'Accept-Encoding: gzip'
      ])
    ]]));

  $is_gzip = 0 === mb_strpos($html, "\x1f" . "\x8b" . "\x08", 0, "US-ASCII");

  return $is_gzip ? zlib_decode($html, ZLIB_ENCODING_DEFLATE) : $html;
}

$html = get('http://www.pogdesign.co.uk/cat/');

echo $html;

这里有什么值得一提的吗?

  • 首先要初始化 PHP 引擎以使用 UTF-8(因为我们不确定 Web 服务器是否会返回 GZIP 内容)。
  • 提供头文件 Accept-Encoding: gzip,告诉 Web 服务器它可以输出 GZIP 内容。
  • 发现 GZIP 内容(应使用带有 ASCII 编码的多字节函数)。
  • 最后使用 ZLIB 方法轻松返回纯文本输出。

9
这段代码对于使用gzencode压缩的字符串非常有效,但对于gzcompress压缩的字符串则不适用,因为它们没有gzip头文件。因此,代码使用了一个函数来判断字符串是否经过压缩,如果包含gzip头,则返回true,如果不包含gzip头但可以通过尝试解压缩来还原,则也返回true,否则返回false。 - futtta
1
@futtta - 不错的补充,顺便提醒一下,在 mb_strpos 中仍应保留 "US-ASCII" - user257319

9

一个字符串和一个压缩后的字符串都是由字节序列组成的。你无法真正区分一个字节序列和另一个字节序列。你应该知道一堆字节是否代表压缩格式,需要查看相应的元数据。

如果你真的需要以编程方式猜测,可以尝试以下几种方法:

  • 尝试解压缩字符串并查看解压操作是否成功。如果失败,则这些字节可能不代表压缩的字符串。
  • 尝试检查明显的“奇怪”字节,如任何0x20之前的字节。这些字节通常不用于普通文本。但是,并没有真正保证它们会出现在压缩的字符串中。
  • 使用mb_check_encoding来查看一个字符串是否在你所怀疑的编码中有效。如果无效,则很可能已经被压缩了(或者你选择了错误的编码)。请注意,几乎任何字节序列在几乎所有单字节编码中都是有效的,因此这只适用于多字节编码。

抱歉我很无助,但我该如何执行第一个要点?当我在未压缩的字符串上调用gzuncompress时,它不会返回错误,只是相同的字符串。 - Max
嗯,对我来说它返回了 false... var_dump(gzuncompress('foobar')); - deceze
2
请注意,"\t"(制表符)、"\n"(换行符)和"\r"(回车符)都可能出现在任何字符串中,并且它们的 ASCII 码值都小于 0x20。 - dotancohen

2
这对我来说很有效:
if (@gzuncompress($_xml)!==false) {
   // gzipped sring

0

你可以像@DiDiegodaFonseca所说的那样,简单地尝试对数据使用gzuncompress()。如果失败了,则它不是由gzcompress()生成的,或者没有被忠实地传输。

如果你真的想要,你可以检查前两个字节是否为zlib头(不是gzip头,如接受的答案中错误地建议的那样)。gzcompress()生成一个zlib流,而不是gzip流。gzencode()生成gzip流。gzdeflate()生成原始deflate流。

RFC 1950描述了zlib头。它是两个字节,将这两个字节作为大端16位无符号整数必须是31的倍数。除了检查这一点之外,你还可以检查第一个字节的低四位是否为8(1000),并且高位为零。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接