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