GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
urlwrite.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2006-2026 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include <string>
31#include <fstream>
32#include <iomanip>
33#include <algorithm>
34
35#include "dir-ops.h"
36#include "file-ops.h"
37#include "file-stat.h"
38#include "glob-match.h"
39#include "oct-env.h"
40#include "oct-handle.h"
41#include "oct-sysdep.h"
42#include "url-transfer.h"
43
44#include "defun.h"
45#include "error.h"
46#include "interpreter.h"
47#include "oct-map.h"
48#include "oct-refcount.h"
49#include "ov-cell.h"
50#include "ov-classdef.h"
51#include "ovl.h"
52#include "pager.h"
53#include "unwind-prot.h"
54#include "url-handle-manager.h"
55
57
58// Helper function to parse property/value pairs into weboptions struct
59static void
60parse_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
135DEFUN (urlwrite, args, nargout,
136 doc: /* -*- texinfo -*-
137@deftypefn {} {} 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{})
142Download a remote file specified by its @var{url} and save it as
143@var{localfile}.
144
145For example:
146
147@example
148@group
149urlwrite ("http://ftp.octave.org/pub/README",
150 "README.txt");
151@end group
152@end example
153
154The full path of the downloaded file is returned in @var{f}.
155
156The variable @var{success} is 1 if the download was successful,
157otherwise it is 0 in which case @var{message} contains an error message.
158
159If no output argument is specified and an error occurs, then the error is
160signaled through Octave's error handling mechanism.
161
162This function uses libcurl. The curl library supports, among others, the HTTP,
163FTP, and FILE protocols. Username and password may be specified in the URL,
164for example:
165
166@example
167@group
168urlwrite ("http://username:password@@example.com/file.txt",
169 "file.txt");
170@end group
171@end example
172
173Additional options can be specified using property/value pairs:
174
175@table @code
176@item Get
177Cell array of name/value pairs for GET request parameters.
178
179@item Post
180Cell array of name/value pairs for POST request parameters.
181
182@item Timeout
183Timeout value in seconds.
184
185@item UserAgent
186User agent string for the request.
187
188@item Username
189Username for authentication.
190
191@item Password
192Password for authentication.
193@end table
194
195Example with property/value pairs:
196
197@example
198@group
199urlwrite ("http://www.example.com/data.txt", "data.txt",
200 "Timeout", 10, "UserAgent", "Octave");
201@end group
202@end example
203
204For backward compatibility, the old calling form is also supported:
205
206@example
207@group
208urlwrite ("http://www.google.com/search", "search.html",
209 "get", @{"query", "octave"@});
210@end group
211@end example
212@seealso{urlread, weboptions}
213@end deftypefn */)
214{
215 int nargin = args.length ();
216
217 // verify arguments
218 if (nargin < 2)
219 print_usage ();
220
221 std::string url = args(0).xstring_value ("urlwrite: URL must be a string");
222
223 // name to store the file if download is successful
224 std::string filename = args(1).xstring_value ("urlwrite: LOCALFILE must be a string");
225
226 std::string method = "get";
227 Array<std::string> param;
228
229 // Create a weboptions struct to hold option values
230 struct weboptions options;
231 // Set default timeout to match weboptions default
232 options.Timeout = 5000; // 5 seconds in milliseconds
233
234 // Check if old calling form with 4 arguments (backward compatibility)
235 if (nargin == 4 && args(2).is_string ()
236 && (args(2).string_value () == "get" || args(2).string_value () == "post"))
237 {
238 // Old calling form: urlwrite(url, file, method, param)
239 method = args(2).xstring_value ("urlwrite: METHOD must be a string");
240
241 if (method != "get" && method != "post")
242 error (R"(urlwrite: METHOD must be "get" or "post")");
243
244 param = args(3).xcellstr_value ("urlwrite: parameters (PARAM) for get and post requests must be given as a cell array of strings");
245
246 if (param.numel () % 2 == 1)
247 error ("urlwrite: number of elements in PARAM must be even");
248 }
249 else if (nargin > 2)
250 {
251 // New property/value pairs form
252 parse_property_value_pairs (args, 2, options, method, param, "urlwrite");
253 }
254
255 // The file should only be deleted if it doesn't initially exist, we
256 // create it, and the download fails. We use unwind_protect to do
257 // it so that the deletion happens no matter how we exit the function.
258
259 std::ofstream ofile =
260 sys::ofstream (filename.c_str (), std::ios::out | std::ios::binary);
261
262 if (! ofile.is_open ())
263 error ("urlwrite: unable to open file");
264
265 int(*unlink_fptr)(const std::string&) = sys::unlink;
266 unwind_action_safe unlink_action (unlink_fptr, filename);
267
268 url_transfer url_xfer (url, ofile);
269
270 octave_value_list retval;
271
272 if (! url_xfer.is_valid ())
273 error ("support for URL transfers was disabled when Octave was built");
274
275 // Apply weboptions if any were set
276 url_xfer.set_weboptions (options);
277
278 // Perform the HTTP action
279 url_xfer.http_action (param, method);
280
281 ofile.close ();
282
283 if (url_xfer.good ())
284 unlink_action.discard ();
285
286 if (nargout > 0)
287 {
288 if (url_xfer.good ())
289 retval = ovl (sys::env::make_absolute (filename), true, "");
290 else
291 retval = ovl ("", false, url_xfer.lasterror ());
292 }
293
294 if (nargout < 2 && ! url_xfer.good ())
295 error ("urlwrite: %s", url_xfer.lasterror ().c_str ());
296
297 return retval;
298}
299
300DEFUN (urlread, args, nargout,
301 doc: /* -*- texinfo -*-
302@deftypefn {} {@var{s} =} urlread (@var{url})
303@deftypefnx {} {@var{s} =} urlread (@var{url}, @var{Name}, @var{Value}, @dots{})
304@deftypefnx {} {[@var{s}, @var{success}] =} urlread (@var{url}, @dots{})
305@deftypefnx {} {[@var{s}, @var{success}, @var{message}] =} urlread (@var{url}, @dots{})
306Download a remote file specified by its @var{url} and return its content
307in string @var{s}.
308
309For example:
310
311@example
312s = urlread ("http://ftp.octave.org/pub/README");
313@end example
314
315The variable @var{success} is 1 if the download was successful,
316otherwise it is 0 in which case @var{message} contains an error
317message.
318
319If no output argument is specified and an error occurs, then the error is
320signaled through Octave's error handling mechanism.
321
322This function uses libcurl. The curl library supports, among others, the HTTP,
323FTP, and FILE protocols. Username and password may be specified in the URL@.
324For example:
325
326@example
327s = urlread ("http://user:password@@example.com/file.txt");
328@end example
329
330Additional options can be specified using property/value pairs:
331
332@table @code
333@item Get
334Cell array of name/value pairs for GET request parameters.
335
336@item Post
337Cell array of name/value pairs for POST request parameters.
338
339@item Timeout
340Timeout value in seconds.
341
342@item UserAgent
343User agent string for the request.
344
345@item Username
346Username for authentication.
347
348@item Password
349Password for authentication.
350
351@item Charset
352Character encoding (stored but not currently used).
353@end table
354
355Example with property/value pairs:
356
357@example
358@group
359s = urlread ("http://www.example.com/data",
360 "Get", @{"term", "octave"@}, "Timeout", 10);
361@end group
362@end example
363
364For backward compatibility, the old calling form is also supported:
365
366@example
367@group
368s = urlread ("http://www.google.com/search",
369 "get", @{"query", "octave"@});
370@end group
371@end example
372@seealso{urlwrite, weboptions}
373@end deftypefn */)
374{
375 int nargin = args.length ();
376
377 // verify arguments
378 if (nargin < 1)
379 print_usage ();
380
381 std::string url = args(0).xstring_value ("urlread: URL must be a string");
382
383 std::string method = "get";
384 Array<std::string> param;
385
386 // Create a weboptions struct to hold option values
387 struct weboptions options;
388 // Set default timeout to match weboptions default
389 options.Timeout = 5000; // 5 seconds in milliseconds
390
391 // Check if old calling form with 3 arguments (backward compatibility)
392 if (nargin == 3 && args(1).is_string ()
393 && (args(1).string_value () == "get" || args(1).string_value () == "post"))
394 {
395 // Old calling form: urlread(url, method, param)
396 method = args(1).xstring_value ("urlread: METHOD must be a string");
397
398 if (method != "get" && method != "post")
399 error (R"(urlread: METHOD must be "get" or "post")");
400
401 param = args(2).xcellstr_value ("urlread: parameters (PARAM) for get and post requests must be given as a cell array of strings");
402
403 if (param.numel () % 2 == 1)
404 error ("urlread: number of elements in PARAM must be even");
405 }
406 else if (nargin > 1)
407 {
408 // New property/value pairs form
409 parse_property_value_pairs (args, 1, options, method, param, "urlread");
410 }
411
412 std::ostringstream buf;
413
414 url_transfer url_xfer = url_transfer (url, buf);
415
416 if (! url_xfer.is_valid ())
417 error ("support for URL transfers was disabled when Octave was built");
418
419 // Apply weboptions if any were set
420 url_xfer.set_weboptions (options);
421
422 // Perform the HTTP action
423 url_xfer.http_action (param, method);
424
425 if (nargout < 2 && ! url_xfer.good ())
426 error ("urlread: %s", url_xfer.lasterror ().c_str ());
427
428 octave_value_list retval (std::max (1, std::min (nargout, 3)));
429
430 retval(0) = buf.str ();
431 if (nargout > 1)
432 retval(1) = url_xfer.good ();
433 if (nargout > 2)
434 retval(2) = url_xfer.good () ? "" : url_xfer.lasterror ();
435
436 return retval;
437}
438
439DEFUN (__restful_service__, args, nargout,
440 doc: /* -*- texinfo -*-
441@deftypefn {} {@var{response} =} __restful_service__ (@var{url}, @var{param}, @var{weboptions})
442Undocumented internal function.
443@end deftypefn */)
444{
445 int nargin = args.length ();
446
447 if (nargin < 1)
448 print_usage ();
449
450 std::string url = args(0).xstring_value ("__restful_service__: URL must be a string");
451
452 std::ostringstream content;
453
454 url_transfer url_xfer (url, content);
455
456 if (! url_xfer.is_valid ())
457 error ("support for URL transfers was disabled when Octave was built");
458
459 Array<std::string> param = args(1).cellstr_value ();
460
461 std::string data, method;
462
463 struct weboptions options;
464
465 cdef_object object
466 = args (nargin - 1).classdef_object_value () -> get_object ();
467
468 // We could've used object.map_value () instead to return a map but that
469 // shows a warning about about overriding access restrictions.
470 // Nevertheless, we are keeping checking that here if the keys are not
471 // equal to "delete" and "display", getting away with the warning.
472 string_vector keys = object.map_keys ();
473
474 for (int i = 0; i < keys.numel (); i++)
475 {
476 if (keys(i) == "Timeout")
477 {
478 float timeout = object.get (keys(i)).float_value ();
479 options.Timeout = static_cast<long> (timeout * 1000);
480 }
481
482 if (keys(i) == "HeaderFields")
483 {
484 options.HeaderFields = object.get (keys(i)).cellstr_value ();
485 }
486
487 // FIXME: 'delete' and 'display', auto-generated, probably by cdef_object
488 // class? Remaining fields have already been adjusted elsewhere in the
489 // m-script. Set 'value' as the Value of the Key wherever it's a string.
490 if (keys(i) != "Timeout" && keys(i) != "HeaderFields"
491 && keys(i) != "delete" && keys(i) != "display")
492 {
493 std::string value = object.get (keys(i)).string_value ();
494
495 if (keys(i) == "UserAgent")
496 options.UserAgent = value;
497
498 if (keys(i) == "Username")
499 options.Username = value;
500
501 if (keys(i) == "Password")
502 options.Password = value;
503
504 if (keys(i) == "ContentReader")
505 // Unimplemented. Only for MATLAB compatibility.
506 options.ContentReader = "";
507
508 if (keys(i) == "RequestMethod")
509 method = value;
510
511 if (keys(i) == "ArrayFormat")
512 options.ArrayFormat = value;
513
514 if (keys(i) == "CertificateFilename")
515 options.CertificateFilename = "";
516 }
517 }
518
519 url_xfer.set_weboptions (options);
520
521 url_xfer.http_action (param, method);
522
523 if (nargout < 2 && ! url_xfer.good ())
524 error ("__restful_service__: %s", url_xfer.lasterror ().c_str ());
525
526 return ovl (content.str ());
527}
528
529OCTAVE_END_NAMESPACE(octave)
N Dimensional Array with copy-on-write semantics.
Definition Array-base.h:130
octave_idx_type numel() const
Number of elements in the array.
Definition Array-base.h:440
octave_idx_type length() const
Definition ovl.h:111
octave_idx_type numel() const
Definition str-vec.h:98
void set_weboptions(const struct weboptions &param)
bool is_valid() const
std::string lasterror() const
void http_action(const Array< std::string > &param, const std::string &action)
bool good() const
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage()
Definition defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition defun.h:56
void error(const char *fmt,...)
Definition error.cc:1008
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition ovl.h:217
std::string UserAgent
std::string ArrayFormat
std::string ContentReader
Array< std::string > HeaderFields
std::string Password
std::string CertificateFilename
std::string Username
std::string CharacterEncoding