[master] 144cedfd9 varnishncsa: Add %{X:[first|last]}i and %{X[:first|last]}o

Walid Boudebouda walid.boudebouda at gmail.com
Mon Sep 15 08:21:05 UTC 2025


commit 144cedfd9b374cf9d5c59fe3e6db4cd0e54dbac3
Author: Walid Boudebouda <walid.boudebouda at gmail.com>
Date:   Wed Sep 3 19:20:27 2025 +0200

    varnishncsa: Add %{X:[first|last]}i and %{X[:first|last]}o
    
    This allows to recover previous behaviour where headers set in VCL
    were captured by %{x}i and %{x}o. The new behaviour still remains the
    default when last and first are not specified.
    
    Unset is deliberately ignored in both cases, as it feels like the most
    appropriate thing to do.
    
    Closes #4385

diff --git a/bin/varnishncsa/varnishncsa.c b/bin/varnishncsa/varnishncsa.c
index 62c08c556..488dee711 100644
--- a/bin/varnishncsa/varnishncsa.c
+++ b/bin/varnishncsa/varnishncsa.c
@@ -92,6 +92,14 @@ enum e_frag {
 	F__MAX,
 };
 
+enum format_policy {
+	FMTPOL_INTERNAL = 1,
+	FMTPOL_REQ,
+	FMTPOL_RESP,
+	FMTPOL_FIRST,
+	FMTPOL_LAST
+};
+
 struct fragment {
 	uint64_t		gen;
 	const char		*b, *e;
@@ -121,6 +129,7 @@ struct watch {
 	char			*key;
 	int			keylen;
 	struct fragment		frag;
+	enum format_policy	match;
 };
 VTAILQ_HEAD(watch_head, watch);
 
@@ -163,12 +172,6 @@ static struct ctx {
 	int			recv_compl;
 } CTX;
 
-enum format_policy {
-        FMTPOL_INTERNAL,
-        FMTPOL_REQ,
-        FMTPOL_RESP,
-};
-
 static void parse_format(const char *format);
 
 static unsigned
@@ -179,6 +182,11 @@ frag_needed(const struct fragment *frag, enum format_policy fp)
 	is_first = CTX.gen != frag->gen;
 
 	switch (fp) {
+	case FMTPOL_LAST:
+		want_frag = 1;
+		want_first = 0;
+		break;
+	case FMTPOL_FIRST:
 	case FMTPOL_INTERNAL:
 		want_first = 1;
 		want_frag = 1;
@@ -593,11 +601,25 @@ addf_hdr(struct watch_head *head, const char *key)
 {
 	struct watch *w;
 	struct format *f;
+	char *match;
 
 	AN(head);
 	AN(key);
 	ALLOC_OBJ(w, WATCH_MAGIC);
 	AN(w);
+
+	match = strchr(key, ':');
+	if (match != NULL) {
+		match++;
+		if (!strncmp(match, "first", 5))
+			w->match = FMTPOL_FIRST;
+		else if (!strncmp(match, "last", 4))
+			w->match = FMTPOL_LAST;
+		else
+			VUT_Error(vut, 1, "Unknown match rule :%s", match);
+		match[-1] = '\0';
+	}
+
 	w->keylen = asprintf(&w->key, "%s:", key);
 	assert(w->keylen > 0);
 	VTAILQ_INSERT_TAIL(head, w, list);
@@ -935,9 +957,10 @@ frag_line(enum format_policy fp, const char *b, const char *e,
     struct fragment *f)
 {
 
-	if (!frag_needed(f, fp))
+	if (!frag_needed(f, fp)) {
 		/* We only grab the same matching record once */
 		return;
+	}
 
 	if (e == NULL)
 		e = b + strlen(b);
@@ -966,7 +989,14 @@ process_hdr(enum format_policy fp, const struct watch_head *head, const char *b,
 		CHECK_OBJ_NOTNULL(w, WATCH_MAGIC);
 		if (!isprefix(w->key, w->keylen, b, e, &p))
 			continue;
-		if (unset) {
+
+		if (w->match) {
+			assert(w->match == FMTPOL_FIRST ||
+			    w->match == FMTPOL_LAST);
+			fp = w->match;
+		}
+
+		if (unset && !w->match) {
 			frag_line(fp, CTX.missing_string,
 			    NULL,
 			    &w->frag);
diff --git a/bin/varnishtest/tests/u00020.vtc b/bin/varnishtest/tests/u00020.vtc
index 795ad4e2d..e91d8b556 100644
--- a/bin/varnishtest/tests/u00020.vtc
+++ b/bin/varnishtest/tests/u00020.vtc
@@ -43,6 +43,15 @@ shell {
 	diff -u expected_sb.txt ncsa_sb.txt
 }
 
+shell {
+	varnishncsa -n ${v1_name} -d -b -F '%{notsent:first}i %{notsent:last}i %{unset:first}i %{unset:last}i' > ncsa_sb.txt
+
+	cat >expected_sb.txt <<-EOF
+	notsent notsent client toolate
+	EOF
+	diff -u expected_sb.txt ncsa_sb.txt
+}
+
 varnish v1 -stop
 
 # Test things we receive from the backend
@@ -78,6 +87,15 @@ shell {
 	diff -u expected_rb.txt ncsa_rb.txt
 }
 
+shell {
+	varnishncsa -n ${v1_name} -d -b -F '%{beresp:first}o %{beresp:last}o %{unset:first}o %{unset:last}o' > ncsa_rb.txt
+
+	cat >expected_rb.txt <<-EOF
+	origin vbr-updated origin origin
+	EOF
+	diff -u expected_rb.txt ncsa_rb.txt
+}
+
 varnish v1 -stop
 
 # Test things we send to the client
@@ -118,6 +136,15 @@ shell {
 	diff -u expected_sc.txt ncsa_sc.txt
 }
 
+shell {
+	varnishncsa -n ${v1_name} -d -c -F '%{resp:first}o %{resp:last}o %{unset:first}o %{unset:last}o' > ncsa_sc.txt
+
+	cat >expected_sc.txt <<-EOF
+	vbr-updated deliver-updated origin origin
+	EOF
+	diff -u expected_sc.txt ncsa_sc.txt
+}
+
 varnish v1 -stop
 
 # Test things we receive from the client
@@ -144,6 +171,7 @@ varnish v1 -vcl+backend {
 		set req.url = "/hash-url?q=hashQuerry";
 		set req.http.Authorization = "basic aGFzaDpwYXNz";
 		set req.http.notreceived = "hash";
+		set req.http.unset = "hash";
 	}
 
 } -start
@@ -166,3 +194,12 @@ shell {
 	EOF
 	diff -u expected_rc.txt ncsa_rc.txt
 }
+
+shell {
+	varnishncsa -n ${v1_name} -d -c -F '%{notreceived:first}i %{notreceived:last}i %{unset:first}i %{unset:last}i' > ncsa_rc.txt
+
+	cat >expected_rc.txt <<-EOF
+	recv hash client hash
+	EOF
+	diff -u expected_rc.txt ncsa_rc.txt
+}


More information about the varnish-commit mailing list