├── README.md
├── davclient.js
├── test.html
└── test_client.js
/README.md:
--------------------------------------------------------------------------------
1 | jsdavclient
2 | ===========
3 | Low-level JavaScript WebDAV client implementation written in Javascript.
4 |
5 | Supports following methods: MKCOL, MOVE, GET, DELETE, COPY, PUT, PROPFIND
6 |
7 |
--------------------------------------------------------------------------------
/davclient.js:
--------------------------------------------------------------------------------
1 | /*
2 | davclient.js - Low-level JavaScript WebDAV client implementation
3 |
4 | Supports following methods: MKCOL, MOVE, GET, DELETE, COPY, PUT, PROPFIND
5 |
6 | Copyright (C) Sven vogler
7 | email s.vogler@gmx.de
8 |
9 | Inspired by implementation of Guido Wesdorp
10 | http://debris.demon.nl/projects/davclient.js/
11 |
12 | This program is free software; you can redistribute it and/or modify
13 | it under the terms of the GNU General Public License as published by
14 | the Free Software Foundation; either version 2 of the License, or
15 | (at your option) any later version.
16 |
17 | This program is distributed in the hope that it will be useful,
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | GNU General Public License for more details.
21 |
22 | You should have received a copy of the GNU General Public License
23 | along with this program; if not, write to the Free Software
24 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 |
26 | */
27 |
28 | var global = this;
29 |
30 | global.string = new function() {
31 | var string = this;
32 |
33 | this.strip = function strip(s) {
34 | /* returns a string with all leading and trailing whitespace removed */
35 | var stripspace = /^\s*([\s\S]*?)\s*$/;
36 | return stripspace.exec(s)[1];
37 | };
38 |
39 | this.deentitize = function deentitize(s) {
40 | /* convert all standard XML entities to the corresponding characters */
41 | // first numbered entities
42 | var numberedreg = /(x?)([a-f0-9]{2,});/ig;
43 | while (true) {
44 | var match = numberedreg.exec(s);
45 | if (!match) {
46 | break;
47 | };
48 | var value = match[2];
49 | var base = 10;
50 | if (match[1]) {
51 | base = 16;
52 | };
53 | value = String.fromCharCode(parseInt(value, base));
54 | s = s.replace(new RegExp(match[0], 'g'), value);
55 | };
56 | // and standard ones
57 | s = s.replace(/>/g, '>');
58 | s = s.replace(/</g, '<');
59 | s = s.replace(/'/g, "'");
60 | s = s.replace(/"/g, '"');
61 | s = s.replace(/&/g, '&');
62 | s = s.replace(/ /g, "");
63 |
64 | // remove the xml declaration as E4X cannot parse it
65 | s = s.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
66 |
67 | return s;
68 | };
69 |
70 |
71 | this.encodeBase64 = function encodeBase64(input) {
72 | return base64.encode(input);
73 | }
74 |
75 | var base64 = {
76 | // private property
77 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
78 |
79 | // public method for encoding
80 | encode : function (input) {
81 | var output = "";
82 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
83 | var i = 0;
84 |
85 | input = base64._utf8_encode(input);
86 |
87 | while (i < input.length) {
88 |
89 | chr1 = input.charCodeAt(i++);
90 | chr2 = input.charCodeAt(i++);
91 | chr3 = input.charCodeAt(i++);
92 |
93 | enc1 = chr1 >> 2;
94 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
95 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
96 | enc4 = chr3 & 63;
97 |
98 | if (isNaN(chr2)) {
99 | enc3 = enc4 = 64;
100 | } else if (isNaN(chr3)) {
101 | enc4 = 64;
102 | }
103 |
104 | output = output +
105 | base64._keyStr.charAt(enc1) + base64._keyStr.charAt(enc2) +
106 | base64._keyStr.charAt(enc3) + base64._keyStr.charAt(enc4);
107 |
108 | }
109 |
110 | return output;
111 | },
112 |
113 |
114 | // private method for UTF-8 encoding
115 | _utf8_encode : function (string) {
116 | string = string.replace(/\r\n/g,"\n");
117 | var utftext = "";
118 |
119 | for (var n = 0; n < string.length; n++) {
120 |
121 | var c = string.charCodeAt(n);
122 |
123 | if (c < 128) {
124 | utftext += String.fromCharCode(c);
125 | }
126 | else if((c > 127) && (c < 2048)) {
127 | utftext += String.fromCharCode((c >> 6) | 192);
128 | utftext += String.fromCharCode((c & 63) | 128);
129 | }
130 | else {
131 | utftext += String.fromCharCode((c >> 12) | 224);
132 | utftext += String.fromCharCode(((c >> 6) & 63) | 128);
133 | utftext += String.fromCharCode((c & 63) | 128);
134 | }
135 |
136 | }
137 |
138 | return utftext;
139 | }
140 | }
141 | };
142 |
143 | global.davlib = new function() {
144 | /* WebDAV for JavaScript
145 |
146 | This is a library containing a low-level and (if loaded, see
147 | 'davfs.js') a high-level API for working with WebDAV capable servers
148 | from JavaScript.
149 |
150 | Quick example of the low-level interface:
151 |
152 | var client = new davlib.DavClient();
153 | client.initialize();
154 |
155 | function alertContent(status, statusstring, content) {
156 | if (status != 200) {
157 | alert('error: ' + statusstring);
158 | return;
159 | };
160 | alert('content received: ' + content);
161 | };
162 |
163 | client.GET('/foo/bar.txt', alertContent);
164 |
165 | Quick example of the high-level interface:
166 |
167 | var fs = new davlib.DavFS();
168 | fs.initialize();
169 |
170 | function alertContent(error, content) {
171 | if (error) {
172 | alert('error: ' + error);
173 | return;
174 | };
175 | alert('content: ' + content);
176 | };
177 |
178 | fs.read('/foo/bar.txt', alertContent);
179 |
180 | */
181 | var davlib = this;
182 |
183 | this.DEBUG = 0;
184 |
185 | this.STATUS_CODES = {
186 | '100': 'Continue',
187 | '101': 'Switching Protocols',
188 | '102': 'Processing',
189 | '200': 'OK',
190 | '201': 'Created',
191 | '202': 'Accepted',
192 | '203': 'None-Authoritive Information',
193 | '204': 'No Content',
194 | '205': 'Reset Content',
195 | '206': 'Partial Content',
196 | '207': 'Multi-Status',
197 | '300': 'Multiple Choices',
198 | '301': 'Moved Permanently',
199 | '302': 'Found',
200 | '303': 'See Other',
201 | '304': 'Not Modified',
202 | '305': 'Use Proxy',
203 | '307': 'Redirect',
204 | '400': 'Bad Request',
205 | '401': 'Unauthorized',
206 | '402': 'Payment Required',
207 | '403': 'Forbidden',
208 | '404': 'Not Found',
209 | '405': 'Method Not Allowed',
210 | '406': 'Not Acceptable',
211 | '407': 'Proxy Authentication Required',
212 | '408': 'Request Time-out',
213 | '409': 'Conflict',
214 | '410': 'Gone',
215 | '411': 'Length Required',
216 | '412': 'Precondition Failed',
217 | '413': 'Request Entity Too Large',
218 | '414': 'Request-URI Too Large',
219 | '415': 'Unsupported Media Type',
220 | '416': 'Requested range not satisfiable',
221 | '417': 'Expectation Failed',
222 | '422': 'Unprocessable Entity',
223 | '423': 'Locked',
224 | '424': 'Failed Dependency',
225 | '500': 'Internal Server Error',
226 | '501': 'Not Implemented',
227 | '502': 'Bad Gateway',
228 | '503': 'Service Unavailable',
229 | '504': 'Gateway Time-out',
230 | '505': 'HTTP Version not supported',
231 | '507': 'Insufficient Storage'
232 | };
233 |
234 | this.DavClient = function() {
235 | /* Low level (subset of) WebDAV client implementation
236 |
237 | Basically what one would expect from a basic DAV client, it
238 | provides a method for every HTTP method used in basic DAV, it
239 | parses PROPFIND requests to handy JS structures and accepts
240 | similar structures for PROPPATCH.
241 |
242 | Requests are handled asynchronously, so instead of waiting until
243 | the response is sent back from the server and returning the
244 | value directly, a handler is registered that is called when
245 | the response is available and the method that sent the request
246 | is ended. For that reason all request methods accept a 'handler'
247 | argument, which will be called (with 3 arguments: statuscode,
248 | statusstring and content (the latter only where appropriate))
249 | when the request is handled by the browser.
250 | The reason for this choice is that Mozilla sometimes freezes
251 | when using XMLHttpRequest for synchronous requests.
252 |
253 | The only 'public' methods on the class are the 'initialize'
254 | method, that needs to be called first thing after instantiating
255 | a DavClient object, and the methods that have a name similar to
256 | an HTTP method (GET, PUT, etc.). The latter all get at least a
257 | 'path' argument, a 'handler' argument and a 'context' argument:
258 |
259 | 'path' - an absolute path to the target resource
260 | 'handler' - a function or method that will be called once
261 | the request has finished (see below)
262 | 'context' - the context used to call the handler, the
263 | 'this' variable inside methods, so usually the
264 | object (instance) the handler is bound to (ignore
265 | when the handler is a function)
266 |
267 | All handlers are called with the same 3 arguments:
268 |
269 | 'status' - the HTTP status code
270 | 'statusstring' - a string representation (see STATUS_CODES
271 | array above) of the status code
272 | 'content' - can be a number of different things:
273 | * when there was an error in a method that targets
274 | a single resource, this contains the error body
275 | * when there was an error in a method that targets
276 | a set of resources (multi-status) it contains
277 | a Root object instance (see below) that contains
278 | the error messages of all the objects
279 | * if the method was GET and there was no error, it
280 | will contain the contents of the resource
281 | * if the method was PROPFIND and there was no error,
282 | it will contain a Root object (see below) that
283 | contains the properties of all the resources
284 | targeted
285 | * if there was no error and there is no content to
286 | return, it will contain null
287 | 'headers' - a mapping (associative array) from lowercase header
288 | name to value (string)
289 |
290 | Basic usage example:
291 |
292 | function handler(status, statusstring, content, headers) {
293 | if (content) {
294 | if (status != '200' && status != '204') {
295 | if (status == '207') {
296 | alert('not going to show multi-status ' +
297 | here...');
298 | };
299 | alert('Error: ' + statusstring);
300 | } else {
301 | alert('Content: ' + content);
302 | };
303 | };
304 | };
305 |
306 | var dc = new DavClient();
307 | dc.initialize('localhost');
308 |
309 | // create a directory
310 | dc.MKCOL('/foo', handler);
311 |
312 | // create a file and save some contents
313 | dc.PUT('/foo/bar.txt', 'baz?', handler);
314 |
315 | // load and alert it (alert happens in the handler)
316 | dc.GET('/foo/bar.txt', handler);
317 |
318 | // delete the dir
319 | dc.DELETE('/foo', handler);
320 |
321 | For detailed information about the HTTP methods and how they
322 | can/should be used in a DAV context, see http://www.webdav.org.
323 |
324 | If you have questions, bug reports, or patches, please file a report
325 | on https://github.com/svogler/jsdavclient
326 |
327 | */
328 | };
329 |
330 | this.DavClient.prototype.initialize = function(host, port, protocol, username, password) {
331 | /* the 'constructor' (needs to be called explicitly!!)
332 |
333 | host - the host name or IP
334 | port - HTTP port of the host (optional, defaults to 80)
335 | protocol - protocol part of URLs (optional, defaults to http)
336 | username - the username for authorization (only Basic auth is supported at that time)
337 | password - the password to use
338 | */
339 | this.host = host || location.hostname;
340 | this.port = port || location.port || 443;
341 | this.protocol = (protocol || location.protocol.substr(0, location.protocol.length - 1 ) || 'https');
342 | this.username = username || null;
343 | this.password = password || null;
344 |
345 | this.request = null;
346 | };
347 |
348 | this.DavClient.prototype.OPTIONS = function(path, handler, context) {
349 | /* perform an OPTIONS request
350 |
351 | find out which HTTP methods are understood by the server
352 | */
353 | // XXX how does this work with * paths?
354 | var request = this._getRequest('OPTIONS', path, handler, context);
355 | request.send('');
356 | };
357 |
358 | this.DavClient.prototype.GET = function(path, handler, context) {
359 | /* perform a GET request
360 |
361 | retrieve the contents of a resource
362 | */
363 | var request = this._getRequest('GET', path, handler, context);
364 | request.send('');
365 | };
366 |
367 | this.DavClient.prototype.PUT = function(path, content, handler,
368 | context, locktoken) {
369 | /* perform a PUT request
370 |
371 | save the contents of a resource to the server
372 |
373 | 'content' - the contents of the resource
374 | */
375 | var request = this._getRequest('PUT', path, handler, context);
376 | request.setRequestHeader("Content-type", "text/xml,charset=UTF-8");
377 | if (locktoken) {
378 | request.setRequestHeader('If', '<' + locktoken + '>');
379 | };
380 | request.send(content);
381 | };
382 |
383 | this.DavClient.prototype.PROPFIND = function(path, handler, context, depth) {
384 | /* perform a PROPFIND request
385 |
386 | read the metadata of a resource (optionally including its children)
387 |
388 | 'depth' - control recursion depth, default 0 (only returning the
389 | properties for the resource itself)
390 | */
391 | var request = this._getRequest('PROPFIND', path, handler, context);
392 | depth = depth || 0;
393 | request.setRequestHeader('Depth', depth);
394 | request.setRequestHeader('Content-type', 'text/xml; charset=UTF-8');
395 | // XXX maybe we want to change this to allow getting selected props
396 | var xml = '' +
397 | '