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