Browse Source

[multiple] etag.[ch] -> http_etag.[ch]; better imp

more efficient implementation of HTTP ETag generation and comparison

modify dekhash() to take hash value to allow for incremental hashing
master
Glenn Strauss 10 months ago
parent
commit
b700a8ca09
  1. 2
      src/CMakeLists.txt
  2. 4
      src/Makefile.am
  3. 2
      src/SConscript
  4. 11
      src/algo_md.h
  5. 2
      src/configfile.c
  6. 183
      src/etag.c
  7. 20
      src/etag.h
  8. 7
      src/http-header-glue.c
  9. 86
      src/http_etag.c
  10. 27
      src/http_etag.h
  11. 2
      src/meson.build
  12. 2
      src/mod_deflate.c
  13. 6
      src/mod_magnet.c
  14. 6
      src/mod_ssi.c
  15. 20
      src/mod_webdav.c
  16. 4
      src/stat_cache.c

2
src/CMakeLists.txt

@ -734,7 +734,7 @@ set(COMMON_SRC
base64.c buffer.c burl.c log.c
http_header.c http_kv.c keyvalue.c chunk.c
http_chunk.c stream.c fdevent.c gw_backend.c
stat_cache.c plugin.c etag.c array.c
stat_cache.c plugin.c http_etag.c array.c
data_string.c data_array.c
data_integer.c
algo_md5.c algo_sha1.c algo_splaytree.c

4
src/Makefile.am

@ -72,7 +72,7 @@ CLEANFILES = versionstamp.h versionstamp.h.tmp lemon$(BUILD_EXEEXT)
common_src=base64.c buffer.c burl.c log.c \
http_header.c http_kv.c keyvalue.c chunk.c \
http_chunk.c stream.c fdevent.c gw_backend.c \
stat_cache.c plugin.c etag.c array.c \
stat_cache.c plugin.c http_etag.c array.c \
data_string.c data_array.c \
data_integer.c \
algo_md5.c algo_sha1.c algo_splaytree.c \
@ -461,7 +461,7 @@ hdr = base64.h buffer.h burl.h network.h log.h http_kv.h keyvalue.h \
http_auth.h http_date.h http_header.h http_vhostdb.h stream.h \
fdevent.h gw_backend.h connections.h base.h base_decls.h stat_cache.h \
plugin.h plugin_config.h \
etag.h array.h vector.h \
http_etag.h array.h vector.h \
fdevent_impl.h network_write.h configfile.h \
mod_ssi.h mod_ssi_expr.h \
sock_addr_cache.h \

2
src/SConscript

@ -58,7 +58,7 @@ def GatherLibs(env, *libs):
common_src = Split("base64.c buffer.c burl.c log.c \
http_header.c http_kv.c keyvalue.c chunk.c \
http_chunk.c stream.c fdevent.c gw_backend.c \
stat_cache.c plugin.c etag.c array.c \
stat_cache.c plugin.c http_etag.c array.c \
data_string.c data_array.c \
data_integer.c \
algo_md5.c algo_sha1.c algo_splaytree.c \

11
src/algo_md.h

@ -28,14 +28,15 @@ static inline uint32_t djbhash(const char *str, const uint32_t len, uint32_t has
/* Donald E. Knuth
* The Art Of Computer Programming Volume 3
* Chapter 6.4, Topic: Sorting and Search */
/*(len should be passed as initial hash value.
* On subsequent calls, pass intermediate hash value for incremental hashing)*/
__attribute_pure__
static inline uint32_t dekhash (const char *str, const uint32_t len);
static inline uint32_t dekhash (const char *str, const uint32_t len)
static inline uint32_t dekhash (const char *str, const uint32_t len, uint32_t hash);
static inline uint32_t dekhash (const char *str, const uint32_t len, uint32_t hash)
{
const unsigned char * const s = (const unsigned char *)str;
uint32_t h = len;
for (uint32_t i = 0; i < len; ++i) h = (h << 5) ^ (h >> 27) ^ s[i];
return h;
for (uint32_t i = 0; i < len; ++i) hash = (hash << 5) ^ (hash >> 27) ^ s[i];
return hash;
}

2
src/configfile.c

@ -2,8 +2,8 @@
#include "base.h"
#include "burl.h"
#include "etag.h"
#include "fdevent.h"
#include "http_etag.h"
#include "keyvalue.h"
#include "log.h"
#include "stream.h"

183
src/etag.c

@ -1,183 +0,0 @@
#include "first.h"
#include "algo_md.h"
#include "buffer.h"
#include "etag.h"
#include <sys/stat.h>
int etag_is_equal(const buffer *etag, const char *line, int weak_ok) {
enum {
START = 0,
CHECK,
CHECK_QUOTED,
SKIP,
SKIP_QUOTED,
TAIL
} state = START;
const char *current;
const char *tok_start;
const char *tok = NULL;
int matched;
if ('*' == line[0] && '\0' == line[1]) {
return 1;
}
if (!etag || buffer_string_is_empty(etag)) return 0;
tok_start = etag->ptr;
if ('W' == tok_start[0]) {
if (!weak_ok || '/' != tok_start[1]) return 0; /* bad etag */
tok_start = tok_start + 2;
}
if ('"' != tok_start[0]) return 0; /* bad etag */
/* we start comparing after the first '"' */
++tok_start;
for (current = line; *current; ++current) {
switch (state) {
case START:
/* wait for etag to start; ignore whitespace and ',' */
switch (*current) {
case 'W':
/* weak etag always starts with 'W/"' */
if ('/' != *++current) return 0; /* bad etag list */
if ('"' != *++current) return 0; /* bad etag list */
if (!weak_ok) {
state = SKIP;
} else {
state = CHECK;
tok = tok_start;
}
break;
case '"':
/* strong etag starts with '"' */
state = CHECK;
tok = tok_start;
break;
case ' ':
case ',':
case '\t':
case '\r':
case '\n':
break;
default:
return 0; /* bad etag list */
}
break;
case CHECK:
/* compare etags (after the beginning '"')
* quoted-pairs must match too (i.e. quoted in both strings):
* > (RFC 2616:) both validators MUST be identical in every way
*/
matched = *tok && *tok == *current;
++tok;
switch (*current) {
case '\\':
state = matched ? CHECK_QUOTED : SKIP_QUOTED;
break;
case '"':
if (*tok) {
/* bad etag - string should end after '"' */
return 0;
}
if (matched) {
/* matching etag: strings were equal */
return 1;
}
state = TAIL;
break;
default:
if (!matched) {
/* strings not matching, skip remainder of etag */
state = SKIP;
}
break;
}
break;
case CHECK_QUOTED:
if (!*tok || *tok != *current) {
/* strings not matching, skip remainder of etag */
state = SKIP;
break;
}
++tok;
state = CHECK;
break;
case SKIP:
/* wait for final (not quoted) '"' */
switch (*current) {
case '\\':
state = SKIP_QUOTED;
break;
case '"':
state = TAIL;
break;
}
break;
case SKIP_QUOTED:
state = SKIP;
break;
case TAIL:
/* search for ',', ignore white space */
switch (*current) {
case ',':
state = START;
break;
case ' ':
case '\t':
case '\r':
case '\n':
break;
default:
return 0; /* bad etag list */
}
break;
}
}
/* no matching etag found */
return 0;
}
int etag_create(buffer *etag, const struct stat *st, int flags) {
if (0 == flags) return 0;
buffer_clear(etag);
if (flags & ETAG_USE_INODE) {
buffer_append_int(etag, st->st_ino);
buffer_append_string_len(etag, CONST_STR_LEN("-"));
}
if (flags & ETAG_USE_SIZE) {
buffer_append_int(etag, st->st_size);
buffer_append_string_len(etag, CONST_STR_LEN("-"));
}
if (flags & ETAG_USE_MTIME) {
buffer_append_int(etag, st->st_mtime);
#ifdef st_mtime /* use high-precision timestamp if available */
#if defined(__APPLE__) && defined(__MACH__)
buffer_append_int(etag, st->st_mtimespec.tv_nsec);
#else
buffer_append_int(etag, st->st_mtim.tv_nsec);
#endif
#endif
}
return 0;
}
void
etag_mutate (buffer * const mut, const buffer * const etag) {
/* mut and etag may be the same, so calculate hash before modifying mut */
const uint32_t h = dekhash(CONST_BUF_LEN(etag));
buffer_copy_string_len(mut, CONST_STR_LEN("\""));
buffer_append_int(mut, h);
buffer_append_string_len(mut, CONST_STR_LEN("\""));
}

20
src/etag.h

@ -1,20 +0,0 @@
#ifndef ETAG_H
#define ETAG_H
#include "first.h"
#include "buffer.h"
#ifdef _AIX
#include <sys/stat.h>
#else
struct stat; /* declaration */
#endif
typedef enum { ETAG_USE_INODE = 1, ETAG_USE_MTIME = 2, ETAG_USE_SIZE = 4 } etag_flags_t;
int etag_is_equal(const buffer *etag, const char *matches, int weak_ok);
int etag_create(buffer *etag, const struct stat *st, int flags);
void etag_mutate(buffer *mut, const buffer *etag);
#endif

7
src/http-header-glue.c

@ -8,9 +8,9 @@
#include "chunk.h"
#include "fdevent.h"
#include "log.h"
#include "etag.h"
#include "http_chunk.h"
#include "http_date.h"
#include "http_etag.h"
#include "http_header.h"
#include "response.h"
#include "sock_addr.h"
@ -200,7 +200,7 @@ int http_response_handle_cachable(request_st * const r, const buffer * const lmo
CONST_STR_LEN("If-None-Match")))) {
/*(weak etag comparison must not be used for ranged requests)*/
int range_request = (0 != light_btst(r->rqst_htags, HTTP_HEADER_RANGE));
if (etag_is_equal(&r->physical.etag, vb->ptr, !range_request)) {
if (http_etag_matches(&r->physical.etag, vb->ptr, !range_request)) {
if (http_method_get_or_head(r->http_method)) {
r->http_status = 304;
return HANDLER_FINISHED;
@ -635,8 +635,7 @@ void http_response_send_file (request_st * const r, buffer * const path) {
const buffer *etag =
stat_cache_etag_get(sce, r->conf.etag_flags);
if (!buffer_string_is_empty(etag)) {
/* generate e-tag */
etag_mutate(&r->physical.etag, etag);
buffer_copy_buffer(&r->physical.etag, etag);
http_header_response_set(r, HTTP_HEADER_ETAG,
CONST_STR_LEN("ETag"),
CONST_BUF_LEN(&r->physical.etag));

86
src/http_etag.c

@ -0,0 +1,86 @@
/*
* http_etag - HTTP ETag manipulation
*
* Copyright(c) 2015,2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
* License: BSD 3-clause (same as lighttpd)
*/
#include "first.h"
#include "http_etag.h"
#include <sys/stat.h>
#include <string.h>
#include "algo_md.h"
#include "buffer.h"
int
http_etag_matches (const buffer * const etag, const char *s, const int weak_ok)
{
if ('*' == s[0] && '\0' == s[1]) return 1;
if (buffer_string_is_empty(etag)) return 0;
uint32_t etag_sz = buffer_string_length(etag);
const char *etag_ptr = etag->ptr;
if (etag_ptr[0] == 'W' && etag_ptr[1] == '/') {
if (!weak_ok) return 0;
etag_ptr += 2;
etag_sz -= 2;
}
while (*s) {
while (*s == ' ' || *s == '\t' || *s == ',') ++s;
if (s[0] == 'W' && s[1] == '/' ? (s+=2, weak_ok) : 1) {
if (0 == strncmp(s, etag_ptr, etag_sz) || *s == '*') {
s += (*s != '*' ? etag_sz : 1);
if (*s == '\0' || *s == ' ' || *s == '\t' || *s == ',')
return 1;
}
}
while (*s != '\0' && *s != ',') ++s;
}
return 0;
}
static void
http_etag_remix (buffer * const etag, const char * const str, const uint32_t len)
{
uint32_t h = dekhash(str, len, len); /*(pass len as initial hash value)*/
uint32_t elen = buffer_string_length(etag);
if (elen > 2) /*(expect "..." if set)*/
h = dekhash(etag->ptr+1, elen-2, h);
buffer_string_set_length(etag, 1);
etag->ptr[0] = '\"';
buffer_append_int(etag, h);
buffer_append_string_len(etag, CONST_STR_LEN("\""));
}
void
http_etag_create (buffer * const etag, const struct stat * const st, const int flags)
{
if (0 == flags) return;
uint64_t x[4];
uint32_t len = 0;
if (flags & ETAG_USE_INODE)
x[len++] = (uint64_t)st->st_ino;
if (flags & ETAG_USE_SIZE)
x[len++] = (uint64_t)st->st_size;
if (flags & ETAG_USE_MTIME) {
x[len++] = (uint64_t)st->st_mtime;
#ifdef st_mtime /* use high-precision timestamp if available */
#if defined(__APPLE__) && defined(__MACH__)
x[len++] = (uint64_t)st->st_mtimespec.tv_nsec;
#else
x[len++] = (uint64_t)st->st_mtim.tv_nsec;
#endif
#endif
}
buffer_clear(etag);
http_etag_remix(etag, (char *)x, len << 3);
}

27
src/http_etag.h

@ -0,0 +1,27 @@
/*
* http_etag - HTTP ETag manipulation
*
* Copyright(c) 2015,2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
* License: BSD 3-clause (same as lighttpd)
*/
#ifndef INCLUDED_HTTP_ETAG_H
#define INCLUDED_HTTP_ETAG_H
#include "first.h"
#include "buffer.h"
#ifdef _AIX
#include <sys/stat.h>
#else
struct stat; /* declaration */
#endif
typedef enum { ETAG_USE_INODE = 1, ETAG_USE_MTIME = 2, ETAG_USE_SIZE = 4 } etag_flags_t;
__attribute_pure__
int http_etag_matches (const buffer *etag, const char *matches, int weak_ok);
void http_etag_create (buffer *etag, const struct stat *st, int flags);
#endif

2
src/meson.build

@ -702,7 +702,7 @@ common_src = [
'data_array.c',
'data_integer.c',
'data_string.c',
'etag.c',
'http_etag.c',
'fdevent_freebsd_kqueue.c',
'fdevent_libev.c',
'fdevent_linux_sysepoll.c',

2
src/mod_deflate.c

@ -113,8 +113,8 @@
#include "fdevent.h"
#include "log.h"
#include "buffer.h"
#include "etag.h"
#include "http_chunk.h"
#include "http_etag.h"
#include "http_header.h"
#include "response.h"
#include "stat_cache.h"

6
src/mod_magnet.c

@ -13,7 +13,6 @@
#include "sock_addr.h"
#include "stat_cache.h"
#include "status_counter.h"
#include "etag.h"
#include <stdlib.h>
#include <string.h>
@ -345,10 +344,7 @@ static int magnet_stat(lua_State *L) {
request_st * const r = magnet_get_request(L);
const buffer *etag = stat_cache_etag_get(sce, r->conf.etag_flags);
if (!buffer_string_is_empty(etag)) {
/* we have to mutate the etag */
buffer * const tb = r->tmp_buf;
etag_mutate(tb, etag);
lua_pushlstring(L, CONST_BUF_LEN(tb));
lua_pushlstring(L, CONST_BUF_LEN(etag));
} else {
lua_pushnil(L);
}

6
src/mod_ssi.c

@ -4,6 +4,7 @@
#include "fdevent.h"
#include "log.h"
#include "buffer.h"
#include "http_etag.h"
#include "http_header.h"
#include "stat_cache.h"
@ -37,8 +38,6 @@
# include <sys/filio.h>
#endif
#include "etag.h"
static handler_ctx * handler_ctx_init(plugin_data *p, log_error_st *errh) {
handler_ctx *hctx = calloc(1, sizeof(*hctx));
force_assert(hctx);
@ -1204,8 +1203,7 @@ static int mod_ssi_handle_request(request_st * const r, handler_ctx * const p) {
if (st.st_mtime < include_file_last_mtime)
st.st_mtime = include_file_last_mtime;
etag_create(&r->physical.etag, &st, r->conf.etag_flags);
etag_mutate(&r->physical.etag, &r->physical.etag);
http_etag_create(&r->physical.etag, &st, r->conf.etag_flags);
http_header_response_set(r, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"), CONST_BUF_LEN(&r->physical.etag));
const buffer * const mtime = http_response_set_last_modified(r, st.st_mtime);

20
src/mod_webdav.c

@ -231,8 +231,8 @@
#include "chunk.h"
#include "fdevent.h"
#include "http_date.h"
#include "http_etag.h"
#include "http_header.h"
#include "etag.h"
#include "log.h"
#include "request.h"
#include "response.h" /* http_response_redirect_to_directory() */
@ -2203,12 +2203,11 @@ webdav_if_match_or_unmodified_since (request_st * const r, struct stat *st)
buffer *etagb = &r->physical.etag;
if (NULL != st && (NULL != im || NULL != inm)) {
etag_create(etagb, st, r->conf.etag_flags);
etag_mutate(etagb, etagb);
http_etag_create(etagb, st, r->conf.etag_flags);
}
if (NULL != im) {
if (NULL == st || !etag_is_equal(etagb, im->ptr, 0))
if (NULL == st || !http_etag_matches(etagb, im->ptr, 0))
return 412; /* Precondition Failed */
}
@ -2216,7 +2215,7 @@ webdav_if_match_or_unmodified_since (request_st * const r, struct stat *st)
if (NULL == st
? !buffer_is_equal_string(inm,CONST_STR_LEN("*"))
|| (errno != ENOENT && errno != ENOTDIR)
: etag_is_equal(etagb, inm->ptr, 1))
: http_etag_matches(etagb, inm->ptr, 1))
return 412; /* Precondition Failed */
}
@ -2236,9 +2235,8 @@ webdav_response_etag (request_st * const r, struct stat *st)
{
if (0 != r->conf.etag_flags) {
buffer *etagb = &r->physical.etag;
etag_create(etagb, st, r->conf.etag_flags);
http_etag_create(etagb, st, r->conf.etag_flags);
stat_cache_update_entry(CONST_BUF_LEN(&r->physical.path), st, etagb);
etag_mutate(etagb, etagb);
http_header_response_set(r, HTTP_HEADER_ETAG,
CONST_STR_LEN("ETag"),
CONST_BUF_LEN(etagb));
@ -3162,8 +3160,7 @@ webdav_propfind_live_props (const webdav_propfind_bufs * const restrict pb,
case WEBDAV_PROP_GETETAG:
if (0 != pb->r->conf.etag_flags) {
buffer *etagb = &pb->r->physical.etag;
etag_create(etagb, &pb->st, pb->r->conf.etag_flags);
etag_mutate(etagb, etagb);
http_etag_create(etagb, &pb->st, pb->r->conf.etag_flags);
buffer_append_string_len(b, CONST_STR_LEN(
"<D:getetag>"));
buffer_append_string_buffer(b, etagb);
@ -3788,10 +3785,9 @@ webdav_has_lock (request_st * const r,
}
if (S_ISDIR(st.st_mode)) continue;/*we ignore etag if dir*/
buffer *etagb = &r->physical.etag;
etag_create(etagb, &st, r->conf.etag_flags);
etag_mutate(etagb, etagb);
http_etag_create(etagb, &st, r->conf.etag_flags);
*p = '\0';
int ematch = etag_is_equal(etagb, etag, 0);
int ematch = http_etag_matches(etagb, etag, 0);
*p = ']';
if (!ematch) {
http_status_set_error(r, 412); /* Precondition Failed */

4
src/stat_cache.c

@ -3,7 +3,7 @@
#include "stat_cache.h"
#include "log.h"
#include "fdevent.h"
#include "etag.h"
#include "http_etag.h"
#include "algo_splaytree.h"
#include <sys/types.h>
@ -1058,7 +1058,7 @@ const buffer * stat_cache_etag_get(stat_cache_entry *sce, int flags) {
if (S_ISREG(sce->st.st_mode) || S_ISDIR(sce->st.st_mode)) {
if (0 == flags) return NULL;
etag_create(&sce->etag, &sce->st, flags);
http_etag_create(&sce->etag, &sce->st, flags);
return &sce->etag;
}

Loading…
Cancel
Save