1 /* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "hash.h"
6 #include "str.h"
7 #include "strescape.h"
8 #include "ioloop.h"
9 #include "istream.h"
10 #include "http-url.h"
11 #include "http-client.h"
12 #include "fts-solr-plugin.h"
13 #include "solr-connection.h"
14 
15 #include <expat.h>
16 
17 struct solr_lookup_context {
18 	pool_t result_pool;
19 	struct istream *payload;
20 	struct io *io;
21 
22 	int request_status;
23 
24 	struct solr_response_parser *parser;
25 	struct solr_result **results;
26 };
27 
28 struct solr_connection_post {
29 	struct solr_connection *conn;
30 
31 	struct http_client_request *http_req;
32 	int request_status;
33 
34 	bool failed:1;
35 };
36 
37 struct solr_connection {
38 	struct event *event;
39 	char *http_host;
40 	in_port_t http_port;
41 	char *http_base_url;
42 	char *http_failure;
43 	char *http_user;
44 	char *http_password;
45 
46 	bool debug:1;
47 	bool posting:1;
48 	bool http_ssl:1;
49 };
50 
51 /* Regardless of the specified URL, make sure path ends in '/' */
solr_connection_create_http_base_url(struct http_url * http_url)52 static char *solr_connection_create_http_base_url(struct http_url *http_url)
53 {
54 	if (http_url->path == NULL)
55 		return i_strconcat("/", http_url->enc_query, NULL);
56 	size_t len = strlen(http_url->path);
57 	if (len > 0 && http_url->path[len-1] != '/')
58 		return i_strconcat(http_url->path, "/",
59 				   http_url->enc_query, NULL);
60 	/* http_url->path is NULL on empty path, so this is impossible. */
61 	i_assert(len != 0);
62 	return i_strconcat(http_url->path, http_url->enc_query, NULL);
63 }
64 
solr_connection_init(const struct fts_solr_settings * solr_set,const struct ssl_iostream_settings * ssl_client_set,struct event * event_parent,struct solr_connection ** conn_r,const char ** error_r)65 int solr_connection_init(const struct fts_solr_settings *solr_set,
66 			 const struct ssl_iostream_settings *ssl_client_set,
67 			 struct event *event_parent,
68 			 struct solr_connection **conn_r, const char **error_r)
69 {
70 	struct http_client_settings http_set;
71 	struct solr_connection *conn;
72 	struct http_url *http_url;
73 	const char *error;
74 
75 	if (http_url_parse(solr_set->url, NULL, HTTP_URL_ALLOW_USERINFO_PART,
76 			   pool_datastack_create(), &http_url, &error) < 0) {
77 		*error_r = t_strdup_printf(
78 			"fts_solr: Failed to parse HTTP url: %s", error);
79 		return -1;
80 	}
81 
82 	conn = i_new(struct solr_connection, 1);
83 	conn->event = event_create(event_parent);
84 	conn->http_host = i_strdup(http_url->host.name);
85 	conn->http_port = http_url->port;
86 	conn->http_base_url = solr_connection_create_http_base_url(http_url);
87 	conn->http_ssl = http_url->have_ssl;
88 	if (http_url->user != NULL) {
89 		conn->http_user = i_strdup(http_url->user);
90 		/* allow empty password */
91 		conn->http_password = i_strdup(http_url->password != NULL ?
92 					       http_url->password : "");
93 	}
94 
95 	conn->debug = solr_set->debug;
96 
97 	if (solr_http_client == NULL) {
98 		i_zero(&http_set);
99 		http_set.max_idle_time_msecs = 5*1000;
100 		http_set.max_parallel_connections = 1;
101 		http_set.max_pipelined_requests = 1;
102 		http_set.max_redirects = 1;
103 		http_set.max_attempts = 3;
104 		http_set.connect_timeout_msecs = 5*1000;
105 		http_set.request_timeout_msecs = 60*1000;
106 		http_set.ssl = ssl_client_set;
107 		http_set.debug = solr_set->debug;
108 		http_set.rawlog_dir = solr_set->rawlog_dir;
109 		http_set.event_parent = conn->event;
110 
111 		/* FIXME: We should initialize a shared client instead. However,
112 		          this is currently not possible due to an obscure bug
113 		          in the blocking HTTP payload API, which causes
114 		          conflicts with other HTTP applications like FTS Tika.
115 		          Using a private client will provide a quick fix for
116 		          now. */
117 		solr_http_client = http_client_init_private(&http_set);
118 	}
119 
120 	*conn_r = conn;
121 	return 0;
122 }
123 
solr_connection_deinit(struct solr_connection ** _conn)124 void solr_connection_deinit(struct solr_connection **_conn)
125 {
126 	struct solr_connection *conn = *_conn;
127 
128 	*_conn = NULL;
129 	event_unref(&conn->event);
130 	i_free(conn->http_host);
131 	i_free(conn->http_base_url);
132 	i_free(conn->http_user);
133 	i_free(conn->http_password);
134 	i_free(conn);
135 }
136 
solr_connection_payload_input(struct solr_lookup_context * lctx)137 static void solr_connection_payload_input(struct solr_lookup_context *lctx)
138 {
139 	int ret;
140 
141 	/* read payload */
142 	ret = solr_response_parse(lctx->parser, &lctx->results);
143 
144 	if (ret == 0) {
145 		/* we will be called again for more data */
146 	} else {
147 		if (lctx->payload->stream_errno != 0) {
148 			i_assert(ret < 0);
149 			i_error("fts_solr: "
150 				"failed to read payload from HTTP server: %s",
151 				i_stream_get_error(lctx->payload));
152 		}
153 		if (ret < 0)
154 			lctx->request_status = -1;
155 		solr_response_parser_deinit(&lctx->parser);
156 		io_remove(&lctx->io);
157 	}
158 }
159 
160 static void
solr_connection_select_response(const struct http_response * response,struct solr_lookup_context * lctx)161 solr_connection_select_response(const struct http_response *response,
162 				struct solr_lookup_context *lctx)
163 {
164 	if (response->status / 100 != 2) {
165 		i_error("fts_solr: Lookup failed: %s",
166 			http_response_get_message(response));
167 		lctx->request_status = -1;
168 		return;
169 	}
170 
171 	if (response->payload == NULL) {
172 		i_error("fts_solr: Lookup failed: Empty response payload");
173 		lctx->request_status = -1;
174 		return;
175 	}
176 
177 	lctx->parser = solr_response_parser_init(lctx->result_pool,
178 						 response->payload);
179 	lctx->payload = response->payload;
180 	lctx->io = io_add_istream(response->payload,
181 				  solr_connection_payload_input, lctx);
182 	solr_connection_payload_input(lctx);
183 }
184 
solr_connection_select(struct solr_connection * conn,const char * query,pool_t pool,struct solr_result *** box_results_r)185 int solr_connection_select(struct solr_connection *conn, const char *query,
186 			   pool_t pool, struct solr_result ***box_results_r)
187 {
188 	struct solr_lookup_context lctx;
189 	struct http_client_request *http_req;
190 	const char *url;
191 
192 	i_zero(&lctx);
193 	lctx.result_pool = pool;
194 
195 	i_free_and_null(conn->http_failure);
196 	url = t_strconcat(conn->http_base_url, "select?", query, NULL);
197 
198 	http_req = http_client_request(solr_http_client, "GET",
199 				       conn->http_host, url,
200 				       solr_connection_select_response,
201 				       &lctx);
202 	if (conn->http_user != NULL) {
203 		http_client_request_set_auth_simple(
204 			http_req, conn->http_user, conn->http_password);
205 	}
206 	http_client_request_set_port(http_req, conn->http_port);
207 	http_client_request_set_ssl(http_req, conn->http_ssl);
208 	http_client_request_submit(http_req);
209 
210 	lctx.request_status = 0;
211 	http_client_wait(solr_http_client);
212 
213 	if (lctx.request_status < 0)
214 		return -1;
215 
216 	*box_results_r = lctx.results;
217 	return 0;
218 }
219 
220 static void
solr_connection_update_response(const struct http_response * response,struct solr_connection_post * post)221 solr_connection_update_response(const struct http_response *response,
222 				struct solr_connection_post *post)
223 {
224 	if (response->status / 100 != 2) {
225 		i_error("fts_solr: Indexing failed: %s",
226 			http_response_get_message(response));
227 		post->request_status = -1;
228 	}
229 }
230 
231 static struct http_client_request *
solr_connection_post_request(struct solr_connection_post * post)232 solr_connection_post_request(struct solr_connection_post *post)
233 {
234 	struct solr_connection *conn = post->conn;
235 	struct http_client_request *http_req;
236 	const char *url;
237 
238 	url = t_strconcat(conn->http_base_url, "update", NULL);
239 
240 	http_req = http_client_request(solr_http_client, "POST",
241 				       conn->http_host, url,
242 				       solr_connection_update_response, post);
243 	if (conn->http_user != NULL) {
244 		http_client_request_set_auth_simple(
245 			http_req, conn->http_user, conn->http_password);
246 	}
247 	http_client_request_set_port(http_req, conn->http_port);
248 	http_client_request_set_ssl(http_req, conn->http_ssl);
249 	http_client_request_add_header(http_req, "Content-Type", "text/xml");
250 	return http_req;
251 }
252 
253 struct solr_connection_post *
solr_connection_post_begin(struct solr_connection * conn)254 solr_connection_post_begin(struct solr_connection *conn)
255 {
256 	struct solr_connection_post *post;
257 
258 	i_assert(!conn->posting);
259 	conn->posting = TRUE;
260 
261 	post = i_new(struct solr_connection_post, 1);
262 	post->conn = conn;
263 	post->http_req = solr_connection_post_request(post);
264 	return post;
265 }
266 
solr_connection_post_more(struct solr_connection_post * post,const unsigned char * data,size_t size)267 void solr_connection_post_more(struct solr_connection_post *post,
268 			       const unsigned char *data, size_t size)
269 {
270 	i_assert(post->conn->posting);
271 
272 	if (post->failed)
273 		return;
274 
275 	if (post->request_status == 0) {
276 		(void)http_client_request_send_payload(
277 			&post->http_req, data, size);
278 	}
279 	if (post->request_status < 0)
280 		post->failed = TRUE;
281 }
282 
solr_connection_post_end(struct solr_connection_post ** _post)283 int solr_connection_post_end(struct solr_connection_post **_post)
284 {
285 	struct solr_connection_post *post = *_post;
286 	struct solr_connection *conn = post->conn;
287 	int ret = post->failed ? -1 : 0;
288 
289 	i_assert(conn->posting);
290 
291 	*_post = NULL;
292 
293 	if (!post->failed) {
294 		if (http_client_request_finish_payload(&post->http_req) < 0 ||
295 		    post->request_status < 0) {
296 			ret = -1;
297 		}
298 	} else {
299 		http_client_request_abort(&post->http_req);
300 	}
301 	i_free(post);
302 
303 	conn->posting = FALSE;
304 	return ret;
305 }
306 
solr_connection_post(struct solr_connection * conn,const char * cmd)307 int solr_connection_post(struct solr_connection *conn, const char *cmd)
308 {
309 	struct istream *post_payload;
310 	struct solr_connection_post post;
311 
312 	i_assert(!conn->posting);
313 
314 	i_zero(&post);
315 	post.conn = conn;
316 
317 	post.http_req = solr_connection_post_request(&post);
318 	post_payload = i_stream_create_from_data(cmd, strlen(cmd));
319 	http_client_request_set_payload(post.http_req, post_payload, TRUE);
320 	i_stream_unref(&post_payload);
321 	http_client_request_submit(post.http_req);
322 
323 	post.request_status = 0;
324 	http_client_wait(solr_http_client);
325 
326 	return post.request_status;
327 }
328