1#include "buffer.h"
2#include "etag.h"
3
4#if defined HAVE_STDINT_H
5# include <stdint.h>
6#elif defined HAVE_INTTYPES_H
7# include <inttypes.h>
8#endif
9
10#include <string.h>
11
12int etag_is_equal(buffer *etag, const char *line, int weak_ok) {
13	enum {
14		START = 0,
15		CHECK,
16		CHECK_QUOTED,
17		SKIP,
18		SKIP_QUOTED,
19		TAIL
20	} state = START;
21
22	const char *current;
23	const char *tok_start;
24	const char *tok = NULL;
25	int matched;
26
27	if ('*' == line[0] && '\0' == line[1]) {
28		return 1;
29	}
30
31	if (!etag || buffer_string_is_empty(etag)) return 0;
32	tok_start = etag->ptr;
33
34	if ('W' == tok_start[0]) {
35		if (!weak_ok || '/' != tok_start[1]) return 0; /* bad etag */
36		tok_start = tok_start + 2;
37	}
38
39	if ('"' != tok_start[0]) return 0; /* bad etag */
40	/* we start comparing after the first '"' */
41	++tok_start;
42
43	for (current = line; *current; ++current) {
44		switch (state) {
45		case START:
46			/* wait for etag to start; ignore whitespace and ',' */
47			switch (*current) {
48			case 'W':
49				/* weak etag always starts with 'W/"' */
50				if ('/' != *++current) return 0; /* bad etag list */
51				if ('"' != *++current) return 0; /* bad etag list */
52				if (!weak_ok) {
53					state = SKIP;
54				} else {
55					state = CHECK;
56					tok = tok_start;
57				}
58				break;
59			case '"':
60				/* strong etag starts with '"' */
61				state = CHECK;
62				tok = tok_start;
63				break;
64			case ' ':
65			case ',':
66			case '\t':
67			case '\r':
68			case '\n':
69				break;
70			default:
71				return 0; /* bad etag list */
72			}
73			break;
74		case CHECK:
75			/* compare etags (after the beginning '"')
76			 * quoted-pairs must match too (i.e. quoted in both strings):
77			 * > (RFC 2616:) both validators MUST be identical in every way
78			 */
79			matched = *tok && *tok == *current;
80			++tok;
81			switch (*current) {
82			case '\\':
83				state = matched ? CHECK_QUOTED : SKIP_QUOTED;
84				break;
85			case '"':
86				if (*tok)  {
87					/* bad etag - string should end after '"' */
88					return 0;
89				}
90				if (matched) {
91					/* matching etag: strings were equal */
92					return 1;
93				}
94
95				state = TAIL;
96				break;
97			default:
98				if (!matched) {
99					/* strings not matching, skip remainder of etag */
100					state = SKIP;
101				}
102				break;
103			}
104			break;
105		case CHECK_QUOTED:
106			if (!*tok || *tok != *current) {
107				/* strings not matching, skip remainder of etag */
108				state = SKIP;
109				break;
110			}
111			++tok;
112			state = CHECK;
113			break;
114		case SKIP:
115			/* wait for final (not quoted) '"' */
116			switch (*current) {
117			case '\\':
118				state = SKIP_QUOTED;
119				break;
120			case '"':
121				state = TAIL;
122				break;
123			}
124			break;
125		case SKIP_QUOTED:
126			state = SKIP;
127			break;
128		case TAIL:
129			/* search for ',', ignore white space */
130			switch (*current) {
131			case ',':
132				state = START;
133				break;
134			case ' ':
135			case '\t':
136			case '\r':
137			case '\n':
138				break;
139			default:
140				return 0; /* bad etag list */
141			}
142			break;
143		}
144	}
145	/* no matching etag found */
146	return 0;
147}
148
149int etag_create(buffer *etag, struct stat *st,etag_flags_t flags) {
150	if (0 == flags) return 0;
151
152	buffer_reset(etag);
153
154	if (flags & ETAG_USE_INODE) {
155		buffer_append_int(etag, st->st_ino);
156		buffer_append_string_len(etag, CONST_STR_LEN("-"));
157	}
158
159	if (flags & ETAG_USE_SIZE) {
160		buffer_append_int(etag, st->st_size);
161		buffer_append_string_len(etag, CONST_STR_LEN("-"));
162	}
163
164	if (flags & ETAG_USE_MTIME) {
165		buffer_append_int(etag, st->st_mtime);
166	}
167
168	return 0;
169}
170
171int etag_mutate(buffer *mut, buffer *etag) {
172	size_t i, len;
173	uint32_t h;
174
175	len = buffer_string_length(etag);
176	for (h=0, i=0; i < len; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]);
177
178	buffer_reset(mut);
179	buffer_copy_string_len(mut, CONST_STR_LEN("\""));
180	buffer_append_int(mut, h);
181	buffer_append_string_len(mut, CONST_STR_LEN("\""));
182
183	return 0;
184}
185