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
{ "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.
*
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
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 */
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);
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);
{ "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" },
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);
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 "
}
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;
}
/*