1 /*
2    Basic HTTP and WebDAV methods
3    Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18    MA 02111-1307, USA
19 
20 */
21 
22 #include "config.h"
23 
24 #include <sys/types.h>
25 #include <sys/stat.h> /* for struct stat */
26 
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #ifdef HAVE_STDLIB_H
34 #include <stdlib.h>
35 #endif
36 
37 #include <errno.h>
38 
39 #include "ne_request.h"
40 #include "ne_alloc.h"
41 #include "ne_utils.h"
42 #include "ne_basic.h"
43 #include "ne_207.h"
44 
45 #ifdef NE_HAVE_DAV
46 #include "ne_uri.h"
47 #include "ne_locks.h"
48 #endif
49 
50 #include "ne_dates.h"
51 #include "ne_internal.h"
52 
ne_getmodtime(ne_session * sess,const char * uri,time_t * modtime)53 int ne_getmodtime(ne_session *sess, const char *uri, time_t *modtime)
54 {
55     ne_request *req = ne_request_create(sess, "HEAD", uri);
56     const char *value;
57     int ret;
58 
59     ret = ne_request_dispatch(req);
60 
61     value = ne_get_response_header(req, "Last-Modified");
62 
63     if (ret == NE_OK && ne_get_status(req)->klass != 2) {
64 	*modtime = -1;
65 	ret = NE_ERROR;
66     }
67     else if (value) {
68         *modtime = ne_httpdate_parse(value);
69     }
70     else {
71         *modtime = -1;
72     }
73 
74     ne_request_destroy(req);
75 
76     return ret;
77 }
78 
79 #ifdef NE_LFS
80 #define ne_fstat fstat64
81 typedef struct stat64 struct_stat;
82 #else
83 #define ne_fstat fstat
84 typedef struct stat struct_stat;
85 #endif
86 
87 /* PUT's from fd to URI */
ne_put(ne_session * sess,const char * uri,int fd)88 int ne_put(ne_session *sess, const char *uri, int fd)
89 {
90     ne_request *req;
91     struct_stat st;
92     int ret;
93 
94     if (ne_fstat(fd, &st)) {
95         int errnum = errno;
96         char buf[200];
97 
98         ne_set_error(sess, _("Could not determine file size: %s"),
99                      ne_strerror(errnum, buf, sizeof buf));
100         return NE_ERROR;
101     }
102 
103     req = ne_request_create(sess, "PUT", uri);
104 
105 #ifdef NE_HAVE_DAV
106     ne_lock_using_resource(req, uri, 0);
107     ne_lock_using_parent(req, uri);
108 #endif
109 
110     ne_set_request_body_fd(req, fd, 0, st.st_size);
111 
112     ret = ne_request_dispatch(req);
113 
114     if (ret == NE_OK && ne_get_status(req)->klass != 2)
115 	ret = NE_ERROR;
116 
117     ne_request_destroy(req);
118 
119     return ret;
120 }
121 
122 /* Dispatch a GET request REQ, writing the response body to FD fd.  If
123  * RANGE is non-NULL, then it is the value of the Range request
124  * header, e.g. "bytes=1-5".  Returns an NE_* error code. */
dispatch_to_fd(ne_request * req,int fd,const char * range)125 static int dispatch_to_fd(ne_request *req, int fd, const char *range)
126 {
127     ne_session *const sess = ne_get_session(req);
128     const ne_status *const st = ne_get_status(req);
129     int ret;
130     size_t rlen;
131 
132     /* length of bytespec after "bytes=" */
133     rlen = range ? strlen(range + 6) : 0;
134 
135     do {
136         const char *value;
137 
138         ret = ne_begin_request(req);
139         if (ret != NE_OK) break;
140 
141         value = ne_get_response_header(req, "Content-Range");
142 
143         /* For a 206 response, check that a Content-Range header is
144          * given which matches the Range request header. */
145         if (range && st->code == 206
146             && (value == NULL || strncmp(value, "bytes ", 6) != 0
147                 || strncmp(range + 6, value + 6, rlen)
148                 || (range[5 + rlen] != '-' && value[6 + rlen] != '/'))) {
149             ne_set_error(sess, _("Response did not include requested range"));
150             return NE_ERROR;
151         }
152 
153         if ((range && st->code == 206) || (!range && st->klass == 2)) {
154             ret = ne_read_response_to_fd(req, fd);
155         } else {
156             ret = ne_discard_response(req);
157         }
158 
159         if (ret == NE_OK) ret = ne_end_request(req);
160     } while (ret == NE_RETRY);
161 
162     return ret;
163 }
164 
get_range_common(ne_session * sess,const char * uri,const char * brange,int fd)165 static int get_range_common(ne_session *sess, const char *uri,
166                             const char *brange, int fd)
167 
168 {
169     ne_request *req = ne_request_create(sess, "GET", uri);
170     const ne_status *status;
171     int ret;
172 
173     ne_add_request_header(req, "Range", brange);
174     ne_add_request_header(req, "Accept-Ranges", "bytes");
175 
176     ret = dispatch_to_fd(req, fd, brange);
177 
178     status = ne_get_status(req);
179 
180     if (ret == NE_OK && status->code == 416) {
181 	/* connection is terminated too early with Apache/1.3, so we check
182 	 * this even if ret == NE_ERROR... */
183 	ne_set_error(sess, _("Range is not satisfiable"));
184 	ret = NE_ERROR;
185     }
186     else if (ret == NE_OK) {
187 	if (status->klass == 2 && status->code != 206) {
188 	    ne_set_error(sess, _("Resource does not support ranged GET requests"));
189 	    ret = NE_ERROR;
190 	}
191 	else if (status->klass != 2) {
192 	    ret = NE_ERROR;
193 	}
194     }
195 
196     ne_request_destroy(req);
197 
198     return ret;
199 }
200 
ne_get_range(ne_session * sess,const char * uri,ne_content_range * range,int fd)201 int ne_get_range(ne_session *sess, const char *uri,
202 		 ne_content_range *range, int fd)
203 {
204     char brange[64];
205 
206     if (range->end == -1) {
207         ne_snprintf(brange, sizeof brange, "bytes=%" FMT_NE_OFF_T "-",
208                     range->start);
209     }
210     else {
211 	ne_snprintf(brange, sizeof brange,
212                     "bytes=%" FMT_NE_OFF_T "-%" FMT_NE_OFF_T,
213                     range->start, range->end);
214     }
215 
216     return get_range_common(sess, uri, brange, fd);
217 }
218 
219 /* Get to given fd */
ne_get(ne_session * sess,const char * uri,int fd)220 int ne_get(ne_session *sess, const char *uri, int fd)
221 {
222     ne_request *req = ne_request_create(sess, "GET", uri);
223     int ret;
224 
225     ret = dispatch_to_fd(req, fd, NULL);
226 
227     if (ret == NE_OK && ne_get_status(req)->klass != 2) {
228 	ret = NE_ERROR;
229     }
230 
231     ne_request_destroy(req);
232 
233     return ret;
234 }
235 
236 
237 /* Get to given fd */
ne_post(ne_session * sess,const char * uri,int fd,const char * buffer)238 int ne_post(ne_session *sess, const char *uri, int fd, const char *buffer)
239 {
240     ne_request *req = ne_request_create(sess, "POST", uri);
241     int ret;
242 
243     ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
244 
245     ne_set_request_body_buffer(req, buffer, strlen(buffer));
246 
247     ret = dispatch_to_fd(req, fd, NULL);
248 
249     if (ret == NE_OK && ne_get_status(req)->klass != 2) {
250 	ret = NE_ERROR;
251     }
252 
253     ne_request_destroy(req);
254 
255     return ret;
256 }
257 
ne_get_content_type(ne_request * req,ne_content_type * ct)258 int ne_get_content_type(ne_request *req, ne_content_type *ct)
259 {
260     const char *value;
261     char *sep, *stype;
262 
263     value = ne_get_response_header(req, "Content-Type");
264     if (value == NULL || strchr(value, '/') == NULL) {
265         return -1;
266     }
267 
268     ct->value = ne_strdup(value);
269 
270     stype = strchr(ct->value, '/');
271 
272     *stype++ = '\0';
273     ct->type = ct->value;
274     ct->charset = NULL;
275 
276     sep = strchr(stype, ';');
277 
278     if (sep) {
279 	char *tok;
280 	/* look for the charset parameter. TODO; probably better to
281 	 * hand-carve a parser than use ne_token/strstr/shave here. */
282 	*sep++ = '\0';
283 	do {
284 	    tok = ne_qtoken(&sep, ';', "\"\'");
285 	    if (tok) {
286 		tok = strstr(tok, "charset=");
287 		if (tok)
288 		    ct->charset = ne_shave(tok+8, "\"\'");
289 	    } else {
290 		break;
291 	    }
292 	} while (sep != NULL);
293     }
294 
295     /* set subtype, losing any trailing whitespace */
296     ct->subtype = ne_shave(stype, " \t");
297 
298     if (ct->charset == NULL && ne_strcasecmp(ct->type, "text") == 0) {
299         /* 3280§3.1: text/xml without charset implies us-ascii. */
300         if (ne_strcasecmp(ct->subtype, "xml") == 0)
301             ct->charset = "us-ascii";
302         /* 2616§3.7.1: subtypes of text/ default to charset ISO-8859-1. */
303         else
304             ct->charset = "ISO-8859-1";
305     }
306 
307     return 0;
308 }
309 
310 static const struct options_map {
311     const char *name;
312     unsigned int cap;
313 } options_map[] = {
314     { "1", NE_CAP_DAV_CLASS1 },
315     { "2", NE_CAP_DAV_CLASS2 },
316     { "3", NE_CAP_DAV_CLASS3 },
317     { "<http://apache.org/dav/propset/fs/1>", NE_CAP_MODDAV_EXEC },
318     { "access-control", NE_CAP_DAV_ACL },
319     { "version-control", NE_CAP_VER_CONTROL },
320     { "checkout-in-place", NE_CAP_CO_IN_PLACE },
321     { "version-history", NE_CAP_VER_HISTORY },
322     { "workspace", NE_CAP_WORKSPACE },
323     { "update", NE_CAP_UPDATE },
324     { "label", NE_CAP_LABEL },
325     { "working-resource", NE_CAP_WORK_RESOURCE },
326     { "merge", NE_CAP_MERGE },
327     { "baseline", NE_CAP_BASELINE },
328     { "version-controlled-collection", NE_CAP_VC_COLLECTION },
329     { "extended-mkcol", NE_CAP_EXT_MKCOL }
330 };
331 
parse_dav_header(const char * value,unsigned int * caps)332 static void parse_dav_header(const char *value, unsigned int *caps)
333 {
334     char *tokens = ne_strdup(value), *pnt = tokens;
335 
336     *caps = 0;
337 
338     do {
339         char *tok = ne_qtoken(&pnt, ',',  "\"'");
340         unsigned n;
341 
342         if (!tok) break;
343 
344         tok = ne_shave(tok, " \r\t\n");
345 
346         for (n = 0; n < sizeof(options_map)/sizeof(options_map[0]); n++) {
347             if (strcmp(tok, options_map[n].name) == 0) {
348                 *caps |= options_map[n].cap;
349             }
350         }
351     } while (pnt != NULL);
352 
353     ne_free(tokens);
354 }
355 
ne_options2(ne_session * sess,const char * uri,unsigned int * caps)356 int ne_options2(ne_session *sess, const char *uri, unsigned int *caps)
357 {
358     ne_request *req = ne_request_create(sess, "OPTIONS", uri);
359     int ret = ne_request_dispatch(req);
360     const char *header = ne_get_response_header(req, "DAV");
361 
362     if (header) parse_dav_header(header, caps);
363 
364     if (ret == NE_OK && ne_get_status(req)->klass != 2) {
365 	ret = NE_ERROR;
366     }
367 
368     ne_request_destroy(req);
369 
370     return ret;
371 }
372 
ne_options(ne_session * sess,const char * path,ne_server_capabilities * caps)373 int ne_options(ne_session *sess, const char *path,
374                ne_server_capabilities *caps)
375 {
376     int ret;
377     unsigned int capmask = 0;
378 
379     memset(caps, 0, sizeof *caps);
380 
381     ret = ne_options2(sess, path, &capmask);
382 
383     caps->dav_class1 = capmask & NE_CAP_DAV_CLASS1 ? 1 : 0;
384     caps->dav_class2 = capmask & NE_CAP_DAV_CLASS2 ? 1 : 0;
385     caps->dav_executable = capmask & NE_CAP_MODDAV_EXEC ? 1 : 0;
386 
387     return ret;
388 }
389 
390 #ifdef NE_HAVE_DAV
391 
ne_add_depth_header(ne_request * req,int depth)392 void ne_add_depth_header(ne_request *req, int depth)
393 {
394     const char *value;
395     switch(depth) {
396     case NE_DEPTH_ZERO:
397 	value = "0";
398 	break;
399     case NE_DEPTH_ONE:
400 	value = "1";
401 	break;
402     default:
403 	value = "infinity";
404 	break;
405     }
406     ne_add_request_header(req, "Depth", value);
407 }
408 
copy_or_move(ne_session * sess,int is_move,int overwrite,int depth,const char * src,const char * dest)409 static int copy_or_move(ne_session *sess, int is_move, int overwrite,
410 			int depth, const char *src, const char *dest)
411 {
412     ne_request *req = ne_request_create( sess, is_move?"MOVE":"COPY", src );
413 
414     /* 2518 S8.9.2 says only use Depth: infinity with MOVE. */
415     if (!is_move) {
416 	ne_add_depth_header(req, depth);
417     }
418 
419 #ifdef NE_HAVE_DAV
420     if (is_move) {
421 	ne_lock_using_resource(req, src, NE_DEPTH_INFINITE);
422     }
423     ne_lock_using_resource(req, dest, NE_DEPTH_INFINITE);
424     /* And we need to be able to add members to the destination's parent */
425     ne_lock_using_parent(req, dest);
426 #endif
427 
428     if (ne_get_session_flag(sess, NE_SESSFLAG_RFC4918)) {
429         ne_add_request_header(req, "Destination", dest);
430     }
431     else {
432         ne_print_request_header(req, "Destination", "%s://%s%s",
433                                 ne_get_scheme(sess),
434                                 ne_get_server_hostport(sess), dest);
435     }
436 
437     ne_add_request_header(req, "Overwrite", overwrite?"T":"F");
438 
439     return ne_simple_request(sess, req);
440 }
441 
ne_copy(ne_session * sess,int overwrite,int depth,const char * src,const char * dest)442 int ne_copy(ne_session *sess, int overwrite, int depth,
443 	     const char *src, const char *dest)
444 {
445     return copy_or_move(sess, 0, overwrite, depth, src, dest);
446 }
447 
ne_move(ne_session * sess,int overwrite,const char * src,const char * dest)448 int ne_move(ne_session *sess, int overwrite,
449 	     const char *src, const char *dest)
450 {
451     return copy_or_move(sess, 1, overwrite, 0, src, dest);
452 }
453 
454 /* Deletes the specified resource. (and in only two lines of code!) */
ne_delete(ne_session * sess,const char * uri)455 int ne_delete(ne_session *sess, const char *uri)
456 {
457     ne_request *req = ne_request_create(sess, "DELETE", uri);
458 
459 #ifdef NE_HAVE_DAV
460     ne_lock_using_resource(req, uri, NE_DEPTH_INFINITE);
461     ne_lock_using_parent(req, uri);
462 #endif
463 
464     /* joe: I asked on the DAV WG list about whether we might get a
465      * 207 error back from a DELETE... conclusion, you shouldn't if
466      * you don't send the Depth header, since we might be an HTTP/1.1
467      * client and a 2xx response indicates success to them.  But
468      * it's all a bit unclear. In any case, DAV servers today do
469      * return 207 to DELETE even if we don't send the Depth header.
470      * So we handle 207 errors appropriately. */
471 
472     return ne_simple_request(sess, req);
473 }
474 
ne_mkcol(ne_session * sess,const char * uri)475 int ne_mkcol(ne_session *sess, const char *uri)
476 {
477     ne_request *req;
478     char *real_uri;
479     int ret;
480 
481     if (ne_path_has_trailing_slash(uri)) {
482 	real_uri = ne_strdup(uri);
483     } else {
484 	real_uri = ne_concat(uri, "/", NULL);
485     }
486 
487     req = ne_request_create(sess, "MKCOL", real_uri);
488 
489 #ifdef NE_HAVE_DAV
490     ne_lock_using_resource(req, real_uri, 0);
491     ne_lock_using_parent(req, real_uri);
492 #endif
493 
494     ret = ne_simple_request(sess, req);
495 
496     ne_free(real_uri);
497 
498     return ret;
499 }
500 
501 #endif /* NE_HAVE_DAV */
502