1156230Smux/*-
2156230Smux * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3156230Smux * All rights reserved.
4156230Smux *
5156230Smux * Redistribution and use in source and binary forms, with or without
6156230Smux * modification, are permitted provided that the following conditions
7156230Smux * are met:
8156230Smux * 1. Redistributions of source code must retain the above copyright
9156230Smux *    notice, this list of conditions and the following disclaimer.
10156230Smux * 2. Redistributions in binary form must reproduce the above copyright
11156230Smux *    notice, this list of conditions and the following disclaimer in the
12156230Smux *    documentation and/or other materials provided with the distribution.
13156230Smux *
14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17156230Smux * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24156230Smux * SUCH DAMAGE.
25156230Smux *
26156230Smux * $FreeBSD$
27156230Smux */
28156230Smux
29156230Smux#include <assert.h>
30156230Smux#include <err.h>
31156230Smux#include <errno.h>
32156230Smux#include <stdio.h>
33156230Smux#include <stdlib.h>
34156230Smux#include <string.h>
35156230Smux#include <time.h>
36156230Smux
37156230Smux#include "diff.h"
38156230Smux#include "keyword.h"
39156230Smux#include "misc.h"
40156230Smux#include "queue.h"
41156230Smux#include "stream.h"
42156230Smux
43156230Smux/*
44156230Smux * The keyword API is used to expand the CVS/RCS keywords in files,
45156230Smux * such as $Id$, $Revision$, etc.  The server does it for us when it
46156230Smux * sends us entire files, but we need to handle the expansion when
47156230Smux * applying a diff update.
48156230Smux */
49156230Smux
50156230Smuxenum rcskey {
51156230Smux	RCSKEY_AUTHOR,
52156230Smux	RCSKEY_CVSHEADER,
53156230Smux	RCSKEY_DATE,
54156230Smux	RCSKEY_HEADER,
55156230Smux	RCSKEY_ID,
56156230Smux	RCSKEY_LOCKER,
57156230Smux	RCSKEY_LOG,
58156230Smux	RCSKEY_NAME,
59156230Smux	RCSKEY_RCSFILE,
60156230Smux	RCSKEY_REVISION,
61156230Smux	RCSKEY_SOURCE,
62156230Smux	RCSKEY_STATE
63156230Smux};
64156230Smux
65156230Smuxtypedef enum rcskey rcskey_t;
66156230Smux
67156230Smuxstruct tag {
68156230Smux	char *ident;
69156230Smux	rcskey_t key;
70156230Smux	int enabled;
71156230Smux	STAILQ_ENTRY(tag) next;
72156230Smux};
73156230Smux
74156230Smuxstatic struct tag	*tag_new(const char *, rcskey_t);
75156230Smuxstatic char		*tag_expand(struct tag *, struct diffinfo *);
76156230Smuxstatic void		 tag_free(struct tag *);
77156230Smux
78156230Smuxstruct keyword {
79156230Smux	STAILQ_HEAD(, tag) keywords;		/* Enabled keywords. */
80156230Smux	size_t minkeylen;
81156230Smux	size_t maxkeylen;
82156230Smux};
83156230Smux
84156230Smux/* Default CVS keywords. */
85156230Smuxstatic struct {
86156230Smux	const char *ident;
87156230Smux	rcskey_t key;
88156230Smux} tag_defaults[] = {
89156230Smux	{ "Author",	RCSKEY_AUTHOR },
90156230Smux	{ "CVSHeader",	RCSKEY_CVSHEADER },
91156230Smux	{ "Date",	RCSKEY_DATE },
92156230Smux	{ "Header",	RCSKEY_HEADER },
93156230Smux	{ "Id",		RCSKEY_ID },
94156230Smux	{ "Locker",	RCSKEY_LOCKER },
95156230Smux	{ "Log",	RCSKEY_LOG },
96156230Smux	{ "Name",	RCSKEY_NAME },
97156230Smux	{ "RCSfile",	RCSKEY_RCSFILE },
98156230Smux	{ "Revision",	RCSKEY_REVISION },
99156230Smux	{ "Source",	RCSKEY_SOURCE },
100156230Smux	{ "State",	RCSKEY_STATE },
101156230Smux	{ NULL,		0, }
102156230Smux};
103156230Smux
104156230Smuxstruct keyword *
105156230Smuxkeyword_new(void)
106156230Smux{
107156230Smux	struct keyword *new;
108156230Smux	struct tag *tag;
109156230Smux	size_t len;
110156230Smux	int i;
111156230Smux
112156230Smux	new = xmalloc(sizeof(struct keyword));
113156230Smux	STAILQ_INIT(&new->keywords);
114156230Smux	new->minkeylen = ~0;
115156230Smux	new->maxkeylen = 0;
116156230Smux	for (i = 0; tag_defaults[i].ident != NULL; i++) {
117156230Smux		tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key);
118156230Smux		STAILQ_INSERT_TAIL(&new->keywords, tag, next);
119156230Smux		len = strlen(tag->ident);
120156230Smux		/*
121156230Smux		 * These values are only computed here and not updated when
122156230Smux		 * adding an alias.  This is a bug, but CVSup has it and we
123156230Smux		 * need to be bug-to-bug compatible since the server will
124156230Smux		 * expect us to do the same, and we will fail with an MD5
125156230Smux		 * checksum mismatch if we don't.
126156230Smux		 */
127156230Smux		new->minkeylen = min(new->minkeylen, len);
128156230Smux		new->maxkeylen = max(new->maxkeylen, len);
129156230Smux	}
130156230Smux	return (new);
131156230Smux}
132156230Smux
133156230Smuxint
134156230Smuxkeyword_decode_expand(const char *expand)
135156230Smux{
136156230Smux
137156230Smux	if (strcmp(expand, ".") == 0)
138156230Smux		return (EXPAND_DEFAULT);
139156230Smux	else if (strcmp(expand, "kv") == 0)
140156230Smux		return (EXPAND_KEYVALUE);
141156230Smux	else if (strcmp(expand, "kvl") == 0)
142156230Smux		return (EXPAND_KEYVALUELOCKER);
143156230Smux	else if (strcmp(expand, "k") == 0)
144156230Smux		return (EXPAND_KEY);
145156230Smux	else if (strcmp(expand, "o") == 0)
146156230Smux		return (EXPAND_OLD);
147156230Smux	else if (strcmp(expand, "b") == 0)
148156230Smux		return (EXPAND_BINARY);
149156230Smux	else if (strcmp(expand, "v") == 0)
150156230Smux		return (EXPAND_VALUE);
151156230Smux	else
152156230Smux		return (-1);
153156230Smux}
154156230Smux
155186781Slulfconst char *
156186781Slulfkeyword_encode_expand(int expand)
157186781Slulf{
158186781Slulf
159186781Slulf	switch (expand) {
160186781Slulf		case EXPAND_DEFAULT:
161186781Slulf			return (".");
162186781Slulf		case EXPAND_KEYVALUE:
163186781Slulf			return ("kv");
164186781Slulf		case EXPAND_KEYVALUELOCKER:
165186781Slulf			return ("kvl");
166186781Slulf		case EXPAND_KEY:
167186781Slulf			return ("k");
168186781Slulf		case EXPAND_OLD:
169186781Slulf			return ("o");
170186781Slulf		case EXPAND_BINARY:
171186781Slulf			return ("b");
172186781Slulf		case EXPAND_VALUE:
173186781Slulf			return ("v");
174186781Slulf	}
175186781Slulf	return (NULL);
176186781Slulf}
177186781Slulf
178156230Smuxvoid
179156230Smuxkeyword_free(struct keyword *keyword)
180156230Smux{
181156230Smux	struct tag *tag;
182156230Smux
183156230Smux	if (keyword == NULL)
184156230Smux		return;
185156230Smux	while (!STAILQ_EMPTY(&keyword->keywords)) {
186156230Smux		tag = STAILQ_FIRST(&keyword->keywords);
187156230Smux		STAILQ_REMOVE_HEAD(&keyword->keywords, next);
188156230Smux		tag_free(tag);
189156230Smux	}
190156230Smux	free(keyword);
191156230Smux}
192156230Smux
193156230Smuxint
194156230Smuxkeyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
195156230Smux{
196156230Smux	struct tag *new, *tag;
197156230Smux
198156230Smux	STAILQ_FOREACH(tag, &keyword->keywords, next) {
199156230Smux		if (strcmp(tag->ident, rcskey) == 0) {
200156230Smux			new = tag_new(ident, tag->key);
201156230Smux			STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
202156230Smux			return (0);
203156230Smux		}
204156230Smux	}
205156230Smux	errno = ENOENT;
206156230Smux	return (-1);
207156230Smux}
208156230Smux
209156230Smuxint
210156230Smuxkeyword_enable(struct keyword *keyword, const char *ident)
211156230Smux{
212156230Smux	struct tag *tag;
213156230Smux	int all;
214156230Smux
215156230Smux	all = 0;
216156230Smux	if (strcmp(ident, ".") == 0)
217156230Smux		all = 1;
218156230Smux
219156230Smux	STAILQ_FOREACH(tag, &keyword->keywords, next) {
220156230Smux		if (!all && strcmp(tag->ident, ident) != 0)
221156230Smux			continue;
222156230Smux		tag->enabled = 1;
223156230Smux		if (!all)
224156230Smux			return (0);
225156230Smux	}
226156230Smux	if (!all) {
227156230Smux		errno = ENOENT;
228156230Smux		return (-1);
229156230Smux	}
230156230Smux	return (0);
231156230Smux}
232156230Smux
233156230Smuxint
234156230Smuxkeyword_disable(struct keyword *keyword, const char *ident)
235156230Smux{
236156230Smux	struct tag *tag;
237156230Smux	int all;
238156230Smux
239156230Smux	all = 0;
240156230Smux	if (strcmp(ident, ".") == 0)
241156230Smux		all = 1;
242156230Smux
243156230Smux	STAILQ_FOREACH(tag, &keyword->keywords, next) {
244156230Smux		if (!all && strcmp(tag->ident, ident) != 0)
245156230Smux			continue;
246156230Smux		tag->enabled = 0;
247156230Smux		if (!all)
248156230Smux			return (0);
249156230Smux	}
250156230Smux
251156230Smux	if (!all) {
252156230Smux		errno = ENOENT;
253156230Smux		return (-1);
254156230Smux	}
255156230Smux	return (0);
256156230Smux}
257156230Smux
258156230Smuxvoid
259156230Smuxkeyword_prepare(struct keyword *keyword)
260156230Smux{
261156230Smux	struct tag *tag, *temp;
262156230Smux
263156230Smux	STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
264156230Smux		if (!tag->enabled) {
265156230Smux			STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
266156230Smux			tag_free(tag);
267156230Smux			continue;
268156230Smux		}
269156230Smux	}
270156230Smux}
271156230Smux
272156230Smux/*
273156230Smux * Expand appropriate RCS keywords.  If there's no tag to expand,
274156230Smux * keyword_expand() returns 0, otherwise it returns 1 and writes a
275156230Smux * pointer to the new line in *buf and the new len in *len.  The
276156230Smux * new line is allocated with malloc() and needs to be freed by the
277156230Smux * caller after use.
278156230Smux */
279156230Smuxint
280156230Smuxkeyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
281156230Smux    size_t size, char **buf, size_t *len)
282156230Smux{
283156230Smux	struct tag *tag;
284156230Smux	char *dollar, *keystart, *valstart, *vallim, *next;
285156230Smux	char *linestart, *newline, *newval, *cp, *tmp;
286156230Smux	size_t left, newsize, vallen;
287156230Smux
288156230Smux	if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
289156230Smux		return (0);
290156230Smux	newline = NULL;
291156230Smux	newsize = 0;
292156230Smux	left = size;
293156230Smux	linestart = cp = line;
294156230Smuxagain:
295156230Smux	dollar = memchr(cp, '$', left);
296156230Smux	if (dollar == NULL) {
297156230Smux		if (newline != NULL) {
298156230Smux			*buf = newline;
299156230Smux			*len = newsize;
300156230Smux			return (1);
301156230Smux		}
302156230Smux		return (0);
303156230Smux	}
304156230Smux	keystart = dollar + 1;
305156230Smux	left -= keystart - cp;
306156230Smux	vallim = memchr(keystart, '$', left);
307156230Smux	if (vallim == NULL) {
308156230Smux		if (newline != NULL) {
309156230Smux			*buf = newline;
310156230Smux			*len = newsize;
311156230Smux			return (1);
312156230Smux		}
313156230Smux		return (0);
314156230Smux	}
315156230Smux	if (vallim == keystart) {
316156230Smux		cp = keystart;
317156230Smux		goto again;
318156230Smux	}
319156230Smux	valstart = memchr(keystart, ':', left);
320156230Smux	if (valstart == keystart) {
321156230Smux		cp = vallim;
322156230Smux		left -= vallim - keystart;
323156230Smux		goto again;
324156230Smux	}
325156230Smux	if (valstart == NULL || valstart > vallim)
326156230Smux		valstart = vallim;
327156230Smux
328156230Smux	if (valstart < keystart + keyword->minkeylen ||
329156230Smux	    valstart > keystart + keyword->maxkeylen) {
330156230Smux		cp = vallim;
331156230Smux		left -= vallim -keystart;
332156230Smux		goto again;
333156230Smux	}
334156230Smux	STAILQ_FOREACH(tag, &keyword->keywords, next) {
335156230Smux		if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
336156230Smux		    tag->ident[valstart - keystart] == '\0') {
337156230Smux			if (newline != NULL)
338156230Smux				tmp = newline;
339156230Smux			else
340156230Smux				tmp = NULL;
341156230Smux			newval = NULL;
342156230Smux			if (di->di_expand == EXPAND_KEY) {
343156230Smux				newsize = dollar - linestart + 1 +
344156230Smux				    valstart - keystart + 1 +
345156230Smux				    size - (vallim + 1 - linestart);
346156230Smux				newline = xmalloc(newsize);
347156230Smux				cp = newline;
348156230Smux				memcpy(cp, linestart, dollar - linestart);
349156230Smux				cp += dollar - linestart;
350156230Smux				*cp++ = '$';
351156230Smux				memcpy(cp, keystart, valstart - keystart);
352156230Smux				cp += valstart - keystart;
353156230Smux				*cp++ = '$';
354156230Smux				next = cp;
355156230Smux				memcpy(cp, vallim + 1,
356156230Smux				    size - (vallim + 1 - linestart));
357156230Smux			} else if (di->di_expand == EXPAND_VALUE) {
358156230Smux				newval = tag_expand(tag, di);
359156230Smux				if (newval == NULL)
360156230Smux					vallen = 0;
361156230Smux				else
362156230Smux					vallen = strlen(newval);
363156230Smux				newsize = dollar - linestart +
364156230Smux				    vallen +
365156230Smux				    size - (vallim + 1 - linestart);
366156230Smux				newline = xmalloc(newsize);
367156230Smux				cp = newline;
368156230Smux				memcpy(cp, linestart, dollar - linestart);
369156230Smux				cp += dollar - linestart;
370156230Smux				if (newval != NULL) {
371156230Smux					memcpy(cp, newval, vallen);
372156230Smux					cp += vallen;
373156230Smux				}
374156230Smux				next = cp;
375156230Smux				memcpy(cp, vallim + 1,
376156230Smux				    size - (vallim + 1 - linestart));
377156230Smux			} else {
378156230Smux				assert(di->di_expand == EXPAND_DEFAULT ||
379156230Smux				    di->di_expand == EXPAND_KEYVALUE ||
380156230Smux				    di->di_expand == EXPAND_KEYVALUELOCKER);
381156230Smux				newval = tag_expand(tag, di);
382156230Smux				if (newval == NULL)
383156230Smux					vallen = 0;
384156230Smux				else
385156230Smux					vallen = strlen(newval);
386156230Smux				newsize = dollar - linestart + 1 +
387156230Smux				    valstart - keystart + 2 +
388156230Smux				    vallen + 2 +
389156230Smux				    size - (vallim + 1 - linestart);
390156230Smux				newline = xmalloc(newsize);
391156230Smux				cp = newline;
392156230Smux				memcpy(cp, linestart, dollar - linestart);
393156230Smux				cp += dollar - linestart;
394156230Smux				*cp++ = '$';
395156230Smux				memcpy(cp, keystart, valstart - keystart);
396156230Smux				cp += valstart - keystart;
397156230Smux				*cp++ = ':';
398156230Smux				*cp++ = ' ';
399156230Smux				if (newval != NULL) {
400156230Smux					memcpy(cp, newval, vallen);
401156230Smux					cp += vallen;
402156230Smux				}
403156230Smux				*cp++ = ' ';
404156230Smux				*cp++ = '$';
405156230Smux				next = cp;
406156230Smux				memcpy(cp, vallim + 1,
407156230Smux				    size - (vallim + 1 - linestart));
408156230Smux			}
409156230Smux			if (newval != NULL)
410156230Smux				free(newval);
411156230Smux			if (tmp != NULL)
412156230Smux				free(tmp);
413156230Smux			/*
414156230Smux			 * Continue looking for tags in the rest of the line.
415156230Smux			 */
416156230Smux			cp = next;
417156230Smux			size = newsize;
418156230Smux			left = size - (cp - newline);
419156230Smux			linestart = newline;
420156230Smux			goto again;
421156230Smux		}
422156230Smux	}
423156230Smux	cp = vallim;
424156230Smux	left = size - (cp - linestart);
425156230Smux	goto again;
426156230Smux}
427156230Smux
428156230Smuxstatic struct tag *
429156230Smuxtag_new(const char *ident, rcskey_t key)
430156230Smux{
431156230Smux	struct tag *new;
432156230Smux
433156230Smux	new = xmalloc(sizeof(struct tag));
434156230Smux	new->ident = xstrdup(ident);
435156230Smux	new->key = key;
436156230Smux	new->enabled = 1;
437156230Smux	return (new);
438156230Smux}
439156230Smux
440156230Smuxstatic void
441156230Smuxtag_free(struct tag *tag)
442156230Smux{
443156230Smux
444156230Smux	free(tag->ident);
445156230Smux	free(tag);
446156230Smux}
447156230Smux
448156230Smux/*
449156230Smux * Expand a specific tag and return the new value.  If NULL
450156230Smux * is returned, the tag is empty.
451156230Smux */
452156230Smuxstatic char *
453156230Smuxtag_expand(struct tag *tag, struct diffinfo *di)
454156230Smux{
455156230Smux	/*
456156230Smux	 * CVS formats dates as "XXXX/XX/XX XX:XX:XX".  32 bytes
457156230Smux	 * is big enough until year 10,000,000,000,000,000 :-).
458156230Smux	 */
459156230Smux	char cvsdate[32];
460156230Smux	struct tm tm;
461156230Smux	char *filename, *val;
462156230Smux	int error;
463156230Smux
464156230Smux	error = rcsdatetotm(di->di_revdate, &tm);
465156230Smux	if (error)
466156230Smux		err(1, "strptime");
467156230Smux	if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
468156230Smux		err(1, "strftime");
469156230Smux	filename = strrchr(di->di_rcsfile, '/');
470156230Smux	if (filename == NULL)
471156230Smux		filename = di->di_rcsfile;
472156230Smux	else
473156230Smux		filename++;
474156230Smux
475156230Smux	switch (tag->key) {
476156230Smux	case RCSKEY_AUTHOR:
477156230Smux		xasprintf(&val, "%s", di->di_author);
478156230Smux		break;
479156230Smux	case RCSKEY_CVSHEADER:
480156230Smux		xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
481156230Smux		    di->di_revnum, cvsdate, di->di_author, di->di_state);
482156230Smux		break;
483156230Smux	case RCSKEY_DATE:
484156230Smux		xasprintf(&val, "%s", cvsdate);
485156230Smux		break;
486156230Smux	case RCSKEY_HEADER:
487156230Smux		xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
488156230Smux		    di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
489156230Smux		    di->di_state);
490156230Smux		break;
491156230Smux	case RCSKEY_ID:
492156230Smux		xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
493156230Smux		    cvsdate, di->di_author, di->di_state);
494156230Smux		break;
495156230Smux	case RCSKEY_LOCKER:
496156230Smux		/*
497156230Smux		 * Unimplemented even in CVSup sources.  It seems we don't
498156230Smux		 * even have this information sent by the server.
499156230Smux		 */
500156230Smux		return (NULL);
501156230Smux	case RCSKEY_LOG:
502156230Smux		/* XXX */
503156230Smux		printf("%s: Implement Log keyword expansion\n", __func__);
504156230Smux		return (NULL);
505156230Smux	case RCSKEY_NAME:
506156230Smux		if (di->di_tag != NULL)
507156230Smux			xasprintf(&val, "%s", di->di_tag);
508156230Smux		else
509156230Smux			return (NULL);
510156230Smux		break;
511156230Smux	case RCSKEY_RCSFILE:
512156230Smux		xasprintf(&val, "%s", filename);
513156230Smux		break;
514156230Smux	case RCSKEY_REVISION:
515156230Smux		xasprintf(&val, "%s", di->di_revnum);
516156230Smux		break;
517156230Smux	case RCSKEY_SOURCE:
518156230Smux		xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
519156230Smux		break;
520156230Smux	case RCSKEY_STATE:
521156230Smux		xasprintf(&val, "%s", di->di_state);
522156230Smux		break;
523156230Smux	}
524156230Smux	return (val);
525156230Smux}
526