source: trunk/base/src/pextlib1.0/curl.c @ 17196

Last change on this file since 17196 was 17196, checked in by jberry, 15 years ago

Add new option to curl fetch command, "--effective-url", which enables
it to return the actual effective url used during a fetch.

The effectiveURL will differ from the requested url in the effect of redirects.

  • Property svn:eol-style set to native
File size: 16.1 KB
Line 
1/*
2 * curl.c
3 * $Id: curl.c,v 1.10 2006/03/27 21:27:02 jberry Exp $
4 *
5 * Copyright (c) 2005 Paul Guyot, Darwinports Team.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of Darwinports Team nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#if HAVE_CONFIG_H
34#include <config.h>
35#endif
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <ctype.h>
40#include <errno.h>
41
42#if HAVE_STRING_H
43#include <string.h>
44#endif
45
46#include <curl/curl.h>
47
48#include <tcl.h>
49#include <tclDecls.h>
50
51#include "curl.h"
52
53/* Avoid a warning with Tcl < 8.4, even if Tcl_GetIndexFromObj's tablePtr
54probably isn't modified. */
55#if (TCL_MAJOR_VERSION > 8) || (TCL_MINOR_VERSION >= 4)
56typedef CONST char* tableEntryString;
57#else
58typedef char* tableEntryString;
59#endif
60
61/*
62 * Some compiled-in constants that we may wish to change later, given more
63 * empirical data.  These represent "best guess" values for now.
64 */
65#define _CURL_CONNECTION_TIMEOUT        ((long)(5 * 60))        /* 5 minutes */
66#define _CURL_MINIMUM_XFER_SPEED        ((long)1024)            /* 1Kb/sec */
67#define _CURL_MINIMUM_XFER_TIMEOUT      ((long)(10 * 60))       /* 10 minutes */
68
69/* ========================================================================= **
70 * Definitions
71 * ========================================================================= */
72#pragma mark Definitions
73
74/* ------------------------------------------------------------------------- **
75 * Prototypes
76 * ------------------------------------------------------------------------- */
77int SetResultFromCurlErrorCode(Tcl_Interp* interp, CURLcode inErrorCode);
78int CurlFetchCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
79int CurlIsNewerCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
80
81/* ========================================================================= **
82 * Entry points
83 * ========================================================================= */
84#pragma mark -
85#pragma mark Entry points
86
87/**
88 * Set the result if a libcurl error occurred return TCL_ERROR.
89 * Otherwise, set the result to "" and return TCL_OK.
90 *
91 * @param interp                pointer to the interpreter.
92 * @param inErrorCode   code of the error.
93 * @return TCL_OK if inErrorCode is 0, TCL_ERROR otherwise.
94 */
95int
96SetResultFromCurlErrorCode(Tcl_Interp* interp, CURLcode inErrorCode)
97{
98        int theResult;
99
100        switch(inErrorCode)
101        {
102                case CURLE_OK:
103                        Tcl_SetResult(interp, "", TCL_STATIC);
104                        theResult = TCL_OK;
105                        break;
106               
107                default:
108#ifdef HAVE_CURL_EASY_STRERROR
109                        Tcl_SetResult(interp, (char*) curl_easy_strerror(inErrorCode), TCL_VOLATILE);
110#else
111                        {
112                                char theErrorString[512];
113                                (void) snprintf(theErrorString, sizeof(theErrorString),
114                                        "curl error %i", inErrorCode);
115                                Tcl_SetResult(interp, theErrorString, TCL_VOLATILE);                           
116                        }
117#endif
118                        theResult = TCL_ERROR;
119        }
120       
121        return theResult;
122}
123
124/**
125 * curl fetch subcommand entry point.
126 *
127 * syntax: curl fetch [-v] [--disable-epsv] [-u userpass] [--effective-url lasturlvar] url filename
128 *
129 * @param interp                current interpreter
130 * @param objc                  number of parameters
131 * @param objv                  parameters
132 */
133int
134CurlFetchCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[])
135{
136        int theResult = TCL_OK;
137        CURL* theHandle = NULL;
138        FILE* theFile = NULL;
139
140        do {
141                long theResponseCode = 0;
142                int noprogress = 1;
143                int useepsv = 1;
144                const char* theUserPassString = NULL;
145                const char* effectiveURLVarName = NULL;
146                char* effectiveURL = NULL;
147                int optioncrsr;
148                int lastoption;
149                const char* theURL;
150                const char* theFilePath;
151                CURLcode theCurlCode;
152               
153                /* we might have options and then the url and the file */
154                /* let's process the options first */
155               
156                optioncrsr = 2;
157                lastoption = objc - 3;
158                while (optioncrsr <= lastoption) {
159                        /* get the option */
160                        const char* theOption = Tcl_GetString(objv[optioncrsr]);
161                       
162                        if (strcmp(theOption, "-v") == 0) {
163                                noprogress = 0;
164                        } else if (strcmp(theOption, "--disable-epsv") == 0) {
165                                useepsv = 0;
166                        } else if (strcmp(theOption, "-u") == 0) {
167                                /* check we also have the parameter */
168                                if (optioncrsr < lastoption) {
169                                        optioncrsr++;
170                                        theUserPassString = Tcl_GetString(objv[optioncrsr]);
171                                } else {
172                                        Tcl_SetResult(interp,
173                                                "curl fetch: -u option requires a parameter",
174                                                TCL_STATIC);
175                                        theResult = TCL_ERROR;
176                                        break;                                 
177                                }
178                        } else if (strcmp(theOption, "--effective-url") == 0) {
179                                /* check we also have the parameter */
180                                if (optioncrsr < lastoption) {
181                                        optioncrsr++;
182                                        effectiveURLVarName = Tcl_GetString(objv[optioncrsr]);
183                                } else {
184                                        Tcl_SetResult(interp,
185                                                "curl fetch: --effective-url option requires a parameter",
186                                                TCL_STATIC);
187                                        theResult = TCL_ERROR;
188                                        break;                                 
189                                }
190                        } else {
191                                char theErrorString[512];
192                                (void) snprintf(theErrorString, sizeof(theErrorString),
193                                        "curl fetch: unknown option %s", theOption);
194                                Tcl_SetResult(interp, theErrorString, TCL_VOLATILE);
195                                theResult = TCL_ERROR;
196                                break;
197                        }
198                       
199                        optioncrsr++;
200                }
201               
202                if (optioncrsr <= lastoption) {
203                        /* something went wrong */
204                        break;
205                }
206
207                /*      first (second) parameter is -v or the url,
208                        second (third) parameter is the file */
209
210                if (objc >= 4) {
211                        /* Retrieve the url */
212                        theURL = Tcl_GetString(objv[objc - 2]);
213       
214                        /* Retrieve the file path */
215                        theFilePath = Tcl_GetString(objv[objc - 1]);
216                } else {
217                        Tcl_WrongNumArgs(interp, 1, objv, "fetch [options] url file");
218                        theResult = TCL_ERROR;
219                        break;
220                }
221               
222                /* Open the file */
223                theFile = fopen( theFilePath, "w" );
224                if (theFile == NULL) {
225                        Tcl_SetResult(interp, strerror(errno), TCL_VOLATILE);
226                        theResult = TCL_ERROR;
227                }
228
229                /* Create the CURL handle */
230                theHandle = curl_easy_init();
231               
232                /* Setup the handle */
233                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_URL, theURL);
234                if (theCurlCode != CURLE_OK) {
235                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
236                        break;
237                }
238               
239                /* -L option */
240                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FOLLOWLOCATION, 1);
241                if (theCurlCode != CURLE_OK) {
242                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
243                        break;
244                }
245
246                /* -f option */
247                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FAILONERROR, 1);
248                if (theCurlCode != CURLE_OK) {
249                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
250                        break;
251                }
252
253                /* write to the file */
254                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_WRITEDATA, theFile);
255                if (theCurlCode != CURLE_OK) {
256                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
257                        break;
258                }
259               
260                /* we want/don't want progress */
261                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, noprogress);
262                if (theCurlCode != CURLE_OK) {
263                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
264                        break;
265                }
266
267                /* we want/don't want to use epsv */
268                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FTP_USE_EPSV, useepsv);
269                if (theCurlCode != CURLE_OK) {
270                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
271                        break;
272                }
273
274                /* set the l/p, if any */
275                if (theUserPassString) {
276                        theCurlCode = curl_easy_setopt(theHandle, CURLOPT_USERPWD, theUserPassString);
277                        if (theCurlCode != CURLE_OK) {
278                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
279                                break;
280                        }
281                }
282
283                /* actually fetch the resource */
284                theCurlCode = curl_easy_perform(theHandle);
285                if (theCurlCode != CURLE_OK) {
286                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
287                        break;
288                }
289               
290                /* close the file */
291                (void) fclose( theFile );
292                theFile = NULL;
293               
294                /* If --effective-url option was given, set given variable name to last effective url used by curl */
295                if (effectiveURLVarName != NULL) {
296                        theCurlCode = curl_easy_getinfo(theHandle, CURLINFO_EFFECTIVE_URL, &effectiveURL);
297                        Tcl_SetVar(interp, effectiveURLVarName,
298                                (effectiveURL == NULL || theCurlCode != CURLE_OK) ? "" : effectiveURL,
299                                0);
300                }
301               
302                /* check everything went fine */
303                theCurlCode = curl_easy_getinfo(theHandle, CURLINFO_HTTP_CODE, &theResponseCode);
304                if (theCurlCode != CURLE_OK) {
305                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
306                        break;
307                }
308               
309                /* we need something between 200 (incl.) and 300 (excl.).*/
310                /* (actually, we sometimes get 0 from GNU FTP servers) */
311                if (((theResponseCode != 0)  && (theResponseCode < 200))
312                        || (theResponseCode >= 300)) {
313                        char theErrorString[512];
314                        (void) snprintf(theErrorString, sizeof(theErrorString),
315                                "Download failed (code = %li)", theResponseCode);
316                        Tcl_SetResult(interp, theErrorString, TCL_VOLATILE);
317                        theResult = TCL_ERROR;
318                        break;
319                }
320               
321                /* clean up */
322                curl_easy_cleanup( theHandle );
323                theHandle = NULL;
324    } while (0);
325   
326    if (theHandle != NULL) {
327        curl_easy_cleanup( theHandle );
328    }
329    if (theFile != NULL) {
330        fclose( theFile );
331    }
332   
333        return theResult;
334}
335
336/**
337 * curl isnewer subcommand entry point.
338 *
339 * @param interp                current interpreter
340 * @param objc                  number of parameters
341 * @param objv                  parameters
342 */
343int
344CurlIsNewerCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[])
345{
346        int theResult = TCL_OK;
347        CURL* theHandle = NULL;
348        FILE* theFile = NULL;
349
350        do {
351                long theResponseCode = 0;
352                const char* theURL;
353                CURLcode theCurlCode;
354                long theModDate;
355                long userModDate;
356                               
357                /*      first (second) parameter is the url,
358                        second (third) parameter is the date */
359                if (objc != 4) {
360                        Tcl_WrongNumArgs(interp, 1, objv, "isnewer url date");
361                        theResult = TCL_ERROR;
362                        break;
363                }
364
365                /* Retrieve the url */
366                theURL = Tcl_GetString(objv[2]);
367
368                /* Get the date */
369                theResult = Tcl_GetLongFromObj(interp, objv[3], &userModDate);
370                if (theResult != TCL_OK) {
371                        break;
372                }
373               
374                /* Open the file (dev/null) */
375                theFile = fopen( "/dev/null", "a" );
376                if (theFile == NULL) {
377                        Tcl_SetResult(interp, strerror(errno), TCL_VOLATILE);
378                        theResult = TCL_ERROR;
379                }
380
381                /* Create the CURL handle */
382                theHandle = curl_easy_init();
383               
384                /* Setup the handle */
385                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_URL, theURL);
386                if (theCurlCode != CURLE_OK) {
387                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
388                        break;
389                }
390               
391                /* -L option */
392                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FOLLOWLOCATION, 1);
393                if (theCurlCode != CURLE_OK) {
394                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
395                        break;
396                }
397
398                /* -f option */
399                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FAILONERROR, 1);
400                if (theCurlCode != CURLE_OK) {
401                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
402                        break;
403                }
404
405                /* set timeout on connections */
406                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_TIMEOUT, _CURL_CONNECTION_TIMEOUT);
407                if (theCurlCode != CURLE_OK) {
408                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
409                        break;
410                }
411
412                /* set minimum connection speed */
413                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_LOW_SPEED_LIMIT, _CURL_MINIMUM_XFER_SPEED);
414                if (theCurlCode != CURLE_OK) {
415                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
416                        break;
417                }
418
419                /* set timeout interval for connections < min xfer speed */
420                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_LOW_SPEED_TIME, _CURL_MINIMUM_XFER_TIMEOUT);
421                if (theCurlCode != CURLE_OK) {
422                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
423                        break;
424                }
425
426                /* write to the file */
427                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_WRITEDATA, theFile);
428                if (theCurlCode != CURLE_OK) {
429                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
430                        break;
431                }
432               
433                /* save the modification date */
434                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FILETIME, 1);
435                if (theCurlCode != CURLE_OK) {
436                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
437                        break;
438                }
439
440                /* skip the download if the file wasn't modified */
441                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
442                if (theCurlCode != CURLE_OK) {
443                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
444                        break;
445                }
446                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_TIMEVALUE, userModDate);
447                if (theCurlCode != CURLE_OK) {
448                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
449                        break;
450                }
451
452                /* we do not want any progress */
453                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, 1);
454                if (theCurlCode != CURLE_OK) {
455                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
456                        break;
457                }
458               
459                /* actually fetch the resource */
460                theCurlCode = curl_easy_perform(theHandle);
461                if (theCurlCode != CURLE_OK) {
462                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
463                        break;
464                }
465               
466                /* close the file */
467                (void) fclose( theFile );
468                theFile = NULL;
469               
470                /* check everything went fine */
471                theCurlCode = curl_easy_getinfo(theHandle, CURLINFO_HTTP_CODE, &theResponseCode);
472                if (theCurlCode != CURLE_OK) {
473                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
474                        break;
475                }
476
477                theModDate = -1;
478
479                if (theResponseCode != 304) {
480                        /* get the modification date */
481                        theCurlCode = curl_easy_getinfo(theHandle, CURLINFO_FILETIME, &theModDate);
482                        if (theCurlCode != CURLE_OK) {
483                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
484                                break;
485                        }
486       
487                        /* clean up */
488                        curl_easy_cleanup( theHandle );
489                        theHandle = NULL;
490       
491                        /* compare this with the date provided by user */
492                        if (theModDate < -1) {
493                                Tcl_SetResult(interp, "Couldn't get resource modification date", TCL_STATIC);
494                                theResult = TCL_ERROR;
495                                break;
496                        }
497                }
498
499                if (theModDate > userModDate) {
500                        Tcl_SetResult(interp, "1", TCL_STATIC);
501                } else {
502                        Tcl_SetResult(interp, "0", TCL_STATIC);
503                }               
504    } while (0);
505   
506    if (theHandle != NULL) {
507        curl_easy_cleanup( theHandle );
508    }
509    if (theFile != NULL) {
510        fclose( theFile );
511    }
512   
513        return theResult;
514}
515
516/**
517 * curl command entry point.
518 *
519 * @param clientData    custom data (ignored)
520 * @param interp                current interpreter
521 * @param objc                  number of parameters
522 * @param objv                  parameters
523 */
524int
525CurlCmd(
526                ClientData clientData UNUSED,
527                Tcl_Interp* interp,
528                int objc, 
529                Tcl_Obj* CONST objv[])
530{
531    typedef enum {
532        kCurlFetch,
533        kCurlIsNewer
534    } EOption;
535   
536        static tableEntryString options[] = {
537                "fetch", "isnewer", NULL
538        };
539        int theResult = TCL_OK;
540    EOption theOptionIndex;
541
542        if (objc < 3) {
543                Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
544                return TCL_ERROR;
545        }
546
547        theResult = Tcl_GetIndexFromObj(
548                                interp,
549                                objv[1],
550                                options,
551                                "option",
552                                0,
553                                (int*) &theOptionIndex);
554        if (theResult == TCL_OK) {
555                switch (theOptionIndex)
556                {
557                        case kCurlFetch:
558                                theResult = CurlFetchCmd(interp, objc, objv);
559                                break;
560
561                        case kCurlIsNewer:
562                                theResult = CurlIsNewerCmd(interp, objc, objv);
563                                break;
564                }
565        }
566       
567        return theResult;
568}
569
570/**
571 * curl init entry point.
572 *
573 * @param interp                current interpreter
574 */
575int
576CurlInit(Tcl_Interp* interp)
577{
578        CURLcode theCurlCode = curl_global_init(CURL_GLOBAL_ALL);
579        return SetResultFromCurlErrorCode(interp, theCurlCode);
580}
581
582/* ============================================================== **
583** As of next Thursday, UNIX will be flushed in favor of TOPS-10. **
584** Please update your programs.                                   **
585** ============================================================== */
Note: See TracBrowser for help on using the repository browser.