Files
asterisk/res/cdrel_custom/formatters.c
T
George Joseph 242276bbaa res_cdrel_custom: Resolve several formatting issues.
Several issues are resolved:

* Internally, floats were used for timestamp values but this could result
in wrapping so they've been changed to doubles.

* Historically, the default CEL eventtime format is `<seconds>.<microseconds>`
with `<microseconds>` always being 6 digits.  This should have continued to be
the case but res_cdrel_custom wasn't checking the `dateformat` setting in
cel.conf and was defaulting to `%F %T`.  res_cdrel_custom now gets the default
date format from cel.conf, which will be whatever the `dateformat` parameter
is set to or `<seconds>.<microseconds>` if not set.

* The timeval field formatter for both CDR and CEL wasn't handling custom
strftime format strings correctly.  This is now fixed so you should be able
to specifiy custom strftime format strings for the CEL `eventtime` and CDR
`start`, `answer` and `end` fields.  For example: `eventtime(%FT%T%z)`.

Resolves: #1844
Resolves: #1845
2026-04-01 19:16:15 +00:00

204 lines
6.8 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@sangoma.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
* \author George Joseph <gjoseph@sangoma.com>
*
* \brief Formatters
*
*/
#include "cdrel.h"
#include "asterisk/json.h"
#include "asterisk/cdr.h"
#include "asterisk/cel.h"
static char *quote_escaper(const char *value, char quote, char quote_escape, char *qvalue)
{
char *ptr = qvalue;
const char *dataptr = value;
if (!qvalue) {
return NULL;
}
while(*dataptr != '\0') {
if (*dataptr == quote) {
*ptr++ = quote_escape;
}
*ptr++ = *dataptr++;
}
*ptr='\0';
return qvalue;
}
static int format_string(struct cdrel_config *config,
struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value)
{
int quotes_count = 0;
int needs_quoting = ast_test_flag(&field->flags, cdrel_flag_quote);
int ix = 0;
int input_len = strlen(input_value->values.string ?: "");
int res = 0;
char *qvalue = NULL;
char *evalue = (char *)input_value->values.string;
output_value->data_type = cdrel_type_string;
output_value->field_name = input_value->field_name;
if (input_len == 0) {
output_value->values.string = ast_strdup(needs_quoting ? "\"\"" : "");
return 0;
}
for (ix = 0; ix < input_len; ix++) {
char c = input_value->values.string[ix];
if (c == config->quote[0]) {
quotes_count++;
needs_quoting = 1;
} else if (c == config->separator[0] || c == '\r' || c == '\n') {
needs_quoting = 1;
}
}
ast_debug(5, "%s: %s=%s %s", cdrel_basename(config->output_filename), input_value->field_name,
input_value->values.string, ast_str_tmp(128, cdrel_get_field_flags(&field->flags, &STR_TMP)));
if (!needs_quoting) {
output_value->values.string = ast_strdup(input_value->values.string);
return output_value->values.string == NULL ? -1 : 0;
}
/* For every quote_count, we need an extra byte for the quote escape character. */
qvalue = ast_alloca(input_len + quotes_count + 1);
if (quotes_count) {
evalue = quote_escaper(input_value->values.string, config->quote[0], config->quote_escape[0], qvalue);
}
res = ast_asprintf(&output_value->values.string, "%s%s%s", config->quote, evalue, config->quote);
return res < 0 ? -1 : 0;
}
#define DEFINE_FORMATTER(_name, _field, _type, _fmt) \
static int format_ ## _name (struct cdrel_config *config, \
struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value) \
{ \
int res = 0; \
char *quote = ""; \
if (input_value->data_type != output_value->data_type) { \
/* Forward to other formatter */ \
return cdrel_field_formatters[output_value->data_type](config, field, input_value, output_value); \
} \
output_value->field_name = input_value->field_name; \
if ((config->quoting_method == cdrel_quoting_method_all || ast_test_flag(&field->flags, cdrel_flag_quote)) \
&& !ast_test_flag(&field->flags, cdrel_flag_noquote)) { \
quote = config->quote; \
} \
res = ast_asprintf(&output_value->values.string, "%s" _fmt "%s", quote, input_value->values._field, quote); \
output_value->data_type = cdrel_type_string; \
return res < 0 ? -1 : 0; \
}
DEFINE_FORMATTER(uint32, uint32, uint32_t, "%u")
DEFINE_FORMATTER(int32, int32, int32_t, "%d")
DEFINE_FORMATTER(uint64, uint64, uint64_t, "%lu")
DEFINE_FORMATTER(int64, int64, int64_t, "%ld")
DEFINE_FORMATTER(double, doubler, double, "%.6f")
static int format_timeval(struct cdrel_config *config,
struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value)
{
struct ast_tm tm;
char tempbuf[64] = { 0, };
int res = 0;
const char *format = "%Y-%m-%d %T";
output_value->field_name = input_value->field_name;
if (field->output_data_type == cdrel_type_int64) {
output_value->data_type = cdrel_type_int64;
output_value->values.int64 = input_value->values.tv.tv_sec;
return format_int64(config, field, output_value, output_value);
} else if (field->output_data_type == cdrel_type_double) {
output_value->data_type = cdrel_type_double;
output_value->values.doubler = ((double)input_value->values.tv.tv_sec) + ((double)input_value->values.tv.tv_usec) / 1000000.0;
return format_double(config, field, output_value, output_value);
} else if (field->output_data_type == cdrel_type_cel_timefmt) {
res = ast_cel_format_eventtime(input_value->values.tv, tempbuf, 64);
if (res != 0) {
return res;
}
input_value->values.string = tempbuf;
input_value->data_type = cdrel_type_string;
output_value->data_type = cdrel_type_string;
return format_string(config, field, input_value, output_value);
} else if (!ast_strlen_zero(field->data)) {
format = field->data;
}
if (input_value->values.tv.tv_sec > 0) {
ast_localtime(&input_value->values.tv, &tm, NULL);
ast_strftime(tempbuf, sizeof(tempbuf), format, &tm);
}
input_value->values.string = tempbuf;
input_value->data_type = cdrel_type_string;
output_value->data_type = cdrel_type_string;
res = format_string(config, field, input_value, output_value);
return res;
}
static int format_amaflags(struct cdrel_config *config,
struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value)
{
int res = 0;
input_value->values.string = (char *)ast_channel_amaflags2string(input_value->values.int64);
input_value->data_type = cdrel_type_string;
output_value->data_type = cdrel_type_string;
res = format_string(config, field, input_value, output_value);
return res;
}
static int format_disposition(struct cdrel_config *config,
struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value)
{
int res = 0;
input_value->values.string = (char *)ast_cdr_disp2str(input_value->values.int64);
input_value->data_type = cdrel_type_string;
output_value->data_type = cdrel_type_string;
res = format_string(config, field, input_value, output_value);
return res;
}
int load_formatters(void)
{
ast_debug(1, "Loading Formatters\n");
cdrel_field_formatters[cdrel_type_string] = format_string;
cdrel_field_formatters[cdrel_type_int32] = format_int32;
cdrel_field_formatters[cdrel_type_uint32] = format_uint32;
cdrel_field_formatters[cdrel_type_int64] = format_int64;
cdrel_field_formatters[cdrel_type_uint64] = format_uint64;
cdrel_field_formatters[cdrel_type_timeval] = format_timeval;
cdrel_field_formatters[cdrel_type_double] = format_double;
cdrel_field_formatters[cdrel_type_amaflags] = format_amaflags;
cdrel_field_formatters[cdrel_type_disposition] = format_disposition;
return 0;
}