Update Red Hat Copyright Notices
[nbdkit.git] / plugins / curl / scripts.c
blobb2f625899962977043a2b969088dc1bb4efb5720
1 /* nbdkit
2 * Copyright Red Hat
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 /* Header and cookie scripts. */
35 #include <config.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <stdbool.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <time.h>
43 #include <assert.h>
44 #include <pthread.h>
46 #include <curl/curl.h>
48 #include <nbdkit-plugin.h>
50 #include "ascii-ctype.h"
51 #include "cleanup.h"
52 #include "utils.h"
54 #include "curldefs.h"
56 #ifndef WIN32
58 /* This lock protects internal state in this file. */
59 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
61 /* Last time header-script or cookie-script was run. */
62 static time_t header_last = 0;
63 static time_t cookie_last = 0;
64 static bool header_script_has_run = false;
65 static bool cookie_script_has_run = false;
66 static unsigned header_iteration = 0;
67 static unsigned cookie_iteration = 0;
69 /* Last set of headers and cookies generated by the scripts. */
70 static struct curl_slist *headers_from_script = NULL;
71 static char *cookies_from_script = NULL;
73 /* Debug scripts by setting -D curl.scripts=1 */
74 NBDKIT_DLL_PUBLIC int curl_debug_scripts;
76 void
77 scripts_unload (void)
79 curl_slist_free_all (headers_from_script);
80 free (cookies_from_script);
83 static int run_header_script (struct curl_handle *);
84 static int run_cookie_script (struct curl_handle *);
85 static void error_from_tmpfile (const char *what, const char *tmpfile);
87 /* This is called from any thread just before we make a curl request.
89 * Because the curl handle must be obtained through get_handle() we
90 * can be assured of exclusive access here.
92 int
93 do_scripts (struct curl_handle *ch)
95 time_t now;
96 struct curl_slist *p;
98 /* Return quickly without acquiring the lock if this feature is not
99 * being used.
101 if (!header_script && !cookie_script)
102 return 0;
104 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
106 /* Run or re-run header-script if we need to. */
107 if (header_script) {
108 time (&now);
109 if (!header_script_has_run ||
110 (header_script_renew > 0 && now - header_last >= header_script_renew)) {
111 if (run_header_script (ch) == -1)
112 return -1;
113 header_last = now;
114 header_script_has_run = true;
118 /* Run or re-run cookie-script if we need to. */
119 if (cookie_script) {
120 time (&now);
121 if (!cookie_script_has_run ||
122 (cookie_script_renew > 0 && now - cookie_last >= cookie_script_renew)) {
123 if (run_cookie_script (ch) == -1)
124 return -1;
125 cookie_last = now;
126 cookie_script_has_run = true;
130 /* Set headers and cookies in the handle.
132 * When calling CURLOPT_HTTPHEADER we have to keep the list around
133 * because unfortunately curl doesn't take a copy. Since we don't
134 * know which other threads might be using it, we must make a copy
135 * of the global list (headers_from_script) per handle
136 * (ch->headers_copy). For CURLOPT_COOKIE, curl internally takes a
137 * copy so we don't need to do this.
139 if (ch->headers_copy) {
140 curl_easy_setopt (ch->c, CURLOPT_HTTPHEADER, NULL);
141 curl_slist_free_all (ch->headers_copy);
142 ch->headers_copy = NULL;
144 for (p = headers_from_script; p != NULL; p = p->next) {
145 if (curl_debug_scripts)
146 nbdkit_debug ("header-script: setting header %s", p->data);
147 ch->headers_copy = curl_slist_append (ch->headers_copy, p->data);
148 if (ch->headers_copy == NULL) {
149 nbdkit_error ("curl_slist_append: %m");
150 return -1;
153 curl_easy_setopt (ch->c, CURLOPT_HTTPHEADER, ch->headers_copy);
155 if (curl_debug_scripts && cookies_from_script)
156 nbdkit_debug ("cookie-script: setting cookie %s", cookies_from_script);
157 curl_easy_setopt (ch->c, CURLOPT_COOKIE, cookies_from_script);
159 return 0;
162 /* This is called with the lock held when we must run or re-run the
163 * header-script.
165 static int
166 run_header_script (struct curl_handle *ch)
168 int fd;
169 char tmpfile[] = "/tmp/errorsXXXXXX";
170 FILE *fp;
171 CLEANUP_FREE char *cmd = NULL, *line = NULL;
172 ssize_t n;
173 size_t len = 0, linelen = 0, nr_headers = 0;
175 assert (header_script != NULL); /* checked by caller */
177 /* Reset the list of headers. */
178 curl_slist_free_all (headers_from_script);
179 headers_from_script = NULL;
181 /* Create a temporary file for the errors so we can redirect them
182 * into nbdkit_error.
184 fd = mkstemp (tmpfile);
185 if (fd == -1) {
186 nbdkit_error ("mkstemp");
187 return -1;
189 close (fd);
191 /* Generate the full script with the local $url variable. */
192 fp = open_memstream (&cmd, &len);
193 if (fp == NULL) {
194 nbdkit_error ("open_memstream: %m");
195 return -1;
197 fprintf (fp, "exec </dev/null\n"); /* Avoid stdin leaking (nbdkit -s). */
198 fprintf (fp, "exec 2>%s\n", tmpfile); /* Catch errors to a temporary file. */
199 fprintf (fp, "url="); /* Set the shell variables. */
200 shell_quote (url, fp);
201 putc ('\n', fp);
202 fprintf (fp, "iteration=%u\n", header_iteration++);
203 putc ('\n', fp);
204 fprintf (fp, "%s", header_script); /* The script or command. */
205 if (fclose (fp) == EOF) {
206 nbdkit_error ("memstream failed");
207 return -1;
210 /* Run the script and read the headers. */
211 nbdkit_debug ("curl: running header-script");
212 fp = popen (cmd, "r");
213 if (fp == NULL) {
214 nbdkit_error ("popen: %m");
215 return -1;
217 while ((n = getline (&line, &linelen, fp)) != -1) {
218 /* Remove trailing \n and whitespace. */
219 while (n > 0 && ascii_isspace (line[n-1]))
220 line[--n] = '\0';
221 if (n == 0)
222 continue;
224 headers_from_script = curl_slist_append (headers_from_script, line);
225 if (headers_from_script == NULL) {
226 nbdkit_error ("curl_slist_append: %m");
227 pclose (fp);
228 return -1;
230 nr_headers++;
233 if (pclose (fp) != 0) {
234 error_from_tmpfile ("header-script", tmpfile);
235 return -1;
238 nbdkit_debug ("header-script returned %zu header(s)", nr_headers);
239 return 0;
242 /* This is called with the lock held when we must run or re-run the
243 * cookie-script.
245 static int
246 run_cookie_script (struct curl_handle *ch)
248 int fd;
249 char tmpfile[] = "/tmp/errorsXXXXXX";
250 FILE *fp;
251 CLEANUP_FREE char *cmd = NULL, *line = NULL;
252 ssize_t n;
253 size_t len = 0, linelen = 0;
255 assert (cookie_script != NULL); /* checked by caller */
257 /* Reset the cookies. */
258 free (cookies_from_script);
259 cookies_from_script = NULL;
261 /* Create a temporary file for the errors so we can redirect them
262 * into nbdkit_error.
264 fd = mkstemp (tmpfile);
265 if (fd == -1) {
266 nbdkit_error ("mkstemp");
267 return -1;
269 close (fd);
271 /* Generate the full script with the local $url variable. */
272 fp = open_memstream (&cmd, &len);
273 if (fp == NULL) {
274 nbdkit_error ("open_memstream: %m");
275 return -1;
277 fprintf (fp, "exec </dev/null\n"); /* Avoid stdin leaking (nbdkit -s). */
278 fprintf (fp, "exec 2>%s\n", tmpfile); /* Catch errors to a temporary file. */
279 fprintf (fp, "url="); /* Set the shell variable. */
280 shell_quote (url, fp);
281 putc ('\n', fp);
282 fprintf (fp, "iteration=%u\n", cookie_iteration++);
283 putc ('\n', fp);
284 fprintf (fp, "%s", cookie_script); /* The script or command. */
285 if (fclose (fp) == EOF) {
286 nbdkit_error ("memstream failed");
287 return -1;
290 /* Run the script and read the cookies. */
291 nbdkit_debug ("curl: running cookie-script");
292 fp = popen (cmd, "r");
293 if (fp == NULL) {
294 nbdkit_error ("popen: %m");
295 return -1;
297 n = getline (&line, &linelen, fp);
298 if (n > 0) {
299 /* Remove trailing \n and whitespace. */
300 while (n > 0 && ascii_isspace (line[n-1]))
301 line[--n] = '\0';
302 if (n > 0) {
303 cookies_from_script = strdup (line);
304 if (cookies_from_script == NULL) {
305 nbdkit_error ("strdup");
306 pclose (fp);
307 return -1;
312 if (pclose (fp) != 0) {
313 error_from_tmpfile ("cookie-script", tmpfile);
314 return -1;
317 nbdkit_debug ("cookie-script returned %scookies",
318 cookies_from_script ? "" : "no ");
319 return 0;
322 /* If the command failed, the error message should be in the temporary
323 * file to which we redirected the script's stderr. We only read the
324 * first line.
326 static void
327 error_from_tmpfile (const char *what, const char *tmpfile)
329 FILE *fp;
330 CLEANUP_FREE char *line = NULL;
331 ssize_t n;
332 size_t linelen = 0;
334 fp = fopen (tmpfile, "r");
336 if (fp && (n = getline (&line, &linelen, fp)) >= 0) {
337 if (n > 0 && line[n-1] == '\n')
338 line[n-1] = '\0';
339 nbdkit_error ("%s failed: %s", what, line);
341 else
342 nbdkit_error ("%s failed", what);
344 if (fp) fclose (fp);
347 #else /* WIN32 */
349 void
350 scripts_unload (void)
355 do_scripts (struct curl_handle *ch)
357 if (!header_script && !cookie_script)
358 return 0;
360 NOT_IMPLEMENTED_ON_WINDOWS ("header-script or cookie-script");
363 #endif /* WIN32 */