c524c55a797618abe0a485574cfcc13445c22e8b
jcasper
  Thu Jul 25 11:52:12 2024 -0700
Adding support for setting FAILONERROR to fakeCurl, refs #33225

diff --git src/hg/lib/fakeCurl.c src/hg/lib/fakeCurl.c
index 360e453..0c5868d 100644
--- src/hg/lib/fakeCurl.c
+++ src/hg/lib/fakeCurl.c
@@ -12,30 +12,31 @@
 
 #include "common.h"
 #include "udc.h"
 #include "fakeCurl.h"
 
 CURL *curl_easy_init(void)
 /* Create a new fakeCurl object.  Dispose of this with curl_easy_cleanup().
  */
 {
     CURL *new = (CURL*) malloc (sizeof(CURL));
     new->url = NULL;
     new->range = NULL;
     new->writeBuffer = NULL;
     new->WriteFunction = NULL;
     new->HeaderFunction = NULL;
+    new->failonerror = 0;
     return new;
 }
 
 void curl_easy_cleanup(CURL *curl)
 /* Free a curl object allocated with curl_easy_init().  Do not attempt
  * to use the pointer's value after calling this function on it.
  */
 {
     if (curl != NULL)
     {
         // clear the local copies of URL strings
         if (curl->url)
             free(curl->url);
         if (curl->range)
             free(curl->range);
@@ -74,30 +75,33 @@
             break;
         case CURLOPT_URL:
             if (curl->url)
                 free(curl->url);
             curl->url = cloneString(va_arg(args,char *));
             break;
         case CURLOPT_FOLLOWLOCATION:
             // ignored
             break;
         case CURLOPT_USERAGENT:
             // ignored
             break;
         case CURLOPT_HEADERFUNCTION:
             curl->HeaderFunction = va_arg(args, curl_write_callback);
             break;
+        case CURLOPT_FAILONERROR:
+            curl->failonerror = 1;
+            break;
         default:
             errAbort("Unexpected curl option supplied to fakeCurl"); 
     }
     va_end(args); 
     return CURLE_OK;
 }
 
 CURLcode curl_easy_perform(CURL *curl)
 /* Perform a fake curl operation via UDC, using the settings establised in the CURL object
  * via calls to curl_easy_setopt().  The return value will be either CURLE_OK (for success)
  * or CURLE_NOTOK (for failure).
  *
  * As noted in curl_easy_setopt(), the content provided to any supplied header function is
  * a faked subset of actual header content - just a "Content-Range" string.
  */
@@ -107,36 +111,57 @@
     if (udc == NULL)
         return CURLE_NOTOK;
     long fileSize = (long) udcFileSize(curl->url);
 
     // Set up the seek offset if there's a range supplied
     long start = 0;
     long end = fileSize;
     if (curl->range != NULL)
     {
         start = atol(curl->range);
         char *end_pos = strrchr(curl->range, '-');
         if (end_pos != NULL && *(end_pos+1) != 0)
             end = atol(end_pos+1);
     }
 
-    // If there's a header function, fake up a Content-Range string for it to parse using the range
-    // and file size.
+    if (end >= fileSize)
+        end = fileSize-1;
+
+    char headerbuf[4096];
+
+    if (start < 0 || start >= fileSize)
+    {
+        // We need to gin up a 416 error response here and abort if FAILONERROR is set.
+        // Otherwise our next step is udcSeek, which will just attempt an lseek
+        // and then errAbort when the requested range isn't within the file bounds.
+        safef(headerbuf, sizeof(headerbuf),
+                "HTTP/1.1 416 Requested Range Not Satisfiable\nContent-Range: bytes */%ld", fileSize);
+        if (curl->HeaderFunction != NULL)
+        {
+            char buf[4096];
+            curl->HeaderFunction(buf, strlen(buf), 1, NULL);
+        }
+        if (curl->failonerror)
+            return CURLE_NOTOK;
+        return CURLE_OK;
+    }
+
+    // Fake up a Content-Range string in a header in case there's a header function to call.
+    safef(headerbuf, sizeof(headerbuf), "Content-Range: bytes %ld-%ld/%ld", start, end, fileSize);
     if (curl->HeaderFunction != NULL)
     {
         char buf[4096];
-        safef(buf, sizeof(buf), "Content-Range: bytes %ld-%ld/%ld", start, end, fileSize);
         curl->HeaderFunction(buf, strlen(buf), 1, NULL);
     }
 
     char *readBuffer = (char*) malloc(end-start);
     udcSeek(udc, start);
     long bytesRead = udcRead(udc, readBuffer, end-start);
     // Technically we should pay attention to the value returned by udcRead, as it might indicate
     // that fewer bytes than requested were actually read.  Worry about that a bit later.
 
     // If writefunction is defined, then call that on the buffer of data we got from udc.
     // Otherwise, put it into the supplied writebuffer (which must exist).
     if (curl->WriteFunction != NULL)
     {
         curl->WriteFunction(readBuffer, bytesRead, 1, curl->writeBuffer);
     }