Skip to content

Commit

Permalink
Add custom body encoder
Browse files Browse the repository at this point in the history
This allows admin to specify an arbitrary content-type and body data
  • Loading branch information
arr2036 committed Mar 26, 2014
1 parent 926a334 commit d3ed2fe
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 45 deletions.
133 changes: 98 additions & 35 deletions src/modules/rlm_rest/rest.c
Expand Up @@ -50,6 +50,7 @@ const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNAVAILABLE
HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
HTTP_BODY_NONE, // HTTP_BODY_NONE
HTTP_BODY_CUSTOM, // HTTP_BODY_CUSTOM
HTTP_BODY_POST, // HTTP_BODY_POST
#ifdef HAVE_JSON
HTTP_BODY_JSON, // HTTP_BODY_JSON
Expand Down Expand Up @@ -193,9 +194,18 @@ const FR_NAME_NUMBER http_content_type_table[] = {
{ "text/x-yaml", HTTP_BODY_YAML },
{ "application/yaml", HTTP_BODY_YAML },
{ "application/x-yaml", HTTP_BODY_YAML },

{ NULL , -1 }
};

/*
* Encoder specific structures.
* @todo split encoders/decoders into submodules.
*/
typedef struct rest_custom_data {
char *p; //!< how much text we've sent so far.
} rest_custom_data_t;

#ifdef HAVE_JSON
/** Flags to control the conversion of JSON values to VALUE_PAIRs.
*
Expand Down Expand Up @@ -412,6 +422,33 @@ int mod_conn_delete(UNUSED void *instance, void *handle)
return true;
}

/** Copies a pre-expanded xlat string to the output buffer
*
* @param[out] out Char buffer to write encoded data to.
* @param[in] size Multiply by nmemb to get the length of ptr.
* @param[in] nmemb Multiply by size to get the length of ptr.
* @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
* @return length of data (including NULL) written to ptr, or 0 if no more
* data to write.
*/
static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
{
rlm_rest_request_t *ctx = userdata;
rest_custom_data_t *data = ctx->encoder;

size_t freespace = (size * nmemb) - 1;
size_t len;

len = strlcpy(out, data->p, freespace);
if (is_truncated(len, freespace)) {
data->p += (freespace - 1);
return freespace - 1;
}
data->p += len;

return len;
}

/** Encodes VALUE_PAIR linked list in POST format
*
* This is a stream function matching the rest_read_t prototype. Multiple
Expand Down Expand Up @@ -617,8 +654,8 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd
static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
{
rlm_rest_request_t *ctx = userdata;
REQUEST *request = ctx->request; /* Used by RDEBUG */
VALUE_PAIR *vp, *next;
REQUEST *request = ctx->request; /* Used by RDEBUG */
VALUE_PAIR *vp, *next;

char *p = out; /* Position in buffer */
char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
Expand Down Expand Up @@ -1803,12 +1840,10 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,

SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS");

content_type = fr_int2str(http_content_type_table, type, NULL);
if (content_type) {
snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s", content_type);
ctx->headers = curl_slist_append(ctx->headers, buffer);
if (!ctx->headers) goto error_header;
}
content_type = fr_int2str(http_content_type_table, type, section->body_str);
snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s", content_type);
ctx->headers = curl_slist_append(ctx->headers, buffer);
if (!ctx->headers) goto error_header;

if (section->timeout) {
SET_OPTION(CURLOPT_TIMEOUT, section->timeout);
Expand Down Expand Up @@ -1981,46 +2016,74 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,

RDEBUG3("Request body content-type will be \"%s\"",
fr_int2str(http_content_type_table, type, section->body_str));
switch (type) {
case HTTP_BODY_NONE:
if (rest_request_config_body(instance, section, request, handle,
NULL) < 0) {
return -1;
}
break;

break;
default:
rad_assert(0);
};

#ifdef HAVE_JSON
case HTTP_BODY_JSON:
rest_request_init(request, &ctx->request, 1);
/*
* Setup encoder specific options
*/
switch (type) {
case HTTP_BODY_NONE:
if (rest_request_config_body(instance, section, request, handle,
NULL) < 0) {
return -1;
}

if (rest_request_config_body(instance, section, request, handle,
rest_encode_json) < 0) {
return -1;
}
break;

break;
#endif
case HTTP_BODY_CUSTOM:
{
rest_custom_data_t *data;
char *expanded = NULL;

case HTTP_BODY_POST:
rest_request_init(request, &ctx->request, 0);
if (radius_axlat(&expanded, request, section->data, NULL, NULL) < 0) {
return -1;
}

if (rest_request_config_body(instance, section, request, handle,
rest_encode_post) < 0) {
return -1;
}
data = talloc_zero(request, rest_custom_data_t);
data->p = expanded;

break;
/* Use the encoder specific pointer to store the data we need to encode */
ctx->request.encoder = data;
if (rest_request_config_body(instance, section, request, handle,
rest_encode_custom) < 0) {
TALLOC_FREE(ctx->request.encoder);
return -1;
}

default:
assert(0);
break;
}

#ifdef HAVE_JSON
case HTTP_BODY_JSON:
rest_request_init(request, &ctx->request, true);

if (rest_request_config_body(instance, section, request, handle,
rest_encode_json) < 0) {
return -1;
}

break;
#endif

case HTTP_BODY_POST:
rest_request_init(request, &ctx->request, false);

if (rest_request_config_body(instance, section, request, handle,
rest_encode_post) < 0) {
return -1;
}

break;

default:
rad_assert(0);
};
assert(0);
}


finish:
SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);

Expand Down
1 change: 1 addition & 0 deletions src/modules/rlm_rest/rest.h
Expand Up @@ -58,6 +58,7 @@ typedef enum {
HTTP_BODY_UNAVAILABLE,
HTTP_BODY_INVALID,
HTTP_BODY_NONE,
HTTP_BODY_CUSTOM,
HTTP_BODY_POST,
HTTP_BODY_JSON,
HTTP_BODY_XML,
Expand Down
60 changes: 50 additions & 10 deletions src/modules/rlm_rest/rlm_rest.c
Expand Up @@ -66,6 +66,7 @@ static const CONF_PARSER section_config[] = {
{ "uri", PW_TYPE_STRING_PTR, offsetof(rlm_rest_section_t, uri), NULL, "" },
{ "method", PW_TYPE_STRING_PTR, offsetof(rlm_rest_section_t, method_str), NULL, "GET" },
{ "body", PW_TYPE_STRING_PTR, offsetof(rlm_rest_section_t, body_str), NULL, "none" },
{ "data", PW_TYPE_STRING_PTR, offsetof(rlm_rest_section_t, data), NULL, NULL },

/* User authentication */
{ "auth", PW_TYPE_STRING_PTR, offsetof(rlm_rest_section_t, auth_str), NULL, "none" },
Expand Down Expand Up @@ -166,7 +167,7 @@ static ssize_t rest_xlat(void *instance, REQUEST *request,
if (!handle) return -1;

/*
* Build xlat'd URI, this allows REST servers to be specified by
* Unescape parts of xlat'd URI, this allows REST servers to be specified by
* request attributes.
*/
len = rest_uri_host_unescape(&uri, instance, request, handle, fmt);
Expand Down Expand Up @@ -495,6 +496,7 @@ static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, r
config->auth = fr_str2int(http_auth_table, config->auth_str, HTTP_AUTH_UNKNOWN);
if (config->auth == HTTP_AUTH_UNKNOWN) {
cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str);

return -1;
} else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) {
cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build "
Expand All @@ -504,19 +506,57 @@ static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, r
}

config->method = fr_str2int(http_method_table, config->method_str, HTTP_METHOD_CUSTOM);
config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);

if (config->body == HTTP_BODY_UNKNOWN) {
cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str);
return -1;
}
/*
* We don't have any custom user data, so we need to select the right encoder based
* on the body type.
*
* To make this slightly more/less confusing, we accept both canonical body_types,
* and content_types.
*/
if (!config->data) {
config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
if (config->body == HTTP_BODY_UNKNOWN) {
config->body = fr_str2int(http_content_type_table, config->body_str, HTTP_BODY_UNKNOWN);
}

if (http_body_type_supported[config->body] == HTTP_BODY_UNSUPPORTED) {
cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches", config->body_str);
return -1;
if (config->body == HTTP_BODY_UNKNOWN) {
cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str);
return -1;
}

switch (http_body_type_supported[config->body])
{
case HTTP_BODY_UNSUPPORTED:
cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches",
config->body_str);
return -1;

case HTTP_BODY_INVALID:
cf_log_err_cs(cs, "Invalid HTTP body type. \"%s\" is not a valid web API data "
"markup format", config->body_str);
return -1;

default:
break;
}
/*
* We have custom body data so we set HTTP_BODY_CUSTOM, but also need to try and
* figure out what content-type to use. So if they've used the canonical form we
* need to convert it back into a proper HTTP content_type value.
*/
} else {
http_body_type_t body;

config->body = HTTP_BODY_CUSTOM;

body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
if (body != HTTP_BODY_UNKNOWN) {
config->body_str = fr_int2str(http_content_type_table, body, config->body_str);
}
}

return 1;
return 0;
}

/*
Expand Down

0 comments on commit d3ed2fe

Please sign in to comment.