| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include <strings.h> | ||
| 5 | #include <errno.h> | ||
| 6 | #include <limits.h> | ||
| 7 | #include <yaml.h> | ||
| 8 | #include "log.h" | ||
| 9 | #include "config.h" | ||
| 10 | |||
| 11 | 1992 | static yaml_node_t *find_yaml_node(yaml_document_t *doc, yaml_node_t *node, const char *key) | |
| 12 | { | ||
| 13 |
2/4✓ Branch 0 taken 1992 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1992 times.
|
1992 | if (!node || node->type != YAML_MAPPING_NODE) |
| 14 | ✗ | return NULL; | |
| 15 | |||
| 16 | 1992 | for (yaml_node_pair_t *pair = node->data.mapping.pairs.start; | |
| 17 |
2/2✓ Branch 0 taken 5795 times.
✓ Branch 1 taken 855 times.
|
6650 | pair < node->data.mapping.pairs.top; pair++) { |
| 18 | 5795 | yaml_node_t *key_node = yaml_document_get_node(doc, pair->key); | |
| 19 |
2/4✓ Branch 0 taken 5795 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5795 times.
✗ Branch 3 not taken.
|
5795 | if (key_node && key_node->type == YAML_SCALAR_NODE && |
| 20 |
2/2✓ Branch 0 taken 1137 times.
✓ Branch 1 taken 4658 times.
|
5795 | strcmp((char *)key_node->data.scalar.value, key) == 0) { |
| 21 | 1137 | return yaml_document_get_node(doc, pair->value); | |
| 22 | } | ||
| 23 | } | ||
| 24 | 855 | return NULL; | |
| 25 | } | ||
| 26 | |||
| 27 | 975 | static int get_node_line(yaml_node_t *node) | |
| 28 | { | ||
| 29 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 975 times.
|
975 | if (!node) |
| 30 | ✗ | return 0; | |
| 31 | 975 | return (int)node->start_mark.line + 1; | |
| 32 | } | ||
| 33 | |||
| 34 | typedef struct { | ||
| 35 | yaml_document_t *document; | ||
| 36 | yaml_node_t *root; | ||
| 37 | ServerConfig *config; | ||
| 38 | const char *section_name; | ||
| 39 | } ConfigParser; | ||
| 40 | |||
| 41 | #define PARSE_FIELD(field_name, parser_func, min_val, max_val, dest) \ | ||
| 42 | do { \ | ||
| 43 | yaml_node_t *_node = find_yaml_node(ctx->document, node, field_name); \ | ||
| 44 | if (_node) { \ | ||
| 45 | char _full_name[128]; \ | ||
| 46 | snprintf(_full_name, sizeof(_full_name), "%s.%s", ctx->section_name, field_name); \ | ||
| 47 | if (parser_func(_node, _full_name, min_val, max_val, dest) != 0) { \ | ||
| 48 | fprintf(stderr, " at line %d\n", get_node_line(_node)); \ | ||
| 49 | return -1; \ | ||
| 50 | } \ | ||
| 51 | } \ | ||
| 52 | } while(0) | ||
| 53 | |||
| 54 | #define PARSE_STRING(field_name, dest, size) \ | ||
| 55 | do { \ | ||
| 56 | yaml_node_t *_node = find_yaml_node(ctx->document, node, field_name); \ | ||
| 57 | if (_node) { \ | ||
| 58 | char _full_name[128]; \ | ||
| 59 | snprintf(_full_name, sizeof(_full_name), "%s.%s", ctx->section_name, field_name); \ | ||
| 60 | if (get_yaml_string(_node, _full_name, dest, size) != 0) { \ | ||
| 61 | fprintf(stderr, " at line %d\n", get_node_line(_node)); \ | ||
| 62 | return -1; \ | ||
| 63 | } \ | ||
| 64 | } \ | ||
| 65 | } while(0) | ||
| 66 | |||
| 67 | #define PARSE_BOOL(field_name, dest) \ | ||
| 68 | do { \ | ||
| 69 | yaml_node_t *_node = find_yaml_node(ctx->document, node, field_name); \ | ||
| 70 | if (_node) { \ | ||
| 71 | char _full_name[128]; \ | ||
| 72 | snprintf(_full_name, sizeof(_full_name), "%s.%s", ctx->section_name, field_name); \ | ||
| 73 | if (get_yaml_bool(_node, _full_name, dest) != 0) { \ | ||
| 74 | fprintf(stderr, " at line %d\n", get_node_line(_node)); \ | ||
| 75 | return -1; \ | ||
| 76 | } \ | ||
| 77 | } \ | ||
| 78 | } while(0) | ||
| 79 | |||
| 80 | 47 | static void set_config_defaults(ServerConfig *config) | |
| 81 | { | ||
| 82 | 47 | memset(config, 0, sizeof(*config)); | |
| 83 | 47 | config->port = 8443; | |
| 84 | 47 | config->max_connections = 100; | |
| 85 | 47 | config->shutdown_timeout_seconds = 30; | |
| 86 | 47 | snprintf(config->log_level, sizeof(config->log_level), "info"); | |
| 87 | |||
| 88 | 47 | config->logging.level = LOG_LEVEL_DEBUG; | |
| 89 | 47 | config->logging.format = LOG_FORMAT_PLAIN; | |
| 90 | 47 | config->logging.buffer_size = 16384; | |
| 91 | 47 | config->logging.rollover_size = 10485760; | |
| 92 | 47 | config->logging.rollover_daily = 1; | |
| 93 | 47 | config->logging.appender_flags = APPENDER_FILE | APPENDER_CONSOLE; | |
| 94 | 47 | snprintf(config->logging.file, sizeof(config->logging.file), "emme.log"); | |
| 95 | |||
| 96 | 47 | snprintf(config->ssl.certificate, sizeof(config->ssl.certificate), "certs/dev.crt"); | |
| 97 | 47 | snprintf(config->ssl.private_key, sizeof(config->ssl.private_key), "certs/dev.key"); | |
| 98 | 47 | config->ssl.session_cache_size = 100000; | |
| 99 | 47 | config->ssl.session_timeout = 300; | |
| 100 | 47 | config->ssl.session_ticket_key[0] = '\0'; | |
| 101 | 47 | config->ssl.read_buffer_size = 32768; | |
| 102 | 47 | config->ssl.enable_partial_write = 1; | |
| 103 | 47 | config->ssl.release_buffers = 1; | |
| 104 | |||
| 105 | 47 | config->http2.keepalive_timeout = 60; | |
| 106 | 47 | config->http2.max_requests_per_connection = 1000; | |
| 107 | 47 | config->http2.max_concurrent_streams = 100; | |
| 108 | |||
| 109 | 47 | config->request_timeout_ms = 30000; | |
| 110 | 47 | config->tls_handshake_timeout_ms = 10000; | |
| 111 | 47 | config->per_ip_connection_limit = 10; | |
| 112 | |||
| 113 | 47 | config->security_headers.enabled = true; | |
| 114 | 47 | config->security_headers.header_count = 0; | |
| 115 | 47 | } | |
| 116 | |||
| 117 | 522 | static int get_yaml_string_ext(yaml_node_t *node, const char *field, char *buffer, size_t size, int line) | |
| 118 | { | ||
| 119 | int written; | ||
| 120 | const char *value; | ||
| 121 | |||
| 122 |
2/4✓ Branch 0 taken 522 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 522 times.
|
522 | if (!node || node->type != YAML_SCALAR_NODE) { |
| 123 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): expected a scalar string\n", field, line); | |
| 124 | ✗ | return -1; | |
| 125 | } | ||
| 126 | |||
| 127 | 522 | value = (const char *)node->data.scalar.value; | |
| 128 | 522 | written = snprintf(buffer, size, "%s", value); | |
| 129 |
2/4✓ Branch 0 taken 522 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 522 times.
|
522 | if (written < 0 || (size_t)written >= size) { |
| 130 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): value too long\n", field, line); | |
| 131 | ✗ | return -1; | |
| 132 | } | ||
| 133 | 522 | return 0; | |
| 134 | } | ||
| 135 | |||
| 136 | 522 | static int get_yaml_string(yaml_node_t *node, const char *field, char *buffer, size_t size) | |
| 137 | { | ||
| 138 | 522 | return get_yaml_string_ext(node, field, buffer, size, get_node_line(node)); | |
| 139 | } | ||
| 140 | |||
| 141 | 200 | static int parse_int_in_range(const char *text, int min, int max, int *result) | |
| 142 | { | ||
| 143 | 200 | char *end = NULL; | |
| 144 | long value; | ||
| 145 | |||
| 146 | 200 | errno = 0; | |
| 147 | 200 | value = strtol(text, &end, 10); | |
| 148 |
4/6✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 199 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 199 times.
|
200 | if (errno != 0 || end == text || *end != '\0') |
| 149 | 1 | return -1; | |
| 150 |
4/4✓ Branch 0 taken 197 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 196 times.
|
199 | if (value < min || value > max) |
| 151 | 3 | return -1; | |
| 152 | |||
| 153 | 196 | *result = (int)value; | |
| 154 | 196 | return 0; | |
| 155 | } | ||
| 156 | |||
| 157 | 87 | static int parse_size_in_range(const char *text, size_t min, size_t max, size_t *result) | |
| 158 | { | ||
| 159 | 87 | char *end = NULL; | |
| 160 | unsigned long long value; | ||
| 161 | |||
| 162 | 87 | errno = 0; | |
| 163 | 87 | value = strtoull(text, &end, 10); | |
| 164 |
3/6✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 87 times.
|
87 | if (errno != 0 || end == text || *end != '\0') |
| 165 | ✗ | return -1; | |
| 166 |
2/4✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 87 times.
|
87 | if (value < min || value > max) |
| 167 | ✗ | return -1; | |
| 168 | |||
| 169 | 87 | *result = (size_t)value; | |
| 170 | 87 | return 0; | |
| 171 | } | ||
| 172 | |||
| 173 | 10 | static int get_yaml_int(yaml_node_t *node, const char *field, int *result) | |
| 174 | { | ||
| 175 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (!node || node->type != YAML_SCALAR_NODE) { |
| 176 | ✗ | fprintf(stderr, "Invalid '%s': expected integer scalar\n", field); | |
| 177 | ✗ | return -1; | |
| 178 | } | ||
| 179 | char *endptr; | ||
| 180 | 10 | long val = strtol((const char *)node->data.scalar.value, &endptr, 10); | |
| 181 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (*endptr != '\0') { |
| 182 | ✗ | fprintf(stderr, "Invalid '%s': expected integer, got '%s'\n", field, (const char *)node->data.scalar.value); | |
| 183 | ✗ | return -1; | |
| 184 | } | ||
| 185 | 10 | *result = (int)val; | |
| 186 | 10 | return 0; | |
| 187 | } | ||
| 188 | |||
| 189 | 200 | static int get_yaml_int_in_range_ext(yaml_node_t *node, const char *field, | |
| 190 | int min, int max, int *result, int line) | ||
| 191 | { | ||
| 192 |
2/4✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 200 times.
|
200 | if (!node || node->type != YAML_SCALAR_NODE) { |
| 193 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): expected integer scalar\n", field, line); | |
| 194 | ✗ | return -1; | |
| 195 | } | ||
| 196 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 196 times.
|
200 | if (parse_int_in_range((const char *)node->data.scalar.value, min, max, result) != 0) { |
| 197 | 4 | fprintf(stderr, "Invalid '%s' (line %d): expected integer in range [%d, %d]\n", | |
| 198 | field, line, min, max); | ||
| 199 | 4 | return -1; | |
| 200 | } | ||
| 201 | 196 | return 0; | |
| 202 | } | ||
| 203 | |||
| 204 | 200 | static int get_yaml_int_in_range(yaml_node_t *node, const char *field, | |
| 205 | int min, int max, int *result) | ||
| 206 | { | ||
| 207 | 200 | return get_yaml_int_in_range_ext(node, field, min, max, result, get_node_line(node)); | |
| 208 | } | ||
| 209 | |||
| 210 | 87 | static int get_yaml_size_in_range_ext(yaml_node_t *node, const char *field, | |
| 211 | size_t min, size_t max, size_t *result, int line) | ||
| 212 | { | ||
| 213 |
2/4✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 87 times.
|
87 | if (!node || node->type != YAML_SCALAR_NODE) { |
| 214 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): expected integer scalar\n", field, line); | |
| 215 | ✗ | return -1; | |
| 216 | } | ||
| 217 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
|
87 | if (parse_size_in_range((const char *)node->data.scalar.value, min, max, result) != 0) { |
| 218 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): expected integer in range [%zu, %zu]\n", | |
| 219 | field, line, min, max); | ||
| 220 | ✗ | return -1; | |
| 221 | } | ||
| 222 | 87 | return 0; | |
| 223 | } | ||
| 224 | |||
| 225 | 87 | static int get_yaml_size_in_range(yaml_node_t *node, const char *field, | |
| 226 | size_t min, size_t max, size_t *result) | ||
| 227 | { | ||
| 228 | 87 | return get_yaml_size_in_range_ext(node, field, min, max, result, get_node_line(node)); | |
| 229 | } | ||
| 230 | |||
| 231 | 99 | static int get_yaml_bool_ext(yaml_node_t *node, const char *field, int *result, int line) | |
| 232 | { | ||
| 233 | const char *value; | ||
| 234 | |||
| 235 |
2/4✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 99 times.
|
99 | if (!node || node->type != YAML_SCALAR_NODE) { |
| 236 | ✗ | fprintf(stderr, "Invalid '%s' (line %d): expected boolean scalar\n", field, line); | |
| 237 | ✗ | return -1; | |
| 238 | } | ||
| 239 | |||
| 240 | 99 | value = (const char *)node->data.scalar.value; | |
| 241 |
6/6✓ Branch 0 taken 36 times.
✓ Branch 1 taken 63 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 13 times.
|
99 | if (strcasecmp(value, "true") == 0 || strcmp(value, "1") == 0 || strcasecmp(value, "yes") == 0) { |
| 242 | 86 | *result = 1; | |
| 243 | 86 | return 0; | |
| 244 | } | ||
| 245 |
5/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
13 | if (strcasecmp(value, "false") == 0 || strcmp(value, "0") == 0 || strcasecmp(value, "no") == 0) { |
| 246 | 12 | *result = 0; | |
| 247 | 12 | return 0; | |
| 248 | } | ||
| 249 | |||
| 250 | 1 | fprintf(stderr, "Invalid '%s' (line %d): expected true/false\n", field, line); | |
| 251 | 1 | return -1; | |
| 252 | } | ||
| 253 | |||
| 254 | 99 | static int get_yaml_bool(yaml_node_t *node, const char *field, int *result) | |
| 255 | { | ||
| 256 | 99 | return get_yaml_bool_ext(node, field, result, get_node_line(node)); | |
| 257 | } | ||
| 258 | |||
| 259 | 31 | static int parse_log_level(const char *value, LogLevel *level) | |
| 260 | { | ||
| 261 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 10 times.
|
31 | if (strcasecmp(value, "debug") == 0) |
| 262 | 21 | *level = LOG_LEVEL_DEBUG; | |
| 263 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
|
10 | else if (strcasecmp(value, "info") == 0) |
| 264 | 2 | *level = LOG_LEVEL_INFO; | |
| 265 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
|
8 | else if (strcasecmp(value, "warn") == 0) |
| 266 | 1 | *level = LOG_LEVEL_WARN; | |
| 267 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | else if (strcasecmp(value, "error") == 0) |
| 268 | 7 | *level = LOG_LEVEL_ERROR; | |
| 269 | else | ||
| 270 | ✗ | return -1; | |
| 271 | 31 | return 0; | |
| 272 | } | ||
| 273 | |||
| 274 | 31 | static int parse_log_format(const char *value, LogFormat *format) | |
| 275 | { | ||
| 276 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1 times.
|
31 | if (strcasecmp(value, "plain") == 0) |
| 277 | 30 | *format = LOG_FORMAT_PLAIN; | |
| 278 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | else if (strcasecmp(value, "json") == 0) |
| 279 | 1 | *format = LOG_FORMAT_JSON; | |
| 280 | else | ||
| 281 | ✗ | return -1; | |
| 282 | 31 | return 0; | |
| 283 | } | ||
| 284 | |||
| 285 | 31 | static int parse_appender_flags(yaml_document_t *doc, yaml_node_t *node, LoggingConfig *log_cfg) | |
| 286 | { | ||
| 287 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (!node) |
| 288 | ✗ | return 0; | |
| 289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (node->type != YAML_SEQUENCE_NODE) { |
| 290 | ✗ | fprintf(stderr, "Invalid 'logging.appender_flags': expected sequence\n"); | |
| 291 | ✗ | return -1; | |
| 292 | } | ||
| 293 | |||
| 294 | 31 | log_cfg->appender_flags = 0; | |
| 295 | 31 | for (yaml_node_item_t *item = node->data.sequence.items.start; | |
| 296 |
2/2✓ Branch 0 taken 52 times.
✓ Branch 1 taken 31 times.
|
83 | item < node->data.sequence.items.top; item++) { |
| 297 | 52 | yaml_node_t *flag_node = yaml_document_get_node(doc, *item); | |
| 298 |
2/4✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 52 times.
|
52 | if (!flag_node || flag_node->type != YAML_SCALAR_NODE) { |
| 299 | ✗ | fprintf(stderr, "Invalid 'logging.appender_flags': entries must be scalars\n"); | |
| 300 | ✗ | return -1; | |
| 301 | } | ||
| 302 | 52 | const char *flag = (const char *)flag_node->data.scalar.value; | |
| 303 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 30 times.
|
52 | if (strcasecmp(flag, "file") == 0) |
| 304 | 22 | log_cfg->appender_flags |= APPENDER_FILE; | |
| 305 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | else if (strcasecmp(flag, "console") == 0) |
| 306 | 30 | log_cfg->appender_flags |= APPENDER_CONSOLE; | |
| 307 | else { | ||
| 308 | ✗ | fprintf(stderr, "Invalid appender flag '%s': expected 'file' or 'console'\n", flag); | |
| 309 | ✗ | return -1; | |
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (log_cfg->appender_flags == 0) { |
| 314 | ✗ | fprintf(stderr, "Invalid 'logging.appender_flags': at least one appender required\n"); | |
| 315 | ✗ | return -1; | |
| 316 | } | ||
| 317 | 31 | return 0; | |
| 318 | } | ||
| 319 | |||
| 320 | 51 | static int parse_health_check_config(yaml_document_t *doc, yaml_node_t *node, HealthCheckConfig *hc) | |
| 321 | { | ||
| 322 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
51 | if (!node || node->type != YAML_MAPPING_NODE) { |
| 323 | 51 | return 0; // Optional, missing is OK | |
| 324 | } | ||
| 325 | |||
| 326 | ✗ | hc->enabled = false; | |
| 327 | ✗ | snprintf(hc->path, sizeof(hc->path), "/health"); | |
| 328 | ✗ | hc->interval_seconds = 10; | |
| 329 | ✗ | hc->timeout_seconds = 5; | |
| 330 | ✗ | hc->unhealthy_threshold = 3; | |
| 331 | ✗ | hc->healthy_threshold = 2; | |
| 332 | |||
| 333 | yaml_node_t *field; | ||
| 334 | |||
| 335 | ✗ | field = find_yaml_node(doc, node, "enabled"); | |
| 336 | ✗ | if (field) { | |
| 337 | int val; | ||
| 338 | ✗ | if (get_yaml_bool(field, "health_check.enabled", &val) == 0) { | |
| 339 | ✗ | hc->enabled = (bool)val; | |
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | ✗ | field = find_yaml_node(doc, node, "path"); | |
| 344 | ✗ | if (field) { | |
| 345 | ✗ | get_yaml_string(field, "health_check.path", hc->path, sizeof(hc->path)); | |
| 346 | } | ||
| 347 | |||
| 348 | ✗ | field = find_yaml_node(doc, node, "interval_seconds"); | |
| 349 | ✗ | if (field) { | |
| 350 | int val; | ||
| 351 | ✗ | if (get_yaml_int(field, "health_check.interval_seconds", &val) == 0) { | |
| 352 | ✗ | hc->interval_seconds = val; | |
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | ✗ | field = find_yaml_node(doc, node, "timeout_seconds"); | |
| 357 | ✗ | if (field) { | |
| 358 | int val; | ||
| 359 | ✗ | if (get_yaml_int(field, "health_check.timeout_seconds", &val) == 0) { | |
| 360 | ✗ | hc->timeout_seconds = val; | |
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | ✗ | field = find_yaml_node(doc, node, "unhealthy_threshold"); | |
| 365 | ✗ | if (field) { | |
| 366 | int val; | ||
| 367 | ✗ | if (get_yaml_int(field, "health_check.unhealthy_threshold", &val) == 0) { | |
| 368 | ✗ | hc->unhealthy_threshold = val; | |
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | ✗ | field = find_yaml_node(doc, node, "healthy_threshold"); | |
| 373 | ✗ | if (field) { | |
| 374 | int val; | ||
| 375 | ✗ | if (get_yaml_int(field, "health_check.healthy_threshold", &val) == 0) { | |
| 376 | ✗ | hc->healthy_threshold = val; | |
| 377 | } | ||
| 378 | } | ||
| 379 | |||
| 380 | ✗ | return 0; | |
| 381 | } | ||
| 382 | |||
| 383 | 51 | static int parse_connection_pool_config(yaml_document_t *doc, yaml_node_t *node, ConnectionPoolConfig *cp) | |
| 384 | { | ||
| 385 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
51 | if (!node || node->type != YAML_MAPPING_NODE) { |
| 386 | 51 | cp->size = BACKEND_POOL_DEFAULT_SIZE; | |
| 387 | 51 | cp->idle_timeout_seconds = BACKEND_POOL_IDLE_TIMEOUT_SEC; | |
| 388 | 51 | return 0; | |
| 389 | } | ||
| 390 | |||
| 391 | ✗ | cp->size = BACKEND_POOL_DEFAULT_SIZE; | |
| 392 | ✗ | cp->idle_timeout_seconds = BACKEND_POOL_IDLE_TIMEOUT_SEC; | |
| 393 | |||
| 394 | yaml_node_t *field; | ||
| 395 | |||
| 396 | ✗ | field = find_yaml_node(doc, node, "size"); | |
| 397 | ✗ | if (field) { | |
| 398 | int val; | ||
| 399 | ✗ | if (get_yaml_int(field, "connection_pool.size", &val) == 0) { | |
| 400 | ✗ | cp->size = val; | |
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | ✗ | field = find_yaml_node(doc, node, "idle_timeout_seconds"); | |
| 405 | ✗ | if (field) { | |
| 406 | int val; | ||
| 407 | ✗ | if (get_yaml_int(field, "connection_pool.idle_timeout_seconds", &val) == 0) { | |
| 408 | ✗ | cp->idle_timeout_seconds = val; | |
| 409 | } | ||
| 410 | } | ||
| 411 | |||
| 412 | ✗ | return 0; | |
| 413 | } | ||
| 414 | |||
| 415 | 51 | static int parse_circuit_breaker_config(yaml_document_t *doc, yaml_node_t *node, CircuitBreakerConfig *cb) | |
| 416 | { | ||
| 417 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
51 | if (!node || node->type != YAML_MAPPING_NODE) { |
| 418 | 51 | cb->enabled = false; | |
| 419 | 51 | cb->failure_threshold = 5; | |
| 420 | 51 | cb->recovery_timeout_seconds = 30; | |
| 421 | 51 | return 0; | |
| 422 | } | ||
| 423 | |||
| 424 | ✗ | cb->enabled = false; | |
| 425 | ✗ | cb->failure_threshold = 5; | |
| 426 | ✗ | cb->recovery_timeout_seconds = 30; | |
| 427 | |||
| 428 | yaml_node_t *field; | ||
| 429 | |||
| 430 | ✗ | field = find_yaml_node(doc, node, "enabled"); | |
| 431 | ✗ | if (field) { | |
| 432 | int val; | ||
| 433 | ✗ | if (get_yaml_bool(field, "circuit_breaker.enabled", &val) == 0) { | |
| 434 | ✗ | cb->enabled = (bool)val; | |
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | ✗ | field = find_yaml_node(doc, node, "failure_threshold"); | |
| 439 | ✗ | if (field) { | |
| 440 | int val; | ||
| 441 | ✗ | if (get_yaml_int(field, "circuit_breaker.failure_threshold", &val) == 0) { | |
| 442 | ✗ | cb->failure_threshold = val; | |
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | ✗ | field = find_yaml_node(doc, node, "recovery_timeout_seconds"); | |
| 447 | ✗ | if (field) { | |
| 448 | int val; | ||
| 449 | ✗ | if (get_yaml_int(field, "circuit_breaker.recovery_timeout_seconds", &val) == 0) { | |
| 450 | ✗ | cb->recovery_timeout_seconds = val; | |
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | ✗ | return 0; | |
| 455 | } | ||
| 456 | |||
| 457 | 93 | static int parse_security_headers_config(yaml_document_t *doc, yaml_node_t *node, SecurityHeadersConfig *shc) | |
| 458 | { | ||
| 459 |
3/4✓ Branch 0 taken 10 times.
✓ Branch 1 taken 83 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
93 | if (!node || node->type != YAML_MAPPING_NODE) { |
| 460 | 83 | shc->enabled = false; | |
| 461 | 83 | shc->header_count = 0; | |
| 462 | 83 | return 0; | |
| 463 | } | ||
| 464 | |||
| 465 | 10 | shc->enabled = false; | |
| 466 | 10 | shc->header_count = 0; | |
| 467 | |||
| 468 | yaml_node_t *field; | ||
| 469 | |||
| 470 | 10 | field = find_yaml_node(doc, node, "enabled"); | |
| 471 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 472 | int val; | ||
| 473 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | if (get_yaml_bool(field, "security_headers.enabled", &val) == 0) { |
| 474 | 10 | shc->enabled = (bool)val; | |
| 475 | } | ||
| 476 | } | ||
| 477 | |||
| 478 | 10 | yaml_node_t *headers_node = find_yaml_node(doc, node, "headers"); | |
| 479 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | if (headers_node && headers_node->type == YAML_SEQUENCE_NODE) { |
| 480 | 10 | for (yaml_node_item_t *item = headers_node->data.sequence.items.start; | |
| 481 |
3/4✓ Branch 0 taken 60 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
|
70 | item < headers_node->data.sequence.items.top && shc->header_count < MAX_SECURITY_HEADERS; |
| 482 | 60 | item++) { | |
| 483 | 60 | yaml_node_t *header_node = yaml_document_get_node(doc, *item); | |
| 484 |
2/4✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
|
60 | if (!header_node || header_node->type != YAML_MAPPING_NODE) |
| 485 | ✗ | continue; | |
| 486 | |||
| 487 | 60 | yaml_node_t *name_node = find_yaml_node(doc, header_node, "name"); | |
| 488 | 60 | yaml_node_t *value_node = find_yaml_node(doc, header_node, "value"); | |
| 489 | |||
| 490 |
2/4✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
|
60 | if (name_node && value_node) { |
| 491 | 60 | SecurityHeader *header = &shc->headers[shc->header_count]; | |
| 492 |
2/4✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
✗ Branch 4 not taken.
|
120 | if (get_yaml_string(name_node, "security_headers[].name", header->name, sizeof(header->name)) == 0 && |
| 493 | 60 | get_yaml_string(value_node, "security_headers[].value", header->value, sizeof(header->value)) == 0) { | |
| 494 | 60 | shc->header_count++; | |
| 495 | } | ||
| 496 | } | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | 10 | return 0; | |
| 501 | } | ||
| 502 | |||
| 503 | 51 | static int parse_cors_config(yaml_document_t *doc, yaml_node_t *node, CORSConfig *cors) | |
| 504 | { | ||
| 505 |
3/4✓ Branch 0 taken 10 times.
✓ Branch 1 taken 41 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
51 | if (!node || node->type != YAML_MAPPING_NODE) { |
| 506 | 41 | cors->enabled = false; | |
| 507 | 41 | cors->allow_credentials = false; | |
| 508 | 41 | cors->max_age_seconds = 86400; | |
| 509 | 41 | return 0; | |
| 510 | } | ||
| 511 | |||
| 512 | 10 | cors->enabled = false; | |
| 513 | 10 | cors->allow_credentials = false; | |
| 514 | 10 | cors->max_age_seconds = 86400; | |
| 515 | |||
| 516 | yaml_node_t *field; | ||
| 517 | |||
| 518 | 10 | field = find_yaml_node(doc, node, "enabled"); | |
| 519 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 520 | int val; | ||
| 521 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | if (get_yaml_bool(field, "cors.enabled", &val) == 0) { |
| 522 | 10 | cors->enabled = (bool)val; | |
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | 10 | field = find_yaml_node(doc, node, "allow_origin"); | |
| 527 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 528 | 10 | get_yaml_string(field, "cors.allow_origin", cors->allow_origin, sizeof(cors->allow_origin)); | |
| 529 | } | ||
| 530 | |||
| 531 | 10 | field = find_yaml_node(doc, node, "allow_methods"); | |
| 532 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 533 | 10 | get_yaml_string(field, "cors.allow_methods", cors->allow_methods, sizeof(cors->allow_methods)); | |
| 534 | } | ||
| 535 | |||
| 536 | 10 | field = find_yaml_node(doc, node, "allow_headers"); | |
| 537 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 538 | 10 | get_yaml_string(field, "cors.allow_headers", cors->allow_headers, sizeof(cors->allow_headers)); | |
| 539 | } | ||
| 540 | |||
| 541 | 10 | field = find_yaml_node(doc, node, "allow_credentials"); | |
| 542 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 543 | int val; | ||
| 544 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | if (get_yaml_bool(field, "cors.allow_credentials", &val) == 0) { |
| 545 | 10 | cors->allow_credentials = (bool)val; | |
| 546 | } | ||
| 547 | } | ||
| 548 | |||
| 549 | 10 | field = find_yaml_node(doc, node, "max_age_seconds"); | |
| 550 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (field) { |
| 551 | int val; | ||
| 552 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | if (get_yaml_int(field, "cors.max_age_seconds", &val) == 0) { |
| 553 | 10 | cors->max_age_seconds = val; | |
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | 10 | return 0; | |
| 558 | } | ||
| 559 | |||
| 560 | 27 | int parse_backend_url(const char *backend, char *host, size_t host_size, int *port) | |
| 561 | { | ||
| 562 | char temp_host[256]; | ||
| 563 | int temp_port; | ||
| 564 | char trailing; | ||
| 565 | |||
| 566 |
2/4✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
|
27 | if (!backend || backend[0] == '\0') |
| 567 | ✗ | return -1; | |
| 568 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
|
27 | if (sscanf(backend, "%255[^:]:%d%c", temp_host, &temp_port, &trailing) != 2) |
| 569 | 1 | return -1; | |
| 570 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (temp_host[0] == '\0') |
| 571 | ✗ | return -1; | |
| 572 |
2/4✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
|
26 | if (temp_port < 1 || temp_port > 65535) |
| 573 | ✗ | return -1; | |
| 574 | |||
| 575 | 26 | snprintf(host, host_size, "%s", temp_host); | |
| 576 | 26 | *port = temp_port; | |
| 577 | 26 | return 0; | |
| 578 | } | ||
| 579 | |||
| 580 | 27 | static int validate_backend(const char *backend) | |
| 581 | { | ||
| 582 | char host[64]; | ||
| 583 | int port; | ||
| 584 | |||
| 585 | 27 | return parse_backend_url(backend, host, sizeof(host), &port); | |
| 586 | } | ||
| 587 | |||
| 588 | 42 | static int validate_routes(const ServerConfig *config) | |
| 589 | { | ||
| 590 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 39 times.
|
90 | for (int i = 0; i < config->route_count; i++) { |
| 591 | 51 | const Route *route = &config->routes[i]; | |
| 592 | |||
| 593 |
2/4✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
|
51 | if (route->path[0] == '\0' || route->path[0] != '/') { |
| 594 | ✗ | fprintf(stderr, "Invalid routes[%d].path: must start with '/'\n", i); | |
| 595 | ✗ | return -1; | |
| 596 | } | ||
| 597 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (route->technology[0] == '\0') { |
| 598 | ✗ | fprintf(stderr, "Invalid routes[%d].technology: required\n", i); | |
| 599 | ✗ | return -1; | |
| 600 | } | ||
| 601 | |||
| 602 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 28 times.
|
51 | if (strcmp(route->technology, "static") == 0) { |
| 603 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 22 times.
|
23 | if (route->document_root[0] == '\0') { |
| 604 | 1 | fprintf(stderr, "Invalid routes[%d].document_root: required for static route\n", i); | |
| 605 | 1 | return -1; | |
| 606 | } | ||
| 607 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 times.
|
28 | } else if (strcmp(route->technology, "reverse_proxy") == 0) { |
| 608 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 26 times.
|
27 | if (validate_backend(route->backend) != 0) { |
| 609 | 1 | fprintf(stderr, "Invalid routes[%d].backend: expected host:port\n", i); | |
| 610 | 1 | return -1; | |
| 611 | } | ||
| 612 | } else { | ||
| 613 | 1 | fprintf(stderr, "Invalid routes[%d].technology '%s': unsupported\n", i, route->technology); | |
| 614 | 1 | return -1; | |
| 615 | } | ||
| 616 | } | ||
| 617 | |||
| 618 | 39 | return 0; | |
| 619 | } | ||
| 620 | |||
| 621 | 31 | static int parse_server_section(ConfigParser *ctx, yaml_node_t *node) | |
| 622 | { | ||
| 623 | 31 | ctx->section_name = "server"; | |
| 624 | |||
| 625 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (node->type != YAML_MAPPING_NODE) { |
| 626 | ✗ | fprintf(stderr, "Invalid 'server' (line %d): expected mapping\n", | |
| 627 | get_node_line(node)); | ||
| 628 | ✗ | return -1; | |
| 629 | } | ||
| 630 | |||
| 631 |
3/4✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 29 times.
|
31 | PARSE_FIELD("port", get_yaml_int_in_range, 1, 65535, &ctx->config->port); |
| 632 |
2/4✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
|
29 | PARSE_FIELD("max_connections", get_yaml_int_in_range, 1, 1000000, &ctx->config->max_connections); |
| 633 |
1/4✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
29 | PARSE_FIELD("shutdown_timeout_seconds", get_yaml_int_in_range, 1, 300, &ctx->config->shutdown_timeout_seconds); |
| 634 |
3/4✓ Branch 1 taken 10 times.
✓ Branch 2 taken 19 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
29 | PARSE_FIELD("request_timeout_ms", get_yaml_int_in_range, 1000, 300000, &ctx->config->request_timeout_ms); |
| 635 |
3/4✓ Branch 1 taken 10 times.
✓ Branch 2 taken 19 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
29 | PARSE_FIELD("tls_handshake_timeout_ms", get_yaml_int_in_range, 1000, 60000, &ctx->config->tls_handshake_timeout_ms); |
| 636 |
3/4✓ Branch 1 taken 10 times.
✓ Branch 2 taken 19 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
29 | PARSE_FIELD("per_ip_connection_limit", get_yaml_int_in_range, 1, 10000, &ctx->config->per_ip_connection_limit); |
| 637 |
2/4✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
|
29 | PARSE_STRING("log_level", ctx->config->log_level, sizeof(ctx->config->log_level)); |
| 638 | |||
| 639 | 29 | return 0; | |
| 640 | } | ||
| 641 | |||
| 642 | 31 | static int parse_logging_section(ConfigParser *ctx, yaml_node_t *node) | |
| 643 | { | ||
| 644 | 31 | ctx->section_name = "logging"; | |
| 645 | |||
| 646 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (node->type != YAML_MAPPING_NODE) { |
| 647 | ✗ | fprintf(stderr, "Invalid 'logging' (line %d): expected mapping\n", | |
| 648 | get_node_line(node)); | ||
| 649 | ✗ | return -1; | |
| 650 | } | ||
| 651 | |||
| 652 |
3/4✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 30 times.
|
31 | PARSE_STRING("file", ctx->config->logging.file, sizeof(ctx->config->logging.file)); |
| 653 |
3/4✓ Branch 1 taken 29 times.
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
|
31 | PARSE_FIELD("buffer_size", get_yaml_size_in_range, 1, 1048576, &ctx->config->logging.buffer_size); |
| 654 | |||
| 655 | 31 | yaml_node_t *level_node = find_yaml_node(ctx->document, node, "level"); | |
| 656 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | if (level_node) { |
| 657 | char level_str[16]; | ||
| 658 | 31 | int line = get_node_line(level_node); | |
| 659 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
|
31 | if (get_yaml_string(level_node, "logging.level", level_str, sizeof(level_str)) != 0) { |
| 660 | ✗ | fprintf(stderr, " at line %d\n", line); | |
| 661 | ✗ | return -1; | |
| 662 | } | ||
| 663 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
|
31 | if (parse_log_level(level_str, &ctx->config->logging.level) != 0) { |
| 664 | ✗ | fprintf(stderr, "Invalid 'logging.level' (line %d): expected debug/info/warn/error\n", line); | |
| 665 | ✗ | return -1; | |
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | 31 | yaml_node_t *format_node = find_yaml_node(ctx->document, node, "format"); | |
| 670 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | if (format_node) { |
| 671 | char format_str[16]; | ||
| 672 | 31 | int line = get_node_line(format_node); | |
| 673 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
|
31 | if (get_yaml_string(format_node, "logging.format", format_str, sizeof(format_str)) != 0) { |
| 674 | ✗ | fprintf(stderr, " at line %d\n", line); | |
| 675 | ✗ | return -1; | |
| 676 | } | ||
| 677 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
|
31 | if (parse_log_format(format_str, &ctx->config->logging.format) != 0) { |
| 678 | ✗ | fprintf(stderr, "Invalid 'logging.format' (line %d): expected plain/json\n", line); | |
| 679 | ✗ | return -1; | |
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 |
3/4✓ Branch 1 taken 29 times.
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
|
31 | PARSE_FIELD("buffer_size", get_yaml_size_in_range, 1, 1048576, &ctx->config->logging.buffer_size); |
| 684 |
3/4✓ Branch 1 taken 29 times.
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
|
31 | PARSE_FIELD("rollover_size", get_yaml_size_in_range, 0, 1099511627776ULL, &ctx->config->logging.rollover_size); |
| 685 |
3/4✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 30 times.
|
31 | PARSE_BOOL("rollover_daily", &ctx->config->logging.rollover_daily); |
| 686 | |||
| 687 | 31 | yaml_node_t *appender_flags_node = find_yaml_node(ctx->document, node, "appender_flags"); | |
| 688 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | if (appender_flags_node) { |
| 689 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
|
31 | if (parse_appender_flags(ctx->document, appender_flags_node, &ctx->config->logging) != 0) { |
| 690 | ✗ | fprintf(stderr, " at line %d\n", get_node_line(appender_flags_node)); | |
| 691 | ✗ | return -1; | |
| 692 | } | ||
| 693 | } | ||
| 694 | |||
| 695 | 31 | return 0; | |
| 696 | } | ||
| 697 | |||
| 698 | 45 | static int parse_ssl_section(ConfigParser *ctx, yaml_node_t *node) | |
| 699 | { | ||
| 700 | 45 | ctx->section_name = "ssl"; | |
| 701 | |||
| 702 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
|
45 | if (node->type != YAML_MAPPING_NODE) { |
| 703 | ✗ | fprintf(stderr, "Invalid 'ssl' (line %d): expected mapping\n", | |
| 704 | get_node_line(node)); | ||
| 705 | ✗ | return -1; | |
| 706 | } | ||
| 707 | |||
| 708 |
3/4✓ Branch 1 taken 44 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 44 times.
|
45 | PARSE_STRING("certificate", ctx->config->ssl.certificate, sizeof(ctx->config->ssl.certificate)); |
| 709 |
3/4✓ Branch 1 taken 44 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 44 times.
|
45 | PARSE_STRING("private_key", ctx->config->ssl.private_key, sizeof(ctx->config->ssl.private_key)); |
| 710 |
3/4✓ Branch 1 taken 18 times.
✓ Branch 2 taken 27 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
|
45 | PARSE_FIELD("session_cache_size", get_yaml_int_in_range, 1000, 1000000, &ctx->config->ssl.session_cache_size); |
| 711 |
3/4✓ Branch 1 taken 18 times.
✓ Branch 2 taken 27 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
|
45 | PARSE_FIELD("session_timeout", get_yaml_int_in_range, 60, 3600, &ctx->config->ssl.session_timeout); |
| 712 |
3/4✓ Branch 1 taken 11 times.
✓ Branch 2 taken 34 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
|
45 | PARSE_STRING("session_ticket_key", ctx->config->ssl.session_ticket_key, sizeof(ctx->config->ssl.session_ticket_key)); |
| 713 |
4/4✓ Branch 1 taken 19 times.
✓ Branch 2 taken 26 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 18 times.
|
45 | PARSE_FIELD("read_buffer_size", get_yaml_int_in_range, 4096, 65536, &ctx->config->ssl.read_buffer_size); |
| 714 |
4/4✓ Branch 1 taken 20 times.
✓ Branch 2 taken 24 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 19 times.
|
44 | PARSE_BOOL("enable_partial_write", &ctx->config->ssl.enable_partial_write); |
| 715 |
3/4✓ Branch 1 taken 19 times.
✓ Branch 2 taken 24 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 19 times.
|
43 | PARSE_BOOL("release_buffers", &ctx->config->ssl.release_buffers); |
| 716 | |||
| 717 | 43 | return 0; | |
| 718 | } | ||
| 719 | |||
| 720 | 19 | static int parse_http2_section(ConfigParser *ctx, yaml_node_t *node) | |
| 721 | { | ||
| 722 | 19 | ctx->section_name = "http2"; | |
| 723 | |||
| 724 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (node->type != YAML_MAPPING_NODE) { |
| 725 | ✗ | fprintf(stderr, "Invalid 'http2' (line %d): expected mapping\n", | |
| 726 | get_node_line(node)); | ||
| 727 | ✗ | return -1; | |
| 728 | } | ||
| 729 | |||
| 730 |
3/4✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 18 times.
|
19 | PARSE_FIELD("keepalive_timeout", get_yaml_int_in_range, 10, 300, &ctx->config->http2.keepalive_timeout); |
| 731 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
|
18 | PARSE_FIELD("max_requests_per_connection", get_yaml_int_in_range, 1, 100000, &ctx->config->http2.max_requests_per_connection); |
| 732 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
|
18 | PARSE_FIELD("max_concurrent_streams", get_yaml_int_in_range, 1, 1000, &ctx->config->http2.max_concurrent_streams); |
| 733 | |||
| 734 | 18 | return 0; | |
| 735 | } | ||
| 736 | |||
| 737 | 51 | static int parse_route_entry(ConfigParser *ctx, yaml_node_t *route_node) | |
| 738 | { | ||
| 739 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (ctx->config->route_count >= MAX_ROUTES) { |
| 740 | ✗ | fprintf(stderr, "Too many routes: maximum supported is %d\n", MAX_ROUTES); | |
| 741 | ✗ | return -1; | |
| 742 | } | ||
| 743 | |||
| 744 | 51 | Route *route = &ctx->config->routes[ctx->config->route_count]; | |
| 745 | 51 | memset(route, 0, sizeof(*route)); | |
| 746 | |||
| 747 | 51 | yaml_node_t *route_field = find_yaml_node(ctx->document, route_node, "path"); | |
| 748 |
2/4✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
|
102 | if (!route_field || |
| 749 | 51 | get_yaml_string(route_field, "routes[].path", | |
| 750 | 51 | route->path, sizeof(route->path)) != 0) { | |
| 751 | ✗ | fprintf(stderr, "Invalid route: missing or invalid 'path'\n"); | |
| 752 | ✗ | return -1; | |
| 753 | } | ||
| 754 | |||
| 755 | 51 | route_field = find_yaml_node(ctx->document, route_node, "technology"); | |
| 756 |
2/4✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
|
102 | if (!route_field || |
| 757 | 51 | get_yaml_string(route_field, "routes[].technology", | |
| 758 | 51 | route->technology, sizeof(route->technology)) != 0) { | |
| 759 | ✗ | fprintf(stderr, "Invalid route: missing or invalid 'technology'\n"); | |
| 760 | ✗ | return -1; | |
| 761 | } | ||
| 762 | |||
| 763 | 51 | route_field = find_yaml_node(ctx->document, route_node, "document_root"); | |
| 764 |
3/4✓ Branch 0 taken 22 times.
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
73 | if (route_field && |
| 765 | 22 | get_yaml_string(route_field, "routes[].document_root", | |
| 766 | 22 | route->document_root, sizeof(route->document_root)) != 0) | |
| 767 | ✗ | return -1; | |
| 768 | |||
| 769 | 51 | route_field = find_yaml_node(ctx->document, route_node, "backend"); | |
| 770 |
3/4✓ Branch 0 taken 28 times.
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
|
79 | if (route_field && |
| 771 | 28 | get_yaml_string(route_field, "routes[].backend", | |
| 772 | 28 | route->backend, sizeof(route->backend)) != 0) | |
| 773 | ✗ | return -1; | |
| 774 | |||
| 775 | // Parse HTTP/2 reverse proxy options | ||
| 776 | 51 | route->http2_enabled = false; | |
| 777 | 51 | route->tls_enabled = true; | |
| 778 | 51 | route->tls_verify = false; | |
| 779 | |||
| 780 | 51 | route_field = find_yaml_node(ctx->document, route_node, "http2_enabled"); | |
| 781 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (route_field) { |
| 782 | int val; | ||
| 783 | ✗ | if (get_yaml_bool(route_field, "routes[].http2_enabled", &val) == 0) { | |
| 784 | ✗ | route->http2_enabled = (bool)val; | |
| 785 | } | ||
| 786 | } | ||
| 787 | |||
| 788 | 51 | route_field = find_yaml_node(ctx->document, route_node, "tls_enabled"); | |
| 789 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (route_field) { |
| 790 | int val; | ||
| 791 | ✗ | if (get_yaml_bool(route_field, "routes[].tls_enabled", &val) == 0) { | |
| 792 | ✗ | route->tls_enabled = (bool)val; | |
| 793 | } | ||
| 794 | } | ||
| 795 | |||
| 796 | 51 | route_field = find_yaml_node(ctx->document, route_node, "tls_verify"); | |
| 797 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (route_field) { |
| 798 | int val; | ||
| 799 | ✗ | if (get_yaml_bool(route_field, "routes[].tls_verify", &val) == 0) { | |
| 800 | ✗ | route->tls_verify = (bool)val; | |
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 | // Parse nested config sections | ||
| 805 | 51 | yaml_node_t *hc_node = find_yaml_node(ctx->document, route_node, "health_check"); | |
| 806 | 51 | parse_health_check_config(ctx->document, hc_node, &route->health_check); | |
| 807 | |||
| 808 | 51 | yaml_node_t *cp_node = find_yaml_node(ctx->document, route_node, "connection_pool"); | |
| 809 | 51 | parse_connection_pool_config(ctx->document, cp_node, &route->connection_pool); | |
| 810 | |||
| 811 | 51 | yaml_node_t *cb_node = find_yaml_node(ctx->document, route_node, "circuit_breaker"); | |
| 812 | 51 | parse_circuit_breaker_config(ctx->document, cb_node, &route->circuit_breaker); | |
| 813 | |||
| 814 | 51 | yaml_node_t *sh_node = find_yaml_node(ctx->document, route_node, "security_headers"); | |
| 815 | 51 | parse_security_headers_config(ctx->document, sh_node, &route->security_headers); | |
| 816 | |||
| 817 | 51 | yaml_node_t *cors_node = find_yaml_node(ctx->document, route_node, "cors"); | |
| 818 | 51 | parse_cors_config(ctx->document, cors_node, &route->cors); | |
| 819 | |||
| 820 | 51 | route->inherit_global_headers = true; | |
| 821 | 51 | yaml_node_t *inherit_node = find_yaml_node(ctx->document, route_node, "inherit_global_headers"); | |
| 822 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (inherit_node) { |
| 823 | int val; | ||
| 824 | ✗ | if (get_yaml_bool(inherit_node, "routes[].inherit_global_headers", &val) == 0) { | |
| 825 | ✗ | route->inherit_global_headers = (bool)val; | |
| 826 | } | ||
| 827 | } | ||
| 828 | |||
| 829 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 29 times.
|
51 | if (route->document_root[0] != '\0') { |
| 830 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | if (realpath(route->document_root, route->document_root_real)) { |
| 831 | 22 | route->document_root_resolved = 1; | |
| 832 | } else { | ||
| 833 | ✗ | route->document_root_resolved = 0; | |
| 834 | ✗ | strncpy(route->document_root_real, route->document_root, | |
| 835 | sizeof(route->document_root_real) - 1); | ||
| 836 | ✗ | route->document_root_real[sizeof(route->document_root_real) - 1] = '\0'; | |
| 837 | } | ||
| 838 | } | ||
| 839 | |||
| 840 | 51 | ctx->config->route_count++; | |
| 841 | 51 | return 0; | |
| 842 | } | ||
| 843 | |||
| 844 | 32 | static int parse_routes_section(ConfigParser *ctx, yaml_node_t *node) | |
| 845 | { | ||
| 846 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (node->type != YAML_SEQUENCE_NODE) { |
| 847 | ✗ | fprintf(stderr, "Invalid 'routes' (line %d): expected sequence\n", | |
| 848 | get_node_line(node)); | ||
| 849 | ✗ | return -1; | |
| 850 | } | ||
| 851 | |||
| 852 | 32 | for (yaml_node_item_t *item = node->data.sequence.items.start; | |
| 853 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 32 times.
|
83 | item < node->data.sequence.items.top; item++) { |
| 854 | 51 | yaml_node_t *route_node = yaml_document_get_node(ctx->document, *item); | |
| 855 |
2/4✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
|
51 | if (!route_node || route_node->type != YAML_MAPPING_NODE) { |
| 856 | ✗ | fprintf(stderr, "Invalid route entry (line %d): expected mapping\n", | |
| 857 | get_node_line(route_node)); | ||
| 858 | ✗ | return -1; | |
| 859 | } | ||
| 860 | |||
| 861 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
|
51 | if (parse_route_entry(ctx, route_node) != 0) |
| 862 | ✗ | return -1; | |
| 863 | } | ||
| 864 | |||
| 865 | 32 | return 0; | |
| 866 | } | ||
| 867 | |||
| 868 | 42 | static int validate_config(const ServerConfig *config) | |
| 869 | { | ||
| 870 |
2/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
|
42 | if (config->ssl.certificate[0] == '\0' || config->ssl.private_key[0] == '\0') { |
| 871 | ✗ | fprintf(stderr, "Invalid SSL config: certificate and private_key are required\n"); | |
| 872 | ✗ | return -1; | |
| 873 | } | ||
| 874 | |||
| 875 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (config->logging.appender_flags == 0) { |
| 876 | ✗ | fprintf(stderr, "Invalid logging config: at least one appender must be enabled\n"); | |
| 877 | ✗ | return -1; | |
| 878 | } | ||
| 879 | |||
| 880 |
3/4✓ Branch 0 taken 33 times.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
|
42 | if ((config->logging.appender_flags & APPENDER_FILE) && config->logging.file[0] == '\0') { |
| 881 | ✗ | fprintf(stderr, "Invalid logging config: file appender requires 'logging.file'\n"); | |
| 882 | ✗ | return -1; | |
| 883 | } | ||
| 884 | |||
| 885 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 39 times.
|
42 | if (validate_routes(config) != 0) |
| 886 | 3 | return -1; | |
| 887 | |||
| 888 | 39 | return 0; | |
| 889 | } | ||
| 890 | |||
| 891 | 47 | int load_config(ServerConfig *config, const char *file_path) | |
| 892 | { | ||
| 893 | 47 | FILE *fh = NULL; | |
| 894 | yaml_parser_t parser; | ||
| 895 | yaml_document_t document; | ||
| 896 | yaml_node_t *root; | ||
| 897 | ConfigParser ctx; | ||
| 898 | 47 | int parser_ready = 0; | |
| 899 | 47 | int document_ready = 0; | |
| 900 | 47 | int rc = -1; | |
| 901 | |||
| 902 |
2/4✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 47 times.
|
47 | if (!config || !file_path) { |
| 903 | ✗ | fprintf(stderr, "Invalid input: config and file_path are required\n"); | |
| 904 | ✗ | return -1; | |
| 905 | } | ||
| 906 | |||
| 907 | 47 | set_config_defaults(config); | |
| 908 | |||
| 909 | 47 | fh = fopen(file_path, "rb"); | |
| 910 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
|
47 | if (!fh) { |
| 911 | ✗ | fprintf(stderr, "Error opening configuration file: %s\n", file_path); | |
| 912 | ✗ | return -1; | |
| 913 | } | ||
| 914 | |||
| 915 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
|
47 | if (!yaml_parser_initialize(&parser)) { |
| 916 | ✗ | fprintf(stderr, "Unable to initialize YAML parser\n"); | |
| 917 | ✗ | goto cleanup; | |
| 918 | } | ||
| 919 | 47 | parser_ready = 1; | |
| 920 | 47 | yaml_parser_set_input_file(&parser, fh); | |
| 921 | |||
| 922 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
|
47 | if (!yaml_parser_load(&parser, &document)) { |
| 923 | ✗ | fprintf(stderr, "Error parsing YAML configuration file\n"); | |
| 924 | ✗ | goto cleanup; | |
| 925 | } | ||
| 926 | 47 | document_ready = 1; | |
| 927 | |||
| 928 | 47 | root = yaml_document_get_root_node(&document); | |
| 929 |
2/4✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 47 times.
|
47 | if (!root || root->type != YAML_MAPPING_NODE) { |
| 930 | ✗ | fprintf(stderr, "YAML document is not a mapping\n"); | |
| 931 | ✗ | goto cleanup; | |
| 932 | } | ||
| 933 | |||
| 934 | 47 | ctx.document = &document; | |
| 935 | 47 | ctx.root = root; | |
| 936 | 47 | ctx.config = config; | |
| 937 | |||
| 938 | 47 | yaml_node_t *node = find_yaml_node(&document, root, "server"); | |
| 939 |
4/4✓ Branch 0 taken 31 times.
✓ Branch 1 taken 16 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 29 times.
|
47 | if (node && parse_server_section(&ctx, node) != 0) |
| 940 | 2 | goto cleanup; | |
| 941 | |||
| 942 | 45 | node = find_yaml_node(&document, root, "logging"); | |
| 943 |
3/4✓ Branch 0 taken 31 times.
✓ Branch 1 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
|
45 | if (node && parse_logging_section(&ctx, node) != 0) |
| 944 | ✗ | goto cleanup; | |
| 945 | |||
| 946 | 45 | node = find_yaml_node(&document, root, "ssl"); | |
| 947 |
3/4✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 43 times.
|
45 | if (node && parse_ssl_section(&ctx, node) != 0) |
| 948 | 2 | goto cleanup; | |
| 949 | |||
| 950 | 43 | node = find_yaml_node(&document, root, "http2"); | |
| 951 |
4/4✓ Branch 0 taken 19 times.
✓ Branch 1 taken 24 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 18 times.
|
43 | if (node && parse_http2_section(&ctx, node) != 0) |
| 952 | 1 | goto cleanup; | |
| 953 | |||
| 954 | 42 | node = find_yaml_node(&document, root, "routes"); | |
| 955 |
3/4✓ Branch 0 taken 32 times.
✓ Branch 1 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
|
42 | if (node && parse_routes_section(&ctx, node) != 0) |
| 956 | ✗ | goto cleanup; | |
| 957 | |||
| 958 | 42 | node = find_yaml_node(&document, root, "security_headers"); | |
| 959 | 42 | parse_security_headers_config(&document, node, &config->security_headers); | |
| 960 | |||
| 961 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 39 times.
|
42 | if (validate_config(config) != 0) |
| 962 | 3 | goto cleanup; | |
| 963 | |||
| 964 | 39 | rc = 0; | |
| 965 | |||
| 966 | 47 | cleanup: | |
| 967 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (document_ready) |
| 968 | 47 | yaml_document_delete(&document); | |
| 969 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (parser_ready) |
| 970 | 47 | yaml_parser_delete(&parser); | |
| 971 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | if (fh) |
| 972 | 47 | fclose(fh); | |
| 973 | 47 | return rc; | |
| 974 | } | ||
| 975 | |||
| 976 | 102 | static void apply_int_env_override(const char *env_name, int *config_value, int min_val, int max_val, | |
| 977 | const char *unit, int scale) | ||
| 978 | { | ||
| 979 | 102 | const char *env_str = getenv(env_name); | |
| 980 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 6 times.
|
102 | if (!env_str) return; |
| 981 | |||
| 982 | char *endptr; | ||
| 983 | 6 | long value = strtol(env_str, &endptr, 10); | |
| 984 |
5/6✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 times.
|
6 | if (*endptr == '\0' && value >= min_val && value <= max_val) { |
| 985 | 4 | *config_value = (int)(value * scale); | |
| 986 | } else { | ||
| 987 | 2 | fprintf(stderr, "Warning: Invalid %s value '%s', using config value %d%s\n", | |
| 988 | env_name, env_str, *config_value, unit); | ||
| 989 | } | ||
| 990 | } | ||
| 991 | |||
| 992 | 51 | static void apply_string_env_override(const char *env_name, char *config_value, size_t max_len) | |
| 993 | { | ||
| 994 | 51 | const char *env_str = getenv(env_name); | |
| 995 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 7 times.
|
51 | if (!env_str) return; |
| 996 | |||
| 997 | 7 | strncpy(config_value, env_str, max_len - 1); | |
| 998 | 7 | config_value[max_len - 1] = '\0'; | |
| 999 | } | ||
| 1000 | |||
| 1001 | 17 | void apply_env_overrides(ServerConfig *config) | |
| 1002 | { | ||
| 1003 | 17 | apply_int_env_override("EMME_PORT", &config->port, 1, 65535, "", 1); | |
| 1004 | 17 | apply_string_env_override("EMME_LOG_LEVEL", config->log_level, sizeof(config->log_level)); | |
| 1005 | 17 | apply_int_env_override("EMME_SHUTDOWN_TIMEOUT", &config->shutdown_timeout_seconds, 1, 300, "", 1); | |
| 1006 | 17 | apply_int_env_override("EMME_MAX_CONNECTIONS", &config->max_connections, 1, 1000000, "", 1); | |
| 1007 | 17 | apply_string_env_override("EMME_SSL_CERT_PATH", config->ssl.certificate, sizeof(config->ssl.certificate)); | |
| 1008 | 17 | apply_string_env_override("EMME_SSL_KEY_PATH", config->ssl.private_key, sizeof(config->ssl.private_key)); | |
| 1009 | 17 | apply_int_env_override("EMME_REQUEST_TIMEOUT", &config->request_timeout_ms, 1, 300, "ms", 1000); | |
| 1010 | 17 | apply_int_env_override("EMME_TLS_HANDSHAKE_TIMEOUT", &config->tls_handshake_timeout_ms, 1, 60, "ms", 1000); | |
| 1011 | 17 | apply_int_env_override("EMME_PER_IP_CONNECTION_LIMIT", &config->per_ip_connection_limit, 1, 10000, "", 1); | |
| 1012 | 17 | } | |
| 1013 |