diff options
author | Taylor Yu <catalyst@torproject.org> | 2019-07-03 23:16:23 -0500 |
---|---|---|
committer | Taylor Yu <catalyst@torproject.org> | 2019-12-08 22:40:00 -0600 |
commit | 1a68a18093d38f9f5b7ed66c8d42e41a565febe0 (patch) | |
tree | f69ce8af9e3b01eda6c00fd44ad5f8d1c4148a77 | |
parent | 1e8bb79bbe9befd86e018e0d5f6d960e9c789462 (diff) | |
download | tor-1a68a18093d38f9f5b7ed66c8d42e41a565febe0.tar.gz tor-1a68a18093d38f9f5b7ed66c8d42e41a565febe0.zip |
reply lines structures
Part of #30984.
-rw-r--r-- | src/feature/control/control_proto.c | 157 | ||||
-rw-r--r-- | src/feature/control/control_proto.h | 72 | ||||
-rw-r--r-- | src/test/test_controller.c | 71 |
3 files changed, 300 insertions, 0 deletions
diff --git a/src/feature/control/control_proto.c b/src/feature/control/control_proto.c index 5dec87491d..4efe735c72 100644 --- a/src/feature/control/control_proto.c +++ b/src/feature/control/control_proto.c @@ -22,6 +22,8 @@ #include "core/or/origin_circuit_st.h" #include "core/or/socks_request_st.h" #include "feature/control/control_connection_st.h" +#include "lib/container/smartlist.h" +#include "lib/encoding/kvline.h" /** Append a NUL-terminated string <b>s</b> to the end of * <b>conn</b>-\>outbuf. @@ -275,3 +277,158 @@ control_write_data(control_connection_t *conn, const char *data) connection_buf_add(esc, esc_len, TO_CONN(conn)); tor_free(esc); } + +/** Write a single reply line to @a conn. + * + * @param conn control connection + * @param line control reply line to write + * @param lastone true if this is the last reply line of a multi-line reply + */ +void +control_write_reply_line(control_connection_t *conn, + const control_reply_line_t *line, bool lastone) +{ + const config_line_t *kvline = line->kvline; + char *s = NULL; + + if (strpbrk(kvline->value, "\r\n") != NULL) { + /* If a key-value pair needs to be encoded as CmdData, it can be + the only key-value pair in that reply line */ + tor_assert(kvline->next == NULL); + control_printf_datareply(conn, line->code, "%s=", kvline->key); + control_write_data(conn, kvline->value); + return; + } + s = kvline_encode(kvline, line->flags); + if (lastone) { + control_write_endreply(conn, line->code, s); + } else { + control_write_midreply(conn, line->code, s); + } + tor_free(s); +} + +/** Write a set of reply lines to @a conn. + * + * @param conn control connection + * @param lines smartlist of pointers to control_reply_line_t to write + */ +void +control_write_reply_lines(control_connection_t *conn, smartlist_t *lines) +{ + bool lastone = false; + + SMARTLIST_FOREACH_BEGIN(lines, control_reply_line_t *, line) { + if (line_sl_idx >= line_sl_len - 1) + lastone = true; + control_write_reply_line(conn, line, lastone); + } SMARTLIST_FOREACH_END(line); +} + +/** Add a single key-value pair as a new reply line to a control reply + * line list. + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param flags kvline encoding flags + * @param key key + * @param val value + */ +void +control_reply_add_1kv(smartlist_t *reply, int code, int flags, + const char *key, const char *val) +{ + control_reply_line_t *line = tor_malloc_zero(sizeof(*line)); + + line->code = code; + line->flags = flags; + config_line_append(&line->kvline, key, val); + smartlist_add(reply, line); +} + +/** Append a single key-value pair to last reply line in a control + * reply line list. + * + * @param reply smartlist of pointers to control_reply_line_t + * @param key key + * @param val value + */ +void +control_reply_append_kv(smartlist_t *reply, const char *key, const char *val) +{ + int len = smartlist_len(reply); + control_reply_line_t *line; + + tor_assert(len > 0); + + line = smartlist_get(reply, len - 1); + config_line_append(&line->kvline, key, val); +} + +/** Add new reply line consisting of the string @a s + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param s string containing the rest of the reply line + */ +void +control_reply_add_str(smartlist_t *reply, int code, const char *s) +{ + control_reply_add_1kv(reply, code, KV_OMIT_KEYS|KV_RAW, "", s); +} + +/** Format a new reply line + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param fmt format string + */ +void +control_reply_add_printf(smartlist_t *reply, int code, const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + va_start(ap, fmt); + (void)tor_vasprintf(&buf, fmt, ap); + va_end(ap); + control_reply_add_str(reply, code, buf); + tor_free(buf); +} + +/** Add a "250 OK" line to a set of control reply lines */ +void +control_reply_add_done(smartlist_t *reply) +{ + control_reply_add_str(reply, 250, "OK"); +} + +/** Free a control_reply_line_t. Don't call this directly; use the + * control_reply_line_free() macro instead. */ +void +control_reply_line_free_(control_reply_line_t *line) +{ + if (!line) + return; + config_free_lines(line->kvline); + tor_free_(line); +} + +/** Clear a smartlist of control_reply_line_t. Doesn't free the + * smartlist, but does free each individual line. */ +void +control_reply_clear(smartlist_t *reply) +{ + SMARTLIST_FOREACH(reply, control_reply_line_t *, line, + control_reply_line_free(line)); + smartlist_clear(reply); +} + +/** Free a smartlist of control_reply_line_t. Don't call this + * directly; use the control_reply_free() macro instead. */ +void +control_reply_free_(smartlist_t *reply) +{ + control_reply_clear(reply); + smartlist_free_(reply); +} diff --git a/src/feature/control/control_proto.h b/src/feature/control/control_proto.h index 3182f3d415..65c81924fd 100644 --- a/src/feature/control/control_proto.h +++ b/src/feature/control/control_proto.h @@ -7,11 +7,56 @@ /** * \file control_proto.h * \brief Header file for control_proto.c. + * + * See @ref replylines for details about the key-value abstraction for + * generating reply lines. **/ #ifndef TOR_CONTROL_PROTO_H #define TOR_CONTROL_PROTO_H +#include "lib/encoding/confline.h" + +/** + * @defgroup replylines Control reply lines + * @brief Key-value structures for control reply lines + * + * Control reply lines are config_line_t key-value structures with + * some additional information to help formatting, such as the numeric + * result code specified in the control protocol and flags affecting + * the way kvline_encode() formats the @a kvline. + * + * Generally, modules implementing control commands will work with + * smartlists of these structures, using functions like + * control_reply_add_str() for adding a reply line consisting of a + * single string, or control_reply_add_1kv() and + * control_reply_append_kv() for composing a line containing one or + * more key-value pairs. + * + * @{ + */ +/** @brief A reply line for the control protocol. + * + * This wraps config_line_t with some additional information that's + * useful when generating control reply lines. + */ +typedef struct control_reply_line_t { + int code; /**< numeric code */ + int flags; /**< kvline encoding flags */ + config_line_t *kvline; /**< kvline */ +} control_reply_line_t; + +void control_reply_line_free_(control_reply_line_t *line); +/** + * @brief Free and null a control_reply_line_t + * + * @param line pointer to control_reply_line_t to free + */ +#define control_reply_line_free(line) \ + FREE_AND_NULL(control_reply_line_t, \ + control_reply_line_free_, (line)) +/** @} */ + void connection_write_str_to_buf(const char *s, control_connection_t *conn); void connection_printf_to_buf(control_connection_t *conn, const char *format, ...) @@ -45,4 +90,31 @@ void control_printf_datareply(control_connection_t *conn, int code, CHECK_PRINTF(3, 4); void control_write_data(control_connection_t *conn, const char *data); +/** @addtogroup replylines + * @{ + */ +void control_write_reply_line(control_connection_t *conn, + const control_reply_line_t *line, bool lastone); +void control_write_reply_lines(control_connection_t *conn, smartlist_t *lines); + +void control_reply_add_1kv(smartlist_t *reply, int code, int flags, + const char *key, const char *val); +void control_reply_append_kv(smartlist_t *reply, const char *key, + const char *val); +void control_reply_add_str(smartlist_t *reply, int code, const char *s); +void control_reply_add_printf(smartlist_t *reply, int code, + const char *fmt, ...) + CHECK_PRINTF(3, 4); +void control_reply_add_done(smartlist_t *reply); + +void control_reply_clear(smartlist_t *reply); +void control_reply_free_(smartlist_t *reply); + +/** @brief Free and null a smartlist of control_reply_line_t. + * + * @param r pointer to smartlist_t of control_reply_line_t to free */ +#define control_reply_free(r) \ + FREE_AND_NULL(smartlist_t, control_reply_free_, (r)) +/** @} */ + #endif /* !defined(TOR_CONTROL_PROTO_H) */ diff --git a/src/test/test_controller.c b/src/test/test_controller.c index d07ec5d0f0..eb68e48476 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -1957,6 +1957,76 @@ test_getinfo_md_all(void *arg) return; } +static smartlist_t *reply_strs; + +static void +mock_control_write_reply_list(control_connection_t *conn, int code, int c, + const char *s) +{ + (void)conn; + /* To make matching easier, don't append "\r\n" */ + smartlist_add_asprintf(reply_strs, "%03d%c%s", code, c, s); +} + +static void +test_control_reply(void *arg) +{ + (void)arg; + smartlist_t *lines = smartlist_new(); + + MOCK(control_write_reply, mock_control_write_reply); + + tor_free(reply_str); + control_reply_clear(lines); + control_reply_add_str(lines, 250, "FOO"); + control_write_reply_lines(NULL, lines); + tt_str_op(reply_str, OP_EQ, "FOO"); + + tor_free(reply_str); + control_reply_clear(lines); + control_reply_add_done(lines); + control_write_reply_lines(NULL, lines); + tt_str_op(reply_str, OP_EQ, "OK"); + + tor_free(reply_str); + control_reply_clear(lines); + UNMOCK(control_write_reply); + MOCK(control_write_reply, mock_control_write_reply_list); + reply_strs = smartlist_new(); + control_reply_add_1kv(lines, 250, 0, "A", "B"); + control_reply_add_1kv(lines, 250, 0, "C", "D"); + control_write_reply_lines(NULL, lines); + tt_int_op(smartlist_len(reply_strs), OP_EQ, 2); + tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250-A=B"); + tt_str_op((char *)smartlist_get(reply_strs, 1), OP_EQ, "250 C=D"); + + control_reply_clear(lines); + SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p)); + smartlist_clear(reply_strs); + control_reply_add_printf(lines, 250, "PROTOCOLINFO %d", 1); + control_reply_add_1kv(lines, 250, KV_OMIT_VALS|KV_RAW, "AUTH", ""); + control_reply_append_kv(lines, "METHODS", "COOKIE"); + control_reply_append_kv(lines, "COOKIEFILE", escaped("/tmp/cookie")); + control_reply_add_done(lines); + control_write_reply_lines(NULL, lines); + tt_int_op(smartlist_len(reply_strs), OP_EQ, 3); + tt_str_op((char *)smartlist_get(reply_strs, 0), + OP_EQ, "250-PROTOCOLINFO 1"); + tt_str_op((char *)smartlist_get(reply_strs, 1), + OP_EQ, "250-AUTH METHODS=COOKIE COOKIEFILE=\"/tmp/cookie\""); + tt_str_op((char *)smartlist_get(reply_strs, 2), + OP_EQ, "250 OK"); + + done: + UNMOCK(control_write_reply); + tor_free(reply_str); + control_reply_free(lines); + if (reply_strs) + SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p)); + smartlist_free(reply_strs); + return; +} + #ifndef COCCI #define PARSER_TEST(type) \ { "parse/" #type, test_controller_parse_cmd, 0, &passthrough_setup, \ @@ -1989,5 +2059,6 @@ struct testcase_t controller_tests[] = { { "download_status_bridge", test_download_status_bridge, 0, NULL, NULL }, { "current_time", test_current_time, 0, NULL, NULL }, { "getinfo_md_all", test_getinfo_md_all, 0, NULL, NULL }, + { "control_reply", test_control_reply, 0, NULL, NULL }, END_OF_TESTCASES }; |