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