1/*
2 * blame.c :  entry point for blame RA functions for ra_serf
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <apr_uri.h>
25#include <serf.h>
26
27#include "svn_hash.h"
28#include "svn_pools.h"
29#include "svn_ra.h"
30#include "svn_dav.h"
31#include "svn_xml.h"
32#include "svn_config.h"
33#include "svn_delta.h"
34#include "svn_path.h"
35#include "svn_base64.h"
36#include "svn_props.h"
37
38#include "svn_private_config.h"
39
40#include "private/svn_string_private.h"
41
42#include "ra_serf.h"
43#include "../libsvn_ra/ra_loader.h"
44
45
46/*
47 * This enum represents the current state of our XML parsing for a REPORT.
48 */
49typedef enum blame_state_e {
50  INITIAL = 0,
51  FILE_REVS_REPORT,
52  FILE_REV,
53  REV_PROP,
54  SET_PROP,
55  REMOVE_PROP,
56  MERGED_REVISION,
57  TXDELTA
58} blame_state_e;
59
60
61typedef struct blame_context_t {
62  /* pool passed to get_file_revs */
63  apr_pool_t *pool;
64
65  /* parameters set by our caller */
66  const char *path;
67  svn_revnum_t start;
68  svn_revnum_t end;
69  svn_boolean_t include_merged_revisions;
70
71  /* blame handler and baton */
72  svn_file_rev_handler_t file_rev;
73  void *file_rev_baton;
74
75  /* As we parse each FILE_REV, we collect data in these variables:
76     property changes and new content.  STREAM is valid when we're
77     in the TXDELTA state, processing the incoming cdata.  */
78  apr_hash_t *rev_props;
79  apr_array_header_t *prop_diffs;
80  apr_pool_t *state_pool;  /* put property stuff in here  */
81
82  svn_stream_t *stream;
83
84} blame_context_t;
85
86
87#define D_ "DAV:"
88#define S_ SVN_XML_NAMESPACE
89static const svn_ra_serf__xml_transition_t blame_ttable[] = {
90  { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
91    FALSE, { NULL }, FALSE },
92
93  { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
94    FALSE, { "path", "rev", NULL }, TRUE },
95
96  { FILE_REV, S_, "rev-prop", REV_PROP,
97    TRUE, { "name", "?encoding", NULL }, TRUE },
98
99  { FILE_REV, S_, "set-prop", SET_PROP,
100    TRUE, { "name", "?encoding", NULL }, TRUE },
101
102  { FILE_REV, S_, "remove-prop", REMOVE_PROP,
103    FALSE, { "name", NULL }, TRUE },
104
105  { FILE_REV, S_, "merged-revision", MERGED_REVISION,
106    FALSE, { NULL }, TRUE },
107
108  { FILE_REV, S_, "txdelta", TXDELTA,
109    FALSE, { NULL }, TRUE },
110
111  { 0 }
112};
113
114
115/* Conforms to svn_ra_serf__xml_opened_t  */
116static svn_error_t *
117blame_opened(svn_ra_serf__xml_estate_t *xes,
118             void *baton,
119             int entered_state,
120             const svn_ra_serf__dav_props_t *tag,
121             apr_pool_t *scratch_pool)
122{
123  blame_context_t *blame_ctx = baton;
124
125  if (entered_state == FILE_REV)
126    {
127      apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
128
129      /* Child elements will store properties in these structures.  */
130      blame_ctx->rev_props = apr_hash_make(state_pool);
131      blame_ctx->prop_diffs = apr_array_make(state_pool,
132                                             5, sizeof(svn_prop_t));
133      blame_ctx->state_pool = state_pool;
134
135      /* Clear this, so we can detect the absence of a TXDELTA.  */
136      blame_ctx->stream = NULL;
137    }
138  else if (entered_state == TXDELTA)
139    {
140      apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
141      apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
142      const char *path;
143      const char *rev;
144      const char *merged_revision;
145      svn_txdelta_window_handler_t txdelta;
146      void *txdelta_baton;
147
148      path = svn_hash_gets(gathered, "path");
149      rev = svn_hash_gets(gathered, "rev");
150      merged_revision = svn_hash_gets(gathered, "merged-revision");
151
152      SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
153                                  path, SVN_STR_TO_REV(rev),
154                                  blame_ctx->rev_props,
155                                  merged_revision != NULL,
156                                  &txdelta, &txdelta_baton,
157                                  blame_ctx->prop_diffs,
158                                  state_pool));
159
160      blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
161                                              txdelta, txdelta_baton,
162                                              TRUE /* error_on_early_close */,
163                                              state_pool),
164                                            state_pool);
165    }
166
167  return SVN_NO_ERROR;
168}
169
170
171/* Conforms to svn_ra_serf__xml_closed_t  */
172static svn_error_t *
173blame_closed(svn_ra_serf__xml_estate_t *xes,
174             void *baton,
175             int leaving_state,
176             const svn_string_t *cdata,
177             apr_hash_t *attrs,
178             apr_pool_t *scratch_pool)
179{
180  blame_context_t *blame_ctx = baton;
181
182  if (leaving_state == FILE_REV)
183    {
184      /* Note that we test STREAM, but any pointer is currently invalid.
185         It was closed when left the TXDELTA state.  */
186      if (blame_ctx->stream == NULL)
187        {
188          const char *path;
189          const char *rev;
190
191          path = svn_hash_gets(attrs, "path");
192          rev = svn_hash_gets(attrs, "rev");
193
194          /* Send a "no content" notification.  */
195          SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
196                                      path, SVN_STR_TO_REV(rev),
197                                      blame_ctx->rev_props,
198                                      FALSE /* result_of_merge */,
199                                      NULL, NULL, /* txdelta / baton */
200                                      blame_ctx->prop_diffs,
201                                      scratch_pool));
202        }
203    }
204  else if (leaving_state == MERGED_REVISION)
205    {
206      svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
207    }
208  else if (leaving_state == TXDELTA)
209    {
210      SVN_ERR(svn_stream_close(blame_ctx->stream));
211    }
212  else
213    {
214      const char *name;
215      const svn_string_t *value;
216
217      SVN_ERR_ASSERT(leaving_state == REV_PROP
218                     || leaving_state == SET_PROP
219                     || leaving_state == REMOVE_PROP);
220
221      name = apr_pstrdup(blame_ctx->state_pool,
222                         svn_hash_gets(attrs, "name"));
223
224      if (leaving_state == REMOVE_PROP)
225        {
226          value = NULL;
227        }
228      else
229        {
230          const char *encoding = svn_hash_gets(attrs, "encoding");
231
232          if (encoding && strcmp(encoding, "base64") == 0)
233            value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
234          else
235            value = svn_string_dup(cdata, blame_ctx->state_pool);
236        }
237
238      if (leaving_state == REV_PROP)
239        {
240          svn_hash_sets(blame_ctx->rev_props, name, value);
241        }
242      else
243        {
244          svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
245
246          prop->name = name;
247          prop->value = value;
248        }
249    }
250
251  return SVN_NO_ERROR;
252}
253
254
255/* Conforms to svn_ra_serf__xml_cdata_t  */
256static svn_error_t *
257blame_cdata(svn_ra_serf__xml_estate_t *xes,
258            void *baton,
259            int current_state,
260            const char *data,
261            apr_size_t len,
262            apr_pool_t *scratch_pool)
263{
264  blame_context_t *blame_ctx = baton;
265
266  if (current_state == TXDELTA)
267    {
268      SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
269      /* Ignore the returned LEN value.  */
270    }
271
272  return SVN_NO_ERROR;
273}
274
275
276/* Implements svn_ra_serf__request_body_delegate_t */
277static svn_error_t *
278create_file_revs_body(serf_bucket_t **body_bkt,
279                      void *baton,
280                      serf_bucket_alloc_t *alloc,
281                      apr_pool_t *pool)
282{
283  serf_bucket_t *buckets;
284  blame_context_t *blame_ctx = baton;
285
286  buckets = serf_bucket_aggregate_create(alloc);
287
288  svn_ra_serf__add_open_tag_buckets(buckets, alloc,
289                                    "S:file-revs-report",
290                                    "xmlns:S", SVN_XML_NAMESPACE,
291                                    NULL);
292
293  svn_ra_serf__add_tag_buckets(buckets,
294                               "S:start-revision", apr_ltoa(pool, blame_ctx->start),
295                               alloc);
296
297  svn_ra_serf__add_tag_buckets(buckets,
298                               "S:end-revision", apr_ltoa(pool, blame_ctx->end),
299                               alloc);
300
301  if (blame_ctx->include_merged_revisions)
302    {
303      svn_ra_serf__add_tag_buckets(buckets,
304                                   "S:include-merged-revisions", NULL,
305                                   alloc);
306    }
307
308  svn_ra_serf__add_tag_buckets(buckets,
309                               "S:path", blame_ctx->path,
310                               alloc);
311
312  svn_ra_serf__add_close_tag_buckets(buckets, alloc,
313                                     "S:file-revs-report");
314
315  *body_bkt = buckets;
316  return SVN_NO_ERROR;
317}
318
319svn_error_t *
320svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
321                           const char *path,
322                           svn_revnum_t start,
323                           svn_revnum_t end,
324                           svn_boolean_t include_merged_revisions,
325                           svn_file_rev_handler_t rev_handler,
326                           void *rev_handler_baton,
327                           apr_pool_t *pool)
328{
329  blame_context_t *blame_ctx;
330  svn_ra_serf__session_t *session = ra_session->priv;
331  svn_ra_serf__handler_t *handler;
332  svn_ra_serf__xml_context_t *xmlctx;
333  const char *req_url;
334  svn_error_t *err;
335
336  blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
337  blame_ctx->pool = pool;
338  blame_ctx->path = path;
339  blame_ctx->file_rev = rev_handler;
340  blame_ctx->file_rev_baton = rev_handler_baton;
341  blame_ctx->start = start;
342  blame_ctx->end = end;
343  blame_ctx->include_merged_revisions = include_merged_revisions;
344
345  SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
346                                      session, NULL /* conn */,
347                                      NULL /* url */, end,
348                                      pool, pool));
349
350  xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
351                                           blame_opened,
352                                           blame_closed,
353                                           blame_cdata,
354                                           blame_ctx,
355                                           pool);
356  handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
357
358  handler->method = "REPORT";
359  handler->path = req_url;
360  handler->body_type = "text/xml";
361  handler->body_delegate = create_file_revs_body;
362  handler->body_delegate_baton = blame_ctx;
363  handler->conn = session->conns[0];
364  handler->session = session;
365
366  err = svn_ra_serf__context_run_one(handler, pool);
367
368  err = svn_error_compose_create(
369            svn_ra_serf__error_on_status(handler->sline,
370                                         handler->path,
371                                         handler->location),
372            err);
373
374  return svn_error_trace(err);
375}
376