xref: /trueos/usr.bin/csup/keyword.c (revision 834fb25a9ed2240101506d137b5be7d71c75f306)
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$
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 
50 enum 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 
65 typedef enum rcskey rcskey_t;
66 
67 struct tag {
68 	char *ident;
69 	rcskey_t key;
70 	int enabled;
71 	STAILQ_ENTRY(tag) next;
72 };
73 
74 static struct tag	*tag_new(const char *, rcskey_t);
75 static char		*tag_expand(struct tag *, struct diffinfo *);
76 static void		 tag_free(struct tag *);
77 
78 struct keyword {
79 	STAILQ_HEAD(, tag) keywords;		/* Enabled keywords. */
80 	size_t minkeylen;
81 	size_t maxkeylen;
82 };
83 
84 /* Default CVS keywords. */
85 static 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 
104 struct keyword *
keyword_new(void)105 keyword_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 
133 int
keyword_decode_expand(const char * expand)134 keyword_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 
155 const char *
keyword_encode_expand(int expand)156 keyword_encode_expand(int expand)
157 {
158 
159 	switch (expand) {
160 		case EXPAND_DEFAULT:
161 			return (".");
162 		case EXPAND_KEYVALUE:
163 			return ("kv");
164 		case EXPAND_KEYVALUELOCKER:
165 			return ("kvl");
166 		case EXPAND_KEY:
167 			return ("k");
168 		case EXPAND_OLD:
169 			return ("o");
170 		case EXPAND_BINARY:
171 			return ("b");
172 		case EXPAND_VALUE:
173 			return ("v");
174 	}
175 	return (NULL);
176 }
177 
178 void
keyword_free(struct keyword * keyword)179 keyword_free(struct keyword *keyword)
180 {
181 	struct tag *tag;
182 
183 	if (keyword == NULL)
184 		return;
185 	while (!STAILQ_EMPTY(&keyword->keywords)) {
186 		tag = STAILQ_FIRST(&keyword->keywords);
187 		STAILQ_REMOVE_HEAD(&keyword->keywords, next);
188 		tag_free(tag);
189 	}
190 	free(keyword);
191 }
192 
193 int
keyword_alias(struct keyword * keyword,const char * ident,const char * rcskey)194 keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
195 {
196 	struct tag *new, *tag;
197 
198 	STAILQ_FOREACH(tag, &keyword->keywords, next) {
199 		if (strcmp(tag->ident, rcskey) == 0) {
200 			new = tag_new(ident, tag->key);
201 			STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
202 			return (0);
203 		}
204 	}
205 	errno = ENOENT;
206 	return (-1);
207 }
208 
209 int
keyword_enable(struct keyword * keyword,const char * ident)210 keyword_enable(struct keyword *keyword, const char *ident)
211 {
212 	struct tag *tag;
213 	int all;
214 
215 	all = 0;
216 	if (strcmp(ident, ".") == 0)
217 		all = 1;
218 
219 	STAILQ_FOREACH(tag, &keyword->keywords, next) {
220 		if (!all && strcmp(tag->ident, ident) != 0)
221 			continue;
222 		tag->enabled = 1;
223 		if (!all)
224 			return (0);
225 	}
226 	if (!all) {
227 		errno = ENOENT;
228 		return (-1);
229 	}
230 	return (0);
231 }
232 
233 int
keyword_disable(struct keyword * keyword,const char * ident)234 keyword_disable(struct keyword *keyword, const char *ident)
235 {
236 	struct tag *tag;
237 	int all;
238 
239 	all = 0;
240 	if (strcmp(ident, ".") == 0)
241 		all = 1;
242 
243 	STAILQ_FOREACH(tag, &keyword->keywords, next) {
244 		if (!all && strcmp(tag->ident, ident) != 0)
245 			continue;
246 		tag->enabled = 0;
247 		if (!all)
248 			return (0);
249 	}
250 
251 	if (!all) {
252 		errno = ENOENT;
253 		return (-1);
254 	}
255 	return (0);
256 }
257 
258 void
keyword_prepare(struct keyword * keyword)259 keyword_prepare(struct keyword *keyword)
260 {
261 	struct tag *tag, *temp;
262 
263 	STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
264 		if (!tag->enabled) {
265 			STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
266 			tag_free(tag);
267 			continue;
268 		}
269 	}
270 }
271 
272 /*
273  * Expand appropriate RCS keywords.  If there's no tag to expand,
274  * keyword_expand() returns 0, otherwise it returns 1 and writes a
275  * pointer to the new line in *buf and the new len in *len.  The
276  * new line is allocated with malloc() and needs to be freed by the
277  * caller after use.
278  */
279 int
keyword_expand(struct keyword * keyword,struct diffinfo * di,char * line,size_t size,char ** buf,size_t * len)280 keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
281     size_t size, char **buf, size_t *len)
282 {
283 	struct tag *tag;
284 	char *dollar, *keystart, *valstart, *vallim, *next;
285 	char *linestart, *newline, *newval, *cp, *tmp;
286 	size_t left, newsize, vallen;
287 
288 	if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
289 		return (0);
290 	newline = NULL;
291 	newsize = 0;
292 	left = size;
293 	linestart = cp = line;
294 again:
295 	dollar = memchr(cp, '$', left);
296 	if (dollar == NULL) {
297 		if (newline != NULL) {
298 			*buf = newline;
299 			*len = newsize;
300 			return (1);
301 		}
302 		return (0);
303 	}
304 	keystart = dollar + 1;
305 	left -= keystart - cp;
306 	vallim = memchr(keystart, '$', left);
307 	if (vallim == NULL) {
308 		if (newline != NULL) {
309 			*buf = newline;
310 			*len = newsize;
311 			return (1);
312 		}
313 		return (0);
314 	}
315 	if (vallim == keystart) {
316 		cp = keystart;
317 		goto again;
318 	}
319 	valstart = memchr(keystart, ':', left);
320 	if (valstart == keystart) {
321 		cp = vallim;
322 		left -= vallim - keystart;
323 		goto again;
324 	}
325 	if (valstart == NULL || valstart > vallim)
326 		valstart = vallim;
327 
328 	if (valstart < keystart + keyword->minkeylen ||
329 	    valstart > keystart + keyword->maxkeylen) {
330 		cp = vallim;
331 		left -= vallim -keystart;
332 		goto again;
333 	}
334 	STAILQ_FOREACH(tag, &keyword->keywords, next) {
335 		if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
336 		    tag->ident[valstart - keystart] == '\0') {
337 			if (newline != NULL)
338 				tmp = newline;
339 			else
340 				tmp = NULL;
341 			newval = NULL;
342 			if (di->di_expand == EXPAND_KEY) {
343 				newsize = dollar - linestart + 1 +
344 				    valstart - keystart + 1 +
345 				    size - (vallim + 1 - linestart);
346 				newline = xmalloc(newsize);
347 				cp = newline;
348 				memcpy(cp, linestart, dollar - linestart);
349 				cp += dollar - linestart;
350 				*cp++ = '$';
351 				memcpy(cp, keystart, valstart - keystart);
352 				cp += valstart - keystart;
353 				*cp++ = '$';
354 				next = cp;
355 				memcpy(cp, vallim + 1,
356 				    size - (vallim + 1 - linestart));
357 			} else if (di->di_expand == EXPAND_VALUE) {
358 				newval = tag_expand(tag, di);
359 				if (newval == NULL)
360 					vallen = 0;
361 				else
362 					vallen = strlen(newval);
363 				newsize = dollar - linestart +
364 				    vallen +
365 				    size - (vallim + 1 - linestart);
366 				newline = xmalloc(newsize);
367 				cp = newline;
368 				memcpy(cp, linestart, dollar - linestart);
369 				cp += dollar - linestart;
370 				if (newval != NULL) {
371 					memcpy(cp, newval, vallen);
372 					cp += vallen;
373 				}
374 				next = cp;
375 				memcpy(cp, vallim + 1,
376 				    size - (vallim + 1 - linestart));
377 			} else {
378 				assert(di->di_expand == EXPAND_DEFAULT ||
379 				    di->di_expand == EXPAND_KEYVALUE ||
380 				    di->di_expand == EXPAND_KEYVALUELOCKER);
381 				newval = tag_expand(tag, di);
382 				if (newval == NULL)
383 					vallen = 0;
384 				else
385 					vallen = strlen(newval);
386 				newsize = dollar - linestart + 1 +
387 				    valstart - keystart + 2 +
388 				    vallen + 2 +
389 				    size - (vallim + 1 - linestart);
390 				newline = xmalloc(newsize);
391 				cp = newline;
392 				memcpy(cp, linestart, dollar - linestart);
393 				cp += dollar - linestart;
394 				*cp++ = '$';
395 				memcpy(cp, keystart, valstart - keystart);
396 				cp += valstart - keystart;
397 				*cp++ = ':';
398 				*cp++ = ' ';
399 				if (newval != NULL) {
400 					memcpy(cp, newval, vallen);
401 					cp += vallen;
402 				}
403 				*cp++ = ' ';
404 				*cp++ = '$';
405 				next = cp;
406 				memcpy(cp, vallim + 1,
407 				    size - (vallim + 1 - linestart));
408 			}
409 			if (newval != NULL)
410 				free(newval);
411 			if (tmp != NULL)
412 				free(tmp);
413 			/*
414 			 * Continue looking for tags in the rest of the line.
415 			 */
416 			cp = next;
417 			size = newsize;
418 			left = size - (cp - newline);
419 			linestart = newline;
420 			goto again;
421 		}
422 	}
423 	cp = vallim;
424 	left = size - (cp - linestart);
425 	goto again;
426 }
427 
428 static struct tag *
tag_new(const char * ident,rcskey_t key)429 tag_new(const char *ident, rcskey_t key)
430 {
431 	struct tag *new;
432 
433 	new = xmalloc(sizeof(struct tag));
434 	new->ident = xstrdup(ident);
435 	new->key = key;
436 	new->enabled = 1;
437 	return (new);
438 }
439 
440 static void
tag_free(struct tag * tag)441 tag_free(struct tag *tag)
442 {
443 
444 	free(tag->ident);
445 	free(tag);
446 }
447 
448 /*
449  * Expand a specific tag and return the new value.  If NULL
450  * is returned, the tag is empty.
451  */
452 static char *
tag_expand(struct tag * tag,struct diffinfo * di)453 tag_expand(struct tag *tag, struct diffinfo *di)
454 {
455 	/*
456 	 * CVS formats dates as "XXXX/XX/XX XX:XX:XX".  32 bytes
457 	 * is big enough until year 10,000,000,000,000,000 :-).
458 	 */
459 	char cvsdate[32];
460 	struct tm tm;
461 	char *filename, *val;
462 	int error;
463 
464 	error = rcsdatetotm(di->di_revdate, &tm);
465 	if (error)
466 		err(1, "strptime");
467 	if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
468 		err(1, "strftime");
469 	filename = strrchr(di->di_rcsfile, '/');
470 	if (filename == NULL)
471 		filename = di->di_rcsfile;
472 	else
473 		filename++;
474 
475 	switch (tag->key) {
476 	case RCSKEY_AUTHOR:
477 		xasprintf(&val, "%s", di->di_author);
478 		break;
479 	case RCSKEY_CVSHEADER:
480 		xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
481 		    di->di_revnum, cvsdate, di->di_author, di->di_state);
482 		break;
483 	case RCSKEY_DATE:
484 		xasprintf(&val, "%s", cvsdate);
485 		break;
486 	case RCSKEY_HEADER:
487 		xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
488 		    di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
489 		    di->di_state);
490 		break;
491 	case RCSKEY_ID:
492 		xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
493 		    cvsdate, di->di_author, di->di_state);
494 		break;
495 	case RCSKEY_LOCKER:
496 		/*
497 		 * Unimplemented even in CVSup sources.  It seems we don't
498 		 * even have this information sent by the server.
499 		 */
500 		return (NULL);
501 	case RCSKEY_LOG:
502 		/* XXX */
503 		printf("%s: Implement Log keyword expansion\n", __func__);
504 		return (NULL);
505 	case RCSKEY_NAME:
506 		if (di->di_tag != NULL)
507 			xasprintf(&val, "%s", di->di_tag);
508 		else
509 			return (NULL);
510 		break;
511 	case RCSKEY_RCSFILE:
512 		xasprintf(&val, "%s", filename);
513 		break;
514 	case RCSKEY_REVISION:
515 		xasprintf(&val, "%s", di->di_revnum);
516 		break;
517 	case RCSKEY_SOURCE:
518 		xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
519 		break;
520 	case RCSKEY_STATE:
521 		xasprintf(&val, "%s", di->di_state);
522 		break;
523 	}
524 	return (val);
525 }
526