xref: /PHP-7.4/ext/mysqlnd/mysqlnd_loaddata.c (revision 96404eb8)
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   | Authors: Andrey Hristov <andrey@php.net>                             |
16   |          Ulf Wendel <uw@php.net>                                     |
17   |          Georg Richter <georg@php.net>                               |
18   +----------------------------------------------------------------------+
19 */
20 #include "php.h"
21 #include "mysqlnd.h"
22 #include "mysqlnd_wireprotocol.h"
23 #include "mysqlnd_priv.h"
24 #include "mysqlnd_debug.h"
25 
26 /* {{{ mysqlnd_local_infile_init */
27 static
mysqlnd_local_infile_init(void ** ptr,const char * const filename)28 int mysqlnd_local_infile_init(void ** ptr, const char * const filename)
29 {
30 	MYSQLND_INFILE_INFO	*info;
31 	php_stream_context	*context = NULL;
32 
33 	DBG_ENTER("mysqlnd_local_infile_init");
34 
35 	info = ((MYSQLND_INFILE_INFO *)mnd_ecalloc(1, sizeof(MYSQLND_INFILE_INFO)));
36 	if (!info) {
37 		DBG_RETURN(1);
38 	}
39 
40 	*ptr = info;
41 
42 	/* check open_basedir */
43 	if (PG(open_basedir)) {
44 		if (php_check_open_basedir_ex(filename, 0) == -1) {
45 			strcpy(info->error_msg, "open_basedir restriction in effect. Unable to open file");
46 			info->error_no = CR_UNKNOWN_ERROR;
47 			DBG_RETURN(1);
48 		}
49 	}
50 
51 	info->filename = filename;
52 	info->fd = php_stream_open_wrapper_ex((char *)filename, "r", 0, NULL, context);
53 
54 	if (info->fd == NULL) {
55 		snprintf((char *)info->error_msg, sizeof(info->error_msg), "Can't find file '%-.64s'.", filename);
56 		info->error_no = MYSQLND_EE_FILENOTFOUND;
57 		DBG_RETURN(1);
58 	}
59 
60 	DBG_RETURN(0);
61 }
62 /* }}} */
63 
64 
65 /* {{{ mysqlnd_local_infile_read */
66 static
mysqlnd_local_infile_read(void * ptr,zend_uchar * buf,unsigned int buf_len)67 int mysqlnd_local_infile_read(void * ptr, zend_uchar * buf, unsigned int buf_len)
68 {
69 	MYSQLND_INFILE_INFO	*info = (MYSQLND_INFILE_INFO *)ptr;
70 	int count;
71 
72 	DBG_ENTER("mysqlnd_local_infile_read");
73 
74 	count = (int) php_stream_read(info->fd, (char *) buf, buf_len);
75 
76 	if (count < 0) {
77 		strcpy(info->error_msg, "Error reading file");
78 		info->error_no = CR_UNKNOWN_ERROR;
79 	}
80 
81 	DBG_RETURN(count);
82 }
83 /* }}} */
84 
85 
86 /* {{{ mysqlnd_local_infile_error */
87 static
mysqlnd_local_infile_error(void * ptr,char * error_buf,unsigned int error_buf_len)88 int	mysqlnd_local_infile_error(void * ptr, char *error_buf, unsigned int error_buf_len)
89 {
90 	MYSQLND_INFILE_INFO	*info = (MYSQLND_INFILE_INFO *)ptr;
91 
92 	DBG_ENTER("mysqlnd_local_infile_error");
93 
94 	if (info) {
95 		strlcpy(error_buf, info->error_msg, error_buf_len);
96 		DBG_INF_FMT("have info, %d", info->error_no);
97 		DBG_RETURN(info->error_no);
98 	}
99 
100 	strlcpy(error_buf, "Unknown error", error_buf_len);
101 	DBG_INF_FMT("no info, %d", CR_UNKNOWN_ERROR);
102 	DBG_RETURN(CR_UNKNOWN_ERROR);
103 }
104 /* }}} */
105 
106 
107 /* {{{ mysqlnd_local_infile_end */
108 static
mysqlnd_local_infile_end(void * ptr)109 void mysqlnd_local_infile_end(void * ptr)
110 {
111 	MYSQLND_INFILE_INFO	*info = (MYSQLND_INFILE_INFO *)ptr;
112 
113 	if (info) {
114 		/* php_stream_close segfaults on NULL */
115 		if (info->fd) {
116 			php_stream_close(info->fd);
117 			info->fd = NULL;
118 		}
119 		mnd_efree(info);
120 	}
121 }
122 /* }}} */
123 
124 
125 /* {{{ mysqlnd_local_infile_default */
126 PHPAPI void
mysqlnd_local_infile_default(MYSQLND_CONN_DATA * conn)127 mysqlnd_local_infile_default(MYSQLND_CONN_DATA * conn)
128 {
129 	conn->infile.local_infile_init = mysqlnd_local_infile_init;
130 	conn->infile.local_infile_read = mysqlnd_local_infile_read;
131 	conn->infile.local_infile_error = mysqlnd_local_infile_error;
132 	conn->infile.local_infile_end = mysqlnd_local_infile_end;
133 }
134 /* }}} */
135 
136 
137 static const char *lost_conn = "Lost connection to MySQL server during LOAD DATA of a local file";
138 
139 
140 /* {{{ mysqlnd_handle_local_infile */
141 enum_func_status
mysqlnd_handle_local_infile(MYSQLND_CONN_DATA * conn,const char * const filename,zend_bool * is_warning)142 mysqlnd_handle_local_infile(MYSQLND_CONN_DATA * conn, const char * const filename, zend_bool * is_warning)
143 {
144 	zend_uchar			*buf = NULL;
145 	zend_uchar			empty_packet[MYSQLND_HEADER_SIZE];
146 	enum_func_status	result = FAIL;
147 	unsigned int		buflen = 4096;
148 	void				*info = NULL;
149 	int					bufsize;
150 	size_t				ret;
151 	MYSQLND_INFILE		infile;
152 	MYSQLND_PFC			* net = conn->protocol_frame_codec;
153 	MYSQLND_VIO			* vio = conn->vio;
154 
155 	DBG_ENTER("mysqlnd_handle_local_infile");
156 
157 	if (!(conn->options->flags & CLIENT_LOCAL_FILES)) {
158 		php_error_docref(NULL, E_WARNING, "LOAD DATA LOCAL INFILE forbidden");
159 		SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE,
160 						"LOAD DATA LOCAL INFILE is forbidden, check mysqli.allow_local_infile");
161 		/* write empty packet to server */
162 		ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info);
163 		*is_warning = TRUE;
164 		goto infile_error;
165 	}
166 
167 	infile = conn->infile;
168 	/* allocate buffer for reading data */
169 	buf = (zend_uchar *) mnd_ecalloc(1, buflen);
170 
171 	*is_warning = FALSE;
172 
173 	/* init handler: allocate read buffer and open file */
174 	if (infile.local_infile_init(&info, (char *)filename)) {
175 		char tmp_buf[sizeof(conn->error_info->error)];
176 		int tmp_error_no;
177 		*is_warning = TRUE;
178 		/* error occurred */
179 		tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf));
180 		SET_CLIENT_ERROR(conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf);
181 		/* write empty packet to server */
182 		ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info);
183 		goto infile_error;
184 	}
185 
186 	/* read data */
187 	while ((bufsize = infile.local_infile_read (info, buf + MYSQLND_HEADER_SIZE, buflen - MYSQLND_HEADER_SIZE)) > 0) {
188 		if ((ret = net->data->m.send(net, vio, buf, bufsize, conn->stats, conn->error_info)) == 0) {
189 			DBG_ERR_FMT("Error during read : %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
190 			SET_CLIENT_ERROR(conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
191 			goto infile_error;
192 		}
193 	}
194 
195 	/* send empty packet for eof */
196 	if ((ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info)) == 0) {
197 		SET_CLIENT_ERROR(conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
198 		goto infile_error;
199 	}
200 
201 	/* error during read occurred */
202 	if (bufsize < 0) {
203 		char tmp_buf[sizeof(conn->error_info->error)];
204 		int tmp_error_no;
205 		*is_warning = TRUE;
206 		DBG_ERR_FMT("Bufsize < 0, warning,  %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
207 		tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf));
208 		SET_CLIENT_ERROR(conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf);
209 		goto infile_error;
210 	}
211 
212 	result = PASS;
213 
214 infile_error:
215 	/* get response from server and update upsert values */
216 	if (FAIL == conn->payload_decoder_factory->m.send_command_handle_response(
217 											conn->payload_decoder_factory,
218 											PROT_OK_PACKET, FALSE, COM_QUERY, FALSE,
219 											conn->error_info,
220 											conn->upsert_status,
221 											&conn->last_message)) {
222 		result = FAIL;
223 	}
224 
225 	(*conn->infile.local_infile_end)(info);
226 	if (buf) {
227 		mnd_efree(buf);
228 	}
229 	DBG_INF_FMT("%s", result == PASS? "PASS":"FAIL");
230 	DBG_RETURN(result);
231 }
232 /* }}} */
233