[http] Allow issuing requests with an explicitly specified HTTP method

The design of IMDSv2 within both AWS and Alibaba Cloud requires the
client to obtain a temporary token via an HTTP PUT request.  There is
no authentication on this request and there is no associated request
body: the requirement to use PUT exists solely to reduce the attack
surface for SSRF attacks (since vulnerable servers are much more
likely to be able to be tricked into issuing a GET request than a PUT
request).

iPXE can currently issue requests using HTTP GET (if the request body
is empty) or HTTP POST (if the request body includes form parameters).
There is no support for issuing a PUT request, or for allowing a
script to explicitly specify the HTTP method.

Add a "--method" option to the "params" command to allow an arbitrary
request method name to be specified, and use this as the HTTP request
method.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/core/params.c b/src/core/params.c
index b14acca..bd96501 100644
--- a/src/core/params.c
+++ b/src/core/params.c
@@ -83,12 +83,16 @@
  * Create request parameter list
  *
  * @v name		Parameter list name (may be NULL)
+ * @v method		Method name (may be NULL)
  * @ret params		Parameter list, or NULL on failure
  */
-struct parameters * create_parameters ( const char *name ) {
+struct parameters * create_parameters ( const char *name,
+					const char *method ) {
 	struct parameters *params;
 	size_t name_len;
+	size_t method_len;
 	char *name_copy;
+	char *method_copy;
 
 	/* Destroy any existing parameter list of this name */
 	params = find_parameters ( name );
@@ -99,23 +103,33 @@
 
 	/* Allocate parameter list */
 	name_len = ( name ? ( strlen ( name ) + 1 /* NUL */ ) : 0 );
-	params = zalloc ( sizeof ( *params ) + name_len );
+	method_len = ( method ? ( strlen ( method ) + 1 /* NUL */ ) : 0 );
+	params = zalloc ( sizeof ( *params ) + name_len + method_len );
 	if ( ! params )
 		return NULL;
 	ref_init ( &params->refcnt, free_parameters );
-	name_copy = ( ( void * ) ( params + 1 ) );
+	name_copy = ( ( ( void * ) params ) + sizeof ( *params ) );
+	method_copy = ( ( ( void * ) name_copy ) + name_len );
 
 	/* Populate parameter list */
 	if ( name ) {
 		strcpy ( name_copy, name );
 		params->name = name_copy;
 	}
+	if ( method ) {
+		strcpy ( method_copy, method );
+		params->method = method_copy;
+	}
 	INIT_LIST_HEAD ( &params->entries );
 
 	/* Add to list of parameter lists */
 	list_add_tail ( &params->list, &parameters );
 
 	DBGC ( params, "PARAMS \"%s\" created\n", params->name );
+	if ( params->method ) {
+		DBGC ( params, "PARAMS \"%s\" method is \"%s\"\n",
+		       params->name, params->method );
+	}
 	return params;
 }
 
diff --git a/src/hci/commands/param_cmd.c b/src/hci/commands/param_cmd.c
index ed57c5e..aec3895 100644
--- a/src/hci/commands/param_cmd.c
+++ b/src/hci/commands/param_cmd.c
@@ -41,6 +41,8 @@
 struct params_options {
 	/** Name */
 	char *name;
+	/** Method */
+	char *method;
 	/** Delete */
 	int delete;
 };
@@ -49,6 +51,8 @@
 static struct option_descriptor params_opts[] = {
 	OPTION_DESC ( "name", 'n', required_argument,
 		      struct params_options, name, parse_string ),
+	OPTION_DESC ( "method", 'm', required_argument,
+		      struct params_options, method, parse_string ),
 	OPTION_DESC ( "delete", 'd', no_argument,
 		      struct params_options, delete, parse_flag ),
 };
@@ -74,7 +78,7 @@
 		return rc;
 
 	/* Create parameter list */
-	params = create_parameters ( opts.name );
+	params = create_parameters ( opts.name, opts.method );
 	if ( ! params )
 		return -ENOMEM;
 
diff --git a/src/include/ipxe/http.h b/src/include/ipxe/http.h
index e84a752..e96c2c8 100644
--- a/src/include/ipxe/http.h
+++ b/src/include/ipxe/http.h
@@ -102,9 +102,16 @@
 	const char *name;
 };
 
-extern struct http_method http_head;
-extern struct http_method http_get;
-extern struct http_method http_post;
+/** HTTP method table */
+#define HTTP_METHODS __table ( struct http_method, "http_methods" )
+
+/** Declare an HTTP method */
+#define __http_method __table_entry ( HTTP_METHODS, 01 )
+
+extern struct http_method http_head __http_method;
+extern struct http_method http_get __http_method;
+extern struct http_method http_post __http_method;
+extern struct http_method http_put __http_method;
 
 /******************************************************************************
  *
diff --git a/src/include/ipxe/params.h b/src/include/ipxe/params.h
index 6400838..bb7420b 100644
--- a/src/include/ipxe/params.h
+++ b/src/include/ipxe/params.h
@@ -21,6 +21,8 @@
 	struct list_head list;
 	/** Name */
 	const char *name;
+	/** Request method */
+	const char *method;
 	/** Parameters */
 	struct list_head entries;
 };
@@ -85,7 +87,8 @@
 	list_for_each_entry ( (param), &(params)->entries, list )
 
 extern struct parameters * find_parameters ( const char *name );
-extern struct parameters * create_parameters ( const char *name );
+extern struct parameters * create_parameters ( const char *name,
+					       const char *method );
 extern struct parameter * add_parameter ( struct parameters *params,
 					  const char *key, const char *value,
 					  unsigned int flags );
diff --git a/src/net/tcp/httpcore.c b/src/net/tcp/httpcore.c
index 1180faa..7dbb0e3 100644
--- a/src/net/tcp/httpcore.c
+++ b/src/net/tcp/httpcore.c
@@ -136,20 +136,43 @@
  */
 
 /** HTTP HEAD method */
-struct http_method http_head = {
+struct http_method http_head __http_method = {
 	.name = "HEAD",
 };
 
 /** HTTP GET method */
-struct http_method http_get = {
+struct http_method http_get __http_method = {
 	.name = "GET",
 };
 
 /** HTTP POST method */
-struct http_method http_post = {
+struct http_method http_post __http_method = {
 	.name = "POST",
 };
 
+/** HTTP PUT method */
+struct http_method http_put __http_method = {
+	.name = "PUT",
+};
+
+/**
+ * Identify HTTP method
+ *
+ * @v name		Method name
+ * @ret method		HTTP method, or NULL if not known
+ */
+static struct http_method * http_method ( const char *name ) {
+	struct http_method *method;
+
+	/* Identify method */
+	for_each_table_entry ( method, HTTP_METHODS ) {
+		if ( strcasecmp ( name, method->name ) == 0 )
+			return method;
+	}
+
+	return NULL;
+}
+
 /******************************************************************************
  *
  * Utility functions
@@ -2025,6 +2048,17 @@
 		data = NULL;
 	}
 
+	/* Use explicitly requested method name if applicable */
+	if ( params && params->method ) {
+		method = http_method ( params->method );
+		if ( ! method ) {
+			DBGC ( uri, "HTTP unsupported method \"%s\"\n",
+			       params->method );
+			rc = -ENOTSUP;
+			goto err_method;
+		}
+	}
+
 	/* Construct request content */
 	content.type = type;
 	content.data = data;
@@ -2035,6 +2069,7 @@
 		goto err_open;
 
  err_open:
+ err_method:
 	free ( data );
  err_alloc:
 	return rc;
diff --git a/src/tests/uri_test.c b/src/tests/uri_test.c
index fa46e22..96942c3 100644
--- a/src/tests/uri_test.c
+++ b/src/tests/uri_test.c
@@ -455,7 +455,7 @@
 	struct uri *dup;
 
 	/* Create parameter list */
-	params = create_parameters ( test->name );
+	params = create_parameters ( test->name, NULL );
 	okx ( params != NULL, file, line );
 	if ( params ) {
 		for ( list = test->list ; list->key ; list++ ) {