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