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