Documentation Source Text

Check-in [130feb0aa0]
Login

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

Overview
Comment:Add support for Last-Modified and If-Modified-Since in althttpd.c.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 130feb0aa035f7cb438d929ba0b267670028767af4d3a350e1739eb2c5c0f8b1
User & Date: drh 2018-02-25 01:29:01.208
Context
2018-02-25
17:25
Change the action codes in the althttpd.c log to be consistent numbers, rather than source code line numbers. Include text at the end of a file that will generate a cross-reference table in SQLite. (check-in: 8aafa56bb9 user: drh tags: trunk)
01:29
Add support for Last-Modified and If-Modified-Since in althttpd.c. (check-in: 130feb0aa0 user: drh tags: trunk)
2018-02-23
21:41
Add support for the "Test Checklist" named "0demo" in the checklist.tcl application. (check-in: 53dab205ad user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to misc/althttpd.c.
65
66
67
68
69
70
71


72
73
74
75
76
77
78
**                   $HOST.website subdirectories, each containing web content 
**                   for a single virtual host.  If launched as root and if
**                   "--user USER" also appears on the command-line and if
**                   "--jail 0" is omitted, then the process runs in a chroot
**                   jail rooted at this directory and under the userid USER.
**                   This option is required for xinetd launch but defaults
**                   to "." for a stand-alone web server.


**
**  --user USER      Define the user under which the process should run if
**                   originally launched as root.  This process will refuse to
**                   run as root (for security).  If this option is omitted and
**                   the process is launched as root, it will abort without
**                   processing any HTTP requests.
**







>
>







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
**                   $HOST.website subdirectories, each containing web content 
**                   for a single virtual host.  If launched as root and if
**                   "--user USER" also appears on the command-line and if
**                   "--jail 0" is omitted, then the process runs in a chroot
**                   jail rooted at this directory and under the userid USER.
**                   This option is required for xinetd launch but defaults
**                   to "." for a stand-alone web server.
**
**  --port N         Run in standalone mode listening on TCP port N
**
**  --user USER      Define the user under which the process should run if
**                   originally launched as root.  This process will refuse to
**                   run as root (for security).  If this option is omitted and
**                   the process is launched as root, it will abort without
**                   processing any HTTP requests.
**
237
238
239
240
241
242
243

244
245
246
247
248
249
250
static char *zContentLength = 0; /* Content length reported in the header */
static char *zContentType = 0;   /* Content type reported in the header */
static char *zQuerySuffix = 0;   /* The part of the URL after the first ? */
static char *zAuthType = 0;      /* Authorization type (basic or digest) */
static char *zAuthArg = 0;       /* Authorization values */
static char *zRemoteUser = 0;    /* REMOTE_USER set by authorization module */
static char *zIfNoneMatch= 0;    /* The If-None-Match header value */

static int nIn = 0;              /* Number of bytes of input */
static int nOut = 0;             /* Number of bytes of output */
static char zReplyStatus[4];     /* Reply status code */
static int statusSent = 0;       /* True after status line is sent */
static char *zLogFile = 0;       /* Log to this file */
static int debugFlag = 0;        /* True if being debugged */
static struct timeval beginTime; /* Time when this process starts */







>







239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
static char *zContentLength = 0; /* Content length reported in the header */
static char *zContentType = 0;   /* Content type reported in the header */
static char *zQuerySuffix = 0;   /* The part of the URL after the first ? */
static char *zAuthType = 0;      /* Authorization type (basic or digest) */
static char *zAuthArg = 0;       /* Authorization values */
static char *zRemoteUser = 0;    /* REMOTE_USER set by authorization module */
static char *zIfNoneMatch= 0;    /* The If-None-Match header value */
static char *zIfModifiedSince=0; /* The If-Modified-Since header value */
static int nIn = 0;              /* Number of bytes of input */
static int nOut = 0;             /* Number of bytes of output */
static char zReplyStatus[4];     /* Reply status code */
static int statusSent = 0;       /* True after status line is sent */
static char *zLogFile = 0;       /* Log to this file */
static int debugFlag = 0;        /* True if being debugged */
static struct timeval beginTime; /* Time when this process starts */
483
484
485
486
487
488
489
490











491
492
493
494
495


496







497


















498











499



500





501
502
503
504
505
506
507
** Break a line at the first \n or \r character seen.
*/
static void RemoveNewline(char *z){
  if( z==0 ) return;
  while( *z && *z!='\n' && *z!='\r' ){ z++; }
  *z = 0;
}












/*
** Print a date tag in the header.  The name of the tag is zTag.
** The date is determined from the unix timestamp given.
*/
static int DateTag(const char *zTag, time_t t){


  struct tm *tm;







  char zDate[100];


















  tm = gmtime(&t);











  strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S %z", tm);



  return printf("%s: %s\r\n", zTag, zDate);





}

/*
** Print the first line of a response followed by the server type.
*/
static void StartResponse(const char *zResultCode){
  time_t now;








>
>
>
>
>
>
>
>
>
>
>





>
>
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
>
>
>
>
>







486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
** Break a line at the first \n or \r character seen.
*/
static void RemoveNewline(char *z){
  if( z==0 ) return;
  while( *z && *z!='\n' && *z!='\r' ){ z++; }
  *z = 0;
}

/* Render seconds since 1970 as an RFC822 date string.  Return
** a pointer to that string in a static buffer.
*/
static char *Rfc822Date(time_t t){
  struct tm *tm;
  static char zDate[100];
  tm = gmtime(&t);
  strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S %z", tm);
  return zDate;
}

/*
** Print a date tag in the header.  The name of the tag is zTag.
** The date is determined from the unix timestamp given.
*/
static int DateTag(const char *zTag, time_t t){
  return printf("%s: %s\r\n", zTag, Rfc822Date(t));
}

/*
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
** a Unix epoch time. <= zero is returned on failure.
*/
time_t ParseRfc822Date(const char *zDate){
  int mday, mon, year, yday, hour, min, sec;
  char zIgnore[4];
  char zMonth[4];
  static const char *const azMonths[] =
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
  if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
                       &mday, zMonth, &year, &hour, &min, &sec)){
    if( year > 1900 ) year -= 1900;
    for(mon=0; azMonths[mon]; mon++){
      if( !strncmp( azMonths[mon], zMonth, 3 )){
        int nDay;
        int isLeapYr;
        static int priorDays[] =
         {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
        if( mon<0 ){
          int nYear = (11 - mon)/12;
          year -= nYear;
          mon += nYear*12;
        }else if( mon>11 ){
          year += mon/12;
          mon %= 12;
        }
        isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
        yday = priorDays[mon] + mday - 1;
        if( isLeapYr && mon>1 ) yday++;
        nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
        return ((nDay*24 + hour)*60 + min)*60 + sec;
      }
    }
  }
  return 0;
}

/*
** Test procedure for ParseRfc822Date
*/
void TestParseRfc822Date(void){
  time_t t1, t2;
  for(t1=0; t1<0x7fffffff; t1 += 127){
    t2 = ParseRfc822Date(Rfc822Date(t1));
    assert( t1==t2 );
  }
}

/*
** Print the first line of a response followed by the server type.
*/
static void StartResponse(const char *zResultCode){
  time_t now;
1183
1184
1185
1186
1187
1188
1189

1190
1191
1192
1193
1194
1195
1196
  /* Get all the optional fields that follow the first line.
  */
  zCookie = 0;
  zAuthType = 0;
  zRemoteUser = 0;
  zReferer = 0;
  zIfNoneMatch = 0;

  while( fgets(zLine,sizeof(zLine),stdin) ){
    char *zFieldName;
    char *zVal;

#ifdef LOG_HEADER
    if( hdrLog ) fprintf(hdrLog, "%s", zLine);
#endif







>







1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
  /* Get all the optional fields that follow the first line.
  */
  zCookie = 0;
  zAuthType = 0;
  zRemoteUser = 0;
  zReferer = 0;
  zIfNoneMatch = 0;
  zIfModifiedSince = 0;
  while( fgets(zLine,sizeof(zLine),stdin) ){
    char *zFieldName;
    char *zVal;

#ifdef LOG_HEADER
    if( hdrLog ) fprintf(hdrLog, "%s", zLine);
#endif
1242
1243
1244
1245
1246
1247
1248


1249
1250
1251
1252
1253
1254
1255
      if( zRealPort ){
        zServerPort = StrDup(zRealPort);
      }
    }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
      zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
    }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
      zIfNoneMatch = StrDup(zVal);


    }
  }
#ifdef LOG_HEADER
  if( hdrLog ) fclose(hdrLog);
#endif

  /* Disallow requests from certain clients */







>
>







1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
      if( zRealPort ){
        zServerPort = StrDup(zRealPort);
      }
    }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
      zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
    }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
      zIfNoneMatch = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"If-Modified-Since:")==0 ){
      zIfModifiedSince = StrDup(zVal);
    }
  }
#ifdef LOG_HEADER
  if( hdrLog ) fclose(hdrLog);
#endif

  /* Disallow requests from certain clients */
1522
1523
1524
1525
1526
1527
1528

1529
1530
1531
1532
1533
1534
1535
      { "CONTENT_TYPE",                &zContentType },
      { "DOCUMENT_ROOT",               &zHome },
      { "GATEWAY_INTERFACE",           &gateway_interface },
      { "HTTP_ACCEPT",                 &zAccept },
      { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
      { "HTTP_COOKIE",                 &zCookie },
      { "HTTP_HOST",                   &zHttpHost },

      { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
      { "HTTP_REFERER",                &zReferer },
      { "HTTP_USER_AGENT",             &zAgent },
      { "PATH",                        &default_path },
      { "PATH_INFO",                   &zPathInfo },
      { "QUERY_STRING",                &zQueryString },
      { "REMOTE_ADDR",                 &zRemoteAddr },







>







1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
      { "CONTENT_TYPE",                &zContentType },
      { "DOCUMENT_ROOT",               &zHome },
      { "GATEWAY_INTERFACE",           &gateway_interface },
      { "HTTP_ACCEPT",                 &zAccept },
      { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
      { "HTTP_COOKIE",                 &zCookie },
      { "HTTP_HOST",                   &zHttpHost },
      { "HTTP_IF_MODIFIED_SINCE",      &zIfModifiedSince },
      { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
      { "HTTP_REFERER",                &zReferer },
      { "HTTP_USER_AGENT",             &zAgent },
      { "PATH",                        &default_path },
      { "PATH_INFO",                   &zPathInfo },
      { "QUERY_STRING",                &zQueryString },
      { "REMOTE_ADDR",                 &zRemoteAddr },
1695
1696
1697
1698
1699
1700
1701

1702
1703
1704
1705
1706




1707

1708
1709
1710
1711
1712
1713
1714
    ** actual content file name, report that as a 404 error. */
    NotFound(__LINE__); /* LOG: Excess URI content past static file name */
  }else{
    /* If it isn't executable then it
    ** must a simple file that needs to be copied to output.
    */
    const char *zContentType = GetMimeType(zFile, lenFile);

    char zETag[100];

    if( zTmpNam ) unlink(zTmpNam);
    sprintf(zETag, "m%xs%x", (int)statbuf.st_mtime, (int)statbuf.st_size);
    if( CompareEtags(zIfNoneMatch,zETag)==0 ){




      StartResponse("304 Not Modified");

      nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
      nOut += printf("ETag: \"%s\"\r\n", zETag);
      nOut += printf("\r\n");
      fflush(stdout);
      MakeLogEntry(0, __LINE__);  /* LOG: ETag Cache Hit */
      return;
    }







>




|
>
>
>
>

>







1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
    ** actual content file name, report that as a 404 error. */
    NotFound(__LINE__); /* LOG: Excess URI content past static file name */
  }else{
    /* If it isn't executable then it
    ** must a simple file that needs to be copied to output.
    */
    const char *zContentType = GetMimeType(zFile, lenFile);
    time_t t;
    char zETag[100];

    if( zTmpNam ) unlink(zTmpNam);
    sprintf(zETag, "m%xs%x", (int)statbuf.st_mtime, (int)statbuf.st_size);
    if( CompareEtags(zIfNoneMatch,zETag)==0
     || (zIfModifiedSince!=0
          && (t = ParseRfc822Date(zIfModifiedSince))>0
          && t>=statbuf.st_mtime)
    ){
      StartResponse("304 Not Modified");
      nOut += DateTag("Last-Modified", statbuf.st_mtime);
      nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
      nOut += printf("ETag: \"%s\"\r\n", zETag);
      nOut += printf("\r\n");
      fflush(stdout);
      MakeLogEntry(0, __LINE__);  /* LOG: ETag Cache Hit */
      return;
    }
1935
1936
1937
1938
1939
1940
1941




1942
1943
1944
1945
1946
1947
1948
      if( atoi(zArg)==0 ){
        useChrootJail = 0;
      }
    }else if( strcmp(z, "-debug")==0 ){
      if( atoi(zArg) ){
        useTimeout = 0;
      }




    }else{
      Malfunction(__LINE__, /* LOG: unknown command-line argument on launch */
                  "unknown argument: [%s]\n", z);
    }
    argv += 2;
    argc -= 2;
  }







>
>
>
>







2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
      if( atoi(zArg)==0 ){
        useChrootJail = 0;
      }
    }else if( strcmp(z, "-debug")==0 ){
      if( atoi(zArg) ){
        useTimeout = 0;
      }
    }else if( strcmp(z, "-datetest")==0 ){
      TestParseRfc822Date();
      printf("Ok\n");
      exit(0);
    }else{
      Malfunction(__LINE__, /* LOG: unknown command-line argument on launch */
                  "unknown argument: [%s]\n", z);
    }
    argv += 2;
    argc -= 2;
  }