From b21a5ccfa1c09133e2502f860181dfd7947959bd Mon Sep 17 00:00:00 2001 From: Herby Gillot Date: Fri, 13 Mar 2026 07:59:17 -0400 Subject: [PATCH] portlivecheck: add livecheck.user_agent option Add livecheck.user_agent to override the HTTP User-Agent for livecheck requests, mirroring the existing fetch.user_agent option. Also document both options in portfile.7, where they were previously undocumented. Fixes: https://trac.macports.org/ticket/64369 --- doc/portfile.7 | 19 ++ src/port1.0/port_test_autoconf.tcl.in | 2 +- src/port1.0/portlivecheck.tcl | 8 +- src/port1.0/tests/portlivecheck.test | 374 ++++++++++++++++++++++++++ 4 files changed, 401 insertions(+), 2 deletions(-) create mode 100644 src/port1.0/tests/portlivecheck.test diff --git a/doc/portfile.7 b/doc/portfile.7 index b4315db834..58357fac6a 100644 --- a/doc/portfile.7 +++ b/doc/portfile.7 @@ -1136,6 +1136,16 @@ Whether to ignore the host SSL certificate (for HTTPS). .Sy Default: .Em no .br +.It Ic fetch.user_agent +HTTP User-Agent to use when fetching the resource. +Useful for servers that block requests from the default curl user agent. +.br +.Sy Type: +.Em optional +.br +.Sy Default: +.Em \&"" +.br .El .Ss FETCHING FROM CVS As an alternative to fetching distribution files, pulling the sources from a @@ -2281,6 +2291,15 @@ md5 sum to use for md5 comparison. .br .Sy Type: .Em optional +.It Ic livecheck.user_agent +HTTP User-Agent to use when fetching the livecheck resource. +Useful for servers that block requests from the default curl user agent. +.br +.Sy Type: +.Em optional +.br +.Sy Default: +.Em \&"" .El .Sh VARIANT OPTIONS MacPorts allows for conditional modification to be specified in a diff --git a/src/port1.0/port_test_autoconf.tcl.in b/src/port1.0/port_test_autoconf.tcl.in index ca569cbaf3..87567530c6 100644 --- a/src/port1.0/port_test_autoconf.tcl.in +++ b/src/port1.0/port_test_autoconf.tcl.in @@ -16,7 +16,7 @@ # 3. Neither the name of Apple Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. -# +# # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/src/port1.0/portlivecheck.tcl b/src/port1.0/portlivecheck.tcl index e2177a4404..c52d8a81b7 100644 --- a/src/port1.0/portlivecheck.tcl +++ b/src/port1.0/portlivecheck.tcl @@ -45,7 +45,7 @@ namespace eval portlivecheck { } # define options -options livecheck.url livecheck.type livecheck.md5 livecheck.regex livecheck.branch livecheck.name livecheck.distname livecheck.version livecheck.ignore_sslcert livecheck.compression livecheck.curloptions +options livecheck.url livecheck.type livecheck.md5 livecheck.regex livecheck.branch livecheck.name livecheck.distname livecheck.version livecheck.ignore_sslcert livecheck.compression livecheck.curloptions livecheck.user_agent # defaults default livecheck.url {$homepage} @@ -59,6 +59,7 @@ default livecheck.version {$version} default livecheck.ignore_sslcert no default livecheck.compression yes default livecheck.curloptions [list --append-http-header "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"] +default livecheck.user_agent {} proc portlivecheck::livecheck_async_start {} { _livecheck_main yes @@ -82,6 +83,7 @@ proc portlivecheck::_livecheck_main {{async no}} { livecheck.ignore_sslcert \ livecheck.compression \ livecheck.curloptions \ + livecheck.user_agent \ git.cmd \ homepage portpath \ master_sites name subport @@ -141,6 +143,10 @@ proc portlivecheck::_livecheck_main {{async no}} { if {[tbool livecheck.compression]} { lappend curl_options "--enable-compression" } + if {${livecheck.user_agent} ne ""} { + lappend curl_options "--user-agent" + lappend curl_options "${livecheck.user_agent}" + } # Check _resources/port1.0/livecheck for available types. set types_dir [getdefaultportresourcepath "port1.0/livecheck"] diff --git a/src/port1.0/tests/portlivecheck.test b/src/port1.0/tests/portlivecheck.test new file mode 100644 index 0000000000..9ac1b31eb1 --- /dev/null +++ b/src/port1.0/tests/portlivecheck.test @@ -0,0 +1,374 @@ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + +package require tcltest 2 +namespace import tcltest::* + +set pwd [file dirname [file normalize $argv0]] + +source ../port_test_autoconf.tcl +package require macports 1.0 + +array set ui_options {} +mportinit ui_options + +package require portlivecheck 1.0 +source ./library.tcl +macports_worker_init + +# --------------------------------------------------------------------------- +# Stub livecheck-type files so _livecheck_main can glob for available types +# without requiring a real MacPorts ports tree on disk. +# --------------------------------------------------------------------------- +# Place stubs under the system temp dir (derived via [file tempfile]) so they +# are not left behind in the source tree after a test run. +set _lc_tmpfd [file tempfile _lc_tmppath] +close $_lc_tmpfd +file delete $_lc_tmppath +set _lc_resource_dir ${_lc_tmppath}_lc_resources +unset _lc_tmpfd _lc_tmppath +file mkdir [file join $_lc_resource_dir port1.0 livecheck] +foreach _t {regex regexm md5 moddate git none fallback} { + close [open [file join $_lc_resource_dir port1.0 livecheck ${_t}.tcl] w] +} +unset _t + +# --------------------------------------------------------------------------- +# Shared fixture +# --------------------------------------------------------------------------- +# +# Notes: +# - livecheck.curloptions is set to an empty list so that curl-option +# inspection tests start with a clean slate, unencumbered by the default +# Accept header. +# - getdefaultportresourcepath is replaced so the type-file glob resolves to +# the stub directory above rather than needing an installed ports tree. +# - curl is replaced by a mock that captures the options passed to it +# ($_test_curl_opts), writes $_test_curl_content to the output file for +# fetch operations, and returns $_test_isnewer for isnewer operations. +# - portpath is set to $pwd, where a Portfile already exists (needed by the +# moddate livecheck type). + +set livecheck_setup { + set subport testport + set name testport + set portpath $pwd + set homepage "https://example.com" + unset -nocomplain master_sites + + set livecheck.type regex + set livecheck.url "https://example.com/download" + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set livecheck.md5 "" + set livecheck.branch "" + set livecheck.name default + set livecheck.distname default + set livecheck.ignore_sslcert no + set livecheck.compression yes + set livecheck.curloptions [list] + set livecheck.user_agent "" + + set _test_curl_opts {} + set _test_curl_content "" + set _test_isnewer 0 + + rename getdefaultportresourcepath _save_getdefaultportresourcepath + proc getdefaultportresourcepath {{path {}}} { + global _lc_resource_dir + return [file join $_lc_resource_dir $path] + } + + rename curl _save_curl + proc curl {operation args} { + global _test_curl_opts _test_curl_content _test_isnewer + switch $operation { + fetch { + set _test_curl_opts $args + set outfile [lindex $args end] + set fd [open $outfile w] + puts -nonewline $fd $_test_curl_content + close $fd + } + isnewer { + return $_test_isnewer + } + } + } +} + +set livecheck_cleanup { + rename curl "" + rename _save_curl curl + rename getdefaultportresourcepath "" + rename _save_getdefaultportresourcepath getdefaultportresourcepath +} + +# --------------------------------------------------------------------------- +# regex tests +# --------------------------------------------------------------------------- + +test livecheck_regex_uptodate { + regex livecheck: extracted version equals livecheck.version, port is up to date +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -output "" -errorOutput "" + +test livecheck_regex_updated { + regex livecheck: extracted version is newer than livecheck.version, reports update +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "version-2.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -output "*seems to have been updated*" -match glob -errorOutput "" + +test livecheck_regex_no_match { + regex livecheck: regex does not match the fetched content, reports error +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "no version information here" + portlivecheck::livecheck_main + return "" +} -errorOutput "*regex didn't match*" -match glob + +test livecheck_regex_extracted_older { + regex livecheck: extracted version is older than livecheck.version, reports error +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.version "2.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -errorOutput "*older than livecheck.version*" -match glob + +test livecheck_regex_multiple_matches_picks_highest { + regex livecheck: when multiple versions match on separate lines, the highest is used +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + # Line 1 has 1.5, line 2 has 2.0 — the highest (2.0) should win. + set _test_curl_content "version-1.5.tar.gz\nversion-2.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -output "*new version: 2.0*" -match glob -errorOutput "" + +# --------------------------------------------------------------------------- +# regexm tests +# --------------------------------------------------------------------------- + +test livecheck_regexm_uptodate { + regexm livecheck: multiline regex finds same version, port is up to date +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regexm + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "preamble\nlatest: version-1.0.tar.gz\ntrailer" + portlivecheck::livecheck_main + return "" +} -output "" -errorOutput "" + +test livecheck_regexm_updated { + regexm livecheck: multiline regex finds newer version, reports update +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regexm + set livecheck.version "1.0" + set livecheck.regex {version-([0-9.]+)\.tar} + set _test_curl_content "preamble\nlatest: version-2.0.tar.gz\ntrailer" + portlivecheck::livecheck_main + return "" +} -output "*seems to have been updated*" -match glob -errorOutput "" + +test livecheck_regexm_span_lines { + regexm livecheck: regex can span multiple lines (the key regexm distinction) +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regexm + set livecheck.version "1.0" + # Regex that requires a newline between "release" and the version token. + set livecheck.regex {release\nversion-([0-9.]+)\.tar} + set _test_curl_content "release\nversion-2.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -output "*seems to have been updated*" -match glob -errorOutput "" + +# --------------------------------------------------------------------------- +# md5 tests +# --------------------------------------------------------------------------- + +test livecheck_md5_changed { + md5 livecheck: remote content has a different md5 than livecheck.md5, reports update +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type md5 + # md5 of the empty string — any non-empty content will differ. + set livecheck.md5 "d41d8cd98f00b204e9800998ecf8427e" + set _test_curl_content "version-2.0.tar.gz" + portlivecheck::livecheck_main + return "" +} -output "*seems to have been updated*" -match glob -errorOutput "" + +test livecheck_md5_unchanged { + md5 livecheck: remote content md5 matches livecheck.md5, port is up to date +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type md5 + set _test_curl_content "stable-content" + + # Pre-compute the md5 of the content the mock curl will serve, then set + # livecheck.md5 to that value so _livecheck_main sees them as equal. + set prefd [file tempfile pretmpfile] + puts -nonewline $prefd $_test_curl_content + close $prefd + set livecheck.md5 [md5 file $pretmpfile] + file delete $pretmpfile + + portlivecheck::livecheck_main + return "" +} -output "" -errorOutput "" + +# --------------------------------------------------------------------------- +# moddate tests +# --------------------------------------------------------------------------- + +test livecheck_moddate_not_newer { + moddate livecheck: remote resource is not newer than Portfile, port is up to date +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type moddate + set _test_isnewer 0 + portlivecheck::livecheck_main + return "" +} -output "" -errorOutput "" + +test livecheck_moddate_newer { + moddate livecheck: remote resource is newer than Portfile, reports update +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type moddate + set _test_isnewer 1 + portlivecheck::livecheck_main + return "" +} -output "*seems to have been updated*" -match glob -errorOutput "" + +# --------------------------------------------------------------------------- +# none test +# --------------------------------------------------------------------------- + +test livecheck_none { + none livecheck: no check is performed, no output is produced +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type none + portlivecheck::livecheck_main + return "" +} -output "" -errorOutput "" + +# --------------------------------------------------------------------------- +# livecheck.user_agent option tests +# --------------------------------------------------------------------------- + +test livecheck_user_agent_set { + livecheck.user_agent: non-empty value causes --user-agent to be passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.compression no + set livecheck.user_agent "TestAgent/1.0" + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + set ua_idx [lsearch -exact $_test_curl_opts "--user-agent"] + if {$ua_idx < 0} { + return "FAIL: --user-agent not found in curl options: $_test_curl_opts" + } + if {[lindex $_test_curl_opts [expr {$ua_idx + 1}]] ne "TestAgent/1.0"} { + return "FAIL: wrong user-agent value: [lindex $_test_curl_opts [expr {$ua_idx + 1}]]" + } + return "ok" +} -result "ok" + +test livecheck_user_agent_unset { + livecheck.user_agent: empty value means --user-agent is not passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.compression no + set livecheck.user_agent "" + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + if {[lsearch -exact $_test_curl_opts "--user-agent"] >= 0} { + return "FAIL: --user-agent unexpectedly present in curl options: $_test_curl_opts" + } + return "ok" +} -result "ok" + +# --------------------------------------------------------------------------- +# livecheck.ignore_sslcert option tests +# --------------------------------------------------------------------------- + +test livecheck_ignore_sslcert_enabled { + livecheck.ignore_sslcert yes: --ignore-ssl-cert is passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.ignore_sslcert yes + set livecheck.compression no + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + if {[lsearch -exact $_test_curl_opts "--ignore-ssl-cert"] < 0} { + return "FAIL: --ignore-ssl-cert not found in curl options: $_test_curl_opts" + } + return "ok" +} -result "ok" + +test livecheck_ignore_sslcert_disabled { + livecheck.ignore_sslcert no: --ignore-ssl-cert is not passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.ignore_sslcert no + set livecheck.compression no + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + if {[lsearch -exact $_test_curl_opts "--ignore-ssl-cert"] >= 0} { + return "FAIL: --ignore-ssl-cert unexpectedly present in curl options: $_test_curl_opts" + } + return "ok" +} -result "ok" + +# --------------------------------------------------------------------------- +# livecheck.compression option tests +# --------------------------------------------------------------------------- + +test livecheck_compression_enabled { + livecheck.compression yes: --enable-compression is passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.compression yes + set livecheck.ignore_sslcert no + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + if {[lsearch -exact $_test_curl_opts "--enable-compression"] < 0} { + return "FAIL: --enable-compression not found in curl options: $_test_curl_opts" + } + return "ok" +} -result "ok" + +test livecheck_compression_disabled { + livecheck.compression no: --enable-compression is not passed to curl +} -setup $livecheck_setup -cleanup $livecheck_cleanup -body { + set livecheck.type regex + set livecheck.compression no + set livecheck.ignore_sslcert no + set _test_curl_content "version-1.0.tar.gz" + portlivecheck::livecheck_main + if {[lsearch -exact $_test_curl_opts "--enable-compression"] >= 0} { + return "FAIL: --enable-compression unexpectedly present in curl options: $_test_curl_opts" + } + return "ok" +} -result "ok" + + +file delete -force $_lc_resource_dir + +cleanupTests