1251881Speter/* 2251881Speter * subst.c : generic eol/keyword substitution routines 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#define APR_WANT_STRFUNC 27251881Speter#include <apr_want.h> 28251881Speter 29251881Speter#include <stdlib.h> 30251881Speter#include <assert.h> 31251881Speter#include <apr_pools.h> 32251881Speter#include <apr_tables.h> 33251881Speter#include <apr_file_io.h> 34251881Speter#include <apr_strings.h> 35251881Speter 36251881Speter#include "svn_hash.h" 37251881Speter#include "svn_cmdline.h" 38251881Speter#include "svn_types.h" 39251881Speter#include "svn_string.h" 40251881Speter#include "svn_time.h" 41251881Speter#include "svn_dirent_uri.h" 42251881Speter#include "svn_path.h" 43251881Speter#include "svn_error.h" 44251881Speter#include "svn_utf.h" 45251881Speter#include "svn_io.h" 46251881Speter#include "svn_subst.h" 47251881Speter#include "svn_pools.h" 48251881Speter#include "private/svn_io_private.h" 49251881Speter 50251881Speter#include "svn_private_config.h" 51251881Speter 52251881Speter#include "private/svn_string_private.h" 53289180Speter#include "private/svn_eol_private.h" 54251881Speter 55251881Speter/** 56251881Speter * The textual elements of a detranslated special file. One of these 57251881Speter * strings must appear as the first element of any special file as it 58251881Speter * exists in the repository or the text base. 59251881Speter */ 60251881Speter#define SVN_SUBST__SPECIAL_LINK_STR "link" 61251881Speter 62251881Spetervoid 63251881Spetersvn_subst_eol_style_from_value(svn_subst_eol_style_t *style, 64251881Speter const char **eol, 65251881Speter const char *value) 66251881Speter{ 67251881Speter if (value == NULL) 68251881Speter { 69251881Speter /* property doesn't exist. */ 70251881Speter *eol = NULL; 71251881Speter if (style) 72251881Speter *style = svn_subst_eol_style_none; 73251881Speter } 74251881Speter else if (! strcmp("native", value)) 75251881Speter { 76251881Speter *eol = APR_EOL_STR; /* whee, a portability library! */ 77251881Speter if (style) 78251881Speter *style = svn_subst_eol_style_native; 79251881Speter } 80251881Speter else if (! strcmp("LF", value)) 81251881Speter { 82251881Speter *eol = "\n"; 83251881Speter if (style) 84251881Speter *style = svn_subst_eol_style_fixed; 85251881Speter } 86251881Speter else if (! strcmp("CR", value)) 87251881Speter { 88251881Speter *eol = "\r"; 89251881Speter if (style) 90251881Speter *style = svn_subst_eol_style_fixed; 91251881Speter } 92251881Speter else if (! strcmp("CRLF", value)) 93251881Speter { 94251881Speter *eol = "\r\n"; 95251881Speter if (style) 96251881Speter *style = svn_subst_eol_style_fixed; 97251881Speter } 98251881Speter else 99251881Speter { 100251881Speter *eol = NULL; 101251881Speter if (style) 102251881Speter *style = svn_subst_eol_style_unknown; 103251881Speter } 104251881Speter} 105251881Speter 106251881Speter 107251881Spetersvn_boolean_t 108251881Spetersvn_subst_translation_required(svn_subst_eol_style_t style, 109251881Speter const char *eol, 110251881Speter apr_hash_t *keywords, 111251881Speter svn_boolean_t special, 112251881Speter svn_boolean_t force_eol_check) 113251881Speter{ 114251881Speter return (special || keywords 115251881Speter || (style != svn_subst_eol_style_none && force_eol_check) 116251881Speter || (style == svn_subst_eol_style_native && 117251881Speter strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0) 118251881Speter || (style == svn_subst_eol_style_fixed && 119251881Speter strcmp(APR_EOL_STR, eol) != 0)); 120251881Speter} 121251881Speter 122251881Speter 123251881Speter 124251881Speter/* Helper function for svn_subst_build_keywords */ 125251881Speter 126251881Speter/* Given a printf-like format string, return a string with proper 127251881Speter * information filled in. 128251881Speter * 129251881Speter * Important API note: This function is the core of the implementation of 130251881Speter * svn_subst_build_keywords (all versions), and as such must implement the 131289180Speter * tolerance of NULL and zero inputs that that function's documentation 132251881Speter * stipulates. 133251881Speter * 134251881Speter * The format codes: 135251881Speter * 136251881Speter * %a author of this revision 137251881Speter * %b basename of the URL of this file 138251881Speter * %d short format of date of this revision 139251881Speter * %D long format of date of this revision 140251881Speter * %P path relative to root of repos 141251881Speter * %r number of this revision 142251881Speter * %R root url of repository 143251881Speter * %u URL of this file 144251881Speter * %_ a space 145251881Speter * %% a literal % 146251881Speter * 147251881Speter * The following special format codes are also recognized: 148251881Speter * %H is equivalent to %P%_%r%_%d%_%a 149251881Speter * %I is equivalent to %b%_%r%_%d%_%a 150251881Speter * 151251881Speter * All memory is allocated out of @a pool. 152251881Speter */ 153251881Speterstatic svn_string_t * 154251881Speterkeyword_printf(const char *fmt, 155251881Speter const char *rev, 156251881Speter const char *url, 157251881Speter const char *repos_root_url, 158251881Speter apr_time_t date, 159251881Speter const char *author, 160251881Speter apr_pool_t *pool) 161251881Speter{ 162289180Speter svn_stringbuf_t *value = svn_stringbuf_create_empty(pool); 163251881Speter const char *cur; 164251881Speter size_t n; 165251881Speter 166251881Speter for (;;) 167251881Speter { 168251881Speter cur = fmt; 169251881Speter 170251881Speter while (*cur != '\0' && *cur != '%') 171251881Speter cur++; 172251881Speter 173251881Speter if ((n = cur - fmt) > 0) /* Do we have an as-is string? */ 174251881Speter svn_stringbuf_appendbytes(value, fmt, n); 175251881Speter 176251881Speter if (*cur == '\0') 177251881Speter break; 178251881Speter 179251881Speter switch (cur[1]) 180251881Speter { 181251881Speter case 'a': /* author of this revision */ 182251881Speter if (author) 183251881Speter svn_stringbuf_appendcstr(value, author); 184251881Speter break; 185251881Speter case 'b': /* basename of this file */ 186251881Speter if (url && *url) 187251881Speter { 188251881Speter const char *base_name = svn_uri_basename(url, pool); 189251881Speter svn_stringbuf_appendcstr(value, base_name); 190251881Speter } 191251881Speter break; 192251881Speter case 'd': /* short format of date of this revision */ 193251881Speter if (date) 194251881Speter { 195251881Speter apr_time_exp_t exploded_time; 196251881Speter const char *human; 197251881Speter 198251881Speter apr_time_exp_gmt(&exploded_time, date); 199251881Speter 200251881Speter human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ", 201251881Speter exploded_time.tm_year + 1900, 202251881Speter exploded_time.tm_mon + 1, 203251881Speter exploded_time.tm_mday, 204251881Speter exploded_time.tm_hour, 205251881Speter exploded_time.tm_min, 206251881Speter exploded_time.tm_sec); 207251881Speter 208251881Speter svn_stringbuf_appendcstr(value, human); 209251881Speter } 210251881Speter break; 211251881Speter case 'D': /* long format of date of this revision */ 212251881Speter if (date) 213251881Speter svn_stringbuf_appendcstr(value, 214251881Speter svn_time_to_human_cstring(date, pool)); 215251881Speter break; 216251881Speter case 'P': /* relative path of this file */ 217251881Speter if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0') 218251881Speter { 219251881Speter const char *repos_relpath; 220251881Speter 221251881Speter repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool); 222251881Speter if (repos_relpath) 223251881Speter svn_stringbuf_appendcstr(value, repos_relpath); 224251881Speter } 225251881Speter break; 226251881Speter case 'R': /* root of repos */ 227251881Speter if (repos_root_url && *repos_root_url != '\0') 228251881Speter svn_stringbuf_appendcstr(value, repos_root_url); 229251881Speter break; 230251881Speter case 'r': /* number of this revision */ 231251881Speter if (rev) 232251881Speter svn_stringbuf_appendcstr(value, rev); 233251881Speter break; 234251881Speter case 'u': /* URL of this file */ 235251881Speter if (url) 236251881Speter svn_stringbuf_appendcstr(value, url); 237251881Speter break; 238251881Speter case '_': /* '%_' => a space */ 239251881Speter svn_stringbuf_appendbyte(value, ' '); 240251881Speter break; 241251881Speter case '%': /* '%%' => a literal % */ 242251881Speter svn_stringbuf_appendbyte(value, *cur); 243251881Speter break; 244251881Speter case '\0': /* '%' as the last character of the string. */ 245251881Speter svn_stringbuf_appendbyte(value, *cur); 246251881Speter /* Now go back one character, since this was just a one character 247251881Speter * sequence, whereas all others are two characters, and we do not 248251881Speter * want to skip the null terminator entirely and carry on 249251881Speter * formatting random memory contents. */ 250251881Speter cur--; 251251881Speter break; 252251881Speter case 'H': 253251881Speter { 254251881Speter svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url, 255251881Speter repos_root_url, date, author, 256251881Speter pool); 257251881Speter svn_stringbuf_appendcstr(value, s->data); 258251881Speter } 259251881Speter break; 260251881Speter case 'I': 261251881Speter { 262251881Speter svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url, 263251881Speter repos_root_url, date, author, 264251881Speter pool); 265251881Speter svn_stringbuf_appendcstr(value, s->data); 266251881Speter } 267251881Speter break; 268251881Speter default: /* Unrecognized code, just print it literally. */ 269251881Speter svn_stringbuf_appendbytes(value, cur, 2); 270251881Speter break; 271251881Speter } 272251881Speter 273251881Speter /* Format code is processed - skip it, and get ready for next chunk. */ 274251881Speter fmt = cur + 2; 275251881Speter } 276251881Speter 277251881Speter return svn_stringbuf__morph_into_string(value); 278251881Speter} 279251881Speter 280251881Speterstatic svn_error_t * 281251881Speterbuild_keywords(apr_hash_t **kw, 282251881Speter svn_boolean_t expand_custom_keywords, 283251881Speter const char *keywords_val, 284251881Speter const char *rev, 285251881Speter const char *url, 286251881Speter const char *repos_root_url, 287251881Speter apr_time_t date, 288251881Speter const char *author, 289251881Speter apr_pool_t *pool) 290251881Speter{ 291251881Speter apr_array_header_t *keyword_tokens; 292251881Speter int i; 293251881Speter *kw = apr_hash_make(pool); 294251881Speter 295251881Speter keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f", 296251881Speter TRUE /* chop */, pool); 297251881Speter 298251881Speter for (i = 0; i < keyword_tokens->nelts; ++i) 299251881Speter { 300251881Speter const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *); 301251881Speter const char *custom_fmt = NULL; 302251881Speter 303251881Speter if (expand_custom_keywords) 304251881Speter { 305251881Speter char *sep; 306251881Speter 307251881Speter /* Check if there is a custom keyword definition, started by '='. */ 308251881Speter sep = strchr(keyword, '='); 309251881Speter if (sep) 310251881Speter { 311251881Speter *sep = '\0'; /* Split keyword's name from custom format. */ 312251881Speter custom_fmt = sep + 1; 313251881Speter } 314251881Speter } 315251881Speter 316251881Speter if (custom_fmt) 317251881Speter { 318251881Speter svn_string_t *custom_val; 319251881Speter 320251881Speter /* Custom keywords must be allowed to match the name of an 321251881Speter * existing fixed keyword. This is for compatibility purposes, 322251881Speter * in case new fixed keywords are added to Subversion which 323251881Speter * happen to match a custom keyword defined somewhere. 324251881Speter * There is only one global namespace for keyword names. */ 325251881Speter custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url, 326251881Speter date, author, pool); 327251881Speter svn_hash_sets(*kw, keyword, custom_val); 328251881Speter } 329251881Speter else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG)) 330251881Speter || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM)) 331251881Speter || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT))) 332251881Speter { 333251881Speter svn_string_t *revision_val; 334251881Speter 335251881Speter revision_val = keyword_printf("%r", rev, url, repos_root_url, 336251881Speter date, author, pool); 337251881Speter svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val); 338251881Speter svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val); 339251881Speter svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val); 340251881Speter } 341251881Speter else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG)) 342251881Speter || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT))) 343251881Speter { 344251881Speter svn_string_t *date_val; 345251881Speter 346251881Speter date_val = keyword_printf("%D", rev, url, repos_root_url, date, 347251881Speter author, pool); 348251881Speter svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val); 349251881Speter svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val); 350251881Speter } 351251881Speter else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG)) 352251881Speter || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT))) 353251881Speter { 354251881Speter svn_string_t *author_val; 355251881Speter 356251881Speter author_val = keyword_printf("%a", rev, url, repos_root_url, date, 357251881Speter author, pool); 358251881Speter svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val); 359251881Speter svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val); 360251881Speter } 361251881Speter else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG)) 362251881Speter || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT))) 363251881Speter { 364251881Speter svn_string_t *url_val; 365251881Speter 366251881Speter url_val = keyword_printf("%u", rev, url, repos_root_url, date, 367251881Speter author, pool); 368251881Speter svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val); 369251881Speter svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val); 370251881Speter } 371251881Speter else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID))) 372251881Speter { 373251881Speter svn_string_t *id_val; 374251881Speter 375251881Speter id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url, 376251881Speter date, author, pool); 377251881Speter svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val); 378251881Speter } 379251881Speter else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER))) 380251881Speter { 381251881Speter svn_string_t *header_val; 382251881Speter 383251881Speter header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url, 384251881Speter date, author, pool); 385251881Speter svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val); 386251881Speter } 387251881Speter } 388251881Speter 389251881Speter return SVN_NO_ERROR; 390251881Speter} 391251881Speter 392251881Spetersvn_error_t * 393251881Spetersvn_subst_build_keywords2(apr_hash_t **kw, 394251881Speter const char *keywords_val, 395251881Speter const char *rev, 396251881Speter const char *url, 397251881Speter apr_time_t date, 398251881Speter const char *author, 399251881Speter apr_pool_t *pool) 400251881Speter{ 401251881Speter return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url, 402251881Speter NULL, date, author, pool)); 403251881Speter} 404251881Speter 405251881Speter 406251881Spetersvn_error_t * 407251881Spetersvn_subst_build_keywords3(apr_hash_t **kw, 408251881Speter const char *keywords_val, 409251881Speter const char *rev, 410251881Speter const char *url, 411251881Speter const char *repos_root_url, 412251881Speter apr_time_t date, 413251881Speter const char *author, 414251881Speter apr_pool_t *pool) 415251881Speter{ 416251881Speter return svn_error_trace(build_keywords(kw, TRUE, keywords_val, 417251881Speter rev, url, repos_root_url, 418251881Speter date, author, pool)); 419251881Speter} 420251881Speter 421251881Speter 422251881Speter/*** Helpers for svn_subst_translate_stream2 ***/ 423251881Speter 424251881Speter 425251881Speter/* Write out LEN bytes of BUF into STREAM. */ 426251881Speter/* ### TODO: 'stream_write()' would be a better name for this. */ 427251881Speterstatic svn_error_t * 428251881Spetertranslate_write(svn_stream_t *stream, 429251881Speter const void *buf, 430251881Speter apr_size_t len) 431251881Speter{ 432251881Speter SVN_ERR(svn_stream_write(stream, buf, &len)); 433251881Speter /* (No need to check LEN, as a short write always produces an error.) */ 434251881Speter return SVN_NO_ERROR; 435251881Speter} 436251881Speter 437251881Speter 438251881Speter/* Perform the substitution of VALUE into keyword string BUF (with len 439251881Speter *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating 440251881Speter *LEN to the new size of the substituted result. Return TRUE if all 441251881Speter goes well, FALSE otherwise. If VALUE is NULL, keyword will be 442251881Speter contracted, else it will be expanded. */ 443251881Speterstatic svn_boolean_t 444251881Spetertranslate_keyword_subst(char *buf, 445251881Speter apr_size_t *len, 446251881Speter const char *keyword, 447251881Speter apr_size_t keyword_len, 448251881Speter const svn_string_t *value) 449251881Speter{ 450251881Speter char *buf_ptr; 451251881Speter 452251881Speter /* Make sure we gotz good stuffs. */ 453251881Speter assert(*len <= SVN_KEYWORD_MAX_LEN); 454251881Speter assert((buf[0] == '$') && (buf[*len - 1] == '$')); 455251881Speter 456251881Speter /* Need at least a keyword and two $'s. */ 457251881Speter if (*len < keyword_len + 2) 458251881Speter return FALSE; 459251881Speter 460251881Speter /* Need at least space for two $'s, two spaces and a colon, and that 461251881Speter leaves zero space for the value itself. */ 462251881Speter if (keyword_len > SVN_KEYWORD_MAX_LEN - 5) 463251881Speter return FALSE; 464251881Speter 465251881Speter /* The keyword needs to match what we're looking for. */ 466251881Speter if (strncmp(buf + 1, keyword, keyword_len)) 467251881Speter return FALSE; 468251881Speter 469251881Speter buf_ptr = buf + 1 + keyword_len; 470251881Speter 471251881Speter /* Check for fixed-length expansion. 472251881Speter * The format of fixed length keyword and its data is 473251881Speter * Unexpanded keyword: "$keyword:: $" 474251881Speter * Expanded keyword: "$keyword:: value $" 475251881Speter * Expanded kw with filling: "$keyword:: value $" 476251881Speter * Truncated keyword: "$keyword:: longval#$" 477251881Speter */ 478251881Speter if ((buf_ptr[0] == ':') /* first char after keyword is ':' */ 479251881Speter && (buf_ptr[1] == ':') /* second char after keyword is ':' */ 480251881Speter && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */ 481251881Speter && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */ 482251881Speter || (buf[*len - 2] == '#')) /* .. or has '#' for next to last 483251881Speter character */ 484251881Speter && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */ 485251881Speter { 486251881Speter /* This is fixed length keyword, so *len remains unchanged */ 487251881Speter apr_size_t max_value_len = *len - (6 + keyword_len); 488251881Speter 489251881Speter if (! value) 490251881Speter { 491251881Speter /* no value, so unexpand */ 492251881Speter buf_ptr += 2; 493251881Speter while (*buf_ptr != '$') 494251881Speter *(buf_ptr++) = ' '; 495251881Speter } 496251881Speter else 497251881Speter { 498251881Speter if (value->len <= max_value_len) 499251881Speter { /* replacement not as long as template, pad with spaces */ 500251881Speter strncpy(buf_ptr + 3, value->data, value->len); 501251881Speter buf_ptr += 3 + value->len; 502251881Speter while (*buf_ptr != '$') 503251881Speter *(buf_ptr++) = ' '; 504251881Speter } 505251881Speter else 506251881Speter { 507251881Speter /* replacement needs truncating */ 508251881Speter strncpy(buf_ptr + 3, value->data, max_value_len); 509251881Speter buf[*len - 2] = '#'; 510251881Speter buf[*len - 1] = '$'; 511251881Speter } 512251881Speter } 513251881Speter return TRUE; 514251881Speter } 515251881Speter 516251881Speter /* Check for unexpanded keyword. */ 517251881Speter else if (buf_ptr[0] == '$') /* "$keyword$" */ 518251881Speter { 519251881Speter /* unexpanded... */ 520251881Speter if (value) 521251881Speter { 522251881Speter /* ...so expand. */ 523251881Speter buf_ptr[0] = ':'; 524251881Speter buf_ptr[1] = ' '; 525251881Speter if (value->len) 526251881Speter { 527251881Speter apr_size_t vallen = value->len; 528251881Speter 529251881Speter /* "$keyword: value $" */ 530251881Speter if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) 531251881Speter vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; 532251881Speter strncpy(buf_ptr + 2, value->data, vallen); 533251881Speter buf_ptr[2 + vallen] = ' '; 534251881Speter buf_ptr[2 + vallen + 1] = '$'; 535251881Speter *len = 5 + keyword_len + vallen; 536251881Speter } 537251881Speter else 538251881Speter { 539251881Speter /* "$keyword: $" */ 540251881Speter buf_ptr[2] = '$'; 541251881Speter *len = 4 + keyword_len; 542251881Speter } 543251881Speter } 544251881Speter else 545251881Speter { 546251881Speter /* ...but do nothing. */ 547251881Speter } 548251881Speter return TRUE; 549251881Speter } 550251881Speter 551251881Speter /* Check for expanded keyword. */ 552251881Speter else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */ 553251881Speter && (buf_ptr[0] == ':') /* first char after keyword is ':' */ 554251881Speter && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */ 555251881Speter && (buf[*len - 2] == ' ')) 556251881Speter || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */ 557251881Speter && (buf_ptr[0] == ':') /* first char after keyword is ':' */ 558251881Speter && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */ 559251881Speter { 560251881Speter /* expanded... */ 561251881Speter if (! value) 562251881Speter { 563251881Speter /* ...so unexpand. */ 564251881Speter buf_ptr[0] = '$'; 565251881Speter *len = 2 + keyword_len; 566251881Speter } 567251881Speter else 568251881Speter { 569251881Speter /* ...so re-expand. */ 570251881Speter buf_ptr[0] = ':'; 571251881Speter buf_ptr[1] = ' '; 572251881Speter if (value->len) 573251881Speter { 574251881Speter apr_size_t vallen = value->len; 575251881Speter 576251881Speter /* "$keyword: value $" */ 577251881Speter if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) 578251881Speter vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; 579251881Speter strncpy(buf_ptr + 2, value->data, vallen); 580251881Speter buf_ptr[2 + vallen] = ' '; 581251881Speter buf_ptr[2 + vallen + 1] = '$'; 582251881Speter *len = 5 + keyword_len + vallen; 583251881Speter } 584251881Speter else 585251881Speter { 586251881Speter /* "$keyword: $" */ 587251881Speter buf_ptr[2] = '$'; 588251881Speter *len = 4 + keyword_len; 589251881Speter } 590251881Speter } 591251881Speter return TRUE; 592251881Speter } 593251881Speter 594251881Speter return FALSE; 595251881Speter} 596251881Speter 597251881Speter/* Parse BUF (whose length is LEN, and which starts and ends with '$'), 598251881Speter trying to match one of the keyword names in KEYWORDS. If such a 599251881Speter keyword is found, update *KEYWORD_NAME with the keyword name and 600251881Speter return TRUE. */ 601251881Speterstatic svn_boolean_t 602251881Spetermatch_keyword(char *buf, 603251881Speter apr_size_t len, 604251881Speter char *keyword_name, 605251881Speter apr_hash_t *keywords) 606251881Speter{ 607251881Speter apr_size_t i; 608251881Speter 609251881Speter /* Early return for ignored keywords */ 610251881Speter if (! keywords) 611251881Speter return FALSE; 612251881Speter 613251881Speter /* Extract the name of the keyword */ 614251881Speter for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++) 615251881Speter keyword_name[i] = buf[i + 1]; 616251881Speter keyword_name[i] = '\0'; 617251881Speter 618251881Speter return svn_hash_gets(keywords, keyword_name) != NULL; 619251881Speter} 620251881Speter 621251881Speter/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN): 622251881Speter optionally perform the substitution in place, update *LEN with 623251881Speter the new length of the translated keyword string, and return TRUE. 624251881Speter If this buffer doesn't contain a known keyword pattern, leave BUF 625251881Speter and *LEN untouched and return FALSE. 626251881Speter 627251881Speter See the docstring for svn_subst_copy_and_translate for how the 628251881Speter EXPAND and KEYWORDS parameters work. 629251881Speter 630251881Speter NOTE: It is assumed that BUF has been allocated to be at least 631251881Speter SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less 632251881Speter than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions 633251881Speter which would result in a keyword string which is greater than 634251881Speter SVN_KEYWORD_MAX_LEN will have their values truncated in such a way 635251881Speter that the resultant keyword string is still valid (begins with 636251881Speter "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */ 637251881Speterstatic svn_boolean_t 638251881Spetertranslate_keyword(char *buf, 639251881Speter apr_size_t *len, 640251881Speter const char *keyword_name, 641251881Speter svn_boolean_t expand, 642251881Speter apr_hash_t *keywords) 643251881Speter{ 644251881Speter const svn_string_t *value; 645251881Speter 646251881Speter /* Make sure we gotz good stuffs. */ 647251881Speter assert(*len <= SVN_KEYWORD_MAX_LEN); 648251881Speter assert((buf[0] == '$') && (buf[*len - 1] == '$')); 649251881Speter 650251881Speter /* Early return for ignored keywords */ 651251881Speter if (! keywords) 652251881Speter return FALSE; 653251881Speter 654251881Speter value = svn_hash_gets(keywords, keyword_name); 655251881Speter 656251881Speter if (value) 657251881Speter { 658251881Speter return translate_keyword_subst(buf, len, 659251881Speter keyword_name, strlen(keyword_name), 660251881Speter expand ? value : NULL); 661251881Speter } 662251881Speter 663251881Speter return FALSE; 664251881Speter} 665251881Speter 666251881Speter/* A boolean expression that evaluates to true if the first STR_LEN characters 667251881Speter of the string STR are one of the end-of-line strings LF, CR, or CRLF; 668251881Speter to false otherwise. */ 669251881Speter#define STRING_IS_EOL(str, str_len) \ 670251881Speter (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \ 671251881Speter ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r'))) 672251881Speter 673251881Speter/* A boolean expression that evaluates to true if the end-of-line string EOL1, 674251881Speter having length EOL1_LEN, and the end-of-line string EOL2, having length 675251881Speter EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the 676251881Speter set {"\n", "\r", "\r\n"}; to false otherwise. 677251881Speter 678251881Speter Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if 679251881Speter EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course 680251881Speter different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both 681251881Speter "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1. 682251881Speter We need only check the one character for equality to determine whether 683251881Speter EOL1 and EOL2 are different in that case. */ 684251881Speter#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \ 685251881Speter (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2))) 686251881Speter 687251881Speter 688251881Speter/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to 689251881Speter the newline string EOL_STR (of length EOL_STR_LEN), writing the 690251881Speter result (which is always EOL_STR) to the stream DST. 691251881Speter 692251881Speter This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n". 693251881Speter 694251881Speter Also check for consistency of the source newline strings across 695251881Speter multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache 696251881Speter of the first newline found. If the current newline is not the same 697251881Speter as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE, 698251881Speter ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL 699251881Speter error. If *SRC_FORMAT_LEN is 0, assume we are examining the first 700251881Speter newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to 701251881Speter use for later consistency checks. 702251881Speter 703251881Speter If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the 704251881Speter newline string that was written (EOL_STR) is not the same as the newline 705251881Speter string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL 706251881Speter untouched. 707251881Speter 708251881Speter Note: all parameters are required even if REPAIR is TRUE. 709251881Speter ### We could require that REPAIR must not change across a sequence of 710251881Speter calls, and could then optimize by not using SRC_FORMAT at all if 711251881Speter REPAIR is TRUE. 712251881Speter*/ 713251881Speterstatic svn_error_t * 714251881Spetertranslate_newline(const char *eol_str, 715251881Speter apr_size_t eol_str_len, 716251881Speter char *src_format, 717251881Speter apr_size_t *src_format_len, 718251881Speter const char *newline_buf, 719251881Speter apr_size_t newline_len, 720251881Speter svn_stream_t *dst, 721251881Speter svn_boolean_t *translated_eol, 722251881Speter svn_boolean_t repair) 723251881Speter{ 724251881Speter SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len)); 725251881Speter 726251881Speter /* If we've seen a newline before, compare it with our cache to 727251881Speter check for consistency, else cache it for future comparisons. */ 728251881Speter if (*src_format_len) 729251881Speter { 730251881Speter /* Comparing with cache. If we are inconsistent and 731251881Speter we are NOT repairing the file, generate an error! */ 732251881Speter if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len, 733251881Speter newline_buf, newline_len)) 734251881Speter return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL); 735251881Speter } 736251881Speter else 737251881Speter { 738251881Speter /* This is our first line ending, so cache it before 739251881Speter handling it. */ 740251881Speter strncpy(src_format, newline_buf, newline_len); 741251881Speter *src_format_len = newline_len; 742251881Speter } 743251881Speter 744251881Speter /* Write the desired newline */ 745251881Speter SVN_ERR(translate_write(dst, eol_str, eol_str_len)); 746251881Speter 747251881Speter /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS() 748251881Speter * because EOL_STR may not be a valid EOL sequence. */ 749251881Speter if (translated_eol != NULL && 750251881Speter (eol_str_len != newline_len || 751251881Speter memcmp(eol_str, newline_buf, eol_str_len) != 0)) 752251881Speter *translated_eol = TRUE; 753251881Speter 754251881Speter return SVN_NO_ERROR; 755251881Speter} 756251881Speter 757251881Speter 758251881Speter 759251881Speter/*** Public interfaces. ***/ 760251881Speter 761251881Spetersvn_boolean_t 762251881Spetersvn_subst_keywords_differ(const svn_subst_keywords_t *a, 763251881Speter const svn_subst_keywords_t *b, 764251881Speter svn_boolean_t compare_values) 765251881Speter{ 766251881Speter if (((a == NULL) && (b == NULL)) /* no A or B */ 767251881Speter /* no A, and B has no contents */ 768251881Speter || ((a == NULL) 769251881Speter && (b->revision == NULL) 770251881Speter && (b->date == NULL) 771251881Speter && (b->author == NULL) 772251881Speter && (b->url == NULL)) 773251881Speter /* no B, and A has no contents */ 774251881Speter || ((b == NULL) && (a->revision == NULL) 775251881Speter && (a->date == NULL) 776251881Speter && (a->author == NULL) 777251881Speter && (a->url == NULL)) 778251881Speter /* neither A nor B has any contents */ 779251881Speter || ((a != NULL) && (b != NULL) 780251881Speter && (b->revision == NULL) 781251881Speter && (b->date == NULL) 782251881Speter && (b->author == NULL) 783251881Speter && (b->url == NULL) 784251881Speter && (a->revision == NULL) 785251881Speter && (a->date == NULL) 786251881Speter && (a->author == NULL) 787251881Speter && (a->url == NULL))) 788251881Speter { 789251881Speter return FALSE; 790251881Speter } 791251881Speter else if ((a == NULL) || (b == NULL)) 792251881Speter return TRUE; 793251881Speter 794251881Speter /* Else both A and B have some keywords. */ 795251881Speter 796251881Speter if ((! a->revision) != (! b->revision)) 797251881Speter return TRUE; 798251881Speter else if ((compare_values && (a->revision != NULL)) 799251881Speter && (strcmp(a->revision->data, b->revision->data) != 0)) 800251881Speter return TRUE; 801251881Speter 802251881Speter if ((! a->date) != (! b->date)) 803251881Speter return TRUE; 804251881Speter else if ((compare_values && (a->date != NULL)) 805251881Speter && (strcmp(a->date->data, b->date->data) != 0)) 806251881Speter return TRUE; 807251881Speter 808251881Speter if ((! a->author) != (! b->author)) 809251881Speter return TRUE; 810251881Speter else if ((compare_values && (a->author != NULL)) 811251881Speter && (strcmp(a->author->data, b->author->data) != 0)) 812251881Speter return TRUE; 813251881Speter 814251881Speter if ((! a->url) != (! b->url)) 815251881Speter return TRUE; 816251881Speter else if ((compare_values && (a->url != NULL)) 817251881Speter && (strcmp(a->url->data, b->url->data) != 0)) 818251881Speter return TRUE; 819251881Speter 820251881Speter /* Else we never found a difference, so they must be the same. */ 821251881Speter 822251881Speter return FALSE; 823251881Speter} 824251881Speter 825251881Spetersvn_boolean_t 826251881Spetersvn_subst_keywords_differ2(apr_hash_t *a, 827251881Speter apr_hash_t *b, 828251881Speter svn_boolean_t compare_values, 829251881Speter apr_pool_t *pool) 830251881Speter{ 831251881Speter apr_hash_index_t *hi; 832251881Speter unsigned int a_count, b_count; 833251881Speter 834251881Speter /* An empty hash is logically equal to a NULL, 835251881Speter * as far as this API is concerned. */ 836251881Speter a_count = (a == NULL) ? 0 : apr_hash_count(a); 837251881Speter b_count = (b == NULL) ? 0 : apr_hash_count(b); 838251881Speter 839251881Speter if (a_count != b_count) 840251881Speter return TRUE; 841251881Speter 842251881Speter if (a_count == 0) 843251881Speter return FALSE; 844251881Speter 845251881Speter /* The hashes are both non-NULL, and have the same number of items. 846251881Speter * We must check that every item of A is present in B. */ 847251881Speter for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi)) 848251881Speter { 849251881Speter const void *key; 850251881Speter apr_ssize_t klen; 851251881Speter void *void_a_val; 852251881Speter svn_string_t *a_val, *b_val; 853251881Speter 854251881Speter apr_hash_this(hi, &key, &klen, &void_a_val); 855251881Speter a_val = void_a_val; 856251881Speter b_val = apr_hash_get(b, key, klen); 857251881Speter 858251881Speter if (!b_val || (compare_values && !svn_string_compare(a_val, b_val))) 859251881Speter return TRUE; 860251881Speter } 861251881Speter 862251881Speter return FALSE; 863251881Speter} 864251881Speter 865251881Speter 866251881Speter/* Baton for translate_chunk() to store its state in. */ 867251881Speterstruct translation_baton 868251881Speter{ 869251881Speter const char *eol_str; 870251881Speter svn_boolean_t *translated_eol; 871251881Speter svn_boolean_t repair; 872251881Speter apr_hash_t *keywords; 873251881Speter svn_boolean_t expand; 874251881Speter 875251881Speter /* 'short boolean' array that encodes what character values 876251881Speter may trigger a translation action, hence are 'interesting' */ 877251881Speter char interesting[256]; 878251881Speter 879251881Speter /* Length of the string EOL_STR points to. */ 880251881Speter apr_size_t eol_str_len; 881251881Speter 882251881Speter /* Buffer to cache any newline state between translation chunks */ 883251881Speter char newline_buf[2]; 884251881Speter 885251881Speter /* Offset (within newline_buf) of the first *unused* character */ 886251881Speter apr_size_t newline_off; 887251881Speter 888251881Speter /* Buffer to cache keyword-parsing state between translation chunks */ 889251881Speter char keyword_buf[SVN_KEYWORD_MAX_LEN]; 890251881Speter 891251881Speter /* Offset (within keyword-buf) to the first *unused* character */ 892251881Speter apr_size_t keyword_off; 893251881Speter 894251881Speter /* EOL style used in the chunk-source */ 895251881Speter char src_format[2]; 896251881Speter 897251881Speter /* Length of the EOL style string found in the chunk-source, 898251881Speter or zero if none encountered yet */ 899251881Speter apr_size_t src_format_len; 900251881Speter 901251881Speter /* If this is svn_tristate_false, translate_newline() will be called 902251881Speter for every newline in the file */ 903251881Speter svn_tristate_t nl_translation_skippable; 904251881Speter}; 905251881Speter 906251881Speter 907251881Speter/* Allocate a baton for use with translate_chunk() in POOL and 908251881Speter * initialize it for the first iteration. 909251881Speter * 910251881Speter * The caller must assure that EOL_STR and KEYWORDS at least 911251881Speter * have the same life time as that of POOL. 912251881Speter */ 913251881Speterstatic struct translation_baton * 914251881Spetercreate_translation_baton(const char *eol_str, 915251881Speter svn_boolean_t *translated_eol, 916251881Speter svn_boolean_t repair, 917251881Speter apr_hash_t *keywords, 918251881Speter svn_boolean_t expand, 919251881Speter apr_pool_t *pool) 920251881Speter{ 921251881Speter struct translation_baton *b = apr_palloc(pool, sizeof(*b)); 922251881Speter 923251881Speter /* For efficiency, convert an empty set of keywords to NULL. */ 924251881Speter if (keywords && (apr_hash_count(keywords) == 0)) 925251881Speter keywords = NULL; 926251881Speter 927251881Speter b->eol_str = eol_str; 928251881Speter b->eol_str_len = eol_str ? strlen(eol_str) : 0; 929251881Speter b->translated_eol = translated_eol; 930251881Speter b->repair = repair; 931251881Speter b->keywords = keywords; 932251881Speter b->expand = expand; 933251881Speter b->newline_off = 0; 934251881Speter b->keyword_off = 0; 935251881Speter b->src_format_len = 0; 936251881Speter b->nl_translation_skippable = svn_tristate_unknown; 937251881Speter 938251881Speter /* Most characters don't start translation actions. 939251881Speter * Mark those that do depending on the parameters we got. */ 940251881Speter memset(b->interesting, FALSE, sizeof(b->interesting)); 941251881Speter if (keywords) 942251881Speter b->interesting['$'] = TRUE; 943251881Speter if (eol_str) 944251881Speter { 945251881Speter b->interesting['\r'] = TRUE; 946251881Speter b->interesting['\n'] = TRUE; 947251881Speter } 948251881Speter 949251881Speter return b; 950251881Speter} 951251881Speter 952251881Speter/* Return TRUE if the EOL starting at BUF matches the eol_str member of B. 953251881Speter * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like 954251881Speter * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is 955251881Speter * more efficient to handle that special case implicitly in the calling code 956251881Speter * by exiting the quick scan loop. 957251881Speter * The caller must ensure that buf[0] and buf[1] refer to valid memory 958251881Speter * locations. 959251881Speter */ 960251881Speterstatic APR_INLINE svn_boolean_t 961251881Spetereol_unchanged(struct translation_baton *b, 962251881Speter const char *buf) 963251881Speter{ 964251881Speter /* If the first byte doesn't match, the whole EOL won't. 965251881Speter * This does also handle the (certainly invalid) case that 966251881Speter * eol_str would be an empty string. 967251881Speter */ 968251881Speter if (buf[0] != b->eol_str[0]) 969251881Speter return FALSE; 970251881Speter 971251881Speter /* two-char EOLs must be a full match */ 972251881Speter if (b->eol_str_len == 2) 973251881Speter return buf[1] == b->eol_str[1]; 974251881Speter 975251881Speter /* The first char matches the required 1-byte EOL. 976251881Speter * But maybe, buf[] contains a 2-byte EOL? 977251881Speter * In that case, the second byte will be interesting 978251881Speter * and not be another EOL of its own. 979251881Speter */ 980251881Speter return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1]; 981251881Speter} 982251881Speter 983251881Speter 984251881Speter/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN 985251881Speter * according to the settings and state stored in baton B. 986251881Speter * 987251881Speter * Write output to stream DST. 988251881Speter * 989251881Speter * To finish a series of chunk translations, flush all buffers by calling 990251881Speter * this routine with a NULL value for BUF. 991251881Speter * 992251881Speter * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if 993251881Speter * an end-of-line sequence was changed, otherwise leave it untouched. 994251881Speter * 995251881Speter * Use POOL for temporary allocations. 996251881Speter */ 997251881Speterstatic svn_error_t * 998251881Spetertranslate_chunk(svn_stream_t *dst, 999251881Speter struct translation_baton *b, 1000251881Speter const char *buf, 1001251881Speter apr_size_t buflen, 1002251881Speter apr_pool_t *pool) 1003251881Speter{ 1004251881Speter const char *p; 1005251881Speter apr_size_t len; 1006251881Speter 1007251881Speter if (buf) 1008251881Speter { 1009251881Speter /* precalculate some oft-used values */ 1010251881Speter const char *end = buf + buflen; 1011251881Speter const char *interesting = b->interesting; 1012251881Speter apr_size_t next_sign_off = 0; 1013251881Speter 1014251881Speter /* At the beginning of this loop, assume that we might be in an 1015251881Speter * interesting state, i.e. with data in the newline or keyword 1016251881Speter * buffer. First try to get to the boring state so we can copy 1017251881Speter * a run of boring characters; then try to get back to the 1018251881Speter * interesting state by processing an interesting character, 1019251881Speter * and repeat. */ 1020251881Speter for (p = buf; p < end;) 1021251881Speter { 1022251881Speter /* Try to get to the boring state, if necessary. */ 1023251881Speter if (b->newline_off) 1024251881Speter { 1025251881Speter if (*p == '\n') 1026251881Speter b->newline_buf[b->newline_off++] = *p++; 1027251881Speter 1028251881Speter SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1029251881Speter b->src_format, 1030251881Speter &b->src_format_len, b->newline_buf, 1031251881Speter b->newline_off, dst, b->translated_eol, 1032251881Speter b->repair)); 1033251881Speter 1034251881Speter b->newline_off = 0; 1035251881Speter } 1036251881Speter else if (b->keyword_off && *p == '$') 1037251881Speter { 1038251881Speter svn_boolean_t keyword_matches; 1039251881Speter char keyword_name[SVN_KEYWORD_MAX_LEN + 1]; 1040251881Speter 1041251881Speter /* If keyword is matched, but not correctly translated, try to 1042251881Speter * look for the next ending '$'. */ 1043251881Speter b->keyword_buf[b->keyword_off++] = *p++; 1044251881Speter keyword_matches = match_keyword(b->keyword_buf, b->keyword_off, 1045251881Speter keyword_name, b->keywords); 1046251881Speter if (!keyword_matches) 1047251881Speter { 1048251881Speter /* reuse the ending '$' */ 1049251881Speter p--; 1050251881Speter b->keyword_off--; 1051251881Speter } 1052251881Speter 1053251881Speter if (!keyword_matches || 1054251881Speter translate_keyword(b->keyword_buf, &b->keyword_off, 1055251881Speter keyword_name, b->expand, b->keywords) || 1056251881Speter b->keyword_off >= SVN_KEYWORD_MAX_LEN) 1057251881Speter { 1058251881Speter /* write out non-matching text or translated keyword */ 1059251881Speter SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1060251881Speter 1061251881Speter next_sign_off = 0; 1062251881Speter b->keyword_off = 0; 1063251881Speter } 1064251881Speter else 1065251881Speter { 1066251881Speter if (next_sign_off == 0) 1067251881Speter next_sign_off = b->keyword_off - 1; 1068251881Speter 1069251881Speter continue; 1070251881Speter } 1071251881Speter } 1072251881Speter else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1 1073251881Speter || (b->keyword_off && (*p == '\r' || *p == '\n'))) 1074251881Speter { 1075251881Speter if (next_sign_off > 0) 1076251881Speter { 1077251881Speter /* rolling back, continue with next '$' in keyword_buf */ 1078251881Speter p -= (b->keyword_off - next_sign_off); 1079251881Speter b->keyword_off = next_sign_off; 1080251881Speter next_sign_off = 0; 1081251881Speter } 1082251881Speter /* No closing '$' found; flush the keyword buffer. */ 1083251881Speter SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1084251881Speter 1085251881Speter b->keyword_off = 0; 1086251881Speter } 1087251881Speter else if (b->keyword_off) 1088251881Speter { 1089251881Speter b->keyword_buf[b->keyword_off++] = *p++; 1090251881Speter continue; 1091251881Speter } 1092251881Speter 1093251881Speter /* translate_newline will modify the baton for src_format_len==0 1094251881Speter or may return an error if b->repair is FALSE. In all other 1095251881Speter cases, we can skip the newline translation as long as source 1096251881Speter EOL format and actual EOL format match. If there is a 1097251881Speter mismatch, translate_newline will be called regardless of 1098251881Speter nl_translation_skippable. 1099251881Speter */ 1100251881Speter if (b->nl_translation_skippable == svn_tristate_unknown && 1101251881Speter b->src_format_len > 0) 1102251881Speter { 1103251881Speter /* test whether translate_newline may return an error */ 1104251881Speter if (b->eol_str_len == b->src_format_len && 1105251881Speter strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0) 1106251881Speter b->nl_translation_skippable = svn_tristate_true; 1107251881Speter else if (b->repair) 1108251881Speter b->nl_translation_skippable = svn_tristate_true; 1109251881Speter else 1110251881Speter b->nl_translation_skippable = svn_tristate_false; 1111251881Speter } 1112251881Speter 1113251881Speter /* We're in the boring state; look for interesting characters. 1114251881Speter Offset len such that it will become 0 in the first iteration. 1115251881Speter */ 1116251881Speter len = 0 - b->eol_str_len; 1117251881Speter 1118251881Speter /* Look for the next EOL (or $) that actually needs translation. 1119251881Speter Stop there or at EOF, whichever is encountered first. 1120251881Speter */ 1121251881Speter do 1122251881Speter { 1123251881Speter /* skip current EOL */ 1124251881Speter len += b->eol_str_len; 1125251881Speter 1126289180Speter if (b->keywords) 1127251881Speter { 1128289180Speter /* Check 4 bytes at once to allow for efficient pipelining 1129289180Speter and to reduce loop condition overhead. */ 1130362181Sdim while ((end - p) >= (len + 4)) 1131289180Speter { 1132289180Speter if (interesting[(unsigned char)p[len]] 1133289180Speter || interesting[(unsigned char)p[len+1]] 1134289180Speter || interesting[(unsigned char)p[len+2]] 1135289180Speter || interesting[(unsigned char)p[len+3]]) 1136289180Speter break; 1137251881Speter 1138289180Speter len += 4; 1139289180Speter } 1140289180Speter 1141289180Speter /* Found an interesting char or EOF in the next 4 bytes. 1142289180Speter Find its exact position. */ 1143289180Speter while ((p + len) < end 1144289180Speter && !interesting[(unsigned char)p[len]]) 1145289180Speter ++len; 1146251881Speter } 1147289180Speter else 1148289180Speter { 1149289180Speter /* use our optimized sub-routine to find the next EOL */ 1150289180Speter const char *start = p + len; 1151289180Speter const char *eol 1152289180Speter = svn_eol__find_eol_start((char *)start, end - start); 1153251881Speter 1154289180Speter /* EOL will be NULL if we did not find a line ending */ 1155289180Speter len += (eol ? eol : end) - start; 1156289180Speter } 1157251881Speter } 1158251881Speter while (b->nl_translation_skippable == 1159251881Speter svn_tristate_true && /* can potentially skip EOLs */ 1160362181Sdim (end - p) > (len + 2) && /* not too close to EOF */ 1161289180Speter eol_unchanged(b, p + len)); /* EOL format already ok */ 1162251881Speter 1163251881Speter while ((p + len) < end && !interesting[(unsigned char)p[len]]) 1164251881Speter len++; 1165251881Speter 1166251881Speter if (len) 1167251881Speter { 1168251881Speter SVN_ERR(translate_write(dst, p, len)); 1169251881Speter p += len; 1170251881Speter } 1171251881Speter 1172251881Speter /* Set up state according to the interesting character, if any. */ 1173251881Speter if (p < end) 1174251881Speter { 1175251881Speter switch (*p) 1176251881Speter { 1177251881Speter case '$': 1178251881Speter b->keyword_buf[b->keyword_off++] = *p++; 1179251881Speter break; 1180251881Speter case '\r': 1181251881Speter b->newline_buf[b->newline_off++] = *p++; 1182251881Speter break; 1183251881Speter case '\n': 1184251881Speter b->newline_buf[b->newline_off++] = *p++; 1185251881Speter 1186251881Speter SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1187251881Speter b->src_format, 1188251881Speter &b->src_format_len, 1189251881Speter b->newline_buf, 1190251881Speter b->newline_off, dst, 1191251881Speter b->translated_eol, b->repair)); 1192251881Speter 1193251881Speter b->newline_off = 0; 1194251881Speter break; 1195251881Speter 1196251881Speter } 1197251881Speter } 1198251881Speter } 1199251881Speter } 1200251881Speter else 1201251881Speter { 1202251881Speter if (b->newline_off) 1203251881Speter { 1204251881Speter SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1205251881Speter b->src_format, &b->src_format_len, 1206251881Speter b->newline_buf, b->newline_off, 1207251881Speter dst, b->translated_eol, b->repair)); 1208251881Speter b->newline_off = 0; 1209251881Speter } 1210251881Speter 1211251881Speter if (b->keyword_off) 1212251881Speter { 1213251881Speter SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1214251881Speter b->keyword_off = 0; 1215251881Speter } 1216251881Speter } 1217251881Speter 1218251881Speter return SVN_NO_ERROR; 1219251881Speter} 1220251881Speter 1221251881Speter/* Baton for use with translated stream callbacks. */ 1222251881Speterstruct translated_stream_baton 1223251881Speter{ 1224251881Speter /* Stream to take input from (before translation) on read 1225251881Speter /write output to (after translation) on write. */ 1226251881Speter svn_stream_t *stream; 1227251881Speter 1228251881Speter /* Input/Output translation batons to make them separate chunk streams. */ 1229251881Speter struct translation_baton *in_baton, *out_baton; 1230251881Speter 1231251881Speter /* Remembers whether any write operations have taken place; 1232251881Speter if so, we need to flush the output chunk stream. */ 1233251881Speter svn_boolean_t written; 1234251881Speter 1235251881Speter /* Buffer to hold translated read data. */ 1236251881Speter svn_stringbuf_t *readbuf; 1237251881Speter 1238251881Speter /* Offset of the first non-read character in readbuf. */ 1239251881Speter apr_size_t readbuf_off; 1240251881Speter 1241251881Speter /* Buffer to hold read data 1242251881Speter between svn_stream_read() and translate_chunk(). */ 1243251881Speter char *buf; 1244251881Speter#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1) 1245251881Speter 1246251881Speter /* Pool for callback iterations */ 1247251881Speter apr_pool_t *iterpool; 1248251881Speter}; 1249251881Speter 1250251881Speter 1251251881Speter/* Implements svn_read_fn_t. */ 1252251881Speterstatic svn_error_t * 1253251881Spetertranslated_stream_read(void *baton, 1254251881Speter char *buffer, 1255251881Speter apr_size_t *len) 1256251881Speter{ 1257251881Speter struct translated_stream_baton *b = baton; 1258251881Speter apr_size_t readlen = SVN__STREAM_CHUNK_SIZE; 1259251881Speter apr_size_t unsatisfied = *len; 1260251881Speter apr_size_t off = 0; 1261251881Speter 1262251881Speter /* Optimization for a frequent special case. The configuration parser (and 1263251881Speter a few others) reads the stream one byte at a time. All the memcpy, pool 1264251881Speter clearing etc. imposes a huge overhead in that case. In most cases, we 1265251881Speter can just take that single byte directly from the read buffer. 1266251881Speter 1267251881Speter Since *len > 1 requires lots of code to be run anyways, we can afford 1268251881Speter the extra overhead of checking for *len == 1. 1269251881Speter 1270251881Speter See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>. 1271251881Speter */ 1272251881Speter if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len) 1273251881Speter { 1274251881Speter /* Just take it from the read buffer */ 1275251881Speter *buffer = b->readbuf->data[b->readbuf_off++]; 1276251881Speter 1277251881Speter return SVN_NO_ERROR; 1278251881Speter } 1279251881Speter 1280251881Speter /* Standard code path. */ 1281251881Speter while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0) 1282251881Speter { 1283251881Speter apr_size_t to_copy; 1284251881Speter apr_size_t buffer_remainder; 1285251881Speter 1286251881Speter svn_pool_clear(b->iterpool); 1287251881Speter /* fill read buffer, if necessary */ 1288251881Speter if (! (b->readbuf_off < b->readbuf->len)) 1289251881Speter { 1290251881Speter svn_stream_t *buf_stream; 1291251881Speter 1292251881Speter svn_stringbuf_setempty(b->readbuf); 1293251881Speter b->readbuf_off = 0; 1294289180Speter SVN_ERR(svn_stream_read_full(b->stream, b->buf, &readlen)); 1295251881Speter buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool); 1296251881Speter 1297251881Speter SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf, 1298251881Speter readlen, b->iterpool)); 1299251881Speter 1300251881Speter if (readlen != SVN__STREAM_CHUNK_SIZE) 1301251881Speter SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0, 1302251881Speter b->iterpool)); 1303251881Speter 1304251881Speter SVN_ERR(svn_stream_close(buf_stream)); 1305251881Speter } 1306251881Speter 1307251881Speter /* Satisfy from the read buffer */ 1308251881Speter buffer_remainder = b->readbuf->len - b->readbuf_off; 1309251881Speter to_copy = (buffer_remainder > unsatisfied) 1310251881Speter ? unsatisfied : buffer_remainder; 1311251881Speter memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy); 1312251881Speter off += to_copy; 1313251881Speter b->readbuf_off += to_copy; 1314251881Speter unsatisfied -= to_copy; 1315251881Speter } 1316251881Speter 1317251881Speter *len -= unsatisfied; 1318251881Speter 1319251881Speter return SVN_NO_ERROR; 1320251881Speter} 1321251881Speter 1322251881Speter/* Implements svn_write_fn_t. */ 1323251881Speterstatic svn_error_t * 1324251881Spetertranslated_stream_write(void *baton, 1325251881Speter const char *buffer, 1326251881Speter apr_size_t *len) 1327251881Speter{ 1328251881Speter struct translated_stream_baton *b = baton; 1329251881Speter svn_pool_clear(b->iterpool); 1330251881Speter 1331251881Speter b->written = TRUE; 1332251881Speter return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool); 1333251881Speter} 1334251881Speter 1335251881Speter/* Implements svn_close_fn_t. */ 1336251881Speterstatic svn_error_t * 1337251881Spetertranslated_stream_close(void *baton) 1338251881Speter{ 1339251881Speter struct translated_stream_baton *b = baton; 1340251881Speter svn_error_t *err = NULL; 1341251881Speter 1342251881Speter if (b->written) 1343251881Speter err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool); 1344251881Speter 1345251881Speter err = svn_error_compose_create(err, svn_stream_close(b->stream)); 1346251881Speter 1347251881Speter svn_pool_destroy(b->iterpool); 1348251881Speter 1349251881Speter return svn_error_trace(err); 1350251881Speter} 1351251881Speter 1352251881Speter 1353251881Speter/* svn_stream_mark_t for translation streams. */ 1354251881Spetertypedef struct mark_translated_t 1355251881Speter{ 1356251881Speter /* Saved translation state. */ 1357251881Speter struct translated_stream_baton saved_baton; 1358251881Speter 1359251881Speter /* Mark set on the underlying stream. */ 1360251881Speter svn_stream_mark_t *mark; 1361251881Speter} mark_translated_t; 1362251881Speter 1363251881Speter/* Implements svn_stream_mark_fn_t. */ 1364251881Speterstatic svn_error_t * 1365251881Spetertranslated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) 1366251881Speter{ 1367251881Speter mark_translated_t *mt; 1368251881Speter struct translated_stream_baton *b = baton; 1369251881Speter 1370251881Speter mt = apr_palloc(pool, sizeof(*mt)); 1371251881Speter SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool)); 1372251881Speter 1373251881Speter /* Save translation state. */ 1374251881Speter mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton, 1375251881Speter sizeof(*mt->saved_baton.in_baton)); 1376251881Speter mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton, 1377251881Speter sizeof(*mt->saved_baton.out_baton)); 1378251881Speter mt->saved_baton.written = b->written; 1379251881Speter mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool); 1380251881Speter mt->saved_baton.readbuf_off = b->readbuf_off; 1381251881Speter mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE); 1382251881Speter 1383251881Speter *mark = (svn_stream_mark_t *)mt; 1384251881Speter 1385251881Speter return SVN_NO_ERROR; 1386251881Speter} 1387251881Speter 1388251881Speter/* Implements svn_stream_seek_fn_t. */ 1389251881Speterstatic svn_error_t * 1390251881Spetertranslated_stream_seek(void *baton, const svn_stream_mark_t *mark) 1391251881Speter{ 1392251881Speter struct translated_stream_baton *b = baton; 1393251881Speter 1394251881Speter if (mark != NULL) 1395251881Speter { 1396251881Speter const mark_translated_t *mt = (const mark_translated_t *)mark; 1397251881Speter 1398251881Speter /* Flush output buffer if necessary. */ 1399251881Speter if (b->written) 1400251881Speter SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, 1401251881Speter b->iterpool)); 1402251881Speter 1403251881Speter SVN_ERR(svn_stream_seek(b->stream, mt->mark)); 1404251881Speter 1405251881Speter /* Restore translation state, avoiding new allocations. */ 1406251881Speter *b->in_baton = *mt->saved_baton.in_baton; 1407251881Speter *b->out_baton = *mt->saved_baton.out_baton; 1408251881Speter b->written = mt->saved_baton.written; 1409251881Speter svn_stringbuf_setempty(b->readbuf); 1410251881Speter svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data, 1411251881Speter mt->saved_baton.readbuf->len); 1412251881Speter b->readbuf_off = mt->saved_baton.readbuf_off; 1413251881Speter memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE); 1414251881Speter } 1415251881Speter else 1416251881Speter { 1417251881Speter SVN_ERR(svn_stream_reset(b->stream)); 1418251881Speter 1419251881Speter b->in_baton->newline_off = 0; 1420251881Speter b->in_baton->keyword_off = 0; 1421251881Speter b->in_baton->src_format_len = 0; 1422251881Speter b->out_baton->newline_off = 0; 1423251881Speter b->out_baton->keyword_off = 0; 1424251881Speter b->out_baton->src_format_len = 0; 1425251881Speter 1426251881Speter b->written = FALSE; 1427251881Speter svn_stringbuf_setempty(b->readbuf); 1428251881Speter b->readbuf_off = 0; 1429251881Speter } 1430251881Speter 1431251881Speter return SVN_NO_ERROR; 1432251881Speter} 1433251881Speter 1434251881Spetersvn_error_t * 1435251881Spetersvn_subst_read_specialfile(svn_stream_t **stream, 1436251881Speter const char *path, 1437251881Speter apr_pool_t *result_pool, 1438251881Speter apr_pool_t *scratch_pool) 1439251881Speter{ 1440251881Speter apr_finfo_t finfo; 1441251881Speter svn_string_t *buf; 1442251881Speter 1443251881Speter /* First determine what type of special file we are 1444251881Speter detranslating. */ 1445251881Speter SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, 1446251881Speter scratch_pool)); 1447251881Speter 1448251881Speter switch (finfo.filetype) { 1449251881Speter case APR_REG: 1450251881Speter /* Nothing special to do here, just create stream from the original 1451251881Speter file's contents. */ 1452251881Speter SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool)); 1453251881Speter break; 1454251881Speter 1455251881Speter case APR_LNK: 1456251881Speter /* Determine the destination of the link. */ 1457251881Speter SVN_ERR(svn_io_read_link(&buf, path, scratch_pool)); 1458251881Speter *stream = svn_stream_from_string(svn_string_createf(result_pool, 1459251881Speter "link %s", 1460251881Speter buf->data), 1461251881Speter result_pool); 1462251881Speter break; 1463251881Speter 1464251881Speter default: 1465251881Speter SVN_ERR_MALFUNCTION(); 1466251881Speter } 1467251881Speter 1468251881Speter return SVN_NO_ERROR; 1469251881Speter} 1470251881Speter 1471251881Speter/* Same as svn_subst_stream_translated(), except for the following. 1472251881Speter * 1473251881Speter * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream 1474251881Speter * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed, 1475251881Speter * otherwise leave it untouched. 1476251881Speter */ 1477251881Speterstatic svn_stream_t * 1478251881Speterstream_translated(svn_stream_t *stream, 1479251881Speter const char *eol_str, 1480251881Speter svn_boolean_t *translated_eol, 1481251881Speter svn_boolean_t repair, 1482251881Speter apr_hash_t *keywords, 1483251881Speter svn_boolean_t expand, 1484251881Speter apr_pool_t *result_pool) 1485251881Speter{ 1486251881Speter struct translated_stream_baton *baton 1487251881Speter = apr_palloc(result_pool, sizeof(*baton)); 1488251881Speter svn_stream_t *s = svn_stream_create(baton, result_pool); 1489251881Speter 1490251881Speter /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL 1491251881Speter so they have the same lifetime as the stream. */ 1492251881Speter if (eol_str) 1493251881Speter eol_str = apr_pstrdup(result_pool, eol_str); 1494251881Speter if (keywords) 1495251881Speter { 1496251881Speter if (apr_hash_count(keywords) == 0) 1497251881Speter keywords = NULL; 1498251881Speter else 1499251881Speter { 1500251881Speter /* deep copy the hash to make sure it's allocated in RESULT_POOL */ 1501251881Speter apr_hash_t *copy = apr_hash_make(result_pool); 1502251881Speter apr_hash_index_t *hi; 1503251881Speter apr_pool_t *subpool; 1504251881Speter 1505251881Speter subpool = svn_pool_create(result_pool); 1506251881Speter for (hi = apr_hash_first(subpool, keywords); 1507251881Speter hi; hi = apr_hash_next(hi)) 1508251881Speter { 1509251881Speter const void *key; 1510251881Speter void *val; 1511251881Speter 1512251881Speter apr_hash_this(hi, &key, NULL, &val); 1513251881Speter svn_hash_sets(copy, apr_pstrdup(result_pool, key), 1514251881Speter svn_string_dup(val, result_pool)); 1515251881Speter } 1516251881Speter svn_pool_destroy(subpool); 1517251881Speter 1518251881Speter keywords = copy; 1519251881Speter } 1520251881Speter } 1521251881Speter 1522251881Speter /* Setup the baton fields */ 1523251881Speter baton->stream = stream; 1524251881Speter baton->in_baton 1525251881Speter = create_translation_baton(eol_str, translated_eol, repair, keywords, 1526251881Speter expand, result_pool); 1527251881Speter baton->out_baton 1528251881Speter = create_translation_baton(eol_str, translated_eol, repair, keywords, 1529251881Speter expand, result_pool); 1530251881Speter baton->written = FALSE; 1531251881Speter baton->readbuf = svn_stringbuf_create_empty(result_pool); 1532251881Speter baton->readbuf_off = 0; 1533251881Speter baton->iterpool = svn_pool_create(result_pool); 1534251881Speter baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE); 1535251881Speter 1536251881Speter /* Setup the stream methods */ 1537289180Speter svn_stream_set_read2(s, NULL /* only full read support */, 1538289180Speter translated_stream_read); 1539251881Speter svn_stream_set_write(s, translated_stream_write); 1540251881Speter svn_stream_set_close(s, translated_stream_close); 1541362181Sdim if (svn_stream_supports_mark(stream)) 1542362181Sdim { 1543362181Sdim svn_stream_set_mark(s, translated_stream_mark); 1544362181Sdim svn_stream_set_seek(s, translated_stream_seek); 1545362181Sdim } 1546251881Speter 1547251881Speter return s; 1548251881Speter} 1549251881Speter 1550251881Spetersvn_stream_t * 1551251881Spetersvn_subst_stream_translated(svn_stream_t *stream, 1552251881Speter const char *eol_str, 1553251881Speter svn_boolean_t repair, 1554251881Speter apr_hash_t *keywords, 1555251881Speter svn_boolean_t expand, 1556251881Speter apr_pool_t *result_pool) 1557251881Speter{ 1558251881Speter return stream_translated(stream, eol_str, NULL, repair, keywords, expand, 1559251881Speter result_pool); 1560251881Speter} 1561251881Speter 1562251881Speter/* Same as svn_subst_translate_cstring2(), except for the following. 1563251881Speter * 1564251881Speter * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an 1565251881Speter * end-of-line sequence was changed, or to FALSE otherwise. 1566251881Speter */ 1567251881Speterstatic svn_error_t * 1568251881Spetertranslate_cstring(const char **dst, 1569251881Speter svn_boolean_t *translated_eol, 1570251881Speter const char *src, 1571251881Speter const char *eol_str, 1572251881Speter svn_boolean_t repair, 1573251881Speter apr_hash_t *keywords, 1574251881Speter svn_boolean_t expand, 1575251881Speter apr_pool_t *pool) 1576251881Speter{ 1577251881Speter svn_stringbuf_t *dst_stringbuf; 1578251881Speter svn_stream_t *dst_stream; 1579251881Speter apr_size_t len = strlen(src); 1580251881Speter 1581251881Speter /* The easy way out: no translation needed, just copy. */ 1582251881Speter if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) 1583251881Speter { 1584251881Speter *dst = apr_pstrmemdup(pool, src, len); 1585251881Speter return SVN_NO_ERROR; 1586251881Speter } 1587251881Speter 1588251881Speter /* Create a stringbuf and wrapper stream to hold the output. */ 1589251881Speter dst_stringbuf = svn_stringbuf_create_empty(pool); 1590251881Speter dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); 1591251881Speter 1592251881Speter if (translated_eol) 1593251881Speter *translated_eol = FALSE; 1594251881Speter 1595251881Speter /* Another wrapper to translate the content. */ 1596251881Speter dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair, 1597251881Speter keywords, expand, pool); 1598251881Speter 1599251881Speter /* Jam the text into the destination stream (to translate it). */ 1600251881Speter SVN_ERR(svn_stream_write(dst_stream, src, &len)); 1601251881Speter 1602251881Speter /* Close the destination stream to flush unwritten data. */ 1603251881Speter SVN_ERR(svn_stream_close(dst_stream)); 1604251881Speter 1605251881Speter *dst = dst_stringbuf->data; 1606251881Speter return SVN_NO_ERROR; 1607251881Speter} 1608251881Speter 1609251881Spetersvn_error_t * 1610251881Spetersvn_subst_translate_cstring2(const char *src, 1611251881Speter const char **dst, 1612251881Speter const char *eol_str, 1613251881Speter svn_boolean_t repair, 1614251881Speter apr_hash_t *keywords, 1615251881Speter svn_boolean_t expand, 1616251881Speter apr_pool_t *pool) 1617251881Speter{ 1618251881Speter return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand, 1619251881Speter pool); 1620251881Speter} 1621251881Speter 1622251881Speter/* Given a special file at SRC, generate a textual representation of 1623251881Speter it in a normal file at DST. Perform all allocations in POOL. */ 1624251881Speter/* ### this should be folded into svn_subst_copy_and_translate3 */ 1625251881Speterstatic svn_error_t * 1626251881Speterdetranslate_special_file(const char *src, const char *dst, 1627251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 1628251881Speter apr_pool_t *scratch_pool) 1629251881Speter{ 1630251881Speter const char *dst_tmp; 1631251881Speter svn_stream_t *src_stream; 1632251881Speter svn_stream_t *dst_stream; 1633251881Speter 1634251881Speter /* Open a temporary destination that we will eventually atomically 1635251881Speter rename into place. */ 1636251881Speter SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, 1637251881Speter svn_dirent_dirname(dst, scratch_pool), 1638251881Speter svn_io_file_del_none, 1639251881Speter scratch_pool, scratch_pool)); 1640251881Speter SVN_ERR(svn_subst_read_specialfile(&src_stream, src, 1641251881Speter scratch_pool, scratch_pool)); 1642251881Speter SVN_ERR(svn_stream_copy3(src_stream, dst_stream, 1643251881Speter cancel_func, cancel_baton, scratch_pool)); 1644251881Speter 1645251881Speter /* Do the atomic rename from our temporary location. */ 1646362181Sdim return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, scratch_pool)); 1647251881Speter} 1648251881Speter 1649251881Speter/* Creates a special file DST from the "normal form" located in SOURCE. 1650251881Speter * 1651251881Speter * All temporary allocations will be done in POOL. 1652251881Speter */ 1653251881Speterstatic svn_error_t * 1654251881Spetercreate_special_file_from_stream(svn_stream_t *source, const char *dst, 1655251881Speter apr_pool_t *pool) 1656251881Speter{ 1657251881Speter svn_stringbuf_t *contents; 1658251881Speter svn_boolean_t eof; 1659251881Speter const char *identifier; 1660251881Speter const char *remainder; 1661251881Speter const char *dst_tmp; 1662251881Speter svn_boolean_t create_using_internal_representation = FALSE; 1663251881Speter 1664251881Speter SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool)); 1665251881Speter 1666251881Speter /* Separate off the identifier. The first space character delimits 1667251881Speter the identifier, after which any remaining characters are specific 1668251881Speter to the actual special file type being created. */ 1669251881Speter identifier = contents->data; 1670251881Speter for (remainder = identifier; *remainder; remainder++) 1671251881Speter { 1672251881Speter if (*remainder == ' ') 1673251881Speter { 1674251881Speter remainder++; 1675251881Speter break; 1676251881Speter } 1677251881Speter } 1678251881Speter 1679251881Speter if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ", 1680251881Speter sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1)) 1681251881Speter { 1682251881Speter /* For symlinks, the type specific data is just a filesystem 1683251881Speter path that the symlink should reference. */ 1684251881Speter svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder, 1685251881Speter ".tmp", pool); 1686251881Speter 1687251881Speter /* If we had an error, check to see if it was because symlinks are 1688362181Sdim not supported on the platform. If so, fall back to using the 1689362181Sdim internal representation. */ 1690362181Sdim if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) 1691251881Speter { 1692362181Sdim svn_error_clear(err); 1693362181Sdim create_using_internal_representation = TRUE; 1694251881Speter } 1695362181Sdim else if (err) 1696362181Sdim { 1697362181Sdim return svn_error_trace(err); 1698362181Sdim } 1699251881Speter } 1700251881Speter else 1701251881Speter { 1702251881Speter /* Just create a normal file using the internal special file 1703251881Speter representation. We don't want a commit of an unknown special 1704251881Speter file type to DoS all the clients. */ 1705251881Speter create_using_internal_representation = TRUE; 1706251881Speter } 1707251881Speter 1708251881Speter /* If nothing else worked, write out the internal representation to 1709289180Speter a file that can be edited by the user. */ 1710251881Speter if (create_using_internal_representation) 1711262250Speter { 1712289180Speter svn_stream_t *new_stream; 1713289180Speter apr_size_t len; 1714251881Speter 1715289180Speter SVN_ERR(svn_stream_open_unique(&new_stream, &dst_tmp, 1716289180Speter svn_dirent_dirname(dst, pool), 1717289180Speter svn_io_file_del_none, 1718289180Speter pool, pool)); 1719262250Speter 1720289180Speter if (!eof) 1721289180Speter svn_stringbuf_appendcstr(contents, "\n"); 1722289180Speter len = contents->len; 1723289180Speter SVN_ERR(svn_stream_write(new_stream, contents->data, &len)); 1724289180Speter SVN_ERR(svn_stream_copy3(svn_stream_disown(source, pool), new_stream, 1725289180Speter NULL, NULL, pool)); 1726262250Speter } 1727262250Speter 1728251881Speter /* Do the atomic rename from our temporary location. */ 1729362181Sdim return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool)); 1730251881Speter} 1731251881Speter 1732251881Speter 1733251881Spetersvn_error_t * 1734251881Spetersvn_subst_copy_and_translate4(const char *src, 1735251881Speter const char *dst, 1736251881Speter const char *eol_str, 1737251881Speter svn_boolean_t repair, 1738251881Speter apr_hash_t *keywords, 1739251881Speter svn_boolean_t expand, 1740251881Speter svn_boolean_t special, 1741251881Speter svn_cancel_func_t cancel_func, 1742251881Speter void *cancel_baton, 1743251881Speter apr_pool_t *pool) 1744251881Speter{ 1745251881Speter svn_stream_t *src_stream; 1746251881Speter svn_stream_t *dst_stream; 1747251881Speter const char *dst_tmp; 1748251881Speter svn_error_t *err; 1749251881Speter svn_node_kind_t kind; 1750251881Speter svn_boolean_t path_special; 1751251881Speter 1752251881Speter SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool)); 1753251881Speter 1754251881Speter /* If this is a 'special' file, we may need to create it or 1755251881Speter detranslate it. */ 1756251881Speter if (special || path_special) 1757251881Speter { 1758251881Speter if (expand) 1759251881Speter { 1760251881Speter if (path_special) 1761251881Speter { 1762251881Speter /* We are being asked to create a special file from a special 1763251881Speter file. Do a temporary detranslation and work from there. */ 1764251881Speter 1765251881Speter /* ### woah. this section just undoes all the work we already did 1766251881Speter ### to read the contents of the special file. shoot... the 1767251881Speter ### svn_subst_read_specialfile even checks the file type 1768251881Speter ### for us! */ 1769251881Speter 1770251881Speter SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool)); 1771251881Speter } 1772251881Speter else 1773251881Speter { 1774251881Speter SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); 1775251881Speter } 1776251881Speter 1777289180Speter SVN_ERR(create_special_file_from_stream(src_stream, dst, pool)); 1778289180Speter 1779289180Speter return svn_error_trace(svn_stream_close(src_stream)); 1780251881Speter } 1781251881Speter /* else !expand */ 1782251881Speter 1783251881Speter return svn_error_trace(detranslate_special_file(src, dst, 1784251881Speter cancel_func, 1785251881Speter cancel_baton, 1786251881Speter pool)); 1787251881Speter } 1788251881Speter 1789251881Speter /* The easy way out: no translation needed, just copy. */ 1790251881Speter if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) 1791251881Speter return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool)); 1792251881Speter 1793251881Speter /* Open source file. */ 1794251881Speter SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); 1795251881Speter 1796251881Speter /* For atomicity, we translate to a tmp file and then rename the tmp file 1797251881Speter over the real destination. */ 1798251881Speter SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, 1799251881Speter svn_dirent_dirname(dst, pool), 1800251881Speter svn_io_file_del_none, pool, pool)); 1801251881Speter 1802251881Speter dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair, 1803251881Speter keywords, expand, pool); 1804251881Speter 1805251881Speter /* ###: use cancel func/baton in place of NULL/NULL below. */ 1806251881Speter err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton, 1807251881Speter pool); 1808251881Speter if (err) 1809251881Speter { 1810251881Speter /* On errors, we have a pathname available. */ 1811251881Speter if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) 1812251881Speter err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err, 1813251881Speter _("File '%s' has inconsistent newlines"), 1814251881Speter svn_dirent_local_style(src, pool)); 1815251881Speter return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, 1816251881Speter FALSE, pool)); 1817251881Speter } 1818251881Speter 1819251881Speter /* Now that dst_tmp contains the translated data, do the atomic rename. */ 1820362181Sdim SVN_ERR(svn_io_file_rename2(dst_tmp, dst, FALSE, pool)); 1821251881Speter 1822251881Speter /* Preserve the source file's permission bits. */ 1823251881Speter SVN_ERR(svn_io_copy_perms(src, dst, pool)); 1824251881Speter 1825251881Speter return SVN_NO_ERROR; 1826251881Speter} 1827251881Speter 1828251881Speter 1829251881Speter/*** 'Special file' stream support */ 1830251881Speter 1831251881Speterstruct special_stream_baton 1832251881Speter{ 1833251881Speter svn_stream_t *read_stream; 1834251881Speter svn_stringbuf_t *write_content; 1835251881Speter svn_stream_t *write_stream; 1836251881Speter const char *path; 1837251881Speter apr_pool_t *pool; 1838251881Speter}; 1839251881Speter 1840251881Speter 1841251881Speterstatic svn_error_t * 1842251881Speterread_handler_special(void *baton, char *buffer, apr_size_t *len) 1843251881Speter{ 1844251881Speter struct special_stream_baton *btn = baton; 1845251881Speter 1846251881Speter if (btn->read_stream) 1847251881Speter /* We actually found a file to read from */ 1848289180Speter return svn_stream_read_full(btn->read_stream, buffer, len); 1849251881Speter else 1850251881Speter return svn_error_createf(APR_ENOENT, NULL, 1851289180Speter _("Can't read special file: File '%s' not found"), 1852251881Speter svn_dirent_local_style(btn->path, btn->pool)); 1853251881Speter} 1854251881Speter 1855251881Speterstatic svn_error_t * 1856251881Speterwrite_handler_special(void *baton, const char *buffer, apr_size_t *len) 1857251881Speter{ 1858251881Speter struct special_stream_baton *btn = baton; 1859251881Speter 1860251881Speter return svn_stream_write(btn->write_stream, buffer, len); 1861251881Speter} 1862251881Speter 1863251881Speter 1864251881Speterstatic svn_error_t * 1865251881Speterclose_handler_special(void *baton) 1866251881Speter{ 1867251881Speter struct special_stream_baton *btn = baton; 1868251881Speter 1869251881Speter if (btn->write_content->len) 1870251881Speter { 1871251881Speter /* yeay! we received data and need to create a special file! */ 1872251881Speter 1873251881Speter svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content, 1874251881Speter btn->pool); 1875251881Speter SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool)); 1876251881Speter } 1877251881Speter 1878251881Speter return SVN_NO_ERROR; 1879251881Speter} 1880251881Speter 1881251881Speter 1882251881Spetersvn_error_t * 1883251881Spetersvn_subst_create_specialfile(svn_stream_t **stream, 1884251881Speter const char *path, 1885251881Speter apr_pool_t *result_pool, 1886251881Speter apr_pool_t *scratch_pool) 1887251881Speter{ 1888251881Speter struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton)); 1889251881Speter 1890251881Speter baton->path = apr_pstrdup(result_pool, path); 1891251881Speter 1892251881Speter /* SCRATCH_POOL may not exist after the function returns. */ 1893251881Speter baton->pool = result_pool; 1894251881Speter 1895251881Speter baton->write_content = svn_stringbuf_create_empty(result_pool); 1896251881Speter baton->write_stream = svn_stream_from_stringbuf(baton->write_content, 1897251881Speter result_pool); 1898251881Speter 1899251881Speter *stream = svn_stream_create(baton, result_pool); 1900251881Speter svn_stream_set_write(*stream, write_handler_special); 1901251881Speter svn_stream_set_close(*stream, close_handler_special); 1902251881Speter 1903251881Speter return SVN_NO_ERROR; 1904251881Speter} 1905251881Speter 1906251881Speter 1907251881Speter/* NOTE: this function is deprecated, but we cannot move it over to 1908251881Speter deprecated.c because it uses stuff private to this file, and it is 1909251881Speter not easily rebuilt in terms of "new" functions. */ 1910251881Spetersvn_error_t * 1911251881Spetersvn_subst_stream_from_specialfile(svn_stream_t **stream, 1912251881Speter const char *path, 1913251881Speter apr_pool_t *pool) 1914251881Speter{ 1915251881Speter struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton)); 1916251881Speter svn_error_t *err; 1917251881Speter 1918251881Speter baton->pool = pool; 1919251881Speter baton->path = apr_pstrdup(pool, path); 1920251881Speter 1921251881Speter err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool); 1922251881Speter 1923251881Speter /* File might not exist because we intend to create it upon close. */ 1924251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1925251881Speter { 1926251881Speter svn_error_clear(err); 1927251881Speter 1928251881Speter /* Note: the special file is missing. the caller won't find out 1929251881Speter until the first read. Oh well. This function is deprecated anyways, 1930251881Speter so they can just deal with the weird behavior. */ 1931251881Speter baton->read_stream = NULL; 1932251881Speter } 1933251881Speter 1934251881Speter baton->write_content = svn_stringbuf_create_empty(pool); 1935251881Speter baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool); 1936251881Speter 1937251881Speter *stream = svn_stream_create(baton, pool); 1938289180Speter svn_stream_set_read2(*stream, NULL /* only full read support */, 1939289180Speter read_handler_special); 1940251881Speter svn_stream_set_write(*stream, write_handler_special); 1941251881Speter svn_stream_set_close(*stream, close_handler_special); 1942251881Speter 1943251881Speter return SVN_NO_ERROR; 1944251881Speter} 1945251881Speter 1946251881Speter 1947251881Speter 1948251881Speter/*** String translation */ 1949251881Spetersvn_error_t * 1950251881Spetersvn_subst_translate_string2(svn_string_t **new_value, 1951251881Speter svn_boolean_t *translated_to_utf8, 1952251881Speter svn_boolean_t *translated_line_endings, 1953251881Speter const svn_string_t *value, 1954251881Speter const char *encoding, 1955251881Speter svn_boolean_t repair, 1956251881Speter apr_pool_t *result_pool, 1957251881Speter apr_pool_t *scratch_pool) 1958251881Speter{ 1959251881Speter const char *val_utf8; 1960251881Speter const char *val_utf8_lf; 1961251881Speter 1962251881Speter if (value == NULL) 1963251881Speter { 1964251881Speter *new_value = NULL; 1965251881Speter return SVN_NO_ERROR; 1966251881Speter } 1967251881Speter 1968289180Speter if (encoding && !strcmp(encoding, "UTF-8")) 1969251881Speter { 1970253734Speter val_utf8 = value->data; 1971253734Speter } 1972253734Speter else if (encoding) 1973253734Speter { 1974251881Speter SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data, 1975251881Speter encoding, scratch_pool)); 1976251881Speter } 1977251881Speter else 1978251881Speter { 1979251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool)); 1980251881Speter } 1981251881Speter 1982251881Speter if (translated_to_utf8) 1983251881Speter *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0); 1984251881Speter 1985251881Speter SVN_ERR(translate_cstring(&val_utf8_lf, 1986251881Speter translated_line_endings, 1987251881Speter val_utf8, 1988251881Speter "\n", /* translate to LF */ 1989251881Speter repair, 1990251881Speter NULL, /* no keywords */ 1991251881Speter FALSE, /* no expansion */ 1992251881Speter scratch_pool)); 1993251881Speter 1994251881Speter *new_value = svn_string_create(val_utf8_lf, result_pool); 1995251881Speter return SVN_NO_ERROR; 1996251881Speter} 1997251881Speter 1998251881Speter 1999251881Spetersvn_error_t * 2000251881Spetersvn_subst_detranslate_string(svn_string_t **new_value, 2001251881Speter const svn_string_t *value, 2002251881Speter svn_boolean_t for_output, 2003251881Speter apr_pool_t *pool) 2004251881Speter{ 2005251881Speter svn_error_t *err; 2006251881Speter const char *val_neol; 2007251881Speter const char *val_nlocale_neol; 2008251881Speter 2009251881Speter if (value == NULL) 2010251881Speter { 2011251881Speter *new_value = NULL; 2012251881Speter return SVN_NO_ERROR; 2013251881Speter } 2014251881Speter 2015251881Speter SVN_ERR(svn_subst_translate_cstring2(value->data, 2016251881Speter &val_neol, 2017251881Speter APR_EOL_STR, /* 'native' eol */ 2018251881Speter FALSE, /* no repair */ 2019251881Speter NULL, /* no keywords */ 2020251881Speter FALSE, /* no expansion */ 2021251881Speter pool)); 2022251881Speter 2023251881Speter if (for_output) 2024251881Speter { 2025251881Speter err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); 2026251881Speter if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) 2027251881Speter { 2028251881Speter val_nlocale_neol = 2029251881Speter svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool); 2030251881Speter svn_error_clear(err); 2031251881Speter } 2032251881Speter else if (err) 2033251881Speter return err; 2034251881Speter } 2035251881Speter else 2036251881Speter { 2037251881Speter err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); 2038251881Speter if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) 2039251881Speter { 2040251881Speter val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool); 2041251881Speter svn_error_clear(err); 2042251881Speter } 2043251881Speter else if (err) 2044251881Speter return err; 2045251881Speter } 2046251881Speter 2047251881Speter *new_value = svn_string_create(val_nlocale_neol, pool); 2048251881Speter 2049251881Speter return SVN_NO_ERROR; 2050251881Speter} 2051