Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/fopen_wrappers.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
14
   |          Jim Winstead <jimw@php.net>                                 |
15
   +----------------------------------------------------------------------+
16
 */
17
18
/* {{{ includes */
19
#include "php.h"
20
#include "php_globals.h"
21
#include "SAPI.h"
22
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <errno.h>
26
#include <sys/types.h>
27
#include <sys/stat.h>
28
#include <fcntl.h>
29
30
#ifdef PHP_WIN32
31
#define O_RDONLY _O_RDONLY
32
#include "win32/param.h"
33
#else
34
#include <sys/param.h>
35
#endif
36
37
#include "ext/standard/head.h"
38
#include "ext/standard/php_standard.h"
39
#include "zend_compile.h"
40
#include "php_network.h"
41
#include "zend_smart_str.h"
42
43
#ifdef HAVE_PWD_H
44
#include <pwd.h>
45
#endif
46
47
#ifdef HAVE_SYS_SOCKET_H
48
#include <sys/socket.h>
49
#endif
50
51
#ifdef PHP_WIN32
52
#include <winsock2.h>
53
#else
54
#include <netinet/in.h>
55
#include <netdb.h>
56
#ifdef HAVE_ARPA_INET_H
57
#include <arpa/inet.h>
58
#endif
59
#endif
60
61
#if defined(PHP_WIN32) || defined(__riscos__)
62
#undef AF_UNIX
63
#endif
64
65
#if defined(AF_UNIX)
66
#include <sys/un.h>
67
#endif
68
/* }}} */
69
70
/* {{{ OnUpdateBaseDir
71
Allows any change to open_basedir setting in during Startup and Shutdown events,
72
or a tightening during activation/runtime/deactivation */
73
PHPAPI ZEND_INI_MH(OnUpdateBaseDir)
74
60
{
75
60
  char **p = ZEND_INI_GET_ADDR();
76
60
  char *pathbuf, *ptr, *end;
77
78
60
  if (stage == PHP_INI_STAGE_STARTUP || stage == PHP_INI_STAGE_SHUTDOWN || stage == PHP_INI_STAGE_ACTIVATE || stage == PHP_INI_STAGE_DEACTIVATE) {
79
38
    if (PG(open_basedir_modified)) {
80
18
      efree(*p);
81
18
    }
82
    /* We're in a PHP_INI_SYSTEM context, no restrictions */
83
38
    *p = new_value ? ZSTR_VAL(new_value) : NULL;
84
38
    PG(open_basedir_modified) = false;
85
38
    return SUCCESS;
86
38
  }
87
88
  /* Shortcut: When we have a open_basedir and someone tries to unset, we know it'll fail */
89
22
  if (!new_value || !*ZSTR_VAL(new_value)) {
90
4
    return FAILURE;
91
4
  }
92
93
  /* Is the proposed open_basedir at least as restrictive as the current setting? */
94
18
  smart_str buf = {0};
95
18
  ptr = pathbuf = estrdup(ZSTR_VAL(new_value));
96
36
  while (ptr && *ptr) {
97
18
    end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
98
18
    if (end != NULL) {
99
0
      *end = '\0';
100
0
      end++;
101
0
    }
102
18
    char resolved_name[MAXPATHLEN + 1];
103
18
    if (expand_filepath(ptr, resolved_name) == NULL) {
104
0
      efree(pathbuf);
105
0
      smart_str_free(&buf);
106
0
      return FAILURE;
107
0
    }
108
18
    if (php_check_open_basedir_ex(resolved_name, 0) != 0) {
109
      /* At least one portion of this open_basedir is less restrictive than the prior one, FAIL */
110
0
      efree(pathbuf);
111
0
      smart_str_free(&buf);
112
0
      return FAILURE;
113
0
    }
114
18
    if (smart_str_get_len(&buf) != 0) {
115
0
      smart_str_appendc(&buf, DEFAULT_DIR_SEPARATOR);
116
0
    }
117
18
    smart_str_appends(&buf, resolved_name);
118
18
    ptr = end;
119
18
  }
120
18
  efree(pathbuf);
121
122
  /* Everything checks out, set it */
123
18
  zend_string *tmp = smart_str_extract(&buf);
124
18
  char *result = estrdup(ZSTR_VAL(tmp));
125
18
  if (PG(open_basedir_modified)) {
126
0
    efree(*p);
127
0
  }
128
18
  *p = result;
129
18
  PG(open_basedir_modified) = true;
130
18
  zend_string_release(tmp);
131
132
18
  return SUCCESS;
133
18
}
134
/* }}} */
135
136
/* {{{ php_check_specific_open_basedir
137
  When open_basedir is not NULL, check if the given filename is located in
138
  open_basedir. Returns -1 if error or not in the open_basedir, else 0.
139
  When open_basedir is NULL, always return 0.
140
*/
141
PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path)
142
1.21k
{
143
1.21k
  char resolved_name[MAXPATHLEN + 1];
144
1.21k
  char resolved_basedir[MAXPATHLEN + 1];
145
1.21k
  char local_open_basedir[MAXPATHLEN];
146
1.21k
  char path_tmp[MAXPATHLEN + 1];
147
1.21k
  char *path_file;
148
1.21k
  size_t resolved_basedir_len;
149
1.21k
  size_t resolved_name_len;
150
1.21k
  size_t path_len;
151
1.21k
  int nesting_level = 0;
152
153
  /* Special case basedir==".": Use script-directory */
154
1.21k
  if (strcmp(basedir, ".") || !VCWD_GETCWD(local_open_basedir, MAXPATHLEN)) {
155
    /* Else use the unmodified path */
156
1.21k
    strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir));
157
1.21k
  }
158
159
1.21k
  path_len = strlen(path);
160
1.21k
  if (path_len > (MAXPATHLEN - 1)) {
161
    /* empty and too long paths are invalid */
162
0
    return -1;
163
0
  }
164
165
  /* normalize and expand path */
166
1.21k
  if (expand_filepath(path, resolved_name) == NULL) {
167
6
    return -1;
168
6
  }
169
170
1.21k
  path_len = strlen(resolved_name);
171
1.21k
  memcpy(path_tmp, resolved_name, path_len + 1); /* safe */
172
173
4.89k
  while (VCWD_REALPATH(path_tmp, resolved_name) == NULL) {
174
3.68k
#if defined(PHP_WIN32) || defined(HAVE_SYMLINK)
175
3.68k
    if (nesting_level == 0) {
176
1.11k
      ssize_t ret;
177
1.11k
      char buf[MAXPATHLEN];
178
179
1.11k
      ret = php_sys_readlink(path_tmp, buf, MAXPATHLEN - 1);
180
1.11k
      if (ret == -1) {
181
        /* not a broken symlink, move along.. */
182
1.11k
      } else {
183
        /* put the real path into the path buffer */
184
0
        memcpy(path_tmp, buf, ret);
185
0
        path_tmp[ret] = '\0';
186
0
      }
187
1.11k
    }
188
3.68k
#endif
189
190
#ifdef PHP_WIN32
191
    path_file = strrchr(path_tmp, DEFAULT_SLASH);
192
    if (!path_file) {
193
      path_file = strrchr(path_tmp, '/');
194
    }
195
#else
196
3.68k
    path_file = strrchr(path_tmp, DEFAULT_SLASH);
197
3.68k
#endif
198
3.68k
    if (!path_file) {
199
      /* none of the path components exist. definitely not in open_basedir.. */
200
0
      return -1;
201
3.68k
    } else {
202
3.68k
      path_len = path_file - path_tmp + 1;
203
#ifdef PHP_WIN32
204
      if (path_len > 1 && path_tmp[path_len - 2] == ':') {
205
        if (path_len != 3) {
206
          return -1;
207
        }
208
        /* this is c:\ */
209
        path_tmp[path_len] = '\0';
210
      } else {
211
        path_tmp[path_len - 1] = '\0';
212
      }
213
#else
214
3.68k
      path_tmp[path_len - 1] = '\0';
215
3.68k
#endif
216
3.68k
    }
217
3.68k
    if (*path_tmp == '\0') {
218
      /* Do not pass an empty string to realpath(), as this will resolve to CWD. */
219
5
      break;
220
5
    }
221
3.67k
    nesting_level++;
222
3.67k
  }
223
224
  /* Resolve open_basedir to resolved_basedir */
225
1.21k
  if (expand_filepath(local_open_basedir, resolved_basedir) != NULL) {
226
1.21k
    size_t basedir_len = strlen(basedir);
227
    /* Handler for basedirs that end with a / */
228
1.21k
    resolved_basedir_len = strlen(resolved_basedir);
229
#ifdef PHP_WIN32
230
    if (basedir[basedir_len - 1] == PHP_DIR_SEPARATOR || basedir[basedir_len - 1] == '/') {
231
#else
232
1.21k
    if (basedir[basedir_len - 1] == PHP_DIR_SEPARATOR) {
233
0
#endif
234
0
      if (resolved_basedir[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) {
235
0
        resolved_basedir[resolved_basedir_len] = PHP_DIR_SEPARATOR;
236
0
        resolved_basedir[++resolved_basedir_len] = '\0';
237
0
      }
238
1.21k
    } else {
239
1.21k
        resolved_basedir[resolved_basedir_len++] = PHP_DIR_SEPARATOR;
240
1.21k
        resolved_basedir[resolved_basedir_len] = '\0';
241
1.21k
    }
242
243
1.21k
    resolved_name_len = strlen(resolved_name);
244
1.21k
    if (path_tmp[path_len - 1] == PHP_DIR_SEPARATOR) {
245
0
      if (resolved_name[resolved_name_len - 1] != PHP_DIR_SEPARATOR) {
246
0
        resolved_name[resolved_name_len] = PHP_DIR_SEPARATOR;
247
0
        resolved_name[++resolved_name_len] = '\0';
248
0
      }
249
0
    }
250
251
    /* Check the path */
252
#ifdef PHP_WIN32
253
    if (strncasecmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) {
254
#else
255
1.21k
    if (strncmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) {
256
78
#endif
257
78
      if (resolved_name_len > resolved_basedir_len &&
258
78
        resolved_name[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) {
259
0
        return -1;
260
78
      } else {
261
        /* File is in the right directory */
262
78
        return 0;
263
78
      }
264
1.13k
    } else {
265
      /* /openbasedir/ and /openbasedir are the same directory */
266
1.13k
      if (resolved_basedir_len == (resolved_name_len + 1) && resolved_basedir[resolved_basedir_len - 1] == PHP_DIR_SEPARATOR) {
267
#ifdef PHP_WIN32
268
        if (strncasecmp(resolved_basedir, resolved_name, resolved_name_len) == 0) {
269
#else
270
264
        if (strncmp(resolved_basedir, resolved_name, resolved_name_len) == 0) {
271
264
#endif
272
264
          return 0;
273
264
        }
274
264
      }
275
869
      return -1;
276
1.13k
    }
277
1.21k
  } else {
278
    /* Unable to resolve the real path, return -1 */
279
0
    return -1;
280
0
  }
281
1.21k
}
282
/* }}} */
283
284
PHPAPI int php_check_open_basedir(const char *path)
285
1.26k
{
286
1.26k
  return php_check_open_basedir_ex(path, 1);
287
1.26k
}
288
289
/* {{{ php_check_open_basedir */
290
PHPAPI int php_check_open_basedir_ex(const char *path, int warn)
291
1.28k
{
292
  /* Only check when open_basedir is available */
293
1.28k
  if (PG(open_basedir) && *PG(open_basedir)) {
294
1.22k
    char *pathbuf;
295
1.22k
    char *ptr;
296
1.22k
    char *end;
297
298
    /* Check if the path is too long so we can give a more useful error
299
    * message. */
300
1.22k
    if (strlen(path) > (MAXPATHLEN - 1)) {
301
3
      php_error_docref(NULL, E_WARNING, "File name is longer than the maximum allowed path length on this platform (%d): %s", MAXPATHLEN, path);
302
3
      errno = EINVAL;
303
3
      return -1;
304
3
    }
305
306
1.21k
    pathbuf = estrdup(PG(open_basedir));
307
308
1.21k
    ptr = pathbuf;
309
310
2.09k
    while (ptr && *ptr) {
311
1.21k
      end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
312
1.21k
      if (end != NULL) {
313
0
        *end = '\0';
314
0
        end++;
315
0
      }
316
317
1.21k
      if (php_check_specific_open_basedir(ptr, path) == 0) {
318
342
        efree(pathbuf);
319
342
        return 0;
320
342
      }
321
322
875
      ptr = end;
323
875
    }
324
875
    if (warn) {
325
875
      php_error_docref(NULL, E_WARNING, "open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s)", path, PG(open_basedir));
326
875
    }
327
875
    efree(pathbuf);
328
875
    errno = EPERM; /* we deny permission to open it */
329
875
    return -1;
330
1.21k
  }
331
332
  /* Nothing to check... */
333
64
  return 0;
334
1.28k
}
335
/* }}} */
336
337
/* {{{ php_fopen_and_set_opened_path */
338
static FILE *php_fopen_and_set_opened_path(const char *path, const char *mode, zend_string **opened_path)
339
64
{
340
64
  FILE *fp;
341
342
64
  if (php_check_open_basedir((char *)path)) {
343
0
    return NULL;
344
0
  }
345
64
  fp = VCWD_FOPEN(path, mode);
346
64
  if (fp && opened_path) {
347
    //TODO :avoid reallocation
348
0
    char *tmp = expand_filepath_with_mode(path, NULL, NULL, 0, CWD_EXPAND);
349
0
    if (tmp) {
350
0
      *opened_path = zend_string_init(tmp, strlen(tmp), 0);
351
0
      efree(tmp);
352
0
    }
353
0
  }
354
64
  return fp;
355
64
}
356
/* }}} */
357
358
/* {{{ php_fopen_primary_script */
359
PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle)
360
0
{
361
0
  char *path_info;
362
0
  zend_string *filename = NULL;
363
0
  zend_string *resolved_path = NULL;
364
0
  size_t length;
365
0
  bool orig_display_errors;
366
367
0
  memset(file_handle, 0, sizeof(zend_file_handle));
368
369
0
  path_info = SG(request_info).request_uri;
370
0
#ifdef HAVE_PWD_H
371
0
  if (PG(user_dir) && *PG(user_dir) && path_info && '/' == path_info[0] && '~' == path_info[1]) {
372
0
    char *s = strchr(path_info + 2, '/');
373
374
0
    if (s) {     /* if there is no path name after the file, do not bother */
375
0
      char user[32];      /* to try open the directory */
376
377
0
      length = s - (path_info + 2);
378
0
      if (length > sizeof(user) - 1) {
379
0
        length = sizeof(user) - 1;
380
0
      }
381
0
      memcpy(user, path_info + 2, length);
382
0
      user[length] = '\0';
383
384
0
      struct passwd *pw;
385
#if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX)
386
      struct passwd pwstruc;
387
      long pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
388
      char *pwbuf;
389
      int err;
390
391
      if (pwbuflen < 1) {
392
        pwbuflen = 1024;
393
      }
394
# if ZEND_DEBUG
395
      /* Test retry logic */
396
      pwbuflen = 1;
397
# endif
398
      pwbuf = emalloc(pwbuflen);
399
400
try_again:
401
      err = getpwnam_r(user, &pwstruc, pwbuf, pwbuflen, &pw);
402
      if (err) {
403
        if (err == ERANGE) {
404
          pwbuflen *= 2;
405
          pwbuf = erealloc(pwbuf, pwbuflen);
406
          goto try_again;
407
        }
408
        efree(pwbuf);
409
        return FAILURE;
410
      }
411
#else
412
0
      pw = getpwnam(user);
413
0
#endif
414
0
      if (pw && pw->pw_dir) {
415
0
        filename = zend_strpprintf(0, "%s%c%s%c%s", pw->pw_dir, PHP_DIR_SEPARATOR, PG(user_dir), PHP_DIR_SEPARATOR, s + 1); /* Safe */
416
0
      } else if (SG(request_info).path_translated) {
417
0
        filename = zend_string_init(SG(request_info).path_translated,
418
0
          strlen(SG(request_info).path_translated), 0);
419
0
      }
420
#if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX)
421
      efree(pwbuf);
422
#endif
423
0
    }
424
0
  } else
425
0
#endif
426
0
  if (PG(doc_root) && path_info && (length = strlen(PG(doc_root))) &&
427
0
    IS_ABSOLUTE_PATH(PG(doc_root), length)) {
428
0
    size_t path_len = strlen(path_info);
429
0
    filename = zend_string_alloc(length + path_len + 2, 0);
430
0
    memcpy(ZSTR_VAL(filename), PG(doc_root), length);
431
0
    if (!IS_SLASH(ZSTR_VAL(filename)[length - 1])) { /* length is never 0 */
432
0
      ZSTR_VAL(filename)[length++] = PHP_DIR_SEPARATOR;
433
0
    }
434
0
    if (IS_SLASH(path_info[0])) {
435
0
      length--;
436
0
    }
437
0
    strncpy(ZSTR_VAL(filename) + length, path_info, path_len + 1);
438
0
    ZSTR_LEN(filename) = length + path_len;
439
0
  } else if (SG(request_info).path_translated) {
440
0
    filename = zend_string_init(SG(request_info).path_translated,
441
0
      strlen(SG(request_info).path_translated), 0);
442
0
  }
443
444
445
0
  if (filename) {
446
0
    resolved_path = zend_resolve_path(filename);
447
0
  }
448
449
0
  if (!resolved_path) {
450
0
    if (filename) {
451
0
      zend_string_release(filename);
452
0
    }
453
    /* we have to free SG(request_info).path_translated here because
454
     * php_destroy_request_info assumes that it will get
455
     * freed when the include_names hash is emptied, but
456
     * we're not adding it in this case */
457
0
    if (SG(request_info).path_translated) {
458
0
      efree(SG(request_info).path_translated);
459
0
      SG(request_info).path_translated = NULL;
460
0
    }
461
0
    return FAILURE;
462
0
  }
463
0
  zend_string_release_ex(resolved_path, 0);
464
465
0
  orig_display_errors = PG(display_errors);
466
0
  PG(display_errors) = 0;
467
0
  zend_stream_init_filename_ex(file_handle, filename);
468
0
  file_handle->primary_script = 1;
469
0
  if (filename) {
470
0
    zend_string_delref(filename);
471
0
  }
472
0
  if (zend_stream_open(file_handle) == FAILURE) {
473
0
    PG(display_errors) = orig_display_errors;
474
0
    if (SG(request_info).path_translated) {
475
0
      efree(SG(request_info).path_translated);
476
0
      SG(request_info).path_translated = NULL;
477
0
    }
478
0
    return FAILURE;
479
0
  }
480
0
  PG(display_errors) = orig_display_errors;
481
482
0
  return SUCCESS;
483
0
}
484
/* }}} */
485
486
53.0k
static zend_string *tsrm_realpath_str(const char *path) {
487
53.0k
  char *realpath = tsrm_realpath(path, NULL);
488
53.0k
  if (!realpath) {
489
2.12k
    return NULL;
490
2.12k
  }
491
50.9k
  zend_string *realpath_str = zend_string_init(realpath, strlen(realpath), 0);
492
50.9k
  efree(realpath);
493
50.9k
  return realpath_str;
494
53.0k
}
495
496
/* {{{ php_resolve_path
497
 * Returns the realpath for given filename according to include path
498
 */
499
PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_length, const char *path)
500
57.6k
{
501
57.6k
  zend_string *resolved_path;
502
57.6k
  char trypath[MAXPATHLEN];
503
57.6k
  const char *ptr, *end, *p;
504
57.6k
  const char *actual_path;
505
57.6k
  php_stream_wrapper *wrapper;
506
57.6k
  zend_string *exec_filename;
507
508
57.6k
  if (!filename || zend_char_has_nul_byte(filename, filename_length)) {
509
6
    return NULL;
510
6
  }
511
512
  /* Don't resolve paths which contain protocol (except of file://) */
513
71.6k
  for (p = filename; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
514
57.6k
  if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) {
515
1.48k
    wrapper = php_stream_locate_url_wrapper(filename, &actual_path, STREAM_OPEN_FOR_INCLUDE);
516
1.48k
    if (wrapper == &php_plain_files_wrapper) {
517
8
      if ((resolved_path = tsrm_realpath_str(actual_path))) {
518
0
        return resolved_path;
519
0
      }
520
8
    }
521
1.48k
    return NULL;
522
1.48k
  }
523
524
56.1k
  if ((*filename == '.' &&
525
1
       (IS_SLASH(filename[1]) ||
526
1
        ((filename[1] == '.') && IS_SLASH(filename[2])))) ||
527
56.1k
      IS_ABSOLUTE_PATH(filename, filename_length) ||
528
#ifdef PHP_WIN32
529
    /* This should count as an absolute local path as well, however
530
       IS_ABSOLUTE_PATH doesn't care about this path form till now. It
531
       might be a big thing to extend, thus just a local handling for
532
       now. */
533
    (filename_length >=2 && IS_SLASH(filename[0]) && !IS_SLASH(filename[1])) ||
534
#endif
535
4.90k
      !path ||
536
51.2k
      !*path) {
537
51.2k
    return tsrm_realpath_str(filename);
538
51.2k
  }
539
540
4.90k
  ptr = path;
541
9.81k
  while (ptr && *ptr) {
542
    /* Check for stream wrapper */
543
4.92k
    int is_stream_wrapper = 0;
544
545
21.8k
    for (p = ptr; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
546
4.92k
    if ((*p == ':') && (p - ptr > 1) && (p[1] == '/') && (p[2] == '/')) {
547
      /* .:// or ..:// is not a stream wrapper */
548
4.01k
      if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) {
549
4.01k
        p += 3;
550
4.01k
        is_stream_wrapper = 1;
551
4.01k
      }
552
4.01k
    }
553
4.92k
    end = strchr(p, DEFAULT_DIR_SEPARATOR);
554
4.92k
    if (end) {
555
949
      if (filename_length > (MAXPATHLEN - 2) || (end-ptr) > MAXPATHLEN || (end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) {
556
9
        ptr = end + 1;
557
9
        continue;
558
9
      }
559
940
      memcpy(trypath, ptr, end-ptr);
560
940
      trypath[end-ptr] = '/';
561
940
      memcpy(trypath+(end-ptr)+1, filename, filename_length+1);
562
940
      ptr = end+1;
563
3.97k
    } else {
564
3.97k
      size_t len = strlen(ptr);
565
566
3.97k
      if (filename_length > (MAXPATHLEN - 2) || len > MAXPATHLEN || len + 1 + filename_length + 1 >= MAXPATHLEN) {
567
0
        break;
568
0
      }
569
3.97k
      memcpy(trypath, ptr, len);
570
3.97k
      trypath[len] = '/';
571
3.97k
      memcpy(trypath+len+1, filename, filename_length+1);
572
3.97k
      ptr = NULL;
573
3.97k
    }
574
4.91k
    actual_path = trypath;
575
4.91k
    if (is_stream_wrapper) {
576
4.01k
      wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE);
577
4.01k
      if (!wrapper) {
578
0
        continue;
579
4.01k
      } else if (wrapper != &php_plain_files_wrapper) {
580
4.01k
        if (wrapper->wops->url_stat) {
581
4.01k
          php_stream_statbuf ssb;
582
583
4.01k
          if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL)) {
584
0
            return zend_string_init(trypath, strlen(trypath), 0);
585
0
          }
586
4.01k
          if (EG(exception)) {
587
22
            return NULL;
588
22
          }
589
4.01k
        }
590
3.98k
        continue;
591
4.01k
      }
592
4.01k
    }
593
906
    if ((resolved_path = tsrm_realpath_str(actual_path))) {
594
0
      return resolved_path;
595
0
    }
596
906
  } /* end provided path */
597
598
  /* check in calling scripts' current working directory as a fallback case
599
   */
600
4.88k
  if (zend_is_executing() &&
601
922
      (exec_filename = zend_get_executed_filename_ex()) != NULL) {
602
922
    const char *exec_fname = ZSTR_VAL(exec_filename);
603
922
    size_t exec_fname_length = ZSTR_LEN(exec_filename);
604
605
10.1k
    while (exec_fname_length > 0) {
606
10.1k
      --exec_fname_length;
607
10.1k
      if (IS_SLASH(exec_fname[exec_fname_length])) {
608
922
        break;
609
922
      }
610
10.1k
    }
611
612
922
    if (exec_fname_length > 0 &&
613
922
      filename_length < (MAXPATHLEN - 2) &&
614
916
        exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) {
615
913
      memcpy(trypath, exec_fname, exec_fname_length + 1);
616
913
      memcpy(trypath+exec_fname_length + 1, filename, filename_length+1);
617
913
      actual_path = trypath;
618
619
      /* Check for stream wrapper */
620
913
      for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
621
913
      if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') && (p[2] == '/')) {
622
0
        wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE);
623
0
        if (!wrapper) {
624
0
          return NULL;
625
0
        } else if (wrapper != &php_plain_files_wrapper) {
626
0
          if (wrapper->wops->url_stat) {
627
0
            php_stream_statbuf ssb;
628
629
0
            if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL)) {
630
0
              return zend_string_init(trypath, strlen(trypath), 0);
631
0
            }
632
0
            if (EG(exception)) {
633
0
              return NULL;
634
0
            }
635
0
          }
636
0
          return NULL;
637
0
        }
638
0
      }
639
640
913
      return tsrm_realpath_str(actual_path);
641
913
    }
642
922
  }
643
644
3.97k
  return NULL;
645
4.88k
}
646
/* }}} */
647
648
/* {{{ php_fopen_with_path
649
 * Tries to open a file with a PATH-style list of directories.
650
 * If the filename starts with "." or "/", the path is ignored.
651
 */
652
PHPAPI FILE *php_fopen_with_path(const char *filename, const char *mode, const char *path, zend_string **opened_path)
653
32
{
654
32
  char *pathbuf, *ptr, *end;
655
32
  char trypath[MAXPATHLEN];
656
32
  FILE *fp;
657
32
  size_t filename_length;
658
32
  zend_string *exec_filename;
659
660
32
  if (opened_path) {
661
32
    *opened_path = NULL;
662
32
  }
663
664
32
  if (!filename) {
665
0
    return NULL;
666
0
  }
667
668
32
  filename_length = strlen(filename);
669
32
#ifndef PHP_WIN32
670
32
  (void) filename_length;
671
32
#endif
672
673
  /* Relative path open */
674
32
  if ((*filename == '.')
675
  /* Absolute path open */
676
32
   || IS_ABSOLUTE_PATH(filename, filename_length)
677
32
   || (!path || !*path)
678
32
  ) {
679
0
    return php_fopen_and_set_opened_path(filename, mode, opened_path);
680
0
  }
681
682
  /* check in provided path */
683
  /* append the calling scripts' current working directory
684
   * as a fallback case
685
   */
686
32
  if (zend_is_executing() &&
687
0
      (exec_filename = zend_get_executed_filename_ex()) != NULL) {
688
0
    const char *exec_fname = ZSTR_VAL(exec_filename);
689
0
    size_t exec_fname_length = ZSTR_LEN(exec_filename);
690
691
0
    while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
692
0
    if ((exec_fname && exec_fname[0] == '[') || exec_fname_length <= 0) {
693
      /* [no active file] or no path */
694
0
      pathbuf = estrdup(path);
695
0
    } else {
696
0
      size_t path_length = strlen(path);
697
698
0
      pathbuf = (char *) emalloc(exec_fname_length + path_length + 1 + 1);
699
0
      memcpy(pathbuf, path, path_length);
700
0
      pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
701
0
      memcpy(pathbuf + path_length + 1, exec_fname, exec_fname_length);
702
0
      pathbuf[path_length + exec_fname_length + 1] = '\0';
703
0
    }
704
32
  } else {
705
32
    pathbuf = estrdup(path);
706
32
  }
707
708
32
  ptr = pathbuf;
709
710
96
  while (ptr && *ptr) {
711
64
    end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
712
64
    if (end != NULL) {
713
32
      *end = '\0';
714
32
      end++;
715
32
    }
716
64
    if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
717
0
      php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
718
0
    }
719
64
    fp = php_fopen_and_set_opened_path(trypath, mode, opened_path);
720
64
    if (fp) {
721
0
      efree(pathbuf);
722
0
      return fp;
723
0
    }
724
64
    ptr = end;
725
64
  } /* end provided path */
726
727
32
  efree(pathbuf);
728
32
  return NULL;
729
32
}
730
/* }}} */
731
732
/* {{{ php_strip_url_passwd */
733
PHPAPI char *php_strip_url_passwd(char *url)
734
3.68k
{
735
3.68k
  char *p, *url_start;
736
737
3.68k
  if (url == NULL) {
738
0
    return "";
739
0
  }
740
741
3.68k
  p = url;
742
743
220k
  while (*p) {
744
218k
    if (*p == ':' && *(p + 1) == '/' && *(p + 2) == '/') {
745
      /* found protocol */
746
1.42k
      url_start = p = p + 3;
747
748
3.67k
      while (*p) {
749
2.27k
        if (*p == '@') {
750
23
          int i;
751
752
32
          for (i = 0; i < 3 && url_start < p; i++, url_start++) {
753
9
            *url_start = '.';
754
9
          }
755
243
          for (; *p; p++) {
756
220
            *url_start++ = *p;
757
220
          }
758
23
          *url_start=0;
759
23
          break;
760
23
        }
761
2.25k
        p++;
762
2.25k
      }
763
1.42k
      return url;
764
1.42k
    }
765
216k
    p++;
766
216k
  }
767
2.26k
  return url;
768
3.68k
}
769
/* }}} */
770
771
/* {{{ expand_filepath */
772
PHPAPI char *expand_filepath(const char *filepath, char *real_path)
773
2.69k
{
774
2.69k
  return expand_filepath_ex(filepath, real_path, NULL, 0);
775
2.69k
}
776
/* }}} */
777
778
/* {{{ expand_filepath_ex */
779
PHPAPI char *expand_filepath_ex(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len)
780
2.69k
{
781
2.69k
  return expand_filepath_with_mode(filepath, real_path, relative_to, relative_to_len, CWD_FILEPATH);
782
2.69k
}
783
/* }}} */
784
785
/* {{{ expand_filepath_use_realpath */
786
PHPAPI char *expand_filepath_with_mode(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len, int realpath_mode)
787
2.69k
{
788
2.69k
  cwd_state new_state;
789
2.69k
  char cwd[MAXPATHLEN];
790
2.69k
  size_t copy_len;
791
2.69k
  size_t path_len;
792
793
2.69k
  if (!filepath[0]) {
794
0
    return NULL;
795
0
  }
796
797
2.69k
  path_len = strlen(filepath);
798
799
2.69k
  if (IS_ABSOLUTE_PATH(filepath, path_len)) {
800
1.82k
    cwd[0] = '\0';
801
1.82k
  } else {
802
870
    const char *iam = SG(request_info).path_translated;
803
870
    const char *result;
804
870
    if (relative_to) {
805
0
      if (relative_to_len > MAXPATHLEN-1U) {
806
0
        return NULL;
807
0
      }
808
0
      result = relative_to;
809
0
      memcpy(cwd, relative_to, relative_to_len+1U);
810
870
    } else {
811
870
      result = VCWD_GETCWD(cwd, MAXPATHLEN);
812
870
    }
813
814
870
    if (!result && (iam != filepath)) {
815
0
      int fdtest = -1;
816
817
0
      fdtest = VCWD_OPEN(filepath, O_RDONLY);
818
0
      if (fdtest != -1) {
819
        /* return a relative file path if for any reason
820
         * we cannot getcwd() and the requested,
821
         * relatively referenced file is accessible */
822
0
        copy_len = path_len > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : path_len;
823
0
        if (real_path) {
824
0
          memcpy(real_path, filepath, copy_len);
825
0
          real_path[copy_len] = '\0';
826
0
        } else {
827
0
          real_path = estrndup(filepath, copy_len);
828
0
        }
829
0
        close(fdtest);
830
0
        return real_path;
831
0
      } else {
832
0
        cwd[0] = '\0';
833
0
      }
834
870
    } else if (!result) {
835
0
      cwd[0] = '\0';
836
0
    }
837
870
  }
838
839
2.69k
  new_state.cwd = estrdup(cwd);
840
2.69k
  new_state.cwd_length = strlen(cwd);
841
842
2.69k
  if (virtual_file_ex(&new_state, filepath, NULL, realpath_mode)) {
843
6
    efree(new_state.cwd);
844
6
    return NULL;
845
6
  }
846
847
2.69k
  if (real_path) {
848
2.69k
    copy_len = new_state.cwd_length > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : new_state.cwd_length;
849
2.69k
    memcpy(real_path, new_state.cwd, copy_len);
850
2.69k
    real_path[copy_len] = '\0';
851
2.69k
  } else {
852
0
    real_path = estrndup(new_state.cwd, new_state.cwd_length);
853
0
  }
854
2.69k
  efree(new_state.cwd);
855
856
2.69k
  return real_path;
857
2.69k
}
858
/* }}} */