1 /*
2    'ls' for cadaver
3    Copyright (C) 2000-2004, 2006, 2008, Joe Orton <joe@manyfish.co.uk>,
4    except where otherwise indicated.
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_props.h>
35 #include <ne_uri.h>
36 #include <ne_alloc.h>
37 #include <ne_dates.h>
38 
39 #include "i18n.h"
40 #include "commands.h"
41 #include "cadaver.h"
42 #include "utils.h"
43 
44 struct fetch_context {
45     struct resource **list;
46     const char *target; /* Request-URI of the PROPFIND */
47     unsigned int include_target; /* Include resource at href */
48 };
49 
50 static const ne_propname ls_props[] = {
51     { "DAV:", "getcontentlength" },
52     { "DAV:", "getlastmodified" },
53     { "http://apache.org/dav/props/", "executable" },
54     { "DAV:", "resourcetype" },
55     { "DAV:", "checked-in" },
56     { "DAV:", "checked-out" },
57     { NULL }
58 };
59 
60 #define ELM_resourcetype (NE_PROPS_STATE_TOP + 1)
61 #define ELM_collection (NE_PROPS_STATE_TOP + 2)
62 
63 static const struct ne_xml_idmap ls_idmap[] = {
64     { "DAV:", "resourcetype", ELM_resourcetype },
65     { "DAV:", "collection", ELM_collection }
66 };
67 
compare_resource(const struct resource * r1,const struct resource * r2)68 static int compare_resource(const struct resource *r1,
69 			    const struct resource *r2)
70 {
71     /* Sort errors first, then collections, then alphabetically */
72     if (r1->type == resr_error) {
73 	return -1;
74     } else if (r2->type == resr_error) {
75 	return 1;
76     } else if (r1->type == resr_collection) {
77 	if (r2->type != resr_collection) {
78 	    return -1;
79 	} else {
80 	    return strcmp(r1->uri, r2->uri);
81 	}
82     } else {
83 	if (r2->type != resr_collection) {
84 	    return strcmp(r1->uri, r2->uri);
85 	} else {
86 	    return 1;
87 	}
88     }
89 }
90 
display_ls_line(struct resource * res)91 static void display_ls_line(struct resource *res)
92 {
93     const char *restype;
94     char exec_char, vcr_char, *name;
95 
96     switch (res->type) {
97     case resr_normal: restype = ""; break;
98     case resr_reference: restype = _("Ref:"); break;
99     case resr_collection: restype = _("Coll:"); break;
100     default:
101 	restype = "???"; break;
102     }
103 
104     if (ne_path_has_trailing_slash(res->uri)) {
105 	res->uri[strlen(res->uri)-1] = '\0';
106     }
107     name = strrchr(res->uri, '/');
108     if (name != NULL && strlen(name+1) > 0) {
109 	name++;
110     } else {
111 	name = res->uri;
112     }
113 
114     name = ne_path_unescape(name);
115 
116     if (res->type == resr_error) {
117 	printf(_("Error: %-30s %d %s\n"), name, res->error_status,
118 	       res->error_reason?res->error_reason:_("unknown"));
119     } else {
120 	exec_char = res->is_executable ? '*' : ' ';
121 	/* 0: no vcr, 1: checkin, 2: checkout */
122 	vcr_char = res->is_vcr==0 ? ' ' : (res->is_vcr==1? '>' : '<');
123 	printf("%5s %c%c%-29s %10" FMT_DAV_SIZE_T "u  %s\n",
124 	       restype, vcr_char, exec_char, name,
125 	       res->size, format_time(res->modtime));
126     }
127 
128     free(name);
129 }
130 
execute_ls(const char * remote)131 void execute_ls(const char *remote)
132 {
133     int ret;
134     char *real_remote;
135     struct resource *reslist = NULL, *current, *next;
136 
137     if (remote != NULL) {
138 	real_remote = resolve_path(session.uri.path, remote, true);
139     } else {
140 	real_remote = ne_strdup(session.uri.path);
141     }
142     out_start(_("Listing collection"), real_remote);
143     ret = fetch_resource_list(session.sess, real_remote, 1, 0, &reslist);
144     if (ret == NE_OK) {
145 	/* Easy this, eh? */
146 	if (reslist == NULL) {
147 	    output(o_finish, _("collection is empty.\n"));
148 	} else {
149 	    out_success();
150 	    for (current = reslist; current!=NULL; current = next) {
151 		next = current->next;
152 		if (strlen(current->uri) > strlen(real_remote)) {
153 		    display_ls_line(current);
154 		}
155 		free_resource(current);
156 	    }
157 	}
158     } else {
159 	out_result(ret);
160     }
161     free(real_remote);
162 }
163 
results(void * userdata,const ne_uri * uri,const ne_prop_result_set * set)164 static void results(void *userdata,
165                     const ne_uri *uri,
166 		    const ne_prop_result_set *set)
167 {
168     struct fetch_context *ctx = userdata;
169     struct resource *current, *previous, *newres;
170     const char *clength, *modtime, *isexec;
171     const char *checkin, *checkout;
172     const ne_status *status = NULL;
173     const char *path = uri->path;
174 
175     newres = ne_propset_private(set);
176 
177     if (ne_path_compare(ctx->target, path) == 0 && !ctx->include_target) {
178 	/* This is the target URI */
179 	NE_DEBUG(NE_DBG_HTTP, "Skipping target resource.\n");
180 	/* Free the private structure. */
181 	ne_free(newres);
182 	return;
183     }
184 
185     newres->uri = ne_strdup(path);
186 
187     clength = ne_propset_value(set, &ls_props[0]);
188     modtime = ne_propset_value(set, &ls_props[1]);
189     isexec = ne_propset_value(set, &ls_props[2]);
190     checkin = ne_propset_value(set, &ls_props[4]);
191     checkout = ne_propset_value(set, &ls_props[5]);
192 
193 
194     if (clength == NULL)
195 	status = ne_propset_status(set, &ls_props[0]);
196     if (modtime == NULL)
197 	status = ne_propset_status(set, &ls_props[1]);
198 
199     if (newres->type == resr_normal && status) {
200 	/* It's an error! */
201 	newres->error_status = status->code;
202 
203 	/* Special hack for Apache 1.3/mod_dav */
204 	if (strcmp(status->reason_phrase, "status text goes here") == 0) {
205 	    const char *desc;
206 	    if (status->code == 401) {
207 		desc = _("Authorization Required");
208 	    } else if (status->klass == 3) {
209 		desc = _("Redirect");
210 	    } else if (status->klass == 5) {
211 		desc = _("Server Error");
212 	    } else {
213 		desc = _("Unknown Error");
214 	    }
215 	    newres->error_reason = ne_strdup(desc);
216 	} else {
217 	    newres->error_reason = ne_strdup(status->reason_phrase);
218 	}
219 	newres->type = resr_error;
220     }
221 
222     if (isexec && strcasecmp(isexec, "T") == 0) {
223 	newres->is_executable = 1;
224     } else {
225 	newres->is_executable = 0;
226     }
227 
228     if (modtime)
229 	newres->modtime = ne_httpdate_parse(modtime);
230 
231     if (clength) {
232         char *p;
233 
234         newres->size = DAV_STRTOL(clength, &p, 10);
235         if (*p) {
236             newres->size = 0;
237         }
238     }
239 
240     /* is vcr */
241     if (checkin) {
242 	newres->is_vcr = 1;
243     } else if (checkout) {
244 	newres->is_vcr = 2;
245     } else {
246 	newres->is_vcr = 0;
247     }
248 
249     NE_DEBUG(NE_DBG_HTTP, "End resource %s\n", newres->uri);
250 
251     for (current = *ctx->list, previous = NULL; current != NULL;
252 	 previous = current, current=current->next) {
253 	if (compare_resource(current, newres) >= 0) {
254 	    break;
255 	}
256     }
257     if (previous) {
258 	previous->next = newres;
259     } else {
260 	*ctx->list = newres;
261     }
262     newres->next = current;
263 }
264 
ls_startelm(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)265 static int ls_startelm(void *userdata, int parent,
266                        const char *nspace, const char *name, const char **atts)
267 {
268     ne_propfind_handler *pfh = userdata;
269     struct resource *r = ne_propfind_current_private(pfh);
270     int state = ne_xml_mapid(ls_idmap, NE_XML_MAPLEN(ls_idmap),
271                              nspace, name);
272 
273     if (r == NULL ||
274         !((parent == NE_207_STATE_PROP && state == ELM_resourcetype) ||
275           (parent == ELM_resourcetype && state == ELM_collection)))
276         return NE_XML_DECLINE;
277 
278     if (state == ELM_collection) {
279 	NE_DEBUG(NE_DBG_HTTP, "This is a collection.\n");
280 	r->type = resr_collection;
281     }
282 
283     return state;
284 }
285 
free_resource(struct resource * res)286 void free_resource(struct resource *res)
287 {
288     if (res->uri) ne_free(res->uri);
289     if (res->error_reason) ne_free(res->error_reason);
290     free(res);
291 }
292 
free_resource_list(struct resource * res)293 void free_resource_list(struct resource *res)
294 {
295     struct resource *next;
296     for (; res != NULL; res = next) {
297 	next = res->next;
298 	free_resource(res);
299     }
300 }
301 
create_private(void * userdata,const ne_uri * uri)302 static void *create_private(void *userdata,
303                     const ne_uri *uri
304     )
305 {
306     return ne_calloc(sizeof(struct resource));
307 }
308 
fetch_resource_list(ne_session * sess,const char * uri,int depth,int include_target,struct resource ** reslist)309 int fetch_resource_list(ne_session *sess, const char *uri,
310 			 int depth, int include_target,
311 			 struct resource **reslist)
312 {
313     ne_propfind_handler *pfh = ne_propfind_create(sess, uri, depth);
314     int ret;
315     struct fetch_context ctx = {0};
316 
317     *reslist = NULL;
318     ctx.list = reslist;
319     ctx.target = uri;
320     ctx.include_target = include_target;
321 
322     ne_xml_push_handler(ne_propfind_get_parser(pfh),
323                         ls_startelm, NULL, NULL, pfh);
324 
325     ne_propfind_set_private(pfh, create_private, NULL, NULL);
326 
327     ret = ne_propfind_named(pfh, ls_props, results, &ctx);
328 
329     ne_propfind_destroy(pfh);
330 
331     return ret;
332 }
333