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"
24 #include "svn_pools.h"
25 #include "svn_base64.h"
26 #include "svn_props.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 */
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. */
47 /* Used by the delta iwndow handler. */
48 svn_txdelta_window_handler_t window_handler
;
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
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
;
73 /* Send a property named NAME with value VAL in an element named ELEM_NAME.
74 Quote NAME and base64-encode VAL if necessary. */
76 send_prop(struct file_rev_baton
*frb
,
77 const char *elem_name
,
79 const svn_string_t
*val
,
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
));
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\">"
99 elem_name
, name
, val
->data
, elem_name
));
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. */
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. */
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
));
128 /* This implements the svn_repos_file_rev_handler2_t interface. */
130 file_rev_handler(void *baton
,
133 apr_hash_t
*rev_props
,
134 svn_boolean_t merged_revision
,
135 svn_txdelta_window_handler_t
*window_handler
,
137 apr_array_header_t
*props
,
140 struct file_rev_baton
*frb
= baton
;
141 apr_pool_t
*subpool
= svn_pool_create(pool
);
142 apr_hash_index_t
*hi
;
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
))
157 const svn_string_t
*pval
;
159 svn_pool_clear(subpool
);
160 apr_hash_this(hi
, &key
, NULL
, &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
);
173 SVN_ERR(send_prop(frb
, "set-prop", prop
->name
, prop
->value
,
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
,
185 /* Send whether this was the result of a merge or not. */
188 SVN_ERR(dav_svn__send_xml(frb
->bb
, frb
->output
,
189 "<S:merged-revision/>"));
193 /* Maybe send text delta. */
196 svn_stream_t
*base64_stream
;
198 base64_stream
= dav_svn__make_base64_output_stream(frb
->bb
, frb
->output
,
200 svn_txdelta_to_svndiff2(&frb
->window_handler
, &frb
->window_baton
,
201 base64_stream
, frb
->svndiff_version
, pool
);
202 *window_handler
= delta_window_handler
;
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>"));
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
);
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. */
221 dav_svn__file_revs_report(const dav_resource
*resource
,
222 const apr_xml_doc
*doc
,
226 dav_error
*derr
= NULL
;
227 apr_status_t apr_err
;
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
;
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? */
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
,
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 */
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
)))
275 path
= svn_path_join(resource
->info
->repos_path
, rel_path
,
278 /* else unknown element; skip it */
281 frb
.bb
= apr_brigade_create(resource
->pool
,
282 output
->c
->bucket_alloc
);
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
);
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",
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",
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
,
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",