Coverage Report

Created: 2026-01-21 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fluent-bit/plugins/out_http/http_conf.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*  Fluent Bit
4
 *  ==========
5
 *  Copyright (C) 2015-2026 The Fluent Bit Authors
6
 *
7
 *  Licensed under the Apache License, Version 2.0 (the "License");
8
 *  you may not use this file except in compliance with the License.
9
 *  You may obtain a copy of the License at
10
 *
11
 *      http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 *  Unless required by applicable law or agreed to in writing, software
14
 *  distributed under the License is distributed on an "AS IS" BASIS,
15
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 *  See the License for the specific language governing permissions and
17
 *  limitations under the License.
18
 */
19
20
#include <fluent-bit/flb_output_plugin.h>
21
#include <fluent-bit/flb_utils.h>
22
#include <fluent-bit/flb_pack.h>
23
#include <fluent-bit/flb_sds.h>
24
#include <fluent-bit/flb_kv.h>
25
#include <fluent-bit/flb_http_client.h>
26
#include <fluent-bit/flb_record_accessor.h>
27
#include <fluent-bit/flb_oauth2.h>
28
29
#ifdef FLB_HAVE_SIGNV4
30
#ifdef FLB_HAVE_AWS
31
#include <fluent-bit/flb_aws_credentials.h>
32
#endif
33
#endif
34
#include "http.h"
35
#include "http_conf.h"
36
37
struct flb_out_http *flb_http_conf_create(struct flb_output_instance *ins,
38
                                          struct flb_config *config)
39
0
{
40
0
    int ret;
41
0
    int ulen;
42
0
    int io_flags = 0;
43
0
    char *protocol = NULL;
44
0
    char *host = NULL;
45
0
    char *port = NULL;
46
0
    char *uri = NULL;
47
0
    char *tmp_uri = NULL;
48
0
    const char *tmp;
49
0
    struct flb_upstream *upstream;
50
0
    struct flb_out_http *ctx = NULL;
51
52
    /* Allocate plugin context */
53
0
    ctx = flb_calloc(1, sizeof(struct flb_out_http));
54
0
    if (!ctx) {
55
0
        flb_errno();
56
0
        return NULL;
57
0
    }
58
0
    ctx->ins = ins;
59
0
    ctx->oauth2_config.enabled = FLB_FALSE;
60
0
    ctx->oauth2_config.auth_method = FLB_OAUTH2_AUTH_METHOD_BASIC;
61
0
    ctx->oauth2_config.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS;
62
0
    ctx->oauth2_ctx = NULL;
63
0
    ctx->oauth2_auth_method = NULL;
64
65
0
    ret = flb_output_config_map_set(ins, (void *) ctx);
66
0
    if (ret == -1) {
67
0
        flb_free(ctx);
68
0
        return NULL;
69
0
    }
70
71
    /* Apply OAuth2 config map properties if any */
72
0
    if (ins->oauth2_config_map && mk_list_size(&ins->oauth2_properties) > 0) {
73
0
        ret = flb_config_map_set(&ins->oauth2_properties, ins->oauth2_config_map,
74
0
                                 &ctx->oauth2_config);
75
0
        if (ret == -1) {
76
0
            flb_free(ctx);
77
0
            return NULL;
78
0
        }
79
80
        /* Handle oauth2.auth_method separately since it's stored in a different field */
81
0
        tmp = flb_kv_get_key_value("oauth2.auth_method", &ins->oauth2_properties);
82
0
        if (tmp) {
83
            /* Store pointer directly - config map owns this string and will free it */
84
0
            ctx->oauth2_auth_method = (flb_sds_t) tmp;
85
0
        }
86
0
    }
87
88
0
    if (ctx->headers_key && !ctx->body_key) {
89
0
        flb_plg_error(ctx->ins, "when setting headers_key, body_key is also required");
90
0
        flb_free(ctx);
91
0
        return NULL;
92
0
    }
93
94
0
    if (ctx->body_key && !ctx->headers_key) {
95
0
        flb_plg_error(ctx->ins, "when setting body_key, headers_key is also required");
96
0
        flb_free(ctx);
97
0
        return NULL;
98
0
    }
99
100
0
    if (ctx->body_key && ctx->headers_key) {
101
0
        ctx->body_ra = flb_ra_create(ctx->body_key, FLB_FALSE);
102
0
        if (!ctx->body_ra) {
103
0
            flb_plg_error(ctx->ins, "failed to allocate body record accessor");
104
0
            flb_free(ctx);
105
0
            return NULL;
106
0
        }
107
108
0
        ctx->headers_ra = flb_ra_create(ctx->headers_key, FLB_FALSE);
109
0
        if (!ctx->headers_ra) {
110
0
            flb_plg_error(ctx->ins, "failed to allocate headers record accessor");
111
0
            flb_free(ctx);
112
0
            return NULL;
113
0
        }
114
0
    }
115
116
    /*
117
     * Check if a Proxy have been set, if so the Upstream manager will use
118
     * the Proxy end-point and then we let the HTTP client know about it, so
119
     * it can adjust the HTTP requests.
120
     */
121
0
    tmp = flb_output_get_property("proxy", ins);
122
0
    if (tmp) {
123
0
        ret = flb_utils_url_split(tmp, &protocol, &host, &port, &uri);
124
0
        if (ret == -1) {
125
0
            flb_plg_error(ctx->ins, "could not parse proxy parameter: '%s'", tmp);
126
0
            flb_free(ctx);
127
0
            return NULL;
128
0
        }
129
130
0
        ctx->proxy_host = host;
131
0
        ctx->proxy_port = atoi(port);
132
0
        ctx->proxy = tmp;
133
0
        flb_free(protocol);
134
0
        flb_free(port);
135
0
        flb_free(uri);
136
0
        uri = NULL;
137
0
    }
138
0
    else {
139
0
        flb_output_net_default("127.0.0.1", 80, ins);
140
0
    }
141
142
    /* Check if AWS SigV4 authentication is enabled */
143
0
#ifdef FLB_HAVE_SIGNV4
144
0
#ifdef FLB_HAVE_AWS
145
0
    if (ctx->has_aws_auth) {
146
0
        ctx->aws_service = flb_output_get_property(FLB_HTTP_AWS_CREDENTIAL_PREFIX
147
0
                                                   "service", ctx->ins);
148
0
        if (!ctx->aws_service) {
149
0
            flb_plg_error(ins, "aws_auth option requires " FLB_HTTP_AWS_CREDENTIAL_PREFIX
150
0
                          "service to be set");
151
0
            flb_free(ctx);
152
0
            return NULL;
153
0
        }
154
155
0
        ctx->aws_provider = flb_managed_chain_provider_create(
156
0
            ins,
157
0
            config,
158
0
            FLB_HTTP_AWS_CREDENTIAL_PREFIX,
159
0
            NULL,
160
0
            flb_aws_client_generator()
161
0
        );
162
0
        if (!ctx->aws_provider) {
163
0
            flb_plg_error(ins, "failed to create aws credential provider for sigv4 auth");
164
0
            flb_free(ctx);
165
0
            return NULL;
166
0
        }
167
168
        /* If managed provider creation succeeds, then region key is present */
169
0
        ctx->aws_region = flb_output_get_property(FLB_HTTP_AWS_CREDENTIAL_PREFIX
170
0
                                                  "region", ctx->ins);
171
0
    }
172
0
#endif /* !FLB_HAVE_AWS */
173
0
#endif /* !FLB_HAVE_SIGNV4 */
174
175
    /* Check if SSL/TLS is enabled */
176
0
#ifdef FLB_HAVE_TLS
177
0
    if (ins->use_tls == FLB_TRUE) {
178
0
        io_flags = FLB_IO_TLS;
179
0
    }
180
0
    else {
181
0
        io_flags = FLB_IO_TCP;
182
0
    }
183
#else
184
    io_flags = FLB_IO_TCP;
185
#endif
186
187
0
    if (ins->host.ipv6 == FLB_TRUE) {
188
0
        io_flags |= FLB_IO_IPV6;
189
0
    }
190
191
0
    if (ctx->proxy) {
192
0
        flb_plg_trace(ctx->ins, "Upstream Proxy=%s:%i",
193
0
                      ctx->proxy_host, ctx->proxy_port);
194
0
        upstream = flb_upstream_create(config,
195
0
                                       ctx->proxy_host,
196
0
                                       ctx->proxy_port,
197
0
                                       io_flags, ins->tls);
198
0
    }
199
0
    else {
200
0
        upstream = flb_upstream_create(config,
201
0
                                       ins->host.name,
202
0
                                       ins->host.port,
203
0
                                       io_flags, ins->tls);
204
0
    }
205
206
0
    if (!upstream) {
207
0
        flb_free(ctx);
208
0
        return NULL;
209
0
    }
210
211
0
    if (ins->host.uri) {
212
0
        uri = flb_strdup(ins->host.uri->full);
213
0
    }
214
0
    else {
215
0
        tmp = flb_output_get_property("uri", ins);
216
0
        if (tmp) {
217
0
            uri = flb_strdup(tmp);
218
0
        }
219
0
    }
220
221
0
    if (!uri) {
222
0
        uri = flb_strdup("/");
223
0
    }
224
0
    else if (uri[0] != '/') {
225
0
        ulen = strlen(uri);
226
0
        tmp_uri = flb_malloc(ulen + 2);
227
0
        tmp_uri[0] = '/';
228
0
        memcpy(tmp_uri + 1, uri, ulen);
229
0
        tmp_uri[ulen + 1] = '\0';
230
0
        flb_free(uri);
231
0
        uri = tmp_uri;
232
0
    }
233
234
    /* Output format */
235
0
    ctx->out_format = FLB_PACK_JSON_FORMAT_NONE;
236
0
    if (ctx->format) {
237
0
        if (strcasecmp(ctx->format, "gelf") == 0) {
238
0
            ctx->out_format = FLB_HTTP_OUT_GELF;
239
0
        }
240
0
        else if (strcasecmp(ctx->format, "msgpack") == 0) {
241
0
            ctx->out_format = FLB_HTTP_OUT_MSGPACK;
242
0
        }
243
0
        else {
244
0
            ret = flb_pack_to_json_format_type(ctx->format);
245
0
            if (ret == -1) {
246
0
                flb_plg_error(ctx->ins, "unrecognized 'format' option. "
247
0
                              "Using 'msgpack'");
248
0
            }
249
0
            else {
250
0
                ctx->out_format = ret;
251
0
            }
252
0
        }
253
0
    }
254
255
    /* Date key */
256
0
    ctx->date_key = ctx->json_date_key;
257
0
    tmp = flb_output_get_property("json_date_key", ins);
258
0
    if (tmp) {
259
        /* Just check if we have to disable it */
260
0
        if (flb_utils_bool(tmp) == FLB_FALSE) {
261
0
            ctx->date_key = NULL;
262
0
        }
263
0
    }
264
265
    /* Date format for JSON output */
266
0
    ctx->json_date_format = FLB_PACK_JSON_DATE_DOUBLE;
267
0
    tmp = flb_output_get_property("json_date_format", ins);
268
0
    if (tmp) {
269
0
        ret = flb_pack_to_json_date_type(tmp);
270
0
        if (ret == -1) {
271
0
            flb_plg_error(ctx->ins, "unrecognized 'json_date_format' option. "
272
0
                          "Using 'double'.");
273
0
        }
274
0
        else {
275
0
            ctx->json_date_format = ret;
276
0
        }
277
0
    }
278
279
    /* Compress (gzip) */
280
0
    tmp = flb_output_get_property("compress", ins);
281
0
    ctx->compress_gzip = FLB_FALSE;
282
0
    if (tmp) {
283
0
        if (strcasecmp(tmp, "gzip") == 0) {
284
0
            ctx->compress_gzip = FLB_TRUE;
285
0
        }
286
0
        else if (strcasecmp(tmp, "snappy") == 0) {
287
0
            ctx->compress_snappy = FLB_TRUE;
288
0
        }
289
0
        else if (strcasecmp(tmp, "zstd") == 0) {
290
0
            ctx->compress_zstd = FLB_TRUE;
291
0
        }
292
0
        else {
293
0
            flb_plg_error(ctx->ins, "invalid compress option '%s'", tmp);
294
0
            flb_free(ctx);
295
0
            return NULL;
296
0
        }
297
0
    }
298
299
    /* HTTP method */
300
0
    ctx->http_method = FLB_HTTP_POST;
301
0
    tmp = flb_output_get_property("http_method", ins);
302
0
    if (tmp) {
303
0
        if (strcasecmp(tmp, "POST") == 0) {
304
0
            ctx->http_method = FLB_HTTP_POST;
305
0
        }
306
0
        else if (strcasecmp(tmp, "PUT") == 0) {
307
0
            ctx->http_method = FLB_HTTP_PUT;
308
0
        }
309
0
        else {
310
0
            flb_plg_error(ctx->ins, "invalid http_method option '%s'. Supported methods are POST and PUT", tmp);
311
0
            flb_free(ctx);
312
0
            return NULL;
313
0
        }
314
0
    }
315
316
0
    ctx->u = upstream;
317
0
    ctx->uri = uri;
318
0
    ctx->host = ins->host.name;
319
0
    ctx->port = ins->host.port;
320
321
0
    if (ctx->oauth2_config.connect_timeout <= 0 &&
322
0
        ins->net_setup.connect_timeout > 0) {
323
0
        ctx->oauth2_config.connect_timeout = ins->net_setup.connect_timeout;
324
0
    }
325
326
0
    if (ctx->oauth2_config.timeout <= 0 && ctx->response_timeout > 0) {
327
0
        ctx->oauth2_config.timeout = ctx->response_timeout;
328
0
    }
329
330
0
    if (ctx->oauth2_config.enabled == FLB_TRUE) {
331
0
        tmp = ctx->oauth2_auth_method ? ctx->oauth2_auth_method :
332
0
              flb_output_get_property("oauth2.auth_method", ins);
333
334
0
        if (tmp) {
335
0
            if (strcasecmp(tmp, "basic") == 0) {
336
0
                ctx->oauth2_config.auth_method = FLB_OAUTH2_AUTH_METHOD_BASIC;
337
0
            }
338
0
            else if (strcasecmp(tmp, "post") == 0) {
339
0
                ctx->oauth2_config.auth_method = FLB_OAUTH2_AUTH_METHOD_POST;
340
0
            }
341
0
            else {
342
0
                flb_plg_error(ctx->ins, "invalid oauth2.auth_method '%s'", tmp);
343
0
                flb_http_conf_destroy(ctx);
344
0
                return NULL;
345
0
            }
346
0
        }
347
348
0
        if (!ctx->oauth2_config.token_url ||
349
0
            !ctx->oauth2_config.client_id ||
350
0
            !ctx->oauth2_config.client_secret) {
351
0
            flb_plg_error(ctx->ins, "oauth2 requires token_url, client_id and client_secret");
352
0
            flb_http_conf_destroy(ctx);
353
0
            return NULL;
354
0
        }
355
356
0
        ctx->oauth2_ctx = flb_oauth2_create_from_config(config, &ctx->oauth2_config);
357
0
        if (!ctx->oauth2_ctx) {
358
0
            flb_plg_error(ctx->ins, "failed to initialize oauth2 context");
359
0
            flb_http_conf_destroy(ctx);
360
0
            return NULL;
361
0
        }
362
0
    }
363
364
    /* Set instance flags into upstream */
365
0
    flb_output_upstream_set(ctx->u, ins);
366
367
0
    return ctx;
368
0
}
369
370
void flb_http_conf_destroy(struct flb_out_http *ctx)
371
0
{
372
0
    if (!ctx) {
373
0
        return;
374
0
    }
375
376
0
    if (ctx->body_ra && ctx->headers_ra) {
377
0
        flb_ra_destroy(ctx->body_ra);
378
0
        flb_ra_destroy(ctx->headers_ra);
379
0
    }
380
381
0
    if (ctx->u) {
382
0
        flb_upstream_destroy(ctx->u);
383
0
    }
384
385
0
#ifdef FLB_HAVE_SIGNV4
386
0
#ifdef FLB_HAVE_AWS
387
0
    if (ctx->aws_provider) {
388
0
        flb_aws_provider_destroy(ctx->aws_provider);
389
0
    }
390
0
#endif
391
0
#endif
392
393
0
    if (ctx->oauth2_ctx) {
394
0
        flb_oauth2_destroy(ctx->oauth2_ctx);
395
        /* OAuth2 context owns cloned copies of the config strings, so we don't
396
         * need to destroy ctx->oauth2_config here. The original strings in
397
         * ctx->oauth2_config are owned by the config map and will be freed by
398
         * flb_config_map_destroy. We set them to NULL after creating the context
399
         * to prevent double-free.
400
         */
401
0
    }
402
0
    else {
403
        /* Only destroy oauth2_config if OAuth2 context wasn't created,
404
         * meaning the strings weren't cloned. But in this case, they're still
405
         * owned by the config map, so we shouldn't free them either.
406
         */
407
0
    }
408
409
0
    flb_free(ctx->proxy_host);
410
0
    flb_free(ctx->uri);
411
0
    flb_free(ctx);
412
0
}