5af58c8af pmdastatsd: fix minor sizeof issues found by Coverity scan b3f78dc82 pmlogconf: fix resource leak found by coverity scan 8a3ed1b26 pmdastatsd: initialize stack variable to keep Coverity happy 6902959e5 pmdastatsd: fix Coverity LOCK issues on error paths 548cad8c5 libpcp_web: ensure context is freed only after timer is fully closed 01e8bb436 services: pmlogger and pmie services want pmcd on boot 20959e794 Fix of 1845241 - Intermittent pmlogconf core dumps 32d6febf4 pcp-atop: resolve other paths of potential null task pointer dereference cda567efe pmproxy: improve diagnostics, particularly relating to http requests e0bb9e66c pmproxy: cleanup, remove unused flags and dead code in http encoding 9da331eb8 pmproxy: support the OPTIONS protocol in HTTP 1.1 1d84081af libpcp_web: add resilience to descriptor lookup paths --- a/src/pmdas/statsd/src/aggregator-metric-duration-exact.c 2019-08-21 11:33:26.000000000 +1000 +++ b/src/pmdas/statsd/src/aggregator-metric-duration-exact.c 2020-06-11 13:10:57.393576397 +1000 @@ -45,7 +45,7 @@ double** new_values = realloc(collection->values, sizeof(double*) * new_length); ALLOC_CHECK("Unable to allocate memory for collection value."); collection->values = new_values; - collection->values[collection->length] = (double*) malloc(sizeof(double*)); + collection->values[collection->length] = (double*) malloc(sizeof(double)); ALLOC_CHECK("Unable to allocate memory for duration collection value."); *(collection->values[collection->length]) = value; collection->length = new_length; --- a/src/pmdas/statsd/src/aggregator-metric-labels.c 2020-02-18 16:32:40.000000000 +1100 +++ b/src/pmdas/statsd/src/aggregator-metric-labels.c 2020-06-11 13:10:57.393576397 +1000 @@ -140,7 +140,7 @@ static char* create_instance_label_segment_str(char* tags) { - char buffer[JSON_BUFFER_SIZE]; + char buffer[JSON_BUFFER_SIZE] = {'\0'}; size_t tags_length = strlen(tags) + 1; if (tags_length > JSON_BUFFER_SIZE) { return NULL; @@ -197,7 +197,7 @@ ALLOC_CHECK("Unable to allocate memory for labels string in metric label record."); memcpy((*out)->labels, datagram->tags, labels_length); struct metric_label_metadata* meta = - (struct metric_label_metadata*) malloc(sizeof(struct metric_label_metadata*)); + (struct metric_label_metadata*) malloc(sizeof(struct metric_label_metadata)); ALLOC_CHECK("Unable to allocate memory for metric label metadata."); (*out)->meta = meta; (*out)->type = METRIC_TYPE_NONE; --- a/src/pmdas/statsd/src/network-listener.c 2019-08-27 11:09:16.000000000 +1000 +++ b/src/pmdas/statsd/src/network-listener.c 2020-06-11 13:10:57.393576397 +1000 @@ -68,7 +68,7 @@ struct timeval tv; freeaddrinfo(res); int max_udp_packet_size = config->max_udp_packet_size; - char *buffer = (char *) malloc(max_udp_packet_size * sizeof(char*)); + char *buffer = (char *) malloc(max_udp_packet_size * sizeof(char)); struct sockaddr_storage src_addr; socklen_t src_addr_len = sizeof(src_addr); int rv; --- a/src/pmlogconf/pmlogconf.c 2020-05-23 13:33:27.000000000 +1000 +++ b/src/pmlogconf/pmlogconf.c 2020-06-11 13:10:57.394576411 +1000 @@ -735,7 +735,7 @@ static int evaluate_number_values(group_t *group, int type, numeric_cmp_t compare) { - unsigned int i, found; + int i, found; pmValueSet *vsp; pmValue *vp; pmAtomValue atom; @@ -769,7 +769,7 @@ static int evaluate_string_values(group_t *group, string_cmp_t compare) { - unsigned int i, found; + int i, found; pmValueSet *vsp; pmValue *vp; pmAtomValue atom; @@ -828,7 +828,7 @@ static int evaluate_string_regexp(group_t *group, regex_cmp_t compare) { - unsigned int i, found; + int i, found; pmValueSet *vsp; pmValue *vp; pmAtomValue atom; @@ -1478,6 +1478,10 @@ } else if (strncmp("#+ groupdir ", bytes, 12) == 0) { group_dircheck(bytes + 12); } else if (strncmp("#+ ", bytes, 3) == 0) { + if (group) { + /* reported by COVERITY RESOURCE LEAK */ + group_free(group); + } group = group_create(bytes + 3, line); head = 0; } else if (group) { --- a/src/pmdas/statsd/src/aggregator-metrics.c 2020-02-18 16:32:40.000000000 +1100 +++ b/src/pmdas/statsd/src/aggregator-metrics.c 2020-06-11 13:10:57.394576411 +1000 @@ -212,7 +212,10 @@ VERBOSE_LOG(0, "Writing metrics to file..."); pthread_mutex_lock(&container->mutex); metrics* m = container->metrics; - if (strlen(config->debug_output_filename) == 0) return; + if (strlen(config->debug_output_filename) == 0) { + pthread_mutex_unlock(&container->mutex); + return; + } int sep = pmPathSeparator(); char debug_output[MAXPATHLEN]; pmsprintf( --- a/src/pmdas/statsd/src/aggregator-stats.c 2020-02-18 16:32:40.000000000 +1100 +++ b/src/pmdas/statsd/src/aggregator-stats.c 2020-06-11 13:10:57.394576411 +1000 @@ -141,7 +141,10 @@ write_stats_to_file(struct agent_config* config, struct pmda_stats_container* stats) { VERBOSE_LOG(0, "Writing stats to file..."); pthread_mutex_lock(&stats->mutex); - if (strlen(config->debug_output_filename) == 0) return; + if (strlen(config->debug_output_filename) == 0) { + pthread_mutex_unlock(&stats->mutex); + return; + } int sep = pmPathSeparator(); char debug_output[MAXPATHLEN]; pmsprintf( --- a/src/libpcp_web/src/webgroup.c 2020-05-22 11:29:27.000000000 +1000 +++ b/src/libpcp_web/src/webgroup.c 2020-06-11 13:10:57.394576411 +1000 @@ -56,17 +56,28 @@ } static void +webgroup_release_context(uv_handle_t *handle) +{ + struct context *context = (struct context *)handle->data; + + if (pmDebugOptions.http) + fprintf(stderr, "releasing context %p\n", context); + + pmwebapi_free_context(context); +} + +static void webgroup_destroy_context(struct context *context, struct webgroups *groups) { context->garbage = 1; if (pmDebugOptions.http) - fprintf(stderr, "freeing context %p\n", context); + fprintf(stderr, "destroying context %p\n", context); uv_timer_stop(&context->timer); if (groups) dictUnlink(groups->contexts, &context->randomid); - pmwebapi_free_context(context); + uv_close((uv_handle_t *)&context->timer, webgroup_release_context); } static void --- a/src/pmie/pmie.service.in 2020-05-27 13:36:47.000000000 +1000 +++ b/src/pmie/pmie.service.in 2020-06-11 13:10:57.394576411 +1000 @@ -4,6 +4,7 @@ After=network-online.target pmcd.service After=pmie_check.timer pmie_check.path pmie_daily.timer BindsTo=pmie_check.timer pmie_check.path pmie_daily.timer +Wants=pmcd.service [Service] Type=notify --- a/src/pmlogger/pmlogger.service.in 2020-05-22 16:48:32.000000000 +1000 +++ b/src/pmlogger/pmlogger.service.in 2020-06-11 13:10:57.394576411 +1000 @@ -4,6 +4,7 @@ After=network-online.target pmcd.service After=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer BindsTo=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer +Wants=pmcd.service [Service] Type=notify --- a/src/pcp/atop/showgeneric.c 2020-03-30 12:13:55.000000000 +1100 +++ b/src/pcp/atop/showgeneric.c 2020-06-11 13:10:57.395576426 +1000 @@ -2024,6 +2024,9 @@ */ for (numusers=i=0; i < numprocs; i++, curprocs++) { + if (*curprocs == NULL) + continue; + if (procsuppress(*curprocs, &procsel)) continue; @@ -2069,6 +2072,9 @@ */ for (numprogs=i=0; i < numprocs; i++, curprocs++) { + if (*curprocs == NULL) + continue; + if (procsuppress(*curprocs, &procsel)) continue; @@ -2112,6 +2118,9 @@ */ for (numconts=i=0; i < numprocs; i++, curprocs++) { + if (*curprocs == NULL) + continue; + if (procsuppress(*curprocs, &procsel)) continue; --- a/src/libpcp_web/src/exports 2020-05-22 15:38:47.000000000 +1000 +++ b/src/libpcp_web/src/exports 2020-06-11 13:10:57.397576455 +1000 @@ -189,3 +189,14 @@ pmWebGroupDestroy; sdsKeyDictCallBacks; } PCP_WEB_1.12; + +PCP_WEB_1.14 { + global: + dictFetchValue; + http_method_str; + http_body_is_final; + http_parser_version; + http_parser_url_init; + http_parser_parse_url; + http_parser_settings_init; +} PCP_WEB_1.13; --- a/src/pmproxy/src/http.c 2020-03-23 09:47:47.000000000 +1100 +++ b/src/pmproxy/src/http.c 2020-06-11 13:10:57.398576470 +1000 @@ -21,6 +21,18 @@ static int chunked_transfer_size; /* pmproxy.chunksize, pagesize by default */ static int smallest_buffer_size = 128; +#define MAX_PARAMS_SIZE 4096 +#define MAX_HEADERS_SIZE 128 + +static sds HEADER_ACCESS_CONTROL_REQUEST_HEADERS, + HEADER_ACCESS_CONTROL_REQUEST_METHOD, + HEADER_ACCESS_CONTROL_ALLOW_METHODS, + HEADER_ACCESS_CONTROL_ALLOW_HEADERS, + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + HEADER_ACCESS_CONTROL_ALLOWED_HEADERS, + HEADER_CONNECTION, HEADER_CONTENT_LENGTH, + HEADER_ORIGIN, HEADER_WWW_AUTHENTICATE; + /* * Simple helpers to manage the cumulative addition of JSON * (arrays and/or objects) to a buffer. @@ -121,45 +133,9 @@ return "text/html"; if (flags & HTTP_FLAG_TEXT) return "text/plain"; - if (flags & HTTP_FLAG_JS) - return "text/javascript"; - if (flags & HTTP_FLAG_CSS) - return "text/css"; - if (flags & HTTP_FLAG_ICO) - return "image/x-icon"; - if (flags & HTTP_FLAG_JPG) - return "image/jpeg"; - if (flags & HTTP_FLAG_PNG) - return "image/png"; - if (flags & HTTP_FLAG_GIF) - return "image/gif"; return "application/octet-stream"; } -http_flags -http_suffix_type(const char *suffix) -{ - if (strcmp(suffix, "js") == 0) - return HTTP_FLAG_JS; - if (strcmp(suffix, "ico") == 0) - return HTTP_FLAG_ICO; - if (strcmp(suffix, "css") == 0) - return HTTP_FLAG_CSS; - if (strcmp(suffix, "png") == 0) - return HTTP_FLAG_PNG; - if (strcmp(suffix, "gif") == 0) - return HTTP_FLAG_GIF; - if (strcmp(suffix, "jpg") == 0) - return HTTP_FLAG_JPG; - if (strcmp(suffix, "jpeg") == 0) - return HTTP_FLAG_JPG; - if (strcmp(suffix, "html") == 0) - return HTTP_FLAG_HTML; - if (strcmp(suffix, "txt") == 0) - return HTTP_FLAG_TEXT; - return 0; -} - static const char * const http_content_encoding(http_flags flags) { @@ -259,26 +235,28 @@ header = sdscatfmt(sdsempty(), "HTTP/%u.%u %u %s\r\n" - "Connection: Keep-Alive\r\n" - "Access-Control-Allow-Origin: *\r\n" - "Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type\r\n", + "%S: Keep-Alive\r\n", parser->http_major, parser->http_minor, - sts, http_status_mapping(sts)); + sts, http_status_mapping(sts), HEADER_CONNECTION); + header = sdscatfmt(header, + "%S: *\r\n" + "%S: %S\r\n", + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + HEADER_ACCESS_CONTROL_ALLOW_HEADERS, + HEADER_ACCESS_CONTROL_ALLOWED_HEADERS); if (sts == HTTP_STATUS_UNAUTHORIZED && client->u.http.realm) - header = sdscatfmt(header, "WWW-Authenticate: Basic realm=\"%S\"\r\n", - client->u.http.realm); + header = sdscatfmt(header, "%S: Basic realm=\"%S\"\r\n", + HEADER_WWW_AUTHENTICATE, client->u.http.realm); - if ((flags & HTTP_FLAG_STREAMING)) - header = sdscatfmt(header, "Transfer-encoding: %s\r\n", "chunked"); - - if (!(flags & HTTP_FLAG_STREAMING)) - header = sdscatfmt(header, "Content-Length: %u\r\n", length); + if ((flags & (HTTP_FLAG_STREAMING | HTTP_FLAG_NO_BODY))) + header = sdscatfmt(header, "Transfer-encoding: chunked\r\n"); + else + header = sdscatfmt(header, "%S: %u\r\n", HEADER_CONTENT_LENGTH, length); - header = sdscatfmt(header, - "Content-Type: %s%s\r\n" - "Date: %s\r\n\r\n", - http_content_type(flags), http_content_encoding(flags), + header = sdscatfmt(header, "Content-Type: %s%s\r\n", + http_content_type(flags), http_content_encoding(flags)); + header = sdscatfmt(header, "Date: %s\r\n\r\n", http_date_string(time(NULL), date, sizeof(date))); if (pmDebugOptions.http && pmDebugOptions.desperate) { @@ -288,8 +266,130 @@ return header; } +static sds +http_header_value(struct client *client, sds header) +{ + if (client->u.http.headers == NULL) + return NULL; + return (sds)dictFetchValue(client->u.http.headers, header); +} + +static sds +http_headers_allowed(sds headers) +{ + (void)headers; + return sdsdup(HEADER_ACCESS_CONTROL_ALLOWED_HEADERS); +} + +/* check whether the (preflight) method being proposed is acceptable */ +static int +http_method_allowed(sds value, http_options options) +{ + if (strcmp(value, "GET") == 0 && (options & HTTP_OPT_GET)) + return 1; + if (strcmp(value, "PUT") == 0 && (options & HTTP_OPT_PUT)) + return 1; + if (strcmp(value, "POST") == 0 && (options & HTTP_OPT_POST)) + return 1; + if (strcmp(value, "HEAD") == 0 && (options & HTTP_OPT_HEAD)) + return 1; + if (strcmp(value, "TRACE") == 0 && (options & HTTP_OPT_TRACE)) + return 1; + return 0; +} + +static char * +http_methods_string(char *buffer, size_t length, http_options options) +{ + char *p = buffer; + + /* ensure room for all options, spaces and comma separation */ + if (!options || length < 48) + return NULL; + + memset(buffer, 0, length); + if (options & HTTP_OPT_GET) + strcat(p, ", GET"); + if (options & HTTP_OPT_PUT) + strcat(p, ", PUT"); + if (options & HTTP_OPT_HEAD) + strcat(p, ", HEAD"); + if (options & HTTP_OPT_POST) + strcat(p, ", POST"); + if (options & HTTP_OPT_TRACE) + strcat(p, ", TRACE"); + if (options & HTTP_OPT_OPTIONS) + strcat(p, ", OPTIONS"); + return p + 2; /* skip leading comma+space */ +} + +static sds +http_response_trace(struct client *client) +{ + dictIterator *iterator; + dictEntry *entry; + sds result = sdsempty(); + + iterator = dictGetSafeIterator(client->u.http.headers); + while ((entry = dictNext(iterator)) != NULL) + result = sdscatfmt("%S: %S\r\n", dictGetKey(entry), dictGetVal(entry)); + dictReleaseIterator(iterator); + return result; +} + +static sds +http_response_access(struct client *client, http_code sts, http_options options) +{ + struct http_parser *parser = &client->u.http.parser; + char buffer[64]; + sds header, value, result; + + value = http_header_value(client, HEADER_ACCESS_CONTROL_REQUEST_METHOD); + if (value && http_method_allowed(value, options) == 0) + sts = HTTP_STATUS_METHOD_NOT_ALLOWED; + + parser->http_major = parser->http_minor = 1; + + header = sdscatfmt(sdsempty(), + "HTTP/%u.%u %u %s\r\n" + "%S: Keep-Alive\r\n", + parser->http_major, parser->http_minor, + sts, http_status_mapping(sts), HEADER_CONNECTION); + header = sdscatfmt(header, "%S: %u\r\n", HEADER_CONTENT_LENGTH, 0); + + if (sts >= HTTP_STATUS_OK && sts < HTTP_STATUS_BAD_REQUEST) { + if ((value = http_header_value(client, HEADER_ORIGIN))) + header = sdscatfmt(header, "%S: %S\r\n", + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, value); + + header = sdscatfmt(header, "%S: %s\r\n", + HEADER_ACCESS_CONTROL_ALLOW_METHODS, + http_methods_string(buffer, sizeof(buffer), options)); + + value = http_header_value(client, HEADER_ACCESS_CONTROL_REQUEST_HEADERS); + if (value && (result = http_headers_allowed(value)) != NULL) { + header = sdscatfmt(header, "%S: %S\r\n", + HEADER_ACCESS_CONTROL_ALLOW_HEADERS, result); + sdsfree(result); + } + } + if (sts == HTTP_STATUS_UNAUTHORIZED && client->u.http.realm) + header = sdscatfmt(header, "%S: Basic realm=\"%S\"\r\n", + HEADER_WWW_AUTHENTICATE, client->u.http.realm); + + header = sdscatfmt(header, "Date: %s\r\n\r\n", + http_date_string(time(NULL), buffer, sizeof(buffer))); + + if (pmDebugOptions.http && pmDebugOptions.desperate) { + fprintf(stderr, "access response to client %p\n", client); + fputs(header, stderr); + } + return header; +} + void -http_reply(struct client *client, sds message, http_code sts, http_flags type) +http_reply(struct client *client, sds message, + http_code sts, http_flags type, http_options options) { http_flags flags = client->u.http.flags; char length[32]; /* hex length */ @@ -313,6 +413,15 @@ suffix = sdsnewlen("0\r\n\r\n", 5); /* chunked suffix */ client->u.http.flags &= ~HTTP_FLAG_STREAMING; /* end of stream! */ + + } else if (flags & HTTP_FLAG_NO_BODY) { + if (client->u.http.parser.method == HTTP_OPTIONS) + buffer = http_response_access(client, sts, options); + else if (client->u.http.parser.method == HTTP_TRACE) + buffer = http_response_trace(client); + else /* HTTP_HEAD */ + buffer = http_response_header(client, 0, sts, type); + suffix = NULL; } else { /* regular non-chunked response - headers + response body */ if (client->buffer == NULL) { suffix = message; @@ -326,10 +435,11 @@ buffer = http_response_header(client, sdslen(suffix), sts, type); } - if (pmDebugOptions.http) { - fprintf(stderr, "HTTP response (client=%p)\n%s%s", - client, buffer, suffix); - } + if (pmDebugOptions.http) + fprintf(stderr, "HTTP %s response (client=%p)\n%s%s", + http_method_str(client->u.http.parser.method), + client, buffer, suffix ? suffix : ""); + client_write(client, buffer, suffix); } @@ -363,7 +473,7 @@ if (pmDebugOptions.desperate) fputs(message, stderr); } - http_reply(client, message, status, HTTP_FLAG_HTML); + http_reply(client, message, status, HTTP_FLAG_HTML, 0); } void @@ -371,6 +481,7 @@ { struct http_parser *parser = &client->u.http.parser; http_flags flags = client->u.http.flags; + const char *method; sds buffer, suffix; /* If the client buffer length is now beyond a set maximum size, @@ -390,16 +501,18 @@ buffer = sdsempty(); } /* prepend a chunked transfer encoding message length (hex) */ - buffer = sdscatprintf(buffer, "%lX\r\n", (unsigned long)sdslen(client->buffer)); + buffer = sdscatprintf(buffer, "%lX\r\n", + (unsigned long)sdslen(client->buffer)); suffix = sdscatfmt(client->buffer, "\r\n"); /* reset for next call - original released on I/O completion */ client->buffer = NULL; /* safe, as now held in 'suffix' */ if (pmDebugOptions.http) { - fprintf(stderr, "HTTP chunked buffer (client %p, len=%lu)\n%s" - "HTTP chunked suffix (client %p, len=%lu)\n%s", - client, (unsigned long)sdslen(buffer), buffer, - client, (unsigned long)sdslen(suffix), suffix); + method = http_method_str(client->u.http.parser.method); + fprintf(stderr, "HTTP %s chunk buffer (client %p, len=%lu)\n%s" + "HTTP %s chunk suffix (client %p, len=%lu)\n%s", + method, client, (unsigned long)sdslen(buffer), buffer, + method, client, (unsigned long)sdslen(suffix), suffix); } client_write(client, buffer, suffix); @@ -527,6 +640,8 @@ if (length == 0) return NULL; + if (length > MAX_PARAMS_SIZE) + return NULL; for (p = url; p < end; p++) { if (*p == '\0') break; @@ -558,6 +673,11 @@ struct servlet *servlet; sds url; + if (pmDebugOptions.http || pmDebugOptions.appl0) + fprintf(stderr, "HTTP %s %.*s\n", + http_method_str(client->u.http.parser.method), + (int)length, offset); + if (!(url = http_url_decode(offset, length, &client->u.http.parameters))) return NULL; for (servlet = proxy->servlets; servlet != NULL; servlet = servlet->next) { @@ -576,13 +696,24 @@ { struct client *client = (struct client *)request->data; struct servlet *servlet; + sds buffer; int sts; http_client_release(client); /* new URL, clean slate */ - - if ((servlet = servlet_lookup(client, offset, length)) != NULL) { + /* server options - https://tools.ietf.org/html/rfc7231#section-4.3.7 */ + if (length == 1 && *offset == '*' && + client->u.http.parser.method == HTTP_OPTIONS) { + buffer = http_response_access(client, HTTP_STATUS_OK, HTTP_SERVER_OPTIONS); + client_write(client, buffer, NULL); + } else if ((servlet = servlet_lookup(client, offset, length)) != NULL) { client->u.http.servlet = servlet; if ((sts = client->u.http.parser.status_code) == 0) { + if (client->u.http.parser.method == HTTP_OPTIONS || + client->u.http.parser.method == HTTP_TRACE || + client->u.http.parser.method == HTTP_HEAD) + client->u.http.flags |= HTTP_FLAG_NO_BODY; + else + client->u.http.flags &= ~HTTP_FLAG_NO_BODY; client->u.http.headers = dictCreate(&sdsOwnDictCallBacks, NULL); return 0; } @@ -616,6 +747,11 @@ if (client->u.http.parser.status_code || !client->u.http.headers) return 0; /* already in process of failing connection */ + if (dictSize(client->u.http.headers) >= MAX_HEADERS_SIZE) { + client->u.http.parser.status_code = + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + return 0; + } field = sdsnewlen(offset, length); if (pmDebugOptions.http) @@ -826,6 +962,17 @@ if (chunked_transfer_size < smallest_buffer_size) chunked_transfer_size = smallest_buffer_size; + HEADER_ACCESS_CONTROL_REQUEST_HEADERS = sdsnew("Access-Control-Request-Headers"); + HEADER_ACCESS_CONTROL_REQUEST_METHOD = sdsnew("Access-Control-Request-Method"); + HEADER_ACCESS_CONTROL_ALLOW_METHODS = sdsnew("Access-Control-Allow-Methods"); + HEADER_ACCESS_CONTROL_ALLOW_HEADERS = sdsnew("Access-Control-Allow-Headers"); + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = sdsnew("Access-Control-Allow-Origin"); + HEADER_ACCESS_CONTROL_ALLOWED_HEADERS = sdsnew("Accept, Accept-Language, Content-Language, Content-Type"); + HEADER_CONNECTION = sdsnew("Connection"); + HEADER_CONTENT_LENGTH = sdsnew("Content-Length"); + HEADER_ORIGIN = sdsnew("Origin"); + HEADER_WWW_AUTHENTICATE = sdsnew("WWW-Authenticate"); + register_servlet(proxy, &pmseries_servlet); register_servlet(proxy, &pmwebapi_servlet); } @@ -839,4 +986,15 @@ servlet->close(proxy); proxymetrics_close(proxy, METRICS_HTTP); + + sdsfree(HEADER_ACCESS_CONTROL_REQUEST_HEADERS); + sdsfree(HEADER_ACCESS_CONTROL_REQUEST_METHOD); + sdsfree(HEADER_ACCESS_CONTROL_ALLOW_METHODS); + sdsfree(HEADER_ACCESS_CONTROL_ALLOW_HEADERS); + sdsfree(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN); + sdsfree(HEADER_ACCESS_CONTROL_ALLOWED_HEADERS); + sdsfree(HEADER_CONNECTION); + sdsfree(HEADER_CONTENT_LENGTH); + sdsfree(HEADER_ORIGIN); + sdsfree(HEADER_WWW_AUTHENTICATE); } --- a/src/pmproxy/src/series.c 2020-02-25 17:47:56.000000000 +1100 +++ b/src/pmproxy/src/series.c 2020-06-11 13:10:57.398576470 +1000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Red Hat. + * Copyright (c) 2019-2020 Red Hat. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -15,8 +15,7 @@ #include typedef enum pmSeriesRestKey { - RESTKEY_NONE = 0, - RESTKEY_SOURCE, + RESTKEY_SOURCE = 1, RESTKEY_DESC, RESTKEY_INSTS, RESTKEY_LABELS, @@ -29,7 +28,8 @@ typedef struct pmSeriesRestCommand { const char *name; - unsigned int size; + unsigned int namelen : 16; + unsigned int options : 16; pmSeriesRestKey key; } pmSeriesRestCommand; @@ -39,7 +39,8 @@ pmSeriesFlags flags; pmSeriesTimeWindow window; uv_work_t loading; - unsigned int working; + unsigned int working : 1; + unsigned int options : 16; int nsids; pmSID *sids; pmSID sid; @@ -55,16 +56,25 @@ } pmSeriesBaton; static pmSeriesRestCommand commands[] = { - { .key = RESTKEY_QUERY, .name = "query", .size = sizeof("query")-1 }, - { .key = RESTKEY_DESC, .name = "descs", .size = sizeof("descs")-1 }, - { .key = RESTKEY_INSTS, .name = "instances", .size = sizeof("instances")-1 }, - { .key = RESTKEY_LABELS, .name = "labels", .size = sizeof("labels")-1 }, - { .key = RESTKEY_METRIC, .name = "metrics", .size = sizeof("metrics")-1 }, - { .key = RESTKEY_SOURCE, .name = "sources", .size = sizeof("sources")-1 }, - { .key = RESTKEY_VALUES, .name = "values", .size = sizeof("values")-1 }, - { .key = RESTKEY_LOAD, .name = "load", .size = sizeof("load")-1 }, - { .key = RESTKEY_PING, .name = "ping", .size = sizeof("ping")-1 }, - { .key = RESTKEY_NONE } + { .key = RESTKEY_QUERY, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "query", .namelen = sizeof("query")-1 }, + { .key = RESTKEY_DESC, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "descs", .namelen = sizeof("descs")-1 }, + { .key = RESTKEY_INSTS, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "instances", .namelen = sizeof("instances")-1 }, + { .key = RESTKEY_LABELS, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "labels", .namelen = sizeof("labels")-1 }, + { .key = RESTKEY_METRIC, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "metrics", .namelen = sizeof("metrics")-1 }, + { .key = RESTKEY_SOURCE, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "sources", .namelen = sizeof("sources")-1 }, + { .key = RESTKEY_VALUES, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "values", .namelen = sizeof("values")-1 }, + { .key = RESTKEY_LOAD, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "load", .namelen = sizeof("load")-1 }, + { .key = RESTKEY_PING, .options = HTTP_OPTIONS_GET, + .name = "ping", .namelen = sizeof("ping")-1 }, + { .name = NULL } /* sentinel */ }; /* constant string keys (initialized during servlet setup) */ @@ -78,8 +88,8 @@ static const char pmseries_success[] = "{\"success\":true}\r\n"; static const char pmseries_failure[] = "{\"success\":false}\r\n"; -static pmSeriesRestKey -pmseries_lookup_restkey(sds url) +static pmSeriesRestCommand * +pmseries_lookup_rest_command(sds url) { pmSeriesRestCommand *cp; const char *name; @@ -88,11 +98,11 @@ strncmp(url, "/series/", sizeof("/series/") - 1) == 0) { name = (const char *)url + sizeof("/series/") - 1; for (cp = &commands[0]; cp->name; cp++) { - if (strncmp(cp->name, name, cp->size) == 0) - return cp->key; + if (strncmp(cp->name, name, cp->namelen) == 0) + return cp; } } - return RESTKEY_NONE; + return NULL; } static void @@ -518,6 +528,7 @@ { pmSeriesBaton *baton = (pmSeriesBaton *)arg; struct client *client = baton->client; + http_options options = baton->options; http_flags flags = client->u.http.flags; http_code code; sds msg; @@ -545,7 +556,7 @@ msg = sdsnewlen(pmseries_failure, sizeof(pmseries_failure) - 1); flags |= HTTP_FLAG_JSON; } - http_reply(client, msg, code, flags); + http_reply(client, msg, code, flags, options); } static void @@ -555,6 +566,14 @@ fprintf(stderr, "series module setup (arg=%p)\n", arg); } +static void +pmseries_log(pmLogLevel level, sds message, void *arg) +{ + pmSeriesBaton *baton = (pmSeriesBaton *)arg; + + proxylog(level, message, baton->client->proxy); +} + static pmSeriesSettings pmseries_settings = { .callbacks.on_match = on_pmseries_match, .callbacks.on_desc = on_pmseries_desc, @@ -567,7 +586,7 @@ .callbacks.on_label = on_pmseries_label, .callbacks.on_done = on_pmseries_done, .module.on_setup = pmseries_setup, - .module.on_info = proxylog, + .module.on_info = pmseries_log, }; static void @@ -686,7 +705,6 @@ case RESTKEY_PING: break; - case RESTKEY_NONE: default: client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST; break; @@ -702,15 +720,16 @@ pmseries_request_url(struct client *client, sds url, dict *parameters) { pmSeriesBaton *baton; - pmSeriesRestKey key; + pmSeriesRestCommand *command; - if ((key = pmseries_lookup_restkey(url)) == RESTKEY_NONE) + if ((command = pmseries_lookup_rest_command(url)) == NULL) return 0; if ((baton = calloc(1, sizeof(*baton))) != NULL) { client->u.http.data = baton; baton->client = client; - baton->restkey = key; + baton->restkey = command->key; + baton->options = command->options; pmseries_setup_request_parameters(client, baton, parameters); } else { client->u.http.parser.status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; @@ -794,10 +813,12 @@ if (baton->query == NULL) { message = sdsnewlen(failed, sizeof(failed) - 1); - http_reply(client, message, HTTP_STATUS_BAD_REQUEST, HTTP_FLAG_JSON); + http_reply(client, message, HTTP_STATUS_BAD_REQUEST, + HTTP_FLAG_JSON, baton->options); } else if (baton->working) { message = sdsnewlen(loading, sizeof(loading) - 1); - http_reply(client, message, HTTP_STATUS_CONFLICT, HTTP_FLAG_JSON); + http_reply(client, message, HTTP_STATUS_CONFLICT, + HTTP_FLAG_JSON, baton->options); } else { uv_queue_work(client->proxy->events, &baton->loading, pmseries_load_work, pmseries_load_done); @@ -810,8 +831,17 @@ pmSeriesBaton *baton = (pmSeriesBaton *)client->u.http.data; int sts; - if (client->u.http.parser.status_code) + if (client->u.http.parser.status_code) { + on_pmseries_done(-EINVAL, baton); + return 1; + } + + if (client->u.http.parser.method == HTTP_OPTIONS || + client->u.http.parser.method == HTTP_TRACE || + client->u.http.parser.method == HTTP_HEAD) { + on_pmseries_done(0, baton); return 0; + } switch (baton->restkey) { case RESTKEY_QUERY: --- a/src/pmproxy/src/webapi.c 2020-04-17 15:39:17.000000000 +1000 +++ b/src/pmproxy/src/webapi.c 2020-06-11 13:10:57.399576484 +1000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Red Hat. + * Copyright (c) 2019-2020 Red Hat. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -18,8 +18,7 @@ #include "util.h" typedef enum pmWebRestKey { - RESTKEY_NONE = 0, - RESTKEY_CONTEXT, + RESTKEY_CONTEXT = 1, RESTKEY_METRIC, RESTKEY_FETCH, RESTKEY_INDOM, @@ -32,7 +31,8 @@ typedef struct pmWebRestCommand { const char *name; - unsigned int size; + unsigned int namelen : 16; + unsigned int options : 16; pmWebRestKey key; } pmWebRestCommand; @@ -47,6 +47,7 @@ sds password; /* from basic auth header */ unsigned int times : 1; unsigned int compat : 1; + unsigned int options : 16; unsigned int numpmids; unsigned int numvsets; unsigned int numinsts; @@ -56,21 +57,31 @@ } pmWebGroupBaton; static pmWebRestCommand commands[] = { - { .key = RESTKEY_CONTEXT, .name = "context", .size = sizeof("context")-1 }, - { .key = RESTKEY_PROFILE, .name = "profile", .size = sizeof("profile")-1 }, - { .key = RESTKEY_SCRAPE, .name = "metrics", .size = sizeof("metrics")-1 }, - { .key = RESTKEY_METRIC, .name = "metric", .size = sizeof("metric")-1 }, - { .key = RESTKEY_DERIVE, .name = "derive", .size = sizeof("derive")-1 }, - { .key = RESTKEY_FETCH, .name = "fetch", .size = sizeof("fetch")-1 }, - { .key = RESTKEY_INDOM, .name = "indom", .size = sizeof("indom")-1 }, - { .key = RESTKEY_STORE, .name = "store", .size = sizeof("store")-1 }, - { .key = RESTKEY_CHILD, .name = "children", .size = sizeof("children")-1 }, - { .key = RESTKEY_NONE } + { .key = RESTKEY_CONTEXT, .options = HTTP_OPTIONS_GET, + .name = "context", .namelen = sizeof("context")-1 }, + { .key = RESTKEY_PROFILE, .options = HTTP_OPTIONS_GET, + .name = "profile", .namelen = sizeof("profile")-1 }, + { .key = RESTKEY_SCRAPE, .options = HTTP_OPTIONS_GET, + .name = "metrics", .namelen = sizeof("metrics")-1 }, + { .key = RESTKEY_METRIC, .options = HTTP_OPTIONS_GET, + .name = "metric", .namelen = sizeof("metric")-1 }, + { .key = RESTKEY_DERIVE, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST, + .name = "derive", .namelen = sizeof("derive")-1 }, + { .key = RESTKEY_FETCH, .options = HTTP_OPTIONS_GET, + .name = "fetch", .namelen = sizeof("fetch")-1 }, + { .key = RESTKEY_INDOM, .options = HTTP_OPTIONS_GET, + .name = "indom", .namelen = sizeof("indom")-1 }, + { .key = RESTKEY_STORE, .options = HTTP_OPTIONS_GET, + .name = "store", .namelen = sizeof("store")-1 }, + { .key = RESTKEY_CHILD, .options = HTTP_OPTIONS_GET, + .name = "children", .namelen = sizeof("children")-1 }, + { .name = NULL } /* sentinel */ }; static pmWebRestCommand openmetrics[] = { - { .key = RESTKEY_SCRAPE, .name = "/metrics", .size = sizeof("/metrics")-1 }, - { .key = RESTKEY_NONE } + { .key = RESTKEY_SCRAPE, .options = HTTP_OPTIONS_GET, + .name = "/metrics", .namelen = sizeof("/metrics")-1 }, + { .name = NULL } /* sentinel */ }; static sds PARAM_NAMES, PARAM_NAME, PARAM_PMIDS, PARAM_PMID, @@ -78,8 +89,8 @@ PARAM_CONTEXT, PARAM_CLIENT; -static pmWebRestKey -pmwebapi_lookup_restkey(sds url, unsigned int *compat, sds *context) +static pmWebRestCommand * +pmwebapi_lookup_rest_command(sds url, unsigned int *compat, sds *context) { pmWebRestCommand *cp; const char *name, *ctxid = NULL; @@ -94,7 +105,7 @@ name++; } while (isdigit((int)(*name))); if (*name++ != '/') - return RESTKEY_NONE; + return NULL; *context = sdsnewlen(ctxid, name - ctxid - 1); } if (*name == '_') { @@ -102,13 +113,13 @@ *compat = 1; /* backward-compatibility mode */ } for (cp = &commands[0]; cp->name; cp++) - if (strncmp(cp->name, name, cp->size) == 0) - return cp->key; + if (strncmp(cp->name, name, cp->namelen) == 0) + return cp; } for (cp = &openmetrics[0]; cp->name; cp++) - if (strncmp(cp->name, url, cp->size) == 0) - return cp->key; - return RESTKEY_NONE; + if (strncmp(cp->name, url, cp->namelen) == 0) + return cp; + return NULL; } static void @@ -584,9 +595,10 @@ { pmWebGroupBaton *baton = (pmWebGroupBaton *)arg; struct client *client = (struct client *)baton->client; - sds quoted, msg; + http_options options = baton->options; http_flags flags = client->u.http.flags; http_code code; + sds quoted, msg; if (pmDebugOptions.series) fprintf(stderr, "%s: client=%p (sts=%d,msg=%s)\n", "on_pmwebapi_done", @@ -596,7 +608,9 @@ code = HTTP_STATUS_OK; /* complete current response with JSON suffix if needed */ if ((msg = baton->suffix) == NULL) { /* empty OK response */ - if (flags & HTTP_FLAG_JSON) { + if (flags & HTTP_FLAG_NO_BODY) { + msg = sdsempty(); + } else if (flags & HTTP_FLAG_JSON) { msg = sdsnewlen("{", 1); if (context) msg = sdscatfmt(msg, "\"context\":%S,", context); @@ -628,10 +642,18 @@ sdsfree(quoted); } - http_reply(client, msg, code, flags); + http_reply(client, msg, code, flags, options); client_put(client); } +static void +on_pmwebapi_info(pmLogLevel level, sds message, void *arg) +{ + pmWebGroupBaton *baton = (pmWebGroupBaton *)arg; + + proxylog(level, message, baton->client->proxy); +} + static pmWebGroupSettings pmwebapi_settings = { .callbacks.on_context = on_pmwebapi_context, .callbacks.on_metric = on_pmwebapi_metric, @@ -645,7 +667,7 @@ .callbacks.on_scrape_labels = on_pmwebapi_scrape_labels, .callbacks.on_check = on_pmwebapi_check, .callbacks.on_done = on_pmwebapi_done, - .module.on_info = proxylog, + .module.on_info = on_pmwebapi_info, }; /* @@ -734,7 +756,6 @@ client->u.http.flags |= HTTP_FLAG_JSON; break; - case RESTKEY_NONE: default: client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST; break; @@ -750,11 +771,11 @@ pmwebapi_request_url(struct client *client, sds url, dict *parameters) { pmWebGroupBaton *baton; - pmWebRestKey key; + pmWebRestCommand *command; unsigned int compat = 0; sds context = NULL; - if ((key = pmwebapi_lookup_restkey(url, &compat, &context)) == RESTKEY_NONE) { + if (!(command = pmwebapi_lookup_rest_command(url, &compat, &context))) { sdsfree(context); return 0; } @@ -762,7 +783,8 @@ if ((baton = calloc(1, sizeof(*baton))) != NULL) { client->u.http.data = baton; baton->client = client; - baton->restkey = key; + baton->restkey = command->key; + baton->options = command->options; baton->compat = compat; baton->context = context; pmwebapi_setup_request_parameters(client, baton, parameters); @@ -885,17 +907,27 @@ uv_loop_t *loop = client->proxy->events; uv_work_t *work; - /* fail early if something has already gone wrong */ - if (client->u.http.parser.status_code != 0) + /* take a reference on the client to prevent freeing races on close */ + client_get(client); + + if (client->u.http.parser.status_code) { + on_pmwebapi_done(NULL, -EINVAL, NULL, baton); return 1; + } + + if (client->u.http.parser.method == HTTP_OPTIONS || + client->u.http.parser.method == HTTP_TRACE || + client->u.http.parser.method == HTTP_HEAD) { + on_pmwebapi_done(NULL, 0, NULL, baton); + return 0; + } - if ((work = (uv_work_t *)calloc(1, sizeof(uv_work_t))) == NULL) + if ((work = (uv_work_t *)calloc(1, sizeof(uv_work_t))) == NULL) { + client_put(client); return 1; + } work->data = baton; - /* take a reference on the client to prevent freeing races on close */ - client_get(client); - /* submit command request to worker thread */ switch (baton->restkey) { case RESTKEY_CONTEXT: @@ -925,11 +957,10 @@ case RESTKEY_SCRAPE: uv_queue_work(loop, work, pmwebapi_scrape, pmwebapi_work_done); break; - case RESTKEY_NONE: default: + pmwebapi_work_done(work, -EINVAL); client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST; - client_put(client); - free(work); + on_pmwebapi_done(NULL, -EINVAL, NULL, baton); return 1; } return 0; --- a/src/pmproxy/src/http.h 2019-12-02 16:43:20.000000000 +1100 +++ b/src/pmproxy/src/http.h 2020-06-11 13:10:57.398576470 +1000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Red Hat. + * Copyright (c) 2019-2020 Red Hat. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -34,29 +34,39 @@ HTTP_FLAG_JSON = (1<<0), HTTP_FLAG_TEXT = (1<<1), HTTP_FLAG_HTML = (1<<2), - HTTP_FLAG_JS = (1<<3), - HTTP_FLAG_CSS = (1<<4), - HTTP_FLAG_ICO = (1<<5), - HTTP_FLAG_JPG = (1<<6), - HTTP_FLAG_PNG = (1<<7), - HTTP_FLAG_GIF = (1<<8), HTTP_FLAG_UTF8 = (1<<10), HTTP_FLAG_UTF16 = (1<<11), + HTTP_FLAG_NO_BODY = (1<<13), HTTP_FLAG_COMPRESS = (1<<14), HTTP_FLAG_STREAMING = (1<<15), /* maximum 16 for server.h */ } http_flags; +typedef enum http_options { + HTTP_OPT_GET = (1 << HTTP_GET), + HTTP_OPT_PUT = (1 << HTTP_PUT), + HTTP_OPT_HEAD = (1 << HTTP_HEAD), + HTTP_OPT_POST = (1 << HTTP_POST), + HTTP_OPT_TRACE = (1 << HTTP_TRACE), + HTTP_OPT_OPTIONS = (1 << HTTP_OPTIONS), + /* maximum 16 in command opts fields */ +} http_options; + +#define HTTP_COMMON_OPTIONS (HTTP_OPT_HEAD | HTTP_OPT_TRACE | HTTP_OPT_OPTIONS) +#define HTTP_OPTIONS_GET (HTTP_COMMON_OPTIONS | HTTP_OPT_GET) +#define HTTP_OPTIONS_PUT (HTTP_COMMON_OPTIONS | HTTP_OPT_PUT) +#define HTTP_OPTIONS_POST (HTTP_COMMON_OPTIONS | HTTP_OPT_POST) +#define HTTP_SERVER_OPTIONS (HTTP_OPTIONS_GET | HTTP_OPT_PUT | HTTP_OPT_POST) + typedef unsigned int http_code; extern void http_transfer(struct client *); -extern void http_reply(struct client *, sds, http_code, http_flags); +extern void http_reply(struct client *, sds, http_code, http_flags, http_options); extern void http_error(struct client *, http_code, const char *); extern int http_decode(const char *, size_t, sds); extern const char *http_status_mapping(http_code); extern const char *http_content_type(http_flags); -extern http_flags http_suffix_type(const char *); extern sds http_get_buffer(struct client *); extern void http_set_buffer(struct client *, sds, http_flags); --- a/qa/1837 1970-01-01 10:00:00.000000000 +1000 +++ b/qa/1837 2020-06-11 13:10:57.396576440 +1000 @@ -0,0 +1,55 @@ +#!/bin/sh +# PCP QA Test No. 1837 +# Exercise PMWEBAPI handling server OPTIONS. +# +# Copyright (c) 2020 Red Hat. All Rights Reserved. +# + +seq=`basename $0` +echo "QA output created by $seq" + +# get standard environment, filters and checks +. ./common.product +. ./common.filter +. ./common.check + +_check_series +which curl >/dev/null 2>&1 || _notrun "No curl binary installed" +curl --request-targets 2>&1 | grep -q 'requires parameter' && \ + _notrun "Test requires curl --request-targets option" + +status=1 # failure is the default! +$sudo rm -rf $tmp.* $seq.full +trap "cd $here; _cleanup; exit \$status" 0 1 2 3 15 + +pmproxy_was_running=false +[ -f $PCP_RUN_DIR/pmproxy.pid ] && pmproxy_was_running=true +echo "pmproxy_was_running=$pmproxy_was_running" >>$here/$seq.full + +_cleanup() +{ + if $pmproxy_was_running + then + echo "Restart pmproxy ..." >>$here/$seq.full + _service pmproxy restart >>$here/$seq.full 2>&1 + _wait_for_pmproxy + else + echo "Stopping pmproxy ..." >>$here/$seq.full + _service pmproxy stop >>$here/$seq.full 2>&1 + fi + $sudo rm -f $tmp.* +} + +# real QA test starts here +_service pmproxy restart >/dev/null 2>&1 + +curl -isS --request-target "*" -X OPTIONS http://localhost:44322 \ + 2>&1 | tee -a $here/$seq.full | _webapi_header_filter + +echo >>$here/$seq.full +echo "=== pmproxy log ===" >>$here/$seq.full +cat $PCP_LOG_DIR/pmproxy/pmproxy.log >>$here/$seq.full + +# success, all done +status=0 +exit --- a/qa/1837.out 1970-01-01 10:00:00.000000000 +1000 +++ b/qa/1837.out 2020-06-11 13:10:57.397576455 +1000 @@ -0,0 +1,6 @@ +QA output created by 1837 + +Access-Control-Allow-Methods: GET, PUT, HEAD, POST, TRACE, OPTIONS +Content-Length: 0 +Date: DATE +HTTP/1.1 200 OK --- a/qa/780 2020-04-14 14:41:41.000000000 +1000 +++ b/qa/780 2020-06-11 13:10:57.397576455 +1000 @@ -1,8 +1,8 @@ #!/bin/sh # PCP QA Test No. 780 -# Exercise PMWEBAPI Access-Control-Allow-Origin HTTP header. +# Exercise PMWEBAPI CORS headers. # -# Copyright (c) 2014,2019 Red Hat. +# Copyright (c) 2014,2019-2020 Red Hat. # seq=`basename $0` @@ -16,7 +16,6 @@ _check_series which curl >/dev/null 2>&1 || _notrun "No curl binary installed" -signal=$PCP_BINADM_DIR/pmsignal status=1 # failure is the default! $sudo rm -rf $tmp.* $seq.full trap "cd $here; _cleanup; exit \$status" 0 1 2 3 15 @@ -39,13 +38,21 @@ $sudo rm -f $tmp.* } -unset http_proxy -unset HTTP_PROXY - # real QA test starts here _service pmproxy restart >/dev/null 2>&1 -curl -s -S "http://localhost:44323/pmapi/context" -I | _webapi_header_filter +echo "=== Basic" | tee -a $here/$seq.full +curl -IsS "http://localhost:44323/pmapi/context" | _webapi_header_filter + +echo "=== Preflight" | tee -a $here/$seq.full +curl -isS -X OPTIONS "http://localhost:44323/series/query?expr=hinv*" | _webapi_header_filter + +echo "=== OK Request Method" | tee -a $here/$seq.full +curl -isS -X OPTIONS -H "Origin: http://example.com" -H "Access-Control-Request-Method: GET" "http://localhost:44323/pmapi/context" | _webapi_header_filter + +echo "=== Bad Request Method" | tee -a $here/$seq.full +curl -isS -X OPTIONS -H "Origin: http://example.com" -H "Access-Control-Request-Method: BAD" "http://localhost:44323/pmapi/context" | _webapi_header_filter + echo >>$here/$seq.full echo "=== pmproxy log ===" >>$here/$seq.full cat $PCP_LOG_DIR/pmproxy/pmproxy.log >>$here/$seq.full --- a/qa/780.out 2020-03-23 09:47:47.000000000 +1100 +++ b/qa/780.out 2020-06-11 13:10:57.397576455 +1000 @@ -1,8 +1,27 @@ QA output created by 780 +=== Basic Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type Access-Control-Allow-Origin: * -Content-Length: SIZE Content-Type: application/json Date: DATE HTTP/1.1 200 OK +Transfer-encoding: chunked +=== Preflight + +Access-Control-Allow-Methods: GET, HEAD, POST, TRACE, OPTIONS +Content-Length: 0 +Date: DATE +HTTP/1.1 200 OK +=== OK Request Method + +Access-Control-Allow-Methods: GET, HEAD, TRACE, OPTIONS +Access-Control-Allow-Origin: http://example.com +Content-Length: 0 +Date: DATE +HTTP/1.1 200 OK +=== Bad Request Method + +Content-Length: 0 +Date: DATE +HTTP/1.1 405 Method Not Allowed --- a/qa/common.check 2020-05-20 10:51:37.000000000 +1000 +++ b/qa/common.check 2020-06-11 13:10:57.397576455 +1000 @@ -2696,7 +2696,7 @@ tee -a $here/$seq.full \ | col -b \ | sed \ - -e 's/^\(Content-Length:\) [0-9][0-9]*/\1 SIZE/g' \ + -e 's/^\(Content-Length:\) [1-9][0-9]*/\1 SIZE/g' \ -e 's/^\(Date:\).*/\1 DATE/g' \ -e 's/\(\"context\":\) [0-9][0-9]*/\1 CTXID/g' \ -e '/^Connection: Keep-Alive/d' \ --- a/qa/group 2020-05-28 09:15:22.000000000 +1000 +++ b/qa/group 2020-06-11 13:10:57.397576455 +1000 @@ -1757,6 +1757,7 @@ 1724 pmda.bpftrace local python 1768 pmfind local 1793 pmrep pcp2xxx python local +1837 pmproxy local 1855 pmda.rabbitmq local 1896 pmlogger logutil pmlc local 4751 libpcp threads valgrind local pcp --- a/qa/1211.out 2020-01-20 16:53:42.000000000 +1100 +++ b/qa/1211.out 2020-06-11 13:10:57.399576484 +1000 @@ -507,9 +507,11 @@ Perform simple source-based query ... Error handling - descriptor for bad series identifier -pmseries: [Error] no descriptor for series identifier no.such.identifier no.such.identifier + PMID: PM_ID_NULL + Data Type: ??? InDom: unknown 0xffffffff + Semantics: unknown Units: unknown Error handling - metric name for bad series identifier --- a/src/libpcp_web/src/query.c 2020-01-20 15:43:31.000000000 +1100 +++ b/src/libpcp_web/src/query.c 2020-06-11 13:10:57.399576484 +1000 @@ -1938,11 +1938,15 @@ return -EPROTO; } - /* sanity check - were we given an invalid series identifier? */ + /* were we given a non-metric series identifier? (e.g. an instance) */ if (elements[0]->type == REDIS_REPLY_NIL) { - infofmt(msg, "no descriptor for series identifier %s", series); - batoninfo(baton, PMLOG_ERROR, msg); - return -EINVAL; + desc->indom = sdscpylen(desc->indom, "unknown", 7); + desc->pmid = sdscpylen(desc->pmid, "PM_ID_NULL", 10); + desc->semantics = sdscpylen(desc->semantics, "unknown", 7); + desc->source = sdscpylen(desc->source, "unknown", 7); + desc->type = sdscpylen(desc->type, "unknown", 7); + desc->units = sdscpylen(desc->units, "unknown", 7); + return 0; } if (extract_string(baton, series, elements[0], &desc->indom, "indom") < 0)