lighttpd1.4/src/etag.c

188 lines
3.8 KiB
C

#include "first.h"
#include "buffer.h"
#include "etag.h"
#include <sys/stat.h>
#include <string.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;
}
int etag_mutate(buffer *mut, const buffer *etag) {
size_t i, len;
uint32_t h;
len = buffer_string_length(etag);
for (h=0, i=0; i < len; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]);
buffer_copy_string_len(mut, CONST_STR_LEN("\""));
buffer_append_int(mut, h);
buffer_append_string_len(mut, CONST_STR_LEN("\""));
return 0;
}