1/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
15
16#include "feedback.h"
17
18#ifdef HAVE_NETDB_H
19#include <netdb.h>
20#endif
21
22#ifdef _WIN32
23#include <ws2tcpip.h>
24#define addrinfo ADDRINFOA
25#endif
26
27namespace feedback {
28
29static const uint FOR_READING= 0;
30static const uint FOR_WRITING= 1;
31
32/**
33 implementation of the Url class that sends the data via HTTP POST request.
34
35 Both http:// and https:// protocols are supported.
36*/
37class Url_http: public Url {
38 protected:
39 const LEX_STRING host, port, path;
40 bool ssl;
41 LEX_STRING proxy_host, proxy_port;
42
43 bool use_proxy()
44 {
45 return proxy_host.length != 0;
46 }
47
48 Url_http(LEX_STRING &url_arg, LEX_STRING &host_arg,
49 LEX_STRING &port_arg, LEX_STRING &path_arg, bool ssl_arg) :
50 Url(url_arg), host(host_arg), port(port_arg), path(path_arg), ssl(ssl_arg)
51 {
52 proxy_host.length= 0;
53 }
54 ~Url_http()
55 {
56 my_free(host.str);
57 my_free(port.str);
58 my_free(path.str);
59 set_proxy(0,0);
60 }
61
62 public:
63 int send(const char* data, size_t data_length);
64 int set_proxy(const char *proxy, size_t proxy_len)
65 {
66 if (use_proxy())
67 {
68 my_free(proxy_host.str);
69 my_free(proxy_port.str);
70 }
71
72 return parse_proxy_server(proxy, proxy_len, &proxy_host, &proxy_port);
73 }
74
75 friend Url* http_create(const char *url, size_t url_length);
76};
77
78/**
79 create a Url_http object out of the url, if possible.
80
81 @note
82 Arbitrary limitations here.
83
84 The url must be http[s]://hostname[:port]/path
85 No username:password@ or ?script=parameters are supported.
86
87 But it's ok. This is not a generic purpose www browser - it only needs to be
88 good enough to POST the data to mariadb.org.
89*/
90Url* http_create(const char *url, size_t url_length)
91{
92 const char *s;
93 LEX_STRING full_url= {const_cast<char*>(url), url_length};
94 LEX_STRING host, port, path;
95 bool ssl= false;
96
97 if (is_prefix(url, "http://"))
98 s= url + 7;
99#ifdef HAVE_OPENSSL
100 else if (is_prefix(url, "https://"))
101 {
102 ssl= true;
103 s= url + 8;
104 }
105#endif
106 else
107 return NULL;
108
109 for (url= s; *s && *s != ':' && *s != '/'; s++) /* no-op */;
110 host.str= const_cast<char*>(url);
111 host.length= s-url;
112
113 if (*s == ':')
114 {
115 for (url= ++s; *s && *s >= '0' && *s <= '9'; s++) /* no-op */;
116 port.str= const_cast<char*>(url);
117 port.length= s-url;
118 }
119 else
120 {
121 if (ssl)
122 {
123 port.str= const_cast<char*>("443");
124 port.length=3;
125 }
126 else
127 {
128 port.str= const_cast<char*>("80");
129 port.length=2;
130 }
131 }
132
133 if (*s == 0)
134 {
135 path.str= const_cast<char*>("/");
136 path.length= 1;
137 }
138 else
139 {
140 path.str= const_cast<char*>(s);
141 path.length= strlen(s);
142 }
143 if (!host.length || !port.length || path.str[0] != '/')
144 return NULL;
145
146 host.str= my_strndup(host.str, host.length, MYF(MY_WME));
147 port.str= my_strndup(port.str, port.length, MYF(MY_WME));
148 path.str= my_strndup(path.str, path.length, MYF(MY_WME));
149
150 if (!host.str || !port.str || !path.str)
151 {
152 my_free(host.str);
153 my_free(port.str);
154 my_free(path.str);
155 return NULL;
156 }
157
158 return new Url_http(full_url, host, port, path, ssl);
159}
160
161/* do the vio_write and check that all data were sent ok */
162#define write_check(VIO, DATA, LEN) \
163 (vio_write((VIO), (uchar*)(DATA), (LEN)) != (LEN))
164
165int Url_http::send(const char* data, size_t data_length)
166{
167 my_socket fd= INVALID_SOCKET;
168 char buf[1024];
169 size_t len= 0;
170
171 addrinfo *addrs, *addr, filter= {0, AF_UNSPEC, SOCK_STREAM, 6, 0, 0, 0, 0};
172 int res= use_proxy() ?
173 getaddrinfo(proxy_host.str, proxy_port.str, &filter, &addrs) :
174 getaddrinfo(host.str, port.str, &filter, &addrs);
175
176 if (res)
177 {
178 sql_print_error("feedback plugin: getaddrinfo() failed for url '%s': %s",
179 full_url.str, gai_strerror(res));
180 return 1;
181 }
182
183 for (addr= addrs; addr != NULL; addr= addr->ai_next)
184 {
185 fd= socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
186 if (fd == INVALID_SOCKET)
187 continue;
188
189 if (connect(fd, addr->ai_addr, (int) addr->ai_addrlen) == 0)
190 break;
191
192 closesocket(fd);
193 fd= INVALID_SOCKET;
194 }
195
196 freeaddrinfo(addrs);
197
198 if (fd == INVALID_SOCKET)
199 {
200 sql_print_error("feedback plugin: could not connect for url '%s'",
201 full_url.str);
202 return 1;
203 }
204
205 Vio *vio= vio_new(fd, VIO_TYPE_TCPIP, 0);
206 if (!vio)
207 {
208 sql_print_error("feedback plugin: vio_new failed for url '%s'",
209 full_url.str);
210 closesocket(fd);
211 return 1;
212 }
213
214#ifdef HAVE_OPENSSL
215 struct st_VioSSLFd *UNINIT_VAR(ssl_fd);
216 if (ssl)
217 {
218 enum enum_ssl_init_error ssl_init_error= SSL_INITERR_NOERROR;
219 ulong ssl_error= 0;
220 if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0, &ssl_init_error, 0, 0)) ||
221 sslconnect(ssl_fd, vio, send_timeout, &ssl_error))
222 {
223 const char *err;
224 if (ssl_init_error != SSL_INITERR_NOERROR)
225 err= sslGetErrString(ssl_init_error);
226 else
227 {
228 ERR_error_string_n(ssl_error, buf, sizeof(buf));
229 buf[sizeof(buf)-1]= 0;
230 err= buf;
231 }
232
233 sql_print_error("feedback plugin: ssl failed for url '%s' %s",
234 full_url.str, err);
235 if (ssl_fd)
236 free_vio_ssl_acceptor_fd(ssl_fd);
237 closesocket(fd);
238 vio_delete(vio);
239 return 1;
240 }
241 }
242#endif
243
244 static const LEX_STRING boundary=
245 { C_STRING_WITH_LEN("----------------------------ba4f3696b39f") };
246 static const LEX_STRING header=
247 { C_STRING_WITH_LEN("\r\n"
248 "Content-Disposition: form-data; name=\"data\"; filename=\"-\"\r\n"
249 "Content-Type: application/octet-stream\r\n\r\n")
250 };
251
252 len= my_snprintf(buf, sizeof(buf),
253 use_proxy() ? "POST http://%s:%s/" : "POST ",
254 host.str, port.str);
255
256 len+= my_snprintf(buf+len, sizeof(buf)-len,
257 "%s HTTP/1.0\r\n"
258 "User-Agent: MariaDB User Feedback Plugin\r\n"
259 "Host: %s:%s\r\n"
260 "Accept: */*\r\n"
261 "Content-Length: %u\r\n"
262 "Content-Type: multipart/form-data; boundary=%s\r\n"
263 "\r\n",
264 path.str, host.str, port.str,
265 (uint)(2*boundary.length + header.length + data_length + 4),
266 boundary.str + 2);
267
268 vio_timeout(vio, FOR_READING, send_timeout);
269 vio_timeout(vio, FOR_WRITING, send_timeout);
270 res = write_check(vio, buf, len)
271 || write_check(vio, boundary.str, boundary.length)
272 || write_check(vio, header.str, header.length)
273 || write_check(vio, data, data_length)
274 || write_check(vio, boundary.str, boundary.length)
275 || write_check(vio, "--\r\n", 4);
276
277 if (res)
278 sql_print_error("feedback plugin: failed to send report to '%s'",
279 full_url.str);
280 else
281 {
282 sql_print_information("feedback plugin: report to '%s' was sent",
283 full_url.str);
284
285 /*
286 if the data were send successfully, read the reply.
287 Extract the first string between <h1>...</h1> tags
288 and put it as a server reply into the error log.
289 */
290 len= 0;
291 for (;;)
292 {
293 size_t i= sizeof(buf) - len - 1;
294 if (i)
295 i= vio_read(vio, (uchar*)buf + len, i);
296 if ((int)i <= 0)
297 break;
298 len+= i;
299 }
300 if (len)
301 {
302 char *from;
303
304 buf[len]= 0; // safety
305
306 if ((from= strstr(buf, "<h1>")))
307 {
308 from+= 4;
309 char *to= strstr(from, "</h1>");
310 if (to)
311 *to= 0;
312 else
313 from= NULL;
314 }
315 if (from)
316 sql_print_information("feedback plugin: server replied '%s'", from);
317 else
318 sql_print_warning("feedback plugin: failed to parse server reply");
319 }
320 else
321 {
322 res= 1;
323 sql_print_error("feedback plugin: failed to read server reply");
324 }
325 }
326
327 vio_delete(vio);
328
329#ifdef HAVE_OPENSSL
330 if (ssl)
331 {
332 SSL_CTX_free(ssl_fd->ssl_context);
333 my_free(ssl_fd);
334 }
335#endif
336
337 return res;
338}
339
340} // namespace feedback
341
342