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