Fossil

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From 052390edaf04a5f5 To 9b686daeeda7b4ca

2026-07-25
13:00
When fossil is behaving as an HTTP client, it always uses HTTP/1.1 and it is able to understand the "chunked" transfer-encoding. Also add support for URI values of the form "file://NAME.http-test" which read the HTTP reply text out of the file "NAME.http-test" in the local filesystem, for testing purposes. check-in: c3bd7c3a0e user: drh tags: trunk
12:57
Enhance the transport layer such that on an HTTP request to file:// where the pathname ends with ".http-test" and a file with that pathname exists in the local filesystem, then the content of that file becomes the HTTP reply. Used for testing. Closed-Leaf check-in: b708acd2b8 user: drh tags: http1-1-chunked
11:11
Fix an off-by-one error in the previous check-in check-in: 9b686daeed user: drh tags: http1-1-chunked
11:04
Fix an integer overflow vulnerability. Shorten all source code lines to be no more than 80 characters. Do not select the "chunked" transfer encoding, if the last token of the transfer-encoding is some keyword that has "chunked" as a suffix, ex: "superchunked". check-in: 82723bf87c user: drh tags: http1-1-chunked
10:45
Merge improved Blob overflow defensive code from trunk. check-in: 109a5e1a04 user: drh tags: http1-1-chunked
10:25
Detect integer overflow in the blob_is_int() and blob_is_int64() routines. check-in: 052390edaf user: drh tags: trunk
10:04
Harden the Blob object against integer overflow attacks. check-in: 4816e03320 user: drh tags: trunk

Changes to src/http.c.
149
150
151
152
153
154
155
156

157
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
149
150
151
152
153
154
155

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175







-
+











+







  if( g.url.subpath ){
    zPath = g.url.subpath;
  }else if( g.url.path==0 || g.url.path[0]==0 ){
    zPath = "/";
  }else{
    zPath = g.url.path;
  }
  blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
  blob_appendf(pHdr, "%s %s HTTP/1.1\r\n",
               nPayload>0 ? "POST" : "GET", zPath);
  if( g.url.proxyAuth ){
    blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
  }
  if( g.zHttpAuth && g.zHttpAuth[0] ){
    const char *zCredentials = g.zHttpAuth;
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "Connection: close\r\n");
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( g.syncInfo.fLoginCardMode>0
      && nPayload>0 && pLogin && blob_size(pLogin) ){
    /* Add sync login card via a transient cookie. We can only do this
       if we know the remote supports it. */
    blob_appendf(pHdr, "Cookie: x-f-l-c=%T\r\n", blob_str(pLogin));
453
454
455
456
457
458
459

460
461
462
463
464
465
466
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468







+







  int iLength;          /* Expected length of the reply payload */
  int rc = 0;           /* Result code */
  int iHttpVersion;     /* Which version of HTTP protocol server uses */
  char *zLine;          /* A single line of the reply header */
  int i;                /* Loop counter */
  int isError = 0;      /* True if the reply is an error message */
  int isCompressed = 1; /* True if the reply is compressed */
  int isChunked = 0;    /* True if Transfer-Encoding: chunked */

  if( g.zHttpCmd!=0 ){
    /* Handle the --transport-command option for "fossil sync" and similar */
    return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
  }

  /* Activate the PATH= auxiliary argument to the ssh command if that
603
604
605
606
607
608
609







610
611
612
613
614
615
616
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625







+
+
+
+
+
+
+







        goto write_err;
      }
      if( iHttpVersion<0 ) iHttpVersion = 1;
      closeConnection = 0;
    }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
      for(i=15; fossil_isspace(zLine[i]); i++){}
      iLength = atoi(&zLine[i]);
    }else if( fossil_strnicmp(zLine, "transfer-encoding:", 18)==0 ){
      /* RFC 7230: "chunked" must be the final transfer-coding so only
      ** match when it appears at the end of the line. */
      if( sqlite3_strlike("%chunked", &zLine[18], 0)==0 ){
        size_t nx = strlen(&zLine[18]);
        if( !fossil_isalnum(zLine[nx+10]) ) isChunked = 1;
      }
    }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
      if( sqlite3_strlike("%close%", &zLine[11], 0)==0 ){
        closeConnection = 1;
      }else if( sqlite3_strlike("%keep-alive%", &zLine[11], 0)==0 ){
        closeConnection = 0;
      }
    }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) &&
733
734
735
736
737
738
739






























































740

741
742
743
744
745
746
747
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810

811
812
813
814
815
816
817
818







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+







    goto write_err;
  }

  /*
  ** Extract the reply payload that follows the header
  */
  blob_zero(pReply);
  if( isChunked ){
    /* Decode an HTTP/1.1 "Transfer-Encoding: chunked" reply body.  Each
    ** chunk is a hex length on its own line (optionally followed by a
    ** ";extension" that is ignored), then that many payload bytes, then a
    ** bare CRLF.  A zero-length chunk terminates the body, after which any
    ** trailer header lines are read and discarded up to the blank line. */
    char *zChunk;
    int sawTerminator = 0;  /* True once the 0-length chunk is seen */
    while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){
      i64 nChunk;           /* Size of this chunk in bytes (wide, unclamped) */
      i64 nPrior;           /* Bytes already in pReply (matches blob nUsed) */
      char *zEnd = 0;       /* End of the hex digits actually parsed */
      while( fossil_isspace(zChunk[0]) ) zChunk++;
      nChunk = strtoll(zChunk, &zEnd, 16);
      if( zEnd==zChunk ){
        /* No hex digit consumed: a blank or malformed chunk-size line, which
        ** is the symptom of a connection that closed mid-stream.  Treat it as
        ** a truncated (failed) */
        fossil_warning("chunked reply: missing or malformed chunk size");
        goto write_err;
      }
      if( nChunk<0 || nChunk>0x7fffffff ){
        /* Negative, or larger than we will ever accept in one chunk. */
        fossil_warning("chunked reply: invalid chunk size");
        goto write_err;
      }
      if( nChunk==0 ){
        /* Final chunk: consume trailer lines up to the terminating blank. */
        sawTerminator = 1;
        while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){}
        break;
      }
      nPrior = blob_size(pReply);
      /* Reserve space without advancing nUsed, so that on error
      ** the blob's reported size equals the bytes actually
      ** received rather the claimed chunk length */
      blob_reserve(pReply, (u64)(nPrior+nChunk));
      {
        unsigned int nRemaining = (unsigned int)nChunk;
        /* transport_receive() may return short; loop until the chunk is
        ** full. */
        while( nRemaining>0 ){
          int nGot;
          nGot = transport_receive(&g.url, &pReply->aData[nPrior], nRemaining);
          if( nGot<=0 ){
            fossil_warning("chunked reply truncated");
            goto write_err;
          }
          nPrior += nGot;
          nRemaining -= nGot;
          pReply->nUsed = (unsigned int)nPrior;
        }
      }
      transport_receive_line(&g.url); /* CRLF that follows the chunk data */
    }
    if( !sawTerminator ){
      /* The loop exited without ever seeing the 0-length terminator chunk,
      ** meaning the peer closed before the body was complete.  A truncated
      ** sync must be an error, never silent success. */
      fossil_warning("chunked reply ended without terminator");
      goto write_err;
    }
  if( iLength==0 ){
  }else if( iLength==0 ){
    /* No content to read */
  }else if( iLength>0 ){
    /* Read content of a known length */
    int iRecvLen;         /* Received length of the reply payload */
    blob_resize(pReply, iLength);
    iRecvLen = transport_receive(&g.url, blob_buffer(pReply), iLength);
    if( mHttpFlags & HTTP_VERBOSE ){