MINOR: http_ext: add rfc7239_n2np converter

Adding new http converter: rfc7239_n2np.

Takes a string representing 7239 forwarded header node (extracted from
either 'for' or 'by' 7239 header fields) as input and translates it
to either unsigned integer or ('_' prefixed obfuscated identifier),
according to 7239RFC.

  Example:
    # extract 'by' field from forwarded header, extract node port from
    # resulting node identifier and store the result in req.fnp
    http-request set-var(req.fnp) req.hdr(forwarded),rfc7239_field(by),rfc7239_n2np
    #input: "by=\"127.0.0.1:9999\""
    #  output: 9999
    #input: "by=\"_name:_port\""
    #  output: "_port"

Depends on:
  - "MINOR: http_ext: introduce http ext converters"
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 37c2d33..b01b30a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -17268,6 +17268,21 @@
     #input: "for=\"_name:_port\""
     #  output: "_name"
 
+rfc7239_n2np
+  Converts RFC7239 node (provided by 'for' or 'by' 7239 header fields)
+  into its corresponding nodeport final form:
+    - unsigned integer
+    - '_obfs' identifier
+
+  Example:
+    # extract 'by' field from forwarded header, extract node port from
+    # resulting node identifier and store the result in req.fnp
+    http-request set-var(req.fnp) req.hdr(forwarded),rfc7239_field(by),rfc7239_n2np
+    #input: "by=\"127.0.0.1:9999\""
+    #  output: 9999
+    #input: "by=\"_name:_port\""
+    #  output: "_port"
+
 add(<value>)
   Adds <value> to the input value of type signed integer, and returns the
   result as a signed integer. <value> can be a numeric value or a variable
diff --git a/src/http_ext.c b/src/http_ext.c
index 3681bd0..62aa55d 100644
--- a/src/http_ext.c
+++ b/src/http_ext.c
@@ -1480,11 +1480,47 @@
 	return 1;
 }
 
+/* input: substring representing 7239 forwarded header node
+ * output: forwarded header nodeport translated to either
+ * integer or str for obfuscated ('_' prefix)
+ */
+static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, void *private)
+{
+	struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
+	struct forwarded_header_node ctx;
+	struct buffer *output;
+
+	if (http_7239_extract_node(&input, &ctx, 1) == 0)
+		return 0; /* could not extract node */
+
+	switch (ctx.nodeport.type) {
+		case FORWARDED_HEADER_UNK:
+			return 0; /* not provided */
+		case FORWARDED_HEADER_OBFS:
+			output = get_trash_chunk();
+			chunk_appendf(output, "_"); /* append obfs prefix */
+			chunk_istcat(output, ctx.nodeport.obfs);
+			smp->flags &= ~SMP_F_CONST;
+			smp->data.type = SMP_T_STR;
+			smp->data.u.str = *output;
+			break;
+		case FORWARDED_HEADER_PORT:
+			smp->data.type = SMP_T_SINT;
+			smp->data.u.sint = ctx.nodeport.port;
+			break;
+		default:
+			return 0; /* unsupported */
+	}
+
+	return 1;
+}
+
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
 	{ "rfc7239_is_valid",  sample_conv_7239_valid,   0,                NULL,   SMP_T_STR,  SMP_T_BOOL},
 	{ "rfc7239_field",     sample_conv_7239_field,   ARG1(1,STR),      NULL,   SMP_T_STR,  SMP_T_STR},
 	{ "rfc7239_n2nn",      sample_conv_7239_n2nn,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
+	{ "rfc7239_n2np",      sample_conv_7239_n2np,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
 	{ NULL, NULL, 0, 0, 0 },
 }};