/*- * Copyright (c) 2003-2006, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include "diff.h" #include "keyword.h" #include "misc.h" #include "queue.h" #include "stream.h" /* * The keyword API is used to expand the CVS/RCS keywords in files, * such as $Id$, $Revision$, etc. The server does it for us when it * sends us entire files, but we need to handle the expansion when * applying a diff update. */ enum rcskey { RCSKEY_AUTHOR, RCSKEY_CVSHEADER, RCSKEY_DATE, RCSKEY_HEADER, RCSKEY_ID, RCSKEY_LOCKER, RCSKEY_LOG, RCSKEY_NAME, RCSKEY_RCSFILE, RCSKEY_REVISION, RCSKEY_SOURCE, RCSKEY_STATE }; typedef enum rcskey rcskey_t; struct tag { char *ident; rcskey_t key; int enabled; STAILQ_ENTRY(tag) next; }; static struct tag *tag_new(const char *, rcskey_t); static char *tag_expand(struct tag *, struct diffinfo *); static void tag_free(struct tag *); struct keyword { STAILQ_HEAD(, tag) keywords; /* Enabled keywords. */ size_t minkeylen; size_t maxkeylen; }; /* Default CVS keywords. */ static struct { const char *ident; rcskey_t key; } tag_defaults[] = { { "Author", RCSKEY_AUTHOR }, { "CVSHeader", RCSKEY_CVSHEADER }, { "Date", RCSKEY_DATE }, { "Header", RCSKEY_HEADER }, { "Id", RCSKEY_ID }, { "Locker", RCSKEY_LOCKER }, { "Log", RCSKEY_LOG }, { "Name", RCSKEY_NAME }, { "RCSfile", RCSKEY_RCSFILE }, { "Revision", RCSKEY_REVISION }, { "Source", RCSKEY_SOURCE }, { "State", RCSKEY_STATE }, { NULL, 0, } }; struct keyword * keyword_new(void) { struct keyword *new; struct tag *tag; size_t len; int i; new = xmalloc(sizeof(struct keyword)); STAILQ_INIT(&new->keywords); new->minkeylen = ~0; new->maxkeylen = 0; for (i = 0; tag_defaults[i].ident != NULL; i++) { tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key); STAILQ_INSERT_TAIL(&new->keywords, tag, next); len = strlen(tag->ident); /* * These values are only computed here and not updated when * adding an alias. This is a bug, but CVSup has it and we * need to be bug-to-bug compatible since the server will * expect us to do the same, and we will fail with an MD5 * checksum mismatch if we don't. */ new->minkeylen = min(new->minkeylen, len); new->maxkeylen = max(new->maxkeylen, len); } return (new); } int keyword_decode_expand(const char *expand) { if (strcmp(expand, ".") == 0) return (EXPAND_DEFAULT); else if (strcmp(expand, "kv") == 0) return (EXPAND_KEYVALUE); else if (strcmp(expand, "kvl") == 0) return (EXPAND_KEYVALUELOCKER); else if (strcmp(expand, "k") == 0) return (EXPAND_KEY); else if (strcmp(expand, "o") == 0) return (EXPAND_OLD); else if (strcmp(expand, "b") == 0) return (EXPAND_BINARY); else if (strcmp(expand, "v") == 0) return (EXPAND_VALUE); else return (-1); } const char * keyword_encode_expand(int expand) { switch (expand) { case EXPAND_DEFAULT: return ("."); case EXPAND_KEYVALUE: return ("kv"); case EXPAND_KEYVALUELOCKER: return ("kvl"); case EXPAND_KEY: return ("k"); case EXPAND_OLD: return ("o"); case EXPAND_BINARY: return ("b"); case EXPAND_VALUE: return ("v"); } return (NULL); } void keyword_free(struct keyword *keyword) { struct tag *tag; if (keyword == NULL) return; while (!STAILQ_EMPTY(&keyword->keywords)) { tag = STAILQ_FIRST(&keyword->keywords); STAILQ_REMOVE_HEAD(&keyword->keywords, next); tag_free(tag); } free(keyword); } int keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey) { struct tag *new, *tag; STAILQ_FOREACH(tag, &keyword->keywords, next) { if (strcmp(tag->ident, rcskey) == 0) { new = tag_new(ident, tag->key); STAILQ_INSERT_HEAD(&keyword->keywords, new, next); return (0); } } errno = ENOENT; return (-1); } int keyword_enable(struct keyword *keyword, const char *ident) { struct tag *tag; int all; all = 0; if (strcmp(ident, ".") == 0) all = 1; STAILQ_FOREACH(tag, &keyword->keywords, next) { if (!all && strcmp(tag->ident, ident) != 0) continue; tag->enabled = 1; if (!all) return (0); } if (!all) { errno = ENOENT; return (-1); } return (0); } int keyword_disable(struct keyword *keyword, const char *ident) { struct tag *tag; int all; all = 0; if (strcmp(ident, ".") == 0) all = 1; STAILQ_FOREACH(tag, &keyword->keywords, next) { if (!all && strcmp(tag->ident, ident) != 0) continue; tag->enabled = 0; if (!all) return (0); } if (!all) { errno = ENOENT; return (-1); } return (0); } void keyword_prepare(struct keyword *keyword) { struct tag *tag, *temp; STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) { if (!tag->enabled) { STAILQ_REMOVE(&keyword->keywords, tag, tag, next); tag_free(tag); continue; } } } /* * Expand appropriate RCS keywords. If there's no tag to expand, * keyword_expand() returns 0, otherwise it returns 1 and writes a * pointer to the new line in *buf and the new len in *len. The * new line is allocated with malloc() and needs to be freed by the * caller after use. */ int keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line, size_t size, char **buf, size_t *len) { struct tag *tag; char *dollar, *keystart, *valstart, *vallim, *next; char *linestart, *newline, *newval, *cp, *tmp; size_t left, newsize, vallen; if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY) return (0); newline = NULL; newsize = 0; left = size; linestart = cp = line; again: dollar = memchr(cp, '$', left); if (dollar == NULL) { if (newline != NULL) { *buf = newline; *len = newsize; return (1); } return (0); } keystart = dollar + 1; left -= keystart - cp; vallim = memchr(keystart, '$', left); if (vallim == NULL) { if (newline != NULL) { *buf = newline; *len = newsize; return (1); } return (0); } if (vallim == keystart) { cp = keystart; goto again; } valstart = memchr(keystart, ':', left); if (valstart == keystart) { cp = vallim; left -= vallim - keystart; goto again; } if (valstart == NULL || valstart > vallim) valstart = vallim; if (valstart < keystart + keyword->minkeylen || valstart > keystart + keyword->maxkeylen) { cp = vallim; left -= vallim -keystart; goto again; } STAILQ_FOREACH(tag, &keyword->keywords, next) { if (strncmp(tag->ident, keystart, valstart - keystart) == 0 && tag->ident[valstart - keystart] == '\0') { if (newline != NULL) tmp = newline; else tmp = NULL; newval = NULL; if (di->di_expand == EXPAND_KEY) { newsize = dollar - linestart + 1 + valstart - keystart + 1 + size - (vallim + 1 - linestart); newline = xmalloc(newsize); cp = newline; memcpy(cp, linestart, dollar - linestart); cp += dollar - linestart; *cp++ = '$'; memcpy(cp, keystart, valstart - keystart); cp += valstart - keystart; *cp++ = '$'; next = cp; memcpy(cp, vallim + 1, size - (vallim + 1 - linestart)); } else if (di->di_expand == EXPAND_VALUE) { newval = tag_expand(tag, di); if (newval == NULL) vallen = 0; else vallen = strlen(newval); newsize = dollar - linestart + vallen + size - (vallim + 1 - linestart); newline = xmalloc(newsize); cp = newline; memcpy(cp, linestart, dollar - linestart); cp += dollar - linestart; if (newval != NULL) { memcpy(cp, newval, vallen); cp += vallen; } next = cp; memcpy(cp, vallim + 1, size - (vallim + 1 - linestart)); } else { assert(di->di_expand == EXPAND_DEFAULT || di->di_expand == EXPAND_KEYVALUE || di->di_expand == EXPAND_KEYVALUELOCKER); newval = tag_expand(tag, di); if (newval == NULL) vallen = 0; else vallen = strlen(newval); newsize = dollar - linestart + 1 + valstart - keystart + 2 + vallen + 2 + size - (vallim + 1 - linestart); newline = xmalloc(newsize); cp = newline; memcpy(cp, linestart, dollar - linestart); cp += dollar - linestart; *cp++ = '$'; memcpy(cp, keystart, valstart - keystart); cp += valstart - keystart; *cp++ = ':'; *cp++ = ' '; if (newval != NULL) { memcpy(cp, newval, vallen); cp += vallen; } *cp++ = ' '; *cp++ = '$'; next = cp; memcpy(cp, vallim + 1, size - (vallim + 1 - linestart)); } if (newval != NULL) free(newval); if (tmp != NULL) free(tmp); /* * Continue looking for tags in the rest of the line. */ cp = next; size = newsize; left = size - (cp - newline); linestart = newline; goto again; } } cp = vallim; left = size - (cp - linestart); goto again; } static struct tag * tag_new(const char *ident, rcskey_t key) { struct tag *new; new = xmalloc(sizeof(struct tag)); new->ident = xstrdup(ident); new->key = key; new->enabled = 1; return (new); } static void tag_free(struct tag *tag) { free(tag->ident); free(tag); } /* * Expand a specific tag and return the new value. If NULL * is returned, the tag is empty. */ static char * tag_expand(struct tag *tag, struct diffinfo *di) { /* * CVS formats dates as "XXXX/XX/XX XX:XX:XX". 32 bytes * is big enough until year 10,000,000,000,000,000 :-). */ char cvsdate[32]; struct tm tm; char *filename, *val; int error; error = rcsdatetotm(di->di_revdate, &tm); if (error) err(1, "strptime"); if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0) err(1, "strftime"); filename = strrchr(di->di_rcsfile, '/'); if (filename == NULL) filename = di->di_rcsfile; else filename++; switch (tag->key) { case RCSKEY_AUTHOR: xasprintf(&val, "%s", di->di_author); break; case RCSKEY_CVSHEADER: xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile, di->di_revnum, cvsdate, di->di_author, di->di_state); break; case RCSKEY_DATE: xasprintf(&val, "%s", cvsdate); break; case RCSKEY_HEADER: xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot, di->di_rcsfile, di->di_revnum, cvsdate, di->di_author, di->di_state); break; case RCSKEY_ID: xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum, cvsdate, di->di_author, di->di_state); break; case RCSKEY_LOCKER: /* * Unimplemented even in CVSup sources. It seems we don't * even have this information sent by the server. */ return (NULL); case RCSKEY_LOG: /* XXX */ printf("%s: Implement Log keyword expansion\n", __func__); return (NULL); case RCSKEY_NAME: if (di->di_tag != NULL) xasprintf(&val, "%s", di->di_tag); else return (NULL); break; case RCSKEY_RCSFILE: xasprintf(&val, "%s", filename); break; case RCSKEY_REVISION: xasprintf(&val, "%s", di->di_revnum); break; case RCSKEY_SOURCE: xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile); break; case RCSKEY_STATE: xasprintf(&val, "%s", di->di_state); break; } return (val); }