mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			358 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
 | |
| 
 | |
| #include "feedback.h"
 | |
| 
 | |
| #ifdef HAVE_NETDB_H
 | |
| #include <netdb.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef _WIN32
 | |
| #include <ws2tcpip.h>
 | |
| #define addrinfo ADDRINFOA
 | |
| #endif
 | |
| 
 | |
| namespace feedback {
 | |
| 
 | |
| static const uint FOR_READING= 0;
 | |
| static const uint FOR_WRITING= 1;
 | |
| 
 | |
| /**
 | |
|   implementation of the Url class that sends the data via HTTP POST request.
 | |
| 
 | |
|   Both http:// and https:// protocols are supported.
 | |
| */
 | |
| class Url_http: public Url {
 | |
|   protected:
 | |
|   const LEX_STRING host, port, path;
 | |
|   LEX_STRING proxy_host, proxy_port;
 | |
|   my_socket fd;
 | |
|   bool ssl;
 | |
| 
 | |
|   bool use_proxy()
 | |
|   {
 | |
|     return proxy_host.length != 0;
 | |
|   }
 | |
| 
 | |
|   Url_http(LEX_STRING &url_arg, LEX_STRING &host_arg,
 | |
|           LEX_STRING &port_arg, LEX_STRING &path_arg, bool ssl_arg) :
 | |
|     Url(url_arg), host(host_arg), port(port_arg), path(path_arg),
 | |
|     fd(INVALID_SOCKET), ssl(ssl_arg)
 | |
|     {
 | |
|       proxy_host.length= 0;
 | |
|     }
 | |
|   ~Url_http() override
 | |
|   {
 | |
|     my_free(host.str);
 | |
|     my_free(port.str);
 | |
|     my_free(path.str);
 | |
|     set_proxy(0,0);
 | |
|   }
 | |
| 
 | |
|   public:
 | |
|   void abort() override;
 | |
|   int send(const char* data, size_t data_length) override;
 | |
|   int set_proxy(const char *proxy, size_t proxy_len) override
 | |
|   {
 | |
|     if (use_proxy())
 | |
|     {
 | |
|       my_free(proxy_host.str);
 | |
|       my_free(proxy_port.str);
 | |
|     }
 | |
| 
 | |
|     return parse_proxy_server(proxy, proxy_len, &proxy_host, &proxy_port);
 | |
|   }
 | |
| 
 | |
|   friend Url* http_create(const char *url, size_t url_length);
 | |
| };
 | |
| 
 | |
| /**
 | |
|   create a Url_http object out of the url, if possible.
 | |
| 
 | |
|   @note
 | |
|   Arbitrary limitations here.
 | |
| 
 | |
|   The url must be http[s]://hostname[:port]/path
 | |
|   No username:password@ or ?script=parameters are supported.
 | |
| 
 | |
|   But it's ok. This is not a generic purpose www browser - it only needs to be
 | |
|   good enough to POST the data to mariadb.org.
 | |
| */
 | |
| Url* http_create(const char *url, size_t url_length)
 | |
| {
 | |
|   const char *s;
 | |
|   LEX_STRING full_url= {const_cast<char*>(url), url_length};
 | |
|   LEX_STRING host, port, path;
 | |
|   bool ssl= false;
 | |
| 
 | |
|   if (is_prefix(url, "http://"))
 | |
|     s= url + 7;
 | |
| #ifdef HAVE_OPENSSL
 | |
|   else if (is_prefix(url, "https://"))
 | |
|   {
 | |
|     ssl= true;
 | |
|     s= url + 8;
 | |
|   }
 | |
| #endif
 | |
|   else
 | |
|     return NULL;
 | |
| 
 | |
|   for (url= s; *s && *s != ':' && *s != '/'; s++) /* no-op */;
 | |
|   host.str= const_cast<char*>(url);
 | |
|   host.length= s-url;
 | |
| 
 | |
|   if (*s == ':')
 | |
|   {
 | |
|     for (url= ++s; *s && *s >= '0' && *s <= '9'; s++) /* no-op */;
 | |
|     port.str= const_cast<char*>(url);
 | |
|     port.length= s-url;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (ssl)
 | |
|     {
 | |
|       port.str= const_cast<char*>("443");
 | |
|       port.length=3;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       port.str= const_cast<char*>("80");
 | |
|       port.length=2;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (*s == 0)
 | |
|   {
 | |
|     path.str= const_cast<char*>("/");
 | |
|     path.length= 1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     path.str= const_cast<char*>(s);
 | |
|     path.length= strlen(s);
 | |
|   }
 | |
|   if (!host.length || !port.length || path.str[0] != '/')
 | |
|     return NULL;
 | |
| 
 | |
|   host.str= my_strndup(PSI_INSTRUMENT_ME, host.str, host.length, MYF(MY_WME));
 | |
|   port.str= my_strndup(PSI_INSTRUMENT_ME, port.str, port.length, MYF(MY_WME));
 | |
|   path.str= my_strndup(PSI_INSTRUMENT_ME, path.str, path.length, MYF(MY_WME));
 | |
| 
 | |
|   if (!host.str || !port.str || !path.str)
 | |
|   {
 | |
|     my_free(host.str);
 | |
|     my_free(port.str);
 | |
|     my_free(path.str);
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   return new Url_http(full_url, host, port, path, ssl);
 | |
| }
 | |
| 
 | |
| void Url_http::abort()
 | |
| {
 | |
|   if (fd != INVALID_SOCKET)
 | |
|     closesocket(fd); // interrupt I/O waits
 | |
| }
 | |
| 
 | |
| /* do the vio_write and check that all data were sent ok */
 | |
| #define write_check(VIO, DATA, LEN)             \
 | |
|   (vio_write((VIO), (uchar*)(DATA), (LEN)) != (LEN))
 | |
| 
 | |
| int Url_http::send(const char* data, size_t data_length)
 | |
| {
 | |
|   char buf[1024];
 | |
|   size_t len= 0;
 | |
| 
 | |
|   addrinfo *addrs, *addr, filter;
 | |
|   bzero((void*) &filter, sizeof(filter));
 | |
|   filter.ai_family= AF_UNSPEC;
 | |
|   filter.ai_socktype= SOCK_STREAM;
 | |
|   filter.ai_protocol=6;
 | |
| 
 | |
|   int res= use_proxy() ?
 | |
|     getaddrinfo(proxy_host.str, proxy_port.str, &filter, &addrs) :
 | |
|     getaddrinfo(host.str, port.str, &filter, &addrs);
 | |
| 
 | |
|   if (res)
 | |
|   {
 | |
|     sql_print_error("feedback plugin: getaddrinfo() failed for url '%s': %s",
 | |
|                     full_url.str, gai_strerror(res));
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(fd == INVALID_SOCKET);
 | |
|   for (addr= addrs; addr != NULL; addr= addr->ai_next)
 | |
|   {
 | |
|     fd= socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
 | |
|     if (fd == INVALID_SOCKET)
 | |
|       continue;
 | |
| 
 | |
|     if (connect(fd, addr->ai_addr, (int) addr->ai_addrlen) == 0)
 | |
|       break;
 | |
| 
 | |
|     closesocket(fd);
 | |
|     fd= INVALID_SOCKET;
 | |
|   }
 | |
| 
 | |
|   freeaddrinfo(addrs);
 | |
| 
 | |
|   if (fd == INVALID_SOCKET)
 | |
|   {
 | |
|     sql_print_error("feedback plugin: could not connect for url '%s'",
 | |
|                     full_url.str);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   Vio *vio= vio_new(fd, VIO_TYPE_TCPIP, 0);
 | |
|   if (!vio)
 | |
|   {
 | |
|     sql_print_error("feedback plugin: vio_new failed for url '%s'",
 | |
|                     full_url.str);
 | |
|     closesocket(fd);
 | |
|     fd= INVALID_SOCKET;
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
| #ifdef HAVE_OPENSSL
 | |
|   struct st_VioSSLFd *UNINIT_VAR(ssl_fd);
 | |
|   if (ssl)
 | |
|   {
 | |
|     enum enum_ssl_init_error ssl_init_error= SSL_INITERR_NOERROR;
 | |
|     ulong ssl_error= 0;
 | |
|     if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0, &ssl_init_error, 0, 0)) ||
 | |
|         sslconnect(ssl_fd, vio, send_timeout, &ssl_error))
 | |
|     {
 | |
|       const char *err;
 | |
|       if (ssl_init_error != SSL_INITERR_NOERROR)
 | |
|         err= sslGetErrString(ssl_init_error);
 | |
|       else
 | |
|       {
 | |
|         ERR_error_string_n(ssl_error, buf, sizeof(buf));
 | |
|         buf[sizeof(buf)-1]= 0;
 | |
|         err= buf;
 | |
|       }
 | |
| 
 | |
|       sql_print_error("feedback plugin: ssl failed for url '%s' %s",
 | |
|                       full_url.str, err);
 | |
|       if (ssl_fd)
 | |
|         free_vio_ssl_acceptor_fd(ssl_fd);
 | |
|       closesocket(fd);
 | |
|       vio_delete(vio);
 | |
|       fd= INVALID_SOCKET;
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   static const LEX_STRING boundary= 
 | |
|   { C_STRING_WITH_LEN("----------------------------ba4f3696b39f") };
 | |
|   static const LEX_STRING header=
 | |
|   { C_STRING_WITH_LEN("\r\n"
 | |
|       "Content-Disposition: form-data; name=\"data\"; filename=\"-\"\r\n"
 | |
|       "Content-Type: application/octet-stream\r\n\r\n")
 | |
|   };
 | |
| 
 | |
|   len= my_snprintf(buf, sizeof(buf),
 | |
|                    use_proxy() ? "POST http://%s:%s/" : "POST ",
 | |
|                    host.str, port.str);
 | |
| 
 | |
|   len+= my_snprintf(buf+len, sizeof(buf)-len,
 | |
|                     "%s HTTP/1.0\r\n"
 | |
|                     "User-Agent: MariaDB User Feedback Plugin\r\n"
 | |
|                     "Host: %s:%s\r\n"
 | |
|                     "Accept: */*\r\n"
 | |
|                     "Content-Length: %u\r\n"
 | |
|                     "Content-Type: multipart/form-data; boundary=%s\r\n"
 | |
|                     "\r\n",
 | |
|                     path.str, host.str, port.str,
 | |
|                     (uint)(2*boundary.length + header.length + data_length + 4),
 | |
|                     boundary.str + 2);
 | |
| 
 | |
|   vio_timeout(vio, FOR_READING, send_timeout);
 | |
|   vio_timeout(vio, FOR_WRITING, send_timeout);
 | |
|   res = write_check(vio, buf, len)
 | |
|      || write_check(vio, boundary.str, boundary.length)
 | |
|      || write_check(vio, header.str, header.length)
 | |
|      || write_check(vio, data, data_length)
 | |
|      || write_check(vio, boundary.str, boundary.length)
 | |
|      || write_check(vio, "--\r\n", 4);
 | |
| 
 | |
|   if (res)
 | |
|     sql_print_error("feedback plugin: failed to send report to '%s'",
 | |
|                     full_url.str);
 | |
|   else
 | |
|   {
 | |
|     sql_print_information("feedback plugin: report to '%s' was sent",
 | |
|                           full_url.str);
 | |
| 
 | |
|     /*
 | |
|       if the data were send successfully, read the reply.
 | |
|       Extract the first string between <h1>...</h1> tags
 | |
|       and put it as a server reply into the error log.
 | |
|     */
 | |
|     len= 0;
 | |
|     for (;;)
 | |
|     {
 | |
|       size_t i= sizeof(buf) - len - 1;
 | |
|       if (i)
 | |
|         i= vio_read(vio, (uchar*)buf + len, i);
 | |
|       if ((int)i <= 0)
 | |
|         break;
 | |
|       len+= i;
 | |
|     }
 | |
|     if (len)
 | |
|     {
 | |
|       char *from;
 | |
| 
 | |
|       buf[len]= 0; // safety
 | |
| 
 | |
|       if ((from= strstr(buf, "<h1>")))
 | |
|       {
 | |
|         from+= 4;
 | |
|         char *to= strstr(from, "</h1>");
 | |
|         if (to)
 | |
|           *to= 0;
 | |
|         else
 | |
|           from= NULL;
 | |
|       }
 | |
|       if (from)
 | |
|         sql_print_information("feedback plugin: server replied '%s'", from);
 | |
|       else
 | |
|         sql_print_warning("feedback plugin: failed to parse server reply");
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       res= 1;
 | |
|       sql_print_error("feedback plugin: failed to read server reply");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   vio_delete(vio);
 | |
| 
 | |
| #ifdef HAVE_OPENSSL
 | |
|   if (ssl)
 | |
|   {
 | |
|     SSL_CTX_free(ssl_fd->ssl_context);
 | |
|     my_free(ssl_fd);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   fd= INVALID_SOCKET;
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| } // namespace feedback
 | |
| 
 | 
