keyword.c revision 156230
1/*-
2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: vendor/csup/dist/contrib/csup/keyword.c 156230 2006-03-03 04:11:29Z mux $
27 */
28
29#include <assert.h>
30#include <err.h>
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <time.h>
36
37#include "diff.h"
38#include "keyword.h"
39#include "misc.h"
40#include "queue.h"
41#include "stream.h"
42
43/*
44 * The keyword API is used to expand the CVS/RCS keywords in files,
45 * such as $Id$, $Revision$, etc.  The server does it for us when it
46 * sends us entire files, but we need to handle the expansion when
47 * applying a diff update.
48 */
49
50enum rcskey {
51	RCSKEY_AUTHOR,
52	RCSKEY_CVSHEADER,
53	RCSKEY_DATE,
54	RCSKEY_HEADER,
55	RCSKEY_ID,
56	RCSKEY_LOCKER,
57	RCSKEY_LOG,
58	RCSKEY_NAME,
59	RCSKEY_RCSFILE,
60	RCSKEY_REVISION,
61	RCSKEY_SOURCE,
62	RCSKEY_STATE
63};
64
65typedef enum rcskey rcskey_t;
66
67struct tag {
68	char *ident;
69	rcskey_t key;
70	int enabled;
71	STAILQ_ENTRY(tag) next;
72};
73
74static struct tag	*tag_new(const char *, rcskey_t);
75static char		*tag_expand(struct tag *, struct diffinfo *);
76static void		 tag_free(struct tag *);
77
78struct keyword {
79	STAILQ_HEAD(, tag) keywords;		/* Enabled keywords. */
80	size_t minkeylen;
81	size_t maxkeylen;
82};
83
84/* Default CVS keywords. */
85static struct {
86	const char *ident;
87	rcskey_t key;
88} tag_defaults[] = {
89	{ "Author",	RCSKEY_AUTHOR },
90	{ "CVSHeader",	RCSKEY_CVSHEADER },
91	{ "Date",	RCSKEY_DATE },
92	{ "Header",	RCSKEY_HEADER },
93	{ "Id",		RCSKEY_ID },
94	{ "Locker",	RCSKEY_LOCKER },
95	{ "Log",	RCSKEY_LOG },
96	{ "Name",	RCSKEY_NAME },
97	{ "RCSfile",	RCSKEY_RCSFILE },
98	{ "Revision",	RCSKEY_REVISION },
99	{ "Source",	RCSKEY_SOURCE },
100	{ "State",	RCSKEY_STATE },
101	{ NULL,		0, }
102};
103
104struct keyword *
105keyword_new(void)
106{
107	struct keyword *new;
108	struct tag *tag;
109	size_t len;
110	int i;
111
112	new = xmalloc(sizeof(struct keyword));
113	STAILQ_INIT(&new->keywords);
114	new->minkeylen = ~0;
115	new->maxkeylen = 0;
116	for (i = 0; tag_defaults[i].ident != NULL; i++) {
117		tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key);
118		STAILQ_INSERT_TAIL(&new->keywords, tag, next);
119		len = strlen(tag->ident);
120		/*
121		 * These values are only computed here and not updated when
122		 * adding an alias.  This is a bug, but CVSup has it and we
123		 * need to be bug-to-bug compatible since the server will
124		 * expect us to do the same, and we will fail with an MD5
125		 * checksum mismatch if we don't.
126		 */
127		new->minkeylen = min(new->minkeylen, len);
128		new->maxkeylen = max(new->maxkeylen, len);
129	}
130	return (new);
131}
132
133int
134keyword_decode_expand(const char *expand)
135{
136
137	if (strcmp(expand, ".") == 0)
138		return (EXPAND_DEFAULT);
139	else if (strcmp(expand, "kv") == 0)
140		return (EXPAND_KEYVALUE);
141	else if (strcmp(expand, "kvl") == 0)
142		return (EXPAND_KEYVALUELOCKER);
143	else if (strcmp(expand, "k") == 0)
144		return (EXPAND_KEY);
145	else if (strcmp(expand, "o") == 0)
146		return (EXPAND_OLD);
147	else if (strcmp(expand, "b") == 0)
148		return (EXPAND_BINARY);
149	else if (strcmp(expand, "v") == 0)
150		return (EXPAND_VALUE);
151	else
152		return (-1);
153}
154
155void
156keyword_free(struct keyword *keyword)
157{
158	struct tag *tag;
159
160	if (keyword == NULL)
161		return;
162	while (!STAILQ_EMPTY(&keyword->keywords)) {
163		tag = STAILQ_FIRST(&keyword->keywords);
164		STAILQ_REMOVE_HEAD(&keyword->keywords, next);
165		tag_free(tag);
166	}
167	free(keyword);
168}
169
170int
171keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
172{
173	struct tag *new, *tag;
174
175	STAILQ_FOREACH(tag, &keyword->keywords, next) {
176		if (strcmp(tag->ident, rcskey) == 0) {
177			new = tag_new(ident, tag->key);
178			STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
179			return (0);
180		}
181	}
182	errno = ENOENT;
183	return (-1);
184}
185
186int
187keyword_enable(struct keyword *keyword, const char *ident)
188{
189	struct tag *tag;
190	int all;
191
192	all = 0;
193	if (strcmp(ident, ".") == 0)
194		all = 1;
195
196	STAILQ_FOREACH(tag, &keyword->keywords, next) {
197		if (!all && strcmp(tag->ident, ident) != 0)
198			continue;
199		tag->enabled = 1;
200		if (!all)
201			return (0);
202	}
203	if (!all) {
204		errno = ENOENT;
205		return (-1);
206	}
207	return (0);
208}
209
210int
211keyword_disable(struct keyword *keyword, const char *ident)
212{
213	struct tag *tag;
214	int all;
215
216	all = 0;
217	if (strcmp(ident, ".") == 0)
218		all = 1;
219
220	STAILQ_FOREACH(tag, &keyword->keywords, next) {
221		if (!all && strcmp(tag->ident, ident) != 0)
222			continue;
223		tag->enabled = 0;
224		if (!all)
225			return (0);
226	}
227
228	if (!all) {
229		errno = ENOENT;
230		return (-1);
231	}
232	return (0);
233}
234
235void
236keyword_prepare(struct keyword *keyword)
237{
238	struct tag *tag, *temp;
239
240	STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
241		if (!tag->enabled) {
242			STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
243			tag_free(tag);
244			continue;
245		}
246	}
247}
248
249/*
250 * Expand appropriate RCS keywords.  If there's no tag to expand,
251 * keyword_expand() returns 0, otherwise it returns 1 and writes a
252 * pointer to the new line in *buf and the new len in *len.  The
253 * new line is allocated with malloc() and needs to be freed by the
254 * caller after use.
255 */
256int
257keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
258    size_t size, char **buf, size_t *len)
259{
260	struct tag *tag;
261	char *dollar, *keystart, *valstart, *vallim, *next;
262	char *linestart, *newline, *newval, *cp, *tmp;
263	size_t left, newsize, vallen;
264
265	if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
266		return (0);
267	newline = NULL;
268	newsize = 0;
269	left = size;
270	linestart = cp = line;
271again:
272	dollar = memchr(cp, '$', left);
273	if (dollar == NULL) {
274		if (newline != NULL) {
275			*buf = newline;
276			*len = newsize;
277			return (1);
278		}
279		return (0);
280	}
281	keystart = dollar + 1;
282	left -= keystart - cp;
283	vallim = memchr(keystart, '$', left);
284	if (vallim == NULL) {
285		if (newline != NULL) {
286			*buf = newline;
287			*len = newsize;
288			return (1);
289		}
290		return (0);
291	}
292	if (vallim == keystart) {
293		cp = keystart;
294		goto again;
295	}
296	valstart = memchr(keystart, ':', left);
297	if (valstart == keystart) {
298		cp = vallim;
299		left -= vallim - keystart;
300		goto again;
301	}
302	if (valstart == NULL || valstart > vallim)
303		valstart = vallim;
304
305	if (valstart < keystart + keyword->minkeylen ||
306	    valstart > keystart + keyword->maxkeylen) {
307		cp = vallim;
308		left -= vallim -keystart;
309		goto again;
310	}
311	STAILQ_FOREACH(tag, &keyword->keywords, next) {
312		if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
313		    tag->ident[valstart - keystart] == '\0') {
314			if (newline != NULL)
315				tmp = newline;
316			else
317				tmp = NULL;
318			newval = NULL;
319			if (di->di_expand == EXPAND_KEY) {
320				newsize = dollar - linestart + 1 +
321				    valstart - keystart + 1 +
322				    size - (vallim + 1 - linestart);
323				newline = xmalloc(newsize);
324				cp = newline;
325				memcpy(cp, linestart, dollar - linestart);
326				cp += dollar - linestart;
327				*cp++ = '$';
328				memcpy(cp, keystart, valstart - keystart);
329				cp += valstart - keystart;
330				*cp++ = '$';
331				next = cp;
332				memcpy(cp, vallim + 1,
333				    size - (vallim + 1 - linestart));
334			} else if (di->di_expand == EXPAND_VALUE) {
335				newval = tag_expand(tag, di);
336				if (newval == NULL)
337					vallen = 0;
338				else
339					vallen = strlen(newval);
340				newsize = dollar - linestart +
341				    vallen +
342				    size - (vallim + 1 - linestart);
343				newline = xmalloc(newsize);
344				cp = newline;
345				memcpy(cp, linestart, dollar - linestart);
346				cp += dollar - linestart;
347				if (newval != NULL) {
348					memcpy(cp, newval, vallen);
349					cp += vallen;
350				}
351				next = cp;
352				memcpy(cp, vallim + 1,
353				    size - (vallim + 1 - linestart));
354			} else {
355				assert(di->di_expand == EXPAND_DEFAULT ||
356				    di->di_expand == EXPAND_KEYVALUE ||
357				    di->di_expand == EXPAND_KEYVALUELOCKER);
358				newval = tag_expand(tag, di);
359				if (newval == NULL)
360					vallen = 0;
361				else
362					vallen = strlen(newval);
363				newsize = dollar - linestart + 1 +
364				    valstart - keystart + 2 +
365				    vallen + 2 +
366				    size - (vallim + 1 - linestart);
367				newline = xmalloc(newsize);
368				cp = newline;
369				memcpy(cp, linestart, dollar - linestart);
370				cp += dollar - linestart;
371				*cp++ = '$';
372				memcpy(cp, keystart, valstart - keystart);
373				cp += valstart - keystart;
374				*cp++ = ':';
375				*cp++ = ' ';
376				if (newval != NULL) {
377					memcpy(cp, newval, vallen);
378					cp += vallen;
379				}
380				*cp++ = ' ';
381				*cp++ = '$';
382				next = cp;
383				memcpy(cp, vallim + 1,
384				    size - (vallim + 1 - linestart));
385			}
386			if (newval != NULL)
387				free(newval);
388			if (tmp != NULL)
389				free(tmp);
390			/*
391			 * Continue looking for tags in the rest of the line.
392			 */
393			cp = next;
394			size = newsize;
395			left = size - (cp - newline);
396			linestart = newline;
397			goto again;
398		}
399	}
400	cp = vallim;
401	left = size - (cp - linestart);
402	goto again;
403}
404
405static struct tag *
406tag_new(const char *ident, rcskey_t key)
407{
408	struct tag *new;
409
410	new = xmalloc(sizeof(struct tag));
411	new->ident = xstrdup(ident);
412	new->key = key;
413	new->enabled = 1;
414	return (new);
415}
416
417static void
418tag_free(struct tag *tag)
419{
420
421	free(tag->ident);
422	free(tag);
423}
424
425/*
426 * Expand a specific tag and return the new value.  If NULL
427 * is returned, the tag is empty.
428 */
429static char *
430tag_expand(struct tag *tag, struct diffinfo *di)
431{
432	/*
433	 * CVS formats dates as "XXXX/XX/XX XX:XX:XX".  32 bytes
434	 * is big enough until year 10,000,000,000,000,000 :-).
435	 */
436	char cvsdate[32];
437	struct tm tm;
438	char *filename, *val;
439	int error;
440
441	error = rcsdatetotm(di->di_revdate, &tm);
442	if (error)
443		err(1, "strptime");
444	if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
445		err(1, "strftime");
446	filename = strrchr(di->di_rcsfile, '/');
447	if (filename == NULL)
448		filename = di->di_rcsfile;
449	else
450		filename++;
451
452	switch (tag->key) {
453	case RCSKEY_AUTHOR:
454		xasprintf(&val, "%s", di->di_author);
455		break;
456	case RCSKEY_CVSHEADER:
457		xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
458		    di->di_revnum, cvsdate, di->di_author, di->di_state);
459		break;
460	case RCSKEY_DATE:
461		xasprintf(&val, "%s", cvsdate);
462		break;
463	case RCSKEY_HEADER:
464		xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
465		    di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
466		    di->di_state);
467		break;
468	case RCSKEY_ID:
469		xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
470		    cvsdate, di->di_author, di->di_state);
471		break;
472	case RCSKEY_LOCKER:
473		/*
474		 * Unimplemented even in CVSup sources.  It seems we don't
475		 * even have this information sent by the server.
476		 */
477		return (NULL);
478	case RCSKEY_LOG:
479		/* XXX */
480		printf("%s: Implement Log keyword expansion\n", __func__);
481		return (NULL);
482	case RCSKEY_NAME:
483		if (di->di_tag != NULL)
484			xasprintf(&val, "%s", di->di_tag);
485		else
486			return (NULL);
487		break;
488	case RCSKEY_RCSFILE:
489		xasprintf(&val, "%s", filename);
490		break;
491	case RCSKEY_REVISION:
492		xasprintf(&val, "%s", di->di_revnum);
493		break;
494	case RCSKEY_SOURCE:
495		xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
496		break;
497	case RCSKEY_STATE:
498		xasprintf(&val, "%s", di->di_state);
499		break;
500	}
501	return (val);
502}
503