Merge the svnserve-logging branch, in its entirety, to trunk, using the
[svn.git] / subversion / mod_dav_svn / reports / file-revs.c
blobb902eaa5ae6803a92c672a9c17112b6af4376339
1 /*
2 * file_revs.c: handle the file-revs-report request and response
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #define APR_WANT_STRFUNC
20 #include <apr_want.h> /* for strcmp() */
22 #include "svn_types.h"
23 #include "svn_xml.h"
24 #include "svn_pools.h"
25 #include "svn_base64.h"
26 #include "svn_props.h"
27 #include "svn_dav.h"
28 #include "private/svn_log.h"
30 #include "../dav_svn.h"
32 struct file_rev_baton {
33 /* this buffers the output for a bit and is automatically flushed,
34 at appropriate times, by the Apache filter system. */
35 apr_bucket_brigade *bb;
37 /* where to deliver the output */
38 ap_filter_t *output;
40 /* Whether we've written the <S:file-revs-report> header. Allows for lazy
41 writes to support mod_dav-based error handling. */
42 svn_boolean_t needs_header;
44 /* SVNDIFF version to use when sending to client. */
45 int svndiff_version;
47 /* Used by the delta iwndow handler. */
48 svn_txdelta_window_handler_t window_handler;
49 void *window_baton;
53 /* If FRB->needs_header is true, send the "<S:file-revs-report>" start
54 tag and set FRB->needs_header to zero. Else do nothing.
55 This is basically duplicated in log.c. Consider factoring if
56 duplicating again. */
57 static svn_error_t *
58 maybe_send_header(struct file_rev_baton *frb)
60 if (frb->needs_header)
62 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
63 DAV_XML_HEADER DEBUG_CR
64 "<S:file-revs-report xmlns:S=\""
65 SVN_XML_NAMESPACE "\" "
66 "xmlns:D=\"DAV:\">" DEBUG_CR));
67 frb->needs_header = FALSE;
69 return SVN_NO_ERROR;
73 /* Send a property named NAME with value VAL in an element named ELEM_NAME.
74 Quote NAME and base64-encode VAL if necessary. */
75 static svn_error_t *
76 send_prop(struct file_rev_baton *frb,
77 const char *elem_name,
78 const char *name,
79 const svn_string_t *val,
80 apr_pool_t *pool)
82 name = apr_xml_quote_string(pool, name, 1);
84 if (svn_xml_is_xml_safe(val->data, val->len))
86 svn_stringbuf_t *tmp = NULL;
87 svn_xml_escape_cdata_string(&tmp, val, pool);
88 val = svn_string_create(tmp->data, pool);
89 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
90 "<S:%s name=\"%s\">%s</S:%s>" DEBUG_CR,
91 elem_name, name, val->data, elem_name));
93 else
95 val = svn_base64_encode_string(val, pool);
96 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
97 "<S:%s name=\"%s\" encoding=\"base64\">"
98 "%s</S:%s>" DEBUG_CR,
99 elem_name, name, val->data, elem_name));
102 return SVN_NO_ERROR;
106 /* This implements the svn_txdelta_window_handler interface.
107 Forward to a more interesting window handler and if we're done, terminate
108 the txdelta and file-rev elements. */
109 static svn_error_t *
110 delta_window_handler(svn_txdelta_window_t *window, void *baton)
112 struct file_rev_baton *frb = baton;
114 SVN_ERR(frb->window_handler(window, frb->window_baton));
116 /* Terminate elements if we're done. */
117 if (!window)
119 frb->window_handler = NULL;
120 frb->window_baton = NULL;
121 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
122 "</S:txdelta></S:file-rev>" DEBUG_CR));
124 return SVN_NO_ERROR;
128 /* This implements the svn_repos_file_rev_handler2_t interface. */
129 static svn_error_t *
130 file_rev_handler(void *baton,
131 const char *path,
132 svn_revnum_t revnum,
133 apr_hash_t *rev_props,
134 svn_boolean_t merged_revision,
135 svn_txdelta_window_handler_t *window_handler,
136 void **window_baton,
137 apr_array_header_t *props,
138 apr_pool_t *pool)
140 struct file_rev_baton *frb = baton;
141 apr_pool_t *subpool = svn_pool_create(pool);
142 apr_hash_index_t *hi;
143 int i;
145 SVN_ERR(maybe_send_header(frb));
147 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
148 "<S:file-rev path=\"%s\" rev=\"%ld\">" DEBUG_CR,
149 apr_xml_quote_string(pool, path, 1), revnum));
151 /* Send rev props. */
152 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
154 const void *key;
155 void *val;
156 const char *pname;
157 const svn_string_t *pval;
159 svn_pool_clear(subpool);
160 apr_hash_this(hi, &key, NULL, &val);
161 pname = key;
162 pval = val;
163 SVN_ERR(send_prop(frb, "rev-prop", pname, pval, subpool));
166 /* Send file prop changes. */
167 for (i = 0; i < props->nelts; ++i)
169 const svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
171 svn_pool_clear(subpool);
172 if (prop->value)
173 SVN_ERR(send_prop(frb, "set-prop", prop->name, prop->value,
174 subpool));
175 else
177 /* Property was removed. */
178 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
179 "<S:remove-prop name=\"%s\"/>" DEBUG_CR,
180 apr_xml_quote_string(subpool, prop->name,
181 1)));
185 /* Send whether this was the result of a merge or not. */
186 if (merged_revision)
188 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output,
189 "<S:merged-revision/>"));
193 /* Maybe send text delta. */
194 if (window_handler)
196 svn_stream_t *base64_stream;
198 base64_stream = dav_svn__make_base64_output_stream(frb->bb, frb->output,
199 pool);
200 svn_txdelta_to_svndiff2(&frb->window_handler, &frb->window_baton,
201 base64_stream, frb->svndiff_version, pool);
202 *window_handler = delta_window_handler;
203 *window_baton = frb;
204 /* Start the txdelta element wich will be terminated by the window
205 handler together with the file-rev element. */
206 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output, "<S:txdelta>"));
208 else
209 /* No txdelta, so terminate the element here. */
210 SVN_ERR(dav_svn__send_xml(frb->bb, frb->output, "</S:file-rev>" DEBUG_CR));
212 svn_pool_destroy(subpool);
214 return SVN_NO_ERROR;
218 /* Respond to a client request for a REPORT of type file-revs-report for the
219 RESOURCE. Get request body from DOC and send result to OUTPUT. */
220 dav_error *
221 dav_svn__file_revs_report(const dav_resource *resource,
222 const apr_xml_doc *doc,
223 ap_filter_t *output)
225 svn_error_t *serr;
226 dav_error *derr = NULL;
227 apr_status_t apr_err;
228 apr_xml_elem *child;
229 int ns;
230 struct file_rev_baton frb;
231 dav_svn__authz_read_baton arb;
232 const char *path = NULL;
234 /* These get determined from the request document. */
235 svn_revnum_t start = SVN_INVALID_REVNUM;
236 svn_revnum_t end = SVN_INVALID_REVNUM;
237 svn_boolean_t include_merged_revisions = FALSE; /* off by default */
239 /* Construct the authz read check baton. */
240 arb.r = resource->info->r;
241 arb.repos = resource->info->repos;
243 /* Sanity check. */
244 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
245 /* ### This is done on other places, but the document element is
246 in this namespace, so is this necessary at all? */
247 if (ns == -1)
249 return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
250 "The request does not contain the 'svn:' "
251 "namespace, so it is not going to have "
252 "certain required elements.",
253 SVN_DAV_ERROR_NAMESPACE,
254 SVN_DAV_ERROR_TAG);
257 /* Get request information. */
258 for (child = doc->root->first_child; child != NULL; child = child->next)
260 /* if this element isn't one of ours, then skip it */
261 if (child->ns != ns)
262 continue;
264 if (strcmp(child->name, "start-revision") == 0)
265 start = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
266 else if (strcmp(child->name, "end-revision") == 0)
267 end = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
268 else if (strcmp(child->name, "include-merged-revisions") == 0)
269 include_merged_revisions = TRUE; /* presence indicates positivity */
270 else if (strcmp(child->name, "path") == 0)
272 const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
273 if ((derr = dav_svn__test_canonical(rel_path, resource->pool)))
274 return derr;
275 path = svn_path_join(resource->info->repos_path, rel_path,
276 resource->pool);
278 /* else unknown element; skip it */
281 frb.bb = apr_brigade_create(resource->pool,
282 output->c->bucket_alloc);
283 frb.output = output;
284 frb.needs_header = TRUE;
285 frb.svndiff_version = resource->info->svndiff_version;
287 /* file_rev_handler will send header first time it is called. */
289 /* Get the revisions and send them. */
290 serr = svn_repos_get_file_revs2(resource->info->repos->repos,
291 path, start, end, include_merged_revisions,
292 dav_svn__authz_read_func(&arb), &arb,
293 file_rev_handler, &frb, resource->pool);
295 if (serr)
297 /* We don't 'goto cleanup' because ap_fflush() tells httpd
298 to write the HTTP headers out, and that includes whatever
299 r->status is at that particular time. When we call
300 dav_svn__convert_err(), we don't immediately set r->status
301 right then, so r->status remains 0, hence HTTP status 200
302 would be misleadingly returned. */
303 return (dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
304 serr->message, resource->pool));
307 if ((serr = maybe_send_header(&frb)))
309 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
310 "Error beginning REPORT reponse",
311 resource->pool);
312 goto cleanup;
315 if ((serr = dav_svn__send_xml(frb.bb, frb.output,
316 "</S:file-revs-report>" DEBUG_CR)))
318 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
319 "Error ending REPORT reponse",
320 resource->pool);
321 goto cleanup;
324 cleanup:
326 /* We've detected a 'high level' svn action to log. */
327 dav_svn__operational_log(resource->info,
328 svn_log__get_file_revs(path, start, end,
329 include_merged_revisions,
330 resource->pool));
332 /* Flush the contents of the brigade (returning an error only if we
333 don't already have one). */
334 if (((apr_err = ap_fflush(output, frb.bb))) && (! derr))
335 derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
336 HTTP_INTERNAL_SERVER_ERROR,
337 "Error flushing brigade",
338 resource->pool);
339 return derr;