xref: /PHP-7.4/main/php_open_temporary_file.c (revision 5d31ee30)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Zeev Suraski <zeev@php.net>                                  |
16    +----------------------------------------------------------------------+
17  */
18 
19 #include "php.h"
20 #include "php_open_temporary_file.h"
21 
22 #include <errno.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 
27 #ifdef PHP_WIN32
28 #define O_RDONLY _O_RDONLY
29 #include "win32/param.h"
30 #include "win32/winutil.h"
31 #else
32 #include <sys/param.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <netdb.h>
36 #if HAVE_ARPA_INET_H
37 #include <arpa/inet.h>
38 #endif
39 #endif
40 #ifdef HAVE_SYS_TIME_H
41 #include <sys/time.h>
42 #endif
43 
44 #ifdef HAVE_SYS_FILE_H
45 #include <sys/file.h>
46 #endif
47 
48 #if !defined(P_tmpdir)
49 #define P_tmpdir ""
50 #endif
51 
52 /* {{{ php_do_open_temporary_file */
53 
54 /* Loosely based on a tempnam() implementation by UCLA */
55 
56 /*
57  * Copyright (c) 1988, 1993
58  *      The Regents of the University of California.  All rights reserved.
59  *
60  * Redistribution and use in source and binary forms, with or without
61  * modification, are permitted provided that the following conditions
62  * are met:
63  * 1. Redistributions of source code must retain the above copyright
64  *    notice, this list of conditions and the following disclaimer.
65  * 2. Redistributions in binary form must reproduce the above copyright
66  *    notice, this list of conditions and the following disclaimer in the
67  *    documentation and/or other materials provided with the distribution.
68  * 3. All advertising materials mentioning features or use of this software
69  *    must display the following acknowledgement:
70  *      This product includes software developed by the University of
71  *      California, Berkeley and its contributors.
72  * 4. Neither the name of the University nor the names of its contributors
73  *    may be used to endorse or promote products derived from this software
74  *    without specific prior written permission.
75  *
76  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
77  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
78  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
79  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
80  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
81  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
82  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
83  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
84  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
85  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
86  * SUCH DAMAGE.
87  */
88 
php_do_open_temporary_file(const char * path,const char * pfx,zend_string ** opened_path_p)89 static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p)
90 {
91 #ifdef PHP_WIN32
92 	char *opened_path = NULL;
93 	size_t opened_path_len;
94 	wchar_t *cwdw, *pfxw, pathw[MAXPATHLEN];
95 #else
96 	char opened_path[MAXPATHLEN];
97 	char *trailing_slash;
98 #endif
99 	char cwd[MAXPATHLEN];
100 	cwd_state new_state;
101 	int fd = -1;
102 #ifndef HAVE_MKSTEMP
103 	int open_flags = O_CREAT | O_TRUNC | O_RDWR
104 #ifdef PHP_WIN32
105 		| _O_BINARY
106 #endif
107 		;
108 #endif
109 
110 	if (!path || !path[0]) {
111 		return -1;
112 	}
113 
114 #ifdef PHP_WIN32
115 	if (!php_win32_check_trailing_space(pfx, strlen(pfx))) {
116 		SetLastError(ERROR_INVALID_NAME);
117 		return -1;
118 	}
119 #endif
120 
121 	if (!VCWD_GETCWD(cwd, MAXPATHLEN)) {
122 		cwd[0] = '\0';
123 	}
124 
125 	new_state.cwd = estrdup(cwd);
126 	new_state.cwd_length = strlen(cwd);
127 
128 	if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
129 		efree(new_state.cwd);
130 		return -1;
131 	}
132 
133 #ifndef PHP_WIN32
134 	if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) {
135 		trailing_slash = "";
136 	} else {
137 		trailing_slash = "/";
138 	}
139 
140 	if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, pfx) >= MAXPATHLEN) {
141 		efree(new_state.cwd);
142 		return -1;
143 	}
144 #endif
145 
146 #ifdef PHP_WIN32
147 	cwdw = php_win32_ioutil_any_to_w(new_state.cwd);
148 	pfxw = php_win32_ioutil_any_to_w(pfx);
149 	if (!cwdw || !pfxw) {
150 		free(cwdw);
151 		free(pfxw);
152 		efree(new_state.cwd);
153 		return -1;
154 	}
155 
156 	if (GetTempFileNameW(cwdw, pfxw, 0, pathw)) {
157 		opened_path = php_win32_ioutil_conv_w_to_any(pathw, PHP_WIN32_CP_IGNORE_LEN, &opened_path_len);
158 		if (!opened_path || opened_path_len >= MAXPATHLEN) {
159 			free(cwdw);
160 			free(pfxw);
161 			efree(new_state.cwd);
162 			return -1;
163 		}
164 		assert(strlen(opened_path) == opened_path_len);
165 
166 		/* Some versions of windows set the temp file to be read-only,
167 		 * which means that opening it will fail... */
168 		if (VCWD_CHMOD(opened_path, 0600)) {
169 			free(cwdw);
170 			free(pfxw);
171 			efree(new_state.cwd);
172 			free(opened_path);
173 			return -1;
174 		}
175 		fd = VCWD_OPEN_MODE(opened_path, open_flags, 0600);
176 	}
177 
178 	free(cwdw);
179 	free(pfxw);
180 #elif defined(HAVE_MKSTEMP)
181 	fd = mkstemp(opened_path);
182 #else
183 	if (mktemp(opened_path)) {
184 		fd = VCWD_OPEN(opened_path, open_flags);
185 	}
186 #endif
187 
188 #ifdef PHP_WIN32
189 	if (fd != -1 && opened_path_p) {
190 		*opened_path_p = zend_string_init(opened_path, opened_path_len, 0);
191 	}
192 	free(opened_path);
193 #else
194 	if (fd != -1 && opened_path_p) {
195 		*opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0);
196 	}
197 #endif
198 	efree(new_state.cwd);
199 	return fd;
200 }
201 /* }}} */
202 
203 /*
204  *  Determine where to place temporary files.
205  */
php_get_temporary_directory(void)206 PHPAPI const char* php_get_temporary_directory(void)
207 {
208 	/* Did we determine the temporary directory already? */
209 	if (PG(php_sys_temp_dir)) {
210 		return PG(php_sys_temp_dir);
211 	}
212 
213 	/* Is there a temporary directory "sys_temp_dir" in .ini defined? */
214 	{
215 		char *sys_temp_dir = PG(sys_temp_dir);
216 		if (sys_temp_dir) {
217 			size_t len = strlen(sys_temp_dir);
218 			if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
219 				PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
220 				return PG(php_sys_temp_dir);
221 			} else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
222 				PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
223 				return PG(php_sys_temp_dir);
224 			}
225 		}
226 	}
227 
228 #ifdef PHP_WIN32
229 	/* We can't count on the environment variables TEMP or TMP,
230 	 * and so must make the Win32 API call to get the default
231 	 * directory for temporary files.  Note this call checks
232 	 * the environment values TMP and TEMP (in order) first.
233 	 */
234 	{
235 		wchar_t sTemp[MAXPATHLEN];
236 		char *tmp;
237 		size_t len = GetTempPathW(MAXPATHLEN, sTemp);
238 
239 		if (!len) {
240 			return NULL;
241 		}
242 
243 		if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
244 			return NULL;
245 		}
246 
247 		PG(php_sys_temp_dir) = estrndup(tmp, len - 1);
248 
249 		free(tmp);
250 		return PG(php_sys_temp_dir);
251 	}
252 #else
253 	/* On Unix use the (usual) TMPDIR environment variable. */
254 	{
255 		char* s = getenv("TMPDIR");
256 		if (s && *s) {
257 			size_t len = strlen(s);
258 
259 			if (s[len - 1] == DEFAULT_SLASH) {
260 				PG(php_sys_temp_dir) = estrndup(s, len - 1);
261 			} else {
262 				PG(php_sys_temp_dir) = estrndup(s, len);
263 			}
264 
265 			return PG(php_sys_temp_dir);
266 		}
267 	}
268 #ifdef P_tmpdir
269 	/* Use the standard default temporary directory. */
270 	if (P_tmpdir) {
271 		PG(php_sys_temp_dir) = estrdup(P_tmpdir);
272 		return PG(php_sys_temp_dir);
273 	}
274 #endif
275 	/* Shouldn't ever(!) end up here ... last ditch default. */
276 	PG(php_sys_temp_dir) = estrdup("/tmp");
277 	return PG(php_sys_temp_dir);
278 #endif
279 }
280 
281 /* {{{ php_open_temporary_file
282  *
283  * Unlike tempnam(), the supplied dir argument takes precedence
284  * over the TMPDIR environment variable
285  * This function should do its best to return a file pointer to a newly created
286  * unique file, on every platform.
287  */
php_open_temporary_fd_ex(const char * dir,const char * pfx,zend_string ** opened_path_p,uint32_t flags)288 PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags)
289 {
290 	int fd;
291 	const char *temp_dir;
292 
293 	if (!pfx) {
294 		pfx = "tmp.";
295 	}
296 	if (opened_path_p) {
297 		*opened_path_p = NULL;
298 	}
299 
300 	if (!dir || *dir == '\0') {
301 def_tmp:
302 		temp_dir = php_get_temporary_directory();
303 
304 		if (temp_dir &&
305 		    *temp_dir != '\0' &&
306 		    (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) {
307 			return php_do_open_temporary_file(temp_dir, pfx, opened_path_p);
308 		} else {
309 			return -1;
310 		}
311 	}
312 
313 	if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) {
314 		return -1;
315 	}
316 
317 	/* Try the directory given as parameter. */
318 	fd = php_do_open_temporary_file(dir, pfx, opened_path_p);
319 	if (fd == -1) {
320 		/* Use default temporary directory. */
321 		if (!(flags & PHP_TMP_FILE_SILENT)) {
322 			php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory");
323 		}
324 		goto def_tmp;
325 	}
326 	return fd;
327 }
328 
php_open_temporary_fd(const char * dir,const char * pfx,zend_string ** opened_path_p)329 PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p)
330 {
331 	return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT);
332 }
333 
php_open_temporary_file(const char * dir,const char * pfx,zend_string ** opened_path_p)334 PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p)
335 {
336 	FILE *fp;
337 	int fd = php_open_temporary_fd(dir, pfx, opened_path_p);
338 
339 	if (fd == -1) {
340 		return NULL;
341 	}
342 
343 	fp = fdopen(fd, "r+b");
344 	if (fp == NULL) {
345 		close(fd);
346 	}
347 
348 	return fp;
349 }
350 /* }}} */
351