Blame SOURCES/redhat-bugzilla-1792971.patch

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