/*! * \internal * \brief ARI HTTP handler. * * This handler takes the HTTP request and turns it into the appropriate * RESTful request (conversion to JSON, routing, etc.) * * \param ser TCP session. * \param urih URI handler. * \param uri URI requested. * \param method HTTP method. * \param get_params HTTP \c GET params. * \param headers HTTP headers. */ static int ast_ari_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers) { RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup); RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free); RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup); struct ast_ari_response response = {}; RAII_VAR(struct ast_variable *, post_vars, NULL, ast_variables_destroy); if (!response_body) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "Out of memory"); return 0; } response.headers = ast_str_create(40); if (!response.headers) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "Out of memory"); return 0; } conf = ast_ari_config_get(); if (!conf || !conf->general) { ast_free(response.headers); ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "URI handler config missing"); return 0; } process_cors_request(headers, &response); /* Process form data from a POST. It could be mixed with query * parameters, which seems a bit odd. But it's allowed, so that's okay * with us. */ post_vars = ast_http_get_post_vars(ser, headers); if (!post_vars) { switch (errno) { case EFBIG: ast_ari_response_error(&response, 413, "Request Entity Too Large", "Request body too large"); goto request_failed; case ENOMEM: ast_http_request_close_on_completion(ser); ast_ari_response_error(&response, 500, "Internal Server Error", "Out of memory"); goto request_failed; case EIO: ast_ari_response_error(&response, 400, "Bad Request", "Error parsing request body"); goto request_failed; } } if (get_params == NULL) { get_params = post_vars; } else if (get_params && post_vars) { /* Has both post_vars and get_params */ struct ast_variable *last_var = post_vars; while (last_var->next) { last_var = last_var->next; } /* The duped get_params will get freed when post_vars gets * ast_variables_destroyed. */ last_var->next = ast_variables_dup(get_params); get_params = post_vars; } user = authenticate_user(get_params, headers); if (response.response_code > 0) { /* POST parameter processing error. Do nothing. */ } else if (!user) { /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response * message is used by an origin server to challenge the * authorization of a user agent. This response MUST include a * WWW-Authenticate header field containing at least one * challenge applicable to the requested resource. */ ast_ari_response_error(&response, 401, "Unauthorized", "Authentication required"); /* Section 1.2: * realm = "realm" "=" realm-value * realm-value = quoted-string * Section 2: * challenge = "Basic" realm */ ast_str_append(&response.headers, 0, "WWW-Authenticate: Basic realm=\"%s\"\r\n", conf->general->auth_realm); } else if (!ast_fully_booted) { ast_http_request_close_on_completion(ser); ast_ari_response_error(&response, 503, "Service Unavailable", "Asterisk not booted"); } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) { ast_ari_response_error(&response, 403, "Forbidden", "Write access denied"); } else if (ast_ends_with(uri, "/")) { remove_trailing_slash(uri, &response); } else if (ast_begins_with(uri, "api-docs/")) { /* Serving up API docs */ if (method != AST_HTTP_GET) { ast_ari_response_error(&response, 405, "Method Not Allowed", "Unsupported method"); } else { /* Skip the api-docs prefix */ ast_ari_get_docs(strchr(uri, '/') + 1, headers, &response); } } else { /* Other RESTful resources */ ast_ari_invoke(ser, uri, method, get_params, headers, &response); } if (response.no_response) { /* The handler indicates no further response is necessary. * Probably because it already handled it */ ast_free(response.headers); return 0; } request_failed: /* If you explicitly want to have no content, set message to * ast_json_null(). */ ast_assert(response.message != NULL); ast_assert(response.response_code > 0); /* response.message could be NULL, in which case the empty response_body * is correct */ if (response.message && !ast_json_is_null(response.message)) { ast_str_append(&response.headers, 0, "Content-type: application/json\r\n"); if (ast_json_dump_str_format(response.message, &response_body, conf->general->format) != 0) { /* Error encoding response */ response.response_code = 500; response.response_text = "Internal Server Error"; ast_str_set(&response_body, 0, "%s", ""); ast_str_set(&response.headers, 0, "%s", ""); } } ast_debug(3, "Examining ARI response:\n%d %s\n%s\n%s\n", response.response_code, response.response_text, ast_str_buffer(response.headers), ast_str_buffer(response_body)); ast_http_send(ser, method, response.response_code, response.response_text, response.headers, response_body, 0, 0); /* ast_http_send takes ownership, so we don't have to free them */ response_body = NULL; ast_json_unref(response.message); return 0; }
static int http_post_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers) { struct ast_variable *var; uint32_t ident; FILE *f; int content_len = 0; struct ast_str *post_dir; GMimeMessage *message; char *boundary_marker = NULL; if (method != AST_HTTP_POST) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); return 0; } if (!urih) { ast_http_error(ser, 400, "Missing URI handle", "There was an error parsing the request"); return 0; } ident = ast_http_manid_from_vars(headers); if (!ident || !astman_is_authed(ident)) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave."); return 0; } if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request."); return 0; } if (!(f = tmpfile())) { ast_log(LOG_ERROR, "Could not create temp file.\n"); ast_http_error(ser, 500, "Internal server error", "Could not create temp file."); return 0; } for (var = headers; var; var = var->next) { fprintf(f, "%s: %s\r\n", var->name, var->value); if (!strcasecmp(var->name, "Content-Length")) { if ((sscanf(var->value, "%30u", &content_len)) != 1) { ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n"); fclose(f); ast_http_request_close_on_completion(ser); ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in POST request!"); return 0; } ast_debug(1, "Got a Content-Length of %d\n", content_len); } else if (!strcasecmp(var->name, "Content-Type")) { boundary_marker = strstr(var->value, "boundary="); if (boundary_marker) { boundary_marker += strlen("boundary="); } } } fprintf(f, "\r\n"); /* * Always mark the body read as failed. * * XXX Should change readmimefile() to always be sure to read * the entire body so we can update the read status and * potentially keep the connection open. */ ast_http_body_read_status(ser, 0); if (0 > readmimefile(ser->stream, f, boundary_marker, content_len)) { ast_debug(1, "Cannot find boundary marker in POST request.\n"); fclose(f); ast_http_error(ser, 400, "Bad Request", "Cannot find boundary marker in POST request."); return 0; } if (fseek(f, SEEK_SET, 0)) { ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n"); fclose(f); ast_http_error(ser, 500, "Internal server error", "Failed to seek temp file back to beginning."); return 0; } post_dir = urih->data; message = parse_message(f); /* Takes ownership and will close f */ if (!message) { ast_log(LOG_ERROR, "Error parsing MIME data\n"); ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request."); return 0; } if (!process_message(message, ast_str_buffer(post_dir))) { ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n"); g_object_unref(message); ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request."); return 0; } g_object_unref(message); /* XXX Passing 200 to the error response routine? */ ast_http_error(ser, 200, "OK", "File successfully uploaded."); return 0; }
static int http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers) { char file_name[64] = "/tmp/test-media-cache-XXXXXX"; struct ast_str *http_header = ast_str_create(128); struct ast_str *cache_control = ast_str_create(128); int fd = -1; int unmodified = 0; int send_file = options.send_file && method == AST_HTTP_GET; if (!http_header) { goto error; } if (send_file) { char buf[1024]; fd = mkstemp(file_name); if (fd == -1) { ast_log(LOG_ERROR, "Unable to open temp file for testing: %s (%d)", strerror(errno), errno); goto error; } memset(buf, 1, sizeof(buf)); if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { ast_log(LOG_ERROR, "Failed to write expected number of bytes to pipe\n"); close(fd); goto error; } close(fd); fd = open(file_name, 0); if (fd == -1) { ast_log(LOG_ERROR, "Unable to open temp file for testing: %s (%d)", strerror(errno), errno); goto error; } } if (options.cache_control.maxage) { SET_OR_APPEND_CACHE_CONTROL(cache_control); ast_str_append(&cache_control, 0, "max-age=%d", options.cache_control.maxage); } if (options.cache_control.s_maxage) { SET_OR_APPEND_CACHE_CONTROL(cache_control); ast_str_append(&cache_control, 0, "s-maxage=%d", options.cache_control.s_maxage); } if (options.cache_control.no_cache) { SET_OR_APPEND_CACHE_CONTROL(cache_control); ast_str_append(&cache_control, 0, "%s", "no-cache"); } if (options.cache_control.must_revalidate) { SET_OR_APPEND_CACHE_CONTROL(cache_control); ast_str_append(&cache_control, 0, "%s", "must-revalidate"); } if (ast_str_strlen(cache_control)) { ast_str_append(&http_header, 0, "%s\r\n", ast_str_buffer(cache_control)); } if (options.expires.tv_sec) { struct ast_tm now_time; char tmbuf[64]; ast_localtime(&options.expires, &now_time, NULL); ast_strftime(tmbuf, sizeof(tmbuf), "%a, %d %b %Y %T %z", &now_time); ast_str_append(&http_header, 0, "Expires: %s\r\n", tmbuf); } if (!ast_strlen_zero(options.etag)) { struct ast_variable *v; ast_str_append(&http_header, 0, "ETag: %s\r\n", options.etag); for (v = headers; v; v = v->next) { if (!strcasecmp(v->name, "If-None-Match") && !strcasecmp(v->value, options.etag)) { unmodified = 1; break; } } } if (!unmodified) { ast_http_send(ser, method, options.status_code, options.status_text, http_header, NULL, send_file ? fd : 0, 1); } else { ast_http_send(ser, method, 304, "Not Modified", http_header, NULL, 0, 1); } if (send_file) { close(fd); unlink(file_name); } ast_free(cache_control); return 0; error: ast_free(http_header); ast_free(cache_control); ast_http_request_close_on_completion(ser); ast_http_error(ser, 418, "I'm a Teapot", "Please don't ask me to brew coffee."); return 0; }