1251881Speter/* 2251881Speter * utf.c: UTF-8 conversion routines 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <stdlib.h> 27251881Speter#include <string.h> 28251881Speter#include <assert.h> 29251881Speter 30251881Speter#include <apr_strings.h> 31251881Speter#include <apr_lib.h> 32251881Speter#include <apr_xlate.h> 33251881Speter#include <apr_atomic.h> 34251881Speter 35251881Speter#include "svn_hash.h" 36251881Speter#include "svn_string.h" 37251881Speter#include "svn_error.h" 38251881Speter#include "svn_pools.h" 39251881Speter#include "svn_ctype.h" 40251881Speter#include "svn_utf.h" 41251881Speter#include "svn_private_config.h" 42251881Speter#include "win32_xlate.h" 43251881Speter 44251881Speter#include "private/svn_utf_private.h" 45251881Speter#include "private/svn_dep_compat.h" 46251881Speter#include "private/svn_string_private.h" 47251881Speter#include "private/svn_mutex.h" 48251881Speter 49251881Speter 50251881Speter 51251881Speter/* Use these static strings to maximize performance on standard conversions. 52251881Speter * Any strings on other locations are still valid, however. 53251881Speter */ 54251881Speterstatic const char *SVN_UTF_NTOU_XLATE_HANDLE = "svn-utf-ntou-xlate-handle"; 55251881Speterstatic const char *SVN_UTF_UTON_XLATE_HANDLE = "svn-utf-uton-xlate-handle"; 56251881Speter 57251881Speterstatic const char *SVN_APR_UTF8_CHARSET = "UTF-8"; 58251881Speter 59251881Speterstatic svn_mutex__t *xlate_handle_mutex = NULL; 60251881Speterstatic svn_boolean_t assume_native_charset_is_utf8 = FALSE; 61251881Speter 62251881Speter/* The xlate handle cache is a global hash table with linked lists of xlate 63251881Speter * handles. In multi-threaded environments, a thread "borrows" an xlate 64251881Speter * handle from the cache during a translation and puts it back afterwards. 65251881Speter * This avoids holding a global lock for all translations. 66251881Speter * If there is no handle for a particular key when needed, a new is 67251881Speter * handle is created and put in the cache after use. 68251881Speter * This means that there will be at most N handles open for a key, where N 69251881Speter * is the number of simultanous handles in use for that key. */ 70251881Speter 71251881Spetertypedef struct xlate_handle_node_t { 72251881Speter apr_xlate_t *handle; 73251881Speter /* FALSE if the handle is not valid, since its pool is being 74251881Speter destroyed. */ 75251881Speter svn_boolean_t valid; 76251881Speter /* The name of a char encoding or APR_LOCALE_CHARSET. */ 77251881Speter const char *frompage, *topage; 78251881Speter struct xlate_handle_node_t *next; 79251881Speter} xlate_handle_node_t; 80251881Speter 81251881Speter/* This maps const char * userdata_key strings to xlate_handle_node_t ** 82251881Speter handles to the first entry in the linked list of xlate handles. We don't 83251881Speter store the pointer to the list head directly in the hash table, since we 84251881Speter remove/insert entries at the head in the list in the code below, and 85251881Speter we can't use apr_hash_set() in each character translation because that 86251881Speter function allocates memory in each call where the value is non-NULL. 87251881Speter Since these allocations take place in a global pool, this would be a 88251881Speter memory leak. */ 89251881Speterstatic apr_hash_t *xlate_handle_hash = NULL; 90251881Speter 91251881Speter/* "1st level cache" to standard conversion maps. We may access these 92251881Speter * using atomic xchange ops, i.e. without further thread synchronization. 93251881Speter * If the respective item is NULL, fallback to hash lookup. 94251881Speter */ 95251881Speterstatic void * volatile xlat_ntou_static_handle = NULL; 96251881Speterstatic void * volatile xlat_uton_static_handle = NULL; 97251881Speter 98251881Speter/* Clean up the xlate handle cache. */ 99251881Speterstatic apr_status_t 100251881Speterxlate_cleanup(void *arg) 101251881Speter{ 102251881Speter /* We set the cache variables to NULL so that translation works in other 103251881Speter cleanup functions, even if it isn't cached then. */ 104251881Speter xlate_handle_hash = NULL; 105251881Speter 106251881Speter /* ensure no stale objects get accessed */ 107251881Speter xlat_ntou_static_handle = NULL; 108251881Speter xlat_uton_static_handle = NULL; 109251881Speter 110251881Speter return APR_SUCCESS; 111251881Speter} 112251881Speter 113251881Speter/* Set the handle of ARG to NULL. */ 114251881Speterstatic apr_status_t 115251881Speterxlate_handle_node_cleanup(void *arg) 116251881Speter{ 117251881Speter xlate_handle_node_t *node = arg; 118251881Speter 119251881Speter node->valid = FALSE; 120251881Speter return APR_SUCCESS; 121251881Speter} 122251881Speter 123251881Spetervoid 124251881Spetersvn_utf_initialize2(svn_boolean_t assume_native_utf8, 125251881Speter apr_pool_t *pool) 126251881Speter{ 127251881Speter if (!xlate_handle_hash) 128251881Speter { 129251881Speter /* We create our own subpool, which we protect with the mutex. 130251881Speter We can't use the pool passed to us by the caller, since we will 131251881Speter use it for xlate handle allocations, possibly in multiple threads, 132251881Speter and pool allocation is not thread-safe. */ 133251881Speter apr_pool_t *subpool = svn_pool_create(pool); 134251881Speter svn_mutex__t *mutex; 135251881Speter svn_error_t *err = svn_mutex__init(&mutex, TRUE, subpool); 136251881Speter if (err) 137251881Speter { 138251881Speter svn_error_clear(err); 139251881Speter return; 140251881Speter } 141251881Speter 142251881Speter xlate_handle_mutex = mutex; 143251881Speter xlate_handle_hash = apr_hash_make(subpool); 144251881Speter 145251881Speter apr_pool_cleanup_register(subpool, NULL, xlate_cleanup, 146251881Speter apr_pool_cleanup_null); 147251881Speter } 148251881Speter 149251881Speter if (!assume_native_charset_is_utf8) 150251881Speter assume_native_charset_is_utf8 = assume_native_utf8; 151251881Speter} 152251881Speter 153251881Speter/* Return a unique string key based on TOPAGE and FROMPAGE. TOPAGE and 154251881Speter * FROMPAGE can be any valid arguments of the same name to 155251881Speter * apr_xlate_open(). Allocate the returned string in POOL. */ 156251881Speterstatic const char* 157251881Speterget_xlate_key(const char *topage, 158251881Speter const char *frompage, 159251881Speter apr_pool_t *pool) 160251881Speter{ 161251881Speter /* In the cases of SVN_APR_LOCALE_CHARSET and SVN_APR_DEFAULT_CHARSET 162251881Speter * topage/frompage is really an int, not a valid string. So generate a 163251881Speter * unique key accordingly. */ 164251881Speter if (frompage == SVN_APR_LOCALE_CHARSET) 165251881Speter frompage = "APR_LOCALE_CHARSET"; 166251881Speter else if (frompage == SVN_APR_DEFAULT_CHARSET) 167251881Speter frompage = "APR_DEFAULT_CHARSET"; 168251881Speter 169251881Speter if (topage == SVN_APR_LOCALE_CHARSET) 170251881Speter topage = "APR_LOCALE_CHARSET"; 171251881Speter else if (topage == SVN_APR_DEFAULT_CHARSET) 172251881Speter topage = "APR_DEFAULT_CHARSET"; 173251881Speter 174251881Speter return apr_pstrcat(pool, "svn-utf-", frompage, "to", topage, 175251881Speter "-xlate-handle", (char *)NULL); 176251881Speter} 177251881Speter 178251881Speter/* Atomically replace the content in *MEM with NEW_VALUE and return 179251881Speter * the previous content of *MEM. If atomicy cannot be guaranteed, 180251881Speter * *MEM will not be modified and NEW_VALUE is simply returned to 181251881Speter * the caller. 182251881Speter */ 183251881Speterstatic APR_INLINE void* 184251881Speteratomic_swap(void * volatile * mem, void *new_value) 185251881Speter{ 186251881Speter#if APR_HAS_THREADS 187251881Speter#if APR_VERSION_AT_LEAST(1,3,0) 188251881Speter /* Cast is necessary because of APR bug: 189251881Speter https://issues.apache.org/bugzilla/show_bug.cgi?id=50731 */ 190251881Speter return apr_atomic_xchgptr((volatile void **)mem, new_value); 191251881Speter#else 192251881Speter /* old APRs don't support atomic swaps. Simply return the 193251881Speter * input to the caller for further proccessing. */ 194251881Speter return new_value; 195251881Speter#endif 196251881Speter#else 197251881Speter /* no threads - no sync. necessary */ 198251881Speter void *old_value = (void*)*mem; 199251881Speter *mem = new_value; 200251881Speter return old_value; 201251881Speter#endif 202251881Speter} 203251881Speter 204251881Speter/* Set *RET to a newly created handle node for converting from FROMPAGE 205251881Speter to TOPAGE, If apr_xlate_open() returns APR_EINVAL or APR_ENOTIMPL, set 206251881Speter (*RET)->handle to NULL. If fail for any other reason, return the error. 207251881Speter Allocate *RET and its xlate handle in POOL. */ 208251881Speterstatic svn_error_t * 209251881Speterxlate_alloc_handle(xlate_handle_node_t **ret, 210251881Speter const char *topage, const char *frompage, 211251881Speter apr_pool_t *pool) 212251881Speter{ 213251881Speter apr_status_t apr_err; 214251881Speter apr_xlate_t *handle; 215251881Speter 216251881Speter /* The error handling doesn't support the following cases, since we don't 217251881Speter use them currently. Catch this here. */ 218251881Speter SVN_ERR_ASSERT(frompage != SVN_APR_DEFAULT_CHARSET 219251881Speter && topage != SVN_APR_DEFAULT_CHARSET 220251881Speter && (frompage != SVN_APR_LOCALE_CHARSET 221251881Speter || topage != SVN_APR_LOCALE_CHARSET)); 222251881Speter 223251881Speter /* Try to create a handle. */ 224251881Speter#if defined(WIN32) 225251881Speter apr_err = svn_subr__win32_xlate_open((win32_xlate_t **)&handle, topage, 226251881Speter frompage, pool); 227251881Speter#else 228251881Speter apr_err = apr_xlate_open(&handle, topage, frompage, pool); 229251881Speter#endif 230251881Speter 231251881Speter if (APR_STATUS_IS_EINVAL(apr_err) || APR_STATUS_IS_ENOTIMPL(apr_err)) 232251881Speter handle = NULL; 233251881Speter else if (apr_err != APR_SUCCESS) 234251881Speter { 235251881Speter const char *errstr; 236253734Speter char apr_strerr[512]; 237253734Speter 238251881Speter /* Can't use svn_error_wrap_apr here because it calls functions in 239251881Speter this file, leading to infinite recursion. */ 240251881Speter if (frompage == SVN_APR_LOCALE_CHARSET) 241251881Speter errstr = apr_psprintf(pool, 242251881Speter _("Can't create a character converter from " 243251881Speter "native encoding to '%s'"), topage); 244251881Speter else if (topage == SVN_APR_LOCALE_CHARSET) 245251881Speter errstr = apr_psprintf(pool, 246251881Speter _("Can't create a character converter from " 247251881Speter "'%s' to native encoding"), frompage); 248251881Speter else 249251881Speter errstr = apr_psprintf(pool, 250251881Speter _("Can't create a character converter from " 251251881Speter "'%s' to '%s'"), frompage, topage); 252251881Speter 253253734Speter /* Just put the error on the stack, since svn_error_create duplicates it 254253734Speter later. APR_STRERR will be in the local encoding, not in UTF-8, though. 255253734Speter */ 256253734Speter svn_strerror(apr_err, apr_strerr, sizeof(apr_strerr)); 257253734Speter return svn_error_create(apr_err, 258253734Speter svn_error_create(apr_err, NULL, apr_strerr), 259253734Speter errstr); 260251881Speter } 261251881Speter 262251881Speter /* Allocate and initialize the node. */ 263251881Speter *ret = apr_palloc(pool, sizeof(xlate_handle_node_t)); 264251881Speter (*ret)->handle = handle; 265251881Speter (*ret)->valid = TRUE; 266251881Speter (*ret)->frompage = ((frompage != SVN_APR_LOCALE_CHARSET) 267251881Speter ? apr_pstrdup(pool, frompage) : frompage); 268251881Speter (*ret)->topage = ((topage != SVN_APR_LOCALE_CHARSET) 269251881Speter ? apr_pstrdup(pool, topage) : topage); 270251881Speter (*ret)->next = NULL; 271251881Speter 272251881Speter /* If we are called from inside a pool cleanup handler, the just created 273251881Speter xlate handle will be closed when that handler returns by a newly 274251881Speter registered cleanup handler, however, the handle is still cached by us. 275251881Speter To prevent this, we register a cleanup handler that will reset the valid 276251881Speter flag of our node, so we don't use an invalid handle. */ 277251881Speter if (handle) 278251881Speter apr_pool_cleanup_register(pool, *ret, xlate_handle_node_cleanup, 279251881Speter apr_pool_cleanup_null); 280251881Speter 281251881Speter return SVN_NO_ERROR; 282251881Speter} 283251881Speter 284251881Speter/* Extend xlate_alloc_handle by using USERDATA_KEY as a key in our 285251881Speter global hash map, if available. 286251881Speter 287251881Speter Allocate *RET and its xlate handle in POOL if svn_utf_initialize() 288251881Speter hasn't been called or USERDATA_KEY is NULL. Else, allocate them 289251881Speter in the pool of xlate_handle_hash. 290251881Speter 291251881Speter Note: this function is not thread-safe. Call get_xlate_handle_node 292251881Speter instead. */ 293251881Speterstatic svn_error_t * 294251881Speterget_xlate_handle_node_internal(xlate_handle_node_t **ret, 295251881Speter const char *topage, const char *frompage, 296251881Speter const char *userdata_key, apr_pool_t *pool) 297251881Speter{ 298251881Speter /* If we already have a handle, just return it. */ 299251881Speter if (userdata_key && xlate_handle_hash) 300251881Speter { 301251881Speter xlate_handle_node_t *old_node = NULL; 302251881Speter 303251881Speter /* 2nd level: hash lookup */ 304251881Speter xlate_handle_node_t **old_node_p = svn_hash_gets(xlate_handle_hash, 305251881Speter userdata_key); 306251881Speter if (old_node_p) 307251881Speter old_node = *old_node_p; 308251881Speter if (old_node) 309251881Speter { 310251881Speter /* Ensure that the handle is still valid. */ 311251881Speter if (old_node->valid) 312251881Speter { 313251881Speter /* Remove from the list. */ 314251881Speter *old_node_p = old_node->next; 315251881Speter old_node->next = NULL; 316251881Speter *ret = old_node; 317251881Speter return SVN_NO_ERROR; 318251881Speter } 319251881Speter } 320251881Speter } 321251881Speter 322251881Speter /* Note that we still have the mutex locked (if it is initialized), so we 323251881Speter can use the global pool for creating the new xlate handle. */ 324251881Speter 325251881Speter /* Use the correct pool for creating the handle. */ 326251881Speter pool = apr_hash_pool_get(xlate_handle_hash); 327251881Speter 328251881Speter return xlate_alloc_handle(ret, topage, frompage, pool); 329251881Speter} 330251881Speter 331251881Speter/* Set *RET to a handle node for converting from FROMPAGE to TOPAGE, 332251881Speter creating the handle node if it doesn't exist in USERDATA_KEY. 333251881Speter If a node is not cached and apr_xlate_open() returns APR_EINVAL or 334251881Speter APR_ENOTIMPL, set (*RET)->handle to NULL. If fail for any other 335251881Speter reason, return the error. 336251881Speter 337251881Speter Allocate *RET and its xlate handle in POOL if svn_utf_initialize() 338251881Speter hasn't been called or USERDATA_KEY is NULL. Else, allocate them 339251881Speter in the pool of xlate_handle_hash. */ 340251881Speterstatic svn_error_t * 341251881Speterget_xlate_handle_node(xlate_handle_node_t **ret, 342251881Speter const char *topage, const char *frompage, 343251881Speter const char *userdata_key, apr_pool_t *pool) 344251881Speter{ 345251881Speter xlate_handle_node_t *old_node = NULL; 346251881Speter 347251881Speter /* If we already have a handle, just return it. */ 348251881Speter if (userdata_key) 349251881Speter { 350251881Speter if (xlate_handle_hash) 351251881Speter { 352251881Speter /* 1st level: global, static items */ 353251881Speter if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE) 354251881Speter old_node = atomic_swap(&xlat_ntou_static_handle, NULL); 355251881Speter else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE) 356251881Speter old_node = atomic_swap(&xlat_uton_static_handle, NULL); 357251881Speter 358251881Speter if (old_node && old_node->valid) 359251881Speter { 360251881Speter *ret = old_node; 361251881Speter return SVN_NO_ERROR; 362251881Speter } 363251881Speter } 364251881Speter else 365251881Speter { 366251881Speter void *p; 367251881Speter /* We fall back on a per-pool cache instead. */ 368251881Speter apr_pool_userdata_get(&p, userdata_key, pool); 369251881Speter old_node = p; 370251881Speter /* Ensure that the handle is still valid. */ 371251881Speter if (old_node && old_node->valid) 372251881Speter { 373251881Speter *ret = old_node; 374251881Speter return SVN_NO_ERROR; 375251881Speter } 376251881Speter 377251881Speter return xlate_alloc_handle(ret, topage, frompage, pool); 378251881Speter } 379251881Speter } 380251881Speter 381251881Speter SVN_MUTEX__WITH_LOCK(xlate_handle_mutex, 382251881Speter get_xlate_handle_node_internal(ret, 383251881Speter topage, 384251881Speter frompage, 385251881Speter userdata_key, 386251881Speter pool)); 387251881Speter 388251881Speter return SVN_NO_ERROR; 389251881Speter} 390251881Speter 391251881Speter/* Put back NODE into the xlate handle cache for use by other calls. 392251881Speter 393251881Speter Note: this function is not thread-safe. Call put_xlate_handle_node 394251881Speter instead. */ 395251881Speterstatic svn_error_t * 396251881Speterput_xlate_handle_node_internal(xlate_handle_node_t *node, 397251881Speter const char *userdata_key) 398251881Speter{ 399251881Speter xlate_handle_node_t **node_p = svn_hash_gets(xlate_handle_hash, userdata_key); 400251881Speter if (node_p == NULL) 401251881Speter { 402251881Speter userdata_key = apr_pstrdup(apr_hash_pool_get(xlate_handle_hash), 403251881Speter userdata_key); 404251881Speter node_p = apr_palloc(apr_hash_pool_get(xlate_handle_hash), 405251881Speter sizeof(*node_p)); 406251881Speter *node_p = NULL; 407251881Speter svn_hash_sets(xlate_handle_hash, userdata_key, node_p); 408251881Speter } 409251881Speter node->next = *node_p; 410251881Speter *node_p = node; 411251881Speter 412251881Speter return SVN_NO_ERROR; 413251881Speter} 414251881Speter 415251881Speter/* Put back NODE into the xlate handle cache for use by other calls. 416251881Speter If there is no global cache, store the handle in POOL. 417251881Speter Ignore errors related to locking/unlocking the mutex. */ 418251881Speterstatic svn_error_t * 419251881Speterput_xlate_handle_node(xlate_handle_node_t *node, 420251881Speter const char *userdata_key, 421251881Speter apr_pool_t *pool) 422251881Speter{ 423251881Speter assert(node->next == NULL); 424251881Speter if (!userdata_key) 425251881Speter return SVN_NO_ERROR; 426251881Speter 427251881Speter /* push previous global node to the hash */ 428251881Speter if (xlate_handle_hash) 429251881Speter { 430251881Speter /* 1st level: global, static items */ 431251881Speter if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE) 432251881Speter node = atomic_swap(&xlat_ntou_static_handle, node); 433251881Speter else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE) 434251881Speter node = atomic_swap(&xlat_uton_static_handle, node); 435251881Speter if (node == NULL) 436251881Speter return SVN_NO_ERROR; 437251881Speter 438251881Speter SVN_MUTEX__WITH_LOCK(xlate_handle_mutex, 439251881Speter put_xlate_handle_node_internal(node, 440251881Speter userdata_key)); 441251881Speter } 442251881Speter else 443251881Speter { 444251881Speter /* Store it in the per-pool cache. */ 445251881Speter apr_pool_userdata_set(node, userdata_key, apr_pool_cleanup_null, pool); 446251881Speter } 447251881Speter 448251881Speter return SVN_NO_ERROR; 449251881Speter} 450251881Speter 451251881Speter/* Return the apr_xlate handle for converting native characters to UTF-8. */ 452251881Speterstatic svn_error_t * 453251881Speterget_ntou_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool) 454251881Speter{ 455251881Speter return get_xlate_handle_node(ret, SVN_APR_UTF8_CHARSET, 456251881Speter assume_native_charset_is_utf8 457251881Speter ? SVN_APR_UTF8_CHARSET 458251881Speter : SVN_APR_LOCALE_CHARSET, 459251881Speter SVN_UTF_NTOU_XLATE_HANDLE, pool); 460251881Speter} 461251881Speter 462251881Speter 463251881Speter/* Return the apr_xlate handle for converting UTF-8 to native characters. 464251881Speter Create one if it doesn't exist. If unable to find a handle, or 465251881Speter unable to create one because apr_xlate_open returned APR_EINVAL, then 466251881Speter set *RET to null and return SVN_NO_ERROR; if fail for some other 467251881Speter reason, return error. */ 468251881Speterstatic svn_error_t * 469251881Speterget_uton_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool) 470251881Speter{ 471251881Speter return get_xlate_handle_node(ret, 472251881Speter assume_native_charset_is_utf8 473251881Speter ? SVN_APR_UTF8_CHARSET 474251881Speter : SVN_APR_LOCALE_CHARSET, 475251881Speter SVN_APR_UTF8_CHARSET, 476251881Speter SVN_UTF_UTON_XLATE_HANDLE, pool); 477251881Speter} 478251881Speter 479251881Speter 480251881Speter/* Copy LEN bytes of SRC, converting non-ASCII and zero bytes to ?\nnn 481251881Speter sequences, allocating the result in POOL. */ 482251881Speterstatic const char * 483251881Speterfuzzy_escape(const char *src, apr_size_t len, apr_pool_t *pool) 484251881Speter{ 485251881Speter const char *src_orig = src, *src_end = src + len; 486251881Speter apr_size_t new_len = 0; 487251881Speter char *new; 488251881Speter const char *new_orig; 489251881Speter 490251881Speter /* First count how big a dest string we'll need. */ 491251881Speter while (src < src_end) 492251881Speter { 493251881Speter if (! svn_ctype_isascii(*src) || *src == '\0') 494251881Speter new_len += 5; /* 5 slots, for "?\XXX" */ 495251881Speter else 496251881Speter new_len += 1; /* one slot for the 7-bit char */ 497251881Speter 498251881Speter src++; 499251881Speter } 500251881Speter 501251881Speter /* Allocate that amount, plus one slot for '\0' character. */ 502251881Speter new = apr_palloc(pool, new_len + 1); 503251881Speter 504251881Speter new_orig = new; 505251881Speter 506251881Speter /* And fill it up. */ 507251881Speter while (src_orig < src_end) 508251881Speter { 509251881Speter if (! svn_ctype_isascii(*src_orig) || src_orig == '\0') 510251881Speter { 511251881Speter /* This is the same format as svn_xml_fuzzy_escape uses, but that 512251881Speter function escapes different characters. Please keep in sync! 513251881Speter ### If we add another fuzzy escape somewhere, we should abstract 514251881Speter ### this out to a common function. */ 515251881Speter apr_snprintf(new, 6, "?\\%03u", (unsigned char) *src_orig); 516251881Speter new += 5; 517251881Speter } 518251881Speter else 519251881Speter { 520251881Speter *new = *src_orig; 521251881Speter new += 1; 522251881Speter } 523251881Speter 524251881Speter src_orig++; 525251881Speter } 526251881Speter 527251881Speter *new = '\0'; 528251881Speter 529251881Speter return new_orig; 530251881Speter} 531251881Speter 532251881Speter/* Convert SRC_LENGTH bytes of SRC_DATA in NODE->handle, store the result 533251881Speter in *DEST, which is allocated in POOL. */ 534251881Speterstatic svn_error_t * 535251881Speterconvert_to_stringbuf(xlate_handle_node_t *node, 536251881Speter const char *src_data, 537251881Speter apr_size_t src_length, 538251881Speter svn_stringbuf_t **dest, 539251881Speter apr_pool_t *pool) 540251881Speter{ 541251881Speter#ifdef WIN32 542251881Speter apr_status_t apr_err; 543251881Speter 544251881Speter apr_err = svn_subr__win32_xlate_to_stringbuf((win32_xlate_t *) node->handle, 545251881Speter src_data, src_length, 546251881Speter dest, pool); 547251881Speter#else 548251881Speter apr_size_t buflen = src_length * 2; 549251881Speter apr_status_t apr_err; 550251881Speter apr_size_t srclen = src_length; 551251881Speter apr_size_t destlen = buflen; 552251881Speter 553251881Speter /* Initialize *DEST to an empty stringbuf. 554251881Speter A 1:2 ratio of input bytes to output bytes (as assigned above) 555251881Speter should be enough for most translations, and if it turns out not 556251881Speter to be enough, we'll grow the buffer again, sizing it based on a 557251881Speter 1:3 ratio of the remainder of the string. */ 558251881Speter *dest = svn_stringbuf_create_ensure(buflen + 1, pool); 559251881Speter 560251881Speter /* Not only does it not make sense to convert an empty string, but 561251881Speter apr-iconv is quite unreasonable about not allowing that. */ 562251881Speter if (src_length == 0) 563251881Speter return SVN_NO_ERROR; 564251881Speter 565251881Speter do 566251881Speter { 567251881Speter /* Set up state variables for xlate. */ 568251881Speter destlen = buflen - (*dest)->len; 569251881Speter 570251881Speter /* Attempt the conversion. */ 571251881Speter apr_err = apr_xlate_conv_buffer(node->handle, 572251881Speter src_data + (src_length - srclen), 573251881Speter &srclen, 574251881Speter (*dest)->data + (*dest)->len, 575251881Speter &destlen); 576251881Speter 577251881Speter /* Now, update the *DEST->len to track the amount of output data 578251881Speter churned out so far from this loop. */ 579251881Speter (*dest)->len += ((buflen - (*dest)->len) - destlen); 580251881Speter buflen += srclen * 3; /* 3 is middle ground, 2 wasn't enough 581251881Speter for all characters in the buffer, 4 is 582251881Speter maximum character size (currently) */ 583251881Speter 584251881Speter 585251881Speter } while (apr_err == APR_SUCCESS && srclen != 0); 586251881Speter#endif 587251881Speter 588251881Speter /* If we exited the loop with an error, return the error. */ 589251881Speter if (apr_err) 590251881Speter { 591251881Speter const char *errstr; 592251881Speter svn_error_t *err; 593251881Speter 594251881Speter /* Can't use svn_error_wrap_apr here because it calls functions in 595251881Speter this file, leading to infinite recursion. */ 596251881Speter if (node->frompage == SVN_APR_LOCALE_CHARSET) 597251881Speter errstr = apr_psprintf 598251881Speter (pool, _("Can't convert string from native encoding to '%s':"), 599251881Speter node->topage); 600251881Speter else if (node->topage == SVN_APR_LOCALE_CHARSET) 601251881Speter errstr = apr_psprintf 602251881Speter (pool, _("Can't convert string from '%s' to native encoding:"), 603251881Speter node->frompage); 604251881Speter else 605251881Speter errstr = apr_psprintf 606251881Speter (pool, _("Can't convert string from '%s' to '%s':"), 607251881Speter node->frompage, node->topage); 608251881Speter 609251881Speter err = svn_error_create(apr_err, NULL, fuzzy_escape(src_data, 610251881Speter src_length, pool)); 611251881Speter return svn_error_create(apr_err, err, errstr); 612251881Speter } 613251881Speter /* Else, exited due to success. Trim the result buffer down to the 614251881Speter right length. */ 615251881Speter (*dest)->data[(*dest)->len] = '\0'; 616251881Speter 617251881Speter return SVN_NO_ERROR; 618251881Speter} 619251881Speter 620251881Speter 621251881Speter/* Return APR_EINVAL if the first LEN bytes of DATA contain anything 622251881Speter other than seven-bit, non-control (except for whitespace) ASCII 623251881Speter characters, finding the error pool from POOL. Otherwise, return 624251881Speter SVN_NO_ERROR. */ 625251881Speterstatic svn_error_t * 626251881Spetercheck_non_ascii(const char *data, apr_size_t len, apr_pool_t *pool) 627251881Speter{ 628251881Speter const char *data_start = data; 629251881Speter 630251881Speter for (; len > 0; --len, data++) 631251881Speter { 632251881Speter if ((! svn_ctype_isascii(*data)) 633251881Speter || ((! svn_ctype_isspace(*data)) 634251881Speter && svn_ctype_iscntrl(*data))) 635251881Speter { 636251881Speter /* Show the printable part of the data, followed by the 637251881Speter decimal code of the questionable character. Because if a 638251881Speter user ever gets this error, she's going to have to spend 639251881Speter time tracking down the non-ASCII data, so we want to help 640251881Speter as much as possible. And yes, we just call the unsafe 641251881Speter data "non-ASCII", even though the actual constraint is 642251881Speter somewhat more complex than that. */ 643251881Speter 644251881Speter if (data - data_start) 645251881Speter { 646251881Speter const char *error_data 647251881Speter = apr_pstrndup(pool, data_start, (data - data_start)); 648251881Speter 649251881Speter return svn_error_createf 650251881Speter (APR_EINVAL, NULL, 651251881Speter _("Safe data '%s' was followed by non-ASCII byte %d: " 652251881Speter "unable to convert to/from UTF-8"), 653251881Speter error_data, *((const unsigned char *) data)); 654251881Speter } 655251881Speter else 656251881Speter { 657251881Speter return svn_error_createf 658251881Speter (APR_EINVAL, NULL, 659251881Speter _("Non-ASCII character (code %d) detected, " 660251881Speter "and unable to convert to/from UTF-8"), 661251881Speter *((const unsigned char *) data)); 662251881Speter } 663251881Speter } 664251881Speter } 665251881Speter 666251881Speter return SVN_NO_ERROR; 667251881Speter} 668251881Speter 669251881Speter/* Construct an error with code APR_EINVAL and with a suitable message 670251881Speter * to describe the invalid UTF-8 sequence DATA of length LEN (which 671251881Speter * may have embedded NULLs). We can't simply print the data, almost 672251881Speter * by definition we don't really know how it is encoded. 673251881Speter */ 674251881Speterstatic svn_error_t * 675251881Speterinvalid_utf8(const char *data, apr_size_t len, apr_pool_t *pool) 676251881Speter{ 677251881Speter const char *last = svn_utf__last_valid(data, len); 678251881Speter const char *valid_txt = "", *invalid_txt = ""; 679251881Speter apr_size_t i; 680251881Speter size_t valid, invalid; 681251881Speter 682251881Speter /* We will display at most 24 valid octets (this may split a leading 683251881Speter multi-byte character) as that should fit on one 80 character line. */ 684251881Speter valid = last - data; 685251881Speter if (valid > 24) 686251881Speter valid = 24; 687251881Speter for (i = 0; i < valid; ++i) 688251881Speter valid_txt = apr_pstrcat(pool, valid_txt, 689251881Speter apr_psprintf(pool, " %02x", 690251881Speter (unsigned char)last[i-valid]), 691251881Speter (char *)NULL); 692251881Speter 693251881Speter /* 4 invalid octets will guarantee that the faulty octet is displayed */ 694251881Speter invalid = data + len - last; 695251881Speter if (invalid > 4) 696251881Speter invalid = 4; 697251881Speter for (i = 0; i < invalid; ++i) 698251881Speter invalid_txt = apr_pstrcat(pool, invalid_txt, 699251881Speter apr_psprintf(pool, " %02x", 700251881Speter (unsigned char)last[i]), 701251881Speter (char *)NULL); 702251881Speter 703251881Speter return svn_error_createf(APR_EINVAL, NULL, 704251881Speter _("Valid UTF-8 data\n(hex:%s)\n" 705251881Speter "followed by invalid UTF-8 sequence\n(hex:%s)"), 706251881Speter valid_txt, invalid_txt); 707251881Speter} 708251881Speter 709251881Speter/* Verify that the sequence DATA of length LEN is valid UTF-8. 710251881Speter If it is not, return an error with code APR_EINVAL. */ 711251881Speterstatic svn_error_t * 712251881Spetercheck_utf8(const char *data, apr_size_t len, apr_pool_t *pool) 713251881Speter{ 714251881Speter if (! svn_utf__is_valid(data, len)) 715251881Speter return invalid_utf8(data, len, pool); 716251881Speter return SVN_NO_ERROR; 717251881Speter} 718251881Speter 719251881Speter/* Verify that the NULL terminated sequence DATA is valid UTF-8. 720251881Speter If it is not, return an error with code APR_EINVAL. */ 721251881Speterstatic svn_error_t * 722251881Spetercheck_cstring_utf8(const char *data, apr_pool_t *pool) 723251881Speter{ 724251881Speter 725251881Speter if (! svn_utf__cstring_is_valid(data)) 726251881Speter return invalid_utf8(data, strlen(data), pool); 727251881Speter return SVN_NO_ERROR; 728251881Speter} 729251881Speter 730251881Speter 731251881Spetersvn_error_t * 732251881Spetersvn_utf_stringbuf_to_utf8(svn_stringbuf_t **dest, 733251881Speter const svn_stringbuf_t *src, 734251881Speter apr_pool_t *pool) 735251881Speter{ 736251881Speter xlate_handle_node_t *node; 737251881Speter svn_error_t *err; 738251881Speter 739251881Speter SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); 740251881Speter 741251881Speter if (node->handle) 742251881Speter { 743251881Speter err = convert_to_stringbuf(node, src->data, src->len, dest, pool); 744251881Speter if (! err) 745251881Speter err = check_utf8((*dest)->data, (*dest)->len, pool); 746251881Speter } 747251881Speter else 748251881Speter { 749251881Speter err = check_non_ascii(src->data, src->len, pool); 750251881Speter if (! err) 751251881Speter *dest = svn_stringbuf_dup(src, pool); 752251881Speter } 753251881Speter 754251881Speter return svn_error_compose_create(err, 755251881Speter put_xlate_handle_node 756251881Speter (node, 757251881Speter SVN_UTF_NTOU_XLATE_HANDLE, 758251881Speter pool)); 759251881Speter} 760251881Speter 761251881Speter 762251881Spetersvn_error_t * 763251881Spetersvn_utf_string_to_utf8(const svn_string_t **dest, 764251881Speter const svn_string_t *src, 765251881Speter apr_pool_t *pool) 766251881Speter{ 767251881Speter svn_stringbuf_t *destbuf; 768251881Speter xlate_handle_node_t *node; 769251881Speter svn_error_t *err; 770251881Speter 771251881Speter SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); 772251881Speter 773251881Speter if (node->handle) 774251881Speter { 775251881Speter err = convert_to_stringbuf(node, src->data, src->len, &destbuf, pool); 776251881Speter if (! err) 777251881Speter err = check_utf8(destbuf->data, destbuf->len, pool); 778251881Speter if (! err) 779251881Speter *dest = svn_stringbuf__morph_into_string(destbuf); 780251881Speter } 781251881Speter else 782251881Speter { 783251881Speter err = check_non_ascii(src->data, src->len, pool); 784251881Speter if (! err) 785251881Speter *dest = svn_string_dup(src, pool); 786251881Speter } 787251881Speter 788251881Speter return svn_error_compose_create(err, 789251881Speter put_xlate_handle_node 790251881Speter (node, 791251881Speter SVN_UTF_NTOU_XLATE_HANDLE, 792251881Speter pool)); 793251881Speter} 794251881Speter 795251881Speter 796251881Speter/* Common implementation for svn_utf_cstring_to_utf8, 797251881Speter svn_utf_cstring_to_utf8_ex, svn_utf_cstring_from_utf8 and 798251881Speter svn_utf_cstring_from_utf8_ex. Convert SRC to DEST using NODE->handle as 799251881Speter the translator and allocating from POOL. */ 800251881Speterstatic svn_error_t * 801251881Speterconvert_cstring(const char **dest, 802251881Speter const char *src, 803251881Speter xlate_handle_node_t *node, 804251881Speter apr_pool_t *pool) 805251881Speter{ 806251881Speter if (node->handle) 807251881Speter { 808251881Speter svn_stringbuf_t *destbuf; 809251881Speter SVN_ERR(convert_to_stringbuf(node, src, strlen(src), 810251881Speter &destbuf, pool)); 811251881Speter *dest = destbuf->data; 812251881Speter } 813251881Speter else 814251881Speter { 815251881Speter apr_size_t len = strlen(src); 816251881Speter SVN_ERR(check_non_ascii(src, len, pool)); 817251881Speter *dest = apr_pstrmemdup(pool, src, len); 818251881Speter } 819251881Speter return SVN_NO_ERROR; 820251881Speter} 821251881Speter 822251881Speter 823251881Spetersvn_error_t * 824251881Spetersvn_utf_cstring_to_utf8(const char **dest, 825251881Speter const char *src, 826251881Speter apr_pool_t *pool) 827251881Speter{ 828251881Speter xlate_handle_node_t *node; 829251881Speter svn_error_t *err; 830251881Speter 831251881Speter SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); 832251881Speter err = convert_cstring(dest, src, node, pool); 833251881Speter SVN_ERR(svn_error_compose_create(err, 834251881Speter put_xlate_handle_node 835251881Speter (node, 836251881Speter SVN_UTF_NTOU_XLATE_HANDLE, 837251881Speter pool))); 838251881Speter return check_cstring_utf8(*dest, pool); 839251881Speter} 840251881Speter 841251881Speter 842251881Spetersvn_error_t * 843251881Spetersvn_utf_cstring_to_utf8_ex2(const char **dest, 844251881Speter const char *src, 845251881Speter const char *frompage, 846251881Speter apr_pool_t *pool) 847251881Speter{ 848251881Speter xlate_handle_node_t *node; 849251881Speter svn_error_t *err; 850251881Speter const char *convset_key = get_xlate_key(SVN_APR_UTF8_CHARSET, frompage, 851251881Speter pool); 852251881Speter 853251881Speter SVN_ERR(get_xlate_handle_node(&node, SVN_APR_UTF8_CHARSET, frompage, 854251881Speter convset_key, pool)); 855251881Speter err = convert_cstring(dest, src, node, pool); 856251881Speter SVN_ERR(svn_error_compose_create(err, 857251881Speter put_xlate_handle_node 858251881Speter (node, 859251881Speter SVN_UTF_NTOU_XLATE_HANDLE, 860251881Speter pool))); 861251881Speter 862251881Speter return check_cstring_utf8(*dest, pool); 863251881Speter} 864251881Speter 865251881Speter 866251881Spetersvn_error_t * 867251881Spetersvn_utf_cstring_to_utf8_ex(const char **dest, 868251881Speter const char *src, 869251881Speter const char *frompage, 870251881Speter const char *convset_key, 871251881Speter apr_pool_t *pool) 872251881Speter{ 873251881Speter return svn_utf_cstring_to_utf8_ex2(dest, src, frompage, pool); 874251881Speter} 875251881Speter 876251881Speter 877251881Spetersvn_error_t * 878251881Spetersvn_utf_stringbuf_from_utf8(svn_stringbuf_t **dest, 879251881Speter const svn_stringbuf_t *src, 880251881Speter apr_pool_t *pool) 881251881Speter{ 882251881Speter xlate_handle_node_t *node; 883251881Speter svn_error_t *err; 884251881Speter 885251881Speter SVN_ERR(get_uton_xlate_handle_node(&node, pool)); 886251881Speter 887251881Speter if (node->handle) 888251881Speter { 889251881Speter err = check_utf8(src->data, src->len, pool); 890251881Speter if (! err) 891251881Speter err = convert_to_stringbuf(node, src->data, src->len, dest, pool); 892251881Speter } 893251881Speter else 894251881Speter { 895251881Speter err = check_non_ascii(src->data, src->len, pool); 896251881Speter if (! err) 897251881Speter *dest = svn_stringbuf_dup(src, pool); 898251881Speter } 899251881Speter 900251881Speter err = svn_error_compose_create( 901251881Speter err, 902251881Speter put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); 903251881Speter 904251881Speter return err; 905251881Speter} 906251881Speter 907251881Speter 908251881Spetersvn_error_t * 909251881Spetersvn_utf_string_from_utf8(const svn_string_t **dest, 910251881Speter const svn_string_t *src, 911251881Speter apr_pool_t *pool) 912251881Speter{ 913251881Speter svn_stringbuf_t *dbuf; 914251881Speter xlate_handle_node_t *node; 915251881Speter svn_error_t *err; 916251881Speter 917251881Speter SVN_ERR(get_uton_xlate_handle_node(&node, pool)); 918251881Speter 919251881Speter if (node->handle) 920251881Speter { 921251881Speter err = check_utf8(src->data, src->len, pool); 922251881Speter if (! err) 923251881Speter err = convert_to_stringbuf(node, src->data, src->len, 924251881Speter &dbuf, pool); 925251881Speter if (! err) 926251881Speter *dest = svn_stringbuf__morph_into_string(dbuf); 927251881Speter } 928251881Speter else 929251881Speter { 930251881Speter err = check_non_ascii(src->data, src->len, pool); 931251881Speter if (! err) 932251881Speter *dest = svn_string_dup(src, pool); 933251881Speter } 934251881Speter 935251881Speter err = svn_error_compose_create( 936251881Speter err, 937251881Speter put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); 938251881Speter 939251881Speter return err; 940251881Speter} 941251881Speter 942251881Speter 943251881Spetersvn_error_t * 944251881Spetersvn_utf_cstring_from_utf8(const char **dest, 945251881Speter const char *src, 946251881Speter apr_pool_t *pool) 947251881Speter{ 948251881Speter xlate_handle_node_t *node; 949251881Speter svn_error_t *err; 950251881Speter 951251881Speter SVN_ERR(check_cstring_utf8(src, pool)); 952251881Speter 953251881Speter SVN_ERR(get_uton_xlate_handle_node(&node, pool)); 954251881Speter err = convert_cstring(dest, src, node, pool); 955251881Speter err = svn_error_compose_create( 956251881Speter err, 957251881Speter put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); 958251881Speter 959251881Speter return err; 960251881Speter} 961251881Speter 962251881Speter 963251881Spetersvn_error_t * 964251881Spetersvn_utf_cstring_from_utf8_ex2(const char **dest, 965251881Speter const char *src, 966251881Speter const char *topage, 967251881Speter apr_pool_t *pool) 968251881Speter{ 969251881Speter xlate_handle_node_t *node; 970251881Speter svn_error_t *err; 971251881Speter const char *convset_key = get_xlate_key(topage, SVN_APR_UTF8_CHARSET, 972251881Speter pool); 973251881Speter 974251881Speter SVN_ERR(check_cstring_utf8(src, pool)); 975251881Speter 976251881Speter SVN_ERR(get_xlate_handle_node(&node, topage, SVN_APR_UTF8_CHARSET, 977251881Speter convset_key, pool)); 978251881Speter err = convert_cstring(dest, src, node, pool); 979251881Speter err = svn_error_compose_create( 980251881Speter err, 981251881Speter put_xlate_handle_node(node, convset_key, pool)); 982251881Speter 983251881Speter return err; 984251881Speter} 985251881Speter 986251881Speter 987251881Spetersvn_error_t * 988251881Spetersvn_utf_cstring_from_utf8_ex(const char **dest, 989251881Speter const char *src, 990251881Speter const char *topage, 991251881Speter const char *convset_key, 992251881Speter apr_pool_t *pool) 993251881Speter{ 994251881Speter return svn_utf_cstring_from_utf8_ex2(dest, src, topage, pool); 995251881Speter} 996251881Speter 997251881Speter 998251881Speterconst char * 999251881Spetersvn_utf__cstring_from_utf8_fuzzy(const char *src, 1000251881Speter apr_pool_t *pool, 1001251881Speter svn_error_t *(*convert_from_utf8) 1002251881Speter (const char **, const char *, apr_pool_t *)) 1003251881Speter{ 1004251881Speter const char *escaped, *converted; 1005251881Speter svn_error_t *err; 1006251881Speter 1007251881Speter escaped = fuzzy_escape(src, strlen(src), pool); 1008251881Speter 1009251881Speter /* Okay, now we have a *new* UTF-8 string, one that's guaranteed to 1010251881Speter contain only 7-bit bytes :-). Recode to native... */ 1011251881Speter err = convert_from_utf8(((const char **) &converted), escaped, pool); 1012251881Speter 1013251881Speter if (err) 1014251881Speter { 1015251881Speter svn_error_clear(err); 1016251881Speter return escaped; 1017251881Speter } 1018251881Speter else 1019251881Speter return converted; 1020251881Speter 1021251881Speter /* ### Check the client locale, maybe we can avoid that second 1022251881Speter * conversion! See Ulrich Drepper's patch at 1023251881Speter * http://subversion.tigris.org/issues/show_bug.cgi?id=807. 1024251881Speter */ 1025251881Speter} 1026251881Speter 1027251881Speter 1028251881Speterconst char * 1029251881Spetersvn_utf_cstring_from_utf8_fuzzy(const char *src, 1030251881Speter apr_pool_t *pool) 1031251881Speter{ 1032251881Speter return svn_utf__cstring_from_utf8_fuzzy(src, pool, 1033251881Speter svn_utf_cstring_from_utf8); 1034251881Speter} 1035251881Speter 1036251881Speter 1037251881Spetersvn_error_t * 1038251881Spetersvn_utf_cstring_from_utf8_stringbuf(const char **dest, 1039251881Speter const svn_stringbuf_t *src, 1040251881Speter apr_pool_t *pool) 1041251881Speter{ 1042251881Speter svn_stringbuf_t *destbuf; 1043251881Speter 1044251881Speter SVN_ERR(svn_utf_stringbuf_from_utf8(&destbuf, src, pool)); 1045251881Speter *dest = destbuf->data; 1046251881Speter 1047251881Speter return SVN_NO_ERROR; 1048251881Speter} 1049251881Speter 1050251881Speter 1051251881Spetersvn_error_t * 1052251881Spetersvn_utf_cstring_from_utf8_string(const char **dest, 1053251881Speter const svn_string_t *src, 1054251881Speter apr_pool_t *pool) 1055251881Speter{ 1056251881Speter svn_stringbuf_t *dbuf; 1057251881Speter xlate_handle_node_t *node; 1058251881Speter svn_error_t *err; 1059251881Speter 1060251881Speter SVN_ERR(get_uton_xlate_handle_node(&node, pool)); 1061251881Speter 1062251881Speter if (node->handle) 1063251881Speter { 1064251881Speter err = check_utf8(src->data, src->len, pool); 1065251881Speter if (! err) 1066251881Speter err = convert_to_stringbuf(node, src->data, src->len, 1067251881Speter &dbuf, pool); 1068251881Speter if (! err) 1069251881Speter *dest = dbuf->data; 1070251881Speter } 1071251881Speter else 1072251881Speter { 1073251881Speter err = check_non_ascii(src->data, src->len, pool); 1074251881Speter if (! err) 1075251881Speter *dest = apr_pstrmemdup(pool, src->data, src->len); 1076251881Speter } 1077251881Speter 1078251881Speter err = svn_error_compose_create( 1079251881Speter err, 1080251881Speter put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); 1081251881Speter 1082251881Speter return err; 1083251881Speter} 1084