Coverage Report

Created: 2026-09-14 06:25

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