serf.c revision 253734
1/* 2 * serf.c : entry point for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#define APR_WANT_STRFUNC 27#include <apr_want.h> 28 29#include <apr_uri.h> 30#include <serf.h> 31 32#include "svn_pools.h" 33#include "svn_ra.h" 34#include "svn_dav.h" 35#include "svn_xml.h" 36#include "../libsvn_ra/ra_loader.h" 37#include "svn_config.h" 38#include "svn_delta.h" 39#include "svn_dirent_uri.h" 40#include "svn_hash.h" 41#include "svn_path.h" 42#include "svn_time.h" 43#include "svn_version.h" 44 45#include "private/svn_dav_protocol.h" 46#include "private/svn_dep_compat.h" 47#include "private/svn_fspath.h" 48#include "private/svn_subr_private.h" 49#include "svn_private_config.h" 50 51#include "ra_serf.h" 52 53 54/* Implements svn_ra__vtable_t.get_version(). */ 55static const svn_version_t * 56ra_serf_version(void) 57{ 58 SVN_VERSION_BODY; 59} 60 61#define RA_SERF_DESCRIPTION \ 62 N_("Module for accessing a repository via WebDAV protocol using serf.") 63 64/* Implements svn_ra__vtable_t.get_description(). */ 65static const char * 66ra_serf_get_description(void) 67{ 68 return _(RA_SERF_DESCRIPTION); 69} 70 71/* Implements svn_ra__vtable_t.get_schemes(). */ 72static const char * const * 73ra_serf_get_schemes(apr_pool_t *pool) 74{ 75 static const char *serf_ssl[] = { "http", "https", NULL }; 76#if 0 77 /* ### Temporary: to shut up a warning. */ 78 static const char *serf_no_ssl[] = { "http", NULL }; 79#endif 80 81 /* TODO: Runtime detection. */ 82 return serf_ssl; 83} 84 85/* Load the setting http-auth-types from the global or server specific 86 section, parse its value and set the types of authentication we should 87 accept from the server. */ 88static svn_error_t * 89load_http_auth_types(apr_pool_t *pool, svn_config_t *config, 90 const char *server_group, 91 int *authn_types) 92{ 93 const char *http_auth_types = NULL; 94 *authn_types = SERF_AUTHN_NONE; 95 96 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL, 97 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL); 98 99 if (server_group) 100 { 101 svn_config_get(config, &http_auth_types, server_group, 102 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types); 103 } 104 105 if (http_auth_types) 106 { 107 char *token; 108 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1); 109 apr_collapse_spaces(auth_types_list, http_auth_types); 110 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL) 111 { 112 if (svn_cstring_casecmp("basic", token) == 0) 113 *authn_types |= SERF_AUTHN_BASIC; 114 else if (svn_cstring_casecmp("digest", token) == 0) 115 *authn_types |= SERF_AUTHN_DIGEST; 116 else if (svn_cstring_casecmp("ntlm", token) == 0) 117 *authn_types |= SERF_AUTHN_NTLM; 118 else if (svn_cstring_casecmp("negotiate", token) == 0) 119 *authn_types |= SERF_AUTHN_NEGOTIATE; 120 else 121 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 122 _("Invalid config: unknown %s " 123 "'%s'"), 124 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token); 125 } 126 } 127 else 128 { 129 /* Nothing specified by the user, so accept all types. */ 130 *authn_types = SERF_AUTHN_ALL; 131 } 132 133 return SVN_NO_ERROR; 134} 135 136/* Default HTTP timeout (in seconds); overridden by the 'http-timeout' 137 runtime configuration variable. */ 138#define DEFAULT_HTTP_TIMEOUT 600 139 140/* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */ 141#define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests" 142 143 144static svn_error_t * 145load_config(svn_ra_serf__session_t *session, 146 apr_hash_t *config_hash, 147 apr_pool_t *pool) 148{ 149 svn_config_t *config, *config_client; 150 const char *server_group; 151 const char *proxy_host = NULL; 152 const char *port_str = NULL; 153 const char *timeout_str = NULL; 154 const char *exceptions; 155 apr_port_t proxy_port; 156 svn_tristate_t chunked_requests; 157 158 if (config_hash) 159 { 160 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS); 161 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG); 162 } 163 else 164 { 165 config = NULL; 166 config_client = NULL; 167 } 168 169 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 170 SVN_CONFIG_SECTION_GLOBAL, 171 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE)); 172 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, 173 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); 174 175 if (session->wc_callbacks->auth_baton) 176 { 177 if (config_client) 178 { 179 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 180 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, 181 config_client); 182 } 183 if (config) 184 { 185 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 186 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, 187 config); 188 } 189 } 190 191 /* Use the default proxy-specific settings if and only if 192 "http-proxy-exceptions" is not set to exclude this host. */ 193 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL, 194 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, ""); 195 if (! svn_cstring_match_glob_list(session->session_url.hostname, 196 svn_cstring_split(exceptions, ",", 197 TRUE, pool))) 198 { 199 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL, 200 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL); 201 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL, 202 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL); 203 svn_config_get(config, &session->proxy_username, 204 SVN_CONFIG_SECTION_GLOBAL, 205 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL); 206 svn_config_get(config, &session->proxy_password, 207 SVN_CONFIG_SECTION_GLOBAL, 208 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL); 209 } 210 211 /* Load the global ssl settings, if set. */ 212 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 213 SVN_CONFIG_SECTION_GLOBAL, 214 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 215 TRUE)); 216 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL, 217 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL); 218 219 /* If set, read the flag that tells us to do bulk updates or not. Defaults 220 to skelta updates. */ 221 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 222 SVN_CONFIG_SECTION_GLOBAL, 223 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 224 "auto", 225 svn_tristate_unknown)); 226 227 /* Load the maximum number of parallel session connections. */ 228 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 229 SVN_CONFIG_SECTION_GLOBAL, 230 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 231 SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS)); 232 233 /* Should we use chunked transfer encoding. */ 234 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 235 SVN_CONFIG_SECTION_GLOBAL, 236 OPTION_HTTP_CHUNKED_REQUESTS, 237 "auto", svn_tristate_unknown)); 238 239 if (config) 240 server_group = svn_config_find_group(config, 241 session->session_url.hostname, 242 SVN_CONFIG_SECTION_GROUPS, pool); 243 else 244 server_group = NULL; 245 246 if (server_group) 247 { 248 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 249 server_group, 250 SVN_CONFIG_OPTION_HTTP_COMPRESSION, 251 session->using_compression)); 252 svn_config_get(config, &timeout_str, server_group, 253 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); 254 255 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 256 SVN_AUTH_PARAM_SERVER_GROUP, server_group); 257 258 /* Load the group proxy server settings, overriding global 259 settings. We intentionally ignore 'http-proxy-exceptions' 260 here because, well, if this site was an exception, why is 261 there a per-server proxy configuration for it? */ 262 svn_config_get(config, &proxy_host, server_group, 263 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host); 264 svn_config_get(config, &port_str, server_group, 265 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str); 266 svn_config_get(config, &session->proxy_username, server_group, 267 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, 268 session->proxy_username); 269 svn_config_get(config, &session->proxy_password, server_group, 270 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, 271 session->proxy_password); 272 273 /* Load the group ssl settings. */ 274 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 275 server_group, 276 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 277 session->trust_default_ca)); 278 svn_config_get(config, &session->ssl_authorities, server_group, 279 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, 280 session->ssl_authorities); 281 282 /* Load the group bulk updates flag. */ 283 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 284 server_group, 285 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 286 "auto", 287 session->bulk_updates)); 288 289 /* Load the maximum number of parallel session connections, 290 overriding global values. */ 291 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 292 server_group, 293 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 294 session->max_connections)); 295 296 /* Should we use chunked transfer encoding. */ 297 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 298 server_group, 299 OPTION_HTTP_CHUNKED_REQUESTS, 300 "auto", chunked_requests)); 301 } 302 303 /* Don't allow the http-max-connections value to be larger than our 304 compiled-in limit, or to be too small to operate. Broken 305 functionality and angry administrators are equally undesirable. */ 306 if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT) 307 session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT; 308 if (session->max_connections < 2) 309 session->max_connections = 2; 310 311 /* Parse the connection timeout value, if any. */ 312 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT); 313 if (timeout_str) 314 { 315 char *endstr; 316 const long int timeout = strtol(timeout_str, &endstr, 10); 317 318 if (*endstr) 319 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 320 _("Invalid config: illegal character in " 321 "timeout value")); 322 if (timeout < 0) 323 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 324 _("Invalid config: negative timeout value")); 325 session->timeout = apr_time_from_sec(timeout); 326 } 327 SVN_ERR_ASSERT(session->timeout >= 0); 328 329 /* Convert the proxy port value, if any. */ 330 if (port_str) 331 { 332 char *endstr; 333 const long int port = strtol(port_str, &endstr, 10); 334 335 if (*endstr) 336 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 337 _("Invalid URL: illegal character in proxy " 338 "port number")); 339 if (port < 0) 340 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 341 _("Invalid URL: negative proxy port number")); 342 if (port > 65535) 343 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 344 _("Invalid URL: proxy port number greater " 345 "than maximum TCP port number 65535")); 346 proxy_port = (apr_port_t) port; 347 } 348 else 349 { 350 proxy_port = 80; 351 } 352 353 if (proxy_host) 354 { 355 apr_sockaddr_t *proxy_addr; 356 apr_status_t status; 357 358 status = apr_sockaddr_info_get(&proxy_addr, proxy_host, 359 APR_UNSPEC, proxy_port, 0, 360 session->pool); 361 if (status) 362 { 363 return svn_ra_serf__wrap_err( 364 status, _("Could not resolve proxy server '%s'"), 365 proxy_host); 366 } 367 session->using_proxy = TRUE; 368 serf_config_proxy(session->context, proxy_addr); 369 } 370 else 371 { 372 session->using_proxy = FALSE; 373 } 374 375 /* Setup detect_chunking and using_chunked_requests based on 376 * the chunked_requests tristate */ 377 if (chunked_requests == svn_tristate_unknown) 378 { 379 session->detect_chunking = TRUE; 380 session->using_chunked_requests = TRUE; 381 } 382 else if (chunked_requests == svn_tristate_true) 383 { 384 session->detect_chunking = FALSE; 385 session->using_chunked_requests = TRUE; 386 } 387 else /* chunked_requests == svn_tristate_false */ 388 { 389 session->detect_chunking = FALSE; 390 session->using_chunked_requests = FALSE; 391 } 392 393 /* Setup authentication. */ 394 SVN_ERR(load_http_auth_types(pool, config, server_group, 395 &session->authn_types)); 396 serf_config_authn_types(session->context, session->authn_types); 397 serf_config_credentials_callback(session->context, 398 svn_ra_serf__credentials_callback); 399 400 return SVN_NO_ERROR; 401} 402#undef DEFAULT_HTTP_TIMEOUT 403 404static void 405svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written) 406{ 407 const svn_ra_serf__session_t *serf_sess = progress_baton; 408 if (serf_sess->progress_func) 409 { 410 serf_sess->progress_func(read + written, -1, 411 serf_sess->progress_baton, 412 serf_sess->pool); 413 } 414} 415 416/* Implements svn_ra__vtable_t.open_session(). */ 417static svn_error_t * 418svn_ra_serf__open(svn_ra_session_t *session, 419 const char **corrected_url, 420 const char *session_URL, 421 const svn_ra_callbacks2_t *callbacks, 422 void *callback_baton, 423 apr_hash_t *config, 424 apr_pool_t *pool) 425{ 426 apr_status_t status; 427 svn_ra_serf__session_t *serf_sess; 428 apr_uri_t url; 429 const char *client_string = NULL; 430 svn_error_t *err; 431 432 if (corrected_url) 433 *corrected_url = NULL; 434 435 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess)); 436 serf_sess->pool = svn_pool_create(pool); 437 serf_sess->wc_callbacks = callbacks; 438 serf_sess->wc_callback_baton = callback_baton; 439 serf_sess->progress_func = callbacks->progress_func; 440 serf_sess->progress_baton = callbacks->progress_baton; 441 serf_sess->cancel_func = callbacks->cancel_func; 442 serf_sess->cancel_baton = callback_baton; 443 444 /* todo: reuse serf context across sessions */ 445 serf_sess->context = serf_context_create(serf_sess->pool); 446 447 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache, 448 serf_sess->pool)); 449 450 451 status = apr_uri_parse(serf_sess->pool, session_URL, &url); 452 if (status) 453 { 454 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 455 _("Illegal URL '%s'"), 456 session_URL); 457 } 458 /* Depending the version of apr-util in use, for root paths url.path 459 will be NULL or "", where serf requires "/". */ 460 if (url.path == NULL || url.path[0] == '\0') 461 { 462 url.path = apr_pstrdup(serf_sess->pool, "/"); 463 } 464 if (!url.port) 465 { 466 url.port = apr_uri_port_of_scheme(url.scheme); 467 } 468 serf_sess->session_url = url; 469 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL); 470 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0); 471 472 serf_sess->supports_deadprop_count = svn_tristate_unknown; 473 474 serf_sess->capabilities = apr_hash_make(serf_sess->pool); 475 476 /* We have to assume that the server only supports HTTP/1.0. Once it's clear 477 HTTP/1.1 is supported, we can upgrade. */ 478 serf_sess->http10 = TRUE; 479 480 /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable 481 this, if we find an intervening proxy does not support chunked requests. */ 482 serf_sess->using_chunked_requests = TRUE; 483 484 SVN_ERR(load_config(serf_sess, config, serf_sess->pool)); 485 486 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool, 487 sizeof(*serf_sess->conns[0])); 488 serf_sess->conns[0]->bkt_alloc = 489 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); 490 serf_sess->conns[0]->session = serf_sess; 491 serf_sess->conns[0]->last_status_code = -1; 492 493 /* create the user agent string */ 494 if (callbacks->get_client_string) 495 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool)); 496 497 if (client_string) 498 serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ", 499 client_string, (char *)NULL); 500 else 501 serf_sess->useragent = USER_AGENT; 502 503 /* go ahead and tell serf about the connection. */ 504 status = 505 serf_connection_create2(&serf_sess->conns[0]->conn, 506 serf_sess->context, 507 url, 508 svn_ra_serf__conn_setup, serf_sess->conns[0], 509 svn_ra_serf__conn_closed, serf_sess->conns[0], 510 serf_sess->pool); 511 if (status) 512 return svn_ra_serf__wrap_err(status, NULL); 513 514 /* Set the progress callback. */ 515 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress, 516 serf_sess); 517 518 serf_sess->num_conns = 1; 519 520 session->priv = serf_sess; 521 522 err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool); 523 524 /* serf should produce a usable error code instead of APR_EGENERAL */ 525 if (err && err->apr_err == APR_EGENERAL) 526 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err, 527 _("Connection to '%s' failed"), session_URL); 528 SVN_ERR(err); 529 530 /* We have set up a useful connection (that doesn't indication a redirect). 531 If we've been told there is possibly a worrisome proxy in our path to the 532 server AND we switched to HTTP/1.1 (chunked requests), then probe for 533 problems in any proxy. */ 534 if ((corrected_url == NULL || *corrected_url == NULL) 535 && serf_sess->detect_chunking && !serf_sess->http10) 536 SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool)); 537 538 return SVN_NO_ERROR; 539} 540 541/* Implements svn_ra__vtable_t.reparent(). */ 542static svn_error_t * 543svn_ra_serf__reparent(svn_ra_session_t *ra_session, 544 const char *url, 545 apr_pool_t *pool) 546{ 547 svn_ra_serf__session_t *session = ra_session->priv; 548 apr_uri_t new_url; 549 apr_status_t status; 550 551 /* If it's the URL we already have, wave our hands and do nothing. */ 552 if (strcmp(session->session_url_str, url) == 0) 553 { 554 return SVN_NO_ERROR; 555 } 556 557 if (!session->repos_root_str) 558 { 559 const char *vcc_url; 560 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 561 } 562 563 if (!svn_uri__is_ancestor(session->repos_root_str, url)) 564 { 565 return svn_error_createf( 566 SVN_ERR_RA_ILLEGAL_URL, NULL, 567 _("URL '%s' is not a child of the session's repository root " 568 "URL '%s'"), url, session->repos_root_str); 569 } 570 571 status = apr_uri_parse(pool, url, &new_url); 572 if (status) 573 { 574 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 575 _("Illegal repository URL '%s'"), url); 576 } 577 578 /* Depending the version of apr-util in use, for root paths url.path 579 will be NULL or "", where serf requires "/". */ 580 /* ### Maybe we should use a string buffer for these strings so we 581 ### don't allocate memory in the session on every reparent? */ 582 if (new_url.path == NULL || new_url.path[0] == '\0') 583 { 584 session->session_url.path = apr_pstrdup(session->pool, "/"); 585 } 586 else 587 { 588 session->session_url.path = apr_pstrdup(session->pool, new_url.path); 589 } 590 session->session_url_str = apr_pstrdup(session->pool, url); 591 592 return SVN_NO_ERROR; 593} 594 595/* Implements svn_ra__vtable_t.get_session_url(). */ 596static svn_error_t * 597svn_ra_serf__get_session_url(svn_ra_session_t *ra_session, 598 const char **url, 599 apr_pool_t *pool) 600{ 601 svn_ra_serf__session_t *session = ra_session->priv; 602 *url = apr_pstrdup(pool, session->session_url_str); 603 return SVN_NO_ERROR; 604} 605 606/* Implements svn_ra__vtable_t.get_latest_revnum(). */ 607static svn_error_t * 608svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session, 609 svn_revnum_t *latest_revnum, 610 apr_pool_t *pool) 611{ 612 svn_ra_serf__session_t *session = ra_session->priv; 613 614 return svn_error_trace(svn_ra_serf__get_youngest_revnum( 615 latest_revnum, session, pool)); 616} 617 618/* Implements svn_ra__vtable_t.rev_proplist(). */ 619static svn_error_t * 620svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, 621 svn_revnum_t rev, 622 apr_hash_t **ret_props, 623 apr_pool_t *pool) 624{ 625 svn_ra_serf__session_t *session = ra_session->priv; 626 apr_hash_t *props; 627 const char *propfind_path; 628 629 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 630 { 631 propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev); 632 633 /* svn_ra_serf__retrieve_props() wants to added the revision as 634 a Label to the PROPFIND, which isn't really necessary when 635 querying a rev-stub URI. *Shrug* Probably okay to leave the 636 Label, but whatever. */ 637 rev = SVN_INVALID_REVNUM; 638 } 639 else 640 { 641 /* Use the VCC as the propfind target path. */ 642 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool)); 643 } 644 645 /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */ 646 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 647 propfind_path, rev, "0", all_props, 648 pool, pool)); 649 650 SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props, 651 pool, pool)); 652 653 return SVN_NO_ERROR; 654} 655 656/* Implements svn_ra__vtable_t.rev_prop(). */ 657static svn_error_t * 658svn_ra_serf__rev_prop(svn_ra_session_t *session, 659 svn_revnum_t rev, 660 const char *name, 661 svn_string_t **value, 662 apr_pool_t *pool) 663{ 664 apr_hash_t *props; 665 666 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool)); 667 668 *value = svn_hash_gets(props, name); 669 670 return SVN_NO_ERROR; 671} 672 673static svn_error_t * 674fetch_path_props(apr_hash_t **props, 675 svn_ra_serf__session_t *session, 676 const char *session_relpath, 677 svn_revnum_t revision, 678 const svn_ra_serf__dav_props_t *desired_props, 679 apr_pool_t *result_pool, 680 apr_pool_t *scratch_pool) 681{ 682 const char *url; 683 684 url = session->session_url.path; 685 686 /* If we have a relative path, append it. */ 687 if (session_relpath) 688 url = svn_path_url_add_component2(url, session_relpath, scratch_pool); 689 690 /* If we were given a specific revision, get a URL that refers to that 691 specific revision (rather than floating with HEAD). */ 692 if (SVN_IS_VALID_REVNUM(revision)) 693 { 694 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, 695 session, NULL /* conn */, 696 url, revision, 697 scratch_pool, scratch_pool)); 698 } 699 700 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. 701 Or we started with SVN_INVALID_REVNUM and URL may be floating. */ 702 SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0], 703 url, SVN_INVALID_REVNUM, 704 desired_props, 705 result_pool, scratch_pool)); 706 707 return SVN_NO_ERROR; 708} 709 710/* Implements svn_ra__vtable_t.check_path(). */ 711static svn_error_t * 712svn_ra_serf__check_path(svn_ra_session_t *ra_session, 713 const char *rel_path, 714 svn_revnum_t revision, 715 svn_node_kind_t *kind, 716 apr_pool_t *pool) 717{ 718 svn_ra_serf__session_t *session = ra_session->priv; 719 apr_hash_t *props; 720 721 svn_error_t *err = fetch_path_props(&props, session, rel_path, 722 revision, check_path_props, 723 pool, pool); 724 725 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 726 { 727 svn_error_clear(err); 728 *kind = svn_node_none; 729 } 730 else 731 { 732 /* Any other error, raise to caller. */ 733 if (err) 734 return svn_error_trace(err); 735 736 SVN_ERR(svn_ra_serf__get_resource_type(kind, props)); 737 } 738 739 return SVN_NO_ERROR; 740} 741 742 743struct dirent_walker_baton_t { 744 /* Update the fields in this entry. */ 745 svn_dirent_t *entry; 746 747 svn_tristate_t *supports_deadprop_count; 748 749 /* If allocations are necessary, then use this pool. */ 750 apr_pool_t *result_pool; 751}; 752 753static svn_error_t * 754dirent_walker(void *baton, 755 const char *ns, 756 const char *name, 757 const svn_string_t *val, 758 apr_pool_t *scratch_pool) 759{ 760 struct dirent_walker_baton_t *dwb = baton; 761 762 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 763 { 764 dwb->entry->has_props = TRUE; 765 } 766 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 767 { 768 dwb->entry->has_props = TRUE; 769 } 770 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 771 { 772 if(strcmp(name, "deadprop-count") == 0) 773 { 774 if (*val->data) 775 { 776 apr_int64_t deadprop_count; 777 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); 778 dwb->entry->has_props = deadprop_count > 0; 779 if (dwb->supports_deadprop_count) 780 *dwb->supports_deadprop_count = svn_tristate_true; 781 } 782 else if (dwb->supports_deadprop_count) 783 *dwb->supports_deadprop_count = svn_tristate_false; 784 } 785 } 786 else if (strcmp(ns, "DAV:") == 0) 787 { 788 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 789 { 790 dwb->entry->created_rev = SVN_STR_TO_REV(val->data); 791 } 792 else if (strcmp(name, "creator-displayname") == 0) 793 { 794 dwb->entry->last_author = val->data; 795 } 796 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 797 { 798 SVN_ERR(svn_time_from_cstring(&dwb->entry->time, 799 val->data, 800 dwb->result_pool)); 801 } 802 else if (strcmp(name, "getcontentlength") == 0) 803 { 804 /* 'getcontentlength' property is empty for directories. */ 805 if (val->len) 806 { 807 SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data)); 808 } 809 } 810 else if (strcmp(name, "resourcetype") == 0) 811 { 812 if (strcmp(val->data, "collection") == 0) 813 { 814 dwb->entry->kind = svn_node_dir; 815 } 816 else 817 { 818 dwb->entry->kind = svn_node_file; 819 } 820 } 821 } 822 823 return SVN_NO_ERROR; 824} 825 826struct path_dirent_visitor_t { 827 apr_hash_t *full_paths; 828 apr_hash_t *base_paths; 829 const char *orig_path; 830 svn_tristate_t supports_deadprop_count; 831 apr_pool_t *result_pool; 832}; 833 834static svn_error_t * 835path_dirent_walker(void *baton, 836 const char *path, apr_ssize_t path_len, 837 const char *ns, apr_ssize_t ns_len, 838 const char *name, apr_ssize_t name_len, 839 const svn_string_t *val, 840 apr_pool_t *pool) 841{ 842 struct path_dirent_visitor_t *dirents = baton; 843 struct dirent_walker_baton_t dwb; 844 svn_dirent_t *entry; 845 846 /* Skip our original path. */ 847 if (strcmp(path, dirents->orig_path) == 0) 848 { 849 return SVN_NO_ERROR; 850 } 851 852 entry = apr_hash_get(dirents->full_paths, path, path_len); 853 854 if (!entry) 855 { 856 const char *base_name; 857 858 entry = svn_dirent_create(pool); 859 860 apr_hash_set(dirents->full_paths, path, path_len, entry); 861 862 base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool), 863 pool); 864 865 svn_hash_sets(dirents->base_paths, base_name, entry); 866 } 867 868 dwb.entry = entry; 869 dwb.supports_deadprop_count = &dirents->supports_deadprop_count; 870 dwb.result_pool = dirents->result_pool; 871 return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool)); 872} 873 874static const svn_ra_serf__dav_props_t * 875get_dirent_props(apr_uint32_t dirent_fields, 876 svn_ra_serf__session_t *session, 877 apr_pool_t *pool) 878{ 879 svn_ra_serf__dav_props_t *prop; 880 apr_array_header_t *props = apr_array_make 881 (pool, 7, sizeof(svn_ra_serf__dav_props_t)); 882 883 if (session->supports_deadprop_count != svn_tristate_false 884 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) 885 { 886 if (dirent_fields & SVN_DIRENT_KIND) 887 { 888 prop = apr_array_push(props); 889 prop->namespace = "DAV:"; 890 prop->name = "resourcetype"; 891 } 892 893 if (dirent_fields & SVN_DIRENT_SIZE) 894 { 895 prop = apr_array_push(props); 896 prop->namespace = "DAV:"; 897 prop->name = "getcontentlength"; 898 } 899 900 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 901 { 902 prop = apr_array_push(props); 903 prop->namespace = SVN_DAV_PROP_NS_DAV; 904 prop->name = "deadprop-count"; 905 } 906 907 if (dirent_fields & SVN_DIRENT_CREATED_REV) 908 { 909 svn_ra_serf__dav_props_t *p = apr_array_push(props); 910 p->namespace = "DAV:"; 911 p->name = SVN_DAV__VERSION_NAME; 912 } 913 914 if (dirent_fields & SVN_DIRENT_TIME) 915 { 916 prop = apr_array_push(props); 917 prop->namespace = "DAV:"; 918 prop->name = SVN_DAV__CREATIONDATE; 919 } 920 921 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 922 { 923 prop = apr_array_push(props); 924 prop->namespace = "DAV:"; 925 prop->name = "creator-displayname"; 926 } 927 } 928 else 929 { 930 /* We found an old subversion server that can't handle 931 the deadprop-count property in the way we expect. 932 933 The neon behavior is to retrieve all properties in this case */ 934 prop = apr_array_push(props); 935 prop->namespace = "DAV:"; 936 prop->name = "allprop"; 937 } 938 939 prop = apr_array_push(props); 940 prop->namespace = NULL; 941 prop->name = NULL; 942 943 return (svn_ra_serf__dav_props_t *) props->elts; 944} 945 946/* Implements svn_ra__vtable_t.stat(). */ 947static svn_error_t * 948svn_ra_serf__stat(svn_ra_session_t *ra_session, 949 const char *rel_path, 950 svn_revnum_t revision, 951 svn_dirent_t **dirent, 952 apr_pool_t *pool) 953{ 954 svn_ra_serf__session_t *session = ra_session->priv; 955 apr_hash_t *props; 956 svn_error_t *err; 957 struct dirent_walker_baton_t dwb; 958 svn_tristate_t deadprop_count = svn_tristate_unknown; 959 960 err = fetch_path_props(&props, 961 session, rel_path, revision, 962 get_dirent_props(SVN_DIRENT_ALL, session, pool), 963 pool, pool); 964 if (err) 965 { 966 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 967 { 968 svn_error_clear(err); 969 *dirent = NULL; 970 return SVN_NO_ERROR; 971 } 972 else 973 return svn_error_trace(err); 974 } 975 976 dwb.entry = svn_dirent_create(pool); 977 dwb.supports_deadprop_count = &deadprop_count; 978 dwb.result_pool = pool; 979 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 980 981 if (deadprop_count == svn_tristate_false 982 && session->supports_deadprop_count == svn_tristate_unknown 983 && !dwb.entry->has_props) 984 { 985 /* We have to requery as the server didn't give us the right 986 information */ 987 session->supports_deadprop_count = svn_tristate_false; 988 989 SVN_ERR(fetch_path_props(&props, 990 session, rel_path, SVN_INVALID_REVNUM, 991 get_dirent_props(SVN_DIRENT_ALL, session, pool), 992 pool, pool)); 993 994 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 995 } 996 997 if (deadprop_count != svn_tristate_unknown) 998 session->supports_deadprop_count = deadprop_count; 999 1000 *dirent = dwb.entry; 1001 1002 return SVN_NO_ERROR; 1003} 1004 1005/* Reads the 'resourcetype' property from the list PROPS and checks if the 1006 * resource at PATH@REVISION really is a directory. Returns 1007 * SVN_ERR_FS_NOT_DIRECTORY if not. 1008 */ 1009static svn_error_t * 1010resource_is_directory(apr_hash_t *props) 1011{ 1012 svn_node_kind_t kind; 1013 1014 SVN_ERR(svn_ra_serf__get_resource_type(&kind, props)); 1015 1016 if (kind != svn_node_dir) 1017 { 1018 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1019 _("Can't get entries of non-directory")); 1020 } 1021 1022 return SVN_NO_ERROR; 1023} 1024 1025/* Implements svn_ra__vtable_t.get_dir(). */ 1026static svn_error_t * 1027svn_ra_serf__get_dir(svn_ra_session_t *ra_session, 1028 apr_hash_t **dirents, 1029 svn_revnum_t *fetched_rev, 1030 apr_hash_t **ret_props, 1031 const char *rel_path, 1032 svn_revnum_t revision, 1033 apr_uint32_t dirent_fields, 1034 apr_pool_t *pool) 1035{ 1036 svn_ra_serf__session_t *session = ra_session->priv; 1037 const char *path; 1038 1039 path = session->session_url.path; 1040 1041 /* If we have a relative path, URI encode and append it. */ 1042 if (rel_path) 1043 { 1044 path = svn_path_url_add_component2(path, rel_path, pool); 1045 } 1046 1047 /* If the user specified a peg revision other than HEAD, we have to fetch 1048 the baseline collection url for that revision. If not, we can use the 1049 public url. */ 1050 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 1051 { 1052 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, 1053 session, NULL /* conn */, 1054 path, revision, 1055 pool, pool)); 1056 revision = SVN_INVALID_REVNUM; 1057 } 1058 /* REVISION is always SVN_INVALID_REVNUM */ 1059 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 1060 1061 /* If we're asked for children, fetch them now. */ 1062 if (dirents) 1063 { 1064 struct path_dirent_visitor_t dirent_walk; 1065 apr_hash_t *props; 1066 const char *rtype; 1067 1068 /* Always request node kind to check that path is really a 1069 * directory. 1070 */ 1071 dirent_fields |= SVN_DIRENT_KIND; 1072 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 1073 path, SVN_INVALID_REVNUM, "1", 1074 get_dirent_props(dirent_fields, 1075 session, pool), 1076 pool, pool)); 1077 1078 /* Check if the path is really a directory. */ 1079 rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype"); 1080 if (rtype == NULL || strcmp(rtype, "collection") != 0) 1081 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1082 _("Can't get entries of non-directory")); 1083 1084 /* We're going to create two hashes to help the walker along. 1085 * We're going to return the 2nd one back to the caller as it 1086 * will have the basenames it expects. 1087 */ 1088 dirent_walk.full_paths = apr_hash_make(pool); 1089 dirent_walk.base_paths = apr_hash_make(pool); 1090 dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool); 1091 dirent_walk.supports_deadprop_count = svn_tristate_unknown; 1092 dirent_walk.result_pool = pool; 1093 1094 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1095 path_dirent_walker, &dirent_walk, 1096 pool)); 1097 1098 if (dirent_walk.supports_deadprop_count == svn_tristate_false 1099 && session->supports_deadprop_count == svn_tristate_unknown 1100 && dirent_fields & SVN_DIRENT_HAS_PROPS) 1101 { 1102 /* We have to requery as the server didn't give us the right 1103 information */ 1104 session->supports_deadprop_count = svn_tristate_false; 1105 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, 1106 session->conns[0], 1107 path, SVN_INVALID_REVNUM, "1", 1108 get_dirent_props(dirent_fields, 1109 session, pool), 1110 pool, pool)); 1111 1112 apr_hash_clear(dirent_walk.full_paths); 1113 apr_hash_clear(dirent_walk.base_paths); 1114 1115 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1116 path_dirent_walker, 1117 &dirent_walk, pool)); 1118 } 1119 1120 *dirents = dirent_walk.base_paths; 1121 1122 if (dirent_walk.supports_deadprop_count != svn_tristate_unknown) 1123 session->supports_deadprop_count = dirent_walk.supports_deadprop_count; 1124 } 1125 1126 /* If we're asked for the directory properties, fetch them too. */ 1127 if (ret_props) 1128 { 1129 apr_hash_t *props; 1130 1131 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0], 1132 path, SVN_INVALID_REVNUM, 1133 all_props, 1134 pool, pool)); 1135 1136 /* Check if the path is really a directory. */ 1137 SVN_ERR(resource_is_directory(props)); 1138 1139 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() 1140 ### put them into POOL, so we're okay. */ 1141 SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool)); 1142 } 1143 1144 return SVN_NO_ERROR; 1145} 1146 1147svn_error_t * 1148svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session, 1149 const char **url, 1150 apr_pool_t *pool) 1151{ 1152 svn_ra_serf__session_t *session = ra_session->priv; 1153 1154 if (!session->repos_root_str) 1155 { 1156 const char *vcc_url; 1157 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1158 } 1159 1160 *url = session->repos_root_str; 1161 return SVN_NO_ERROR; 1162} 1163 1164/* TODO: to fetch the uuid from the repository, we need: 1165 1. a path that exists in HEAD 1166 2. a path that's readable 1167 1168 get_uuid handles the case where a path doesn't exist in HEAD and also the 1169 case where the root of the repository is not readable. 1170 However, it does not handle the case where we're fetching path not existing 1171 in HEAD of a repository with unreadable root directory. 1172 1173 Implements svn_ra__vtable_t.get_uuid(). 1174 */ 1175static svn_error_t * 1176svn_ra_serf__get_uuid(svn_ra_session_t *ra_session, 1177 const char **uuid, 1178 apr_pool_t *pool) 1179{ 1180 svn_ra_serf__session_t *session = ra_session->priv; 1181 1182 if (!session->uuid) 1183 { 1184 const char *vcc_url; 1185 1186 /* We should never get here if we have HTTP v2 support, because 1187 any server with that support should be transmitting the 1188 UUID in the initial OPTIONS response. */ 1189 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 1190 1191 /* We're not interested in vcc_url and relative_url, but this call also 1192 stores the repository's uuid in the session. */ 1193 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1194 if (!session->uuid) 1195 { 1196 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 1197 _("The UUID property was not found on the " 1198 "resource or any of its parents")); 1199 } 1200 } 1201 1202 *uuid = session->uuid; 1203 1204 return SVN_NO_ERROR; 1205} 1206 1207 1208static const svn_ra__vtable_t serf_vtable = { 1209 ra_serf_version, 1210 ra_serf_get_description, 1211 ra_serf_get_schemes, 1212 svn_ra_serf__open, 1213 svn_ra_serf__reparent, 1214 svn_ra_serf__get_session_url, 1215 svn_ra_serf__get_latest_revnum, 1216 svn_ra_serf__get_dated_revision, 1217 svn_ra_serf__change_rev_prop, 1218 svn_ra_serf__rev_proplist, 1219 svn_ra_serf__rev_prop, 1220 svn_ra_serf__get_commit_editor, 1221 svn_ra_serf__get_file, 1222 svn_ra_serf__get_dir, 1223 svn_ra_serf__get_mergeinfo, 1224 svn_ra_serf__do_update, 1225 svn_ra_serf__do_switch, 1226 svn_ra_serf__do_status, 1227 svn_ra_serf__do_diff, 1228 svn_ra_serf__get_log, 1229 svn_ra_serf__check_path, 1230 svn_ra_serf__stat, 1231 svn_ra_serf__get_uuid, 1232 svn_ra_serf__get_repos_root, 1233 svn_ra_serf__get_locations, 1234 svn_ra_serf__get_location_segments, 1235 svn_ra_serf__get_file_revs, 1236 svn_ra_serf__lock, 1237 svn_ra_serf__unlock, 1238 svn_ra_serf__get_lock, 1239 svn_ra_serf__get_locks, 1240 svn_ra_serf__replay, 1241 svn_ra_serf__has_capability, 1242 svn_ra_serf__replay_range, 1243 svn_ra_serf__get_deleted_rev, 1244 svn_ra_serf__register_editor_shim_callbacks, 1245 svn_ra_serf__get_inherited_props 1246}; 1247 1248svn_error_t * 1249svn_ra_serf__init(const svn_version_t *loader_version, 1250 const svn_ra__vtable_t **vtable, 1251 apr_pool_t *pool) 1252{ 1253 static const svn_version_checklist_t checklist[] = 1254 { 1255 { "svn_subr", svn_subr_version }, 1256 { "svn_delta", svn_delta_version }, 1257 { NULL, NULL } 1258 }; 1259 int serf_major; 1260 int serf_minor; 1261 int serf_patch; 1262 1263 SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist)); 1264 1265 /* Simplified version check to make sure we can safely use the 1266 VTABLE parameter. The RA loader does a more exhaustive check. */ 1267 if (loader_version->major != SVN_VER_MAJOR) 1268 { 1269 return svn_error_createf( 1270 SVN_ERR_VERSION_MISMATCH, NULL, 1271 _("Unsupported RA loader version (%d) for ra_serf"), 1272 loader_version->major); 1273 } 1274 1275 /* Make sure that we have loaded a compatible library: the MAJOR must 1276 match, and the minor must be at *least* what we compiled against. 1277 The patch level is simply ignored. */ 1278 serf_lib_version(&serf_major, &serf_minor, &serf_patch); 1279 if (serf_major != SERF_MAJOR_VERSION 1280 || serf_minor < SERF_MINOR_VERSION) 1281 { 1282 return svn_error_createf( 1283 /* ### should return a unique error */ 1284 SVN_ERR_VERSION_MISMATCH, NULL, 1285 _("ra_serf was compiled for serf %d.%d.%d but loaded " 1286 "an incompatible %d.%d.%d library"), 1287 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION, 1288 serf_major, serf_minor, serf_patch); 1289 } 1290 1291 *vtable = &serf_vtable; 1292 1293 return SVN_NO_ERROR; 1294} 1295 1296/* Compatibility wrapper for pre-1.2 subversions. Needed? */ 1297#define NAME "ra_serf" 1298#define DESCRIPTION RA_SERF_DESCRIPTION 1299#define VTBL serf_vtable 1300#define INITFUNC svn_ra_serf__init 1301#define COMPAT_INITFUNC svn_ra_serf_init 1302#include "../libsvn_ra/wrapper_template.h" 1303