Skip to content

Fix gzfile() truncating lines with NUL bytes and splitting long lines - #23902

Open
lacatoire wants to merge 1 commit into
php:masterfrom
lacatoire:fix/gzfile-nul-and-line-split
Open

lacatoire wants to merge 1 commit into
php:masterfrom
lacatoire:fix/gzfile-nul-and-line-split

Conversation

@lacatoire

@lacatoire lacatoire commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

gzfile() reads each line into a fixed 8192-byte buffer with php_stream_gets() and stores it with add_index_string(), which recomputes the length with strlen(). This causes two bugs:

  • A NUL byte in the line silently truncates everything after it.
  • Lines longer than 8190 bytes are split into multiple array entries.

file() already avoids both issues by using php_stream_get_line() with a NULL/0 buffer (dynamically sized) and add_index_stringl() (explicit length). This applies the same approach to gzfile().

Reproducer:

<?php
$data = "before\0after\nline2\n";
file_put_contents('n.gz', gzencode($data));
var_dump(array_map('bin2hex', gzfile('n.gz'))); // drops everything from \0 onward

A regression test is included covering NUL preservation (checked against gzgets() output) and a 20001-byte line staying as a single array entry.

The loop used a fixed 8192-byte C buffer with php_stream_gets() and stored
lines via add_index_string() (strlen-based). This caused two bugs:
- NUL bytes truncated the stored string mid-line (data loss)
- Lines longer than 8190 bytes were split into multiple entries

Replace with php_stream_get_line(stream, NULL, 0, &len) + add_index_stringl(),
matching how file() handles the same cases. The buffer is now allocated by
the stream layer as needed, removing both the NUL and the size limit.

Fixes: #925

@Sjord Sjord left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but the test could use some polishing.

$tmp = tempnam(sys_get_temp_dir(), 'gzfile');

// --- NUL byte preservation ---
$data_nul = "avant\0apres\nligne2\n";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make more sense to use English test strings.

$lines = gzfile($tmp);
var_dump(count($lines)); // 2
var_dump(strlen($lines[0])); // 12 (avant\0apres\n)
var_dump(bin2hex($lines[0])); // contains NUL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addslashes could be useful to assert this while still being human-readable.

var_dump(count($lines)); // 1
var_dump(strlen($lines[0])); // 20001

unlink($tmp);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this go into a --CLEAN-- block?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants