subst.c revision 262253
1/* 2 * subst.c : generic eol/keyword substitution routines 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#define APR_WANT_STRFUNC 27#include <apr_want.h> 28 29#include <stdlib.h> 30#include <assert.h> 31#include <apr_pools.h> 32#include <apr_tables.h> 33#include <apr_file_io.h> 34#include <apr_strings.h> 35 36#include "svn_hash.h" 37#include "svn_cmdline.h" 38#include "svn_types.h" 39#include "svn_string.h" 40#include "svn_time.h" 41#include "svn_dirent_uri.h" 42#include "svn_path.h" 43#include "svn_error.h" 44#include "svn_utf.h" 45#include "svn_io.h" 46#include "svn_subst.h" 47#include "svn_pools.h" 48#include "private/svn_io_private.h" 49 50#include "svn_private_config.h" 51 52#include "private/svn_string_private.h" 53 54/** 55 * The textual elements of a detranslated special file. One of these 56 * strings must appear as the first element of any special file as it 57 * exists in the repository or the text base. 58 */ 59#define SVN_SUBST__SPECIAL_LINK_STR "link" 60 61void 62svn_subst_eol_style_from_value(svn_subst_eol_style_t *style, 63 const char **eol, 64 const char *value) 65{ 66 if (value == NULL) 67 { 68 /* property doesn't exist. */ 69 *eol = NULL; 70 if (style) 71 *style = svn_subst_eol_style_none; 72 } 73 else if (! strcmp("native", value)) 74 { 75 *eol = APR_EOL_STR; /* whee, a portability library! */ 76 if (style) 77 *style = svn_subst_eol_style_native; 78 } 79 else if (! strcmp("LF", value)) 80 { 81 *eol = "\n"; 82 if (style) 83 *style = svn_subst_eol_style_fixed; 84 } 85 else if (! strcmp("CR", value)) 86 { 87 *eol = "\r"; 88 if (style) 89 *style = svn_subst_eol_style_fixed; 90 } 91 else if (! strcmp("CRLF", value)) 92 { 93 *eol = "\r\n"; 94 if (style) 95 *style = svn_subst_eol_style_fixed; 96 } 97 else 98 { 99 *eol = NULL; 100 if (style) 101 *style = svn_subst_eol_style_unknown; 102 } 103} 104 105 106svn_boolean_t 107svn_subst_translation_required(svn_subst_eol_style_t style, 108 const char *eol, 109 apr_hash_t *keywords, 110 svn_boolean_t special, 111 svn_boolean_t force_eol_check) 112{ 113 return (special || keywords 114 || (style != svn_subst_eol_style_none && force_eol_check) 115 || (style == svn_subst_eol_style_native && 116 strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0) 117 || (style == svn_subst_eol_style_fixed && 118 strcmp(APR_EOL_STR, eol) != 0)); 119} 120 121 122 123/* Helper function for svn_subst_build_keywords */ 124 125/* Given a printf-like format string, return a string with proper 126 * information filled in. 127 * 128 * Important API note: This function is the core of the implementation of 129 * svn_subst_build_keywords (all versions), and as such must implement the 130 * tolerance of NULL and zero inputs that that function's documention 131 * stipulates. 132 * 133 * The format codes: 134 * 135 * %a author of this revision 136 * %b basename of the URL of this file 137 * %d short format of date of this revision 138 * %D long format of date of this revision 139 * %P path relative to root of repos 140 * %r number of this revision 141 * %R root url of repository 142 * %u URL of this file 143 * %_ a space 144 * %% a literal % 145 * 146 * The following special format codes are also recognized: 147 * %H is equivalent to %P%_%r%_%d%_%a 148 * %I is equivalent to %b%_%r%_%d%_%a 149 * 150 * All memory is allocated out of @a pool. 151 */ 152static svn_string_t * 153keyword_printf(const char *fmt, 154 const char *rev, 155 const char *url, 156 const char *repos_root_url, 157 apr_time_t date, 158 const char *author, 159 apr_pool_t *pool) 160{ 161 svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool); 162 const char *cur; 163 size_t n; 164 165 for (;;) 166 { 167 cur = fmt; 168 169 while (*cur != '\0' && *cur != '%') 170 cur++; 171 172 if ((n = cur - fmt) > 0) /* Do we have an as-is string? */ 173 svn_stringbuf_appendbytes(value, fmt, n); 174 175 if (*cur == '\0') 176 break; 177 178 switch (cur[1]) 179 { 180 case 'a': /* author of this revision */ 181 if (author) 182 svn_stringbuf_appendcstr(value, author); 183 break; 184 case 'b': /* basename of this file */ 185 if (url && *url) 186 { 187 const char *base_name = svn_uri_basename(url, pool); 188 svn_stringbuf_appendcstr(value, base_name); 189 } 190 break; 191 case 'd': /* short format of date of this revision */ 192 if (date) 193 { 194 apr_time_exp_t exploded_time; 195 const char *human; 196 197 apr_time_exp_gmt(&exploded_time, date); 198 199 human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ", 200 exploded_time.tm_year + 1900, 201 exploded_time.tm_mon + 1, 202 exploded_time.tm_mday, 203 exploded_time.tm_hour, 204 exploded_time.tm_min, 205 exploded_time.tm_sec); 206 207 svn_stringbuf_appendcstr(value, human); 208 } 209 break; 210 case 'D': /* long format of date of this revision */ 211 if (date) 212 svn_stringbuf_appendcstr(value, 213 svn_time_to_human_cstring(date, pool)); 214 break; 215 case 'P': /* relative path of this file */ 216 if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0') 217 { 218 const char *repos_relpath; 219 220 repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool); 221 if (repos_relpath) 222 svn_stringbuf_appendcstr(value, repos_relpath); 223 } 224 break; 225 case 'R': /* root of repos */ 226 if (repos_root_url && *repos_root_url != '\0') 227 svn_stringbuf_appendcstr(value, repos_root_url); 228 break; 229 case 'r': /* number of this revision */ 230 if (rev) 231 svn_stringbuf_appendcstr(value, rev); 232 break; 233 case 'u': /* URL of this file */ 234 if (url) 235 svn_stringbuf_appendcstr(value, url); 236 break; 237 case '_': /* '%_' => a space */ 238 svn_stringbuf_appendbyte(value, ' '); 239 break; 240 case '%': /* '%%' => a literal % */ 241 svn_stringbuf_appendbyte(value, *cur); 242 break; 243 case '\0': /* '%' as the last character of the string. */ 244 svn_stringbuf_appendbyte(value, *cur); 245 /* Now go back one character, since this was just a one character 246 * sequence, whereas all others are two characters, and we do not 247 * want to skip the null terminator entirely and carry on 248 * formatting random memory contents. */ 249 cur--; 250 break; 251 case 'H': 252 { 253 svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url, 254 repos_root_url, date, author, 255 pool); 256 svn_stringbuf_appendcstr(value, s->data); 257 } 258 break; 259 case 'I': 260 { 261 svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url, 262 repos_root_url, date, author, 263 pool); 264 svn_stringbuf_appendcstr(value, s->data); 265 } 266 break; 267 default: /* Unrecognized code, just print it literally. */ 268 svn_stringbuf_appendbytes(value, cur, 2); 269 break; 270 } 271 272 /* Format code is processed - skip it, and get ready for next chunk. */ 273 fmt = cur + 2; 274 } 275 276 return svn_stringbuf__morph_into_string(value); 277} 278 279static svn_error_t * 280build_keywords(apr_hash_t **kw, 281 svn_boolean_t expand_custom_keywords, 282 const char *keywords_val, 283 const char *rev, 284 const char *url, 285 const char *repos_root_url, 286 apr_time_t date, 287 const char *author, 288 apr_pool_t *pool) 289{ 290 apr_array_header_t *keyword_tokens; 291 int i; 292 *kw = apr_hash_make(pool); 293 294 keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f", 295 TRUE /* chop */, pool); 296 297 for (i = 0; i < keyword_tokens->nelts; ++i) 298 { 299 const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *); 300 const char *custom_fmt = NULL; 301 302 if (expand_custom_keywords) 303 { 304 char *sep; 305 306 /* Check if there is a custom keyword definition, started by '='. */ 307 sep = strchr(keyword, '='); 308 if (sep) 309 { 310 *sep = '\0'; /* Split keyword's name from custom format. */ 311 custom_fmt = sep + 1; 312 } 313 } 314 315 if (custom_fmt) 316 { 317 svn_string_t *custom_val; 318 319 /* Custom keywords must be allowed to match the name of an 320 * existing fixed keyword. This is for compatibility purposes, 321 * in case new fixed keywords are added to Subversion which 322 * happen to match a custom keyword defined somewhere. 323 * There is only one global namespace for keyword names. */ 324 custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url, 325 date, author, pool); 326 svn_hash_sets(*kw, keyword, custom_val); 327 } 328 else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG)) 329 || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM)) 330 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT))) 331 { 332 svn_string_t *revision_val; 333 334 revision_val = keyword_printf("%r", rev, url, repos_root_url, 335 date, author, pool); 336 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val); 337 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val); 338 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val); 339 } 340 else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG)) 341 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT))) 342 { 343 svn_string_t *date_val; 344 345 date_val = keyword_printf("%D", rev, url, repos_root_url, date, 346 author, pool); 347 svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val); 348 svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val); 349 } 350 else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG)) 351 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT))) 352 { 353 svn_string_t *author_val; 354 355 author_val = keyword_printf("%a", rev, url, repos_root_url, date, 356 author, pool); 357 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val); 358 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val); 359 } 360 else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG)) 361 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT))) 362 { 363 svn_string_t *url_val; 364 365 url_val = keyword_printf("%u", rev, url, repos_root_url, date, 366 author, pool); 367 svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val); 368 svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val); 369 } 370 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID))) 371 { 372 svn_string_t *id_val; 373 374 id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url, 375 date, author, pool); 376 svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val); 377 } 378 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER))) 379 { 380 svn_string_t *header_val; 381 382 header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url, 383 date, author, pool); 384 svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val); 385 } 386 } 387 388 return SVN_NO_ERROR; 389} 390 391svn_error_t * 392svn_subst_build_keywords2(apr_hash_t **kw, 393 const char *keywords_val, 394 const char *rev, 395 const char *url, 396 apr_time_t date, 397 const char *author, 398 apr_pool_t *pool) 399{ 400 return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url, 401 NULL, date, author, pool)); 402} 403 404 405svn_error_t * 406svn_subst_build_keywords3(apr_hash_t **kw, 407 const char *keywords_val, 408 const char *rev, 409 const char *url, 410 const char *repos_root_url, 411 apr_time_t date, 412 const char *author, 413 apr_pool_t *pool) 414{ 415 return svn_error_trace(build_keywords(kw, TRUE, keywords_val, 416 rev, url, repos_root_url, 417 date, author, pool)); 418} 419 420 421/*** Helpers for svn_subst_translate_stream2 ***/ 422 423 424/* Write out LEN bytes of BUF into STREAM. */ 425/* ### TODO: 'stream_write()' would be a better name for this. */ 426static svn_error_t * 427translate_write(svn_stream_t *stream, 428 const void *buf, 429 apr_size_t len) 430{ 431 SVN_ERR(svn_stream_write(stream, buf, &len)); 432 /* (No need to check LEN, as a short write always produces an error.) */ 433 return SVN_NO_ERROR; 434} 435 436 437/* Perform the substitution of VALUE into keyword string BUF (with len 438 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating 439 *LEN to the new size of the substituted result. Return TRUE if all 440 goes well, FALSE otherwise. If VALUE is NULL, keyword will be 441 contracted, else it will be expanded. */ 442static svn_boolean_t 443translate_keyword_subst(char *buf, 444 apr_size_t *len, 445 const char *keyword, 446 apr_size_t keyword_len, 447 const svn_string_t *value) 448{ 449 char *buf_ptr; 450 451 /* Make sure we gotz good stuffs. */ 452 assert(*len <= SVN_KEYWORD_MAX_LEN); 453 assert((buf[0] == '$') && (buf[*len - 1] == '$')); 454 455 /* Need at least a keyword and two $'s. */ 456 if (*len < keyword_len + 2) 457 return FALSE; 458 459 /* Need at least space for two $'s, two spaces and a colon, and that 460 leaves zero space for the value itself. */ 461 if (keyword_len > SVN_KEYWORD_MAX_LEN - 5) 462 return FALSE; 463 464 /* The keyword needs to match what we're looking for. */ 465 if (strncmp(buf + 1, keyword, keyword_len)) 466 return FALSE; 467 468 buf_ptr = buf + 1 + keyword_len; 469 470 /* Check for fixed-length expansion. 471 * The format of fixed length keyword and its data is 472 * Unexpanded keyword: "$keyword:: $" 473 * Expanded keyword: "$keyword:: value $" 474 * Expanded kw with filling: "$keyword:: value $" 475 * Truncated keyword: "$keyword:: longval#$" 476 */ 477 if ((buf_ptr[0] == ':') /* first char after keyword is ':' */ 478 && (buf_ptr[1] == ':') /* second char after keyword is ':' */ 479 && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */ 480 && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */ 481 || (buf[*len - 2] == '#')) /* .. or has '#' for next to last 482 character */ 483 && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */ 484 { 485 /* This is fixed length keyword, so *len remains unchanged */ 486 apr_size_t max_value_len = *len - (6 + keyword_len); 487 488 if (! value) 489 { 490 /* no value, so unexpand */ 491 buf_ptr += 2; 492 while (*buf_ptr != '$') 493 *(buf_ptr++) = ' '; 494 } 495 else 496 { 497 if (value->len <= max_value_len) 498 { /* replacement not as long as template, pad with spaces */ 499 strncpy(buf_ptr + 3, value->data, value->len); 500 buf_ptr += 3 + value->len; 501 while (*buf_ptr != '$') 502 *(buf_ptr++) = ' '; 503 } 504 else 505 { 506 /* replacement needs truncating */ 507 strncpy(buf_ptr + 3, value->data, max_value_len); 508 buf[*len - 2] = '#'; 509 buf[*len - 1] = '$'; 510 } 511 } 512 return TRUE; 513 } 514 515 /* Check for unexpanded keyword. */ 516 else if (buf_ptr[0] == '$') /* "$keyword$" */ 517 { 518 /* unexpanded... */ 519 if (value) 520 { 521 /* ...so expand. */ 522 buf_ptr[0] = ':'; 523 buf_ptr[1] = ' '; 524 if (value->len) 525 { 526 apr_size_t vallen = value->len; 527 528 /* "$keyword: value $" */ 529 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) 530 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; 531 strncpy(buf_ptr + 2, value->data, vallen); 532 buf_ptr[2 + vallen] = ' '; 533 buf_ptr[2 + vallen + 1] = '$'; 534 *len = 5 + keyword_len + vallen; 535 } 536 else 537 { 538 /* "$keyword: $" */ 539 buf_ptr[2] = '$'; 540 *len = 4 + keyword_len; 541 } 542 } 543 else 544 { 545 /* ...but do nothing. */ 546 } 547 return TRUE; 548 } 549 550 /* Check for expanded keyword. */ 551 else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */ 552 && (buf_ptr[0] == ':') /* first char after keyword is ':' */ 553 && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */ 554 && (buf[*len - 2] == ' ')) 555 || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */ 556 && (buf_ptr[0] == ':') /* first char after keyword is ':' */ 557 && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */ 558 { 559 /* expanded... */ 560 if (! value) 561 { 562 /* ...so unexpand. */ 563 buf_ptr[0] = '$'; 564 *len = 2 + keyword_len; 565 } 566 else 567 { 568 /* ...so re-expand. */ 569 buf_ptr[0] = ':'; 570 buf_ptr[1] = ' '; 571 if (value->len) 572 { 573 apr_size_t vallen = value->len; 574 575 /* "$keyword: value $" */ 576 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) 577 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; 578 strncpy(buf_ptr + 2, value->data, vallen); 579 buf_ptr[2 + vallen] = ' '; 580 buf_ptr[2 + vallen + 1] = '$'; 581 *len = 5 + keyword_len + vallen; 582 } 583 else 584 { 585 /* "$keyword: $" */ 586 buf_ptr[2] = '$'; 587 *len = 4 + keyword_len; 588 } 589 } 590 return TRUE; 591 } 592 593 return FALSE; 594} 595 596/* Parse BUF (whose length is LEN, and which starts and ends with '$'), 597 trying to match one of the keyword names in KEYWORDS. If such a 598 keyword is found, update *KEYWORD_NAME with the keyword name and 599 return TRUE. */ 600static svn_boolean_t 601match_keyword(char *buf, 602 apr_size_t len, 603 char *keyword_name, 604 apr_hash_t *keywords) 605{ 606 apr_size_t i; 607 608 /* Early return for ignored keywords */ 609 if (! keywords) 610 return FALSE; 611 612 /* Extract the name of the keyword */ 613 for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++) 614 keyword_name[i] = buf[i + 1]; 615 keyword_name[i] = '\0'; 616 617 return svn_hash_gets(keywords, keyword_name) != NULL; 618} 619 620/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN): 621 optionally perform the substitution in place, update *LEN with 622 the new length of the translated keyword string, and return TRUE. 623 If this buffer doesn't contain a known keyword pattern, leave BUF 624 and *LEN untouched and return FALSE. 625 626 See the docstring for svn_subst_copy_and_translate for how the 627 EXPAND and KEYWORDS parameters work. 628 629 NOTE: It is assumed that BUF has been allocated to be at least 630 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less 631 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions 632 which would result in a keyword string which is greater than 633 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way 634 that the resultant keyword string is still valid (begins with 635 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */ 636static svn_boolean_t 637translate_keyword(char *buf, 638 apr_size_t *len, 639 const char *keyword_name, 640 svn_boolean_t expand, 641 apr_hash_t *keywords) 642{ 643 const svn_string_t *value; 644 645 /* Make sure we gotz good stuffs. */ 646 assert(*len <= SVN_KEYWORD_MAX_LEN); 647 assert((buf[0] == '$') && (buf[*len - 1] == '$')); 648 649 /* Early return for ignored keywords */ 650 if (! keywords) 651 return FALSE; 652 653 value = svn_hash_gets(keywords, keyword_name); 654 655 if (value) 656 { 657 return translate_keyword_subst(buf, len, 658 keyword_name, strlen(keyword_name), 659 expand ? value : NULL); 660 } 661 662 return FALSE; 663} 664 665/* A boolean expression that evaluates to true if the first STR_LEN characters 666 of the string STR are one of the end-of-line strings LF, CR, or CRLF; 667 to false otherwise. */ 668#define STRING_IS_EOL(str, str_len) \ 669 (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \ 670 ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r'))) 671 672/* A boolean expression that evaluates to true if the end-of-line string EOL1, 673 having length EOL1_LEN, and the end-of-line string EOL2, having length 674 EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the 675 set {"\n", "\r", "\r\n"}; to false otherwise. 676 677 Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if 678 EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course 679 different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both 680 "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1. 681 We need only check the one character for equality to determine whether 682 EOL1 and EOL2 are different in that case. */ 683#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \ 684 (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2))) 685 686 687/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to 688 the newline string EOL_STR (of length EOL_STR_LEN), writing the 689 result (which is always EOL_STR) to the stream DST. 690 691 This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n". 692 693 Also check for consistency of the source newline strings across 694 multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache 695 of the first newline found. If the current newline is not the same 696 as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE, 697 ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL 698 error. If *SRC_FORMAT_LEN is 0, assume we are examining the first 699 newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to 700 use for later consistency checks. 701 702 If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the 703 newline string that was written (EOL_STR) is not the same as the newline 704 string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL 705 untouched. 706 707 Note: all parameters are required even if REPAIR is TRUE. 708 ### We could require that REPAIR must not change across a sequence of 709 calls, and could then optimize by not using SRC_FORMAT at all if 710 REPAIR is TRUE. 711*/ 712static svn_error_t * 713translate_newline(const char *eol_str, 714 apr_size_t eol_str_len, 715 char *src_format, 716 apr_size_t *src_format_len, 717 const char *newline_buf, 718 apr_size_t newline_len, 719 svn_stream_t *dst, 720 svn_boolean_t *translated_eol, 721 svn_boolean_t repair) 722{ 723 SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len)); 724 725 /* If we've seen a newline before, compare it with our cache to 726 check for consistency, else cache it for future comparisons. */ 727 if (*src_format_len) 728 { 729 /* Comparing with cache. If we are inconsistent and 730 we are NOT repairing the file, generate an error! */ 731 if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len, 732 newline_buf, newline_len)) 733 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL); 734 } 735 else 736 { 737 /* This is our first line ending, so cache it before 738 handling it. */ 739 strncpy(src_format, newline_buf, newline_len); 740 *src_format_len = newline_len; 741 } 742 743 /* Write the desired newline */ 744 SVN_ERR(translate_write(dst, eol_str, eol_str_len)); 745 746 /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS() 747 * because EOL_STR may not be a valid EOL sequence. */ 748 if (translated_eol != NULL && 749 (eol_str_len != newline_len || 750 memcmp(eol_str, newline_buf, eol_str_len) != 0)) 751 *translated_eol = TRUE; 752 753 return SVN_NO_ERROR; 754} 755 756 757 758/*** Public interfaces. ***/ 759 760svn_boolean_t 761svn_subst_keywords_differ(const svn_subst_keywords_t *a, 762 const svn_subst_keywords_t *b, 763 svn_boolean_t compare_values) 764{ 765 if (((a == NULL) && (b == NULL)) /* no A or B */ 766 /* no A, and B has no contents */ 767 || ((a == NULL) 768 && (b->revision == NULL) 769 && (b->date == NULL) 770 && (b->author == NULL) 771 && (b->url == NULL)) 772 /* no B, and A has no contents */ 773 || ((b == NULL) && (a->revision == NULL) 774 && (a->date == NULL) 775 && (a->author == NULL) 776 && (a->url == NULL)) 777 /* neither A nor B has any contents */ 778 || ((a != NULL) && (b != NULL) 779 && (b->revision == NULL) 780 && (b->date == NULL) 781 && (b->author == NULL) 782 && (b->url == NULL) 783 && (a->revision == NULL) 784 && (a->date == NULL) 785 && (a->author == NULL) 786 && (a->url == NULL))) 787 { 788 return FALSE; 789 } 790 else if ((a == NULL) || (b == NULL)) 791 return TRUE; 792 793 /* Else both A and B have some keywords. */ 794 795 if ((! a->revision) != (! b->revision)) 796 return TRUE; 797 else if ((compare_values && (a->revision != NULL)) 798 && (strcmp(a->revision->data, b->revision->data) != 0)) 799 return TRUE; 800 801 if ((! a->date) != (! b->date)) 802 return TRUE; 803 else if ((compare_values && (a->date != NULL)) 804 && (strcmp(a->date->data, b->date->data) != 0)) 805 return TRUE; 806 807 if ((! a->author) != (! b->author)) 808 return TRUE; 809 else if ((compare_values && (a->author != NULL)) 810 && (strcmp(a->author->data, b->author->data) != 0)) 811 return TRUE; 812 813 if ((! a->url) != (! b->url)) 814 return TRUE; 815 else if ((compare_values && (a->url != NULL)) 816 && (strcmp(a->url->data, b->url->data) != 0)) 817 return TRUE; 818 819 /* Else we never found a difference, so they must be the same. */ 820 821 return FALSE; 822} 823 824svn_boolean_t 825svn_subst_keywords_differ2(apr_hash_t *a, 826 apr_hash_t *b, 827 svn_boolean_t compare_values, 828 apr_pool_t *pool) 829{ 830 apr_hash_index_t *hi; 831 unsigned int a_count, b_count; 832 833 /* An empty hash is logically equal to a NULL, 834 * as far as this API is concerned. */ 835 a_count = (a == NULL) ? 0 : apr_hash_count(a); 836 b_count = (b == NULL) ? 0 : apr_hash_count(b); 837 838 if (a_count != b_count) 839 return TRUE; 840 841 if (a_count == 0) 842 return FALSE; 843 844 /* The hashes are both non-NULL, and have the same number of items. 845 * We must check that every item of A is present in B. */ 846 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi)) 847 { 848 const void *key; 849 apr_ssize_t klen; 850 void *void_a_val; 851 svn_string_t *a_val, *b_val; 852 853 apr_hash_this(hi, &key, &klen, &void_a_val); 854 a_val = void_a_val; 855 b_val = apr_hash_get(b, key, klen); 856 857 if (!b_val || (compare_values && !svn_string_compare(a_val, b_val))) 858 return TRUE; 859 } 860 861 return FALSE; 862} 863 864 865/* Baton for translate_chunk() to store its state in. */ 866struct translation_baton 867{ 868 const char *eol_str; 869 svn_boolean_t *translated_eol; 870 svn_boolean_t repair; 871 apr_hash_t *keywords; 872 svn_boolean_t expand; 873 874 /* 'short boolean' array that encodes what character values 875 may trigger a translation action, hence are 'interesting' */ 876 char interesting[256]; 877 878 /* Length of the string EOL_STR points to. */ 879 apr_size_t eol_str_len; 880 881 /* Buffer to cache any newline state between translation chunks */ 882 char newline_buf[2]; 883 884 /* Offset (within newline_buf) of the first *unused* character */ 885 apr_size_t newline_off; 886 887 /* Buffer to cache keyword-parsing state between translation chunks */ 888 char keyword_buf[SVN_KEYWORD_MAX_LEN]; 889 890 /* Offset (within keyword-buf) to the first *unused* character */ 891 apr_size_t keyword_off; 892 893 /* EOL style used in the chunk-source */ 894 char src_format[2]; 895 896 /* Length of the EOL style string found in the chunk-source, 897 or zero if none encountered yet */ 898 apr_size_t src_format_len; 899 900 /* If this is svn_tristate_false, translate_newline() will be called 901 for every newline in the file */ 902 svn_tristate_t nl_translation_skippable; 903}; 904 905 906/* Allocate a baton for use with translate_chunk() in POOL and 907 * initialize it for the first iteration. 908 * 909 * The caller must assure that EOL_STR and KEYWORDS at least 910 * have the same life time as that of POOL. 911 */ 912static struct translation_baton * 913create_translation_baton(const char *eol_str, 914 svn_boolean_t *translated_eol, 915 svn_boolean_t repair, 916 apr_hash_t *keywords, 917 svn_boolean_t expand, 918 apr_pool_t *pool) 919{ 920 struct translation_baton *b = apr_palloc(pool, sizeof(*b)); 921 922 /* For efficiency, convert an empty set of keywords to NULL. */ 923 if (keywords && (apr_hash_count(keywords) == 0)) 924 keywords = NULL; 925 926 b->eol_str = eol_str; 927 b->eol_str_len = eol_str ? strlen(eol_str) : 0; 928 b->translated_eol = translated_eol; 929 b->repair = repair; 930 b->keywords = keywords; 931 b->expand = expand; 932 b->newline_off = 0; 933 b->keyword_off = 0; 934 b->src_format_len = 0; 935 b->nl_translation_skippable = svn_tristate_unknown; 936 937 /* Most characters don't start translation actions. 938 * Mark those that do depending on the parameters we got. */ 939 memset(b->interesting, FALSE, sizeof(b->interesting)); 940 if (keywords) 941 b->interesting['$'] = TRUE; 942 if (eol_str) 943 { 944 b->interesting['\r'] = TRUE; 945 b->interesting['\n'] = TRUE; 946 } 947 948 return b; 949} 950 951/* Return TRUE if the EOL starting at BUF matches the eol_str member of B. 952 * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like 953 * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is 954 * more efficient to handle that special case implicitly in the calling code 955 * by exiting the quick scan loop. 956 * The caller must ensure that buf[0] and buf[1] refer to valid memory 957 * locations. 958 */ 959static APR_INLINE svn_boolean_t 960eol_unchanged(struct translation_baton *b, 961 const char *buf) 962{ 963 /* If the first byte doesn't match, the whole EOL won't. 964 * This does also handle the (certainly invalid) case that 965 * eol_str would be an empty string. 966 */ 967 if (buf[0] != b->eol_str[0]) 968 return FALSE; 969 970 /* two-char EOLs must be a full match */ 971 if (b->eol_str_len == 2) 972 return buf[1] == b->eol_str[1]; 973 974 /* The first char matches the required 1-byte EOL. 975 * But maybe, buf[] contains a 2-byte EOL? 976 * In that case, the second byte will be interesting 977 * and not be another EOL of its own. 978 */ 979 return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1]; 980} 981 982 983/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN 984 * according to the settings and state stored in baton B. 985 * 986 * Write output to stream DST. 987 * 988 * To finish a series of chunk translations, flush all buffers by calling 989 * this routine with a NULL value for BUF. 990 * 991 * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if 992 * an end-of-line sequence was changed, otherwise leave it untouched. 993 * 994 * Use POOL for temporary allocations. 995 */ 996static svn_error_t * 997translate_chunk(svn_stream_t *dst, 998 struct translation_baton *b, 999 const char *buf, 1000 apr_size_t buflen, 1001 apr_pool_t *pool) 1002{ 1003 const char *p; 1004 apr_size_t len; 1005 1006 if (buf) 1007 { 1008 /* precalculate some oft-used values */ 1009 const char *end = buf + buflen; 1010 const char *interesting = b->interesting; 1011 apr_size_t next_sign_off = 0; 1012 1013 /* At the beginning of this loop, assume that we might be in an 1014 * interesting state, i.e. with data in the newline or keyword 1015 * buffer. First try to get to the boring state so we can copy 1016 * a run of boring characters; then try to get back to the 1017 * interesting state by processing an interesting character, 1018 * and repeat. */ 1019 for (p = buf; p < end;) 1020 { 1021 /* Try to get to the boring state, if necessary. */ 1022 if (b->newline_off) 1023 { 1024 if (*p == '\n') 1025 b->newline_buf[b->newline_off++] = *p++; 1026 1027 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1028 b->src_format, 1029 &b->src_format_len, b->newline_buf, 1030 b->newline_off, dst, b->translated_eol, 1031 b->repair)); 1032 1033 b->newline_off = 0; 1034 } 1035 else if (b->keyword_off && *p == '$') 1036 { 1037 svn_boolean_t keyword_matches; 1038 char keyword_name[SVN_KEYWORD_MAX_LEN + 1]; 1039 1040 /* If keyword is matched, but not correctly translated, try to 1041 * look for the next ending '$'. */ 1042 b->keyword_buf[b->keyword_off++] = *p++; 1043 keyword_matches = match_keyword(b->keyword_buf, b->keyword_off, 1044 keyword_name, b->keywords); 1045 if (!keyword_matches) 1046 { 1047 /* reuse the ending '$' */ 1048 p--; 1049 b->keyword_off--; 1050 } 1051 1052 if (!keyword_matches || 1053 translate_keyword(b->keyword_buf, &b->keyword_off, 1054 keyword_name, b->expand, b->keywords) || 1055 b->keyword_off >= SVN_KEYWORD_MAX_LEN) 1056 { 1057 /* write out non-matching text or translated keyword */ 1058 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1059 1060 next_sign_off = 0; 1061 b->keyword_off = 0; 1062 } 1063 else 1064 { 1065 if (next_sign_off == 0) 1066 next_sign_off = b->keyword_off - 1; 1067 1068 continue; 1069 } 1070 } 1071 else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1 1072 || (b->keyword_off && (*p == '\r' || *p == '\n'))) 1073 { 1074 if (next_sign_off > 0) 1075 { 1076 /* rolling back, continue with next '$' in keyword_buf */ 1077 p -= (b->keyword_off - next_sign_off); 1078 b->keyword_off = next_sign_off; 1079 next_sign_off = 0; 1080 } 1081 /* No closing '$' found; flush the keyword buffer. */ 1082 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1083 1084 b->keyword_off = 0; 1085 } 1086 else if (b->keyword_off) 1087 { 1088 b->keyword_buf[b->keyword_off++] = *p++; 1089 continue; 1090 } 1091 1092 /* translate_newline will modify the baton for src_format_len==0 1093 or may return an error if b->repair is FALSE. In all other 1094 cases, we can skip the newline translation as long as source 1095 EOL format and actual EOL format match. If there is a 1096 mismatch, translate_newline will be called regardless of 1097 nl_translation_skippable. 1098 */ 1099 if (b->nl_translation_skippable == svn_tristate_unknown && 1100 b->src_format_len > 0) 1101 { 1102 /* test whether translate_newline may return an error */ 1103 if (b->eol_str_len == b->src_format_len && 1104 strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0) 1105 b->nl_translation_skippable = svn_tristate_true; 1106 else if (b->repair) 1107 b->nl_translation_skippable = svn_tristate_true; 1108 else 1109 b->nl_translation_skippable = svn_tristate_false; 1110 } 1111 1112 /* We're in the boring state; look for interesting characters. 1113 Offset len such that it will become 0 in the first iteration. 1114 */ 1115 len = 0 - b->eol_str_len; 1116 1117 /* Look for the next EOL (or $) that actually needs translation. 1118 Stop there or at EOF, whichever is encountered first. 1119 */ 1120 do 1121 { 1122 /* skip current EOL */ 1123 len += b->eol_str_len; 1124 1125 /* Check 4 bytes at once to allow for efficient pipelining 1126 and to reduce loop condition overhead. */ 1127 while ((p + len + 4) <= end) 1128 { 1129 if (interesting[(unsigned char)p[len]] 1130 || interesting[(unsigned char)p[len+1]] 1131 || interesting[(unsigned char)p[len+2]] 1132 || interesting[(unsigned char)p[len+3]]) 1133 break; 1134 1135 len += 4; 1136 } 1137 1138 /* Found an interesting char or EOF in the next 4 bytes. 1139 Find its exact position. */ 1140 while ((p + len) < end && !interesting[(unsigned char)p[len]]) 1141 ++len; 1142 } 1143 while (b->nl_translation_skippable == 1144 svn_tristate_true && /* can potentially skip EOLs */ 1145 p + len + 2 < end && /* not too close to EOF */ 1146 eol_unchanged (b, p + len)); /* EOL format already ok */ 1147 1148 while ((p + len) < end && !interesting[(unsigned char)p[len]]) 1149 len++; 1150 1151 if (len) 1152 { 1153 SVN_ERR(translate_write(dst, p, len)); 1154 p += len; 1155 } 1156 1157 /* Set up state according to the interesting character, if any. */ 1158 if (p < end) 1159 { 1160 switch (*p) 1161 { 1162 case '$': 1163 b->keyword_buf[b->keyword_off++] = *p++; 1164 break; 1165 case '\r': 1166 b->newline_buf[b->newline_off++] = *p++; 1167 break; 1168 case '\n': 1169 b->newline_buf[b->newline_off++] = *p++; 1170 1171 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1172 b->src_format, 1173 &b->src_format_len, 1174 b->newline_buf, 1175 b->newline_off, dst, 1176 b->translated_eol, b->repair)); 1177 1178 b->newline_off = 0; 1179 break; 1180 1181 } 1182 } 1183 } 1184 } 1185 else 1186 { 1187 if (b->newline_off) 1188 { 1189 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, 1190 b->src_format, &b->src_format_len, 1191 b->newline_buf, b->newline_off, 1192 dst, b->translated_eol, b->repair)); 1193 b->newline_off = 0; 1194 } 1195 1196 if (b->keyword_off) 1197 { 1198 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); 1199 b->keyword_off = 0; 1200 } 1201 } 1202 1203 return SVN_NO_ERROR; 1204} 1205 1206/* Baton for use with translated stream callbacks. */ 1207struct translated_stream_baton 1208{ 1209 /* Stream to take input from (before translation) on read 1210 /write output to (after translation) on write. */ 1211 svn_stream_t *stream; 1212 1213 /* Input/Output translation batons to make them separate chunk streams. */ 1214 struct translation_baton *in_baton, *out_baton; 1215 1216 /* Remembers whether any write operations have taken place; 1217 if so, we need to flush the output chunk stream. */ 1218 svn_boolean_t written; 1219 1220 /* Buffer to hold translated read data. */ 1221 svn_stringbuf_t *readbuf; 1222 1223 /* Offset of the first non-read character in readbuf. */ 1224 apr_size_t readbuf_off; 1225 1226 /* Buffer to hold read data 1227 between svn_stream_read() and translate_chunk(). */ 1228 char *buf; 1229#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1) 1230 1231 /* Pool for callback iterations */ 1232 apr_pool_t *iterpool; 1233}; 1234 1235 1236/* Implements svn_read_fn_t. */ 1237static svn_error_t * 1238translated_stream_read(void *baton, 1239 char *buffer, 1240 apr_size_t *len) 1241{ 1242 struct translated_stream_baton *b = baton; 1243 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE; 1244 apr_size_t unsatisfied = *len; 1245 apr_size_t off = 0; 1246 1247 /* Optimization for a frequent special case. The configuration parser (and 1248 a few others) reads the stream one byte at a time. All the memcpy, pool 1249 clearing etc. imposes a huge overhead in that case. In most cases, we 1250 can just take that single byte directly from the read buffer. 1251 1252 Since *len > 1 requires lots of code to be run anyways, we can afford 1253 the extra overhead of checking for *len == 1. 1254 1255 See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>. 1256 */ 1257 if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len) 1258 { 1259 /* Just take it from the read buffer */ 1260 *buffer = b->readbuf->data[b->readbuf_off++]; 1261 1262 return SVN_NO_ERROR; 1263 } 1264 1265 /* Standard code path. */ 1266 while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0) 1267 { 1268 apr_size_t to_copy; 1269 apr_size_t buffer_remainder; 1270 1271 svn_pool_clear(b->iterpool); 1272 /* fill read buffer, if necessary */ 1273 if (! (b->readbuf_off < b->readbuf->len)) 1274 { 1275 svn_stream_t *buf_stream; 1276 1277 svn_stringbuf_setempty(b->readbuf); 1278 b->readbuf_off = 0; 1279 SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen)); 1280 buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool); 1281 1282 SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf, 1283 readlen, b->iterpool)); 1284 1285 if (readlen != SVN__STREAM_CHUNK_SIZE) 1286 SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0, 1287 b->iterpool)); 1288 1289 SVN_ERR(svn_stream_close(buf_stream)); 1290 } 1291 1292 /* Satisfy from the read buffer */ 1293 buffer_remainder = b->readbuf->len - b->readbuf_off; 1294 to_copy = (buffer_remainder > unsatisfied) 1295 ? unsatisfied : buffer_remainder; 1296 memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy); 1297 off += to_copy; 1298 b->readbuf_off += to_copy; 1299 unsatisfied -= to_copy; 1300 } 1301 1302 *len -= unsatisfied; 1303 1304 return SVN_NO_ERROR; 1305} 1306 1307/* Implements svn_write_fn_t. */ 1308static svn_error_t * 1309translated_stream_write(void *baton, 1310 const char *buffer, 1311 apr_size_t *len) 1312{ 1313 struct translated_stream_baton *b = baton; 1314 svn_pool_clear(b->iterpool); 1315 1316 b->written = TRUE; 1317 return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool); 1318} 1319 1320/* Implements svn_close_fn_t. */ 1321static svn_error_t * 1322translated_stream_close(void *baton) 1323{ 1324 struct translated_stream_baton *b = baton; 1325 svn_error_t *err = NULL; 1326 1327 if (b->written) 1328 err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool); 1329 1330 err = svn_error_compose_create(err, svn_stream_close(b->stream)); 1331 1332 svn_pool_destroy(b->iterpool); 1333 1334 return svn_error_trace(err); 1335} 1336 1337 1338/* svn_stream_mark_t for translation streams. */ 1339typedef struct mark_translated_t 1340{ 1341 /* Saved translation state. */ 1342 struct translated_stream_baton saved_baton; 1343 1344 /* Mark set on the underlying stream. */ 1345 svn_stream_mark_t *mark; 1346} mark_translated_t; 1347 1348/* Implements svn_stream_mark_fn_t. */ 1349static svn_error_t * 1350translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) 1351{ 1352 mark_translated_t *mt; 1353 struct translated_stream_baton *b = baton; 1354 1355 mt = apr_palloc(pool, sizeof(*mt)); 1356 SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool)); 1357 1358 /* Save translation state. */ 1359 mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton, 1360 sizeof(*mt->saved_baton.in_baton)); 1361 mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton, 1362 sizeof(*mt->saved_baton.out_baton)); 1363 mt->saved_baton.written = b->written; 1364 mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool); 1365 mt->saved_baton.readbuf_off = b->readbuf_off; 1366 mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE); 1367 1368 *mark = (svn_stream_mark_t *)mt; 1369 1370 return SVN_NO_ERROR; 1371} 1372 1373/* Implements svn_stream_seek_fn_t. */ 1374static svn_error_t * 1375translated_stream_seek(void *baton, const svn_stream_mark_t *mark) 1376{ 1377 struct translated_stream_baton *b = baton; 1378 1379 if (mark != NULL) 1380 { 1381 const mark_translated_t *mt = (const mark_translated_t *)mark; 1382 1383 /* Flush output buffer if necessary. */ 1384 if (b->written) 1385 SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, 1386 b->iterpool)); 1387 1388 SVN_ERR(svn_stream_seek(b->stream, mt->mark)); 1389 1390 /* Restore translation state, avoiding new allocations. */ 1391 *b->in_baton = *mt->saved_baton.in_baton; 1392 *b->out_baton = *mt->saved_baton.out_baton; 1393 b->written = mt->saved_baton.written; 1394 svn_stringbuf_setempty(b->readbuf); 1395 svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data, 1396 mt->saved_baton.readbuf->len); 1397 b->readbuf_off = mt->saved_baton.readbuf_off; 1398 memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE); 1399 } 1400 else 1401 { 1402 SVN_ERR(svn_stream_reset(b->stream)); 1403 1404 b->in_baton->newline_off = 0; 1405 b->in_baton->keyword_off = 0; 1406 b->in_baton->src_format_len = 0; 1407 b->out_baton->newline_off = 0; 1408 b->out_baton->keyword_off = 0; 1409 b->out_baton->src_format_len = 0; 1410 1411 b->written = FALSE; 1412 svn_stringbuf_setempty(b->readbuf); 1413 b->readbuf_off = 0; 1414 } 1415 1416 return SVN_NO_ERROR; 1417} 1418 1419/* Implements svn_stream__is_buffered_fn_t. */ 1420static svn_boolean_t 1421translated_stream_is_buffered(void *baton) 1422{ 1423 struct translated_stream_baton *b = baton; 1424 return svn_stream__is_buffered(b->stream); 1425} 1426 1427svn_error_t * 1428svn_subst_read_specialfile(svn_stream_t **stream, 1429 const char *path, 1430 apr_pool_t *result_pool, 1431 apr_pool_t *scratch_pool) 1432{ 1433 apr_finfo_t finfo; 1434 svn_string_t *buf; 1435 1436 /* First determine what type of special file we are 1437 detranslating. */ 1438 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, 1439 scratch_pool)); 1440 1441 switch (finfo.filetype) { 1442 case APR_REG: 1443 /* Nothing special to do here, just create stream from the original 1444 file's contents. */ 1445 SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool)); 1446 break; 1447 1448 case APR_LNK: 1449 /* Determine the destination of the link. */ 1450 SVN_ERR(svn_io_read_link(&buf, path, scratch_pool)); 1451 *stream = svn_stream_from_string(svn_string_createf(result_pool, 1452 "link %s", 1453 buf->data), 1454 result_pool); 1455 break; 1456 1457 default: 1458 SVN_ERR_MALFUNCTION(); 1459 } 1460 1461 return SVN_NO_ERROR; 1462} 1463 1464/* Same as svn_subst_stream_translated(), except for the following. 1465 * 1466 * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream 1467 * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed, 1468 * otherwise leave it untouched. 1469 */ 1470static svn_stream_t * 1471stream_translated(svn_stream_t *stream, 1472 const char *eol_str, 1473 svn_boolean_t *translated_eol, 1474 svn_boolean_t repair, 1475 apr_hash_t *keywords, 1476 svn_boolean_t expand, 1477 apr_pool_t *result_pool) 1478{ 1479 struct translated_stream_baton *baton 1480 = apr_palloc(result_pool, sizeof(*baton)); 1481 svn_stream_t *s = svn_stream_create(baton, result_pool); 1482 1483 /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL 1484 so they have the same lifetime as the stream. */ 1485 if (eol_str) 1486 eol_str = apr_pstrdup(result_pool, eol_str); 1487 if (keywords) 1488 { 1489 if (apr_hash_count(keywords) == 0) 1490 keywords = NULL; 1491 else 1492 { 1493 /* deep copy the hash to make sure it's allocated in RESULT_POOL */ 1494 apr_hash_t *copy = apr_hash_make(result_pool); 1495 apr_hash_index_t *hi; 1496 apr_pool_t *subpool; 1497 1498 subpool = svn_pool_create(result_pool); 1499 for (hi = apr_hash_first(subpool, keywords); 1500 hi; hi = apr_hash_next(hi)) 1501 { 1502 const void *key; 1503 void *val; 1504 1505 apr_hash_this(hi, &key, NULL, &val); 1506 svn_hash_sets(copy, apr_pstrdup(result_pool, key), 1507 svn_string_dup(val, result_pool)); 1508 } 1509 svn_pool_destroy(subpool); 1510 1511 keywords = copy; 1512 } 1513 } 1514 1515 /* Setup the baton fields */ 1516 baton->stream = stream; 1517 baton->in_baton 1518 = create_translation_baton(eol_str, translated_eol, repair, keywords, 1519 expand, result_pool); 1520 baton->out_baton 1521 = create_translation_baton(eol_str, translated_eol, repair, keywords, 1522 expand, result_pool); 1523 baton->written = FALSE; 1524 baton->readbuf = svn_stringbuf_create_empty(result_pool); 1525 baton->readbuf_off = 0; 1526 baton->iterpool = svn_pool_create(result_pool); 1527 baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE); 1528 1529 /* Setup the stream methods */ 1530 svn_stream_set_read(s, translated_stream_read); 1531 svn_stream_set_write(s, translated_stream_write); 1532 svn_stream_set_close(s, translated_stream_close); 1533 svn_stream_set_mark(s, translated_stream_mark); 1534 svn_stream_set_seek(s, translated_stream_seek); 1535 svn_stream__set_is_buffered(s, translated_stream_is_buffered); 1536 1537 return s; 1538} 1539 1540svn_stream_t * 1541svn_subst_stream_translated(svn_stream_t *stream, 1542 const char *eol_str, 1543 svn_boolean_t repair, 1544 apr_hash_t *keywords, 1545 svn_boolean_t expand, 1546 apr_pool_t *result_pool) 1547{ 1548 return stream_translated(stream, eol_str, NULL, repair, keywords, expand, 1549 result_pool); 1550} 1551 1552/* Same as svn_subst_translate_cstring2(), except for the following. 1553 * 1554 * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an 1555 * end-of-line sequence was changed, or to FALSE otherwise. 1556 */ 1557static svn_error_t * 1558translate_cstring(const char **dst, 1559 svn_boolean_t *translated_eol, 1560 const char *src, 1561 const char *eol_str, 1562 svn_boolean_t repair, 1563 apr_hash_t *keywords, 1564 svn_boolean_t expand, 1565 apr_pool_t *pool) 1566{ 1567 svn_stringbuf_t *dst_stringbuf; 1568 svn_stream_t *dst_stream; 1569 apr_size_t len = strlen(src); 1570 1571 /* The easy way out: no translation needed, just copy. */ 1572 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) 1573 { 1574 *dst = apr_pstrmemdup(pool, src, len); 1575 return SVN_NO_ERROR; 1576 } 1577 1578 /* Create a stringbuf and wrapper stream to hold the output. */ 1579 dst_stringbuf = svn_stringbuf_create_empty(pool); 1580 dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); 1581 1582 if (translated_eol) 1583 *translated_eol = FALSE; 1584 1585 /* Another wrapper to translate the content. */ 1586 dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair, 1587 keywords, expand, pool); 1588 1589 /* Jam the text into the destination stream (to translate it). */ 1590 SVN_ERR(svn_stream_write(dst_stream, src, &len)); 1591 1592 /* Close the destination stream to flush unwritten data. */ 1593 SVN_ERR(svn_stream_close(dst_stream)); 1594 1595 *dst = dst_stringbuf->data; 1596 return SVN_NO_ERROR; 1597} 1598 1599svn_error_t * 1600svn_subst_translate_cstring2(const char *src, 1601 const char **dst, 1602 const char *eol_str, 1603 svn_boolean_t repair, 1604 apr_hash_t *keywords, 1605 svn_boolean_t expand, 1606 apr_pool_t *pool) 1607{ 1608 return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand, 1609 pool); 1610} 1611 1612/* Given a special file at SRC, generate a textual representation of 1613 it in a normal file at DST. Perform all allocations in POOL. */ 1614/* ### this should be folded into svn_subst_copy_and_translate3 */ 1615static svn_error_t * 1616detranslate_special_file(const char *src, const char *dst, 1617 svn_cancel_func_t cancel_func, void *cancel_baton, 1618 apr_pool_t *scratch_pool) 1619{ 1620 const char *dst_tmp; 1621 svn_stream_t *src_stream; 1622 svn_stream_t *dst_stream; 1623 1624 /* Open a temporary destination that we will eventually atomically 1625 rename into place. */ 1626 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, 1627 svn_dirent_dirname(dst, scratch_pool), 1628 svn_io_file_del_none, 1629 scratch_pool, scratch_pool)); 1630 SVN_ERR(svn_subst_read_specialfile(&src_stream, src, 1631 scratch_pool, scratch_pool)); 1632 SVN_ERR(svn_stream_copy3(src_stream, dst_stream, 1633 cancel_func, cancel_baton, scratch_pool)); 1634 1635 /* Do the atomic rename from our temporary location. */ 1636 return svn_error_trace(svn_io_file_rename(dst_tmp, dst, scratch_pool)); 1637} 1638 1639/* Creates a special file DST from the "normal form" located in SOURCE. 1640 * 1641 * All temporary allocations will be done in POOL. 1642 */ 1643static svn_error_t * 1644create_special_file_from_stream(svn_stream_t *source, const char *dst, 1645 apr_pool_t *pool) 1646{ 1647 svn_stringbuf_t *contents; 1648 svn_boolean_t eof; 1649 const char *identifier; 1650 const char *remainder; 1651 const char *dst_tmp; 1652 svn_boolean_t create_using_internal_representation = FALSE; 1653 1654 SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool)); 1655 1656 /* Separate off the identifier. The first space character delimits 1657 the identifier, after which any remaining characters are specific 1658 to the actual special file type being created. */ 1659 identifier = contents->data; 1660 for (remainder = identifier; *remainder; remainder++) 1661 { 1662 if (*remainder == ' ') 1663 { 1664 remainder++; 1665 break; 1666 } 1667 } 1668 1669 if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ", 1670 sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1)) 1671 { 1672 /* For symlinks, the type specific data is just a filesystem 1673 path that the symlink should reference. */ 1674 svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder, 1675 ".tmp", pool); 1676 1677 /* If we had an error, check to see if it was because symlinks are 1678 not supported on the platform. If so, fall back 1679 to using the internal representation. */ 1680 if (err) 1681 { 1682 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) 1683 { 1684 svn_error_clear(err); 1685 create_using_internal_representation = TRUE; 1686 } 1687 else 1688 return err; 1689 } 1690 } 1691 else 1692 { 1693 /* Just create a normal file using the internal special file 1694 representation. We don't want a commit of an unknown special 1695 file type to DoS all the clients. */ 1696 create_using_internal_representation = TRUE; 1697 } 1698 1699 /* If nothing else worked, write out the internal representation to 1700 a file that can be edited by the user. 1701 1702 ### this only writes the first line! 1703 */ 1704 if (create_using_internal_representation) 1705 { 1706 apr_file_t *new_file; 1707 SVN_ERR(svn_io_open_unique_file3(&new_file, &dst_tmp, 1708 svn_dirent_dirname(dst, pool), 1709 svn_io_file_del_none, 1710 pool, pool)); 1711 1712 SVN_ERR(svn_io_file_write_full(new_file, 1713 contents->data, contents->len, NULL, 1714 pool)); 1715 1716 SVN_ERR(svn_io_file_close(new_file, pool)); 1717 } 1718 1719 /* Do the atomic rename from our temporary location. */ 1720 return svn_io_file_rename(dst_tmp, dst, pool); 1721} 1722 1723 1724svn_error_t * 1725svn_subst_copy_and_translate4(const char *src, 1726 const char *dst, 1727 const char *eol_str, 1728 svn_boolean_t repair, 1729 apr_hash_t *keywords, 1730 svn_boolean_t expand, 1731 svn_boolean_t special, 1732 svn_cancel_func_t cancel_func, 1733 void *cancel_baton, 1734 apr_pool_t *pool) 1735{ 1736 svn_stream_t *src_stream; 1737 svn_stream_t *dst_stream; 1738 const char *dst_tmp; 1739 svn_error_t *err; 1740 svn_node_kind_t kind; 1741 svn_boolean_t path_special; 1742 1743 SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool)); 1744 1745 /* If this is a 'special' file, we may need to create it or 1746 detranslate it. */ 1747 if (special || path_special) 1748 { 1749 if (expand) 1750 { 1751 if (path_special) 1752 { 1753 /* We are being asked to create a special file from a special 1754 file. Do a temporary detranslation and work from there. */ 1755 1756 /* ### woah. this section just undoes all the work we already did 1757 ### to read the contents of the special file. shoot... the 1758 ### svn_subst_read_specialfile even checks the file type 1759 ### for us! */ 1760 1761 SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool)); 1762 } 1763 else 1764 { 1765 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); 1766 } 1767 1768 return svn_error_trace(create_special_file_from_stream(src_stream, 1769 dst, pool)); 1770 } 1771 /* else !expand */ 1772 1773 return svn_error_trace(detranslate_special_file(src, dst, 1774 cancel_func, 1775 cancel_baton, 1776 pool)); 1777 } 1778 1779 /* The easy way out: no translation needed, just copy. */ 1780 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) 1781 return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool)); 1782 1783 /* Open source file. */ 1784 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); 1785 1786 /* For atomicity, we translate to a tmp file and then rename the tmp file 1787 over the real destination. */ 1788 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, 1789 svn_dirent_dirname(dst, pool), 1790 svn_io_file_del_none, pool, pool)); 1791 1792 dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair, 1793 keywords, expand, pool); 1794 1795 /* ###: use cancel func/baton in place of NULL/NULL below. */ 1796 err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton, 1797 pool); 1798 if (err) 1799 { 1800 /* On errors, we have a pathname available. */ 1801 if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) 1802 err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err, 1803 _("File '%s' has inconsistent newlines"), 1804 svn_dirent_local_style(src, pool)); 1805 return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, 1806 FALSE, pool)); 1807 } 1808 1809 /* Now that dst_tmp contains the translated data, do the atomic rename. */ 1810 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool)); 1811 1812 /* Preserve the source file's permission bits. */ 1813 SVN_ERR(svn_io_copy_perms(src, dst, pool)); 1814 1815 return SVN_NO_ERROR; 1816} 1817 1818 1819/*** 'Special file' stream support */ 1820 1821struct special_stream_baton 1822{ 1823 svn_stream_t *read_stream; 1824 svn_stringbuf_t *write_content; 1825 svn_stream_t *write_stream; 1826 const char *path; 1827 apr_pool_t *pool; 1828}; 1829 1830 1831static svn_error_t * 1832read_handler_special(void *baton, char *buffer, apr_size_t *len) 1833{ 1834 struct special_stream_baton *btn = baton; 1835 1836 if (btn->read_stream) 1837 /* We actually found a file to read from */ 1838 return svn_stream_read(btn->read_stream, buffer, len); 1839 else 1840 return svn_error_createf(APR_ENOENT, NULL, 1841 "Can't read special file: File '%s' not found", 1842 svn_dirent_local_style(btn->path, btn->pool)); 1843} 1844 1845static svn_error_t * 1846write_handler_special(void *baton, const char *buffer, apr_size_t *len) 1847{ 1848 struct special_stream_baton *btn = baton; 1849 1850 return svn_stream_write(btn->write_stream, buffer, len); 1851} 1852 1853 1854static svn_error_t * 1855close_handler_special(void *baton) 1856{ 1857 struct special_stream_baton *btn = baton; 1858 1859 if (btn->write_content->len) 1860 { 1861 /* yeay! we received data and need to create a special file! */ 1862 1863 svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content, 1864 btn->pool); 1865 SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool)); 1866 } 1867 1868 return SVN_NO_ERROR; 1869} 1870 1871 1872svn_error_t * 1873svn_subst_create_specialfile(svn_stream_t **stream, 1874 const char *path, 1875 apr_pool_t *result_pool, 1876 apr_pool_t *scratch_pool) 1877{ 1878 struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton)); 1879 1880 baton->path = apr_pstrdup(result_pool, path); 1881 1882 /* SCRATCH_POOL may not exist after the function returns. */ 1883 baton->pool = result_pool; 1884 1885 baton->write_content = svn_stringbuf_create_empty(result_pool); 1886 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, 1887 result_pool); 1888 1889 *stream = svn_stream_create(baton, result_pool); 1890 svn_stream_set_write(*stream, write_handler_special); 1891 svn_stream_set_close(*stream, close_handler_special); 1892 1893 return SVN_NO_ERROR; 1894} 1895 1896 1897/* NOTE: this function is deprecated, but we cannot move it over to 1898 deprecated.c because it uses stuff private to this file, and it is 1899 not easily rebuilt in terms of "new" functions. */ 1900svn_error_t * 1901svn_subst_stream_from_specialfile(svn_stream_t **stream, 1902 const char *path, 1903 apr_pool_t *pool) 1904{ 1905 struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton)); 1906 svn_error_t *err; 1907 1908 baton->pool = pool; 1909 baton->path = apr_pstrdup(pool, path); 1910 1911 err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool); 1912 1913 /* File might not exist because we intend to create it upon close. */ 1914 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1915 { 1916 svn_error_clear(err); 1917 1918 /* Note: the special file is missing. the caller won't find out 1919 until the first read. Oh well. This function is deprecated anyways, 1920 so they can just deal with the weird behavior. */ 1921 baton->read_stream = NULL; 1922 } 1923 1924 baton->write_content = svn_stringbuf_create_empty(pool); 1925 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool); 1926 1927 *stream = svn_stream_create(baton, pool); 1928 svn_stream_set_read(*stream, read_handler_special); 1929 svn_stream_set_write(*stream, write_handler_special); 1930 svn_stream_set_close(*stream, close_handler_special); 1931 1932 return SVN_NO_ERROR; 1933} 1934 1935 1936 1937/*** String translation */ 1938svn_error_t * 1939svn_subst_translate_string2(svn_string_t **new_value, 1940 svn_boolean_t *translated_to_utf8, 1941 svn_boolean_t *translated_line_endings, 1942 const svn_string_t *value, 1943 const char *encoding, 1944 svn_boolean_t repair, 1945 apr_pool_t *result_pool, 1946 apr_pool_t *scratch_pool) 1947{ 1948 const char *val_utf8; 1949 const char *val_utf8_lf; 1950 1951 if (value == NULL) 1952 { 1953 *new_value = NULL; 1954 return SVN_NO_ERROR; 1955 } 1956 1957 if (encoding && !strcmp(encoding, "UTF-8")) 1958 { 1959 val_utf8 = value->data; 1960 } 1961 else if (encoding) 1962 { 1963 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data, 1964 encoding, scratch_pool)); 1965 } 1966 else 1967 { 1968 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool)); 1969 } 1970 1971 if (translated_to_utf8) 1972 *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0); 1973 1974 SVN_ERR(translate_cstring(&val_utf8_lf, 1975 translated_line_endings, 1976 val_utf8, 1977 "\n", /* translate to LF */ 1978 repair, 1979 NULL, /* no keywords */ 1980 FALSE, /* no expansion */ 1981 scratch_pool)); 1982 1983 *new_value = svn_string_create(val_utf8_lf, result_pool); 1984 return SVN_NO_ERROR; 1985} 1986 1987 1988svn_error_t * 1989svn_subst_detranslate_string(svn_string_t **new_value, 1990 const svn_string_t *value, 1991 svn_boolean_t for_output, 1992 apr_pool_t *pool) 1993{ 1994 svn_error_t *err; 1995 const char *val_neol; 1996 const char *val_nlocale_neol; 1997 1998 if (value == NULL) 1999 { 2000 *new_value = NULL; 2001 return SVN_NO_ERROR; 2002 } 2003 2004 SVN_ERR(svn_subst_translate_cstring2(value->data, 2005 &val_neol, 2006 APR_EOL_STR, /* 'native' eol */ 2007 FALSE, /* no repair */ 2008 NULL, /* no keywords */ 2009 FALSE, /* no expansion */ 2010 pool)); 2011 2012 if (for_output) 2013 { 2014 err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); 2015 if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) 2016 { 2017 val_nlocale_neol = 2018 svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool); 2019 svn_error_clear(err); 2020 } 2021 else if (err) 2022 return err; 2023 } 2024 else 2025 { 2026 err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); 2027 if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) 2028 { 2029 val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool); 2030 svn_error_clear(err); 2031 } 2032 else if (err) 2033 return err; 2034 } 2035 2036 *new_value = svn_string_create(val_nlocale_neol, pool); 2037 2038 return SVN_NO_ERROR; 2039} 2040