1 /*
2 * util.c:
3 * # ****************************************************************************
4 * # TRASHY LITTLE SUBROUTINES
5 * # ****************************************************************************
6 *
7 * ====================================================================
8 * Licensed to the Apache Software Foundation (ASF) under one
9 * or more contributor license agreements. See the NOTICE file
10 * distributed with this work for additional information
11 * regarding copyright ownership. The ASF licenses this file
12 * to you under the Apache License, Version 2.0 (the
13 * "License"); you may not use this file except in compliance
14 * with the License. You may obtain a copy of the License at
15 *
16 * http://www.apache.org/licenses/LICENSE-2.0
17 *
18 * Unless required by applicable law or agreed to in writing,
19 * software distributed under the License is distributed on an
20 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 * KIND, either express or implied. See the License for the
22 * specific language governing permissions and limitations
23 * under the License.
24 * ====================================================================
25 */
26
27 #include <apr_xml.h>
28 #include <apr_errno.h>
29 #include <apr_uri.h>
30 #include <apr_buckets.h>
31
32 #include <mod_dav.h>
33 #include <http_protocol.h>
34 #include <http_core.h>
35
36 #include "svn_error.h"
37 #include "svn_fs.h"
38 #include "svn_dav.h"
39 #include "svn_base64.h"
40 #include "svn_ctype.h"
41
42 #include "dav_svn.h"
43 #include "private/svn_fspath.h"
44 #include "private/svn_string_private.h"
45
46 dav_error *
dav_svn__new_error(apr_pool_t * pool,int status,int error_id,apr_status_t aprerr,const char * desc)47 dav_svn__new_error(apr_pool_t *pool,
48 int status,
49 int error_id,
50 apr_status_t aprerr,
51 const char *desc)
52 {
53 if (error_id == 0)
54 error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
55
56 /*
57 * Note: dav_new_error() in httpd 2.0/2.2 always treated
58 * the errno field in dav_error as an apr_status_t when
59 * logging; on some platforms errno and apr_status_t
60 * aren't directly interchangeable. The code for httpd
61 * > 2.2 below perpetuates this.
62 */
63 #if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
64 return dav_new_error(pool, status, error_id, aprerr, desc);
65 #else
66
67 errno = aprerr; /* For the same reason as in dav_svn__new_error_svn */
68
69 return dav_new_error(pool, status, error_id, desc);
70 #endif
71 }
72
73 dav_error *
dav_svn__new_error_svn(apr_pool_t * pool,int status,int error_id,apr_status_t aprerr,const char * desc)74 dav_svn__new_error_svn(apr_pool_t *pool,
75 int status,
76 int error_id,
77 apr_status_t aprerr,
78 const char *desc)
79 {
80 if (error_id == 0)
81 error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
82
83 #if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
84 return dav_new_error_tag(pool, status, error_id, aprerr,
85 desc, SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
86 #else
87 /* dav_new_error_tag will record errno so we use it to pass aprerr.
88 This overrwites any existing errno value but since Subversion
89 makes no attempt to avoid system calls after a failed system call
90 there is no guarantee that any existing errno represents a
91 relevant error. */
92 errno = aprerr;
93
94 return dav_new_error_tag(pool, status, error_id, desc,
95 SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
96 #endif
97 }
98
99
100 /* Build up a chain of DAV errors that correspond to the underlying SVN
101 errors that caused this problem. */
102 static dav_error *
build_error_chain(apr_pool_t * pool,svn_error_t * err,int status)103 build_error_chain(apr_pool_t *pool, svn_error_t *err, int status)
104 {
105 char buffer[128];
106 const char *msg = svn_err_best_message(err, buffer, sizeof(buffer));
107
108 dav_error *derr = dav_svn__new_error_svn(pool, status, err->apr_err, 0,
109 apr_pstrdup(pool, msg));
110
111 if (err->child)
112 derr->prev = build_error_chain(pool, err->child, status);
113
114 return derr;
115 }
116
117
118 dav_error *
dav_svn__convert_err(svn_error_t * serr,int status,const char * message,apr_pool_t * pool)119 dav_svn__convert_err(svn_error_t *serr,
120 int status,
121 const char *message,
122 apr_pool_t *pool)
123 {
124 dav_error *derr;
125
126 /* Remove the trace-only error chain links. We need predictable
127 protocol behavior regardless of whether or not we're in a
128 debugging build. */
129 svn_error_t *purged_serr = svn_error_purge_tracing(serr);
130
131 /* ### someday mod_dav_svn will send back 'rich' error tags, much
132 finer grained than plain old svn_error_t's. But for now, all
133 svn_error_t's are marshalled to the client via the single
134 generic <svn:error/> tag nestled within a <D:error> block. */
135
136 /* Examine the Subverion error code, and select the most
137 appropriate HTTP status code. If no more appropriate HTTP
138 status code maps to the Subversion error code, use the one
139 suggested status provided by the caller. */
140 switch (purged_serr->apr_err)
141 {
142 case SVN_ERR_FS_NOT_FOUND:
143 case SVN_ERR_FS_NO_SUCH_REVISION:
144 status = HTTP_NOT_FOUND;
145 break;
146 case SVN_ERR_UNSUPPORTED_FEATURE:
147 status = HTTP_NOT_IMPLEMENTED;
148 break;
149 case SVN_ERR_FS_LOCK_OWNER_MISMATCH:
150 case SVN_ERR_FS_PATH_ALREADY_LOCKED:
151 status = HTTP_LOCKED;
152 break;
153 case SVN_ERR_FS_PROP_BASEVALUE_MISMATCH:
154 status = HTTP_PRECONDITION_FAILED;
155 break;
156 /* add other mappings here */
157 }
158
159 derr = build_error_chain(pool, purged_serr, status);
160 if (message != NULL
161 && !svn_error_find_cause(purged_serr, SVN_ERR_REPOS_HOOK_FAILURE))
162 /* Don't hide hook failures; we might hide the error text */
163 derr = dav_push_error(pool, status, purged_serr->apr_err,
164 message, derr);
165
166 /* Now, destroy the Subversion error. */
167 svn_error_clear(serr);
168
169 return derr;
170 }
171
172
173 /* Set *REVISION to the youngest revision in which an interesting
174 history item (a modification, or a copy) occurred for PATH under
175 ROOT. Use POOL for scratchwork. */
176 static svn_error_t *
get_last_history_rev(svn_revnum_t * revision,svn_fs_root_t * root,const char * path,apr_pool_t * pool)177 get_last_history_rev(svn_revnum_t *revision,
178 svn_fs_root_t *root,
179 const char *path,
180 apr_pool_t *pool)
181 {
182 svn_fs_history_t *history;
183 const char *ignored;
184
185 /* Get an initial HISTORY baton. */
186 SVN_ERR(svn_fs_node_history2(&history, root, path, pool, pool));
187
188 /* Now get the first *real* point of interesting history. */
189 SVN_ERR(svn_fs_history_prev2(&history, history, FALSE, pool, pool));
190
191 /* Fetch the location information for this history step. */
192 return svn_fs_history_location(&ignored, revision, history, pool);
193 }
194
195
196 svn_revnum_t
dav_svn__get_safe_cr(svn_fs_root_t * root,const char * path,apr_pool_t * pool)197 dav_svn__get_safe_cr(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
198 {
199 svn_revnum_t revision = svn_fs_revision_root_revision(root);
200 svn_revnum_t history_rev;
201 svn_fs_root_t *other_root;
202 svn_fs_t *fs = svn_fs_root_fs(root);
203 svn_fs_node_relation_t node_relation;
204 svn_error_t *err;
205
206 if ((err = get_last_history_rev(&history_rev, root, path, pool)))
207 {
208 svn_error_clear(err);
209 return revision; /* couldn't find last history rev */
210 }
211
212 if ((err = svn_fs_revision_root(&other_root, fs, history_rev, pool)))
213 {
214 svn_error_clear(err);
215 return revision; /* couldn't open the history rev */
216 }
217
218 if ((err = svn_fs_node_relation(&node_relation, root, path,
219 other_root, path, pool)))
220 {
221 svn_error_clear(err);
222 return revision;
223 }
224
225 if (node_relation == svn_fs_node_unchanged)
226 return history_rev; /* the history rev is safe! the same node
227 exists at the same path in both revisions. */
228
229 /* default */
230 return revision;
231 }
232
233
234 const char *
dav_svn__build_uri(const dav_svn_repos * repos,enum dav_svn__build_what what,svn_revnum_t revision,const char * path,svn_boolean_t add_href,apr_pool_t * pool)235 dav_svn__build_uri(const dav_svn_repos *repos,
236 enum dav_svn__build_what what,
237 svn_revnum_t revision,
238 const char *path,
239 svn_boolean_t add_href,
240 apr_pool_t *pool)
241 {
242 const char *root_path = repos->root_path;
243 const char *special_uri = repos->special_uri;
244 const char *path_uri = path ? svn_path_uri_encode(path, pool) : NULL;
245 const char *href1 = add_href ? "<D:href>" : "";
246 const char *href2 = add_href ? "</D:href>" : "";
247
248 /* The first character of root_path is guaranteed to be "/". If
249 there's no component beyond that, then just use "", so that
250 appending another "/" later does not result in "//". */
251 if (root_path[1] == '\0')
252 root_path = "";
253
254 switch (what)
255 {
256 case DAV_SVN__BUILD_URI_ACT_COLLECTION:
257 return apr_psprintf(pool, "%s%s/%s/act/%s",
258 href1, root_path, special_uri, href2);
259
260 case DAV_SVN__BUILD_URI_BASELINE:
261 return apr_psprintf(pool, "%s%s/%s/bln/%ld%s",
262 href1, root_path, special_uri, revision, href2);
263
264 case DAV_SVN__BUILD_URI_BC:
265 return apr_psprintf(pool, "%s%s/%s/bc/%ld/%s",
266 href1, root_path, special_uri, revision, href2);
267
268 case DAV_SVN__BUILD_URI_PUBLIC:
269 return apr_psprintf(pool, "%s%s%s%s",
270 href1, root_path, path_uri, href2);
271
272 case DAV_SVN__BUILD_URI_VERSION:
273 return apr_psprintf(pool, "%s%s/%s/ver/%ld%s%s",
274 href1, root_path, special_uri,
275 revision, path_uri, href2);
276
277 case DAV_SVN__BUILD_URI_REVROOT:
278 return apr_psprintf(pool, "%s%s/%s/rvr/%ld%s%s",
279 href1, root_path, special_uri,
280 revision, path_uri, href2);
281
282 case DAV_SVN__BUILD_URI_VCC:
283 return apr_psprintf(pool, "%s%s/%s/vcc/" DAV_SVN__DEFAULT_VCC_NAME "%s",
284 href1, root_path, special_uri, href2);
285
286 default:
287 /* programmer error somewhere */
288 SVN_ERR_MALFUNCTION_NO_RETURN();
289 }
290
291 /* NOTREACHED */
292 }
293
294
295 svn_error_t *
dav_svn__simple_parse_uri(dav_svn__uri_info * info,const dav_resource * relative,const char * uri,apr_pool_t * pool)296 dav_svn__simple_parse_uri(dav_svn__uri_info *info,
297 const dav_resource *relative,
298 const char *uri,
299 apr_pool_t *pool)
300 {
301 apr_uri_t comp;
302 const char *path;
303 apr_size_t len1;
304 apr_size_t len2;
305 const char *slash;
306 const char *created_rev_str;
307
308 /* parse the input URI, in case it is more than just a path */
309 if (apr_uri_parse(pool, uri, &comp) != APR_SUCCESS)
310 goto malformed_uri;
311
312 /* ### ignore all URI parts but the path (for now) */
313
314 /* clean up the URI */
315 if (comp.path == NULL)
316 path = "/";
317 else
318 {
319 ap_getparents(comp.path);
320 ap_no2slash(comp.path);
321 path = comp.path;
322 }
323
324 /*
325 * Does the URI path specify the same repository? It does not if one of:
326 *
327 * 1) input is shorter than the path to our repository
328 * 2) input is longer, but there is no separator
329 * [ http://host/repos vs http://host/repository ]
330 * 3) the two paths do not match
331 */
332 len1 = strlen(path);
333 len2 = strlen(relative->info->repos->root_path);
334 if (len2 == 1 && relative->info->repos->root_path[0] == '/')
335 len2 = 0;
336
337 if (len1 < len2
338 || (len1 > len2 && path[len2] != '/')
339 || memcmp(path, relative->info->repos->root_path, len2) != 0)
340 {
341 return svn_error_create(SVN_ERR_APMOD_MALFORMED_URI, NULL,
342 "Unusable URI: it does not refer to this "
343 "repository");
344 }
345
346 /* prep the return value */
347 memset(info, 0, sizeof(*info));
348 info->rev = SVN_INVALID_REVNUM;
349
350 path += len2; /* now points to "/" or "\0" */
351 len1 -= len2;
352
353 if (len1 <= 1)
354 {
355 info->repos_path = "/";
356 return NULL;
357 }
358
359 /* skip over the leading "/" */
360 ++path;
361 --len1;
362
363 /* is this a special URI? */
364 len2 = strlen(relative->info->repos->special_uri);
365 if (len1 < len2
366 || (len1 > len2 && path[len2] != '/')
367 || memcmp(path, relative->info->repos->special_uri, len2) != 0)
368 {
369 /* this is an ordinary "public" URI, so back up to include the
370 leading '/' and just return... no need to parse further. */
371 info->repos_path = svn_path_uri_decode(path - 1, pool);
372 return NULL;
373 }
374
375 path += len2; /* now points to "/" or "\0" just past the special URI */
376 len1 -= len2;
377
378 /* ### we don't handle the root of the special area yet */
379 if (len1 <= 1)
380 goto unhandled_form;
381
382 /* Find the next component, and ensure something is there. */
383 slash = ap_strchr_c(path + 1, '/');
384 if (slash == NULL || slash[1] == '\0')
385 goto unhandled_form;
386 len2 = slash - path;
387
388 /* Figure out what we have here */
389 if (len2 == 4 && memcmp(path, "/act/", 5) == 0)
390 {
391 /* an activity */
392 info->activity_id = path + 5;
393 }
394 else if (len2 == 4 &&
395 (memcmp(path, "/ver/", 5) == 0 || memcmp(path, "/rvr/", 5) == 0))
396 {
397 /* a version resource */
398 path += 5;
399 len1 -= 5;
400 slash = ap_strchr_c(path, '/');
401 if (slash == NULL)
402 {
403 created_rev_str = apr_pstrndup(pool, path, len1);
404 info->rev = SVN_STR_TO_REV(created_rev_str);
405 info->repos_path = "/";
406 }
407 else
408 {
409 created_rev_str = apr_pstrndup(pool, path, slash - path);
410 info->rev = SVN_STR_TO_REV(created_rev_str);
411 info->repos_path = svn_path_uri_decode(slash, pool);
412 }
413 if (info->rev == SVN_INVALID_REVNUM)
414 goto malformed_uri;
415 }
416 else
417 goto unhandled_form;
418
419 return NULL;
420
421 malformed_uri:
422 return svn_error_create(SVN_ERR_APMOD_MALFORMED_URI, NULL,
423 "The specified URI could not be parsed");
424
425 unhandled_form:
426 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
427 "Unsupported URI form");
428 }
429
430 svn_boolean_t
dav_svn__is_parentpath_list(request_rec * r)431 dav_svn__is_parentpath_list(request_rec *r)
432 {
433 const char *fs_parent_path = dav_svn__get_fs_parent_path(r);
434
435 if (fs_parent_path && dav_svn__get_list_parentpath_flag(r))
436 {
437 const char *root_path = dav_svn__get_root_dir(r);
438 char *uri = apr_pstrdup(r->pool, r->uri);
439 char *parentpath = apr_pstrdup(r->pool, root_path);
440 apr_size_t uri_len = strlen(uri);
441 apr_size_t parentpath_len = strlen(parentpath);
442
443 if (uri[uri_len-1] == '/')
444 uri[uri_len-1] = '\0';
445
446 if (parentpath[parentpath_len-1] == '/')
447 parentpath[parentpath_len-1] = '\0';
448
449 if (strcmp(parentpath, uri) == 0)
450 {
451 return TRUE;
452 }
453 }
454 return FALSE;
455 }
456
457 /* ### move this into apr_xml */
458 int
dav_svn__find_ns(const apr_array_header_t * namespaces,const char * uri)459 dav_svn__find_ns(const apr_array_header_t *namespaces, const char *uri)
460 {
461 int i;
462
463 for (i = 0; i < namespaces->nelts; ++i)
464 if (strcmp(APR_XML_GET_URI_ITEM(namespaces, i), uri) == 0)
465 return i;
466 return -1;
467 }
468
469
470 /*** Output helpers ***/
471
472
473 struct dav_svn__output
474 {
475 request_rec *r;
476 };
477
478 dav_svn__output *
dav_svn__output_create(request_rec * r,apr_pool_t * pool)479 dav_svn__output_create(request_rec *r,
480 apr_pool_t *pool)
481 {
482 dav_svn__output *output = apr_pcalloc(pool, sizeof(*output));
483 output->r = r;
484 return output;
485 }
486
487 apr_bucket_alloc_t *
dav_svn__output_get_bucket_alloc(dav_svn__output * output)488 dav_svn__output_get_bucket_alloc(dav_svn__output *output)
489 {
490 return output->r->connection->bucket_alloc;
491 }
492
493 svn_error_t *
dav_svn__output_pass_brigade(dav_svn__output * output,apr_bucket_brigade * bb)494 dav_svn__output_pass_brigade(dav_svn__output *output,
495 apr_bucket_brigade *bb)
496 {
497 apr_status_t status;
498
499 status = ap_pass_brigade(output->r->output_filters, bb);
500 /* Empty the brigade here, as required by ap_pass_brigade(). */
501 apr_brigade_cleanup(bb);
502 if (status)
503 return svn_error_create(status, NULL, "Could not write data to filter");
504
505 /* Check for an aborted connection, since the brigade functions don't
506 appear to return useful errors when the connection is dropped. */
507 if (output->r->connection->aborted)
508 return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, NULL, NULL);
509 return SVN_NO_ERROR;
510 }
511
512
513 /*** Brigade I/O wrappers ***/
514
515
516 svn_error_t *
dav_svn__brigade_write(apr_bucket_brigade * bb,dav_svn__output * output,const char * data,apr_size_t len)517 dav_svn__brigade_write(apr_bucket_brigade *bb,
518 dav_svn__output *output,
519 const char *data,
520 apr_size_t len)
521 {
522 apr_status_t apr_err;
523 apr_err = apr_brigade_write(bb, ap_filter_flush,
524 output->r->output_filters, data, len);
525 if (apr_err)
526 return svn_error_create(apr_err, 0, NULL);
527 /* Check for an aborted connection, since the brigade functions don't
528 appear to be return useful errors when the connection is dropped. */
529 if (output->r->connection->aborted)
530 return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, 0, NULL);
531 return SVN_NO_ERROR;
532 }
533
534
535 svn_error_t *
dav_svn__brigade_puts(apr_bucket_brigade * bb,dav_svn__output * output,const char * str)536 dav_svn__brigade_puts(apr_bucket_brigade *bb,
537 dav_svn__output *output,
538 const char *str)
539 {
540 apr_status_t apr_err;
541 apr_err = apr_brigade_puts(bb, ap_filter_flush,
542 output->r->output_filters, str);
543 if (apr_err)
544 return svn_error_create(apr_err, 0, NULL);
545 /* Check for an aborted connection, since the brigade functions don't
546 appear to be return useful errors when the connection is dropped. */
547 if (output->r->connection->aborted)
548 return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, 0, NULL);
549 return SVN_NO_ERROR;
550 }
551
552
553 svn_error_t *
dav_svn__brigade_printf(apr_bucket_brigade * bb,dav_svn__output * output,const char * fmt,...)554 dav_svn__brigade_printf(apr_bucket_brigade *bb,
555 dav_svn__output *output,
556 const char *fmt,
557 ...)
558 {
559 apr_status_t apr_err;
560 va_list ap;
561
562 va_start(ap, fmt);
563 apr_err = apr_brigade_vprintf(bb, ap_filter_flush,
564 output->r->output_filters, fmt, ap);
565 va_end(ap);
566 if (apr_err)
567 return svn_error_create(apr_err, 0, NULL);
568 /* Check for an aborted connection, since the brigade functions don't
569 appear to be return useful errors when the connection is dropped. */
570 if (output->r->connection->aborted)
571 return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, 0, NULL);
572 return SVN_NO_ERROR;
573 }
574
575
576 svn_error_t *
dav_svn__brigade_putstrs(apr_bucket_brigade * bb,dav_svn__output * output,...)577 dav_svn__brigade_putstrs(apr_bucket_brigade *bb,
578 dav_svn__output *output,
579 ...)
580 {
581 apr_status_t apr_err;
582 va_list ap;
583
584 va_start(ap, output);
585 apr_err = apr_brigade_vputstrs(bb, ap_filter_flush,
586 output->r->output_filters, ap);
587 va_end(ap);
588 if (apr_err)
589 return svn_error_create(apr_err, NULL, NULL);
590 /* Check for an aborted connection, since the brigade functions don't
591 appear to return useful errors when the connection is dropped. */
592 if (output->r->connection->aborted)
593 return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, NULL, NULL);
594 return SVN_NO_ERROR;
595 }
596
597
598
599
600 dav_error *
dav_svn__test_canonical(const char * path,apr_pool_t * pool)601 dav_svn__test_canonical(const char *path, apr_pool_t *pool)
602 {
603 if (path[0] == '\0')
604 return NULL;
605 if (svn_path_is_url(path) && svn_uri_is_canonical(path, pool))
606 return NULL;
607 if ((path[0] == '/') && svn_fspath__is_canonical(path))
608 return NULL;
609 if (svn_relpath_is_canonical(path))
610 return NULL;
611
612 /* Otherwise, generate a generic HTTP_BAD_REQUEST error. */
613 return dav_svn__new_error_svn(
614 pool, HTTP_BAD_REQUEST, 0, 0,
615 apr_psprintf(pool,
616 "Path '%s' is not canonicalized; "
617 "there is a problem with the client.", path));
618 }
619
620
621 dav_error *
dav_svn__sanitize_error(svn_error_t * serr,const char * new_msg,int http_status,request_rec * r)622 dav_svn__sanitize_error(svn_error_t *serr,
623 const char *new_msg,
624 int http_status,
625 request_rec *r)
626 {
627 svn_error_t *safe_err = serr;
628 if (new_msg != NULL)
629 {
630 /* Purge error tracing from the error chain. */
631 svn_error_t *purged_serr = svn_error_purge_tracing(serr);
632
633 /* Sanitization is necessary. Create a new, safe error and
634 log the original error. */
635 safe_err = svn_error_create(purged_serr->apr_err, NULL, new_msg);
636 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
637 "%s", purged_serr->message);
638
639 /* Log the entire error chain. */
640 while (purged_serr->child)
641 {
642 purged_serr = purged_serr->child;
643 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
644 "%s", purged_serr->message);
645 }
646
647 svn_error_clear(serr);
648 }
649
650 return dav_svn__convert_err(safe_err, http_status,
651 apr_psprintf(r->pool, "%s", safe_err->message),
652 r->pool);
653 }
654
655
656 struct brigade_write_baton
657 {
658 apr_bucket_brigade *bb;
659 dav_svn__output *output;
660 };
661
662
663 /* This implements 'svn_write_fn_t'. */
664 static svn_error_t *
brigade_write_fn(void * baton,const char * data,apr_size_t * len)665 brigade_write_fn(void *baton, const char *data, apr_size_t *len)
666 {
667 struct brigade_write_baton *wb = baton;
668 apr_status_t apr_err;
669
670 apr_err = apr_brigade_write(wb->bb, ap_filter_flush,
671 wb->output->r->output_filters, data, *len);
672
673 if (apr_err != APR_SUCCESS)
674 return svn_error_wrap_apr(apr_err, "Error writing base64 data");
675
676 return SVN_NO_ERROR;
677 }
678
679
680 svn_stream_t *
dav_svn__make_base64_output_stream(apr_bucket_brigade * bb,dav_svn__output * output,apr_pool_t * pool)681 dav_svn__make_base64_output_stream(apr_bucket_brigade *bb,
682 dav_svn__output *output,
683 apr_pool_t *pool)
684 {
685 struct brigade_write_baton *wb = apr_palloc(pool, sizeof(*wb));
686 svn_stream_t *stream = svn_stream_create(wb, pool);
687
688 wb->bb = bb;
689 wb->output = output;
690 svn_stream_set_write(stream, brigade_write_fn);
691
692 return svn_base64_encode2(stream, FALSE, pool);
693 }
694
695 void
dav_svn__operational_log(struct dav_resource_private * info,const char * line)696 dav_svn__operational_log(struct dav_resource_private *info, const char *line)
697 {
698 apr_table_set(info->r->subprocess_env, "SVN-ACTION", line);
699 apr_table_set(info->r->subprocess_env, "SVN-REPOS",
700 svn_path_uri_encode(info->repos->fs_path, info->r->pool));
701 apr_table_set(info->r->subprocess_env, "SVN-REPOS-NAME",
702 svn_path_uri_encode(info->repos->repo_basename, info->r->pool));
703 }
704
705
706 dav_error *
dav_svn__final_flush_or_error(request_rec * r,apr_bucket_brigade * bb,dav_svn__output * output,dav_error * preferred_err,apr_pool_t * pool)707 dav_svn__final_flush_or_error(request_rec *r,
708 apr_bucket_brigade *bb,
709 dav_svn__output *output,
710 dav_error *preferred_err,
711 apr_pool_t *pool)
712 {
713 dav_error *derr = preferred_err;
714 svn_boolean_t do_flush;
715
716 do_flush = r->sent_bodyct > 0;
717 if (! do_flush)
718 {
719 /* Ask about the length of the bucket brigade, ignoring errors. */
720 apr_off_t len = 0;
721 (void)apr_brigade_length(bb, FALSE, &len);
722 do_flush = (len != 0);
723 }
724
725 /* If there's something in the bucket brigade to flush, or we've
726 already started sending data down the wire, flush what we've
727 got. We only keep any error retrieved from the flush if weren't
728 provided a more-important DERR, though. */
729 if (do_flush)
730 {
731 apr_status_t apr_err = ap_fflush(output->r->output_filters, bb);
732 if (apr_err && (! derr))
733 derr = dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, apr_err,
734 "Error flushing brigade.");
735 }
736 return derr;
737 }
738
dav_svn__log_err(request_rec * r,dav_error * err,int level)739 void dav_svn__log_err(request_rec *r,
740 dav_error *err,
741 int level)
742 {
743 dav_error *errscan;
744
745 /* Log the errors */
746 /* ### should have a directive to log the first or all */
747 for (errscan = err; errscan != NULL; errscan = errscan->prev) {
748 apr_status_t status;
749
750 if (errscan->desc == NULL)
751 continue;
752
753 #if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
754 status = errscan->aprerr;
755 #else
756 status = errscan->save_errno;
757 #endif
758
759 ap_log_rerror(APLOG_MARK, level, status, r,
760 "%s [%d, #%d]",
761 errscan->desc, errscan->status, errscan->error_id);
762 }
763 }
764
765 int
dav_svn__error_response_tag(request_rec * r,dav_error * err)766 dav_svn__error_response_tag(request_rec *r,
767 dav_error *err)
768 {
769 r->status = err->status;
770
771 /* ### I really don't think this is needed; gotta test */
772 r->status_line = ap_get_status_line(err->status);
773
774 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
775 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:error xmlns:D=\"DAV:\"", r);
776
777 if (err->desc != NULL)
778 ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
779
780 if (err->namespace != NULL)
781 ap_rprintf(r, " xmlns:C=\"%s\">" DEBUG_CR "<C:%s/>" DEBUG_CR,
782 err->namespace, err->tagname);
783 else if (err->tagname != NULL)
784 ap_rprintf(r, ">" DEBUG_CR "<D:%s/>" DEBUG_CR, err->tagname);
785 else
786 ap_rputs(">" DEBUG_CR, r);
787
788 /* here's our mod_dav specific tag: */
789 if (err->desc != NULL)
790 ap_rprintf(r, "<m:human-readable errcode=\"%d\">" DEBUG_CR "%s" DEBUG_CR
791 "</m:human-readable>" DEBUG_CR, err->error_id,
792 apr_xml_quote_string(r->pool, err->desc, 0));
793
794 ap_rputs("</D:error>" DEBUG_CR, r);
795
796 /* the response has been sent. */
797 /*
798 * ### Use of DONE obviates logging..!
799 */
800 return DONE;
801 }
802
803
804 /* Set *REQUEST_STR to a string containing the contents of the body of
805 request R, allocated from POOL.
806
807 NOTE: This was shamelessly stolen and modified from Apache's
808 ap_xml_parse_input(). */
809 static int
request_body_to_string(svn_string_t ** request_str,request_rec * r,apr_pool_t * pool)810 request_body_to_string(svn_string_t **request_str,
811 request_rec *r,
812 apr_pool_t *pool)
813 {
814 apr_bucket_brigade *brigade;
815 int seen_eos;
816 apr_status_t status;
817 apr_off_t total_read = 0;
818 apr_off_t limit_req_body = ap_get_limit_xml_body(r);
819 int result = HTTP_BAD_REQUEST;
820 const char *content_length_str;
821 char *endp;
822 apr_off_t content_length;
823 svn_stringbuf_t *buf;
824
825 *request_str = NULL;
826
827 content_length_str = apr_table_get(r->headers_in, "Content-Length");
828 if (content_length_str)
829 {
830 if (apr_strtoff(&content_length, content_length_str, &endp, 10)
831 || endp == content_length_str || *endp || content_length < 0)
832 {
833 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid Content-Length");
834 return HTTP_REQUEST_ENTITY_TOO_LARGE;
835 }
836 }
837 else
838 content_length = 0;
839
840 if (limit_req_body && (limit_req_body < content_length))
841 {
842 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
843 "Requested content-length of %" APR_OFF_T_FMT " is larger "
844 "than the configured limit of %" APR_OFF_T_FMT,
845 content_length, limit_req_body);
846 return HTTP_REQUEST_ENTITY_TOO_LARGE;
847 }
848
849 if (content_length)
850 {
851 /* Do not allocate more than 1 MB until we receive request body. */
852 apr_size_t alloc_len = 1 * 1024 *1024;
853 if (content_length < alloc_len)
854 alloc_len = (apr_size_t) content_length;
855
856 buf = svn_stringbuf_create_ensure(alloc_len, pool);
857 }
858 else
859 {
860 buf = svn_stringbuf_create_empty(pool);
861 }
862
863 brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
864 seen_eos = 0;
865 total_read = 0;
866
867 do
868 {
869 apr_bucket *bucket;
870
871 status = ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES,
872 APR_BLOCK_READ, 2048);
873 if (status != APR_SUCCESS)
874 goto cleanup;
875
876 for (bucket = APR_BRIGADE_FIRST(brigade);
877 bucket != APR_BRIGADE_SENTINEL(brigade);
878 bucket = APR_BUCKET_NEXT(bucket))
879 {
880 const char *data;
881 apr_size_t len;
882
883 if (APR_BUCKET_IS_EOS(bucket))
884 {
885 seen_eos = 1;
886 break;
887 }
888
889 if (APR_BUCKET_IS_METADATA(bucket))
890 continue;
891
892 status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
893 if (status != APR_SUCCESS)
894 goto cleanup;
895
896 total_read += len;
897 if (limit_req_body && total_read > limit_req_body)
898 {
899 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
900 "Request body is larger than the configured "
901 "limit of %" APR_OFF_T_FMT, limit_req_body);
902 result = HTTP_REQUEST_ENTITY_TOO_LARGE;
903 goto cleanup;
904 }
905
906 svn_stringbuf_appendbytes(buf, data, len);
907 }
908
909 apr_brigade_cleanup(brigade);
910 }
911 while (!seen_eos);
912
913 apr_brigade_destroy(brigade);
914
915 /* Make an svn_string_t from our svn_stringbuf_t. */
916 *request_str = svn_stringbuf__morph_into_string(buf);
917 return OK;
918
919 cleanup:
920 apr_brigade_destroy(brigade);
921
922 /* Apache will supply a default error, plus the error log above. */
923 return result;
924 }
925
926 int
dav_svn__parse_request_skel(svn_skel_t ** skel,request_rec * r,apr_pool_t * pool)927 dav_svn__parse_request_skel(svn_skel_t **skel,
928 request_rec *r,
929 apr_pool_t *pool)
930 {
931 svn_string_t *skel_str;
932 int status;
933
934 *skel = NULL;
935 status = request_body_to_string(&skel_str, r, pool);
936 if (status != OK)
937 return status;
938
939 *skel = svn_skel__parse(skel_str->data, skel_str->len, pool);
940 return OK;
941 }
942
943 svn_error_t *
dav_svn__get_youngest_rev(svn_revnum_t * youngest_p,dav_svn_repos * repos,apr_pool_t * scratch_pool)944 dav_svn__get_youngest_rev(svn_revnum_t *youngest_p,
945 dav_svn_repos *repos,
946 apr_pool_t *scratch_pool)
947 {
948 if (repos->youngest_rev == SVN_INVALID_REVNUM)
949 {
950 svn_revnum_t revnum;
951 SVN_ERR(svn_fs_youngest_rev(&revnum, repos->fs, scratch_pool));
952 repos->youngest_rev = revnum;
953 }
954
955 *youngest_p = repos->youngest_rev;
956 return SVN_NO_ERROR;
957 }
958
959 const char *
dav_svn__fuzzy_escape_author(const char * author,svn_boolean_t is_svn_client,apr_pool_t * result_pool,apr_pool_t * scratch_pool)960 dav_svn__fuzzy_escape_author(const char *author,
961 svn_boolean_t is_svn_client,
962 apr_pool_t *result_pool,
963 apr_pool_t *scratch_pool)
964 {
965 apr_size_t len = strlen(author);
966 if (is_svn_client && !svn_xml_is_xml_safe(author, len))
967 {
968 /* We are talking to a Subversion client, which will (like any proper
969 xml parser) error out if we produce control characters in XML.
970
971 However Subversion clients process both the generic
972 <creator-displayname /> as the custom element for svn:author.
973
974 Let's skip outputting the invalid characters here to make the XML
975 valid, so clients can see the custom element.
976
977 Subversion Clients will then either use a slightly invalid
978 author (unlikely) or more likely use the second result, which
979 will be transferred with full escaping capabilities.
980
981 We have tests in place to assert proper behavior over the RA layer.
982 */
983 apr_size_t i;
984 svn_stringbuf_t *buf;
985
986 buf = svn_stringbuf_ncreate(author, len, scratch_pool);
987
988 for (i = 0; i < buf->len; i++)
989 {
990 char c = buf->data[i];
991
992 if (svn_ctype_iscntrl(c))
993 {
994 svn_stringbuf_remove(buf, i--, 1);
995 }
996 }
997
998 author = buf->data;
999 }
1000
1001 return apr_xml_quote_string(result_pool, author, 1);
1002 }
1003