In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / file_revs.c
blob763b57de1899a15f2eb6a71a89bccd0189ced0d8
1 /*
2 * file_revs.c : routines for requesting and parsing file-revs reports
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 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h> /* for strcmp() */
24 #include <apr_tables.h>
25 #include <apr_strings.h>
26 #include <apr_xml.h>
28 #include "svn_error.h"
29 #include "svn_pools.h"
30 #include "svn_delta.h"
31 #include "svn_io.h"
32 #include "svn_path.h"
33 #include "svn_xml.h"
34 #include "svn_base64.h"
35 #include "svn_props.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_private_config.h"
39 #include "ra_neon.h"
43 /*** Code ***/
45 struct report_baton {
46 /* From the caller. */
47 svn_file_rev_handler_t handler;
48 void *handler_baton;
50 /* Arguments for the callback. */
51 const char *path;
52 svn_revnum_t revnum;
53 apr_hash_t *rev_props;
54 apr_array_header_t *prop_diffs;
56 /* The current property name. */
57 const char *prop_name;
59 /* Is the current property encoded in base64? */
60 svn_boolean_t base64_prop;
62 /* Buffer for accumulating CDATA for prop values. */
63 svn_stringbuf_t *cdata_accum;
65 /* Stream for writing text delta to. */
66 svn_stream_t *stream;
68 /* Merged revision flag. */
69 svn_boolean_t merged_rev;
71 svn_boolean_t had_txdelta; /* Did we have a txdelta in this file-rev elem? */
73 apr_pool_t *subpool;
76 /* Prepare RB for a new revision. */
77 static void
78 reset_file_rev(struct report_baton *rb)
80 svn_pool_clear(rb->subpool);
81 rb->path = NULL;
82 rb->revnum = SVN_INVALID_REVNUM;
83 rb->rev_props = apr_hash_make(rb->subpool);
84 rb->prop_diffs = apr_array_make(rb->subpool, 0, sizeof(svn_prop_t));
85 rb->merged_rev = FALSE;
86 rb->had_txdelta = FALSE;
87 /* Just in case... */
88 rb->stream = NULL;
92 /* Our beloved elements. */
93 static const svn_ra_neon__xml_elm_t report_elements[] =
95 { SVN_XML_NAMESPACE, "file-revs-report", ELEM_file_revs_report, 0 },
96 { SVN_XML_NAMESPACE, "file-rev", ELEM_file_rev, 0 },
97 { SVN_XML_NAMESPACE, "rev-prop", ELEM_rev_prop, 0 },
98 { SVN_XML_NAMESPACE, "set-prop", ELEM_set_prop, 0 },
99 { SVN_XML_NAMESPACE, "remove-prop", ELEM_remove_prop, 0 },
100 { SVN_XML_NAMESPACE, "merged-revision", ELEM_merged_revision, 0 },
101 { SVN_XML_NAMESPACE, "txdelta", ELEM_txdelta, 0 },
102 { NULL }
106 /* This implements the `svn_ra_neon__startelm_cb_t' prototype. */
107 static svn_error_t *
108 start_element(int *elem, void *userdata, int parent_state, const char *ns,
109 const char *ln, const char **atts)
111 struct report_baton *rb = userdata;
112 const svn_ra_neon__xml_elm_t *elm;
113 const char *att;
115 elm = svn_ra_neon__lookup_xml_elem(report_elements, ns, ln);
117 /* Skip unknown elements. */
118 if (!elm)
120 *elem = NE_XML_DECLINE;
121 return SVN_NO_ERROR;
124 switch (parent_state)
126 case ELEM_root:
127 if (elm->id != ELEM_file_revs_report)
128 return UNEXPECTED_ELEMENT(ns, ln);
129 break;
131 case ELEM_file_revs_report:
132 if (elm->id == ELEM_file_rev)
134 reset_file_rev(rb);
135 att = svn_xml_get_attr_value("rev", atts);
136 if (!att)
137 return MISSING_ATTR(ns, ln, "rev");
138 rb->revnum = SVN_STR_TO_REV(att);
139 att = svn_xml_get_attr_value("path", atts);
140 if (!att)
141 return MISSING_ATTR(ns, ln, "path");
142 rb->path = apr_pstrdup(rb->subpool, att);
144 else
145 return UNEXPECTED_ELEMENT(ns, ln);
146 break;
148 case ELEM_file_rev:
149 /* txdelta must be the last elem in file-rev. */
150 if (rb->had_txdelta)
151 return UNEXPECTED_ELEMENT(ns, ln);
152 switch (elm->id)
154 case ELEM_rev_prop:
155 case ELEM_set_prop:
156 att = svn_xml_get_attr_value("name", atts);
157 if (!att)
158 return MISSING_ATTR(ns, ln, "name");
159 rb->prop_name = apr_pstrdup(rb->subpool, att);
160 att = svn_xml_get_attr_value("encoding", atts);
161 if (att && strcmp(att, "base64") == 0)
162 rb->base64_prop = TRUE;
163 else
164 rb->base64_prop = FALSE;
165 break;
166 case ELEM_remove_prop:
168 svn_prop_t *prop = apr_array_push(rb->prop_diffs);
169 att = svn_xml_get_attr_value("name", atts);
170 if (!att || *att == '\0')
171 return MISSING_ATTR(ns, ln, "name");
172 prop->name = apr_pstrdup(rb->subpool, att);
173 prop->value = NULL;
175 break;
176 case ELEM_txdelta:
178 svn_txdelta_window_handler_t whandler = NULL;
179 void *wbaton;
180 /* It's time to call our hanlder. */
181 SVN_ERR(rb->handler(rb->handler_baton, rb->path, rb->revnum,
182 rb->rev_props, rb->merged_rev, &whandler,
183 &wbaton, rb->prop_diffs, rb->subpool));
184 if (whandler)
185 rb->stream = svn_base64_decode
186 (svn_txdelta_parse_svndiff(whandler, wbaton, TRUE,
187 rb->subpool), rb->subpool);
189 break;
190 case ELEM_merged_revision:
192 rb->merged_rev = TRUE;
194 break;
195 default:
196 return UNEXPECTED_ELEMENT(ns, ln);
198 break;
199 default:
200 return UNEXPECTED_ELEMENT(ns, ln);
203 *elem = elm->id;
205 return SVN_NO_ERROR;
208 /* Extract the property value from RB, possibly base64-decoding it.
209 Resets RB->cdata_accum. */
210 static const svn_string_t *
211 extract_propval(struct report_baton *rb)
213 const svn_string_t *v = svn_string_create_from_buf(rb->cdata_accum,
214 rb->subpool);
215 svn_stringbuf_setempty(rb->cdata_accum);
216 if (rb->base64_prop)
217 return svn_base64_decode_string(v, rb->subpool);
218 else
219 return v;
222 /* This implements the `svn_ra_neon__endelm_cb_t' prototype. */
223 static svn_error_t *
224 end_element(void *userdata, int state,
225 const char *nspace, const char *elt_name)
227 struct report_baton *rb = userdata;
229 switch (state)
231 case ELEM_file_rev:
232 /* If we had no txdelta, we call the handler here, informing it that
233 there were no content changes. */
234 if (!rb->had_txdelta)
235 SVN_ERR(rb->handler(rb->handler_baton, rb->path, rb->revnum,
236 rb->rev_props, rb->merged_rev, NULL, NULL,
237 rb->prop_diffs, rb->subpool));
238 break;
240 case ELEM_rev_prop:
241 apr_hash_set(rb->rev_props, rb->prop_name, APR_HASH_KEY_STRING,
242 extract_propval(rb));
243 break;
245 case ELEM_set_prop:
247 svn_prop_t *prop = apr_array_push(rb->prop_diffs);
248 prop->name = rb->prop_name;
249 prop->value = extract_propval(rb);
250 break;
253 case ELEM_txdelta:
254 if (rb->stream)
256 SVN_ERR(svn_stream_close(rb->stream));
257 rb->stream = NULL;
259 rb->had_txdelta = TRUE;
260 break;
262 return SVN_NO_ERROR;
265 /* This implements the `svn_ra_neon__cdata_cb' prototype. */
266 static svn_error_t *
267 cdata_handler(void *userdata, int state,
268 const char *cdata, size_t len)
270 struct report_baton *rb = userdata;
272 switch (state)
274 case ELEM_rev_prop:
275 case ELEM_set_prop:
276 svn_stringbuf_appendbytes(rb->cdata_accum, cdata, len);
277 break;
278 case ELEM_txdelta:
279 if (rb->stream)
281 apr_size_t l = len;
282 SVN_ERR(svn_stream_write(rb->stream, cdata, &l));
283 if (l != len)
284 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
285 _("Failed to write full amount to stream"));
288 /* In other cases, we just ingore the CDATA. */
291 return SVN_NO_ERROR;
294 svn_error_t *
295 svn_ra_neon__get_file_revs(svn_ra_session_t *session,
296 const char *path,
297 svn_revnum_t start,
298 svn_revnum_t end,
299 svn_boolean_t include_merged_revisions,
300 svn_file_rev_handler_t handler,
301 void *handler_baton,
302 apr_pool_t *pool)
304 svn_ra_neon__session_t *ras = session->priv;
305 svn_stringbuf_t *request_body = svn_stringbuf_create("", pool);
306 svn_string_t bc_url, bc_relative;
307 const char *final_bc_url;
308 int http_status = 0;
309 struct report_baton rb;
310 svn_error_t *err;
311 apr_hash_t *request_headers = apr_hash_make(pool);
312 static const char request_head[]
313 = "<S:file-revs-report xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR;
314 static const char request_tail[]
315 = "</S:file-revs-report>";
317 apr_hash_set(request_headers, "Accept-Encoding", APR_HASH_KEY_STRING,
318 "svndiff1;q=0.9,svndiff;q=0.8");
320 /* Construct request body. */
321 svn_stringbuf_appendcstr(request_body, request_head);
322 svn_stringbuf_appendcstr(request_body,
323 apr_psprintf(pool,
324 "<S:start-revision>%ld"
325 "</S:start-revision>", start));
326 svn_stringbuf_appendcstr(request_body,
327 apr_psprintf(pool,
328 "<S:end-revision>%ld"
329 "</S:end-revision>", end));
330 if (include_merged_revisions)
332 svn_stringbuf_appendcstr(request_body,
333 apr_psprintf(pool,
334 "<S:include-merged-revisions/>"));
337 svn_stringbuf_appendcstr(request_body, "<S:path>");
338 svn_stringbuf_appendcstr(request_body,
339 apr_xml_quote_string(pool, path, 0));
340 svn_stringbuf_appendcstr(request_body, "</S:path>");
341 svn_stringbuf_appendcstr(request_body, request_tail);
343 /* Initialize the baton. */
344 rb.handler = handler;
345 rb.handler_baton = handler_baton;
346 rb.cdata_accum = svn_stringbuf_create("", pool);
347 rb.subpool = svn_pool_create(pool);
348 reset_file_rev(&rb);
350 /* ras's URL may not exist in HEAD, and thus it's not safe to send
351 it as the main argument to the REPORT request; it might cause
352 dav_get_resource() to choke on the server. So instead, we pass a
353 baseline-collection URL, which we get from END. */
354 SVN_ERR(svn_ra_neon__get_baseline_info(NULL, &bc_url, &bc_relative, NULL,
355 ras, ras->url->data, end,
356 pool));
357 final_bc_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
358 pool);
360 /* Dispatch the request. */
361 err = svn_ra_neon__parsed_request(ras, "REPORT", final_bc_url,
362 request_body->data, NULL, NULL,
363 start_element, cdata_handler, end_element,
364 &rb, request_headers, &http_status, FALSE,
365 pool);
367 /* Map status 501: Method Not Implemented to our not implemented error.
368 1.0.x servers and older don't support this report. */
369 if (http_status == 501)
370 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
371 _("'get-file-revs' REPORT not implemented"));
373 SVN_ERR(err);
375 /* Caller expects at least one revision. Signal error otherwise. */
376 if (!SVN_IS_VALID_REVNUM(rb.revnum))
377 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
378 _("The file-revs report didn't contain any "
379 "revisions"));
381 svn_pool_destroy(rb.subpool);
383 return SVN_NO_ERROR;