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