1 /*
2  * get-location-segments.c: mod_dav_svn REPORT handler for mapping
3  *                          revision ranges to path locations along
4  *                          the history of an object
5  *
6  * ====================================================================
7  *    Licensed to the Apache Software Foundation (ASF) under one
8  *    or more contributor license agreements.  See the NOTICE file
9  *    distributed with this work for additional information
10  *    regarding copyright ownership.  The ASF licenses this file
11  *    to you under the Apache License, Version 2.0 (the
12  *    "License"); you may not use this file except in compliance
13  *    with the License.  You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  *    Unless required by applicable law or agreed to in writing,
18  *    software distributed under the License is distributed on an
19  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20  *    KIND, either express or implied.  See the License for the
21  *    specific language governing permissions and limitations
22  *    under the License.
23  * ====================================================================
24  */
25 
26 #include <apr_tables.h>
27 #include <apr_uuid.h>
28 
29 #include <httpd.h>
30 #include <http_log.h>
31 #include <mod_dav.h>
32 
33 #include "svn_fs.h"
34 #include "svn_xml.h"
35 #include "svn_repos.h"
36 #include "svn_dav.h"
37 #include "svn_time.h"
38 #include "svn_pools.h"
39 #include "svn_props.h"
40 #include "svn_dav.h"
41 #include "svn_base64.h"
42 
43 #include "private/svn_fspath.h"
44 
45 #include "../dav_svn.h"
46 
47 
48 struct location_segment_baton
49 {
50   svn_boolean_t sent_opener;
51   dav_svn__output *output;
52   apr_bucket_brigade *bb;
53   dav_svn__authz_read_baton arb;
54 };
55 
56 
57 /* Send the get-location-segments-report XML open tag if it hasn't
58    been sent already.  */
59 static svn_error_t *
maybe_send_opener(struct location_segment_baton * b)60 maybe_send_opener(struct location_segment_baton *b)
61 {
62   if (! b->sent_opener)
63     {
64       SVN_ERR(dav_svn__brigade_puts(b->bb, b->output, DAV_XML_HEADER DEBUG_CR
65                                     "<S:get-location-segments-report "
66                                     "xmlns:S=\"" SVN_XML_NAMESPACE
67                                     "\" xmlns:D=\"DAV:\">" DEBUG_CR));
68       b->sent_opener = TRUE;
69     }
70   return SVN_NO_ERROR;
71 }
72 
73 
74 /* Implements `svn_location_segment_receiver_t'; helper for
75    dav_svn__get_location_segments_report(). */
76 static svn_error_t *
location_segment_receiver(svn_location_segment_t * segment,void * baton,apr_pool_t * pool)77 location_segment_receiver(svn_location_segment_t *segment,
78                           void *baton,
79                           apr_pool_t *pool)
80 {
81   struct location_segment_baton *b = baton;
82 
83   SVN_ERR(maybe_send_opener(b));
84 
85   if (segment->path)
86     {
87       const char *path_quoted = apr_xml_quote_string(pool, segment->path, 1);
88 
89       SVN_ERR(dav_svn__brigade_printf(b->bb, b->output,
90                            "<S:location-segment path=\"%s\" "
91                            "range-start=\"%ld\" range-end=\"%ld\"/>" DEBUG_CR,
92                            path_quoted,
93                            segment->range_start, segment->range_end));
94     }
95   else
96     {
97       SVN_ERR(dav_svn__brigade_printf(b->bb, b->output,
98                            "<S:location-segment "
99                            "range-start=\"%ld\" range-end=\"%ld\"/>" DEBUG_CR,
100                            segment->range_start, segment->range_end));
101     }
102   return SVN_NO_ERROR;
103 }
104 
105 
106 dav_error *
dav_svn__get_location_segments_report(const dav_resource * resource,const apr_xml_doc * doc,dav_svn__output * output)107 dav_svn__get_location_segments_report(const dav_resource *resource,
108                                       const apr_xml_doc *doc,
109                                       dav_svn__output *output)
110 {
111   svn_error_t *serr;
112   dav_error *derr = NULL;
113   apr_bucket_brigade *bb;
114   int ns;
115   apr_xml_elem *child;
116   const char *abs_path = NULL;
117   svn_revnum_t peg_revision = SVN_INVALID_REVNUM;
118   svn_revnum_t start_rev = SVN_INVALID_REVNUM;
119   svn_revnum_t end_rev = SVN_INVALID_REVNUM;
120   dav_svn__authz_read_baton arb;
121   struct location_segment_baton location_segment_baton;
122 
123   /* Sanity check. */
124   if (!resource->info->repos_path)
125     return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
126                               "The request does not specify a repository path");
127   ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
128   if (ns == -1)
129     {
130       return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST, 0, 0,
131                                     "The request does not contain the 'svn:' "
132                                     "namespace, so it is not going to have "
133                                     "certain required elements");
134     }
135 
136   /* Gather the parameters. */
137   for (child = doc->root->first_child; child != NULL; child = child->next)
138     {
139       /* If this element isn't one of ours, then skip it. */
140       if (child->ns != ns)
141         continue;
142 
143       if (strcmp(child->name, "peg-revision") == 0)
144         {
145           peg_revision = SVN_STR_TO_REV(dav_xml_get_cdata(child,
146                                                           resource->pool, 1));
147         }
148       else if (strcmp(child->name, "start-revision") == 0)
149         {
150           start_rev = SVN_STR_TO_REV(dav_xml_get_cdata(child,
151                                                        resource->pool, 1));
152         }
153       else if (strcmp(child->name, "end-revision") == 0)
154         {
155           end_rev = SVN_STR_TO_REV(dav_xml_get_cdata(child,
156                                                      resource->pool, 1));
157         }
158       else if (strcmp(child->name, "path") == 0)
159         {
160           const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
161           if ((derr = dav_svn__test_canonical(rel_path, resource->pool)))
162             return derr;
163 
164           /* Force REL_PATH to be a relative path, not an fspath. */
165           rel_path = svn_relpath_canonicalize(rel_path, resource->pool);
166 
167           /* Append the REL_PATH to the base FS path to get an
168              absolute repository path. */
169           abs_path = svn_fspath__join(resource->info->repos_path, rel_path,
170                                       resource->pool);
171         }
172     }
173 
174   /* Check that all parameters are present and valid. */
175   if (! abs_path)
176     return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST, 0, 0,
177                                   "Not all parameters passed");
178 
179   /* No START_REV or PEG_REVISION?  We'll use HEAD. */
180   if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
181     {
182       svn_revnum_t youngest;
183 
184       serr = dav_svn__get_youngest_rev(&youngest, resource->info->repos,
185                                        resource->pool);
186       if (serr != NULL)
187         return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
188                                     "Could not determine youngest revision",
189                                     resource->pool);
190 
191       if (!SVN_IS_VALID_REVNUM(start_rev))
192         start_rev = youngest;
193       if (!SVN_IS_VALID_REVNUM(peg_revision))
194         peg_revision = youngest;
195     }
196 
197   /* No END_REV?  We'll use 0. */
198   if (!SVN_IS_VALID_REVNUM(end_rev))
199     end_rev = 0;
200 
201   if (end_rev > start_rev)
202     return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST,
203                                   SVN_ERR_FS_NO_SUCH_REVISION, 0,
204                                   "End revision must not be younger than "
205                                   "start revision");
206   if (start_rev > peg_revision)
207     return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST,
208                                   SVN_ERR_FS_NO_SUCH_REVISION, 0,
209                                   "Start revision must not be younger than "
210                                   "peg revision");
211 
212   /* Build an authz read baton. */
213   arb.r = resource->info->r;
214   arb.repos = resource->info->repos;
215 
216   /* Build the bucket brigade we'll use for output. */
217   bb = apr_brigade_create(resource->pool,
218                           dav_svn__output_get_bucket_alloc(output));
219 
220   /* Do what we came here for. */
221   location_segment_baton.sent_opener = FALSE;
222   location_segment_baton.output = output;
223   location_segment_baton.bb = bb;
224   if ((serr = svn_repos_node_location_segments(resource->info->repos->repos,
225                                                abs_path, peg_revision,
226                                                start_rev, end_rev,
227                                                location_segment_receiver,
228                                                &location_segment_baton,
229                                                dav_svn__authz_read_func(&arb),
230                                                &arb, resource->pool)))
231     {
232       derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, NULL,
233                                   resource->pool);
234       goto cleanup;
235     }
236 
237   if ((serr = maybe_send_opener(&location_segment_baton)))
238     {
239       derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
240                                   "Error beginning REPORT response.",
241                                   resource->pool);
242       goto cleanup;
243     }
244 
245   if ((serr = dav_svn__brigade_puts(bb, output,
246                                     "</S:get-location-segments-report>"
247                                     DEBUG_CR)))
248     {
249       derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
250                                   "Error ending REPORT response.",
251                                   resource->pool);
252       goto cleanup;
253     }
254 
255  cleanup:
256   return dav_svn__final_flush_or_error(resource->info->r, bb, output,
257                                        derr, resource->pool);
258 }
259