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