Skip to content

Commit 7f1808c

Browse files
committed
Add weboptions to urlread() and urlwrite()
The Matlab versions of urlread() and urlwrite() can accept various options such as timeouts and user credentials etc. This changeset implements those features in Octave, while retaining backward compatibility with the old calling form (passing cell arrays). It also adds 'CharacterEncoding' to the weboptions struct definition for Matlab compatibility and future use. * libinterp/corefcn/urlwrite.cc: Add a new static helper function 'parse_property_value_pairs()' to parse any options that are passed to Furlread and Furlwrite, and change those two DEFUNs to use the new function and the weboptions features. * liboctave/util/url-transfer.h: Add a new member variable 'std::string CharacterEncoding' to 'struct weboptions'.
1 parent 60abf8a commit 7f1808c

File tree

2 files changed

+191
-22
lines changed

2 files changed

+191
-22
lines changed

libinterp/corefcn/urlwrite.cc

Lines changed: 190 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <string>
3131
#include <fstream>
3232
#include <iomanip>
33+
#include <algorithm>
3334

3435
#include "dir-ops.h"
3536
#include "file-ops.h"
@@ -54,12 +55,90 @@
5455

5556
OCTAVE_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+
57135
DEFUN (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{})
63142
Download 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
101206
urlwrite ("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",
176298
DEFUN (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{})
182304
Download a remote file specified by its @var{url} and return its content
183305
in string @var{s}.
184306
@@ -203,33 +325,70 @@ For example:
203325
s = 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
213364
s = 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 ())

liboctave/util/url-transfer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct weboptions
4949
std::string RequestMethod;
5050
std::string ArrayFormat;
5151
std::string CertificateFilename;
52+
std::string CharacterEncoding;
5253
};
5354

5455
class OCTAVE_API base_url_transfer

0 commit comments

Comments
 (0)