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 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 155const char * 156keyword_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 178void 179keyword_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 193int 194keyword_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 209int 210keyword_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 233int 234keyword_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 258void 259keyword_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 */ 279int 280keyword_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; 294again: 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 428static struct tag * 429tag_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 440static void 441tag_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 */ 452static char * 453tag_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