Skip to content

Bug: Fragment part of path lost in PHP >= 7.3 (files with # in name) #90

@dg

Description

@dg

Bug: Fragment part of path lost in PHP >= 7.3

Summary

Files with # character in their name cannot be accessed via ssh2.sftp:// stream wrapper in PHP >= 7.3 due to a regression in php_ssh2_fopen_wrapper_parse_path().

Description

The function php_ssh2_fopen_wrapper_parse_path() in ssh2_fopen_wrappers.c has different code paths for PHP < 7.3 and PHP >= 7.3. The comment says:

/*
    Find resource->path in the path string, then copy the entire string from the original path.
    This includes ?query#fragment in the path string
*/

However, the PHP >= 7.3 code doesn't actually preserve the fragment:

#if PHP_VERSION_ID < 70300
{
    char * s;
    s = resource->path;
    resource->path = estrdup(strstr(path, resource->path));  // Correct: copies including #fragment
    efree(s);
}
#else
{
    zend_string *tmp;
    tmp = resource->path;
    resource->path = zend_string_init(ZSTR_VAL(resource->path), ZSTR_LEN(resource->path), 0);  // Bug: just copies same length
    zend_string_release(tmp);
}
#endif

The PHP < 7.3 version uses strstr() to find the path in the original string and copies from that position to the end, preserving the #fragment. The PHP >= 7.3 version just creates a copy with the same length, losing everything after #.

Steps to Reproduce

$connection = ssh2_connect('example.com', 22);
ssh2_auth_password($connection, 'user', 'pass');
$sftp = ssh2_sftp($connection);

$path = '/test#file.txt';
$url = 'ssh2.sftp://' . (int) $sftp . $path;

file_put_contents($url, 'test');  // Creates file named "test" instead of "test#file.txt"

Expected Result

File test#file.txt is created.

Actual Result

File test is created (everything after # is lost).

Environment

  • PHP 8.4.13
  • ssh2 extension from PECL

Suggested Fix

#else
{
    const char *path_in_original = strstr(path, ZSTR_VAL(resource->path));
    if (path_in_original) {
        zend_string_release(resource->path);
        resource->path = zend_string_init(path_in_original, strlen(path_in_original), 0);
    }
}
#endif

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions