Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package gap-curlinterface for openSUSE:Factory checked in at 2026-09-18 22:08:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/gap-curlinterface (Old) and /work/SRC/openSUSE:Factory/.gap-curlinterface.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gap-curlinterface" Fri Sep 18 22:08:54 2026 rev:8 rq:1378830 version:2.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/gap-curlinterface/gap-curlinterface.changes 2026-07-21 23:13:18.008246473 +0200 +++ /work/SRC/openSUSE:Factory/.gap-curlinterface.new.383539/gap-curlinterface.changes 2026-09-18 22:09:17.003495631 +0200 @@ -1,0 +2,10 @@ +Fri Sep 18 13:00:02 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 2.5.0 + * Add a `targetFile` option which writes the response body + directly to a file, so downloads are no longer bounded by + available memory. + * Fix inverted `verifyCert` check which disabled TLS certificate + verification by default. + +------------------------------------------------------------------- Old: ---- curlInterface-2.4.4.tar.gz New: ---- curlInterface-2.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ gap-curlinterface.spec ++++++ --- /var/tmp/diff_new_pack.MIdR5s/_old 2026-09-18 22:09:17.702524927 +0200 +++ /var/tmp/diff_new_pack.MIdR5s/_new 2026-09-18 22:09:17.705525053 +0200 @@ -17,7 +17,7 @@ Name: gap-curlinterface -Version: 2.4.4 +Version: 2.5.0 Release: 0 Summary: GAP: Web Access via curl License: GPL-2.0-or-later ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.MIdR5s/_old 2026-09-18 22:09:17.736526352 +0200 +++ /var/tmp/diff_new_pack.MIdR5s/_new 2026-09-18 22:09:17.739526478 +0200 @@ -1,5 +1,5 @@ -mtime: 1784644609 -commit: 3c24c9df4b8adb4810b780461ef744d14ee694c08f15ad0debf9695e59ae2f57 +mtime: 1789736428 +commit: 5af870767ae4af7729ad5db4811a8f48457c243d0455cad15e0f79d0f8b84d77 url: https://src.opensuse.org/jengelh/gap-curlinterface revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-18 15:00:28.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ curlInterface-2.4.4.tar.gz -> curlInterface-2.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/.release new/curlInterface-2.5.0/.release --- old/curlInterface-2.4.4/.release 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/.release 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -#!/bin/sh -# This file is executed by the `release` script from -# https://github.com/gap-system/ReleaseTools -rm -f .clang-format diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/CHANGES new/curlInterface-2.5.0/CHANGES --- old/curlInterface-2.4.4/CHANGES 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/CHANGES 2026-09-18 02:00:00.000000000 +0200 @@ -1,5 +1,11 @@ This file describes changes in the curlInterface package. +2.5.0 (2026-09-18) + - Add a `targetFile` option which writes the response body directly to + a file, so downloads are no longer bounded by available memory + - Fix inverted `verifyCert` check which disabled TLS certificate + verification by default + 2.4.4 (2026-07-19) - Remove dependency on GAPDoc - Prefer a Homebrew libcurl installation on macOS when available diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/PackageInfo.g new/curlInterface-2.5.0/PackageInfo.g --- old/curlInterface-2.4.4/PackageInfo.g 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/PackageInfo.g 2026-09-18 02:00:00.000000000 +0200 @@ -10,8 +10,8 @@ PackageName := "curlInterface", Subtitle := "Simple Web Access", -Version := "2.4.4", -Date := "19/07/2026", # dd/mm/yyyy format +Version := "2.5.0", +Date := "18/09/2026", # dd/mm/yyyy format License := "GPL-2.0-or-later", Persons := [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/_Chapter_Overview.xml new/curlInterface-2.5.0/doc/_Chapter_Overview.xml --- old/curlInterface-2.4.4/doc/_Chapter_Overview.xml 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/_Chapter_Overview.xml 2026-09-18 02:00:00.000000000 +0200 @@ -72,6 +72,7 @@ For descriptions of the output and the additional argument <A>opts</A>, see <Ref Func="CurlRequest"/>. <P/> +<!--AutoDocSource gap/curl.gd:57-63--> <Example><![CDATA[ gap> r := DownloadURL("www.gap-system.org");; gap> r.success; @@ -95,6 +96,7 @@ For descriptions of the output and the additional argument <A>opts</A>, see <Ref Func="CurlRequest"/>. <P/> +<!--AutoDocSource gap/curl.gd:77-83--> <Example><![CDATA[ gap> r := PostToURL("httpbun.com/post", "animal=tiger");; gap> r.success; @@ -118,6 +120,7 @@ For descriptions of the output and the additional argument <A>opts</A>, see <Ref Func="CurlRequest"/>. <P/> +<!--AutoDocSource gap/curl.gd:97-103--> <Example><![CDATA[ gap> r := DeleteURL("www.google.com");; gap> r.success; @@ -174,6 +177,24 @@ <C>maxTime</C>: Maximum time in seconds that you allow each transfer to take. 0 means no limitation. (default <K>0</K>). </Item> +<Item> +<C>targetFile</C>: the name of a file to write the body of the response + to, as a string, or <K>false</K> to have it returned as a string + (the default). The data is written as it arrives, so the size of + the response is not limited by the available memory. + <P/> + The body first goes to a temporary file next to + <C>targetFile</C>, which is renamed into place once the transfer + has succeeded. A failed request thus leaves an existing file at + <C>targetFile</C> untouched, and never leaves a partial one behind. + A file that cannot be replaced, such as a directory or a file + without write permission, is reported before the transfer starts. + <P/> + Beware that with the default <C>failOnError := false</C> a 404 + response counts as success, and its empty body then replaces the + contents of <C>targetFile</C>. Pass <C>failOnError := true</C> + when writing to a file, unless error pages are wanted. +</Item> </List> <P/> As output, this function returns a record containing some of the following @@ -185,7 +206,7 @@ </Item> <Item> <C>result</C>: body of the information sent by the server (only if - <C>success = true</C>); + <C>success = true</C> and no <C>targetFile</C> was given); </Item> <Item> <C>error</C>: human-readable string saying what went wrong (only if @@ -209,6 +230,7 @@ </Item> </List> <P/> +<!--AutoDocSource gap/curl.gd:173-184--> <Example><![CDATA[ gap> r := CurlRequest("https://www.google.com", > "HEAD", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/_entities.xml new/curlInterface-2.5.0/doc/_entities.xml --- old/curlInterface-2.4.4/doc/_entities.xml 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/_entities.xml 2026-09-18 02:00:00.000000000 +0200 @@ -1,4 +1,4 @@ -<!ENTITY RELEASEDATE '19 July 2026'> +<!ENTITY RELEASEDATE '18 September 2026'> <!ENTITY RELEASEYEAR '2026'> -<!ENTITY VERSION '2.4.4'> +<!ENTITY VERSION '2.5.0'> <!ENTITY curl '<Package>curl</Package>'> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/_main.tex new/curlInterface-2.5.0/doc/_main.tex --- old/curlInterface-2.4.4/doc/_main.tex 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/_main.tex 2026-09-18 02:00:00.000000000 +0200 @@ -100,8 +100,8 @@ \mbox{}}}\\ \vfill -{\Huge 2.4.4 \mbox{}}\\[1cm] -{ 19 July 2026 \mbox{}}\\[1cm] +{\Huge 2.5.0 \mbox{}}\\[1cm] +{ 18 September 2026 \mbox{}}\\[1cm] \mbox{}\\[2cm] {\Large \textbf{\strut Christopher Jefferson \strut\mbox{}}}\\ {\Large \textbf{\strut Michael Young \strut\mbox{}}}\\ @@ -230,7 +230,7 @@ "http://" or "https://". For descriptions of the output and the additional argument \mbox{\texttt{\mdseries\slshape opts}}, see \texttt{CurlRequest} (\ref{CurlRequest}). - + \begin{Verbatim}[commandchars=@|A,fontsize=\small,frame=single,label=Example] @gapprompt|gap>A @gapinput|r := DownloadURL("www.gap-system.org");;A @gapprompt|gap>A @gapinput|r.success;A @@ -257,7 +257,7 @@ "http://" or "https://". \mbox{\texttt{\mdseries\slshape str}} should be the string which will be sent to the server as a POST request. For descriptions of the output and the additional argument \mbox{\texttt{\mdseries\slshape opts}}, see \texttt{CurlRequest} (\ref{CurlRequest}). - + \begin{Verbatim}[commandchars=!@|,fontsize=\small,frame=single,label=Example] !gapprompt@gap>| !gapinput@r := PostToURL("httpbun.com/post", "animal=tiger");;| !gapprompt@gap>| [email protected];| @@ -285,7 +285,7 @@ either "http://" or "https://". For descriptions of the output and the additional argument \mbox{\texttt{\mdseries\slshape opts}}, see \texttt{CurlRequest} (\ref{CurlRequest}). - + \begin{Verbatim}[commandchars=!@|,fontsize=\small,frame=single,label=Example] !gapprompt@gap>| !gapinput@r := DeleteURL("www.google.com");;| !gapprompt@gap>| [email protected];| @@ -324,6 +324,16 @@ error (corresponds to the curl option \texttt{CURLOPT{\textunderscore}FAILONERROR}, the default is \texttt{false}). \item \texttt{maxTime}: Maximum time in seconds that you allow each transfer to take. 0 means no limitation. (default \texttt{0}). +\item \texttt{targetFile}: the name of a file to write the body of the response to, as a string, or \texttt{false} to have it returned as a string (the default). The data is written as it +arrives, so the size of the response is not limited by the available memory. + + The body first goes to a temporary file next to \texttt{targetFile}, which is renamed into place once the transfer has succeeded. A failed +request thus leaves an existing file at \texttt{targetFile} untouched, and never leaves a partial one behind. A file that cannot be +replaced, such as a directory or a file without write permission, is reported +before the transfer starts. + + Beware that with the default \texttt{failOnError := false} a 404 response counts as success, and its empty body then replaces the +contents of \texttt{targetFile}. Pass \texttt{failOnError := true} when writing to a file, unless error pages are wanted. \end{itemize} @@ -332,7 +342,7 @@ \begin{itemize} \item \texttt{success}: a boolean describing whether the request was successfully received by the server; -\item \texttt{result}: body of the information sent by the server (only if \texttt{success = true}); +\item \texttt{result}: body of the information sent by the server (only if \texttt{success = true} and no \texttt{targetFile} was given); \item \texttt{error}: human\texttt{\symbol{45}}readable string saying what went wrong (only if \texttt{success = false}). \end{itemize} @@ -347,7 +357,7 @@ \end{itemize} - + \begin{Verbatim}[commandchars=!@|,fontsize=\small,frame=single,label=Example] !gapprompt@gap>| !gapinput@r := CurlRequest("https://www.google.com",| !gapprompt@>| !gapinput@ "HEAD",| diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap0.html new/curlInterface-2.5.0/doc/chap0.html --- old/curlInterface-2.4.4/doc/chap0.html 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap0.html 2026-09-18 02:00:00.000000000 +0200 @@ -29,10 +29,10 @@ <h2>Simple Web Access</h2> <p> - 2.4.4</p> + 2.5.0</p> <p> - 19 July 2026 + 18 September 2026 </p> </div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap0.txt new/curlInterface-2.5.0/doc/chap0.txt --- old/curlInterface-2.4.4/doc/chap0.txt 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap0.txt 2026-09-18 02:00:00.000000000 +0200 @@ -6,10 +6,10 @@ [1X Simple Web Access [101X - 2.4.4 + 2.5.0 - 19 July 2026 + 18 September 2026 Christopher Jefferson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap0_mj.html new/curlInterface-2.5.0/doc/chap0_mj.html --- old/curlInterface-2.4.4/doc/chap0_mj.html 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap0_mj.html 2026-09-18 02:00:00.000000000 +0200 @@ -32,10 +32,10 @@ <h2>Simple Web Access</h2> <p> - 2.4.4</p> + 2.5.0</p> <p> - 19 July 2026 + 18 September 2026 </p> </div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap1.html new/curlInterface-2.5.0/doc/chap1.html --- old/curlInterface-2.4.4/doc/chap1.html 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap1.html 2026-09-18 02:00:00.000000000 +0200 @@ -177,6 +177,13 @@ <li><p><code class="code">maxTime</code>: Maximum time in seconds that you allow each transfer to take. 0 means no limitation. (default <code class="keyw">0</code>).</p> </li> +<li><p><code class="code">targetFile</code>: the name of a file to write the body of the response to, as a string, or <code class="keyw">false</code> to have it returned as a string (the default). The data is written as it arrives, so the size of the response is not limited by the available memory.</p> + +<p>The body first goes to a temporary file next to <code class="code">targetFile</code>, which is renamed into place once the transfer has succeeded. A failed request thus leaves an existing file at <code class="code">targetFile</code> untouched, and never leaves a partial one behind. A file that cannot be replaced, such as a directory or a file without write permission, is reported before the transfer starts.</p> + +<p>Beware that with the default <code class="code">failOnError := false</code> a 404 response counts as success, and its empty body then replaces the contents of <code class="code">targetFile</code>. Pass <code class="code">failOnError := true</code> when writing to a file, unless error pages are wanted.</p> + +</li> </ul> <p>As output, this function returns a record containing some of the following components, which describe the outcome of the request:</p> @@ -185,7 +192,7 @@ <li><p><code class="code">success</code>: a boolean describing whether the request was successfully received by the server;</p> </li> -<li><p><code class="code">result</code>: body of the information sent by the server (only if <code class="code">success = true</code>);</p> +<li><p><code class="code">result</code>: body of the information sent by the server (only if <code class="code">success = true</code> and no <code class="code">targetFile</code> was given);</p> </li> <li><p><code class="code">error</code>: human-readable string saying what went wrong (only if <code class="code">success = false</code>).</p> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap1.txt new/curlInterface-2.5.0/doc/chap1.txt --- old/curlInterface-2.4.4/doc/chap1.txt 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap1.txt 2026-09-18 02:00:00.000000000 +0200 @@ -132,6 +132,23 @@ [30X [33X[0;6Y[10XmaxTime[110X: Maximum time in seconds that you allow each transfer to take. 0 means no limitation. (default [9X0[109X).[133X + [30X [33X[0;6Y[10XtargetFile[110X: the name of a file to write the body of the response to, + as a string, or [9Xfalse[109X to have it returned as a string (the default). + The data is written as it arrives, so the size of the response is not + limited by the available memory.[133X + + [33X[0;6YThe body first goes to a temporary file next to [10XtargetFile[110X, which is + renamed into place once the transfer has succeeded. A failed request + thus leaves an existing file at [10XtargetFile[110X untouched, and never leaves + a partial one behind. A file that cannot be replaced, such as a + directory or a file without write permission, is reported before the + transfer starts.[133X + + [33X[0;6YBeware that with the default [10XfailOnError := false[110X a 404 response + counts as success, and its empty body then replaces the contents of + [10XtargetFile[110X. Pass [10XfailOnError := true[110X when writing to a file, unless + error pages are wanted.[133X + [33X[0;0YAs output, this function returns a record containing some of the following components, which describe the outcome of the request:[133X @@ -139,7 +156,7 @@ received by the server;[133X [30X [33X[0;6Y[10Xresult[110X: body of the information sent by the server (only if [10Xsuccess = - true[110X);[133X + true[110X and no [10XtargetFile[110X was given);[133X [30X [33X[0;6Y[10Xerror[110X: human-readable string saying what went wrong (only if [10Xsuccess = false[110X).[133X diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/chap1_mj.html new/curlInterface-2.5.0/doc/chap1_mj.html --- old/curlInterface-2.4.4/doc/chap1_mj.html 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/chap1_mj.html 2026-09-18 02:00:00.000000000 +0200 @@ -180,6 +180,13 @@ <li><p><code class="code">maxTime</code>: Maximum time in seconds that you allow each transfer to take. 0 means no limitation. (default <code class="keyw">0</code>).</p> </li> +<li><p><code class="code">targetFile</code>: the name of a file to write the body of the response to, as a string, or <code class="keyw">false</code> to have it returned as a string (the default). The data is written as it arrives, so the size of the response is not limited by the available memory.</p> + +<p>The body first goes to a temporary file next to <code class="code">targetFile</code>, which is renamed into place once the transfer has succeeded. A failed request thus leaves an existing file at <code class="code">targetFile</code> untouched, and never leaves a partial one behind. A file that cannot be replaced, such as a directory or a file without write permission, is reported before the transfer starts.</p> + +<p>Beware that with the default <code class="code">failOnError := false</code> a 404 response counts as success, and its empty body then replaces the contents of <code class="code">targetFile</code>. Pass <code class="code">failOnError := true</code> when writing to a file, unless error pages are wanted.</p> + +</li> </ul> <p>As output, this function returns a record containing some of the following components, which describe the outcome of the request:</p> @@ -188,7 +195,7 @@ <li><p><code class="code">success</code>: a boolean describing whether the request was successfully received by the server;</p> </li> -<li><p><code class="code">result</code>: body of the information sent by the server (only if <code class="code">success = true</code>);</p> +<li><p><code class="code">result</code>: body of the information sent by the server (only if <code class="code">success = true</code> and no <code class="code">targetFile</code> was given);</p> </li> <li><p><code class="code">error</code>: human-readable string saying what went wrong (only if <code class="code">success = false</code>).</p> Binary files old/curlInterface-2.4.4/doc/manual.pdf and new/curlInterface-2.5.0/doc/manual.pdf differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/manual.six new/curlInterface-2.5.0/doc/manual.six --- old/curlInterface-2.4.4/doc/manual.six 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/manual.six 2026-09-18 02:00:00.000000000 +0200 @@ -20,7 +20,7 @@ 28, 3, "macos", "X7FF92C7E7A41189D" ], [ "\033[1X\033[33X\033[0;-2YFunctions\033[133X\033[101X", "1.2", [ 1, 2, 0 ], 39, 3, "functions", "X86FA580F8055B274" ], - [ "Index", "ind", [ "Ind", 0, 0 ], 1, 6, "index", "X83A0356F839C696F" ], + [ "Index", "ind", [ "Ind", 0, 0 ], 1, 7, "index", "X83A0356F839C696F" ], [ "\033[2XDownloadURL\033[102X", "1.2-1", [ 1, 2, 1 ], 45, 4, "downloadurl", "X7827F7DE85113D7E" ], [ "\033[2XPostToURL\033[102X", "1.2-2", [ 1, 2, 2 ], 63, 4, "posttourl", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/doc/title.xml new/curlInterface-2.5.0/doc/title.xml --- old/curlInterface-2.4.4/doc/title.xml 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/doc/title.xml 2026-09-18 02:00:00.000000000 +0200 @@ -9,7 +9,7 @@ Simple Web Access </Subtitle> <Version> - 2.4.4 + 2.5.0 </Version> <Author> Christopher Jefferson @@ -36,6 +36,6 @@ <Homepage>http://mct25.host.cs.st-andrews.ac.uk/</Homepage> </Author> <Date> - 19 July 2026 + 18 September 2026 </Date> </TitlePage> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/gap/curl.gd new/curlInterface-2.5.0/gap/curl.gd --- old/curlInterface-2.4.4/gap/curl.gd 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/gap/curl.gd 2026-09-18 02:00:00.000000000 +0200 @@ -136,13 +136,29 @@ #! the default is <K>false</K>). #! * <C>maxTime</C>: Maximum time in seconds that you allow each transfer #! to take. 0 means no limitation. (default <K>0</K>). +#! * <C>targetFile</C>: the name of a file to write the body of the response +#! to, as a string, or <K>false</K> to have it returned as a string +#! (the default). The data is written as it arrives, so the size of +#! the response is not limited by the available memory. +#! <P/> +#! The body first goes to a temporary file next to +#! <C>targetFile</C>, which is renamed into place once the transfer +#! has succeeded. A failed request thus leaves an existing file at +#! <C>targetFile</C> untouched, and never leaves a partial one behind. +#! A file that cannot be replaced, such as a directory or a file +#! without write permission, is reported before the transfer starts. +#! <P/> +#! Beware that with the default <C>failOnError := false</C> a 404 +#! response counts as success, and its empty body then replaces the +#! contents of <C>targetFile</C>. Pass <C>failOnError := true</C> +#! when writing to a file, unless error pages are wanted. #! #! As output, this function returns a record containing some of the following #! components, which describe the outcome of the request: #! * <C>success</C>: a boolean describing whether the request was #! successfully received by the server; #! * <C>result</C>: body of the information sent by the server (only if -#! <C>success = true</C>); +#! <C>success = true</C> and no <C>targetFile</C> was given); #! * <C>error</C>: human-readable string saying what went wrong (only if #! <C>success = false</C>). #! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/gap/curl.gi new/curlInterface-2.5.0/gap/curl.gi --- old/curlInterface-2.4.4/gap/curl.gi 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/gap/curl.gi 2026-09-18 02:00:00.000000000 +0200 @@ -9,7 +9,7 @@ # Get options r := rec(verifyCert := true, verbose := false, followRedirect := true, - failOnError:= false, maxTime := 0); + failOnError:= false, maxTime := 0, targetFile := false); if Length(opts) = 1 then if not IsRecord(opts[1]) then ErrorNoReturn("CurlRequest: <opts> must be a record"); @@ -42,13 +42,18 @@ " must be a non-negative integer"); fi; od; + if r.targetFile <> false and not IsString(r.targetFile) then + ErrorNoReturn("CurlRequest: <opts>.targetFile must be a string or ", + "false"); + fi; return CURL_REQUEST(URL, type, out_string, r.verifyCert, r.verbose, r.followRedirect, r.failOnError, - r.maxTime); + r.maxTime, + r.targetFile); end); InstallGlobalFunction("DownloadURL", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/src/curl.c new/curlInterface-2.5.0/src/curl.c --- old/curlInterface-2.4.4/src/curl.c 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/src/curl.c 2026-09-18 02:00:00.000000000 +0200 @@ -6,6 +6,9 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> #include <curl/curl.h> #undef PACKAGE @@ -33,6 +36,29 @@ return size * nmemb; } +// Write straight to a file, for CURLOPT_WRITEDATA when a target is given. +size_t write_file(char * ptr, size_t size, size_t nmemb, void * outstream) +{ + return fwrite(ptr, size, nmemb, (FILE *)outstream); +} + +// room for the ".<pid>.part" that names the temporary file +#define TARGET_TEMP_SUFFIX_LEN 32 + +// Could a completed download be moved onto <path>? A directory cannot be +// replaced by a file, and an existing file we may not write should not be +// replaced either, even though 'rename' would happily do it. Saying so here +// costs a 'stat' and saves downloading a body that has nowhere to go. +static int can_replace_file(const char * path) +{ + struct stat st; + + if (stat(path, &st) != 0) + return 1; // nothing there yet; opening the temporary file decides + + return S_ISDIR(st.st_mode) ? 0 : access(path, W_OK) == 0; +} + Obj FuncCURL_REQUEST(Obj self, Obj input_list) { CURL * curl; @@ -42,9 +68,12 @@ curl_off_t len; char urlbuf[4096] = { 0 }; char * typebuf = NULL; + char * targetbuf = NULL; + char * tempbuf = NULL; + FILE * targetfile = NULL; const int n = LEN_PLIST(input_list); - GAP_ASSERT(n == 8); // paranoia check, GAP enforces this + GAP_ASSERT(n == 9); // paranoia check, GAP enforces this Obj URL = ELM_PLIST(input_list, 1); if (!IS_STRING_REP(URL)) { @@ -70,6 +99,42 @@ } memcpy(urlbuf, CONST_CSTR_STRING(URL), len); + // If a target file was given, write the body straight into it instead of + // building it up in memory. Copy the name out of the GAP string for the + // same reason as the URL above. + Obj target = ELM_PLIST(input_list, 9); + if (target != False) { + if (!IS_STRING_REP(target)) { + target = CopyToStringRep(target); + } + len = GET_LEN_STRING(target) + 1; + targetbuf = (char *)malloc(len); + memcpy(targetbuf, CONST_CSTR_STRING(target), len); + + // Write to a temporary file beside the target and rename it into + // place once the transfer succeeded, so that a failure leaves + // whatever was at the target alone instead of truncating it. The + // pid keeps two GAP processes fetching the same target apart. + tempbuf = (char *)malloc(len + TARGET_TEMP_SUFFIX_LEN); + snprintf(tempbuf, len + TARGET_TEMP_SUFFIX_LEN, "%s.%ld.part", + targetbuf, (long)getpid()); + + if (can_replace_file(targetbuf)) + targetfile = fopen(tempbuf, "wb"); + if (targetfile == NULL) { + Obj prec = NEW_PREC(2); + SET_LEN_PREC(prec, 2); + SET_RNAM_PREC(prec, 1, RNamName("success")); + SET_ELM_PREC(prec, 1, False); + SET_RNAM_PREC(prec, 2, RNamName("error")); + SET_ELM_PREC(prec, 2, MakeImmString("cannot open target file")); + CHANGED_BAG(prec); + free(tempbuf); + free(targetbuf); + return prec; + } + } + res = curl_global_init(CURL_GLOBAL_DEFAULT); if (res != 0) { ErrorMayQuit("CURL_REQUEST: failed to initialize libcurl (error %d)", @@ -82,8 +147,14 @@ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl, CURLOPT_URL, urlbuf); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, in_string); + if (targetfile != NULL) { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, targetfile); + } + else { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, in_string); + } curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curlInterface/GAP package"); @@ -130,7 +201,7 @@ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, typebuf); } - if (verifyCert == True) { + if (verifyCert == False) { // // If you want to connect to a site who isn't using a certificate // that is signed by one of the certs in the CA bundle you have, @@ -171,6 +242,19 @@ curl_global_cleanup(); free(typebuf); + if (targetfile != NULL) { + if (fclose(targetfile) != 0 && errorstring == 0) + errorstring = MakeImmString("cannot write target file"); + if (errorstring == 0 && rename(tempbuf, targetbuf) != 0) + errorstring = MakeImmString("cannot write target file"); + if (errorstring) + remove(tempbuf); + free(tempbuf); + free(targetbuf); + } + + // With a target file there is no body to hand back, so the result record + // has just 'success', or 'success' and 'error'. Obj prec = NEW_PREC(2); SET_LEN_PREC(prec, 2); SET_RNAM_PREC(prec, 1, RNamName("success")); @@ -179,6 +263,10 @@ SET_RNAM_PREC(prec, 2, RNamName("error")); SET_ELM_PREC(prec, 2, errorstring); } + else if (targetfile != NULL) { + SET_LEN_PREC(prec, 1); + SET_ELM_PREC(prec, 1, True); + } else { SET_ELM_PREC(prec, 1, True); SET_RNAM_PREC(prec, 2, RNamName("result")); @@ -195,8 +283,8 @@ // Table of functions to export static StructGVarFunc GVarFuncs[] = { - GVAR_FUNC(CURL_REQUEST, 8, - "url, type, out_string, verifyCert, verbose, followRedirect, failOnError, maxTime"), + GVAR_FUNC(CURL_REQUEST, 9, + "url, type, out_string, verifyCert, verbose, followRedirect, failOnError, maxTime, targetFile"), GVAR_FUNC(CURL_VERSION, 0, ""), { 0 } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/tst/basic.tst new/curlInterface-2.5.0/tst/basic.tst --- old/curlInterface-2.4.4/tst/basic.tst 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/tst/basic.tst 2026-09-18 02:00:00.000000000 +0200 @@ -1,4 +1,4 @@ -#@local r, url, postString, requestType, server, baseurl +#@local r, url, postString, requestType, server, baseurl, file gap> LoadPackage( "curlInterface", false ); true gap> LoadPackage( "io", false ); @@ -110,4 +110,72 @@ false gap> DownloadURL( url, rec( maxTime := 5 ) ).result; "download test response\n" + +# Downloading to a file +gap> file := Filename( DirectoryTemporary(), "target" );; +gap> r := DownloadURL( Concatenation( baseurl, "/success" ), +> rec( targetFile := file ) );; +gap> r.success; +true + +# with a target file there is no body to hand back +gap> RecNames( r ); +[ "success" ] +gap> StringFile( file ); +"download test response\n" + +# a failed request must not leave the file behind +gap> RemoveFile( file );; +gap> r := DownloadURL( Concatenation( baseurl, "/missing" ), +> rec( targetFile := file, failOnError := true ) );; +gap> r.success; +false +gap> IsExistingFile( file ); +false + +# nor after the connection drops mid-transfer +gap> r := DownloadURL( Concatenation( baseurl, "/disconnect" ), +> rec( targetFile := file ) );; +gap> r.success; +false +gap> IsExistingFile( file ); +false + +# an existing file survives a failed download, contents and all +gap> FileString( file, "do not touch\n" );; +gap> r := DownloadURL( Concatenation( baseurl, "/missing" ), +> rec( targetFile := file, failOnError := true ) );; +gap> r.success; +false +gap> StringFile( file ); +"do not touch\n" + +# and likewise when the connection drops mid-transfer +gap> r := DownloadURL( Concatenation( baseurl, "/disconnect" ), +> rec( targetFile := file ) );; +gap> r.success; +false +gap> StringFile( file ); +"do not touch\n" + +# a successful download replaces it +gap> r := DownloadURL( Concatenation( baseurl, "/success" ), +> rec( targetFile := file ) );; +gap> r.success; +true +gap> StringFile( file ); +"download test response\n" +gap> RemoveFile( file );; + +# a target file that cannot be opened is reported, not fatal +gap> r := DownloadURL( Concatenation( baseurl, "/success" ), +> rec( targetFile := "/no/such/directory/target" ) );; +gap> r.success; +false +gap> r.error; +"cannot open target file" + +# argument checking +gap> DownloadURL( baseurl, rec( targetFile := 42 ) ); +Error, CurlRequest: <opts>.targetFile must be a string or false gap> CURLINTERFACE_StopHTTPTestServer( server );; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/tst/errors.tst new/curlInterface-2.5.0/tst/errors.tst --- old/curlInterface-2.4.4/tst/errors.tst 2026-07-19 02:00:00.000000000 +0200 +++ new/curlInterface-2.5.0/tst/errors.tst 2026-09-18 02:00:00.000000000 +0200 @@ -63,4 +63,4 @@ # number of arguments gap> CURL_REQUEST(); -Error, Function: number of arguments must be 8 (not 0) +Error, Function: number of arguments must be 9 (not 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/tst/https-server.g new/curlInterface-2.5.0/tst/https-server.g --- old/curlInterface-2.4.4/tst/https-server.g 1970-01-01 01:00:00.000000000 +0100 +++ new/curlInterface-2.5.0/tst/https-server.g 2026-09-18 02:00:00.000000000 +0200 @@ -0,0 +1,125 @@ +############################################################################# +## +## An HTTPS server with a self-signed certificate for the curlInterface +## tests, used to check that certificates really are verified unless +## `verifyCert` is false. Implemented via `openssl s_server`; if openssl is +## missing, the tests using it are skipped. +## + +BindGlobal( "CURLINTERFACE_OpenSSL", function() + local openssl; + + openssl := Filename( DirectoriesSystemPrograms(), "openssl" ); + if openssl = fail or not IsExecutableFile( openssl ) then + return fail; + fi; + return openssl; +end ); + +# Bind port 0 to have the kernel pick a free port, then release it again. +BindGlobal( "CURLINTERFACE_FreePort", function() + local listener, address, port; + + listener := IO_socket( IO.PF_INET, IO.SOCK_STREAM, "tcp" ); + if listener = fail then + return fail; + fi; + if IO_bind( listener, IO_MakeIPAddressPort( "127.0.0.1", 0 ) ) = fail then + IO_close( listener ); + return fail; + fi; + address := IO_getsockname( listener ); + port := 256 * INT_CHAR( address[3] ) + INT_CHAR( address[4] ); + IO_close( listener ); + return port; +end ); + +# Waits for the server to accept requests. Tries both settings of +# `verifyCert`, so that this succeeds no matter which of them rejects a +# self-signed certificate. +BindGlobal( "CURLINTERFACE_WaitForHTTPSServer", function( url ) + local i; + + for i in [ 1 .. 10 ] do + if DownloadURL( url, rec( verifyCert := false ) ).success or + DownloadURL( url ).success then + return true; + fi; + Sleep( 1 ); + od; + return false; +end ); + +# Returns a record with components `pid` and `url`, or `fail` if no HTTPS +# server could be started. +BindGlobal( "CURLINTERFACE_StartHTTPSTestServer", function() + local openssl, sh, dir, cert, key, port, url, pid, devnull; + + openssl := CURLINTERFACE_OpenSSL(); + sh := Filename( DirectoriesSystemPrograms(), "sh" ); + if openssl = fail or sh = fail then + return fail; + fi; + + # LibreSSL's `req` has no `-quiet`, so silence it via the shell + dir := DirectoryTemporary(); + cert := Filename( dir, "cert.pem" ); + key := Filename( dir, "key.pem" ); + if Process( dir, sh, InputTextNone(), OutputTextNone(), + [ "-c", Concatenation( + "'", openssl, "' req -x509 -newkey rsa:2048 -nodes", + " -keyout '", key, "' -out '", cert, "'", + " -days 1 -subj /CN=localhost 2>/dev/null" ) ] ) <> 0 then + return fail; + fi; + + port := CURLINTERFACE_FreePort(); + if port = fail then + return fail; + fi; + url := Concatenation( "https://localhost:", String( port ), "/" ); + + pid := IO_fork(); + if pid = 0 then + devnull := IO_open( "/dev/null", IO.O_WRONLY, 0 ); + if devnull <> fail then + IO_dup2( devnull, 1 ); + IO_dup2( devnull, 2 ); + fi; + IO_execv( openssl, + [ "s_server", "-quiet", "-www", "-accept", String( port ), + "-cert", cert, "-key", key ] ); + IO_exit( 1 ); + elif pid < 0 then + return fail; + fi; + + if CURLINTERFACE_WaitForHTTPSServer( url ) <> true then + IO_kill( pid, IO.SIGTERM ); + IO_WaitPid( pid, true ); + return fail; + fi; + + return rec( pid := pid, url := url ); +end ); + +BindGlobal( "CURLINTERFACE_StopHTTPSTestServer", function( server ) + if server <> fail then + IO_kill( server.pid, IO.SIGTERM ); + IO_WaitPid( server.pid, true ); + fi; +end ); + +# Downloads from the test server once with the default options and once with +# `verifyCert := false`, and returns the two `success` values. +BindGlobal( "CURLINTERFACE_VerifyCertResults", function( server ) + if server = fail then + if CURLINTERFACE_OpenSSL() = fail then + # skip the test on systems without openssl + return [ false, true ]; + fi; + return "could not start the HTTPS test server"; + fi; + return [ DownloadURL( server.url ).success, + DownloadURL( server.url, rec( verifyCert := false ) ).success ]; +end ); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curlInterface-2.4.4/tst/https.tst new/curlInterface-2.5.0/tst/https.tst --- old/curlInterface-2.4.4/tst/https.tst 1970-01-01 01:00:00.000000000 +0100 +++ new/curlInterface-2.5.0/tst/https.tst 2026-09-18 02:00:00.000000000 +0200 @@ -0,0 +1,13 @@ +#@local server +gap> LoadPackage( "curlInterface", false ); +true +gap> LoadPackage( "io", false ); +true +gap> ReadPackage( "curlInterface", "tst/https-server.g" );; +gap> server := CURLINTERFACE_StartHTTPSTestServer();; + +# A self-signed certificate is rejected by default, and accepted only if +# verification is turned off explicitly +gap> CURLINTERFACE_VerifyCertResults( server ); +[ false, true ] +gap> CURLINTERFACE_StopHTTPSTestServer( server );
