4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
33 /* Header and cookie scripts. */
46 #include <curl/curl.h>
48 #include <nbdkit-plugin.h>
50 #include "ascii-ctype.h"
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
;
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.
93 do_scripts (struct curl_handle
*ch
)
98 /* Return quickly without acquiring the lock if this feature is not
101 if (!header_script
&& !cookie_script
)
104 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
106 /* Run or re-run header-script if we need to. */
109 if (!header_script_has_run
||
110 (header_script_renew
> 0 && now
- header_last
>= header_script_renew
)) {
111 if (run_header_script (ch
) == -1)
114 header_script_has_run
= true;
118 /* Run or re-run cookie-script if we need to. */
121 if (!cookie_script_has_run
||
122 (cookie_script_renew
> 0 && now
- cookie_last
>= cookie_script_renew
)) {
123 if (run_cookie_script (ch
) == -1)
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");
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
);
162 /* This is called with the lock held when we must run or re-run the
166 run_header_script (struct curl_handle
*ch
)
169 char tmpfile
[] = "/tmp/errorsXXXXXX";
171 CLEANUP_FREE
char *cmd
= NULL
, *line
= NULL
;
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
184 fd
= mkstemp (tmpfile
);
186 nbdkit_error ("mkstemp");
191 /* Generate the full script with the local $url variable. */
192 fp
= open_memstream (&cmd
, &len
);
194 nbdkit_error ("open_memstream: %m");
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
);
202 fprintf (fp
, "iteration=%u\n", header_iteration
++);
204 fprintf (fp
, "%s", header_script
); /* The script or command. */
205 if (fclose (fp
) == EOF
) {
206 nbdkit_error ("memstream failed");
210 /* Run the script and read the headers. */
211 nbdkit_debug ("curl: running header-script");
212 fp
= popen (cmd
, "r");
214 nbdkit_error ("popen: %m");
217 while ((n
= getline (&line
, &linelen
, fp
)) != -1) {
218 /* Remove trailing \n and whitespace. */
219 while (n
> 0 && ascii_isspace (line
[n
-1]))
224 headers_from_script
= curl_slist_append (headers_from_script
, line
);
225 if (headers_from_script
== NULL
) {
226 nbdkit_error ("curl_slist_append: %m");
233 if (pclose (fp
) != 0) {
234 error_from_tmpfile ("header-script", tmpfile
);
238 nbdkit_debug ("header-script returned %zu header(s)", nr_headers
);
242 /* This is called with the lock held when we must run or re-run the
246 run_cookie_script (struct curl_handle
*ch
)
249 char tmpfile
[] = "/tmp/errorsXXXXXX";
251 CLEANUP_FREE
char *cmd
= NULL
, *line
= NULL
;
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
264 fd
= mkstemp (tmpfile
);
266 nbdkit_error ("mkstemp");
271 /* Generate the full script with the local $url variable. */
272 fp
= open_memstream (&cmd
, &len
);
274 nbdkit_error ("open_memstream: %m");
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
);
282 fprintf (fp
, "iteration=%u\n", cookie_iteration
++);
284 fprintf (fp
, "%s", cookie_script
); /* The script or command. */
285 if (fclose (fp
) == EOF
) {
286 nbdkit_error ("memstream failed");
290 /* Run the script and read the cookies. */
291 nbdkit_debug ("curl: running cookie-script");
292 fp
= popen (cmd
, "r");
294 nbdkit_error ("popen: %m");
297 n
= getline (&line
, &linelen
, fp
);
299 /* Remove trailing \n and whitespace. */
300 while (n
> 0 && ascii_isspace (line
[n
-1]))
303 cookies_from_script
= strdup (line
);
304 if (cookies_from_script
== NULL
) {
305 nbdkit_error ("strdup");
312 if (pclose (fp
) != 0) {
313 error_from_tmpfile ("cookie-script", tmpfile
);
317 nbdkit_debug ("cookie-script returned %scookies",
318 cookies_from_script
? "" : "no ");
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
327 error_from_tmpfile (const char *what
, const char *tmpfile
)
330 CLEANUP_FREE
char *line
= NULL
;
334 fp
= fopen (tmpfile
, "r");
336 if (fp
&& (n
= getline (&line
, &linelen
, fp
)) >= 0) {
337 if (n
> 0 && line
[n
-1] == '\n')
339 nbdkit_error ("%s failed: %s", what
, line
);
342 nbdkit_error ("%s failed", what
);
350 scripts_unload (void)
355 do_scripts (struct curl_handle
*ch
)
357 if (!header_script
&& !cookie_script
)
360 NOT_IMPLEMENTED_ON_WINDOWS ("header-script or cookie-script");