In the command-line client, forbid
[svn.git] / subversion / libsvn_subr / subst.c
blob6776f3710ac59301e5d3be92ef16f8f06684266b
1 /*
2 * subst.c : generic eol/keyword substitution routines
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <apr_general.h> /* for strcasecmp() */
27 #include <apr_pools.h>
28 #include <apr_tables.h>
29 #include <apr_file_io.h>
30 #include <apr_strings.h>
32 #include "svn_cmdline.h"
33 #include "svn_types.h"
34 #include "svn_string.h"
35 #include "svn_time.h"
36 #include "svn_path.h"
37 #include "svn_error.h"
38 #include "svn_utf.h"
39 #include "svn_io.h"
40 #include "svn_subst.h"
41 #include "svn_pools.h"
43 #include "svn_private_config.h"
45 /* The Repository Default EOL used for files which
46 * use the 'native' eol style.
48 #define SVN_SUBST__DEFAULT_EOL_STR "\n"
50 /**
51 * The textual elements of a detranslated special file. One of these
52 * strings must appear as the first element of any special file as it
53 * exists in the repository or the text base.
55 #define SVN_SUBST__SPECIAL_LINK_STR "link"
57 void
58 svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
59 const char **eol,
60 const char *value)
62 if (value == NULL)
64 /* property doesn't exist. */
65 *eol = NULL;
66 if (style)
67 *style = svn_subst_eol_style_none;
69 else if (! strcmp("native", value))
71 *eol = APR_EOL_STR; /* whee, a portability library! */
72 if (style)
73 *style = svn_subst_eol_style_native;
75 else if (! strcmp("LF", value))
77 *eol = "\n";
78 if (style)
79 *style = svn_subst_eol_style_fixed;
81 else if (! strcmp("CR", value))
83 *eol = "\r";
84 if (style)
85 *style = svn_subst_eol_style_fixed;
87 else if (! strcmp("CRLF", value))
89 *eol = "\r\n";
90 if (style)
91 *style = svn_subst_eol_style_fixed;
93 else
95 *eol = NULL;
96 if (style)
97 *style = svn_subst_eol_style_unknown;
102 svn_boolean_t
103 svn_subst_translation_required(svn_subst_eol_style_t style,
104 const char *eol,
105 apr_hash_t *keywords,
106 svn_boolean_t special,
107 svn_boolean_t force_eol_check)
109 return (special || keywords
110 || (style != svn_subst_eol_style_none && force_eol_check)
111 || (style == svn_subst_eol_style_native &&
112 strcmp(APR_EOL_STR, SVN_SUBST__DEFAULT_EOL_STR) != 0)
113 || (style == svn_subst_eol_style_fixed &&
114 strcmp(APR_EOL_STR, eol) != 0));
118 svn_error_t *
119 svn_subst_translate_to_normal_form(const char *src,
120 const char *dst,
121 svn_subst_eol_style_t eol_style,
122 const char *eol_str,
123 svn_boolean_t always_repair_eols,
124 apr_hash_t *keywords,
125 svn_boolean_t special,
126 apr_pool_t *pool)
129 if (eol_style == svn_subst_eol_style_native)
130 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
131 else if (! (eol_style == svn_subst_eol_style_fixed
132 || eol_style == svn_subst_eol_style_none))
133 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
135 return svn_subst_copy_and_translate3(src, dst, eol_str,
136 eol_style == svn_subst_eol_style_fixed
137 || always_repair_eols,
138 keywords,
139 FALSE /* contract keywords */,
140 special,
141 pool);
144 svn_error_t *
145 svn_subst_stream_translated_to_normal_form(svn_stream_t **stream,
146 svn_stream_t *source,
147 svn_subst_eol_style_t eol_style,
148 const char *eol_str,
149 svn_boolean_t always_repair_eols,
150 apr_hash_t *keywords,
151 apr_pool_t *pool)
153 if (eol_style == svn_subst_eol_style_native)
154 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
155 else if (! (eol_style == svn_subst_eol_style_fixed
156 || eol_style == svn_subst_eol_style_none))
157 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
159 *stream = svn_subst_stream_translated(source, eol_str,
160 eol_style == svn_subst_eol_style_fixed
161 || always_repair_eols,
162 keywords, FALSE, pool);
164 return SVN_NO_ERROR;
168 /* Helper function for svn_subst_build_keywords */
170 /* Given a printf-like format string, return a string with proper
171 * information filled in.
173 * Important API note: This function is the core of the implementation of
174 * svn_subst_build_keywords (all versions), and as such must implement the
175 * tolerance of NULL and zero inputs that that function's documention
176 * stipulates.
178 * The format codes:
180 * %a author of this revision
181 * %b basename of the URL of this file
182 * %d short format of date of this revision
183 * %D long format of date of this revision
184 * %r number of this revision
185 * %u URL of this file
186 * %% a literal %
188 * All memory is allocated out of @a pool.
190 static svn_string_t *
191 keyword_printf(const char *fmt,
192 const char *rev,
193 const char *url,
194 apr_time_t date,
195 const char *author,
196 apr_pool_t *pool)
198 svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool);
199 const char *cur;
200 int n;
202 for (;;)
204 cur = fmt;
206 while (*cur != '\0' && *cur != '%')
207 cur++;
209 if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
210 svn_stringbuf_appendbytes(value, fmt, n);
212 if (*cur == '\0')
213 break;
215 switch (cur[1])
217 case 'a': /* author of this revision */
218 if (author)
219 svn_stringbuf_appendcstr(value, author);
220 break;
221 case 'b': /* basename of this file */
222 if (url)
224 const char *base_name
225 = svn_path_uri_decode(svn_path_basename(url, pool), pool);
226 svn_stringbuf_appendcstr(value, base_name);
228 break;
229 case 'd': /* short format of date of this revision */
230 if (date)
232 apr_time_exp_t exploded_time;
233 const char *human;
235 apr_time_exp_gmt(&exploded_time, date);
237 human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
238 exploded_time.tm_year + 1900,
239 exploded_time.tm_mon + 1,
240 exploded_time.tm_mday,
241 exploded_time.tm_hour,
242 exploded_time.tm_min,
243 exploded_time.tm_sec);
245 svn_stringbuf_appendcstr(value, human);
247 break;
248 case 'D': /* long format of date of this revision */
249 if (date)
250 svn_stringbuf_appendcstr(value,
251 svn_time_to_human_cstring(date, pool));
252 break;
253 case 'r': /* number of this revision */
254 if (rev)
255 svn_stringbuf_appendcstr(value, rev);
256 break;
257 case 'u': /* URL of this file */
258 if (url)
259 svn_stringbuf_appendcstr(value, url);
260 break;
261 case '%': /* '%%' => a literal % */
262 svn_stringbuf_appendbytes(value, cur, 1);
263 break;
264 case '\0': /* '%' as the last character of the string. */
265 svn_stringbuf_appendbytes(value, cur, 1);
266 /* Now go back one character, since this was just a one character
267 * sequence, whereas all others are two characters, and we do not
268 * want to skip the null terminator entirely and carry on
269 * formatting random memory contents. */
270 cur--;
271 break;
272 default: /* Unrecognized code, just print it literally. */
273 svn_stringbuf_appendbytes(value, cur, 2);
274 break;
277 /* Format code is processed - skip it, and get ready for next chunk. */
278 fmt = cur + 2;
281 return svn_string_create_from_buf(value, pool);
284 /* Convert an old-style svn_subst_keywords_t struct * into a new-style
285 * keywords hash. Keyword values are shallow copies, so the produced
286 * hash must not be assumed to have lifetime longer than the struct it
287 * is based on. A NULL input causes a NULL output. */
288 static apr_hash_t *
289 kwstruct_to_kwhash(const svn_subst_keywords_t *kwstruct,
290 apr_pool_t *pool)
292 apr_hash_t *kwhash;
294 if (kwstruct == NULL)
295 return NULL;
297 kwhash = apr_hash_make(pool);
299 if (kwstruct->revision)
301 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_LONG,
302 APR_HASH_KEY_STRING, kwstruct->revision);
303 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_MEDIUM,
304 APR_HASH_KEY_STRING, kwstruct->revision);
305 apr_hash_set(kwhash, SVN_KEYWORD_REVISION_SHORT,
306 APR_HASH_KEY_STRING, kwstruct->revision);
308 if (kwstruct->date)
310 apr_hash_set(kwhash, SVN_KEYWORD_DATE_LONG,
311 APR_HASH_KEY_STRING, kwstruct->date);
312 apr_hash_set(kwhash, SVN_KEYWORD_DATE_SHORT,
313 APR_HASH_KEY_STRING, kwstruct->date);
315 if (kwstruct->author)
317 apr_hash_set(kwhash, SVN_KEYWORD_AUTHOR_LONG,
318 APR_HASH_KEY_STRING, kwstruct->author);
319 apr_hash_set(kwhash, SVN_KEYWORD_AUTHOR_SHORT,
320 APR_HASH_KEY_STRING, kwstruct->author);
322 if (kwstruct->url)
324 apr_hash_set(kwhash, SVN_KEYWORD_URL_LONG,
325 APR_HASH_KEY_STRING, kwstruct->url);
326 apr_hash_set(kwhash, SVN_KEYWORD_URL_SHORT,
327 APR_HASH_KEY_STRING, kwstruct->url);
329 if (kwstruct->id)
331 apr_hash_set(kwhash, SVN_KEYWORD_ID,
332 APR_HASH_KEY_STRING, kwstruct->id);
335 return kwhash;
338 svn_error_t *
339 svn_subst_build_keywords(svn_subst_keywords_t *kw,
340 const char *keywords_val,
341 const char *rev,
342 const char *url,
343 apr_time_t date,
344 const char *author,
345 apr_pool_t *pool)
347 apr_hash_t *kwhash;
348 const svn_string_t *val;
350 SVN_ERR(svn_subst_build_keywords2(&kwhash, keywords_val, rev,
351 url, date, author, pool));
353 /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are
354 * replicating here, is to write to a slot in the svn_subst_keywords_t
355 * only if the relevant keyword was present in keywords_val, otherwise
356 * leaving that slot untouched. */
358 val = apr_hash_get(kwhash, SVN_KEYWORD_REVISION_LONG, APR_HASH_KEY_STRING);
359 if (val)
360 kw->revision = val;
362 val = apr_hash_get(kwhash, SVN_KEYWORD_DATE_LONG, APR_HASH_KEY_STRING);
363 if (val)
364 kw->date = val;
366 val = apr_hash_get(kwhash, SVN_KEYWORD_AUTHOR_LONG, APR_HASH_KEY_STRING);
367 if (val)
368 kw->author = val;
370 val = apr_hash_get(kwhash, SVN_KEYWORD_URL_LONG, APR_HASH_KEY_STRING);
371 if (val)
372 kw->url = val;
374 val = apr_hash_get(kwhash, SVN_KEYWORD_ID, APR_HASH_KEY_STRING);
375 if (val)
376 kw->id = val;
378 return SVN_NO_ERROR;
381 svn_error_t *
382 svn_subst_build_keywords2(apr_hash_t **kw,
383 const char *keywords_val,
384 const char *rev,
385 const char *url,
386 apr_time_t date,
387 const char *author,
388 apr_pool_t *pool)
390 apr_array_header_t *keyword_tokens;
391 int i;
392 *kw = apr_hash_make(pool);
394 keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
395 TRUE /* chop */, pool);
397 for (i = 0; i < keyword_tokens->nelts; ++i)
399 const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
401 if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
402 || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
403 || (! strcasecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
405 svn_string_t *revision_val;
407 revision_val = keyword_printf("%r", rev, url, date, author, pool);
408 apr_hash_set(*kw, SVN_KEYWORD_REVISION_LONG,
409 APR_HASH_KEY_STRING, revision_val);
410 apr_hash_set(*kw, SVN_KEYWORD_REVISION_MEDIUM,
411 APR_HASH_KEY_STRING, revision_val);
412 apr_hash_set(*kw, SVN_KEYWORD_REVISION_SHORT,
413 APR_HASH_KEY_STRING, revision_val);
415 else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
416 || (! strcasecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
418 svn_string_t *date_val;
420 date_val = keyword_printf("%D", rev, url, date, author, pool);
421 apr_hash_set(*kw, SVN_KEYWORD_DATE_LONG,
422 APR_HASH_KEY_STRING, date_val);
423 apr_hash_set(*kw, SVN_KEYWORD_DATE_SHORT,
424 APR_HASH_KEY_STRING, date_val);
426 else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
427 || (! strcasecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
429 svn_string_t *author_val;
431 author_val = keyword_printf("%a", rev, url, date, author, pool);
432 apr_hash_set(*kw, SVN_KEYWORD_AUTHOR_LONG,
433 APR_HASH_KEY_STRING, author_val);
434 apr_hash_set(*kw, SVN_KEYWORD_AUTHOR_SHORT,
435 APR_HASH_KEY_STRING, author_val);
437 else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
438 || (! strcasecmp(keyword, SVN_KEYWORD_URL_SHORT)))
440 svn_string_t *url_val;
442 url_val = keyword_printf("%u", rev, url, date, author, pool);
443 apr_hash_set(*kw, SVN_KEYWORD_URL_LONG,
444 APR_HASH_KEY_STRING, url_val);
445 apr_hash_set(*kw, SVN_KEYWORD_URL_SHORT,
446 APR_HASH_KEY_STRING, url_val);
448 else if ((! strcasecmp(keyword, SVN_KEYWORD_ID)))
450 svn_string_t *id_val;
452 id_val = keyword_printf("%b %r %d %a", rev, url, date, author,
453 pool);
454 apr_hash_set(*kw, SVN_KEYWORD_ID,
455 APR_HASH_KEY_STRING, id_val);
459 return SVN_NO_ERROR;
463 /*** Helpers for svn_subst_translate_stream2 ***/
466 /* Write out LEN bytes of BUF into STREAM. */
467 static svn_error_t *
468 translate_write(svn_stream_t *stream,
469 const void *buf,
470 apr_size_t len)
472 apr_size_t wrote = len;
473 svn_error_t *write_err = svn_stream_write(stream, buf, &wrote);
474 if ((write_err) || (len != wrote))
475 return write_err;
477 return SVN_NO_ERROR;
481 /* Perform the substition of VALUE into keyword string BUF (with len
482 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
483 *LEN to the new size of the substituted result. Return TRUE if all
484 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
485 contracted, else it will be expanded. */
486 static svn_boolean_t
487 translate_keyword_subst(char *buf,
488 apr_size_t *len,
489 const char *keyword,
490 apr_size_t keyword_len,
491 const svn_string_t *value)
493 char *buf_ptr;
495 /* Make sure we gotz good stuffs. */
496 assert(*len <= SVN_KEYWORD_MAX_LEN);
497 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
499 /* Need at least a keyword and two $'s. */
500 if (*len < keyword_len + 2)
501 return FALSE;
503 /* The keyword needs to match what we're looking for. */
504 if (strncmp(buf + 1, keyword, keyword_len))
505 return FALSE;
507 buf_ptr = buf + 1 + keyword_len;
509 /* Check for fixed-length expansion.
510 * The format of fixed length keyword and its data is
511 * Unexpanded keyword: "$keyword:: $"
512 * Expanded keyword: "$keyword:: value $"
513 * Expanded kw with filling: "$keyword:: value $"
514 * Truncated keyword: "$keyword:: longval#$"
516 if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
517 && (buf_ptr[1] == ':') /* second char after keyword is ':' */
518 && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
519 && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */
520 || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
521 character */
522 && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */
524 /* This is fixed length keyword, so *len remains unchanged */
525 apr_size_t max_value_len = *len - (6 + keyword_len);
527 if (! value)
529 /* no value, so unexpand */
530 buf_ptr += 2;
531 while (*buf_ptr != '$')
532 *(buf_ptr++) = ' ';
534 else
536 if (value->len <= max_value_len)
537 { /* replacement not as long as template, pad with spaces */
538 strncpy(buf_ptr + 3, value->data, value->len);
539 buf_ptr += 3 + value->len;
540 while (*buf_ptr != '$')
541 *(buf_ptr++) = ' ';
543 else
545 /* replacement needs truncating */
546 strncpy(buf_ptr + 3, value->data, max_value_len);
547 buf[*len - 2] = '#';
548 buf[*len - 1] = '$';
551 return TRUE;
554 /* Check for unexpanded keyword. */
555 else if (buf_ptr[0] == '$') /* "$keyword$" */
557 /* unexpanded... */
558 if (value)
560 /* ...so expand. */
561 buf_ptr[0] = ':';
562 buf_ptr[1] = ' ';
563 if (value->len)
565 apr_size_t vallen = value->len;
567 /* "$keyword: value $" */
568 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
569 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
570 strncpy(buf_ptr + 2, value->data, vallen);
571 buf_ptr[2 + vallen] = ' ';
572 buf_ptr[2 + vallen + 1] = '$';
573 *len = 5 + keyword_len + vallen;
575 else
577 /* "$keyword: $" */
578 buf_ptr[2] = '$';
579 *len = 4 + keyword_len;
582 else
584 /* ...but do nothing. */
586 return TRUE;
589 /* Check for expanded keyword. */
590 else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
591 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
592 && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */
593 && (buf[*len - 2] == ' '))
594 || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
595 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
596 && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */
598 /* expanded... */
599 if (! value)
601 /* ...so unexpand. */
602 buf_ptr[0] = '$';
603 *len = 2 + keyword_len;
605 else
607 /* ...so re-expand. */
608 buf_ptr[0] = ':';
609 buf_ptr[1] = ' ';
610 if (value->len)
612 apr_size_t vallen = value->len;
614 /* "$keyword: value $" */
615 if (vallen > (SVN_KEYWORD_MAX_LEN - 5))
616 vallen = SVN_KEYWORD_MAX_LEN - 5;
617 strncpy(buf_ptr + 2, value->data, vallen);
618 buf_ptr[2 + vallen] = ' ';
619 buf_ptr[2 + vallen + 1] = '$';
620 *len = 5 + keyword_len + vallen;
622 else
624 /* "$keyword: $" */
625 buf_ptr[2] = '$';
626 *len = 4 + keyword_len;
629 return TRUE;
632 return FALSE;
635 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
636 trying to match one of the keyword names in KEYWORDS. If such a
637 keyword is found, update *KEYWORD_NAME with the keyword name and
638 return TRUE. */
639 static svn_boolean_t
640 match_keyword(char *buf,
641 apr_size_t len,
642 char *keyword_name,
643 apr_hash_t *keywords)
645 apr_size_t i;
647 /* Early return for ignored keywords */
648 if (! keywords)
649 return FALSE;
651 /* Extract the name of the keyword */
652 for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
653 keyword_name[i] = buf[i + 1];
654 keyword_name[i] = '\0';
656 return apr_hash_get(keywords, keyword_name, APR_HASH_KEY_STRING) != NULL;
659 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
660 optionally perform the substitution in place, update *LEN with
661 the new length of the translated keyword string, and return TRUE.
662 If this buffer doesn't contain a known keyword pattern, leave BUF
663 and *LEN untouched and return FALSE.
665 See the docstring for svn_subst_copy_and_translate for how the
666 EXPAND and KEYWORDS parameters work.
668 NOTE: It is assumed that BUF has been allocated to be at least
669 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
670 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
671 which would result in a keyword string which is greater than
672 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
673 that the resultant keyword string is still valid (begins with
674 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
675 static svn_boolean_t
676 translate_keyword(char *buf,
677 apr_size_t *len,
678 const char *keyword_name,
679 svn_boolean_t expand,
680 apr_hash_t *keywords)
682 const svn_string_t *value;
684 /* Make sure we gotz good stuffs. */
685 assert(*len <= SVN_KEYWORD_MAX_LEN);
686 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
688 /* Early return for ignored keywords */
689 if (! keywords)
690 return FALSE;
692 value = apr_hash_get(keywords, keyword_name, APR_HASH_KEY_STRING);
694 if (value)
696 return translate_keyword_subst(buf, len,
697 keyword_name, strlen(keyword_name),
698 expand ? value : NULL);
701 return FALSE;
705 /* Translate NEWLINE_BUF (length of NEWLINE_LEN) to the newline format
706 specified in EOL_STR (length of EOL_STR_LEN), and write the
707 translated thing to FILE (whose path is DST_PATH).
709 SRC_FORMAT (length *SRC_FORMAT_LEN) is a cache of the first newline
710 found while processing SRC_PATH. If the current newline is not the
711 same style as that of SRC_FORMAT, look to the REPAIR parameter. If
712 REPAIR is TRUE, ignore the inconsistency, else return an
713 SVN_ERR_IO_INCONSISTENT_EOL error. If we are examining the first
714 newline in the file, copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
715 use for later consistency checks. */
716 static svn_error_t *
717 translate_newline(const char *eol_str,
718 apr_size_t eol_str_len,
719 char *src_format,
720 apr_size_t *src_format_len,
721 char *newline_buf,
722 apr_size_t newline_len,
723 svn_stream_t *dst,
724 svn_boolean_t repair)
726 /* If this is the first newline we've seen, cache it
727 future comparisons, else compare it with our cache to
728 check for consistency. */
729 if (*src_format_len)
731 /* Comparing with cache. If we are inconsistent and
732 we are NOT repairing the file, generate an error! */
733 if ((! repair) &&
734 ((*src_format_len != newline_len) ||
735 (strncmp(src_format, newline_buf, newline_len))))
736 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
738 else
740 /* This is our first line ending, so cache it before
741 handling it. */
742 strncpy(src_format, newline_buf, newline_len);
743 *src_format_len = newline_len;
745 /* Translate the newline */
746 return translate_write(dst, eol_str, eol_str_len);
751 /*** Public interfaces. ***/
753 svn_boolean_t
754 svn_subst_keywords_differ(const svn_subst_keywords_t *a,
755 const svn_subst_keywords_t *b,
756 svn_boolean_t compare_values)
758 if (((a == NULL) && (b == NULL)) /* no A or B */
759 /* no A, and B has no contents */
760 || ((a == NULL)
761 && (b->revision == NULL)
762 && (b->date == NULL)
763 && (b->author == NULL)
764 && (b->url == NULL))
765 /* no B, and A has no contents */
766 || ((b == NULL) && (a->revision == NULL)
767 && (a->date == NULL)
768 && (a->author == NULL)
769 && (a->url == NULL))
770 /* neither A nor B has any contents */
771 || ((a != NULL) && (b != NULL)
772 && (b->revision == NULL)
773 && (b->date == NULL)
774 && (b->author == NULL)
775 && (b->url == NULL)
776 && (a->revision == NULL)
777 && (a->date == NULL)
778 && (a->author == NULL)
779 && (a->url == NULL)))
781 return FALSE;
783 else if ((a == NULL) || (b == NULL))
784 return TRUE;
786 /* Else both A and B have some keywords. */
788 if ((! a->revision) != (! b->revision))
789 return TRUE;
790 else if ((compare_values && (a->revision != NULL))
791 && (strcmp(a->revision->data, b->revision->data) != 0))
792 return TRUE;
794 if ((! a->date) != (! b->date))
795 return TRUE;
796 else if ((compare_values && (a->date != NULL))
797 && (strcmp(a->date->data, b->date->data) != 0))
798 return TRUE;
800 if ((! a->author) != (! b->author))
801 return TRUE;
802 else if ((compare_values && (a->author != NULL))
803 && (strcmp(a->author->data, b->author->data) != 0))
804 return TRUE;
806 if ((! a->url) != (! b->url))
807 return TRUE;
808 else if ((compare_values && (a->url != NULL))
809 && (strcmp(a->url->data, b->url->data) != 0))
810 return TRUE;
812 /* Else we never found a difference, so they must be the same. */
814 return FALSE;
817 svn_boolean_t
818 svn_subst_keywords_differ2(apr_hash_t *a,
819 apr_hash_t *b,
820 svn_boolean_t compare_values,
821 apr_pool_t *pool)
823 apr_hash_index_t *hi;
824 unsigned int a_count, b_count;
826 /* An empty hash is logically equal to a NULL,
827 * as far as this API is concerned. */
828 a_count = (a == NULL) ? 0 : apr_hash_count(a);
829 b_count = (b == NULL) ? 0 : apr_hash_count(b);
831 if (a_count != b_count)
832 return TRUE;
834 if (a_count == 0)
835 return FALSE;
837 /* The hashes are both non-NULL, and have the same number of items.
838 * We must check that every item of A is present in B. */
839 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
841 const void *key;
842 apr_ssize_t klen;
843 void *void_a_val;
844 svn_string_t *a_val, *b_val;
846 apr_hash_this(hi, &key, &klen, &void_a_val);
847 a_val = void_a_val;
848 b_val = apr_hash_get(b, key, klen);
850 if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
851 return TRUE;
854 return FALSE;
857 svn_error_t *
858 svn_subst_translate_stream2(svn_stream_t *s, /* src stream */
859 svn_stream_t *d, /* dst stream */
860 const char *eol_str,
861 svn_boolean_t repair,
862 const svn_subst_keywords_t *keywords,
863 svn_boolean_t expand,
864 apr_pool_t *pool)
866 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
868 return svn_subst_translate_stream3(s, d, eol_str, repair, kh, expand, pool);
871 /* Baton for translate_chunk() to store its state in. */
872 struct translation_baton
874 const char *eol_str;
875 svn_boolean_t repair;
876 apr_hash_t *keywords;
877 svn_boolean_t expand;
879 /* Characters (excluding the terminating NUL character) which
880 may trigger a translation action, hence are 'interesting' */
881 const char *interesting;
883 /* Length of the string EOL_STR points to. */
884 apr_size_t eol_str_len;
886 /* Buffer to cache any newline state between translation chunks */
887 char newline_buf[2];
889 /* Offset (within newline_buf) of the first *unused* character */
890 apr_size_t newline_off;
892 /* Buffer to cache keyword-parsing state between translation chunks */
893 char keyword_buf[SVN_KEYWORD_MAX_LEN];
895 /* Offset (within keyword-buf) to the first *unused* character */
896 apr_size_t keyword_off;
898 /* EOL style used in the chunk-source */
899 char src_format[2];
901 /* Length of the EOL style string found in the chunk-source,
902 or zero if none encountered yet */
903 apr_size_t src_format_len;
907 /* Allocate a baton for use with translate_chunk() in POOL and
908 * initialize it for the first iteration.
910 * The caller must assure that EOL_STR and KEYWORDS at least
911 * have the same life time as that of POOL.
915 static struct translation_baton *
916 create_translation_baton(const char *eol_str,
917 svn_boolean_t repair,
918 apr_hash_t *keywords,
919 svn_boolean_t expand,
920 apr_pool_t *pool)
922 struct translation_baton *b = apr_palloc(pool, sizeof(*b));
924 /* For efficiency, convert an empty set of keywords to NULL. */
925 if (keywords && (apr_hash_count(keywords) == 0))
926 keywords = NULL;
928 b->eol_str = eol_str;
929 b->eol_str_len = eol_str ? strlen(eol_str) : 0;
930 b->repair = repair;
931 b->keywords = keywords;
932 b->expand = expand;
933 b->interesting = (eol_str && keywords) ? "$\r\n" : eol_str ? "\r\n" : "$";
934 b->newline_off = 0;
935 b->keyword_off = 0;
936 b->src_format_len = 0;
938 return b;
941 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
942 * according to the settings and state stored in baton B.
944 * Write output to stream DST.
946 * To finish a series of chunk translations, flush all buffers by calling
947 * this routine with a NULL value for BUF.
949 * Use POOL for temporary allocations.
951 static svn_error_t *
952 translate_chunk(svn_stream_t *dst,
953 struct translation_baton *b,
954 const char *buf,
955 apr_size_t buflen,
956 apr_pool_t *pool)
958 const char *p;
959 apr_size_t len;
961 if (buf)
963 /* precalculate some oft-used values */
964 const char *end = buf + buflen;
965 const char *interesting = b->interesting;
966 apr_size_t next_sign_off = 0;
968 /* At the beginning of this loop, assume that we might be in an
969 * interesting state, i.e. with data in the newline or keyword
970 * buffer. First try to get to the boring state so we can copy
971 * a run of boring characters; then try to get back to the
972 * interesting state by processing an interesting character,
973 * and repeat. */
974 for (p = buf; p < end;)
976 /* Try to get to the boring state, if necessary. */
977 if (b->newline_off)
979 if (*p == '\n')
980 b->newline_buf[b->newline_off++] = *p++;
982 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
983 b->src_format,
984 &b->src_format_len, b->newline_buf,
985 b->newline_off, dst, b->repair));
987 b->newline_off = 0;
989 else if (b->keyword_off && *p == '$')
991 svn_boolean_t keyword_matches;
992 char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
994 /* If keyword is matched, but not correctly translated, try to
995 * look for the next ending '$'. */
996 b->keyword_buf[b->keyword_off++] = *p++;
997 keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
998 keyword_name, b->keywords);
999 if (keyword_matches == FALSE)
1001 /* reuse the ending '$' */
1002 p--;
1003 b->keyword_off--;
1006 if (keyword_matches == FALSE ||
1007 translate_keyword(b->keyword_buf, &b->keyword_off,
1008 keyword_name, b->expand, b->keywords) ||
1009 b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1011 /* write out non-matching text or translated keyword */
1012 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1014 next_sign_off = 0;
1015 b->keyword_off = 0;
1017 else
1019 if (next_sign_off == 0)
1020 next_sign_off = b->keyword_off - 1;
1022 continue;
1025 else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1026 || (b->keyword_off && (*p == '\r' || *p == '\n')))
1028 if (next_sign_off > 0)
1030 /* rolling back, continue with next '$' in keyword_buf */
1031 p -= (b->keyword_off - next_sign_off);
1032 b->keyword_off = next_sign_off;
1033 next_sign_off = 0;
1035 /* No closing '$' found; flush the keyword buffer. */
1036 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1038 b->keyword_off = 0;
1040 else if (b->keyword_off)
1042 b->keyword_buf[b->keyword_off++] = *p++;
1043 continue;
1046 /* We're in the boring state; look for interest characters. */
1047 len = 0;
1049 /* We wanted memcspn(), but lacking that, the loop below has
1050 the same effect.
1052 Also, skip NUL characters explicitly, since strchr()
1053 considers them part of the string argument,
1054 but we don't consider them interesting
1056 while ((p + len) < end
1057 && (! p[len] || ! strchr(interesting, p[len])))
1058 len++;
1060 if (len)
1061 SVN_ERR(translate_write(dst, p, len));
1063 p += len;
1065 /* Set up state according to the interesting character, if any. */
1066 if (p < end)
1068 switch (*p)
1070 case '$':
1071 b->keyword_buf[b->keyword_off++] = *p++;
1072 break;
1073 case '\r':
1074 b->newline_buf[b->newline_off++] = *p++;
1075 break;
1076 case '\n':
1077 b->newline_buf[b->newline_off++] = *p++;
1079 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1080 b->src_format,
1081 &b->src_format_len,
1082 b->newline_buf,
1083 b->newline_off, dst, b->repair));
1085 b->newline_off = 0;
1086 break;
1092 else
1094 if (b->newline_off)
1096 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1097 b->src_format, &b->src_format_len,
1098 b->newline_buf, b->newline_off,
1099 dst, b->repair));
1100 b->newline_off = 0;
1103 if (b->keyword_off)
1105 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1106 b->keyword_off = 0;
1110 return SVN_NO_ERROR;
1113 /* Baton for use with translated stream callbacks. */
1114 struct translated_stream_baton
1116 /* Stream to take input from (before translation) on read
1117 /write output to (after translation) on write. */
1118 svn_stream_t *stream;
1120 /* Input/Output translation batons to make them separate chunk streams. */
1121 struct translation_baton *in_baton, *out_baton;
1123 /* Remembers whether any write operations have taken place;
1124 if so, we need to flush the output chunk stream. */
1125 svn_boolean_t written;
1127 /* Buffer to hold translated read data. */
1128 svn_stringbuf_t *readbuf;
1130 /* Offset of the first non-read character in readbuf. */
1131 apr_size_t readbuf_off;
1133 /* Buffer to hold read data
1134 between svn_stream_read() and translate_chunk(). */
1135 char *buf;
1137 /* Pool in which (only!) this baton is allocated. */
1138 apr_pool_t *pool;
1140 /* Pool for callback iterations */
1141 apr_pool_t *iterpool;
1145 static svn_error_t *
1146 translated_stream_read(void *baton,
1147 char *buffer,
1148 apr_size_t *len)
1150 struct translated_stream_baton *b = baton;
1151 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1152 apr_size_t unsatisfied = *len;
1153 apr_size_t off = 0;
1154 apr_pool_t *iterpool;
1156 iterpool = b->iterpool;
1157 while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1159 apr_size_t to_copy;
1160 apr_size_t buffer_remainder;
1162 svn_pool_clear(iterpool);
1163 /* fill read buffer, if necessary */
1164 if (! (b->readbuf_off < b->readbuf->len))
1166 svn_stream_t *buf_stream;
1168 svn_stringbuf_setempty(b->readbuf);
1169 b->readbuf_off = 0;
1170 SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen));
1171 buf_stream = svn_stream_from_stringbuf(b->readbuf, iterpool);
1173 SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1174 readlen, iterpool));
1176 if (readlen != SVN__STREAM_CHUNK_SIZE)
1177 SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1178 iterpool));
1180 SVN_ERR(svn_stream_close(buf_stream));
1183 /* Satisfy from the read buffer */
1184 buffer_remainder = b->readbuf->len - b->readbuf_off;
1185 to_copy = (buffer_remainder > unsatisfied)
1186 ? unsatisfied : buffer_remainder;
1187 memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1188 off += to_copy;
1189 b->readbuf_off += to_copy;
1190 unsatisfied -= to_copy;
1193 *len -= unsatisfied;
1195 return SVN_NO_ERROR;
1198 static svn_error_t *
1199 translated_stream_write(void *baton,
1200 const char *buffer,
1201 apr_size_t *len)
1203 struct translated_stream_baton *b = baton;
1204 svn_pool_clear(b->iterpool);
1206 b->written = TRUE;
1207 SVN_ERR(translate_chunk(b->stream, b->out_baton, buffer, *len,
1208 b->iterpool));
1210 return SVN_NO_ERROR;
1213 static svn_error_t *
1214 translated_stream_close(void *baton)
1216 struct translated_stream_baton *b = baton;
1218 if (b->written)
1219 SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool));
1221 SVN_ERR(svn_stream_close(b->stream));
1223 svn_pool_destroy(b->pool); /* Also destroys the baton itself */
1224 return SVN_NO_ERROR;
1227 /* Given a special file at SRC, set TRANSLATED_STREAM_P to a stream
1228 with the textual representation of it. Perform all allocations in POOL. */
1229 static svn_error_t *
1230 detranslated_stream_special(svn_stream_t **translated_stream_p,
1231 const char *src,
1232 apr_pool_t *pool)
1234 apr_finfo_t finfo;
1235 apr_file_t *s;
1236 svn_string_t *buf;
1237 svn_stringbuf_t *strbuf;
1239 /* First determine what type of special file we are
1240 detranslating. */
1241 SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1243 switch (finfo.filetype) {
1244 case APR_REG:
1245 /* Nothing special to do here, just create stream from the original
1246 file's contents. */
1247 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1248 APR_OS_DEFAULT, pool));
1249 *translated_stream_p = svn_stream_from_aprfile2(s, FALSE, pool);
1251 break;
1252 case APR_LNK:
1253 /* Determine the destination of the link. */
1254 SVN_ERR(svn_io_read_link(&buf, src, pool));
1255 strbuf = svn_stringbuf_createf(pool, "link %s", buf->data);
1256 *translated_stream_p = svn_stream_from_stringbuf(strbuf, pool);
1258 break;
1259 default:
1260 abort();
1263 return SVN_NO_ERROR;
1266 svn_error_t *
1267 svn_subst_stream_detranslated(svn_stream_t **stream_p,
1268 const char *src,
1269 svn_subst_eol_style_t eol_style,
1270 const char *eol_str,
1271 svn_boolean_t always_repair_eols,
1272 apr_hash_t *keywords,
1273 svn_boolean_t special,
1274 apr_pool_t *pool)
1276 apr_file_t *file_h;
1277 svn_stream_t *src_stream;
1279 if (special)
1280 return detranslated_stream_special(stream_p, src, pool);
1282 if (eol_style == svn_subst_eol_style_native)
1283 eol_str = SVN_SUBST__DEFAULT_EOL_STR;
1284 else if (! (eol_style == svn_subst_eol_style_fixed
1285 || eol_style == svn_subst_eol_style_none))
1286 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
1288 SVN_ERR(svn_io_file_open(&file_h, src, APR_READ,
1289 APR_OS_DEFAULT, pool));
1291 src_stream = svn_stream_from_aprfile2(file_h, FALSE, pool);
1293 *stream_p = svn_subst_stream_translated(
1294 src_stream, eol_str,
1295 eol_style == svn_subst_eol_style_fixed || always_repair_eols,
1296 keywords, FALSE, pool);
1298 return SVN_NO_ERROR;
1301 svn_stream_t *
1302 svn_subst_stream_translated(svn_stream_t *stream,
1303 const char *eol_str,
1304 svn_boolean_t repair,
1305 apr_hash_t *keywords,
1306 svn_boolean_t expand,
1307 apr_pool_t *pool)
1309 apr_pool_t *baton_pool = svn_pool_create(pool);
1310 struct translated_stream_baton *baton
1311 = apr_palloc(baton_pool, sizeof(*baton));
1312 svn_stream_t *s = svn_stream_create(baton, baton_pool);
1314 /* Make sure EOL_STR and KEYWORDS are allocated in POOL, as
1315 required by create_translation_baton() */
1316 if (eol_str)
1317 eol_str = apr_pstrdup(baton_pool, eol_str);
1318 if (keywords)
1320 if (apr_hash_count(keywords) == 0)
1321 keywords = NULL;
1322 else
1324 /* deep copy the hash to make sure it's allocated in POOL */
1325 apr_hash_t *copy = apr_hash_make(baton_pool);
1326 apr_hash_index_t *hi;
1328 for (hi = apr_hash_first(pool, keywords);
1329 hi; hi = apr_hash_next(hi))
1331 const void *key;
1332 void *val;
1333 apr_hash_this(hi, &key, NULL, &val);
1334 apr_hash_set(copy, apr_pstrdup(baton_pool, key),
1335 APR_HASH_KEY_STRING,
1336 svn_string_dup(val, baton_pool));
1339 keywords = copy;
1343 /* Setup the baton fields */
1344 baton->stream = stream;
1345 baton->in_baton
1346 = create_translation_baton(eol_str, repair, keywords, expand, baton_pool);
1347 baton->out_baton
1348 = create_translation_baton(eol_str, repair, keywords, expand, baton_pool);
1349 baton->written = FALSE;
1350 baton->readbuf = svn_stringbuf_create("", baton_pool);
1351 baton->readbuf_off = 0;
1352 baton->iterpool = svn_pool_create(baton_pool);
1353 baton->pool = baton_pool;
1354 baton->buf = apr_palloc(baton->pool, SVN__STREAM_CHUNK_SIZE + 1);
1356 /* Setup the stream methods */
1357 svn_stream_set_read(s, translated_stream_read);
1358 svn_stream_set_write(s, translated_stream_write);
1359 svn_stream_set_close(s, translated_stream_close);
1361 return s;
1365 svn_error_t *
1366 svn_subst_translate_stream3(svn_stream_t *s, /* src stream */
1367 svn_stream_t *d, /* dst stream */
1368 const char *eol_str,
1369 svn_boolean_t repair,
1370 apr_hash_t *keywords,
1371 svn_boolean_t expand,
1372 apr_pool_t *pool)
1374 apr_pool_t *subpool = svn_pool_create(pool);
1375 apr_pool_t *iterpool = svn_pool_create(subpool);
1376 struct translation_baton *baton;
1377 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1378 char *buf = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE);
1380 /* The docstring requires that *some* translation be requested. */
1381 assert(eol_str || keywords);
1383 baton = create_translation_baton(eol_str, repair, keywords, expand, pool);
1384 while (readlen == SVN__STREAM_CHUNK_SIZE)
1386 svn_pool_clear(iterpool);
1387 SVN_ERR(svn_stream_read(s, buf, &readlen));
1388 SVN_ERR(translate_chunk(d, baton, buf, readlen, iterpool));
1391 SVN_ERR(translate_chunk(d, baton, NULL, 0, iterpool));
1393 svn_pool_destroy(subpool); /* also destroys iterpool */
1394 return SVN_NO_ERROR;
1398 svn_error_t *
1399 svn_subst_translate_stream(svn_stream_t *s, /* src stream */
1400 svn_stream_t *d, /* dst stream */
1401 const char *eol_str,
1402 svn_boolean_t repair,
1403 const svn_subst_keywords_t *keywords,
1404 svn_boolean_t expand)
1406 apr_pool_t *pool = svn_pool_create(NULL);
1407 svn_error_t *err = svn_subst_translate_stream2(s, d, eol_str, repair,
1408 keywords, expand, pool);
1409 svn_pool_destroy(pool);
1410 return err;
1414 svn_error_t *
1415 svn_subst_translate_cstring(const char *src,
1416 const char **dst,
1417 const char *eol_str,
1418 svn_boolean_t repair,
1419 const svn_subst_keywords_t *keywords,
1420 svn_boolean_t expand,
1421 apr_pool_t *pool)
1423 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
1425 return svn_subst_translate_cstring2(src, dst, eol_str, repair,
1426 kh, expand, pool);
1429 svn_error_t *
1430 svn_subst_translate_cstring2(const char *src,
1431 const char **dst,
1432 const char *eol_str,
1433 svn_boolean_t repair,
1434 apr_hash_t *keywords,
1435 svn_boolean_t expand,
1436 apr_pool_t *pool)
1438 svn_stringbuf_t *src_stringbuf, *dst_stringbuf;
1439 svn_stream_t *src_stream, *dst_stream;
1440 svn_error_t *err;
1442 src_stringbuf = svn_stringbuf_create(src, pool);
1444 /* The easy way out: no translation needed, just copy. */
1445 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1447 dst_stringbuf = svn_stringbuf_dup(src_stringbuf, pool);
1448 goto all_good;
1451 /* Convert our stringbufs into streams. */
1452 src_stream = svn_stream_from_stringbuf(src_stringbuf, pool);
1453 dst_stringbuf = svn_stringbuf_create("", pool);
1454 dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1456 /* Translate src stream into dst stream. */
1457 err = svn_subst_translate_stream3(src_stream, dst_stream,
1458 eol_str, repair, keywords, expand, pool);
1459 if (err)
1461 svn_error_clear(svn_stream_close(src_stream));
1462 svn_error_clear(svn_stream_close(dst_stream));
1463 return err;
1466 /* clean up nicely. */
1467 SVN_ERR(svn_stream_close(src_stream));
1468 SVN_ERR(svn_stream_close(dst_stream));
1470 all_good:
1471 *dst = dst_stringbuf->data;
1472 return SVN_NO_ERROR;
1476 svn_error_t *
1477 svn_subst_copy_and_translate(const char *src,
1478 const char *dst,
1479 const char *eol_str,
1480 svn_boolean_t repair,
1481 const svn_subst_keywords_t *keywords,
1482 svn_boolean_t expand,
1483 apr_pool_t *pool)
1485 return svn_subst_copy_and_translate2(src, dst, eol_str, repair, keywords,
1486 expand, FALSE, pool);
1490 /* Set SRC_STREAM to a stream from which the internal representation
1491 * for the special file at SRC can be read.
1493 * The stream returned will be allocated in POOL.
1495 static svn_error_t *
1496 detranslate_special_file_to_stream(svn_stream_t **src_stream,
1497 const char *src,
1498 apr_pool_t *pool)
1500 apr_finfo_t finfo;
1501 apr_file_t *s;
1502 svn_string_t *buf;
1504 /* First determine what type of special file we are
1505 detranslating. */
1506 SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool));
1508 switch (finfo.filetype) {
1509 case APR_REG:
1510 /* Nothing special to do here, just copy the original file's
1511 contents. */
1512 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1513 APR_OS_DEFAULT, pool));
1514 *src_stream = svn_stream_from_aprfile(s, pool);
1516 break;
1517 case APR_LNK:
1518 /* Determine the destination of the link. */
1520 *src_stream = svn_stream_from_stringbuf(svn_stringbuf_create ("", pool),
1521 pool);
1522 SVN_ERR(svn_io_read_link(&buf, src, pool));
1524 SVN_ERR(svn_stream_printf(*src_stream, pool, "link %s",
1525 buf->data));
1526 break;
1527 default:
1528 abort ();
1531 return SVN_NO_ERROR;
1534 /* Given a special file at SRC, generate a textual representation of
1535 it in a normal file at DST. Perform all allocations in POOL. */
1536 static svn_error_t *
1537 detranslate_special_file(const char *src, const char *dst, apr_pool_t *pool)
1539 const char *dst_tmp;
1540 apr_file_t *d;
1541 svn_stream_t *src_stream, *dst_stream;
1544 /* Open a temporary destination that we will eventually atomically
1545 rename into place. */
1546 SVN_ERR(svn_io_open_unique_file2(&d, &dst_tmp, dst,
1547 ".tmp", svn_io_file_del_none, pool));
1549 dst_stream = svn_stream_from_aprfile2(d, FALSE, pool);
1551 SVN_ERR(detranslate_special_file_to_stream(&src_stream, src, pool));
1552 SVN_ERR(svn_stream_copy(src_stream, dst_stream, pool));
1554 SVN_ERR(svn_stream_close(dst_stream));
1556 /* Do the atomic rename from our temporary location. */
1557 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1559 return SVN_NO_ERROR;
1562 /* Creates a special file DST from the internal representation given
1563 * in SRC.
1565 * All temporary allocations will be done in POOL.
1567 static svn_error_t *
1568 create_special_file_from_stringbuf(svn_stringbuf_t *src, const char *dst,
1569 apr_pool_t *pool)
1571 char *identifier, *remainder;
1572 const char *dst_tmp;
1573 svn_boolean_t create_using_internal_representation = FALSE;
1575 /* Separate off the identifier. The first space character delimits
1576 the identifier, after which any remaining characters are specific
1577 to the actual special file type being created. */
1578 identifier = src->data;
1579 for (remainder = identifier; *remainder; remainder++)
1581 if (*remainder == ' ')
1583 remainder++;
1584 break;
1588 if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1589 strlen(SVN_SUBST__SPECIAL_LINK_STR " ")))
1591 /* For symlinks, the type specific data is just a filesystem
1592 path that the symlink should reference. */
1593 svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1594 ".tmp", pool);
1596 /* If we had an error, check to see if it was because symlinks are
1597 not supported on the platform. If so, fall back
1598 to using the internal representation. */
1599 if (err)
1601 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1603 svn_error_clear(err);
1604 create_using_internal_representation = TRUE;
1606 else
1607 return err;
1610 else
1612 /* Just create a normal file using the internal special file
1613 representation. We don't want a commit of an unknown special
1614 file type to DoS all the clients. */
1615 create_using_internal_representation = TRUE;
1618 /* If nothing else worked, write out the internal representation to
1619 a file that can be edited by the user. */
1620 if (create_using_internal_representation)
1622 apr_file_t *dst_tmp_file;
1623 apr_size_t written;
1626 SVN_ERR(svn_io_open_unique_file2(&dst_tmp_file, &dst_tmp,
1627 dst, ".tmp", svn_io_file_del_none,
1628 pool));
1629 SVN_ERR(svn_io_file_write_full(dst_tmp_file, src->data, src->len,
1630 &written, pool));
1631 SVN_ERR(svn_io_file_close(dst_tmp_file, pool));
1634 /* Do the atomic rename from our temporary location. */
1635 return svn_io_file_rename(dst_tmp, dst, pool);
1638 /* Given a file containing a repository representation of a special
1639 file in SRC, create the appropriate special file at location DST.
1640 Perform all allocations in POOL. */
1641 static svn_error_t *
1642 create_special_file(const char *src, const char *dst, apr_pool_t *pool)
1644 svn_stringbuf_t *contents;
1645 svn_node_kind_t kind;
1646 svn_boolean_t is_special;
1647 svn_stream_t *source;
1649 /* Check to see if we are being asked to create a special file from
1650 a special file. If so, do a temporary detranslation and work
1651 from there. */
1652 SVN_ERR(svn_io_check_special_path(src, &kind, &is_special, pool));
1654 if (is_special)
1656 svn_boolean_t eof;
1658 SVN_ERR(detranslate_special_file_to_stream(&source, src, pool));
1659 /* The special file normal form doesn't have line endings,
1660 * so, read all of the file into the stringbuf */
1661 SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1663 else
1664 /* Read in the detranslated file. */
1665 SVN_ERR(svn_stringbuf_from_file(&contents, src, pool));
1667 return create_special_file_from_stringbuf(contents, dst, pool);
1671 svn_error_t *
1672 svn_subst_copy_and_translate2(const char *src,
1673 const char *dst,
1674 const char *eol_str,
1675 svn_boolean_t repair,
1676 const svn_subst_keywords_t *keywords,
1677 svn_boolean_t expand,
1678 svn_boolean_t special,
1679 apr_pool_t *pool)
1681 apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
1683 return svn_subst_copy_and_translate3(src, dst, eol_str,
1684 repair, kh, expand, special,
1685 pool);
1688 svn_error_t *
1689 svn_subst_copy_and_translate3(const char *src,
1690 const char *dst,
1691 const char *eol_str,
1692 svn_boolean_t repair,
1693 apr_hash_t *keywords,
1694 svn_boolean_t expand,
1695 svn_boolean_t special,
1696 apr_pool_t *pool)
1698 const char *dst_tmp = NULL;
1699 svn_stream_t *src_stream, *dst_stream;
1700 apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */
1701 svn_error_t *err;
1702 svn_node_kind_t kind;
1703 svn_boolean_t path_special;
1705 SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1707 /* If this is a 'special' file, we may need to create it or
1708 detranslate it. */
1709 if (special || path_special)
1711 if (expand)
1712 SVN_ERR(create_special_file(src, dst, pool));
1713 else
1714 SVN_ERR(detranslate_special_file(src, dst, pool));
1716 return SVN_NO_ERROR;
1719 /* The easy way out: no translation needed, just copy. */
1720 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1721 return svn_io_copy_file(src, dst, FALSE, pool);
1723 /* Open source file. */
1724 SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED,
1725 APR_OS_DEFAULT, pool));
1727 /* For atomicity, we translate to a tmp file and
1728 then rename the tmp file over the real destination. */
1729 SVN_ERR(svn_io_open_unique_file2(&d, &dst_tmp, dst,
1730 ".tmp", svn_io_file_del_on_pool_cleanup,
1731 pool));
1733 /* Now convert our two open files into streams. */
1734 src_stream = svn_stream_from_aprfile(s, pool);
1735 dst_stream = svn_stream_from_aprfile(d, pool);
1737 /* Translate src stream into dst stream. */
1738 err = svn_subst_translate_stream3(src_stream, dst_stream, eol_str,
1739 repair, keywords, expand, pool);
1740 if (err)
1742 if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1743 return svn_error_createf
1744 (SVN_ERR_IO_INCONSISTENT_EOL, err,
1745 _("File '%s' has inconsistent newlines"),
1746 svn_path_local_style(src, pool));
1747 else
1748 return err;
1751 /* clean up nicely. */
1752 SVN_ERR(svn_stream_close(src_stream));
1753 SVN_ERR(svn_stream_close(dst_stream));
1754 SVN_ERR(svn_io_file_close(s, pool));
1755 SVN_ERR(svn_io_file_close(d, pool));
1757 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1758 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1760 return SVN_NO_ERROR;
1764 /*** 'Special file' stream support */
1766 struct special_stream_baton
1768 svn_stream_t *read_stream;
1769 svn_stringbuf_t *write_content;
1770 svn_stream_t *write_stream;
1771 const char *path;
1772 apr_pool_t *pool;
1776 static svn_error_t *
1777 read_handler_special(void *baton, char *buffer, apr_size_t *len)
1779 struct special_stream_baton *btn = baton;
1781 if (btn->read_stream)
1782 /* We actually found a file to read from */
1783 return svn_stream_read(btn->read_stream, buffer, len);
1784 else
1785 return svn_error_createf(APR_ENOENT, NULL,
1786 "Can't read special file: File '%s' not found",
1787 svn_path_local_style (btn->path, btn->pool));
1790 static svn_error_t *
1791 write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1793 struct special_stream_baton *btn = baton;
1795 return svn_stream_write(btn->write_stream, buffer, len);
1799 static svn_error_t *
1800 close_handler_special (void *baton)
1802 struct special_stream_baton *btn = baton;
1804 if (btn->write_content->len)
1806 /* yeay! we received data and need to create a special file! */
1808 SVN_ERR(create_special_file_from_stringbuf(btn->write_content,
1809 btn->path,
1810 btn->pool));
1813 return SVN_NO_ERROR;
1817 svn_error_t *
1818 svn_subst_stream_from_specialfile(svn_stream_t **stream,
1819 const char *path,
1820 apr_pool_t *pool)
1822 struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1823 svn_error_t *err;
1825 baton->pool = pool;
1826 baton->path = apr_pstrdup(pool, path);
1828 err = detranslate_special_file_to_stream(&baton->read_stream, path, pool);
1830 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1832 svn_error_clear(err);
1833 baton->read_stream = NULL;
1836 baton->write_content = svn_stringbuf_create("", pool);
1837 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1839 *stream = svn_stream_create(baton, pool);
1840 svn_stream_set_read(*stream, read_handler_special);
1841 svn_stream_set_write(*stream, write_handler_special);
1842 svn_stream_set_close(*stream, close_handler_special);
1844 return SVN_NO_ERROR;
1849 /*** String translation */
1850 svn_error_t *
1851 svn_subst_translate_string(svn_string_t **new_value,
1852 const svn_string_t *value,
1853 const char *encoding,
1854 apr_pool_t *pool)
1856 const char *val_utf8;
1857 const char *val_utf8_lf;
1859 if (value == NULL)
1861 *new_value = NULL;
1862 return SVN_NO_ERROR;
1865 if (encoding)
1867 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1868 encoding, pool));
1870 else
1872 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, pool));
1875 SVN_ERR(svn_subst_translate_cstring2(val_utf8,
1876 &val_utf8_lf,
1877 "\n", /* translate to LF */
1878 FALSE, /* no repair */
1879 NULL, /* no keywords */
1880 FALSE, /* no expansion */
1881 pool));
1883 *new_value = svn_string_create(val_utf8_lf, pool);
1885 return SVN_NO_ERROR;
1889 svn_error_t *
1890 svn_subst_detranslate_string(svn_string_t **new_value,
1891 const svn_string_t *value,
1892 svn_boolean_t for_output,
1893 apr_pool_t *pool)
1895 svn_error_t *err;
1896 const char *val_neol;
1897 const char *val_nlocale_neol;
1899 if (value == NULL)
1901 *new_value = NULL;
1902 return SVN_NO_ERROR;
1905 SVN_ERR(svn_subst_translate_cstring2(value->data,
1906 &val_neol,
1907 APR_EOL_STR, /* 'native' eol */
1908 FALSE, /* no repair */
1909 NULL, /* no keywords */
1910 FALSE, /* no expansion */
1911 pool));
1913 if (for_output)
1915 err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
1916 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
1918 val_nlocale_neol =
1919 svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
1920 svn_error_clear(err);
1922 else if (err)
1923 return err;
1925 else
1927 err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
1928 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
1930 val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
1931 svn_error_clear(err);
1933 else if (err)
1934 return err;
1937 *new_value = svn_string_create(val_nlocale_neol, pool);
1939 return SVN_NO_ERROR;