← Commit list

6bff85be68

Viktor Szakats
Viktor Szakats
2026-07-22
SUCCESS Duration: 196s

Subject

tool_xattr: add support for Windows alternate data stream

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

.github/scripts/pyspelling.words+1 -0
@@ -392,6 +392,7 @@ IMAPS
imaps
impacket
implementers
+INI
init
initializer
inlined
docs/cmdline-opts/xattr.md+8 -3
@@ -20,6 +20,11 @@ Store metadata in the extended file attributes.
When saving output to a file, tell curl to store file metadata in extended
file attributes. Currently, `curl` is stored in the `creator` attribute,
-the URL is stored in the `xdg.origin.url` attribute and, for HTTP, the content
-type is stored in the `mime_type` attribute. If the file system does not
-support extended attributes, a warning is issued.
+the URL is stored in the `xdg.origin.url` attribute, for HTTP, the content
+type is stored in the `mime_type` attribute, and if set, the referrer URL in
+`user.xdg.referrer.url`. If the file system does not support extended
+attributes, a warning is issued.
+
+Since curl 8.22.0 this option is also supported on Windows, where it creates
+an Alternate Data Stream named `Zone.Identifier`. It contains an INI formatted
+`ZoneTransfer` section, with values: `HostUrl`, `ReferrerUrl` (if set).
src/tool_operate.c+1 -1
@@ -650,7 +650,7 @@ static CURLcode post_output_handling(struct per_transfer *per,
/* Set file extended attributes */
if(!result && config->xattr && outs->fopened && outs->stream) {
- rc = fwrite_xattr(curl, per->url, fileno(outs->stream));
+ rc = fwrite_xattr(curl, per->url, fileno(outs->stream), outs->filename);
if(rc) {
char errbuf[STRERROR_LEN];
warnf("Error setting extended attributes on '%s': %s", outs->filename,
src/tool_xattr.c+74 -15
@@ -27,19 +27,6 @@
#ifdef USE_XATTR
-/* mapping table of curl metadata to extended attribute names */
-static const struct xattr_mapping {
- const char *attr; /* name of the xattr */
- CURLINFO info;
-} mappings[] = {
- /* mappings proposed by
- * https://freedesktop.org/wiki/CommonExtendedAttributes/
- */
- { "user.xdg.referrer.url", CURLINFO_REFERER },
- { "user.mime_type", CURLINFO_CONTENT_TYPE },
- { NULL, CURLINFO_NONE } /* last element, abort here */
-};
-
/* returns a new URL that needs to be freed */
/* @unittest: 1621 */
UNITTEST char *stripcredentials(const char *url)
@@ -74,6 +61,20 @@ error:
return NULL;
}
+#ifndef _WIN32
+/* mapping table of curl metadata to extended attribute names */
+static const struct xattr_mapping {
+ const char *attr; /* name of the xattr */
+ CURLINFO info;
+} mappings[] = {
+ /* mappings proposed by
+ * https://freedesktop.org/wiki/CommonExtendedAttributes/
+ */
+ { "user.xdg.referrer.url", CURLINFO_REFERER },
+ { "user.mime_type", CURLINFO_CONTENT_TYPE },
+ { NULL, CURLINFO_NONE } /* last element, abort here */
+};
+
static int xattr(int fd,
const char *attr, /* name of the xattr */
const char *value)
@@ -102,13 +103,70 @@ static int xattr(int fd,
}
return err;
}
+#else
+static int win32_file_stream(CURL *curl, FILE *fs, const char *url)
+{
+ int err = 1;
+ char *value = NULL;
+ char *nurl = stripcredentials(url);
+ CURLcode result = curl_easy_getinfo(curl, CURLINFO_REFERER, &value);
+
+ if(nurl && !result) {
+ err = 0;
+ err |= (fputs("[ZoneTransfer]\n", fs) == EOF);
+ if(value) {
+ err |= (fputs("ReferrerUrl=", fs) == EOF);
+ err |= (fputs(value, fs) == EOF);
+ err |= (fputs("\n", fs) == EOF);
+ }
+ err |= (fputs("HostUrl=", fs) == EOF);
+ err |= (fputs(nurl, fs) == EOF);
+ err |= (fputs("\n", fs) == EOF);
+ }
+ curl_free(nurl);
+ return err;
+}
+#endif /* !_WIN32 */
+
/* store metadata from the curl request alongside the downloaded
* file using extended attributes
*/
-int fwrite_xattr(CURL *curl, const char *url, int fd)
+int fwrite_xattr(CURL *curl, const char *url, int fd, const char *filename)
{
+ int err;
+#ifdef _WIN32
+ char *fn_abs, *fn_stream;
+ FILE *fs;
+ (void)fd;
+
+ /* convert to absolute path to prevent Windows interpreting a 'X:<stream>'
+ filename as 'drive-letter:<filename>'. */
+ fn_abs = _fullpath(NULL, filename, 0);
+ if(!fn_abs)
+ return 1;
+
+ fn_stream = curl_maprintf("%s:%s", fn_abs, "Zone.Identifier");
+ /* !checksrc! disable BANNEDFUNC 1 */
+ free(fn_abs); /* allocated by CRT, use system free() */
+ if(!fn_stream)
+ return 1;
+
+ fs = curlx_fopen(fn_stream, FOPEN_WRITETEXT);
+ curl_free(fn_stream);
+ if(!fs)
+ return 1;
+
+#ifdef DEBUGBUILD
+ if(getenv("CURL_FAKE_XATTR"))
+ win32_file_stream(curl, stdout, url);
+#endif
+ err = win32_file_stream(curl, fs, url);
+ curlx_fclose(fs);
+#else
int i = 0;
- int err = xattr(fd, "user.creator", "curl");
+ (void)filename;
+
+ err = xattr(fd, "user.creator", "curl");
/* loop through all xattr-curlinfo pairs and abort on a set error */
while(!err && mappings[i].attr) {
@@ -125,6 +183,7 @@ int fwrite_xattr(CURL *curl, const char *url, int fd)
err = xattr(fd, "user.xdg.origin.url", nurl);
curl_free(nurl);
}
+#endif
return err;
}
#endif
src/tool_xattr.h+4 -2
@@ -33,17 +33,19 @@
# include <sys/types.h>
# include <sys/extattr.h>
# define USE_XATTR
+#elif defined(_WIN32)
+# define USE_XATTR
#endif
#ifdef USE_XATTR
-int fwrite_xattr(CURL *curl, const char *url, int fd);
+int fwrite_xattr(CURL *curl, const char *url, int fd, const char *filename);
#ifdef UNITTESTS
UNITTEST char *stripcredentials(const char *url);
#endif
#else
-#define fwrite_xattr(a, b, c) 0
+#define fwrite_xattr(a, b, c, d) 0
#endif
#endif /* HEADER_CURL_TOOL_XATTR_H */
tests/data/test644+5 -0
@@ -76,9 +76,14 @@ Accept: */*
</protocol>
<stdout mode="text">
+%if win32
+[ZoneTransfer]
+HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%else
user.creator => curl
user.mime_type => text/html
user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%endif
</stdout>
</verify>
</testcase>
tests/data/test687+5 -0
@@ -53,9 +53,14 @@ Accept: */*
</protocol>
<stdout mode="text">
+%if win32
+[ZoneTransfer]
+HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%else
user.creator => curl
user.mime_type => fake/data
user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%endif
</stdout>
</verify>
</testcase>
tests/data/test688+9 -1
@@ -39,7 +39,7 @@ CURL_FAKE_XATTR=1
basic --xattr with (uppercase) -O
</name>
<command>
---xattr -O --output-dir %LOGDIR http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+--xattr -O --output-dir %LOGDIR http://%HOSTIP:%HTTPPORT/%TESTNUMBER --referer https://referrer.invalid/
</command>
</client>
@@ -50,12 +50,20 @@ GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
+Referer: https://referrer.invalid/
</protocol>
<stdout mode="text">
+%if win32
+[ZoneTransfer]
+ReferrerUrl=https://referrer.invalid/
+HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%else
user.creator => curl
+user.xdg.referrer.url => https://referrer.invalid/
user.mime_type => fake/data
user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+%endif
</stdout>
</verify>
</testcase>