libasyncd
ad_http_handler.c
Go to the documentation of this file.
1 #include <stdbool.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <unistd.h>
6 #include <limits.h>
7 #include <assert.h>
8 #include <errno.h>
9 #include <event2/buffer.h>
10 #include "qlibc/qlibc.h"
11 #include "ad_server.h"
12 #include "ad_http_handler.h"
13 #include "macro.h"
14 
15 /**
16  * HTTP protocol request/response handler.
17  *
18  * @file ad_http_handler.c
19  */
20 
21 #ifndef _DOXYGEN_SKIP
22 static ad_http_t *http_new(struct evbuffer *out);
23 static void http_free(ad_http_t *http);
24 static void http_free_cb(ad_conn_t *conn, void *userdata);
25 static size_t http_add_inbuf(struct evbuffer *buffer, ad_http_t *http, size_t maxsize);
26 
27 static int http_parser(ad_http_t *http, struct evbuffer *in);
28 static int parse_requestline(ad_http_t *http, char *line);
29 static int parse_headers(ad_http_t *http, struct evbuffer *in);
30 static int parse_body(ad_http_t *http, struct evbuffer *in);
31 static ssize_t parse_chunked_body(ad_http_t *http, struct evbuffer *in);
32 
33 static bool isValidPathname(const char *path);
34 static void correctPathname(char *path);
35 static char *evbuffer_peekln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style);
36 static ssize_t evbuffer_drainln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style);
37 
38 #endif
39 
40 /**
41  * HTTP protocol handler hook.
42  *
43  * This hook provides an easy way to handle HTTP request/response.
44  *
45  * @note
46  * This hook must be registered at the top of hook chain.
47  *
48  * @code
49  * ad_server_t *server = ad_server_new();
50  * ad_server_register_hook(server, ad_http_handler, NULL);
51  * @endcode
52  */
53 int ad_http_handler(short event, ad_conn_t *conn, void *userdata) {
54  if (event & AD_EVENT_INIT) {
55  DEBUG("==> HTTP INIT");
56  ad_http_t *http = http_new(conn->out);
57  if (http == NULL) return AD_CLOSE;
58  ad_conn_set_extra(conn, http, http_free_cb);
59  return AD_OK;
60  } else if (event & AD_EVENT_READ) {
61  DEBUG("==> HTTP READ");
62  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
63  int status = http_parser(http, conn->in);
64  if (conn->method == NULL && http->request.method != NULL) {
65  ad_conn_set_method(conn, http->request.method);
66  }
67  return status;
68  } else if (event & AD_EVENT_WRITE) {
69  DEBUG("==> HTTP WRITE");
70  return AD_OK;
71  } else if (event & AD_EVENT_CLOSE) {
72  DEBUG("==> HTTP CLOSE=%x (TIMEOUT=%d, SHUTDOWN=%d)",
73  event, event & AD_EVENT_TIMEOUT, event & AD_EVENT_SHUTDOWN);
74  return AD_OK;
75  }
76 
77  BUG_EXIT();
78 }
79 
80 /**
81  * Return the request status.
82  */
84  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
85  if (http == NULL) return AD_HTTP_ERROR;
86  return http->request.status;
87 }
88 
89 struct evbuffer *ad_http_get_inbuf(ad_conn_t *conn) {
90  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
91  return http->request.inbuf;
92 }
93 
94 struct evbuffer *ad_http_get_outbuf(ad_conn_t *conn) {
95  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
96  return http->response.outbuf;
97 }
98 
99 const char *ad_http_get_request_header(ad_conn_t *conn, const char *name) {
100  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
101  return http->request.headers->getstr(http->request.headers, name, false);
102 }
103 
105  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
106  return http->request.contentlength;
107 }
108 
109 /**
110  * @param maxsize maximum length of data to pull up. 0 to pull up everything.
111  */
112 void *ad_http_get_content(ad_conn_t *conn, size_t maxsize, size_t *storedsize) {
113  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
114 
115  size_t inbuflen = evbuffer_get_length(http->request.inbuf);
116  size_t readlen = (maxsize == 0) ? inbuflen : ((inbuflen < maxsize) ? inbuflen : maxsize);
117  if (readlen == 0) return NULL;
118 
119  void *data = malloc(readlen);
120  if (data == NULL) return NULL;
121 
122  size_t removedlen = evbuffer_remove(http->request.inbuf, data, readlen);
123  if (storedsize) *storedsize = removedlen;
124 
125  return data;
126 }
127 
128 /**
129  * @param value value string to set. NULL to remove the header.
130  * @return 0 on success, -1 if we already sent it out.
131  */
132 int ad_http_add_response_header(ad_conn_t *conn, const char *name, const char *value) {
133  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
134  if (http->response.frozen_header) {
135  return -1;
136  }
137 
138  if (value != NULL) {
139  http->response.headers->putstr(http->response.headers, name, value);
140  } else {
141  http->response.headers->remove(http->response.headers, name);
142  }
143 
144  return 0;
145 }
146 
147 /**
148  *
149  * @return 0 on success, -1 if we already sent it out.
150  */
151 int ad_http_set_response_code(ad_conn_t *conn, int code, const char *reason) {
152  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
153  if (http->response.frozen_header) {
154  return -1;
155  }
156 
157  http->response.code = code;
158  if (reason) http->response.reason = strdup(reason);
159 
160  return 0;
161 }
162 
163 /**
164  *
165  * @param size content size. -1 for chunked transfer encoding.
166  * @return 0 on success, -1 if we already sent it out.
167  */
168 int ad_http_set_response_content(ad_conn_t *conn, const char *contenttype, off_t size) {
169  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
170  if (http->response.frozen_header) {
171  return -1;
172  }
173 
174  // Set Content-Type header.
175  ad_http_add_response_header(conn, "Content-Type", (contenttype) ? contenttype : HTTP_DEF_CONTENTTYPE);
176  if (size >= 0) {
177  char clenval[20+1];
178  sprintf(clenval, "%jd", size);
179  ad_http_add_response_header(conn, "Content-Length", clenval);
180  http->response.contentlength = size;
181  } else {
182  ad_http_add_response_header(conn, "Transfer-Encoding", "chunked");
183  http->response.contentlength = -1;
184  }
185 
186  return 0;
187 }
188 
189 /**
190  * @return total bytes sent, 0 on error.
191  */
192 size_t ad_http_response(ad_conn_t *conn, int code, const char *contenttype, const void *data, off_t size) {
193  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
194  if (http->response.frozen_header) {
195  return 0;
196  }
198  ad_http_set_response_content(conn, contenttype, size);
199  return ad_http_send_data(conn, data, size);
200 }
201 
202 /**
203  *
204  * @return 0 total bytes put in out buffer, -1 if we already sent it out.
205  */
207  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
208  if (http->response.frozen_header) {
209  return 0;
210  }
211  http->response.frozen_header = true;
212 
213  // Send status line.
214  const char *reason = (http->response.reason) ? http->response.reason : ad_http_get_reason(http->response.code);
215  evbuffer_add_printf(http->response.outbuf, "%s %d %s" HTTP_CRLF, http->request.httpver, http->response.code, reason);
216 
217  // Send headers.
218  qdlnobj_t obj;
219  bzero((void*)&obj, sizeof(obj));
220  qlisttbl_t *tbl = http->response.headers;
221  tbl->lock(tbl);
222  while(tbl->getnext(tbl, &obj, NULL, false)) {
223  evbuffer_add_printf(http->response.outbuf, "%s: %s" HTTP_CRLF, (char*)obj.name, (char*)obj.data);
224  }
225  tbl->unlock(tbl);
226 
227  // Send empty line, indicator of end of header.
228  evbuffer_add(http->response.outbuf, HTTP_CRLF, CONST_STRLEN(HTTP_CRLF));
229 
230  return evbuffer_get_length(http->response.outbuf);
231 }
232 
233 /**
234  *
235  * @return 0 on success, -1 if we already sent it out.
236  */
237 size_t ad_http_send_data(ad_conn_t *conn, const void *data, size_t size) {
238  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
239 
240  if (http->response.contentlength < 0) {
241  WARN("Content-Length is not set. Invalid usage.");
242  return 0;
243  }
244 
245  if ((http->response.bodyout + size) > http->response.contentlength) {
246  WARN("Trying to send more data than supposed to");
247  return 0;
248  }
249 
250  size_t beforesize = evbuffer_get_length(http->response.outbuf);
251  if (! http->response.frozen_header) {
252  ad_http_send_header(conn);
253  }
254 
255  if (data != NULL && size > 0) {
256  if (evbuffer_add(http->response.outbuf, data, size)) return 0;
257  }
258 
259  http->response.bodyout += size;
260  return (evbuffer_get_length(http->response.outbuf) - beforesize);
261 }
262 
263 size_t ad_http_send_chunk(ad_conn_t *conn, const void *data, size_t size) {
264  ad_http_t *http = (ad_http_t *)ad_conn_get_extra(conn);
265 
266  if (http->response.contentlength >= 0) {
267  WARN("Content-Length is set. Invalid usage.");
268  return 0;
269  }
270 
271  if (! http->response.frozen_header) {
272  ad_http_send_header(conn);
273  }
274 
275  size_t beforesize = evbuffer_get_length(http->response.outbuf);
276  int status = 0;
277  if (size > 0) {
278  status += evbuffer_add_printf(http->response.outbuf, "%zu" HTTP_CRLF, size);
279  status += evbuffer_add(http->response.outbuf, data, size);
280  status += evbuffer_add(http->response.outbuf, HTTP_CRLF, CONST_STRLEN(HTTP_CRLF));
281  } else {
282  status += evbuffer_add_printf(http->response.outbuf, "0" HTTP_CRLF HTTP_CRLF);
283  }
284  if (status != 0) {
285  WARN("Failed to add data to out-buffer. (size:%jd)", size);
286  return 0;
287  }
288 
289  size_t bytesout = evbuffer_get_length(http->response.outbuf) - beforesize;
290  http->response.bodyout += bytesout;
291  return bytesout;
292 }
293 
294 const char *ad_http_get_reason(int code) {
295  switch (code) {
296  case HTTP_CODE_CONTINUE : return "Continue";
297  case HTTP_CODE_OK : return "OK";
298  case HTTP_CODE_CREATED : return "Created";
299  case HTTP_CODE_NO_CONTENT : return "No content";
300  case HTTP_CODE_PARTIAL_CONTENT : return "Partial Content";
301  case HTTP_CODE_MULTI_STATUS : return "Multi Status";
302  case HTTP_CODE_MOVED_TEMPORARILY : return "Moved Temporarily";
303  case HTTP_CODE_NOT_MODIFIED : return "Not Modified";
304  case HTTP_CODE_BAD_REQUEST : return "Bad Request";
305  case HTTP_CODE_UNAUTHORIZED : return "Authorization Required";
306  case HTTP_CODE_FORBIDDEN : return "Forbidden";
307  case HTTP_CODE_NOT_FOUND : return "Not Found";
308  case HTTP_CODE_METHOD_NOT_ALLOWED : return "Method Not Allowed";
309  case HTTP_CODE_REQUEST_TIME_OUT : return "Request Time Out";
310  case HTTP_CODE_GONE : return "Gone";
311  case HTTP_CODE_REQUEST_URI_TOO_LONG : return "Request URI Too Long";
312  case HTTP_CODE_LOCKED : return "Locked";
313  case HTTP_CODE_INTERNAL_SERVER_ERROR : return "Internal Server Error";
314  case HTTP_CODE_NOT_IMPLEMENTED : return "Not Implemented";
315  case HTTP_CODE_SERVICE_UNAVAILABLE : return "Service Unavailable";
316  }
317 
318  WARN("Undefined code found. %d", code);
319  return "-";
320 }
321 
322 
323 
324 /******************************************************************************
325  * Private internal functions.
326  *****************************************************************************/
327 #ifndef _DOXYGEN_SKIP
328 
329 static ad_http_t *http_new(struct evbuffer *out) {
330  // Create a new connection container.
331  ad_http_t *http = NEW_OBJECT(ad_http_t);
332  if (http == NULL) return NULL;
333 
334  // Allocate additional resources.
335  http->request.inbuf = evbuffer_new();
336  http->request.headers = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
337  http->response.headers = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
338  if(http->request.inbuf == NULL || http->request.headers == NULL || http->response.headers == NULL) {
339  http_free(http);
340  return NULL;
341  }
342 
343  // Initialize structure.
345  http->request.contentlength = -1;
346  http->response.contentlength = -1;
347  http->response.outbuf = out;
348 
349  return http;
350 }
351 
352 static void http_free(ad_http_t *http) {
353  if (http) {
354  if (http->request.inbuf) evbuffer_free(http->request.inbuf);
355  if (http->request.method) free(http->request.method);
356  if (http->request.uri) free(http->request.uri);
357  if (http->request.httpver) free(http->request.httpver);
358  if (http->request.path) free(http->request.path);
359  if (http->request.query) free(http->request.query);
360 
361  if (http->request.headers) http->request.headers->free(http->request.headers);
362  if (http->request.host) free(http->request.host);
363  if (http->request.domain) free(http->request.domain);
364 
365  if (http->response.headers) http->response.headers->free(http->response.headers);
366  if (http->response.reason) free(http->response.reason);
367 
368  free(http);
369  }
370 }
371 
372 static void http_free_cb(ad_conn_t *conn, void *userdata) {
373  http_free((ad_http_t *)userdata);
374 }
375 
376 static size_t http_add_inbuf(struct evbuffer *buffer, ad_http_t *http, size_t maxsize) {
377  if (maxsize == 0 || evbuffer_get_length(buffer) == 0) {
378  return 0;
379  }
380 
381  return evbuffer_remove_buffer(buffer, http->request.inbuf, maxsize);
382 }
383 
384 static int http_parser(ad_http_t *http, struct evbuffer *in) {
385  ASSERT(http != NULL && in != NULL);
386 
387  if (http->request.status == AD_HTTP_REQ_INIT) {
388  char *line = evbuffer_readln(in, NULL, EVBUFFER_EOL_CRLF);
389  if (line == NULL) return http->request.status;
390  http->request.status = parse_requestline(http, line);
391  free(line);
392  // Do not call user callbacks until I reach the next state.
393  if (http->request.status == AD_HTTP_REQ_INIT) {
394  return AD_TAKEOVER;
395  }
396  }
397 
399  http->request.status = parse_headers(http, in);
400  // Do not call user callbacks until I reach the next state.
402  return AD_TAKEOVER;
403  }
404  }
405 
406  if (http->request.status == AD_HTTP_REQ_HEADER_DONE) {
407  http->request.status = parse_body(http, in);
408  // Do not call user callbacks until I reach the next state.
409  if (http->request.status == AD_HTTP_REQ_HEADER_DONE) {
410  return AD_TAKEOVER;
411  }
412  }
413 
414  if (http->request.status == AD_HTTP_REQ_DONE) {
415  return AD_OK;
416  }
417 
418  if (http->request.status == AD_HTTP_ERROR) {
419  return AD_CLOSE;
420  }
421 
422  BUG_EXIT();
423  return AD_CLOSE;
424 }
425 
426 
427 static int parse_requestline(ad_http_t *http, char *line) {
428  // Parse request line.
429  char *saveptr;
430  char *method = strtok_r(line, " ", &saveptr);
431  char *uri = strtok_r(NULL, " ", &saveptr);
432  char *httpver = strtok_r(NULL, " ", &saveptr);
433  char *tmp = strtok_r(NULL, " ", &saveptr);
434 
435  if (method == NULL || uri == NULL || httpver == NULL || tmp != NULL) {
436  DEBUG("Invalid request line. %s", line);
437  return AD_HTTP_ERROR;
438  }
439 
440  // Set request method
441  http->request.method = qstrupper(strdup(method));
442 
443  // Set HTTP version
444  http->request.httpver = qstrupper(strdup(httpver));
445  if (strcmp(http->request.httpver, HTTP_PROTOCOL_09)
446  && strcmp(http->request.httpver, HTTP_PROTOCOL_10)
447  && strcmp(http->request.httpver, HTTP_PROTOCOL_11)
448  ) {
449  DEBUG("Unknown protocol: %s", http->request.httpver);
450  return AD_HTTP_ERROR;
451  }
452 
453  // Set URI
454  if (uri[0] == '/') {
455  http->request.uri = strdup(uri);
456  } else if ((tmp = strstr(uri, "://"))) {
457  // divide URI into host and path
458  char *path = strstr(tmp + CONST_STRLEN("://"), "/");
459  if (path == NULL) { // URI has no path ex) http://domain.com:80
460  http->request.headers->putstr(http->request.headers, "Host", tmp + CONST_STRLEN("://"));
461  http->request.uri = strdup("/");
462  } else { // URI has path, ex) http://domain.com:80/path
463  *path = '\0';
464  http->request.headers->putstr(http->request.headers, "Host", tmp + CONST_STRLEN("://"));
465  *path = '/';
466  http->request.uri = strdup(path);
467  }
468  } else {
469  DEBUG("Invalid URI format. %s", uri);
470  return AD_HTTP_ERROR;
471  }
472 
473  // Set request path. Only path part from URI.
474  http->request.path = strdup(http->request.uri);
475  tmp = strstr(http->request.path, "?");
476  if (tmp) {
477  *tmp ='\0';
478  http->request.query = strdup(tmp + 1);
479  } else {
480  http->request.query = strdup("");
481  }
482  qurl_decode(http->request.path);
483 
484  // check path
485  if (isValidPathname(http->request.path) == false) {
486  DEBUG("Invalid URI format : %s", http->request.uri);
487  return AD_HTTP_ERROR;
488  }
489  correctPathname(http->request.path);
490 
491  DEBUG("Method=%s, URI=%s, VER=%s", http->request.method, http->request.uri, http->request.httpver);
492 
494 }
495 
496 static int parse_headers(ad_http_t *http, struct evbuffer *in) {
497  char *line;
498  while ((line = evbuffer_readln(in, NULL, EVBUFFER_EOL_CRLF))) {
499  if (IS_EMPTY_STR(line)) {
500  const char *clen = http->request.headers->getstr(http->request.headers, "Content-Length", false);
501  http->request.contentlength = (clen) ? atol(clen) : -1;
503  }
504  // Parse
505  char *name, *value;
506  char *tmp = strstr(line, ":");
507  if (tmp) {
508  *tmp = '\0';
509  name = qstrtrim(line);
510  value = qstrtrim(tmp + 1);
511  } else {
512  name = qstrtrim(line);
513  value = "";
514  }
515  // Add
516  http->request.headers->putstr(http->request.headers, name, value);
517 
518  free(line);
519  }
520 
521  return http->request.status;
522 }
523 
524 static int parse_body(ad_http_t *http, struct evbuffer *in) {
525  // Handle static data case.
526  if (http->request.contentlength == 0) {
527  return AD_HTTP_REQ_DONE;
528  } else if (http->request.contentlength > 0) {
529  if (http->request.contentlength > http->request.bodyin) {
530  size_t maxread = http->request.contentlength - http->request.bodyin;
531  if (maxread > 0 && evbuffer_get_length(in) > 0) {
532  http->request.bodyin += http_add_inbuf(in, http, maxread);
533  }
534  }
535  if (http->request.contentlength == http->request.bodyin) {
536  return AD_HTTP_REQ_DONE;
537  }
538  } else {
539  // Check if Transfer-Encoding is chunked.
540  const char *tranenc = http->request.headers->getstr(http->request.headers, "Transfer-Encoding", false);
541  if (tranenc != NULL && !strcmp(tranenc, "chunked")) {
542  // TODO: handle chunked encoding
543  for(;;) {
544  ssize_t chunksize = parse_chunked_body(http, in);
545  if (chunksize > 0) {
546  continue;
547  } else if (chunksize == 0) {
548  return AD_HTTP_REQ_DONE;
549  } else if (chunksize == -1) {
550  return http->request.status;
551  } else {
552  return AD_HTTP_ERROR;
553  }
554  }
555  } else {
556  return AD_HTTP_REQ_DONE;
557  }
558  }
559 
560  return http->request.status;
561 }
562 
563 /**
564  * Parse chunked body and append it to inbuf.
565  *
566  * @return number of bytes in a chunk. so 0 for the ending chunk. -1 for not enough data, -2 format error.
567  */
568 static ssize_t parse_chunked_body(ad_http_t *http, struct evbuffer *in) {
569  // Peek chunk size.
570  size_t crlf_len = 0;
571  char *line = evbuffer_peekln(in, &crlf_len, EVBUFFER_EOL_CRLF);
572  if (line == NULL) return -1; // not enough data.
573  size_t linelen = strlen(line);
574 
575  // Parse chunk size
576  int chunksize = -1;
577  sscanf(line, "%x", &chunksize);
578  free(line);
579  if (chunksize < 0) return -2; // format error
580 
581  // Check if we've received whole data of this chunk.
582  size_t datalen = linelen + crlf_len + chunksize + crlf_len;
583  size_t inbuflen = evbuffer_get_length(in);
584  if (inbuflen < datalen) {
585  return -1; // not enough data.
586  }
587 
588  // Copy chunk body
589  evbuffer_drainln(in, NULL, EVBUFFER_EOL_CRLF);
590  http_add_inbuf(in, http, chunksize);
591  evbuffer_drainln(in, NULL, EVBUFFER_EOL_CRLF);
592 
593  return chunksize;
594 }
595 
596 /**
597  * validate file path
598  */
599 static bool isValidPathname(const char *path) {
600  if (path == NULL) return false;
601 
602  int len = strlen(path);
603  if (len == 0 || len >= PATH_MAX) return false;
604  else if (path[0] != '/') return false;
605  else if (strpbrk(path, "\\:*?\"<>|") != NULL) return false;
606 
607  // check folder name length
608  int n;
609  char *t;
610  for (n = 0, t = (char *)path; *t != '\0'; t++) {
611  if (*t == '/') {
612  n = 0;
613  continue;
614  }
615  if (n >= FILENAME_MAX) {
616  DEBUG("Filename too long.");
617  return false;
618  }
619  n++;
620  }
621 
622  return true;
623 }
624 
625 /**
626  * Correct pathname.
627  *
628  * @note
629  * remove : heading & tailing white spaces, double slashes, tailing slash
630  */
631 static void correctPathname(char *path) {
632  // Take care of head & tail white spaces.
633  qstrtrim(path);
634 
635  // Take care of double slashes.
636  while (strstr(path, "//") != NULL) qstrreplace("sr", path, "//", "/");
637 
638  // Take care of tailing slash.
639  int len = strlen(path);
640  if (len <= 1) return;
641  if (path[len - 1] == '/') path[len - 1] = '\0';
642 }
643 
644 static char *evbuffer_peekln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style) {
645  // Check if first line has arrived.
646  struct evbuffer_ptr ptr = evbuffer_search_eol(buffer, NULL, n_read_out, eol_style);
647  if (ptr.pos == -1) return NULL;
648 
649  char *line = (char *)malloc(ptr.pos + 1);
650  if (line == NULL) return NULL;
651 
652  // Linearizes buffer
653  if (ptr.pos > 0) {
654  char *bufferptr = (char *)evbuffer_pullup(buffer, ptr.pos);
655  ASSERT(bufferptr != NULL);
656  strncpy(line, bufferptr, ptr.pos);
657  }
658  line[ptr.pos] = '\0';
659 
660  return line;
661 }
662 
663 static ssize_t evbuffer_drainln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style) {
664  char *line = evbuffer_readln(buffer, n_read_out, eol_style);
665  if (line == NULL) return -1;
666 
667  size_t linelen = strlen(line);
668  free(line);
669  return linelen;
670 }
671 
672 #endif _DOXYGEN_SKIP