1 /*
2 'version' for cadaver
3 Copyright (C) 2003-2006, Joe Orton <joe@manyfish.co.uk>
4 Copyright (C) 2002-2003, GRASE Lab, UCSC <grase@cse.ucsc.edu>,
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "config.h"
22
23 #ifdef HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif
26
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30
31 #include <time.h>
32
33 #include <ne_request.h>
34 #include <ne_basic.h>
35 #include <ne_props.h>
36 #include <ne_uri.h>
37 #include <ne_alloc.h>
38 #include <ne_dates.h>
39
40 #include "i18n.h"
41 #include "commands.h"
42 #include "cadaver.h"
43 #include "utils.h"
44
45 /* Message body for REPORT */
46 static const char *report_body =
47 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
48 "<D:version-tree xmlns:D=\"DAV:\">"
49 " <D:prop>"
50 " <D:version-name/>"
51 " <D:creator-displayname/>"
52 " <D:getcontentlength/>"
53 " <D:getlastmodified/>"
54 " <D:successor-set/>"
55 " </D:prop>"
56 "</D:version-tree>";
57
58 typedef struct report_res
59 {
60 char *href;
61
62 /* live props */
63 char *version_name;
64 char *creator_displayname;
65 char *getcontentlength;
66 char *getlastmodified;
67 char *successor_set;
68
69 struct report_res *next;
70 }
71 report_res;
72
73
74 /* Search XML parser context */
75 typedef struct
76 {
77 report_res *root;
78 report_res *curr;
79 int result_num;
80 int start_prop;
81 int err_code;
82
83 ne_buffer *cdata;
84 }
85 report_ctx;
86
87 enum
88 {
89 ELEM_multistatus = 1,
90 ELEM_response,
91 ELEM_href,
92 ELEM_prop,
93 ELEM_propstat,
94 ELEM_status,
95
96 /* props from RFC 2518 , 23 Appendices 23.1 */
97 ELEM_version_name,
98 ELEM_creator_displayname,
99 ELEM_getcontentlength,
100 ELEM_getlastmodified,
101
102 ELEM_ignore
103 };
104
105
106 static const struct ne_xml_idmap report_elements[] = {
107 {"DAV:", "multistatus", ELEM_multistatus},
108 {"DAV:", "response", ELEM_response},
109 {"DAV:", "href", ELEM_href},
110 {"DAV:", "propstat", ELEM_propstat},
111 {"DAV:", "prop", ELEM_prop},
112 {"DAV:", "status", ELEM_status},
113
114 /* Live props */
115 {"DAV:", "version-name", ELEM_version_name},
116 {"DAV:", "creator-displayname", ELEM_creator_displayname},
117 {"DAV:", "getcontentlength", ELEM_getcontentlength},
118 {"DAV:", "getlastmodified", ELEM_getlastmodified},
119 };
120
121 /* We do not validate at this point */
122 /*
123 static int validate_report_elements(void *userdata,
124 ne_xml_elmid parent, ne_xml_elmid child)
125 {
126 return NE_XML_VALID;
127 }
128 */
129
130 /* Set xml parser error */
set_xml_error(report_ctx * sctx,const char * format,...)131 static void set_xml_error(report_ctx * sctx, const char *format, ...)
132 {
133 va_list ap;
134 char buf[512];
135
136 va_start(ap, format);
137 ne_vsnprintf(buf, sizeof buf, format, ap);
138 va_end(ap);
139
140 ne_set_error(session.sess, "%s", buf);
141 sctx->err_code = NE_ERROR;
142 }
143
start_element(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)144 static int start_element(void *userdata, int parent,
145 const char *nspace,
146 const char *name,
147 const char **atts)
148 {
149 report_ctx *rctx = (report_ctx *) userdata;
150 int state = ne_xml_mapid(report_elements,
151 NE_XML_MAPLEN(report_elements), nspace, name);
152
153 /* Error occured, ignore remain part */
154 if (rctx->err_code != NE_OK)
155 return rctx->err_code;
156
157 ne_buffer_clear(rctx->cdata);
158
159 switch (state) {
160
161 case ELEM_response: /* Start of new response */
162 rctx->curr = ne_calloc(sizeof(report_res));
163 rctx->result_num++;
164 break;
165
166 case ELEM_prop: /* Start of prop */
167 if (rctx->curr == NULL) {
168 set_xml_error(rctx, "XML : <%s> is in the wrong place",
169 name);
170 break;
171 }
172 rctx->start_prop = 1;
173 break;
174
175 case ELEM_propstat: /* expecting props */
176 case ELEM_href: /* href */
177 case ELEM_ignore:
178 default:
179 break;
180 }
181
182 return state;
183 }
184
185 #define REPORT_CP_ELEM(rctx, curr, name, desc, src) \
186 do { \
187 if ((curr) == NULL) \
188 set_xml_error((rctx), "XML : </%s> is in the wrong place", (name));\
189 else if (src)\
190 (desc) = ne_strdup(src);\
191 } while (0)
192
193
cdata_report(void * userdata,int state,const char * buf,size_t len)194 static int cdata_report(void *userdata, int state, const char *buf, size_t len)
195 {
196 report_ctx *rctx = (report_ctx *) userdata;
197 ne_buffer_append(rctx->cdata, buf, len);
198 return 0;
199 }
200
201
202 static int
end_element(void * userdata,int state,const char * nspace,const char * name)203 end_element(void *userdata, int state, const char *nspace, const char *name)
204 {
205 report_ctx *rctx = (report_ctx *) userdata;
206 const char *cdata = rctx->cdata->data;
207
208 /* Error occured, ignore remain part */
209 if (rctx->err_code != NE_OK)
210 return rctx->err_code;
211
212 switch (state) {
213 case ELEM_response: /* End of new response */
214 /* Nothing to add */
215 if (rctx->curr == NULL) {
216 set_xml_error(rctx, "XML : </%s> is in the wrong place",
217 name);
218 break;
219 }
220
221 /* No HREF */
222 if (rctx->curr->href == NULL) {
223 set_xml_error(rctx, "XML : No href info in the <%s>...</%s>",
224 name, name);
225 break;
226 }
227 /* make link */
228 rctx->curr->next = rctx->root;
229 rctx->root = rctx->curr;
230 rctx->curr = NULL;
231 break;
232
233 case ELEM_href: /* href */
234 REPORT_CP_ELEM(rctx, rctx->curr, name, rctx->curr->href, cdata);
235 break;
236
237 case ELEM_version_name:
238 REPORT_CP_ELEM(rctx, rctx->curr, name,
239 rctx->curr->version_name, cdata);
240 break;
241
242 case ELEM_creator_displayname:
243 REPORT_CP_ELEM(rctx, rctx->curr, name,
244 rctx->curr->creator_displayname, cdata);
245 break;
246
247 case ELEM_getcontentlength:
248 REPORT_CP_ELEM(rctx, rctx->curr, name,
249 rctx->curr->getcontentlength, cdata);
250 break;
251
252 case ELEM_getlastmodified:
253 REPORT_CP_ELEM(rctx, rctx->curr, name,
254 rctx->curr->getlastmodified, cdata);
255 break;
256
257 case ELEM_prop: /* Start of prop */
258 if (rctx->curr == NULL)
259 set_xml_error(rctx, "XML : </%s> is in the wrong place",
260 name);
261 else /* stop to props */
262 rctx->start_prop = 0;
263 break;
264
265 case ELEM_ignore:
266 case ELEM_propstat: /* expecting props */
267 default:
268 break;
269 }
270
271 return NE_OK;
272 }
273
274
report_ctx_destroy(report_ctx * sctx)275 static void report_ctx_destroy(report_ctx * sctx)
276 {
277 report_res *res, *res_free;
278
279 ne_buffer_destroy(sctx->cdata);
280
281 for (res = sctx->root; res;) {
282 ne_free(res->href);
283
284 /* live props */
285 if (res->version_name) ne_free(res->version_name);
286 if (res->creator_displayname) ne_free(res->creator_displayname);
287 if (res->getcontentlength) ne_free(res->getcontentlength);
288 if (res->getlastmodified) ne_free(res->getlastmodified);
289
290 res_free = res;
291 res = res->next;
292 ne_free(res_free);
293 }
294 }
295
296 /* Give UI feedback for request 'req', which got dispatch return code
297 * 'ret'. */
req_result(ne_request * req,int ret)298 static void req_result(ne_request *req, int ret)
299 {
300 if (ret != NE_OK) {
301 out_result(ret);
302 } else if (ne_get_status(req)->klass != 2) {
303 out_result(NE_ERROR);
304 } else {
305 out_success();
306 }
307 }
308
simple_request(const char * remote,const char * verb,const char * method)309 static void simple_request(const char *remote, const char *verb,
310 const char *method)
311 {
312 char *real_remote;
313 ne_request *req;
314 int ret;
315
316 if (remote != NULL) {
317 real_remote = resolve_path(session.uri.path, remote, true);
318 } else {
319 real_remote = ne_strdup(session.uri.path);
320 }
321
322 out_start(verb, remote);
323
324 req = ne_request_create(session.sess, method, real_remote);
325
326 ne_lock_using_resource(req, real_remote, 0);
327
328 ret = ne_request_dispatch(req);
329
330 req_result(req, ret);
331
332 ne_request_destroy(req);
333 free(real_remote);
334 }
335
execute_version(const char * remote)336 void execute_version(const char *remote)
337 {
338 simple_request(remote, _("Versioning"), "VERSION-CONTROL");
339 }
340
execute_checkin(const char * remote)341 void execute_checkin(const char *remote)
342 {
343 simple_request(remote, _("Checking in"), "CHECKIN");
344 }
345
execute_checkout(const char * remote)346 void execute_checkout(const char *remote)
347 {
348 simple_request(remote, _("Checking out"), "CHECKOUT");
349 }
350
execute_uncheckout(const char * remote)351 void execute_uncheckout(const char *remote)
352 {
353 simple_request(remote, _("Cancelling check out of"), "UNCHECKOUT");
354 }
355
356 /* displays report results */
display_report_results(report_ctx * rctx)357 static int display_report_results(report_ctx * rctx)
358 {
359 report_res *res;
360
361 if (rctx->err_code) {
362 return rctx->err_code;
363 }
364
365 printf(_(" %d version%s in history:\n"), rctx->result_num,
366 rctx->result_num==1?"":"s");
367
368 for (res = rctx->root; res; res = res->next) {
369 long modtime = res->getlastmodified ?
370 ne_httpdate_parse(res->getlastmodified) : 0;
371 int size = res->getcontentlength ? atol(res->getcontentlength) : 0;
372
373 printf("%-40s %10d %s <%s>\n", res->href,
374 size, format_time(modtime), res->version_name);
375 }
376
377 return rctx->err_code;
378 }
379
do_report(const char * real_remote,report_ctx * rctx)380 static int do_report(const char *real_remote,
381 report_ctx *rctx)
382 {
383 int ret;
384 ne_request *req;
385 ne_xml_parser *report_parser;
386
387 /* create/prep the request */
388 if ((req = ne_request_create(session.sess, "REPORT", real_remote)) == NULL)
389 return NE_ERROR;
390
391 /* Plug our XML parser */
392 report_parser = ne_xml_create();
393 ne_xml_push_handler(report_parser, start_element,
394 cdata_report,
395 end_element,
396 rctx);
397
398 ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
399
400 ne_add_response_body_reader(req, ne_accept_2xx, ne_xml_parse_v,
401 report_parser);
402 /* Set body */
403 ne_set_request_body_buffer(req, report_body, strlen(report_body));
404
405 /* run the request, see what comes back. */
406 ret = ne_request_dispatch(req);
407
408 if (ne_get_status(req)->klass != 2)
409 ret = ne_get_status(req)->code;
410
411 ne_request_destroy(req);
412
413 return ret;
414 }
415
416
execute_history(const char * remote)417 void execute_history(const char *remote)
418 {
419 int ret;
420 char *real_remote;
421 report_ctx *rctx = ne_calloc(sizeof(report_ctx));
422 rctx->cdata = ne_buffer_create();
423
424 if (remote != NULL) {
425 real_remote = resolve_path(session.uri.path, remote, false);
426 } else {
427 real_remote = ne_strdup(session.uri.path);
428 }
429 out_start(_("Version history of"), real_remote);
430
431 /* Run search */
432 ret = do_report(real_remote, rctx);
433 if (ret != NE_OK) {
434 /* Print out error message */
435 out_result(ret);
436 } else {
437 /* show report result */
438 display_report_results(rctx);
439 }
440
441 free(real_remote);
442 report_ctx_destroy(rctx);
443 }
444
445
execute_label(const char * remote,const char * act,const char * value)446 void execute_label(const char *remote, const char *act, const char *value)
447 {
448 int ret;
449 char *real_remote;
450 ne_request *req;
451 ne_buffer *label_body;
452
453 if (strcasecmp(act, "add") && strcasecmp(act, "remove") &&
454 strcasecmp(act, "set")) {
455 printf(_("Invalid action `%s' given.\n"), act);
456 return;
457 }
458
459 if (remote != NULL) {
460 real_remote = resolve_path(session.uri.path, remote, true);
461 } else {
462 real_remote = ne_strdup(session.uri.path);
463 }
464
465 out_start(_("Labelling"), real_remote);
466
467 /* Create Label Body */
468 label_body = ne_buffer_create();
469
470 /* Create the request body */
471 ne_buffer_zappend(label_body,
472 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
473 "<D:label xmlns:D=\"DAV:\">\n");
474
475 /* Adction */
476 ne_buffer_concat(label_body, "<D:", act, "><D:label-name>", value,
477 "</D:label-name></D:", act, ">", NULL);
478 ne_buffer_zappend(label_body,
479 "</D:label>\n");
480
481 /* create/prep the request */
482 req = ne_request_create(session.sess, "LABEL", real_remote);
483
484 ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
485
486 /* Set body */
487 ne_set_request_body_buffer(req, label_body->data,
488 ne_buffer_size(label_body));
489
490 /* run the request, see what comes back. */
491 ret = ne_request_dispatch(req);
492
493 /* Print out status */
494 req_result(req, ret);
495
496 ne_buffer_destroy(label_body);
497 ne_request_destroy(req);
498 free(real_remote);
499 }
500
501 static const ne_propname vcr_props[] = {
502 { "DAV:", "checked-in" },
503 { "DAV:", "checked-out" },
504 { NULL }
505 };
506
vcr_results(void * userdata,const ne_uri * uri,const ne_prop_result_set * set)507 static void vcr_results(void *userdata, const ne_uri *uri,
508 const ne_prop_result_set *set)
509 {
510 const char *checkin, *checkout;
511 int *isvcr = userdata;
512
513 checkin = ne_propset_value(set, &vcr_props[0]);
514 checkout = ne_propset_value(set, &vcr_props[1]);
515
516 /* is vcr */
517 if (checkin) {
518 *isvcr = 1;
519 } else if (checkout) {
520 *isvcr = 2;
521 }
522 }
523
is_vcr(const char * uri)524 int is_vcr(const char *uri)
525 {
526 int ret, vcr = 0;
527 ne_propfind_handler *pfh = ne_propfind_create(session.sess, uri, NE_DEPTH_ZERO);
528 ret = ne_propfind_named(pfh, vcr_props, vcr_results, &vcr);
529 ne_propfind_destroy(pfh);
530
531 if (ret != NE_OK)
532 return 0;
533
534 return vcr;
535 }
536