diff -pru a/dpi.c b/dpi.c
--- a/dpi.c	2026-06-23 23:37:55.936722737 +0200
+++ b/dpi.c	2026-07-02 01:31:19.010575476 +0200
@@ -29,15 +29,16 @@ static void check_auth(void) {
 	if (key != local_key) errx(1, "wrong dillo key");
 }
 
-static void get_url(char *url_buf, size_t url_len) {
+static void get_url(char *url_buf, size_t url_len, char **dst) {
 	char buf[21];
 	size_t len;
-	int rc;
-	size_t i;
+	int rc, sc;
+	size_t i, j;
 
 	rc = read_all(STDIN_FILENO, buf, sizeof(buf));
 	if (rc < 0) err(1, "read open_url");
-	if (strncmp(buf, "<cmd='open_url' url='", 21)) {
+	sc = strncmp(buf, "<cmd='save_url' url='", 21); 
+	if (strncmp(buf, "<cmd='open_url' url='", 21) && sc) {
 		err(1, "bad open_url cmd: %.*s", (int) sizeof(buf), buf);
 	}
 	len = url_len;
@@ -47,15 +48,23 @@ static void get_url(char *url_buf, size_
 	for (i = 0; i < len-1; i++) {
 		if (url_buf[i] == '\'' && url_buf[i+1] == ' ') break;
 	}
+	j = i;
+	if ( !sc && !strncmp(url_buf + i, "' destination='", 15)) {
+		*dst = url_buf + i + 15;
+		for (i = i + 15; i < len-1; i++) {
+			if (url_buf[i] == '\'' && url_buf[i+1] == ' ') break;
+		}
+	}
 	if (i > len-3 || strncmp(url_buf + i, "' '>", 4)) {
 		err(1, "bad url end: %.*s", (int) len, url_buf);
 	}
 	url_buf[i] = '\0';
+	url_buf[j] = '\0';
 }
 
-void dpi_read_request(char *url_buf, size_t url_len) {
+void dpi_read_request(char *url_buf, size_t url_len, char **dst) {
 	check_auth();
-	get_url(url_buf, url_len);
+	get_url(url_buf, url_len, dst);
 }
 
 void dpi_send_status(const char *status) {
diff -pru a/dpi.h b/dpi.h
--- a/dpi.h	2026-06-23 23:37:55.936722737 +0200
+++ b/dpi.h	2026-07-02 01:31:19.010575476 +0200
@@ -2,6 +2,6 @@
 
 #include <stddef.h>
 
-void dpi_read_request(char *url_buf, size_t url_len);
+void dpi_read_request(char *url_buf, size_t url_len, char **dst);
 void dpi_send_status(const char *status);
 void dpi_send_header(const char *url, const char *content_type);
diff -pru a/gopher.filter.dpi.c b/gopher.filter.dpi.c
--- a/gopher.filter.dpi.c	2026-06-23 23:37:55.936722737 +0200
+++ b/gopher.filter.dpi.c	2026-07-02 01:31:19.010575476 +0200
@@ -10,6 +10,10 @@
 #include "dpi.h"
 #include "io.h"
 
+
+static const char *detect_image_content_type(const char buf[4]);
+
+
 static void respond_errstr(const char *errstr, const char *fmt, ...) {
 	va_list ap;
 	int err = errno;
@@ -375,6 +379,61 @@ static void read_data(int s) {
 	}
 }
 
+static void save_url(int s, const char *url, char type, const char *dst) {
+	char buf[4096];
+	char ibuf[4];
+	const char *ct = "text/plain";
+	size_t len = sizeof(buf);
+	FILE *log_fp, *data_fp;
+	int rc, fd;
+
+	snprintf(buf, len, "%s.log.tmp", dst);
+	if ((log_fp = fopen(buf, "wb")) == NULL) {
+		warn("save_url open log '%s'", buf);
+	}
+	fprintf(log_fp, "save_url <dpi cmd='save_url' url='%s' " \
+			"destination='%s' '>\n\n", url, dst);
+	fflush(log_fp);
+	/* always (re)write from 0 */
+	data_fp = fopen(dst, "wb");
+	if (data_fp == NULL) {
+		fprintf(log_fp, 
+			"save_url not ok: Can't open destination '%s'\n" \
+			"save_url END\n\n", dst);
+		fflush(log_fp);
+		fclose(log_fp);
+		err(1, "save_url open dst '%s'", dst);
+	}
+	fd = fileno(data_fp);
+	if (type == 'h') ct = "text/html";
+	if (type == 'g' || type == 'p' || type == 'I') {
+		rc = read_all(s, ibuf, sizeof(ibuf));
+		if (rc < 0) err(1, "save_url read image type");
+		ct = detect_image_content_type(ibuf);
+		rc = write_all(fd, ibuf, 4);
+		if (rc < 0) err(1, "save_url write");
+	}
+	fprintf(log_fp, "Length: unspecified [%s]\n\n", ct);
+	fflush(log_fp);
+
+	while (1) {
+		rc = read_some(s, buf, &len);
+		if (rc < 0 && errno == EPIPE) break;
+		if (rc < 0) { warn("save_url read"); break; }
+		rc = write_all(fd, buf, len);
+		if (rc < 0) { warn("save_url write"); break; }
+	}
+	
+        if (rc < 0 && errno == EPIPE) fprintf(log_fp, "save_url yes ok\n");
+	else fprintf(log_fp, "save_url not ok'\n");
+
+	fprintf(log_fp, "save_url END\n\n");
+
+	fflush(log_fp);
+	fclose(log_fp);
+	fclose(data_fp);
+}
+
 static void render_dir(int s, const char *url) {
 	dpi_send_header(url, "text/html");
 	printf("<!DOCTYPE html>");
@@ -440,7 +499,7 @@ static void fix_path(char *path) {
 	query[-1] = '\t';
 }
 
-static void respond(const char *url) {
+static void respond(const char *url, const char *dst) {
 	int rc;
 	const char *errstr = NULL;
 	char host[1024];
@@ -460,6 +519,10 @@ static void respond(const char *url) {
 	if (rc < 0) { respond_err("write request"); return; }
 	rc = write_all(s, "\r\n", 2);
 	if (rc < 0) { respond_err("write request end"); return; }
+	if (dst != NULL) {
+		save_url(s, url, type, dst);
+		return ;
+	}
 	switch (type) {
 		case '1':
 		case '7':
@@ -484,8 +547,9 @@ static void respond(const char *url) {
 }
 
 int main(void) {
-	char url[1024];
+	char url[2048];
+	char *dst = NULL;
 	setvbuf(stdout, NULL, _IONBF, BUFSIZ);
-	dpi_read_request(url, sizeof(url));
-	respond(url);
+	dpi_read_request(url, sizeof(url), &dst);
+	respond(url, dst);
 }
