3030#include < string>
3131#include < fstream>
3232#include < iomanip>
33+ #include < algorithm>
3334
3435#include " dir-ops.h"
3536#include " file-ops.h"
5455
5556OCTAVE_BEGIN_NAMESPACE (octave)
5657
58+ // Helper function to parse property/value pairs into weboptions struct
59+ static void
60+ parse_property_value_pairs (const octave_value_list& args, int start_idx,
61+ struct weboptions & options,
62+ std::string& method, Array<std::string>& param,
63+ const std::string& who)
64+ {
65+ int nargin = args.length ();
66+
67+ if ((nargin - start_idx) % 2 != 0 )
68+ error (" %s: property/value arguments must occur in pairs" , who.c_str ());
69+
70+ // Parse property/value pairs
71+ for (int i = start_idx; i < nargin; i += 2 )
72+ {
73+ if (! args (i).is_string ())
74+ error (" %s: property names must be strings" , who.c_str ());
75+
76+ std::string property = args (i).string_value ();
77+
78+ // Convert property name to lowercase for case-insensitive comparison
79+ std::transform (property.begin (), property.end (), property.begin (),
80+ [] (unsigned char c) { return std::tolower (c); });
81+
82+ if (property == " get" )
83+ {
84+ method = " get" ;
85+ std::string err_msg = who + " : 'Get' value must be a cell array of strings" ;
86+ param = args (i+1 ).xcellstr_value (err_msg.c_str ());
87+ if (param.numel () % 2 == 1 )
88+ error (" %s: number of elements in 'Get' cell array must be even" , who.c_str ());
89+ }
90+ else if (property == " post" )
91+ {
92+ method = " post" ;
93+ std::string err_msg = who + " : 'Post' value must be a cell array of strings" ;
94+ param = args (i+1 ).xcellstr_value (err_msg.c_str ());
95+ if (param.numel () % 2 == 1 )
96+ error (" %s: number of elements in 'Post' cell array must be even" , who.c_str ());
97+ }
98+ else if (property == " timeout" )
99+ {
100+ std::string err_msg = who + " : 'Timeout' value must be numeric" ;
101+ double timeout = args (i+1 ).xdouble_value (err_msg.c_str ());
102+ if (timeout <= 0 )
103+ error (" %s: 'Timeout' value must be positive" , who.c_str ());
104+ // Convert to milliseconds
105+ options.Timeout = static_cast <long > (timeout * 1000 );
106+ }
107+ else if (property == " useragent" )
108+ {
109+ std::string err_msg = who + " : 'UserAgent' value must be a string" ;
110+ options.UserAgent = args (i+1 ).xstring_value (err_msg.c_str ());
111+ }
112+ else if (property == " username" )
113+ {
114+ std::string err_msg = who + " : 'Username' value must be a string" ;
115+ options.Username = args (i+1 ).xstring_value (err_msg.c_str ());
116+ }
117+ else if (property == " password" )
118+ {
119+ std::string err_msg = who + " : 'Password' value must be a string" ;
120+ options.Password = args (i+1 ).xstring_value (err_msg.c_str ());
121+ }
122+ else if (property == " charset" )
123+ {
124+ // Store for potential future use, but not used by curl_transfer yet
125+ std::string err_msg = who + " : 'Charset' value must be a string" ;
126+ options.CharacterEncoding = args (i+1 ).xstring_value (err_msg.c_str ());
127+ }
128+ else
129+ {
130+ error (" %s: unknown property '%s'" , who.c_str (), args (i).string_value ().c_str ());
131+ }
132+ }
133+ }
134+
57135DEFUN (urlwrite, args, nargout,
58136 doc: /* -*- texinfo -*-
59137@deftypefn {} {} urlwrite (@var{url}, @var{localfile})
60- @deftypefnx {} {@var{f} =} urlwrite (@var{url}, @var{localfile})
61- @deftypefnx {} {[@var{f}, @var{success}] =} urlwrite (@var{url}, @var{localfile})
62- @deftypefnx {} {[@var{f}, @var{success}, @var{message}] =} urlwrite (@var{url}, @var{localfile})
138+ @deftypefnx {} {} urlwrite (@var{url}, @var{localfile}, @var{Name}, @var{Value}, @dots{})
139+ @deftypefnx {} {@var{f} =} urlwrite (@var{url}, @var{localfile}, @dots{})
140+ @deftypefnx {} {[@var{f}, @var{success}] =} urlwrite (@var{url}, @var{localfile}, @dots{})
141+ @deftypefnx {} {[@var{f}, @var{success}, @var{message}] =} urlwrite (@var{url}, @var{localfile}, @dots{})
63142Download a remote file specified by its @var{url} and save it as
64143@var{localfile}.
65144
@@ -91,36 +170,70 @@ urlwrite ("http://username:password@@example.com/file.txt",
91170@end group
92171@end example
93172
94- GET and POST requests can be specified by @var{method} and @var{param}.
95- The parameter @var{method} is either @samp{get} or @samp{post} and
96- @var{param} is a cell array of parameter and value pairs.
97- For example:
173+ Additional options can be specified using property/value pairs:
174+
175+ @table @code
176+ @item Get
177+ Cell array of name/value pairs for GET request parameters.
178+
179+ @item Post
180+ Cell array of name/value pairs for POST request parameters.
181+
182+ @item Timeout
183+ Timeout value in seconds.
184+
185+ @item UserAgent
186+ User agent string for the request.
98187
188+ @item Username
189+ Username for authentication.
190+
191+ @item Password
192+ Password for authentication.
193+ @end table
194+
195+ Example with property/value pairs:
196+ @example
197+ @group
198+ urlwrite ("http://www.example.com/data.txt", "data.txt",
199+ "Timeout", 10, "UserAgent", "Octave");
200+ @end group
201+ @end example
202+
203+ For backward compatibility, the old calling form is also supported:
99204@example
100205@group
101206urlwrite ("http://www.google.com/search", "search.html",
102207 "get", @{"query", "octave"@});
103208@end group
104209@end example
105- @seealso{urlread}
210+ @seealso{urlread, weboptions }
106211@end deftypefn */ )
107212{
108213 int nargin = args.length ();
109214
110215 // verify arguments
111- if (nargin != 2 && nargin != 4 )
216+ if (nargin < 2 )
112217 print_usage ();
113218
114219 std::string url = args (0 ).xstring_value (" urlwrite: URL must be a string" );
115220
116221 // name to store the file if download is successful
117222 std::string filename = args (1 ).xstring_value (" urlwrite: LOCALFILE must be a string" );
118223
119- std::string method;
224+ std::string method = " get " ;
120225 Array<std::string> param;
121226
122- if (nargin == 4 )
227+ // Create a weboptions struct to hold option values
228+ struct weboptions options;
229+ // Set default timeout to match weboptions default
230+ options.Timeout = 5000 ; // 5 seconds in milliseconds
231+
232+ // Check if old calling form with 4 arguments (backward compatibility)
233+ if (nargin == 4 && args (2 ).is_string ()
234+ && (args (2 ).string_value () == " get" || args (2 ).string_value () == " post" ))
123235 {
236+ // Old calling form: urlwrite(url, file, method, param)
124237 method = args (2 ).xstring_value (" urlwrite: METHOD must be a string" );
125238
126239 if (method != " get" && method != " post" )
@@ -131,6 +244,11 @@ urlwrite ("http://www.google.com/search", "search.html",
131244 if (param.numel () % 2 == 1 )
132245 error (" urlwrite: number of elements in PARAM must be even" );
133246 }
247+ else if (nargin > 2 )
248+ {
249+ // New property/value pairs form
250+ parse_property_value_pairs (args, 2 , options, method, param, " urlwrite" );
251+ }
134252
135253 // The file should only be deleted if it doesn't initially exist, we
136254 // create it, and the download fails. We use unwind_protect to do
@@ -152,6 +270,10 @@ urlwrite ("http://www.google.com/search", "search.html",
152270 if (! url_xfer.is_valid ())
153271 error (" support for URL transfers was disabled when Octave was built" );
154272
273+ // Apply weboptions if any were set
274+ url_xfer.set_weboptions (options);
275+
276+ // Perform the HTTP action
155277 url_xfer.http_action (param, method);
156278
157279 ofile.close ();
@@ -176,9 +298,9 @@ urlwrite ("http://www.google.com/search", "search.html",
176298DEFUN (urlread, args, nargout,
177299 doc: /* -*- texinfo -*-
178300@deftypefn {} {@var{s} =} urlread (@var{url})
179- @deftypefnx {} {[ @var{s}, @var{success}] =} urlread (@var{url})
180- @deftypefnx {} {[@var{s}, @var{success}, @var{message} ] =} urlread (@var{url})
181- @deftypefnx {} {[@dots{} ] =} urlread (@var{url}, @var{method}, @var{param })
301+ @deftypefnx {} {@var{s} =} urlread (@var{url}, @var{Name}, @var{Value}, @dots{ })
302+ @deftypefnx {} {[@var{s}, @var{success}] =} urlread (@var{url}, @dots{ })
303+ @deftypefnx {} {[@var{s}, @var{success}, @var{message} ] =} urlread (@var{url}, @dots{ })
182304Download a remote file specified by its @var{url} and return its content
183305in string @var{s}.
184306
@@ -203,33 +325,70 @@ For example:
203325s = urlread ("http://user:password@@example.com/file.txt");
204326@end example
205327
206- GET and POST requests can be specified by @var{method} and @var{param}.
207- The parameter @var{method} is either @samp{get} or @samp{post} and
208- @var{param} is a cell array of parameter and value pairs.
209- For example:
328+ Additional options can be specified using property/value pairs:
329+
330+ @table @code
331+ @item Get
332+ Cell array of name/value pairs for GET request parameters.
333+
334+ @item Post
335+ Cell array of name/value pairs for POST request parameters.
336+
337+ @item Timeout
338+ Timeout value in seconds.
210339
340+ @item UserAgent
341+ User agent string for the request.
342+
343+ @item Username
344+ Username for authentication.
345+
346+ @item Password
347+ Password for authentication.
348+
349+ @item Charset
350+ Character encoding (stored but not currently used).
351+ @end table
352+
353+ Example with property/value pairs:
354+ @example
355+ @group
356+ s = urlread ("http://www.example.com/data",
357+ "Get", @{"term", "octave"@}, "Timeout", 10);
358+ @end group
359+ @end example
360+
361+ For backward compatibility, the old calling form is also supported:
211362@example
212363@group
213364s = urlread ("http://www.google.com/search",
214365 "get", @{"query", "octave"@});
215366@end group
216367@end example
217- @seealso{urlwrite}
368+ @seealso{urlwrite, weboptions }
218369@end deftypefn */ )
219370{
220371 int nargin = args.length ();
221372
222373 // verify arguments
223- if (nargin != 1 && nargin != 3 )
374+ if (nargin < 1 )
224375 print_usage ();
225376
226377 std::string url = args (0 ).xstring_value (" urlread: URL must be a string" );
227378
228- std::string method;
379+ std::string method = " get " ;
229380 Array<std::string> param;
230381
231- if (nargin == 3 )
382+ // Create a weboptions struct to hold option values
383+ struct weboptions options;
384+ // Set default timeout to match weboptions default
385+ options.Timeout = 5000 ; // 5 seconds in milliseconds
386+
387+ // Check if old calling form with 3 arguments (backward compatibility)
388+ if (nargin == 3 && args (1 ).is_string ()
389+ && (args (1 ).string_value () == " get" || args (1 ).string_value () == " post" ))
232390 {
391+ // Old calling form: urlread(url, method, param)
233392 method = args (1 ).xstring_value (" urlread: METHOD must be a string" );
234393
235394 if (method != " get" && method != " post" )
@@ -240,6 +399,11 @@ s = urlread ("http://www.google.com/search",
240399 if (param.numel () % 2 == 1 )
241400 error (" urlread: number of elements in PARAM must be even" );
242401 }
402+ else if (nargin > 1 )
403+ {
404+ // New property/value pairs form
405+ parse_property_value_pairs (args, 1 , options, method, param, " urlread" );
406+ }
243407
244408 std::ostringstream buf;
245409
@@ -248,6 +412,10 @@ s = urlread ("http://www.google.com/search",
248412 if (! url_xfer.is_valid ())
249413 error (" support for URL transfers was disabled when Octave was built" );
250414
415+ // Apply weboptions if any were set
416+ url_xfer.set_weboptions (options);
417+
418+ // Perform the HTTP action
251419 url_xfer.http_action (param, method);
252420
253421 if (nargout < 2 && ! url_xfer.good ())
0 commit comments