Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / rest / Webdav.java @ af6aa461

History | View | Annotate | Download (112.6 kB)

1
/*
2
 * Copyright 2007, 2008, 2009 Electronic Business Systems Ltd.
3
 *
4
 * This file is part of GSS.
5
 *
6
 * GSS 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 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GSS 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 GSS.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
package gr.ebs.gss.server.rest;
20

    
21
import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import gr.ebs.gss.common.exceptions.DuplicateNameException;
23
import gr.ebs.gss.common.exceptions.GSSIOException;
24
import gr.ebs.gss.common.exceptions.InsufficientPermissionsException;
25
import gr.ebs.gss.common.exceptions.ObjectNotFoundException;
26
import gr.ebs.gss.common.exceptions.QuotaExceededException;
27
import gr.ebs.gss.common.exceptions.RpcException;
28
import gr.ebs.gss.server.domain.AuditInfo;
29
import gr.ebs.gss.server.domain.FileBody;
30
import gr.ebs.gss.server.domain.FileHeader;
31
import gr.ebs.gss.server.domain.Folder;
32
import gr.ebs.gss.server.domain.User;
33
import gr.ebs.gss.server.ejb.ExternalAPI;
34
import gr.ebs.gss.server.ejb.TransactionHelper;
35

    
36
import java.io.BufferedInputStream;
37
import java.io.ByteArrayInputStream;
38
import java.io.ByteArrayOutputStream;
39
import java.io.File;
40
import java.io.FileInputStream;
41
import java.io.IOException;
42
import java.io.InputStream;
43
import java.io.InputStreamReader;
44
import java.io.OutputStreamWriter;
45
import java.io.PrintWriter;
46
import java.io.RandomAccessFile;
47
import java.io.Reader;
48
import java.io.StringReader;
49
import java.io.StringWriter;
50
import java.io.UnsupportedEncodingException;
51
import java.io.Writer;
52
import java.net.URLDecoder;
53
import java.security.MessageDigest;
54
import java.security.NoSuchAlgorithmException;
55
import java.text.SimpleDateFormat;
56
import java.util.ArrayList;
57
import java.util.Date;
58
import java.util.Enumeration;
59
import java.util.Hashtable;
60
import java.util.Iterator;
61
import java.util.List;
62
import java.util.Locale;
63
import java.util.Stack;
64
import java.util.StringTokenizer;
65
import java.util.TimeZone;
66
import java.util.Vector;
67
import java.util.concurrent.Callable;
68

    
69
import javax.naming.Context;
70
import javax.naming.InitialContext;
71
import javax.naming.NamingException;
72
import javax.rmi.PortableRemoteObject;
73
import javax.servlet.ServletContext;
74
import javax.servlet.ServletException;
75
import javax.servlet.ServletOutputStream;
76
import javax.servlet.UnavailableException;
77
import javax.servlet.http.HttpServlet;
78
import javax.servlet.http.HttpServletRequest;
79
import javax.servlet.http.HttpServletResponse;
80
import javax.xml.parsers.DocumentBuilder;
81
import javax.xml.parsers.DocumentBuilderFactory;
82
import javax.xml.parsers.ParserConfigurationException;
83

    
84
import gr.ebs.gss.server.rest.LockInfo;
85
import gr.ebs.gss.server.rest.WebdavStatus;
86
import org.apache.commons.httpclient.HttpStatus;
87
import org.apache.commons.logging.Log;
88
import org.apache.commons.logging.LogFactory;
89
import org.w3c.dom.Document;
90
import org.w3c.dom.Element;
91
import org.w3c.dom.Node;
92
import org.w3c.dom.NodeList;
93
import org.xml.sax.EntityResolver;
94
import org.xml.sax.InputSource;
95
import org.xml.sax.SAXException;
96

    
97
/**
98
 * The implementation of the WebDAV service.
99
 *
100
 * @author past
101
 */
102
public class Webdav extends HttpServlet {
103

    
104
        /**
105
         * The request attribute containing the user who owns the requested
106
         * namespace.
107
         */
108
        protected static final String OWNER_ATTRIBUTE = "owner";
109

    
110
        /**
111
         * The request attribute containing the user making the request.
112
         */
113
        protected static final String USER_ATTRIBUTE = "user";
114

    
115
        /**
116
         * The logger.
117
         */
118
        private static Log logger = LogFactory.getLog(Webdav.class);
119

    
120
        /**
121
     *
122
     */
123
        protected static final String METHOD_GET = "GET";
124

    
125
        /**
126
     *
127
     */
128
        protected static final String METHOD_POST = "POST";
129

    
130
        /**
131
     *
132
     */
133
        protected static final String METHOD_PUT = "PUT";
134

    
135
        /**
136
     *
137
     */
138
        protected static final String METHOD_DELETE = "DELETE";
139

    
140
        /**
141
     *
142
     */
143
        protected static final String METHOD_HEAD = "HEAD";
144

    
145
        /**
146
     *
147
     */
148
        private static final String METHOD_OPTIONS = "OPTIONS";
149

    
150
        /**
151
     *
152
     */
153
        private static final String METHOD_PROPFIND = "PROPFIND";
154

    
155
        /**
156
     *
157
     */
158
        private static final String METHOD_PROPPATCH = "PROPPATCH";
159

    
160
        /**
161
     *
162
     */
163
        private static final String METHOD_MKCOL = "MKCOL";
164

    
165
        /**
166
     *
167
     */
168
        private static final String METHOD_COPY = "COPY";
169

    
170
        /**
171
     *
172
     */
173
        private static final String METHOD_MOVE = "MOVE";
174

    
175
        /**
176
     *
177
     */
178
        private static final String METHOD_LOCK = "LOCK";
179

    
180
        /**
181
     *
182
     */
183
        private static final String METHOD_UNLOCK = "UNLOCK";
184

    
185
        /**
186
         * Default depth is infinite.
187
         */
188
        static final int INFINITY = 3; // To limit tree browsing a bit
189

    
190
        /**
191
         * PROPFIND - Specify a property mask.
192
         */
193
        private static final int FIND_BY_PROPERTY = 0;
194

    
195
        /**
196
         * PROPFIND - Display all properties.
197
         */
198
        private static final int FIND_ALL_PROP = 1;
199

    
200
        /**
201
         * PROPFIND - Return property names.
202
         */
203
        private static final int FIND_PROPERTY_NAMES = 2;
204

    
205
        /**
206
         * Default namespace.
207
         */
208
        private static final String DEFAULT_NAMESPACE = "DAV:";
209

    
210
        /**
211
         * Create a new lock.
212
         */
213
        private static final int LOCK_CREATION = 0;
214

    
215
        /**
216
         * Refresh lock.
217
         */
218
        private static final int LOCK_REFRESH = 1;
219

    
220
        /**
221
         * Default lock timeout value.
222
         */
223
        private static final int DEFAULT_TIMEOUT = 3600;
224

    
225
        /**
226
         * Maximum lock timeout.
227
         */
228
        private static final int MAX_TIMEOUT = 604800;
229

    
230
        /**
231
         * Size of file transfer buffer in bytes.
232
         */
233
        private static final int BUFFER_SIZE = 4096;
234

    
235
        /**
236
         * The output buffer size to use when serving resources.
237
         */
238
        protected int output = 2048;
239

    
240
        /**
241
         * The input buffer size to use when serving resources.
242
         */
243
        private int input = 2048;
244

    
245
        /**
246
         * MIME multipart separation string
247
         */
248
        protected static final String mimeSeparation = "GSS_MIME_BOUNDARY";
249

    
250
        /**
251
         * Simple date format for the creation date ISO representation (partial).
252
         */
253
        private static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
254

    
255
        /**
256
         * HTTP date format.
257
         */
258
        private static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
259

    
260
        /**
261
         * Array containing the safe characters set.
262
         */
263
        private static URLEncoder urlEncoder;
264

    
265
        /**
266
         * File encoding to be used when reading static files. If none is specified
267
         * the platform default is used.
268
         */
269
        private String fileEncoding = null;
270

    
271
        /**
272
         * The style sheet for displaying the directory listings.
273
         */
274
        private static final String GSS_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}";
275

    
276
        /**
277
         * Secret information used to generate reasonably secure lock ids.
278
         */
279
        private String secret = "gss-webdav";
280

    
281

    
282
        /**
283
         * Full range marker.
284
         */
285
        protected static ArrayList FULL = new ArrayList();
286

    
287
        /**
288
         * MD5 message digest provider.
289
         */
290
        protected static MessageDigest md5Helper;
291

    
292
        /**
293
         * The MD5 helper object for this class.
294
         */
295
        protected static final MD5Encoder md5Encoder = new MD5Encoder();
296

    
297
        /**
298
         * GMT timezone - all HTTP dates are on GMT
299
         */
300
        static {
301
                creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
302
                urlEncoder = new URLEncoder();
303
                urlEncoder.addSafeCharacter('-');
304
                urlEncoder.addSafeCharacter('_');
305
                urlEncoder.addSafeCharacter('.');
306
                urlEncoder.addSafeCharacter('*');
307
                urlEncoder.addSafeCharacter('/');
308
        }
309

    
310
        @Override
311
        public void init() throws ServletException {
312
                if (getServletConfig().getInitParameter("input") != null)
313
                        input = Integer.parseInt(getServletConfig().getInitParameter("input"));
314

    
315
                if (getServletConfig().getInitParameter("output") != null)
316
                        output = Integer.parseInt(getServletConfig().getInitParameter("output"));
317

    
318
                fileEncoding = getServletConfig().getInitParameter("fileEncoding");
319

    
320
                // Sanity check on the specified buffer sizes
321
                if (input < 256)
322
                        input = 256;
323
                if (output < 256)
324
                        output = 256;
325
                if (logger.isDebugEnabled())
326
                        logger.debug("Input buffer size=" + input + ", output buffer size=" + output);
327

    
328
                if (getServletConfig().getInitParameter("secret") != null)
329
                        secret = getServletConfig().getInitParameter("secret");
330

    
331
                // Load the MD5 helper used to calculate signatures.
332
                try {
333
                        md5Helper = MessageDigest.getInstance("MD5");
334
                } catch (NoSuchAlgorithmException e) {
335
                        throw new UnavailableException("No MD5");
336
                }
337
        }
338

    
339
        /**
340
         * A helper method that retrieves a reference to the ExternalAPI bean and
341
         * stores it for future use.
342
         *
343
         * @return an ExternalAPI instance
344
         * @throws RpcException in case an error occurs
345
         */
346
        protected ExternalAPI getService() throws RpcException {
347
                try {
348
                        final Context ctx = new InitialContext();
349
                        final Object ref = ctx.lookup(getConfiguration().getString("externalApiPath"));
350
                        return (ExternalAPI) PortableRemoteObject.narrow(ref, ExternalAPI.class);
351
                } catch (final NamingException e) {
352
                        logger.error("Unable to retrieve the ExternalAPI EJB", e);
353
                        throw new RpcException("An error occurred while contacting the naming service");
354
                }
355
        }
356

    
357
        private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
358
                try {
359
                        new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
360
                                @Override
361
                                public Void call() throws Exception {
362
                                        getService().updateAccounting(user, date, bandwidthDiff);
363
                                        return null;
364
                                }
365
                        });
366
                } catch (RuntimeException e) {
367
                        throw e;
368
                } catch (Exception e) {
369
                        // updateAccounting() doesn't throw any checked exceptions
370
                        assert false;
371
                }
372
        }
373

    
374
        @Override
375
        public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
376
                String method = request.getMethod();
377

    
378
                if (logger.isDebugEnabled()) {
379
                        String path = request.getPathInfo();
380
                        if (path == null)
381
                                path = request.getServletPath();
382
                        if (path == null || path.equals(""))
383
                                path = "/";
384
                        logger.debug("[" + method + "] " + path);
385
                }
386

    
387
                try {
388
                        User user = null;
389
                        if (request.getUserPrincipal() != null) { // Let unauthenticated
390
                                                                                                                // OPTIONS go through;
391
                                                                                                                // all others will be
392
                                                                                                                // blocked by
393
                                                                                                                // authentication anyway
394
                                                                                                                // before we get here.
395
                                user = getService().findUser(request.getUserPrincipal().getName());
396
                                if (user == null) {
397
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
398
                                        return;
399
                                }
400
                        }
401
                        request.setAttribute(USER_ATTRIBUTE, user);
402
                        request.setAttribute(OWNER_ATTRIBUTE, user);
403
                } catch (RpcException e) {
404
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
405
                        return;
406
                }
407
                if (method.equals(METHOD_GET))
408
                        doGet(request, response);
409
                else if (method.equals(METHOD_POST))
410
                        doPost(request, response);
411
                else if (method.equals(METHOD_PUT))
412
                        doPut(request, response);
413
                else if (method.equals(METHOD_DELETE))
414
                        doDelete(request, response);
415
                else if (method.equals(METHOD_HEAD))
416
                        doHead(request, response);
417
                else if (method.equals(METHOD_PROPFIND))
418
                        doPropfind(request, response);
419
                else if (method.equals(METHOD_PROPPATCH))
420
                        doProppatch(request, response);
421
                else if (method.equals(METHOD_MKCOL))
422
                        doMkcol(request, response);
423
                else if (method.equals(METHOD_COPY))
424
                        doCopy(request, response);
425
                else if (method.equals(METHOD_MOVE))
426
                        doMove(request, response);
427
                else if (method.equals(METHOD_LOCK))
428
                        doLock(request, response);
429
                else if (method.equals(METHOD_UNLOCK))
430
                        doUnlock(request, response);
431
                else if (method.equals(METHOD_OPTIONS))
432
                        doOptions(request, response);
433
                else
434
                        // DefaultServlet processing for TRACE, etc.
435
                        super.service(request, response);
436
        }
437

    
438
        @Override
439
        protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
440
                resp.addHeader("DAV", "1,2");
441
                StringBuffer methodsAllowed = new StringBuffer();
442
                try {
443
                        methodsAllowed = determineMethodsAllowed(req);
444
                } catch (RpcException e) {
445
                        resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
446
                        return;
447
                }
448
                resp.addHeader("Allow", methodsAllowed.toString());
449
                resp.addHeader("MS-Author-Via", "DAV");
450
        }
451

    
452
        /**
453
         * Implement the PROPFIND method.
454
         *
455
         * @param req the HTTP request
456
         * @param resp the HTTP response
457
         * @throws ServletException
458
         * @throws IOException
459
         */
460
        private void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
461
                String path = getRelativePath(req);
462
                if (path.endsWith("/") && !path.equals("/"))
463
                        path = path.substring(0, path.length() - 1);
464

    
465
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
466
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
467
                        return;
468
                }
469

    
470
                // Properties which are to be displayed.
471
                Vector<String> properties = null;
472
                // Propfind depth
473
                int depth = INFINITY;
474
                // Propfind type
475
                int type = FIND_ALL_PROP;
476

    
477
                String depthStr = req.getHeader("Depth");
478

    
479
                if (depthStr == null)
480
                        depth = INFINITY;
481
                else if (depthStr.equals("0"))
482
                        depth = 0;
483
                else if (depthStr.equals("1"))
484
                        depth = 1;
485
                else if (depthStr.equals("infinity"))
486
                        depth = INFINITY;
487

    
488
                Node propNode = null;
489

    
490
                if (req.getInputStream().available() > 0) {
491
                        DocumentBuilder documentBuilder = getDocumentBuilder();
492

    
493
                        try {
494
                                Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
495

    
496
                                // Get the root element of the document
497
                                Element rootElement = document.getDocumentElement();
498
                                NodeList childList = rootElement.getChildNodes();
499

    
500
                                for (int i = 0; i < childList.getLength(); i++) {
501
                                        Node currentNode = childList.item(i);
502
                                        switch (currentNode.getNodeType()) {
503
                                                case Node.TEXT_NODE:
504
                                                        break;
505
                                                case Node.ELEMENT_NODE:
506
                                                        if (currentNode.getNodeName().endsWith("prop")) {
507
                                                                type = FIND_BY_PROPERTY;
508
                                                                propNode = currentNode;
509
                                                        }
510
                                                        if (currentNode.getNodeName().endsWith("propname"))
511
                                                                type = FIND_PROPERTY_NAMES;
512
                                                        if (currentNode.getNodeName().endsWith("allprop"))
513
                                                                type = FIND_ALL_PROP;
514
                                                        break;
515
                                        }
516
                                }
517
                        } catch (SAXException e) {
518
                                // Something went wrong - use the defaults.
519
                                if (logger.isDebugEnabled())
520
                                        logger.debug(e.getMessage());
521
                        } catch (IOException e) {
522
                                // Something went wrong - use the defaults.
523
                                if (logger.isDebugEnabled())
524
                                        logger.debug(e.getMessage());
525
                        }
526
                }
527

    
528
                if (type == FIND_BY_PROPERTY) {
529
                        properties = new Vector<String>();
530
                        NodeList childList = propNode.getChildNodes();
531

    
532
                        for (int i = 0; i < childList.getLength(); i++) {
533
                                Node currentNode = childList.item(i);
534
                                switch (currentNode.getNodeType()) {
535
                                        case Node.TEXT_NODE:
536
                                                break;
537
                                        case Node.ELEMENT_NODE:
538
                                                String nodeName = currentNode.getNodeName();
539
                                                String propertyName = null;
540
                                                if (nodeName.indexOf(':') != -1)
541
                                                        propertyName = nodeName.substring(nodeName.indexOf(':') + 1);
542
                                                else
543
                                                        propertyName = nodeName;
544
                                                // href is a live property which is handled differently
545
                                                properties.addElement(propertyName);
546
                                                break;
547
                                }
548
                        }
549
                }
550
                User user = getUser(req);
551
                boolean exists = true;
552
                Object object = null;
553
                try {
554
                        object = getService().getResourceAtPath(user.getId(), path, true);
555
                } catch (ObjectNotFoundException e) {
556
                        exists = false;
557
                } catch (RpcException e) {
558
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
559
                        return;
560
                }
561
                if (!exists) {
562
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
563
                        return;
564
                }
565
                resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
566
                resp.setContentType("text/xml; charset=UTF-8");
567
                // Create multistatus object
568
                XMLWriter generatedXML = new XMLWriter(resp.getWriter());
569
                generatedXML.writeXMLHeader();
570
                generatedXML.writeElement(null, "D:multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
571
                if (depth == 0)
572
                        parseProperties(req, generatedXML, path, type, properties, object);
573
                else {
574
                        // The stack always contains the object of the current level
575
                        Stack<String> stack = new Stack<String>();
576
                        stack.push(path);
577

    
578
                        // Stack of the objects one level below
579
                        Stack<String> stackBelow = new Stack<String>();
580
                        while (!stack.isEmpty() && depth >= 0) {
581
                                String currentPath = stack.pop();
582
                                try {
583
                                        object = getService().getResourceAtPath(user.getId(), currentPath, true);
584
                                } catch (ObjectNotFoundException e) {
585
                                        continue;
586
                                } catch (RpcException e) {
587
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
588
                                        return;
589
                                }
590
                                parseProperties(req, generatedXML, currentPath, type, properties, object);
591
                                if (object instanceof Folder && depth > 0) {
592
                                        Folder folderLocal = (Folder) object;
593
                                        // Retrieve the subfolders.
594
                                        List subfolders = folderLocal.getSubfolders();
595
                                        Iterator iter = subfolders.iterator();
596
                                        while (iter.hasNext()) {
597
                                                Folder f = (Folder) iter.next();
598
                                                String newPath = currentPath;
599
                                                if (!newPath.endsWith("/"))
600
                                                        newPath += "/";
601
                                                newPath += f.getName();
602
                                                stackBelow.push(newPath);
603
                                        }
604
                                        // Retrieve the files.
605
                                        List<FileHeader> files;
606
                                        try {
607
                                                files = getService().getFiles(user.getId(), folderLocal.getId(), true);
608
                                        } catch (ObjectNotFoundException e) {
609
                                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
610
                                                return;
611
                                        } catch (InsufficientPermissionsException e) {
612
                                                resp.sendError(HttpServletResponse.SC_FORBIDDEN, path);
613
                                                return;
614
                                        } catch (RpcException e) {
615
                                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
616
                                                return;
617
                                        }
618
                                        for (FileHeader file : files) {
619
                                                String newPath = currentPath;
620
                                                if (!newPath.endsWith("/"))
621
                                                        newPath += "/";
622
                                                newPath += file.getName();
623
                                                stackBelow.push(newPath);
624
                                        }
625
                                }
626
                                if (stack.isEmpty()) {
627
                                        depth--;
628
                                        stack = stackBelow;
629
                                        stackBelow = new Stack<String>();
630
                                }
631
                                generatedXML.sendData();
632
                        }
633
                }
634
                generatedXML.writeElement(null, "D:multistatus", XMLWriter.CLOSING);
635
                generatedXML.sendData();
636
        }
637

    
638
        /**
639
         * PROPPATCH Method.
640
         *
641
         * @param req the HTTP request
642
         * @param resp the HTTP response
643
         * @throws IOException if an error occurs while sending the response
644
         */
645
        private void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
646
                if (isLocked(req)) {
647
                        resp.sendError(WebdavStatus.SC_LOCKED);
648
                        return;
649
                }
650
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
651
        }
652

    
653
        @Override
654
        protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
655
                if (isLocked(req)) {
656
                        resp.sendError(WebdavStatus.SC_LOCKED);
657
                        return;
658
                }
659
                deleteResource(req, resp);
660
        }
661

    
662
        @Override
663
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
664
                // Serve the requested resource, including the data content
665
                try {
666
                        serveResource(req, resp, true);
667
                } catch (ObjectNotFoundException e) {
668
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
669
                        return;
670
                } catch (InsufficientPermissionsException e) {
671
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
672
                        return;
673
                } catch (RpcException e) {
674
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
675
                        return;
676
                }
677
        }
678

    
679
        @Override
680
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
681
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
682
        }
683

    
684
        @Override
685
        protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
686
                if (isLocked(req)) {
687
                        resp.sendError(WebdavStatus.SC_LOCKED);
688
                        return;
689
                }
690

    
691
                final User user = getUser(req);
692
                String path = getRelativePath(req);
693
                boolean exists = true;
694
                Object resource = null;
695
                FileHeader file = null;
696
                try {
697
                        resource = getService().getResourceAtPath(user.getId(), path, true);
698
                } catch (ObjectNotFoundException e) {
699
                        exists = false;
700
                } catch (RpcException e) {
701
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
702
                        return;
703
                }
704

    
705
                if (exists)
706
                        if (resource instanceof FileHeader)
707
                                file = (FileHeader) resource;
708
                        else {
709
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
710
                                return;
711
                        }
712
                boolean result = true;
713

    
714
                // Temporary content file used to support partial PUT.
715
                File contentFile = null;
716

    
717
                Range range = parseContentRange(req, resp);
718

    
719
                InputStream resourceInputStream = null;
720

    
721
                // Append data specified in ranges to existing content for this
722
                // resource - create a temporary file on the local filesystem to
723
                // perform this operation.
724
                // Assume just one range is specified for now
725
                if (range != null) {
726
                        try {
727
                                contentFile = executePartialPut(req, range, path);
728
                        } catch (RpcException e) {
729
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
730
                                return;
731
                        } catch (ObjectNotFoundException e) {
732
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
733
                                return;
734
                        } catch (InsufficientPermissionsException e) {
735
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
736
                                return;
737
                        }
738
                        resourceInputStream = new FileInputStream(contentFile);
739
                } else
740
                        resourceInputStream = req.getInputStream();
741

    
742
                try {
743
                        Object parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
744
                        if (!(parent instanceof Folder)) {
745
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
746
                                return;
747
                        }
748
                        final Folder folderLocal = (Folder) parent;
749
                        final String name = getLastElement(path);
750
                        final String mimeType = getServletContext().getMimeType(name);
751
                File uploadedFile = null;
752
                try {
753
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
754
                        } catch (IOException ex) {
755
                                throw new GSSIOException(ex, false);
756
                        }
757
                        // FIXME: Add attributes
758
                        FileHeader fileLocal = null;
759
                        final FileHeader f = file;
760
                        final File uf = uploadedFile;
761
                        if (exists)
762
                                fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
763
                                        @Override
764
                                        public FileHeader call() throws Exception {
765
                                                return getService().updateFileContents(user.getId(), f.getId(), mimeType, uf.length(), uf.getAbsolutePath());
766
                                        }
767
                                });
768
                        else
769
                                fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
770
                                        @Override
771
                                        public FileHeader call() throws Exception {
772
                                                return getService().createFile(user.getId(), folderLocal.getId(), name, mimeType, uf.length(), uf.getAbsolutePath());
773
                                        }
774
                                });
775
                        updateAccounting(user, new Date(), fileLocal.getCurrentBody().getFileSize());
776
                } catch (ObjectNotFoundException e) {
777
                        result = false;
778
                } catch (InsufficientPermissionsException e) {
779
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
780
                        return;
781
                } catch (QuotaExceededException e) {
782
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
783
                        return;
784
                } catch (GSSIOException e) {
785
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
786
                        return;
787
                } catch (RpcException e) {
788
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
789
                        return;
790
                } catch (DuplicateNameException e) {
791
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
792
                        return;
793
                } catch (Exception e) {
794
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
795
                        return;
796
                }
797

    
798
                if (result) {
799
                        if (exists)
800
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
801
                        else
802
                                resp.setStatus(HttpServletResponse.SC_CREATED);
803
                } else
804
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
805

    
806
        }
807

    
808
        @Override
809
        protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
810
                // Serve the requested resource, without the data content
811
                try {
812
                        serveResource(req, resp, false);
813
                } catch (ObjectNotFoundException e) {
814
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
815
                        return;
816
                } catch (InsufficientPermissionsException e) {
817
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
818
                        return;
819
                } catch (RpcException e) {
820
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
821
                        return;
822
                }
823
        }
824

    
825
        /**
826
         * The UNLOCK method.
827
         *
828
         * @param req the HTTP request
829
         * @param resp the HTTP response
830
         * @throws IOException if an error occurs while sending the response
831
         */
832
        private void doUnlock(@SuppressWarnings("unused") HttpServletRequest req, HttpServletResponse resp) throws IOException {
833
                resp.setStatus(WebdavStatus.SC_NO_CONTENT);
834
        }
835

    
836
        /**
837
         * The LOCK method.
838
         *
839
         * @param req the HTTP request
840
         * @param resp the HTTP response
841
         * @throws IOException if an error occurs while sending the response
842
         * @throws ServletException
843
         */
844
        private void doLock(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
845
                LockInfo lock = new LockInfo();
846
                // Parsing lock request
847

    
848
                // Parsing depth header
849
                String depthStr = req.getHeader("Depth");
850
                if (depthStr == null)
851
                        lock.depth = INFINITY;
852
                else if (depthStr.equals("0"))
853
                        lock.depth = 0;
854
                else
855
                        lock.depth = INFINITY;
856

    
857
                // Parsing timeout header
858
                int lockDuration = DEFAULT_TIMEOUT;
859
                String lockDurationStr = req.getHeader("Timeout");
860
                if (lockDurationStr == null)
861
                        lockDuration = DEFAULT_TIMEOUT;
862
                else {
863
                        int commaPos = lockDurationStr.indexOf(",");
864
                        // If multiple timeouts, just use the first
865
                        if (commaPos != -1)
866
                                lockDurationStr = lockDurationStr.substring(0, commaPos);
867
                        if (lockDurationStr.startsWith("Second-"))
868
                                lockDuration = new Integer(lockDurationStr.substring(7)).intValue();
869
                        else if (lockDurationStr.equalsIgnoreCase("infinity"))
870
                                lockDuration = MAX_TIMEOUT;
871
                        else
872
                                try {
873
                                        lockDuration = new Integer(lockDurationStr).intValue();
874
                                } catch (NumberFormatException e) {
875
                                        lockDuration = MAX_TIMEOUT;
876
                                }
877
                        if (lockDuration == 0)
878
                                lockDuration = DEFAULT_TIMEOUT;
879
                        if (lockDuration > MAX_TIMEOUT)
880
                                lockDuration = MAX_TIMEOUT;
881
                }
882
                lock.expiresAt = System.currentTimeMillis() + lockDuration * 1000;
883

    
884
                int lockRequestType = LOCK_CREATION;
885
                Node lockInfoNode = null;
886
                DocumentBuilder documentBuilder = getDocumentBuilder();
887

    
888
                try {
889
                        Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
890
                        // Get the root element of the document
891
                        Element rootElement = document.getDocumentElement();
892
                        lockInfoNode = rootElement;
893
                } catch (IOException e) {
894
                        lockRequestType = LOCK_REFRESH;
895
                } catch (SAXException e) {
896
                        lockRequestType = LOCK_REFRESH;
897
                }
898

    
899
                if (lockInfoNode != null) {
900
                        // Reading lock information
901
                        NodeList childList = lockInfoNode.getChildNodes();
902
                        StringWriter strWriter = null;
903
                        DOMWriter domWriter = null;
904

    
905
                        Node lockScopeNode = null;
906
                        Node lockTypeNode = null;
907
                        Node lockOwnerNode = null;
908

    
909
                        for (int i = 0; i < childList.getLength(); i++) {
910
                                Node currentNode = childList.item(i);
911
                                switch (currentNode.getNodeType()) {
912
                                        case Node.TEXT_NODE:
913
                                                break;
914
                                        case Node.ELEMENT_NODE:
915
                                                String nodeName = currentNode.getNodeName();
916
                                                if (nodeName.endsWith("lockscope"))
917
                                                        lockScopeNode = currentNode;
918
                                                if (nodeName.endsWith("locktype"))
919
                                                        lockTypeNode = currentNode;
920
                                                if (nodeName.endsWith("owner"))
921
                                                        lockOwnerNode = currentNode;
922
                                                break;
923
                                }
924
                        }
925

    
926
                        if (lockScopeNode != null) {
927
                                childList = lockScopeNode.getChildNodes();
928
                                for (int i = 0; i < childList.getLength(); i++) {
929
                                        Node currentNode = childList.item(i);
930
                                        switch (currentNode.getNodeType()) {
931
                                                case Node.TEXT_NODE:
932
                                                        break;
933
                                                case Node.ELEMENT_NODE:
934
                                                        String tempScope = currentNode.getNodeName();
935
                                                        if (tempScope.indexOf(':') != -1)
936
                                                                lock.scope = tempScope.substring(tempScope.indexOf(':') + 1);
937
                                                        else
938
                                                                lock.scope = tempScope;
939
                                                        break;
940
                                        }
941
                                }
942
                                if (lock.scope == null)
943
                                        // Bad request
944
                                        resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
945
                        } else
946
                                // Bad request
947
                                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
948

    
949
                        if (lockTypeNode != null) {
950
                                childList = lockTypeNode.getChildNodes();
951
                                for (int i = 0; i < childList.getLength(); i++) {
952
                                        Node currentNode = childList.item(i);
953
                                        switch (currentNode.getNodeType()) {
954
                                                case Node.TEXT_NODE:
955
                                                        break;
956
                                                case Node.ELEMENT_NODE:
957
                                                        String tempType = currentNode.getNodeName();
958
                                                        if (tempType.indexOf(':') != -1)
959
                                                                lock.type = tempType.substring(tempType.indexOf(':') + 1);
960
                                                        else
961
                                                                lock.type = tempType;
962
                                                        break;
963
                                        }
964
                                }
965

    
966
                                if (lock.type == null)
967
                                        // Bad request
968
                                        resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
969
                        } else
970
                                // Bad request
971
                                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
972

    
973
                        if (lockOwnerNode != null) {
974
                                childList = lockOwnerNode.getChildNodes();
975
                                for (int i = 0; i < childList.getLength(); i++) {
976
                                        Node currentNode = childList.item(i);
977
                                        switch (currentNode.getNodeType()) {
978
                                                case Node.TEXT_NODE:
979
                                                        lock.owner += currentNode.getNodeValue();
980
                                                        break;
981
                                                case Node.ELEMENT_NODE:
982
                                                        strWriter = new StringWriter();
983
                                                        domWriter = new DOMWriter(strWriter, true);
984
                                                        domWriter.setQualifiedNames(false);
985
                                                        domWriter.print(currentNode);
986
                                                        lock.owner += strWriter.toString();
987
                                                        break;
988
                                        }
989
                                }
990

    
991
                                if (lock.owner == null)
992
                                        // Bad request
993
                                        resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
994
                        } else
995
                                lock.owner = new String();
996
                }
997

    
998
                String path = getRelativePath(req);
999
                lock.path = path;
1000
                User user = getUser(req);
1001
                boolean exists = true;
1002
                Object object = null;
1003
                try {
1004
                        object = getService().getResourceAtPath(user.getId(), path, true);
1005
                } catch (ObjectNotFoundException e) {
1006
                        exists = false;
1007
                } catch (RpcException e) {
1008
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1009
                        return;
1010
                }
1011

    
1012
                if (lockRequestType == LOCK_CREATION) {
1013
                        // Generating lock id
1014
                        String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" + lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" + lock.expiresAt + "-" + System.currentTimeMillis() + "-" + secret;
1015
                        String lockToken = md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
1016

    
1017
                        if (exists && object instanceof Folder && lock.depth == INFINITY)
1018
                                // Locking a collection (and all its member resources)
1019
                                lock.tokens.addElement(lockToken);
1020
                        else {
1021
                                // Locking a single resource
1022
                                lock.tokens.addElement(lockToken);
1023
                                // Add the Lock-Token header as by RFC 2518 8.10.1
1024
                                // - only do this for newly created locks
1025
                                resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">");
1026

    
1027
                        }
1028
                }
1029

    
1030
                if (lockRequestType == LOCK_REFRESH) {
1031

    
1032
                }
1033

    
1034
                // Set the status, then generate the XML response containing
1035
                // the lock information.
1036
                XMLWriter generatedXML = new XMLWriter();
1037
                generatedXML.writeXMLHeader();
1038
                generatedXML.writeElement(null, "prop" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1039
                generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1040
                lock.toXML(generatedXML);
1041
                generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
1042
                generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1043

    
1044
                resp.setStatus(WebdavStatus.SC_OK);
1045
                resp.setContentType("text/xml; charset=UTF-8");
1046
                Writer writer = resp.getWriter();
1047
                writer.write(generatedXML.toString());
1048
                writer.close();
1049
        }
1050

    
1051
        /**
1052
         * The MOVE method.
1053
         *
1054
         * @param req the HTTP request
1055
         * @param resp the HTTP response
1056
         * @throws IOException if an error occurs while sending the response
1057
         * @throws ServletException
1058
         */
1059
        private void doMove(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1060
                if (isLocked(req)) {
1061
                        resp.sendError(WebdavStatus.SC_LOCKED);
1062
                        return;
1063
                }
1064

    
1065
                String path = getRelativePath(req);
1066

    
1067
                if (copyResource(req, resp))
1068
                        deleteResource(path, req, resp, false);
1069
        }
1070

    
1071
        /**
1072
         * The COPY method.
1073
         *
1074
         * @param req the HTTP request
1075
         * @param resp the HTTP response
1076
         * @throws IOException if an error occurs while sending the response
1077
         * @throws ServletException
1078
         */
1079
        private void doCopy(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1080
                copyResource(req, resp);
1081
        }
1082

    
1083
        /**
1084
         * The MKCOL method.
1085
         *
1086
         * @param req the HTTP request
1087
         * @param resp the HTTP response
1088
         * @throws IOException if an error occurs while sending the response
1089
         * @throws ServletException
1090
         */
1091
        private void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1092
                if (isLocked(req)) {
1093
                        resp.sendError(WebdavStatus.SC_LOCKED);
1094
                        return;
1095
                }
1096
                final String path = getRelativePath(req);
1097
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
1098
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
1099
                        return;
1100
                }
1101

    
1102
                final User user = getUser(req);
1103
                boolean exists = true;
1104
                try {
1105
                        getService().getResourceAtPath(user.getId(), path, true);
1106
                } catch (ObjectNotFoundException e) {
1107
                        exists = false;
1108
                } catch (RpcException e) {
1109
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1110
                        return;
1111
                }
1112

    
1113
                // Can't create a collection if a resource already exists at the given
1114
                // path.
1115
                if (exists) {
1116
                        // Get allowed methods.
1117
                        StringBuffer methodsAllowed;
1118
                        try {
1119
                                methodsAllowed = determineMethodsAllowed(req);
1120
                        } catch (RpcException e) {
1121
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1122
                                return;
1123
                        }
1124
                        resp.addHeader("Allow", methodsAllowed.toString());
1125
                        resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
1126
                        return;
1127
                }
1128

    
1129
                if (req.getInputStream().available() > 0) {
1130
                        DocumentBuilder documentBuilder = getDocumentBuilder();
1131
                        try {
1132
                                @SuppressWarnings("unused")
1133
                                Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
1134
                                // TODO : Process this request body
1135
                                resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
1136
                                return;
1137
                        } catch (SAXException saxe) {
1138
                                // Parse error - assume invalid content
1139
                                resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1140
                                return;
1141
                        }
1142
                }
1143

    
1144
                Object parent;
1145
                try {
1146
                        parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
1147
                } catch (ObjectNotFoundException e) {
1148
                        resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1149
                        return;
1150
                } catch (RpcException e) {
1151
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1152
                        return;
1153
                }
1154
                try {
1155
                        if (parent instanceof Folder) {
1156
                                final Folder folderLocal = (Folder) parent;
1157
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1158
                                        @Override
1159
                                        public Void call() throws Exception {
1160
                                                getService().createFolder(user.getId(), folderLocal.getId(), getLastElement(path));
1161
                                                return null;
1162
                                        }
1163
                                });
1164
                        } else {
1165
                                resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1166
                                return;
1167
                        }
1168
                } catch (DuplicateNameException e) {
1169
                        // XXX If the existing name is a folder we should be returning
1170
                        // SC_METHOD_NOT_ALLOWED, or even better, just do the createFolder
1171
                        // without checking first and then deal with the exceptions.
1172
                        resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1173
                        return;
1174
                } catch (InsufficientPermissionsException e) {
1175
                        resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1176
                        return;
1177
                } catch (ObjectNotFoundException e) {
1178
                        resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1179
                        return;
1180
                } catch (RpcException e) {
1181
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1182
                        return;
1183
                } catch (Exception e) {
1184
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1185
                        return;
1186
                }
1187
                resp.setStatus(WebdavStatus.SC_CREATED);
1188
        }
1189

    
1190
        /**
1191
         * For a provided path, remove the last element and return the rest, that is
1192
         * the path of the parent folder.
1193
         *
1194
         * @param path the specified path
1195
         * @return the path of the parent folder
1196
         * @throws ObjectNotFoundException if the provided string contains no path
1197
         *             delimiters
1198
         */
1199
        protected String getParentPath(String path) throws ObjectNotFoundException {
1200
                int lastDelimiter = path.lastIndexOf('/');
1201
                if (lastDelimiter == 0)
1202
                        return "/";
1203
                if (lastDelimiter == -1)
1204
                        // No path found.
1205
                        throw new ObjectNotFoundException("There is no parent in the path: " + path);
1206
                else if (lastDelimiter < path.length() - 1)
1207
                        // Return the part before the delimiter.
1208
                        return path.substring(0, lastDelimiter);
1209
                else {
1210
                        // Remove the trailing delimiter and then recurse.
1211
                        String strippedTrail = path.substring(0, lastDelimiter);
1212
                        return getParentPath(strippedTrail);
1213
                }
1214
        }
1215

    
1216
        /**
1217
         * Get the last element in a path that denotes the file or folder name.
1218
         *
1219
         * @param path the provided path
1220
         * @return the last element in the path
1221
         */
1222
        protected String getLastElement(String path) {
1223
                int lastDelimiter = path.lastIndexOf('/');
1224
                if (lastDelimiter == -1)
1225
                        // No path found.
1226
                        return path;
1227
                else if (lastDelimiter < path.length() - 1)
1228
                        // Return the part after the delimiter.
1229
                        return path.substring(lastDelimiter + 1);
1230
                else {
1231
                        // Remove the trailing delimiter and then recurse.
1232
                        String strippedTrail = path.substring(0, lastDelimiter);
1233
                        return getLastElement(strippedTrail);
1234
                }
1235
        }
1236

    
1237
        /**
1238
         * Only use the PathInfo for determining the requested path. If the
1239
         * ServletPath is non-null, it will be because the WebDAV servlet has been
1240
         * mapped to a URL other than /* to configure editing at different URL than
1241
         * normal viewing.
1242
         *
1243
         * @param request the servlet request we are processing
1244
         * @return the relative path
1245
         * @throws UnsupportedEncodingException
1246
         */
1247
        protected String getRelativePath(HttpServletRequest request) {
1248
                // Remove the servlet path from the request URI.
1249
                String p = request.getRequestURI();
1250
                String servletPath = request.getContextPath() + request.getServletPath();
1251
                String result = p.substring(servletPath.length());
1252
                try {
1253
                        result = URLDecoder.decode(result, "UTF-8");
1254
                } catch (UnsupportedEncodingException e) {
1255
                }
1256
                if (result == null || result.equals(""))
1257
                        result = "/";
1258
                return result;
1259

    
1260
        }
1261

    
1262
        /**
1263
         * Return JAXP document builder instance.
1264
         *
1265
         * @return the DocumentBuilder
1266
         * @throws ServletException
1267
         */
1268
        private DocumentBuilder getDocumentBuilder() throws ServletException {
1269
                DocumentBuilder documentBuilder = null;
1270
                DocumentBuilderFactory documentBuilderFactory = null;
1271
                try {
1272
                        documentBuilderFactory = DocumentBuilderFactory.newInstance();
1273
                        documentBuilderFactory.setNamespaceAware(true);
1274
                        documentBuilderFactory.setExpandEntityReferences(false);
1275
                        documentBuilder = documentBuilderFactory.newDocumentBuilder();
1276
                        documentBuilder.setEntityResolver(new WebdavResolver(getServletContext()));
1277
                } catch (ParserConfigurationException e) {
1278
                        throw new ServletException("Error while creating a document builder");
1279
                }
1280
                return documentBuilder;
1281
        }
1282

    
1283
        /**
1284
         * Generate the namespace declarations.
1285
         *
1286
         * @return the namespace declarations
1287
         */
1288
        private String generateNamespaceDeclarations() {
1289
                return " xmlns:D=\"" + DEFAULT_NAMESPACE + "\"";
1290
        }
1291

    
1292
        /**
1293
         * Propfind helper method. Dispays the properties of a lock-null resource.
1294
         *
1295
         * @param req the HTTP request
1296
         * @param resources Resources object associated with this context
1297
         * @param generatedXML XML response to the Propfind request
1298
         * @param path Path of the current resource
1299
         * @param type Propfind type
1300
         * @param propertiesVector If the propfind type is find properties by name,
1301
         *            then this Vector contains those properties
1302
         */
1303
        @SuppressWarnings("unused")
1304
        private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector propertiesVector) {
1305
                return;
1306
        }
1307

    
1308
        /**
1309
         * Propfind helper method.
1310
         *
1311
         * @param req The servlet request
1312
         * @param resources Resources object associated with this context
1313
         * @param generatedXML XML response to the Propfind request
1314
         * @param path Path of the current resource
1315
         * @param type Propfind type
1316
         * @param propertiesVector If the propfind type is find properties by name,
1317
         *            then this Vector contains those properties
1318
         * @param resource the resource object
1319
         */
1320
        private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector<String> propertiesVector, Object resource) {
1321

    
1322
                // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1323
                // (the "toUpperCase()" avoids problems on Windows systems)
1324
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1325
                        return;
1326

    
1327
                Folder folderLocal = null;
1328
                FileHeader fileLocal = null;
1329
                if (resource instanceof Folder)
1330
                        folderLocal = (Folder) resource;
1331
                else
1332
                        fileLocal = (FileHeader) resource;
1333
                // Retrieve the creation date.
1334
                long creation = 0;
1335
                if (folderLocal != null)
1336
                        creation = folderLocal.getAuditInfo().getCreationDate().getTime();
1337
                else
1338
                        creation = fileLocal.getAuditInfo().getCreationDate().getTime();
1339
                // Retrieve the modification date.
1340
                long modification = 0;
1341
                if (folderLocal != null)
1342
                        modification = folderLocal.getAuditInfo().getCreationDate().getTime();
1343
                else
1344
                        modification = fileLocal.getAuditInfo().getCreationDate().getTime();
1345

    
1346
                generatedXML.writeElement(null, "D:response", XMLWriter.OPENING);
1347
                String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1348

    
1349
                // Generating href element
1350
                generatedXML.writeElement(null, "D:href", XMLWriter.OPENING);
1351

    
1352
                String href = req.getContextPath() + req.getServletPath();
1353
                if (href.endsWith("/") && path.startsWith("/"))
1354
                        href += path.substring(1);
1355
                else
1356
                        href += path;
1357
                if (folderLocal != null && !href.endsWith("/"))
1358
                        href += "/";
1359

    
1360
                generatedXML.writeText(rewriteUrl(href));
1361

    
1362
                generatedXML.writeElement(null, "D:href", XMLWriter.CLOSING);
1363

    
1364
                String resourceName = path;
1365
                int lastSlash = path.lastIndexOf('/');
1366
                if (lastSlash != -1)
1367
                        resourceName = resourceName.substring(lastSlash + 1);
1368
                if (resourceName.isEmpty())
1369
                        resourceName = "/";
1370

    
1371
                switch (type) {
1372

    
1373
                        case FIND_ALL_PROP:
1374

    
1375
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.OPENING);
1376
                                generatedXML.writeElement(null, "D:prop", XMLWriter.OPENING);
1377

    
1378
                                generatedXML.writeProperty(null, "D:creationdate", getISOCreationDate(creation));
1379
                                generatedXML.writeElement(null, "D:displayname", XMLWriter.OPENING);
1380
                                generatedXML.writeData(resourceName);
1381
                                generatedXML.writeElement(null, "D:displayname", XMLWriter.CLOSING);
1382
                                if (fileLocal != null) {
1383
                                        generatedXML.writeProperty(null, "D:getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1384
                                        generatedXML.writeProperty(null, "D:getcontentlength", String.valueOf(fileLocal.getCurrentBody().getFileSize()));
1385
                                        String contentType = fileLocal.getCurrentBody().getMimeType();
1386
                                        if (contentType != null)
1387
                                                generatedXML.writeProperty(null, "D:getcontenttype", contentType);
1388
                                        generatedXML.writeProperty(null, "D:getetag", getETag(fileLocal, null));
1389
                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.NO_CONTENT);
1390
                                } else {
1391
                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.OPENING);
1392
                                        generatedXML.writeElement(null, "D:collection", XMLWriter.NO_CONTENT);
1393
                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.CLOSING);
1394
                                }
1395

    
1396
                                generatedXML.writeProperty(null, "D:source", "");
1397

    
1398
                                String supportedLocks = "<D:lockentry>" + "<D:lockscope><D:exclusive/></D:lockscope>" + "<D:locktype><D:write/></D:locktype>" + "</D:lockentry>" + "<D:lockentry>" + "<D:lockscope><D:shared/></D:lockscope>" + "<D:locktype><D:write/></D:locktype>" + "</D:lockentry>";
1399
                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.OPENING);
1400
                                generatedXML.writeText(supportedLocks);
1401
                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.CLOSING);
1402

    
1403
                                generateLockDiscovery(path, generatedXML);
1404

    
1405
                                generatedXML.writeElement(null, "D:prop", XMLWriter.CLOSING);
1406
                                generatedXML.writeElement(null, "D:status", XMLWriter.OPENING);
1407
                                generatedXML.writeText(status);
1408
                                generatedXML.writeElement(null, "D:status", XMLWriter.CLOSING);
1409
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.CLOSING);
1410

    
1411
                                break;
1412

    
1413
                        case FIND_PROPERTY_NAMES:
1414

    
1415
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.OPENING);
1416
                                generatedXML.writeElement(null, "D:prop", XMLWriter.OPENING);
1417

    
1418
                                generatedXML.writeElement(null, "D:creationdate", XMLWriter.NO_CONTENT);
1419
                                generatedXML.writeElement(null, "D:displayname", XMLWriter.NO_CONTENT);
1420
                                if (fileLocal != null) {
1421
                                        generatedXML.writeElement(null, "D:getcontentlanguage", XMLWriter.NO_CONTENT);
1422
                                        generatedXML.writeElement(null, "D:getcontentlength", XMLWriter.NO_CONTENT);
1423
                                        generatedXML.writeElement(null, "D:getcontenttype", XMLWriter.NO_CONTENT);
1424
                                        generatedXML.writeElement(null, "D:getetag", XMLWriter.NO_CONTENT);
1425
                                        generatedXML.writeElement(null, "D:getlastmodified", XMLWriter.NO_CONTENT);
1426
                                }
1427
                                generatedXML.writeElement(null, "D:resourcetype", XMLWriter.NO_CONTENT);
1428
                                generatedXML.writeElement(null, "D:source", XMLWriter.NO_CONTENT);
1429
                                generatedXML.writeElement(null, "D:lockdiscovery", XMLWriter.NO_CONTENT);
1430

    
1431
                                generatedXML.writeElement(null, "D:prop", XMLWriter.CLOSING);
1432
                                generatedXML.writeElement(null, "D:status", XMLWriter.OPENING);
1433
                                generatedXML.writeText(status);
1434
                                generatedXML.writeElement(null, "D:status", XMLWriter.CLOSING);
1435
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.CLOSING);
1436

    
1437
                                break;
1438

    
1439
                        case FIND_BY_PROPERTY:
1440

    
1441
                                Vector<String> propertiesNotFound = new Vector<String>();
1442

    
1443
                                // Parse the list of properties
1444

    
1445
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.OPENING);
1446
                                generatedXML.writeElement(null, "D:prop", XMLWriter.OPENING);
1447

    
1448
                                Enumeration<String> properties = propertiesVector.elements();
1449

    
1450
                                while (properties.hasMoreElements()) {
1451

    
1452
                                        String property = properties.nextElement();
1453

    
1454
                                        if (property.equals("D:creationdate"))
1455
                                                generatedXML.writeProperty(null, "D:creationdate", getISOCreationDate(creation));
1456
                                        else if (property.equals("D:displayname")) {
1457
                                                generatedXML.writeElement(null, "D:displayname", XMLWriter.OPENING);
1458
                                                generatedXML.writeData(resourceName);
1459
                                                generatedXML.writeElement(null, "D:displayname", XMLWriter.CLOSING);
1460
                                        } else if (property.equals("D:getcontentlanguage")) {
1461
                                                if (folderLocal != null)
1462
                                                        propertiesNotFound.addElement(property);
1463
                                                else
1464
                                                        generatedXML.writeElement(null, "D:getcontentlanguage", XMLWriter.NO_CONTENT);
1465
                                        } else if (property.equals("D:getcontentlength")) {
1466
                                                if (folderLocal != null)
1467
                                                        propertiesNotFound.addElement(property);
1468
                                                else
1469
                                                        generatedXML.writeProperty(null, "D:getcontentlength", String.valueOf(fileLocal.getCurrentBody().getFileSize()));
1470
                                        } else if (property.equals("D:getcontenttype")) {
1471
                                                if (folderLocal != null)
1472
                                                        propertiesNotFound.addElement(property);
1473
                                                else
1474
                                                        // XXX Once we properly store the MIME type in the
1475
                                                        // file,
1476
                                                        // retrieve it from there.
1477
                                                        generatedXML.writeProperty(null, "D:getcontenttype", getServletContext().getMimeType(fileLocal.getName()));
1478
                                        } else if (property.equals("D:getetag")) {
1479
                                                if (folderLocal != null)
1480
                                                        propertiesNotFound.addElement(property);
1481
                                                else
1482
                                                        generatedXML.writeProperty(null, "D:getetag", getETag(fileLocal, null));
1483
                                        } else if (property.equals("D:getlastmodified")) {
1484
                                                if (folderLocal != null)
1485
                                                        propertiesNotFound.addElement(property);
1486
                                                else
1487
                                                        generatedXML.writeProperty(null, "D:getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1488
                                        } else if (property.equals("D:resourcetype")) {
1489
                                                if (folderLocal != null) {
1490
                                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.OPENING);
1491
                                                        generatedXML.writeElement(null, "D:collection", XMLWriter.NO_CONTENT);
1492
                                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.CLOSING);
1493
                                                } else
1494
                                                        generatedXML.writeElement(null, "D:resourcetype", XMLWriter.NO_CONTENT);
1495
                                        } else if (property.equals("D:source"))
1496
                                                generatedXML.writeProperty(null, "D:source", "");
1497
                                        else if (property.equals("D:supportedlock")) {
1498
                                                supportedLocks = "<D:lockentry>" + "<D:lockscope><D:exclusive/></D:lockscope>" + "<D:locktype><D:write/></D:locktype>" + "</D:lockentry>" + "<D:lockentry>" + "<D:lockscope><D:shared/></D:lockscope>" + "<D:locktype><D:write/></D:locktype>" + "</D:lockentry>";
1499
                                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.OPENING);
1500
                                                generatedXML.writeText(supportedLocks);
1501
                                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.CLOSING);
1502
                                        } else if (property.equals("D:lockdiscovery")) {
1503
                                                if (!generateLockDiscovery(path, generatedXML))
1504
                                                        propertiesNotFound.addElement(property);
1505
                                        } else
1506
                                                propertiesNotFound.addElement(property);
1507
                                }
1508

    
1509
                                generatedXML.writeElement(null, "D:prop", XMLWriter.CLOSING);
1510
                                generatedXML.writeElement(null, "D:status", XMLWriter.OPENING);
1511
                                generatedXML.writeText(status);
1512
                                generatedXML.writeElement(null, "D:status", XMLWriter.CLOSING);
1513
                                generatedXML.writeElement(null, "D:propstat", XMLWriter.CLOSING);
1514

    
1515
                                Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1516

    
1517
                                if (propertiesNotFoundList.hasMoreElements()) {
1518

    
1519
                                        status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1520

    
1521
                                        generatedXML.writeElement(null, "D:propstat", XMLWriter.OPENING);
1522
                                        generatedXML.writeElement(null, "D:prop", XMLWriter.OPENING);
1523

    
1524
                                        while (propertiesNotFoundList.hasMoreElements())
1525
                                                generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1526
                                        generatedXML.writeElement(null, "D:prop", XMLWriter.CLOSING);
1527
                                        generatedXML.writeElement(null, "D:status", XMLWriter.OPENING);
1528
                                        generatedXML.writeText(status);
1529
                                        generatedXML.writeElement(null, "D:status", XMLWriter.CLOSING);
1530
                                        generatedXML.writeElement(null, "D:propstat", XMLWriter.CLOSING);
1531
                                }
1532

    
1533
                                break;
1534

    
1535
                }
1536

    
1537
                generatedXML.writeElement(null, "D:response", XMLWriter.CLOSING);
1538

    
1539
        }
1540

    
1541
        /**
1542
         * Get the ETag associated with a file.
1543
         *
1544
         * @param file the FileHeader object for this file
1545
         * @param oldBody the old version of the file, if requested
1546
         * @return a string containing the ETag
1547
         */
1548
        protected String getETag(FileHeader file, FileBody oldBody) {
1549
                if (oldBody == null)
1550
                        return "\"" + file.getCurrentBody().getFileSize() + "-" + file.getAuditInfo().getModificationDate().getTime() + "\"";
1551
                return "\"" + oldBody.getFileSize() + "-" + oldBody.getAuditInfo().getModificationDate().getTime() + "\"";
1552
        }
1553

    
1554
        /**
1555
         * URL rewriter.
1556
         *
1557
         * @param path Path which has to be rewritten
1558
         * @return the rewritten URL
1559
         */
1560
        protected String rewriteUrl(String path) {
1561
                return urlEncoder.encode(path);
1562
        }
1563

    
1564
        /**
1565
         * Print the lock discovery information associated with a path.
1566
         *
1567
         * @param path Path
1568
         * @param generatedXML XML data to which the locks info will be appended
1569
         * @return true if at least one lock was displayed
1570
         */
1571
        @SuppressWarnings("unused")
1572
        private boolean generateLockDiscovery(String path,  XMLWriter generatedXML) {
1573
                        return false;
1574
        }
1575

    
1576
        /**
1577
         * Get creation date in ISO format.
1578
         *
1579
         * @param creationDate
1580
         * @return the formatted date
1581
         */
1582
        private String getISOCreationDate(long creationDate) {
1583
                String dateValue = null;
1584
                synchronized (creationDateFormat) {
1585
                        dateValue = creationDateFormat.format(new Date(creationDate));
1586
                }
1587
                StringBuffer creationDateValue = new StringBuffer(dateValue);
1588
                /*
1589
                int offset = Calendar.getInstance().getTimeZone().getRawOffset()
1590
                    / 3600000; // FIXME ?
1591
                if (offset < 0) {
1592
                    creationDateValue.append("-");
1593
                    offset = -offset;
1594
                } else if (offset > 0) {
1595
                    creationDateValue.append("+");
1596
                }
1597
                if (offset != 0) {
1598
                    if (offset < 10)
1599
                        creationDateValue.append("0");
1600
                    creationDateValue.append(offset + ":00");
1601
                } else {
1602
                    creationDateValue.append("Z");
1603
                }
1604
                 */
1605
                return creationDateValue.toString();
1606
        }
1607

    
1608
        /**
1609
         * Determines the methods normally allowed for the resource.
1610
         *
1611
         * @param req the HTTP request
1612
         * @return a list of the allowed methods
1613
         * @throws RpcException if there is an error while communicating with the
1614
         *             backend
1615
         */
1616
        private StringBuffer determineMethodsAllowed(HttpServletRequest req) throws RpcException {
1617
                StringBuffer methodsAllowed = new StringBuffer();
1618
                boolean exists = true;
1619
                Object object = null;
1620
                User user = getUser(req);
1621
                String path = getRelativePath(req);
1622
                if (user == null && "/".equals(path))
1623
                        // Special case: OPTIONS request before authentication
1624
                        return new StringBuffer("OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND, PUT, MKCOL");
1625
                try {
1626
                        object = getService().getResourceAtPath(user.getId(), path, true);
1627
                } catch (ObjectNotFoundException e) {
1628
                        exists = false;
1629
                }
1630

    
1631
                if (!exists) {
1632
                        methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
1633
                        return methodsAllowed;
1634
                }
1635

    
1636
                methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
1637
                methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
1638
                methodsAllowed.append(", PROPFIND");
1639

    
1640
                if (!(object instanceof Folder))
1641
                        methodsAllowed.append(", PUT");
1642

    
1643
                return methodsAllowed;
1644
        }
1645

    
1646
        /**
1647
         * Check to see if a resource is currently write locked. The method will
1648
         * look at the "If" header to make sure the client has given the appropriate
1649
         * lock tokens.
1650
         *
1651
         * @param req the HTTP request
1652
         * @return boolean true if the resource is locked (and no appropriate lock
1653
         *         token has been found for at least one of the non-shared locks
1654
         *         which are present on the resource).
1655
         */
1656
        private boolean isLocked(@SuppressWarnings("unused") HttpServletRequest req) {
1657
                return false;
1658
        }
1659

    
1660
        /**
1661
         * Check to see if a resource is currently write locked.
1662
         *
1663
         * @param path Path of the resource
1664
         * @param ifHeader "If" HTTP header which was included in the request
1665
         * @return boolean true if the resource is locked (and no appropriate lock
1666
         *         token has been found for at least one of the non-shared locks
1667
         *         which are present on the resource).
1668
         */
1669
        private boolean isLocked(@SuppressWarnings("unused") String path, @SuppressWarnings("unused") String ifHeader) {
1670
                return false;
1671
        }
1672

    
1673
        /**
1674
         * Parse the content-range header.
1675
         *
1676
         * @param request The servlet request we are processing
1677
         * @param response The servlet response we are creating
1678
         * @return Range
1679
         * @throws IOException
1680
         */
1681
        protected Range parseContentRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
1682

    
1683
                // Retrieving the content-range header (if any is specified
1684
                String rangeHeader = request.getHeader("Content-Range");
1685

    
1686
                if (rangeHeader == null)
1687
                        return null;
1688

    
1689
                // bytes is the only range unit supported
1690
                if (!rangeHeader.startsWith("bytes")) {
1691
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1692
                        return null;
1693
                }
1694

    
1695
                rangeHeader = rangeHeader.substring(6).trim();
1696

    
1697
                int dashPos = rangeHeader.indexOf('-');
1698
                int slashPos = rangeHeader.indexOf('/');
1699

    
1700
                if (dashPos == -1) {
1701
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1702
                        return null;
1703
                }
1704

    
1705
                if (slashPos == -1) {
1706
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1707
                        return null;
1708
                }
1709

    
1710
                Range range = new Range();
1711

    
1712
                try {
1713
                        range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
1714
                        range.end = Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
1715
                        range.length = Long.parseLong(rangeHeader.substring(slashPos + 1, rangeHeader.length()));
1716
                } catch (NumberFormatException e) {
1717
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1718
                        return null;
1719
                }
1720

    
1721
                if (!range.validate()) {
1722
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1723
                        return null;
1724
                }
1725

    
1726
                return range;
1727

    
1728
        }
1729

    
1730
        /**
1731
         * Handle a partial PUT. New content specified in request is appended to
1732
         * existing content in oldRevisionContent (if present). This code does not
1733
         * support simultaneous partial updates to the same resource.
1734
         *
1735
         * @param req
1736
         * @param range
1737
         * @param path
1738
         * @return
1739
         * @throws IOException
1740
         * @throws RpcException
1741
         * @throws InsufficientPermissionsException
1742
         * @throws ObjectNotFoundException
1743
         */
1744
        protected File executePartialPut(HttpServletRequest req, Range range, String path) throws IOException, RpcException, ObjectNotFoundException, InsufficientPermissionsException {
1745
                // Append data specified in ranges to existing content for this
1746
                // resource - create a temporary file on the local file system to
1747
                // perform this operation.
1748
                File tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
1749
                // Convert all '/' characters to '.' in resourcePath
1750
                String convertedResourcePath = path.replace('/', '.');
1751
                File contentFile = new File(tempDir, convertedResourcePath);
1752
                if (contentFile.createNewFile())
1753
                        // Clean up contentFile when Tomcat is terminated.
1754
                        contentFile.deleteOnExit();
1755

    
1756
                RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw");
1757

    
1758
                User user = getUser(req);
1759
                User owner = getOwner(req);
1760
                FileHeader oldResource = null;
1761
                try {
1762
                        Object obj = getService().getResourceAtPath(owner.getId(), path, true);
1763
                        if (obj instanceof FileHeader)
1764
                                oldResource = (FileHeader) obj;
1765
                } catch (ObjectNotFoundException e) {
1766
                        // Do nothing.
1767
                }
1768

    
1769
                // Copy data in oldRevisionContent to contentFile
1770
                if (oldResource != null) {
1771
                        InputStream contents = getService().getFileContents(user.getId(), oldResource.getId());
1772
                        BufferedInputStream bufOldRevStream = new BufferedInputStream(contents, BUFFER_SIZE);
1773

    
1774
                        int numBytesRead;
1775
                        byte[] copyBuffer = new byte[BUFFER_SIZE];
1776
                        while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1)
1777
                                randAccessContentFile.write(copyBuffer, 0, numBytesRead);
1778

    
1779
                        bufOldRevStream.close();
1780
                }
1781

    
1782
                randAccessContentFile.setLength(range.length);
1783

    
1784
                // Append data in request input stream to contentFile
1785
                randAccessContentFile.seek(range.start);
1786
                int numBytesRead;
1787
                byte[] transferBuffer = new byte[BUFFER_SIZE];
1788
                BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
1789
                while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1)
1790
                        randAccessContentFile.write(transferBuffer, 0, numBytesRead);
1791
                randAccessContentFile.close();
1792
                requestBufInStream.close();
1793

    
1794
                return contentFile;
1795

    
1796
        }
1797

    
1798
        /**
1799
         * Serve the specified resource, optionally including the data content.
1800
         *
1801
         * @param req The servlet request we are processing
1802
         * @param resp The servlet response we are creating
1803
         * @param content Should the content be included?
1804
         * @exception IOException if an input/output error occurs
1805
         * @exception ServletException if a servlet-specified error occurs
1806
         * @throws RpcException
1807
         * @throws InsufficientPermissionsException
1808
         * @throws ObjectNotFoundException
1809
         */
1810
        protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException, ServletException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
1811

    
1812
                // Identify the requested resource path
1813
                String path = getRelativePath(req);
1814
                if (logger.isDebugEnabled())
1815
                        if (content)
1816
                                logger.debug("Serving resource '" + path + "' headers and data");
1817
                        else
1818
                                logger.debug("Serving resource '" + path + "' headers only");
1819

    
1820
                User user = getUser(req);
1821
                boolean exists = true;
1822
                Object resource = null;
1823
                FileHeader file = null;
1824
                Folder folder = null;
1825
                try {
1826
                        resource = getService().getResourceAtPath(user.getId(), path, true);
1827
                } catch (ObjectNotFoundException e) {
1828
                        exists = false;
1829
                } catch (RpcException e) {
1830
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1831
                        return;
1832
                }
1833

    
1834
                if (!exists) {
1835
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
1836
                        return;
1837
                }
1838

    
1839
                if (resource instanceof Folder)
1840
                        folder = (Folder) resource;
1841
                else
1842
                        file = (FileHeader) resource;
1843

    
1844
                // If the resource is not a collection, and the resource path
1845
                // ends with "/" or "\", return NOT FOUND
1846
                if (folder == null)
1847
                        if (path.endsWith("/") || path.endsWith("\\")) {
1848
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
1849
                                return;
1850
                        }
1851

    
1852
                // Check if the conditions specified in the optional If headers are
1853
                // satisfied.
1854
                if (folder == null)
1855
                        // Checking If headers
1856
                        if (!checkIfHeaders(req, resp, file, null))
1857
                                return;
1858

    
1859
                // Find content type.
1860
                String contentType = null;
1861
                if (file != null) {
1862
                        contentType = file.getCurrentBody().getMimeType();
1863
                        if (contentType == null) {
1864
                                contentType = getServletContext().getMimeType(file.getName());
1865
                                file.getCurrentBody().setMimeType(contentType);
1866
                        }
1867
                } else
1868
                        contentType = "text/html;charset=UTF-8";
1869

    
1870
                ArrayList ranges = null;
1871
                long contentLength = -1L;
1872

    
1873
                if (file != null) {
1874
                        // Accept ranges header
1875
                        resp.setHeader("Accept-Ranges", "bytes");
1876
                        // Parse range specifier
1877
                        ranges = parseRange(req, resp, file, null);
1878
                        // ETag header
1879
                        resp.setHeader("ETag", getETag(file, null));
1880
                        // Last-Modified header
1881
                        resp.setHeader("Last-Modified", getLastModifiedHttp(file.getAuditInfo()));
1882
                        // Get content length
1883
                        contentLength = file.getCurrentBody().getFileSize();
1884
                        // Special case for zero length files, which would cause a
1885
                        // (silent) ISE when setting the output buffer size
1886
                        if (contentLength == 0L)
1887
                                content = false;
1888
                }
1889

    
1890
                ServletOutputStream ostream = null;
1891
                PrintWriter writer = null;
1892

    
1893
                if (content)
1894
                        try {
1895
                                ostream = resp.getOutputStream();
1896
                        } catch (IllegalStateException e) {
1897
                                // If it fails, we try to get a Writer instead if we're
1898
                                // trying to serve a text file
1899
                                if (contentType == null || contentType.startsWith("text") || contentType.endsWith("xml"))
1900
                                        writer = resp.getWriter();
1901
                                else
1902
                                        throw e;
1903
                        }
1904

    
1905
                if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
1906
                        // Set the appropriate output headers
1907
                        if (contentType != null) {
1908
                                if (logger.isDebugEnabled())
1909
                                        logger.debug("DefaultServlet.serveFile:  contentType='" + contentType + "'");
1910
                                resp.setContentType(contentType);
1911
                        }
1912
                        if (file != null && contentLength >= 0) {
1913
                                if (logger.isDebugEnabled())
1914
                                        logger.debug("DefaultServlet.serveFile:  contentLength=" + contentLength);
1915
                                if (contentLength < Integer.MAX_VALUE)
1916
                                        resp.setContentLength((int) contentLength);
1917
                                else
1918
                                        // Set the content-length as String to be able to use a long
1919
                                        resp.setHeader("content-length", "" + contentLength);
1920
                        }
1921

    
1922
                        InputStream renderResult = null;
1923
                        if (folder != null)
1924
                                if (content)
1925
                                        // Serve the directory browser
1926
                                        renderResult = renderHtml(req.getContextPath(), path, folder, req);
1927

    
1928
                        // Copy the input stream to our output stream (if requested)
1929
                        if (content) {
1930
                                try {
1931
                                        resp.setBufferSize(output);
1932
                                } catch (IllegalStateException e) {
1933
                                        // Silent catch
1934
                                }
1935
                                if (ostream != null)
1936
                                        copy(file, renderResult, ostream, req, null);
1937
                                else
1938
                                        copy(file, renderResult, writer, req, null);
1939
                                updateAccounting(user, new Date(), contentLength);
1940
                        }
1941
                } else {
1942
                        if (ranges == null || ranges.isEmpty())
1943
                                return;
1944
                        // Partial content response.
1945
                        resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1946

    
1947
                        if (ranges.size() == 1) {
1948
                                Range range = (Range) ranges.get(0);
1949
                                resp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length);
1950
                                long length = range.end - range.start + 1;
1951
                                if (length < Integer.MAX_VALUE)
1952
                                        resp.setContentLength((int) length);
1953
                                else
1954
                                        // Set the content-length as String to be able to use a long
1955
                                        resp.setHeader("content-length", "" + length);
1956

    
1957
                                if (contentType != null) {
1958
                                        if (logger.isDebugEnabled())
1959
                                                logger.debug("DefaultServlet.serveFile:  contentType='" + contentType + "'");
1960
                                        resp.setContentType(contentType);
1961
                                }
1962

    
1963
                                if (content) {
1964
                                        try {
1965
                                                resp.setBufferSize(output);
1966
                                        } catch (IllegalStateException e) {
1967
                                                // Silent catch
1968
                                        }
1969
                                        if (ostream != null)
1970
                                                copy(file, ostream, range, req, null);
1971
                                        else
1972
                                                copy(file, writer, range, req, null);
1973
                                        updateAccounting(user, new Date(), contentLength);
1974
                                }
1975

    
1976
                        } else {
1977

    
1978
                                resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
1979

    
1980
                                if (content) {
1981
                                        try {
1982
                                                resp.setBufferSize(output);
1983
                                        } catch (IllegalStateException e) {
1984
                                                // Silent catch
1985
                                        }
1986
                                        if (ostream != null)
1987
                                                copy(file, ostream, ranges.iterator(), contentType, req, null);
1988
                                        else
1989
                                                copy(file, writer, ranges.iterator(), contentType, req, null);
1990
                                }
1991

    
1992
                        }
1993

    
1994
                }
1995

    
1996
        }
1997

    
1998
        /**
1999
         * Retrieve the last modified date of a resource in HTTP format.
2000
         *
2001
         * @param auditInfo the audit info for the specified resource
2002
         * @return the last modified date in HTTP format
2003
         */
2004
        protected String getLastModifiedHttp(AuditInfo auditInfo) {
2005
                Date modifiedDate = auditInfo.getModificationDate();
2006
                if (modifiedDate == null)
2007
                        modifiedDate = auditInfo.getCreationDate();
2008
                if (modifiedDate == null)
2009
                        modifiedDate = new Date();
2010
                String lastModifiedHttp = null;
2011
                synchronized (format) {
2012
                        lastModifiedHttp = format.format(modifiedDate);
2013
                }
2014
                return lastModifiedHttp;
2015
        }
2016

    
2017
        /**
2018
         * Parse the range header.
2019
         *
2020
         * @param request The servlet request we are processing
2021
         * @param response The servlet response we are creating
2022
         * @param file
2023
         * @param oldBody the old version of the file, if requested
2024
         * @return Vector of ranges
2025
         * @throws IOException
2026
         */
2027
        protected ArrayList parseRange(HttpServletRequest request, HttpServletResponse response, FileHeader file, FileBody oldBody) throws IOException {
2028
                // Checking If-Range
2029
                String headerValue = request.getHeader("If-Range");
2030
                if (headerValue != null) {
2031
                        long headerValueTime = -1L;
2032
                        try {
2033
                                headerValueTime = request.getDateHeader("If-Range");
2034
                        } catch (IllegalArgumentException e) {
2035
                                // Do nothing.
2036
                        }
2037

    
2038
                        String eTag = getETag(file, oldBody);
2039
                        long lastModified = oldBody == null ?
2040
                                                file.getAuditInfo().getModificationDate().getTime() :
2041
                                                oldBody.getAuditInfo().getModificationDate().getTime();
2042

    
2043
                        if (headerValueTime == -1L) {
2044
                                // If the ETag the client gave does not match the entity
2045
                                // etag, then the entire entity is returned.
2046
                                if (!eTag.equals(headerValue.trim()))
2047
                                        return FULL;
2048
                        } else
2049
                        // If the timestamp of the entity the client got is older than
2050
                        // the last modification date of the entity, the entire entity
2051
                        // is returned.
2052
                        if (lastModified > headerValueTime + 1000)
2053
                                return FULL;
2054
                }
2055

    
2056
                long fileLength = oldBody == null ? file.getCurrentBody().getFileSize() : oldBody.getFileSize();
2057
                if (fileLength == 0)
2058
                        return null;
2059

    
2060
                // Retrieving the range header (if any is specified).
2061
                String rangeHeader = request.getHeader("Range");
2062

    
2063
                if (rangeHeader == null)
2064
                        return null;
2065
                // bytes is the only range unit supported (and I don't see the point
2066
                // of adding new ones).
2067
                if (!rangeHeader.startsWith("bytes")) {
2068
                        response.addHeader("Content-Range", "bytes */" + fileLength);
2069
                        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2070
                        return null;
2071
                }
2072

    
2073
                rangeHeader = rangeHeader.substring(6);
2074

    
2075
                // Vector that will contain all the ranges which are successfully
2076
                // parsed.
2077
                ArrayList<Range> result = new ArrayList<Range>();
2078
                StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
2079
                // Parsing the range list
2080
                while (commaTokenizer.hasMoreTokens()) {
2081
                        String rangeDefinition = commaTokenizer.nextToken().trim();
2082

    
2083
                        Range currentRange = new Range();
2084
                        currentRange.length = fileLength;
2085

    
2086
                        int dashPos = rangeDefinition.indexOf('-');
2087

    
2088
                        if (dashPos == -1) {
2089
                                response.addHeader("Content-Range", "bytes */" + fileLength);
2090
                                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2091
                                return null;
2092
                        }
2093

    
2094
                        if (dashPos == 0)
2095
                                try {
2096
                                        long offset = Long.parseLong(rangeDefinition);
2097
                                        currentRange.start = fileLength + offset;
2098
                                        currentRange.end = fileLength - 1;
2099
                                } catch (NumberFormatException e) {
2100
                                        response.addHeader("Content-Range", "bytes */" + fileLength);
2101
                                        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2102
                                        return null;
2103
                                }
2104
                        else
2105
                                try {
2106
                                        currentRange.start = Long.parseLong(rangeDefinition.substring(0, dashPos));
2107
                                        if (dashPos < rangeDefinition.length() - 1)
2108
                                                currentRange.end = Long.parseLong(rangeDefinition.substring(dashPos + 1, rangeDefinition.length()));
2109
                                        else
2110
                                                currentRange.end = fileLength - 1;
2111
                                } catch (NumberFormatException e) {
2112
                                        response.addHeader("Content-Range", "bytes */" + fileLength);
2113
                                        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2114
                                        return null;
2115
                                }
2116

    
2117
                        if (!currentRange.validate()) {
2118
                                response.addHeader("Content-Range", "bytes */" + fileLength);
2119
                                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2120
                                return null;
2121
                        }
2122
                        result.add(currentRange);
2123
                }
2124
                return result;
2125
        }
2126

    
2127
        /**
2128
         * Check if the conditions specified in the optional If headers are
2129
         * satisfied.
2130
         *
2131
         * @param request The servlet request we are processing
2132
         * @param response The servlet response we are creating
2133
         * @param file the file resource against which the checks will be made
2134
         * @param oldBody the old version of the file, if requested
2135
         * @return boolean true if the resource meets all the specified conditions,
2136
         *         and false if any of the conditions is not satisfied, in which
2137
         *         case request processing is stopped
2138
         * @throws IOException
2139
         */
2140
        protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response,
2141
                                FileHeader file, FileBody oldBody) throws IOException {
2142
                // TODO : Checking the WebDAV If header
2143
                return checkIfMatch(request, response, file, oldBody) &&
2144
                                checkIfModifiedSince(request, response, file, oldBody) &&
2145
                                checkIfNoneMatch(request, response, file, oldBody) &&
2146
                                checkIfUnmodifiedSince(request, response, file, oldBody);
2147
        }
2148

    
2149
        /**
2150
         * Check if the if-match condition is satisfied.
2151
         *
2152
         * @param request The servlet request we are processing
2153
         * @param response The servlet response we are creating
2154
         * @param file the file object
2155
         * @param oldBody the old version of the file, if requested
2156
         * @return boolean true if the resource meets the specified condition, and
2157
         *         false if the condition is not satisfied, in which case request
2158
         *         processing is stopped
2159
         * @throws IOException
2160
         */
2161
        private boolean checkIfMatch(HttpServletRequest request, HttpServletResponse response,
2162
                                FileHeader file, FileBody oldBody) throws IOException {
2163
                String eTag = getETag(file, oldBody);
2164
                String headerValue = request.getHeader("If-Match");
2165
                if (headerValue != null)
2166
                        if (headerValue.indexOf('*') == -1) {
2167
                                StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2168
                                boolean conditionSatisfied = false;
2169
                                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2170
                                        String currentToken = commaTokenizer.nextToken();
2171
                                        if (currentToken.trim().equals(eTag))
2172
                                                conditionSatisfied = true;
2173
                                }
2174
                                // If none of the given ETags match, 412 Precodition failed is
2175
                                // sent back.
2176
                                if (!conditionSatisfied) {
2177
                                        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2178
                                        return false;
2179
                                }
2180
                        }
2181
                return true;
2182
        }
2183

    
2184
        /**
2185
         * Check if the if-modified-since condition is satisfied.
2186
         *
2187
         * @param request The servlet request we are processing
2188
         * @param response The servlet response we are creating
2189
         * @param file the file object
2190
         * @param oldBody the old version of the file, if requested
2191
         * @return boolean true if the resource meets the specified condition, and
2192
         *         false if the condition is not satisfied, in which case request
2193
         *         processing is stopped
2194
         */
2195
        private boolean checkIfModifiedSince(HttpServletRequest request,
2196
                                HttpServletResponse response, FileHeader file, FileBody oldBody) {
2197
                try {
2198
                        long headerValue = request.getDateHeader("If-Modified-Since");
2199
                        long lastModified = oldBody == null ?
2200
                                                file.getAuditInfo().getModificationDate().getTime() :
2201
                                                oldBody.getAuditInfo().getModificationDate().getTime();
2202
                        if (headerValue != -1)
2203
                                // If an If-None-Match header has been specified, if modified
2204
                                // since is ignored.
2205
                                if (request.getHeader("If-None-Match") == null && lastModified < headerValue + 1000) {
2206
                                        // The entity has not been modified since the date
2207
                                        // specified by the client. This is not an error case.
2208
                                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2209
                                        response.setHeader("ETag", getETag(file, oldBody));
2210
                                        return false;
2211
                                }
2212
                } catch (IllegalArgumentException illegalArgument) {
2213
                        return true;
2214
                }
2215
                return true;
2216
        }
2217

    
2218
        /**
2219
         * Check if the if-none-match condition is satisfied.
2220
         *
2221
         * @param request The servlet request we are processing
2222
         * @param response The servlet response we are creating
2223
         * @param file the file object
2224
         * @param oldBody the old version of the file, if requested
2225
         * @return boolean true if the resource meets the specified condition, and
2226
         *         false if the condition is not satisfied, in which case request
2227
         *         processing is stopped
2228
         * @throws IOException
2229
         */
2230
        private boolean checkIfNoneMatch(HttpServletRequest request,
2231
                                HttpServletResponse response, FileHeader file, FileBody oldBody)
2232
                        throws IOException {
2233
                String eTag = getETag(file, oldBody);
2234
                String headerValue = request.getHeader("If-None-Match");
2235
                if (headerValue != null) {
2236
                        boolean conditionSatisfied = false;
2237
                        if (!headerValue.equals("*")) {
2238
                                StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2239
                                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2240
                                        String currentToken = commaTokenizer.nextToken();
2241
                                        if (currentToken.trim().equals(eTag))
2242
                                                conditionSatisfied = true;
2243
                                }
2244
                        } else
2245
                                conditionSatisfied = true;
2246
                        if (conditionSatisfied) {
2247
                                // For GET and HEAD, we should respond with 304 Not Modified.
2248
                                // For every other method, 412 Precondition Failed is sent
2249
                                // back.
2250
                                if ("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod())) {
2251
                                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2252
                                        response.setHeader("ETag", getETag(file, oldBody));
2253
                                        return false;
2254
                                }
2255
                                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2256
                                return false;
2257
                        }
2258
                }
2259
                return true;
2260
        }
2261

    
2262
        /**
2263
         * Check if the if-unmodified-since condition is satisfied.
2264
         *
2265
         * @param request The servlet request we are processing
2266
         * @param response The servlet response we are creating
2267
         * @param file the file object
2268
         * @param oldBody the old version of the file, if requested
2269
         * @return boolean true if the resource meets the specified condition, and
2270
         *         false if the condition is not satisfied, in which case request
2271
         *         processing is stopped
2272
         * @throws IOException
2273
         */
2274
        private boolean checkIfUnmodifiedSince(HttpServletRequest request,
2275
                                HttpServletResponse response, FileHeader file, FileBody oldBody)
2276
                        throws IOException {
2277
                try {
2278
                        long lastModified = oldBody == null ?
2279
                                                file.getAuditInfo().getModificationDate().getTime() :
2280
                                                oldBody.getAuditInfo().getModificationDate().getTime();
2281
                        long headerValue = request.getDateHeader("If-Unmodified-Since");
2282
                        if (headerValue != -1)
2283
                                if (lastModified >= headerValue + 1000) {
2284
                                        // The entity has not been modified since the date
2285
                                        // specified by the client. This is not an error case.
2286
                                        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2287
                                        return false;
2288
                                }
2289
                } catch (IllegalArgumentException illegalArgument) {
2290
                        return true;
2291
                }
2292
                return true;
2293
        }
2294

    
2295
        /**
2296
         * Copy the contents of the specified input stream to the specified output
2297
         * stream, and ensure that both streams are closed before returning (even in
2298
         * the face of an exception).
2299
         *
2300
         * @param file the file resource
2301
         * @param is
2302
         * @param ostream The output stream to write to
2303
         * @param req the HTTP request
2304
         * @param oldBody the old version of the file, if requested
2305
         * @exception IOException if an input/output error occurs
2306
         * @throws RpcException
2307
         * @throws InsufficientPermissionsException
2308
         * @throws ObjectNotFoundException
2309
         */
2310
        protected void copy(FileHeader file, InputStream is, ServletOutputStream ostream,
2311
                                HttpServletRequest req, FileBody oldBody) throws IOException,
2312
                                ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2313
                IOException exception = null;
2314
                InputStream resourceInputStream = null;
2315
                User user = getUser(req);
2316
                if (user == null)
2317
                        throw new ObjectNotFoundException("No user or owner specified");
2318
                if (file != null)
2319
                        resourceInputStream = oldBody == null ?
2320
                                                getService().getFileContents(user.getId(), file.getId()) :
2321
                                                getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2322
                else
2323
                        resourceInputStream = is;
2324

    
2325
                InputStream istream = new BufferedInputStream(resourceInputStream, input);
2326
                // Copy the input stream to the output stream
2327
                exception = copyRange(istream, ostream);
2328
                // Clean up the input stream
2329
                istream.close();
2330
                // Rethrow any exception that has occurred
2331
                if (exception != null)
2332
                        throw exception;
2333
        }
2334

    
2335
        /**
2336
         * Copy the contents of the specified input stream to the specified output
2337
         * stream, and ensure that both streams are closed before returning (even in
2338
         * the face of an exception).
2339
         *
2340
         * @param istream The input stream to read from
2341
         * @param ostream The output stream to write to
2342
         * @return Exception which occurred during processing
2343
         */
2344
        private IOException copyRange(InputStream istream, ServletOutputStream ostream) {
2345
                // Copy the input stream to the output stream
2346
                IOException exception = null;
2347
                byte buffer[] = new byte[input];
2348
                int len = buffer.length;
2349
                while (true)
2350
                        try {
2351
                                len = istream.read(buffer);
2352
                                if (len == -1)
2353
                                        break;
2354
                                ostream.write(buffer, 0, len);
2355
                        } catch (IOException e) {
2356
                                exception = e;
2357
                                len = -1;
2358
                                break;
2359
                        }
2360
                return exception;
2361
        }
2362

    
2363
        /**
2364
         * Copy the contents of the specified input stream to the specified output
2365
         * stream, and ensure that both streams are closed before returning (even in
2366
         * the face of an exception).
2367
         *
2368
         * @param file
2369
         * @param is
2370
         * @param resourceInfo The resource info
2371
         * @param writer The writer to write to
2372
         * @param req the HTTP request
2373
         * @param oldBody the old version of the file, if requested
2374
         * @exception IOException if an input/output error occurs
2375
         * @throws RpcException
2376
         * @throws InsufficientPermissionsException
2377
         * @throws ObjectNotFoundException
2378
         */
2379
        protected void copy(FileHeader file, InputStream is, PrintWriter writer,
2380
                                HttpServletRequest req, FileBody oldBody) throws IOException,
2381
                                ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2382
                IOException exception = null;
2383
                
2384
                User user = getUser(req);
2385
                InputStream resourceInputStream = null;
2386
                if (file != null)
2387
                        resourceInputStream = oldBody == null ?
2388
                                                getService().getFileContents(user.getId(), file.getId()) :
2389
                                                getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2390
                else
2391
                        resourceInputStream = is;
2392

    
2393
                Reader reader;
2394
                if (fileEncoding == null)
2395
                        reader = new InputStreamReader(resourceInputStream);
2396
                else
2397
                        reader = new InputStreamReader(resourceInputStream, fileEncoding);
2398

    
2399
                // Copy the input stream to the output stream
2400
                exception = copyRange(reader, writer);
2401
                // Clean up the reader
2402
                reader.close();
2403
                // Rethrow any exception that has occurred
2404
                if (exception != null)
2405
                        throw exception;
2406
        }
2407

    
2408
        /**
2409
         * Copy the contents of the specified input stream to the specified output
2410
         * stream, and ensure that both streams are closed before returning (even in
2411
         * the face of an exception).
2412
         *
2413
         * @param reader The reader to read from
2414
         * @param writer The writer to write to
2415
         * @return Exception which occurred during processing
2416
         */
2417
        private IOException copyRange(Reader reader, PrintWriter writer) {
2418
                // Copy the input stream to the output stream
2419
                IOException exception = null;
2420
                char buffer[] = new char[input];
2421
                int len = buffer.length;
2422
                while (true)
2423
                        try {
2424
                                len = reader.read(buffer);
2425
                                if (len == -1)
2426
                                        break;
2427
                                writer.write(buffer, 0, len);
2428
                        } catch (IOException e) {
2429
                                exception = e;
2430
                                len = -1;
2431
                                break;
2432
                        }
2433
                return exception;
2434
        }
2435

    
2436
        /**
2437
         * Copy the contents of the specified input stream to the specified output
2438
         * stream, and ensure that both streams are closed before returning (even in
2439
         * the face of an exception).
2440
         *
2441
         * @param file
2442
         * @param writer The writer to write to
2443
         * @param ranges Enumeration of the ranges the client wanted to retrieve
2444
         * @param contentType Content type of the resource
2445
         * @param req the HTTP request
2446
         * @param oldBody the old version of the file, if requested
2447
         * @exception IOException if an input/output error occurs
2448
         * @throws RpcException
2449
         * @throws InsufficientPermissionsException
2450
         * @throws ObjectNotFoundException
2451
         */
2452
        protected void copy(FileHeader file, PrintWriter writer, Iterator ranges,
2453
                                String contentType, HttpServletRequest req, FileBody oldBody)
2454
                        throws IOException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2455
                User user = getUser(req);
2456
                IOException exception = null;
2457
                while (exception == null && ranges.hasNext()) {
2458
                        InputStream resourceInputStream = oldBody == null ?
2459
                                                getService().getFileContents(user.getId(), file.getId()) :
2460
                                                getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2461
                        Reader reader;
2462
                        if (fileEncoding == null)
2463
                                reader = new InputStreamReader(resourceInputStream);
2464
                        else
2465
                                reader = new InputStreamReader(resourceInputStream, fileEncoding);
2466
                        Range currentRange = (Range) ranges.next();
2467
                        // Writing MIME header.
2468
                        writer.println();
2469
                        writer.println("--" + mimeSeparation);
2470
                        if (contentType != null)
2471
                                writer.println("Content-Type: " + contentType);
2472
                        writer.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
2473
                        writer.println();
2474
                        // Printing content
2475
                        exception = copyRange(reader, writer, currentRange.start, currentRange.end);
2476
                        reader.close();
2477
                }
2478
                writer.println();
2479
                writer.print("--" + mimeSeparation + "--");
2480
                // Rethrow any exception that has occurred
2481
                if (exception != null)
2482
                        throw exception;
2483
        }
2484

    
2485
        /**
2486
         * Copy the contents of the specified input stream to the specified output
2487
         * stream, and ensure that both streams are closed before returning (even in
2488
         * the face of an exception).
2489
         *
2490
         * @param istream The input stream to read from
2491
         * @param ostream The output stream to write to
2492
         * @param start Start of the range which will be copied
2493
         * @param end End of the range which will be copied
2494
         * @return Exception which occurred during processing
2495
         */
2496
        private IOException copyRange(InputStream istream, ServletOutputStream ostream, long start, long end) {
2497
                if (logger.isDebugEnabled())
2498
                        logger.debug("Serving bytes:" + start + "-" + end);
2499
                try {
2500
                        istream.skip(start);
2501
                } catch (IOException e) {
2502
                        return e;
2503
                }
2504
                IOException exception = null;
2505
                long bytesToRead = end - start + 1;
2506
                byte buffer[] = new byte[input];
2507
                int len = buffer.length;
2508
                while (bytesToRead > 0 && len >= buffer.length) {
2509
                        try {
2510
                                len = istream.read(buffer);
2511
                                if (bytesToRead >= len) {
2512
                                        ostream.write(buffer, 0, len);
2513
                                        bytesToRead -= len;
2514
                                } else {
2515
                                        ostream.write(buffer, 0, (int) bytesToRead);
2516
                                        bytesToRead = 0;
2517
                                }
2518
                        } catch (IOException e) {
2519
                                exception = e;
2520
                                len = -1;
2521
                        }
2522
                        if (len < buffer.length)
2523
                                break;
2524
                }
2525
                return exception;
2526
        }
2527

    
2528
        /**
2529
         * Copy the contents of the specified input stream to the specified output
2530
         * stream, and ensure that both streams are closed before returning (even in
2531
         * the face of an exception).
2532
         *
2533
         * @param reader The reader to read from
2534
         * @param writer The writer to write to
2535
         * @param start Start of the range which will be copied
2536
         * @param end End of the range which will be copied
2537
         * @return Exception which occurred during processing
2538
         */
2539
        private IOException copyRange(Reader reader, PrintWriter writer, long start, long end) {
2540
                try {
2541
                        reader.skip(start);
2542
                } catch (IOException e) {
2543
                        return e;
2544
                }
2545
                IOException exception = null;
2546
                long bytesToRead = end - start + 1;
2547
                char buffer[] = new char[input];
2548
                int len = buffer.length;
2549
                while (bytesToRead > 0 && len >= buffer.length) {
2550
                        try {
2551
                                len = reader.read(buffer);
2552
                                if (bytesToRead >= len) {
2553
                                        writer.write(buffer, 0, len);
2554
                                        bytesToRead -= len;
2555
                                } else {
2556
                                        writer.write(buffer, 0, (int) bytesToRead);
2557
                                        bytesToRead = 0;
2558
                                }
2559
                        } catch (IOException e) {
2560
                                exception = e;
2561
                                len = -1;
2562
                        }
2563
                        if (len < buffer.length)
2564
                                break;
2565
                }
2566
                return exception;
2567
        }
2568

    
2569
        /**
2570
         * Copy the contents of the specified input stream to the specified output
2571
         * stream, and ensure that both streams are closed before returning (even in
2572
         * the face of an exception).
2573
         *
2574
         * @param file
2575
         * @param ostream The output stream to write to
2576
         * @param range Range the client wanted to retrieve
2577
         * @param req the HTTP request
2578
         * @param oldBody the old version of the file, if requested
2579
         * @exception IOException if an input/output error occurs
2580
         * @throws RpcException
2581
         * @throws InsufficientPermissionsException
2582
         * @throws ObjectNotFoundException
2583
         */
2584
        protected void copy(FileHeader file, ServletOutputStream ostream, Range range,
2585
                                HttpServletRequest req, FileBody oldBody) throws IOException,
2586
                                ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2587
                IOException exception = null;
2588
                User user = getUser(req);
2589
                InputStream resourceInputStream = oldBody == null ?
2590
                                        getService().getFileContents(user.getId(), file.getId()) :
2591
                                        getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2592
                InputStream istream = new BufferedInputStream(resourceInputStream, input);
2593
                exception = copyRange(istream, ostream, range.start, range.end);
2594
                // Clean up the input stream
2595
                istream.close();
2596
                // Rethrow any exception that has occurred
2597
                if (exception != null)
2598
                        throw exception;
2599
        }
2600

    
2601
        /**
2602
         * Copy the contents of the specified input stream to the specified output
2603
         * stream, and ensure that both streams are closed before returning (even in
2604
         * the face of an exception).
2605
         *
2606
         * @param file
2607
         * @param writer The writer to write to
2608
         * @param range Range the client wanted to retrieve
2609
         * @param req the HTTP request
2610
         * @param oldBody the old version of the file, if requested
2611
         * @exception IOException if an input/output error occurs
2612
         * @throws RpcException
2613
         * @throws InsufficientPermissionsException
2614
         * @throws ObjectNotFoundException
2615
         */
2616
        protected void copy(FileHeader file, PrintWriter writer, Range range,
2617
                                HttpServletRequest req, FileBody oldBody) throws IOException,
2618
                                ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2619
                IOException exception = null;
2620
                User user = getUser(req);
2621
                InputStream resourceInputStream = oldBody == null ?
2622
                                        getService().getFileContents(user.getId(), file.getId()) :
2623
                                        getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2624
                Reader reader;
2625
                if (fileEncoding == null)
2626
                        reader = new InputStreamReader(resourceInputStream);
2627
                else
2628
                        reader = new InputStreamReader(resourceInputStream, fileEncoding);
2629

    
2630
                exception = copyRange(reader, writer, range.start, range.end);
2631
                // Clean up the input stream
2632
                reader.close();
2633
                // Rethrow any exception that has occurred
2634
                if (exception != null)
2635
                        throw exception;
2636
        }
2637

    
2638
        /**
2639
         * Copy the contents of the specified input stream to the specified output
2640
         * stream, and ensure that both streams are closed before returning (even in
2641
         * the face of an exception).
2642
         *
2643
         * @param file
2644
         * @param ostream The output stream to write to
2645
         * @param ranges Enumeration of the ranges the client wanted to retrieve
2646
         * @param contentType Content type of the resource
2647
         * @param req the HTTP request
2648
         * @param oldBody the old version of the file, if requested
2649
         * @exception IOException if an input/output error occurs
2650
         * @throws RpcException
2651
         * @throws InsufficientPermissionsException
2652
         * @throws ObjectNotFoundException
2653
         */
2654
        protected void copy(FileHeader file, ServletOutputStream ostream,
2655
                                Iterator ranges, String contentType, HttpServletRequest req,
2656
                                FileBody oldBody) throws IOException, ObjectNotFoundException,
2657
                                InsufficientPermissionsException, RpcException {
2658
                IOException exception = null;
2659
                User user = getUser(req);
2660
                while (exception == null && ranges.hasNext()) {
2661
                        InputStream resourceInputStream = oldBody == null ?
2662
                                                getService().getFileContents(user.getId(), file.getId()) :
2663
                                                getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2664
                        InputStream istream = new BufferedInputStream(resourceInputStream, input);
2665
                        Range currentRange = (Range) ranges.next();
2666
                        // Writing MIME header.
2667
                        ostream.println();
2668
                        ostream.println("--" + mimeSeparation);
2669
                        if (contentType != null)
2670
                                ostream.println("Content-Type: " + contentType);
2671
                        ostream.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
2672
                        ostream.println();
2673

    
2674
                        // Printing content
2675
                        exception = copyRange(istream, ostream, currentRange.start, currentRange.end);
2676
                        istream.close();
2677
                }
2678

    
2679
                ostream.println();
2680
                ostream.print("--" + mimeSeparation + "--");
2681
                // Rethrow any exception that has occurred
2682
                if (exception != null)
2683
                        throw exception;
2684
        }
2685

    
2686
        /**
2687
         * Return an InputStream to an HTML representation of the contents of this
2688
         * directory.
2689
         *
2690
         * @param contextPath Context path to which our internal paths are relative
2691
         * @param path the requested path to the resource
2692
         * @param folder the specified directory
2693
         * @param req the HTTP request
2694
         * @return an input stream with the rendered contents
2695
         * @throws IOException
2696
         * @throws ServletException
2697
         */
2698
        private InputStream renderHtml(String contextPath, String path, Folder folder, HttpServletRequest req) throws IOException, ServletException {
2699
                String name = folder.getName();
2700
                // Prepare a writer to a buffered area
2701
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
2702
                OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2703
                PrintWriter writer = new PrintWriter(osWriter);
2704
                StringBuffer sb = new StringBuffer();
2705
                // rewriteUrl(contextPath) is expensive. cache result for later reuse
2706
                String rewrittenContextPath = rewriteUrl(contextPath);
2707
                // Render the page header
2708
                sb.append("<html>\r\n");
2709
                sb.append("<head>\r\n");
2710
                sb.append("<title>");
2711
                sb.append("Index of " + name);
2712
                sb.append("</title>\r\n");
2713
                sb.append("<STYLE><!--");
2714
                sb.append(GSS_CSS);
2715
                sb.append("--></STYLE> ");
2716
                sb.append("</head>\r\n");
2717
                sb.append("<body>");
2718
                sb.append("<h1>");
2719
                sb.append("Index of " + name);
2720

    
2721
                // Render the link to our parent (if required)
2722
                String parentDirectory = path;
2723
                if (parentDirectory.endsWith("/"))
2724
                        parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2725
                int slash = parentDirectory.lastIndexOf('/');
2726
                if (slash >= 0) {
2727
                        String parent = path.substring(0, slash);
2728
                        sb.append(" - <a href=\"");
2729
                        sb.append(rewrittenContextPath);
2730
                        if (parent.equals(""))
2731
                                parent = "/";
2732
                        sb.append(rewriteUrl(parent));
2733
                        if (!parent.endsWith("/"))
2734
                                sb.append("/");
2735
                        sb.append("\">");
2736
                        sb.append("<b>");
2737
                        sb.append("Up To " + parent);
2738
                        sb.append("</b>");
2739
                        sb.append("</a>");
2740
                }
2741

    
2742
                sb.append("</h1>");
2743
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2744

    
2745
                sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2746

    
2747
                // Render the column headings
2748
                sb.append("<tr>\r\n");
2749
                sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2750
                sb.append("Name");
2751
                sb.append("</strong></font></td>\r\n");
2752
                sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2753
                sb.append("Size");
2754
                sb.append("</strong></font></td>\r\n");
2755
                sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2756
                sb.append("Last modified");
2757
                sb.append("</strong></font></td>\r\n");
2758
                sb.append("</tr>");
2759
                // Render the directory entries within this directory
2760
                boolean shade = false;
2761
                Iterator iter = folder.getSubfolders().iterator();
2762
                while (iter.hasNext()) {
2763
                        Folder subf = (Folder) iter.next();
2764
                        String resourceName = subf.getName();
2765
                        if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2766
                                continue;
2767

    
2768
                        sb.append("<tr");
2769
                        if (shade)
2770
                                sb.append(" bgcolor=\"#eeeeee\"");
2771
                        sb.append(">\r\n");
2772
                        shade = !shade;
2773

    
2774
                        sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2775
                        sb.append("<a href=\"");
2776
                        sb.append(rewrittenContextPath);
2777
                        sb.append(rewriteUrl(path + resourceName));
2778
                        sb.append("/");
2779
                        sb.append("\"><tt>");
2780
                        sb.append(RequestUtil.filter(resourceName));
2781
                        sb.append("/");
2782
                        sb.append("</tt></a></td>\r\n");
2783

    
2784
                        sb.append("<td align=\"right\"><tt>");
2785
                        sb.append("&nbsp;");
2786
                        sb.append("</tt></td>\r\n");
2787

    
2788
                        sb.append("<td align=\"right\"><tt>");
2789
                        sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2790
                        sb.append("</tt></td>\r\n");
2791

    
2792
                        sb.append("</tr>\r\n");
2793
                }
2794
                List<FileHeader> files;
2795
                try {
2796
                        User user = getUser(req);
2797
                        files = getService().getFiles(user.getId(), folder.getId(), true);
2798
                } catch (ObjectNotFoundException e) {
2799
                        throw new ServletException(e.getMessage());
2800
                } catch (InsufficientPermissionsException e) {
2801
                        throw new ServletException(e.getMessage());
2802
                } catch (RpcException e) {
2803
                        throw new ServletException(e.getMessage());
2804
                }
2805
                for (FileHeader fileLocal : files) {
2806
                        String resourceName = fileLocal.getName();
2807
                        if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2808
                                continue;
2809

    
2810
                        sb.append("<tr");
2811
                        if (shade)
2812
                                sb.append(" bgcolor=\"#eeeeee\"");
2813
                        sb.append(">\r\n");
2814
                        shade = !shade;
2815

    
2816
                        sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2817
                        sb.append("<a href=\"");
2818
                        sb.append(rewrittenContextPath);
2819
                        sb.append(rewriteUrl(path + resourceName));
2820
                        sb.append("\"><tt>");
2821
                        sb.append(RequestUtil.filter(resourceName));
2822
                        sb.append("</tt></a></td>\r\n");
2823

    
2824
                        sb.append("<td align=\"right\"><tt>");
2825
                        sb.append(renderSize(fileLocal.getCurrentBody().getFileSize()));
2826
                        sb.append("</tt></td>\r\n");
2827

    
2828
                        sb.append("<td align=\"right\"><tt>");
2829
                        sb.append(getLastModifiedHttp(fileLocal.getAuditInfo()));
2830
                        sb.append("</tt></td>\r\n");
2831

    
2832
                        sb.append("</tr>\r\n");
2833
                }
2834

    
2835
                // Render the page footer
2836
                sb.append("</table>\r\n");
2837

    
2838
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2839

    
2840
                sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2841
                sb.append("</body>\r\n");
2842
                sb.append("</html>\r\n");
2843

    
2844
                // Return an input stream to the underlying bytes
2845
                writer.write(sb.toString());
2846
                writer.flush();
2847
                return new ByteArrayInputStream(stream.toByteArray());
2848

    
2849
        }
2850

    
2851
        /**
2852
         * Render the specified file size (in bytes).
2853
         *
2854
         * @param size File size (in bytes)
2855
         * @return the size as a string
2856
         */
2857
        protected String renderSize(long size) {
2858
                long leftSide = size / 1024;
2859
                long rightSide = size % 1024 / 103; // Makes 1 digit
2860
                if (leftSide == 0 && rightSide == 0 && size > 0)
2861
                        rightSide = 1;
2862
                return "" + leftSide + "." + rightSide + " kb";
2863
        }
2864

    
2865
        /**
2866
         * Copy a resource.
2867
         *
2868
         * @param req Servlet request
2869
         * @param resp Servlet response
2870
         * @return boolean true if the copy is successful
2871
         * @throws IOException
2872
         */
2873
        private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
2874
                // Parsing destination header
2875
                String destinationPath = req.getHeader("Destination");
2876
                if (destinationPath == null) {
2877
                        resp.sendError(WebdavStatus.SC_BAD_REQUEST);
2878
                        return false;
2879
                }
2880

    
2881
                // Remove url encoding from destination
2882
                destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
2883

    
2884
                int protocolIndex = destinationPath.indexOf("://");
2885
                if (protocolIndex >= 0) {
2886
                        // if the Destination URL contains the protocol, we can safely
2887
                        // trim everything upto the first "/" character after "://"
2888
                        int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4);
2889
                        if (firstSeparator < 0)
2890
                                destinationPath = "/";
2891
                        else
2892
                                destinationPath = destinationPath.substring(firstSeparator);
2893
                } else {
2894
                        String hostName = req.getServerName();
2895
                        if (hostName != null && destinationPath.startsWith(hostName))
2896
                                destinationPath = destinationPath.substring(hostName.length());
2897

    
2898
                        int portIndex = destinationPath.indexOf(":");
2899
                        if (portIndex >= 0)
2900
                                destinationPath = destinationPath.substring(portIndex);
2901

    
2902
                        if (destinationPath.startsWith(":")) {
2903
                                int firstSeparator = destinationPath.indexOf("/");
2904
                                if (firstSeparator < 0)
2905
                                        destinationPath = "/";
2906
                                else
2907
                                        destinationPath = destinationPath.substring(firstSeparator);
2908
                        }
2909
                }
2910

    
2911
                // Normalize destination path (remove '.' and '..')
2912
                destinationPath = RequestUtil.normalize(destinationPath);
2913

    
2914
                String contextPath = req.getContextPath();
2915
                if (contextPath != null && destinationPath.startsWith(contextPath))
2916
                        destinationPath = destinationPath.substring(contextPath.length());
2917

    
2918
                String pathInfo = req.getPathInfo();
2919
                if (pathInfo != null) {
2920
                        String servletPath = req.getServletPath();
2921
                        if (servletPath != null && destinationPath.startsWith(servletPath))
2922
                                destinationPath = destinationPath.substring(servletPath.length());
2923
                }
2924

    
2925
                if (logger.isDebugEnabled())
2926
                        logger.debug("Dest path :" + destinationPath);
2927

    
2928
                if (destinationPath.toUpperCase().startsWith("/WEB-INF") || destinationPath.toUpperCase().startsWith("/META-INF")) {
2929
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
2930
                        return false;
2931
                }
2932

    
2933
                String path = getRelativePath(req);
2934

    
2935
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
2936
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
2937
                        return false;
2938
                }
2939

    
2940
                if (destinationPath.equals(path)) {
2941
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
2942
                        return false;
2943
                }
2944

    
2945
                // Parsing overwrite header
2946
                boolean overwrite = true;
2947
                String overwriteHeader = req.getHeader("Overwrite");
2948

    
2949
                if (overwriteHeader != null)
2950
                        if (overwriteHeader.equalsIgnoreCase("T"))
2951
                                overwrite = true;
2952
                        else
2953
                                overwrite = false;
2954

    
2955
                User user = getUser(req);
2956
                // Overwriting the destination
2957
                boolean exists = true;
2958
                try {
2959
                        getService().getResourceAtPath(user.getId(), destinationPath, true);
2960
                } catch (ObjectNotFoundException e) {
2961
                        exists = false;
2962
                } catch (RpcException e) {
2963
                        resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
2964
                        return false;
2965
                }
2966

    
2967
                if (overwrite) {
2968
                        // Delete destination resource, if it exists
2969
                        if (exists) {
2970
                                if (!deleteResource(destinationPath, req, resp, true))
2971
                                        return false;
2972
                        } else
2973
                                resp.setStatus(WebdavStatus.SC_CREATED);
2974
                } else // If the destination exists, then it's a conflict
2975
                if (exists) {
2976
                        resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
2977
                        return false;
2978
                } else
2979
                        resp.setStatus(WebdavStatus.SC_CREATED);
2980

    
2981
                // Copying source to destination.
2982
                Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
2983
                boolean result;
2984
                try {
2985
                        result = copyResource(errorList, path, destinationPath, req);
2986
                } catch (RpcException e) {
2987
                        resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
2988
                        return false;
2989
                }
2990
                if (!result || !errorList.isEmpty()) {
2991
                        sendReport(req, resp, errorList);
2992
                        return false;
2993
                }
2994
                return true;
2995
        }
2996

    
2997
        /**
2998
         * Copy a collection.
2999
         *
3000
         * @param errorList Hashtable containing the list of errors which occurred
3001
         *            during the copy operation
3002
         * @param source Path of the resource to be copied
3003
         * @param theDest Destination path
3004
         * @param req the HTTP request
3005
         * @return boolean true if the copy is successful
3006
         * @throws RpcException
3007
         */
3008
        private boolean copyResource(Hashtable<String, Integer> errorList, String source, String theDest, HttpServletRequest req) throws RpcException {
3009

    
3010
                String dest = theDest;
3011
                // Fix the destination path when copying collections.
3012
                if (source.endsWith("/") && !dest.endsWith("/"))
3013
                        dest += "/";
3014

    
3015
                if (logger.isDebugEnabled())
3016
                        logger.debug("Copy: " + source + " To: " + dest);
3017

    
3018
                final User user = getUser(req);
3019
                Object object = null;
3020
                try {
3021
                        object = getService().getResourceAtPath(user.getId(), source, true);
3022
                } catch (ObjectNotFoundException e) {
3023
                }
3024

    
3025
                if (object instanceof Folder) {
3026
                        final Folder folderLocal = (Folder) object;
3027
                        try {
3028
                                final String des = dest;
3029
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3030
                                        @Override
3031
                                        public Void call() throws Exception {
3032
                                                getService().copyFolder(user.getId(), folderLocal.getId(), des);
3033
                                                return null;
3034
                                        }
3035
                                });
3036
                        } catch (ObjectNotFoundException e) {
3037
                                errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3038
                                return false;
3039
                        } catch (DuplicateNameException e) {
3040
                                errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3041
                                return false;
3042
                        } catch (InsufficientPermissionsException e) {
3043
                                errorList.put(dest, new Integer(WebdavStatus.SC_FORBIDDEN));
3044
                                return false;
3045
                        } catch (Exception e) {
3046
                                errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3047
                                return false;
3048
                        }
3049

    
3050
                        try {
3051
                                String newSource = source;
3052
                                if (!source.endsWith("/"))
3053
                                        newSource += "/";
3054
                                String newDest = dest;
3055
                                if (!dest.endsWith("/"))
3056
                                        newDest += "/";
3057
                                // Recursively copy the subfolders.
3058
                                Iterator iter = folderLocal.getSubfolders().iterator();
3059
                                while (iter.hasNext()) {
3060
                                        Folder subf = (Folder) iter.next();
3061
                                        String resourceName = subf.getName();
3062
                                        copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3063
                                }
3064
                                // Recursively copy the files.
3065
                                List<FileHeader> files;
3066
                                files = getService().getFiles(user.getId(), folderLocal.getId(), true);
3067
                                for (FileHeader file : files) {
3068
                                        String resourceName = file.getName();
3069
                                        copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3070
                                }
3071
                        } catch (RpcException e) {
3072
                                errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3073
                                return false;
3074
                        } catch (ObjectNotFoundException e) {
3075
                                errorList.put(source, new Integer(WebdavStatus.SC_NOT_FOUND));
3076
                                return false;
3077
                        } catch (InsufficientPermissionsException e) {
3078
                                errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3079
                                return false;
3080
                        }
3081

    
3082
                } else if (object instanceof FileHeader) {
3083
                        final FileHeader fileLocal = (FileHeader) object;
3084
                        try {
3085
                                final String des = dest;
3086
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3087
                                        @Override
3088
                                        public Void call() throws Exception {
3089
                                                getService().copyFile(user.getId(), fileLocal.getId(), des);
3090
                                                return null;
3091
                                        }
3092
                                });
3093
                        } catch (ObjectNotFoundException e) {
3094
                                errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3095
                                return false;
3096
                        } catch (DuplicateNameException e) {
3097
                                errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT));
3098
                                return false;
3099
                        } catch (InsufficientPermissionsException e) {
3100
                                errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3101
                                return false;
3102
                        } catch (QuotaExceededException e) {
3103
                                errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3104
                                return false;
3105
                        } catch (GSSIOException e) {
3106
                                errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3107
                                return false;
3108
                        } catch (Exception e) {
3109
                                errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3110
                                return false;
3111
                        }
3112
                } else {
3113
                        errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3114
                        return false;
3115
                }
3116
                return true;
3117
        }
3118

    
3119
        /**
3120
         * Delete a resource.
3121
         *
3122
         * @param req Servlet request
3123
         * @param resp Servlet response
3124
         * @return boolean true if the deletion is successful
3125
         * @throws IOException
3126
         */
3127
        private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3128
                String path = getRelativePath(req);
3129
                return deleteResource(path, req, resp, true);
3130
        }
3131

    
3132
        /**
3133
         * Delete a resource.
3134
         *
3135
         * @param path Path of the resource which is to be deleted
3136
         * @param req Servlet request
3137
         * @param resp Servlet response
3138
         * @param setStatus Should the response status be set on successful
3139
         *            completion
3140
         * @return boolean true if the deletion is successful
3141
         * @throws IOException
3142
         */
3143
        private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws IOException {
3144
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3145
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
3146
                        return false;
3147
                }
3148
                String ifHeader = req.getHeader("If");
3149
                if (ifHeader == null)
3150
                        ifHeader = "";
3151

    
3152
                String lockTokenHeader = req.getHeader("Lock-Token");
3153
                if (lockTokenHeader == null)
3154
                        lockTokenHeader = "";
3155

    
3156
                if (isLocked(path, ifHeader + lockTokenHeader)) {
3157
                        resp.sendError(WebdavStatus.SC_LOCKED);
3158
                        return false;
3159
                }
3160

    
3161
                final User user = getUser(req);
3162
                boolean exists = true;
3163
                Object object = null;
3164
                try {
3165
                        object = getService().getResourceAtPath(user.getId(), path, true);
3166
                } catch (ObjectNotFoundException e) {
3167
                        exists = false;
3168
                } catch (RpcException e) {
3169
                        resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3170
                        return false;
3171
                }
3172

    
3173
                if (!exists) {
3174
                        resp.sendError(WebdavStatus.SC_NOT_FOUND);
3175
                        return false;
3176
                }
3177

    
3178
                Folder folderLocal = null;
3179
                FileHeader fileLocal = null;
3180
                if (object instanceof Folder)
3181
                        folderLocal = (Folder) object;
3182
                else
3183
                        fileLocal = (FileHeader) object;
3184

    
3185
                if (fileLocal != null)
3186
                        try {
3187
                                final FileHeader f = fileLocal;
3188
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3189
                                        @Override
3190
                                        public Void call() throws Exception {
3191
                                                getService().deleteFile(user.getId(), f.getId());
3192
                                                return null;
3193
                                        }
3194
                                });
3195
                        } catch (InsufficientPermissionsException e) {
3196
                                resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
3197
                                return false;
3198
                        } catch (ObjectNotFoundException e) {
3199
                                // Although we had already found the object, it was
3200
                                // probably deleted from another thread.
3201
                                resp.sendError(WebdavStatus.SC_NOT_FOUND);
3202
                                return false;
3203
                        } catch (RpcException e) {
3204
                                resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3205
                                return false;
3206
                        } catch (Exception e) {
3207
                                resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3208
                                return false;
3209
                        }
3210
                else if (folderLocal != null) {
3211
                        Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3212
                        deleteCollection(req, folderLocal, path, errorList);
3213
                        try {
3214
                                final Folder f = folderLocal;
3215
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3216
                                        @Override
3217
                                        public Void call() throws Exception {
3218
                                                getService().deleteFolder(user.getId(), f.getId());
3219
                                                return null;
3220
                                        }
3221
                                });
3222
                        } catch (InsufficientPermissionsException e) {
3223
                                errorList.put(path, new Integer(WebdavStatus.SC_METHOD_NOT_ALLOWED));
3224
                        } catch (ObjectNotFoundException e) {
3225
                                errorList.put(path, new Integer(WebdavStatus.SC_NOT_FOUND));
3226
                        } catch (RpcException e) {
3227
                                errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3228
                        } catch (Exception e) {
3229
                                errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3230
                        }
3231

    
3232
                        if (!errorList.isEmpty()) {
3233
                                sendReport(req, resp, errorList);
3234
                                return false;
3235
                        }
3236
                }
3237
                if (setStatus)
3238
                        resp.setStatus(WebdavStatus.SC_NO_CONTENT);
3239
                return true;
3240
        }
3241

    
3242
        /**
3243
         * Deletes a collection.
3244
         *
3245
         * @param req the HTTP request
3246
         * @param folder the folder whose contents will be deleted
3247
         * @param path Path to the collection to be deleted
3248
         * @param errorList Contains the list of the errors which occurred
3249
         */
3250
        private void deleteCollection(HttpServletRequest req, Folder folder, String path, Hashtable<String, Integer> errorList) {
3251

    
3252
                if (logger.isDebugEnabled())
3253
                        logger.debug("Delete:" + path);
3254

    
3255
                if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3256
                        errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
3257
                        return;
3258
                }
3259

    
3260
                String ifHeader = req.getHeader("If");
3261
                if (ifHeader == null)
3262
                        ifHeader = "";
3263

    
3264
                String lockTokenHeader = req.getHeader("Lock-Token");
3265
                if (lockTokenHeader == null)
3266
                        lockTokenHeader = "";
3267

    
3268
                Iterator iter = folder.getSubfolders().iterator();
3269
                while (iter.hasNext()) {
3270
                        Folder subf = (Folder) iter.next();
3271
                        String childName = path;
3272
                        if (!childName.equals("/"))
3273
                                childName += "/";
3274
                        childName += subf.getName();
3275

    
3276
                        if (isLocked(childName, ifHeader + lockTokenHeader))
3277
                                errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
3278
                        else
3279
                                try {
3280
                                        final User user = getUser(req);
3281
                                        Object object = getService().getResourceAtPath(user.getId(), childName, true);
3282
                                        Folder childFolder = null;
3283
                                        FileHeader childFile = null;
3284
                                        if (object instanceof Folder)
3285
                                                childFolder = (Folder) object;
3286
                                        else
3287
                                                childFile = (FileHeader) object;
3288
                                        if (childFolder != null) {
3289
                                                final Folder cf = childFolder;
3290
                                                deleteCollection(req, childFolder, childName, errorList);
3291
                                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3292
                                                        @Override
3293
                                                        public Void call() throws Exception {
3294
                                                                getService().deleteFolder(user.getId(), cf.getId());
3295
                                                                return null;
3296
                                                        }
3297
                                                });
3298
                                        } else if (childFile != null) {
3299
                                                final FileHeader cf = childFile;
3300
                                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3301
                                                        @Override
3302
                                                        public Void call() throws Exception {
3303
                                                                getService().deleteFile(user.getId(), cf.getId());
3304
                                                                return null;
3305
                                                        }
3306
                                                });
3307
                                        }
3308
                                } catch (ObjectNotFoundException e) {
3309
                                        errorList.put(childName, new Integer(WebdavStatus.SC_NOT_FOUND));
3310
                                } catch (InsufficientPermissionsException e) {
3311
                                        errorList.put(childName, new Integer(WebdavStatus.SC_FORBIDDEN));
3312
                                } catch (RpcException e) {
3313
                                        errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3314
                                } catch (Exception e) {
3315
                                        errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3316
                                }
3317
                }
3318
        }
3319

    
3320
        /**
3321
         * Send a multistatus element containing a complete error report to the
3322
         * client.
3323
         *
3324
         * @param req Servlet request
3325
         * @param resp Servlet response
3326
         * @param errorList List of error to be displayed
3327
         * @throws IOException
3328
         */
3329
        private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable errorList) throws IOException {
3330

    
3331
                resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3332

    
3333
                String absoluteUri = req.getRequestURI();
3334
                String relativePath = getRelativePath(req);
3335

    
3336
                XMLWriter generatedXML = new XMLWriter();
3337
                generatedXML.writeXMLHeader();
3338

    
3339
                generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
3340

    
3341
                Enumeration pathList = errorList.keys();
3342
                while (pathList.hasMoreElements()) {
3343

    
3344
                        String errorPath = (String) pathList.nextElement();
3345
                        int errorCode = ((Integer) errorList.get(errorPath)).intValue();
3346

    
3347
                        generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3348

    
3349
                        generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3350
                        String toAppend = errorPath.substring(relativePath.length());
3351
                        if (!toAppend.startsWith("/"))
3352
                                toAppend = "/" + toAppend;
3353
                        generatedXML.writeText(absoluteUri + toAppend);
3354
                        generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3355
                        generatedXML.writeElement(null, "status", XMLWriter.OPENING);
3356
                        generatedXML.writeText("HTTP/1.1 " + errorCode + " " + WebdavStatus.getStatusText(errorCode));
3357
                        generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
3358

    
3359
                        generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
3360

    
3361
                }
3362

    
3363
                generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
3364

    
3365
                Writer writer = resp.getWriter();
3366
                writer.write(generatedXML.toString());
3367
                writer.close();
3368

    
3369
        }
3370

    
3371
        // --------------------------------------------- WebdavResolver Inner Class
3372
        /**
3373
         * Work around for XML parsers that don't fully respect
3374
         * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)}.
3375
         * External references are filtered out for security reasons. See
3376
         * CVE-2007-5461.
3377
         */
3378
        private class WebdavResolver implements EntityResolver {
3379

    
3380
                /**
3381
                 * A private copy of the servlet context.
3382
                 */
3383
                private ServletContext context;
3384

    
3385
                /**
3386
                 * Construct the resolver by passing the servlet context.
3387
                 *
3388
                 * @param theContext the servlet context
3389
                 */
3390
                public WebdavResolver(ServletContext theContext) {
3391
                        context = theContext;
3392
                }
3393

    
3394
                @Override
3395
                public InputSource resolveEntity(String publicId, String systemId) {
3396
                        context.log("The request included a reference to an external entity with PublicID " + publicId + " and SystemID " + systemId + " which was ignored");
3397
                        return new InputSource(new StringReader("Ignored external entity"));
3398
                }
3399
        }
3400

    
3401
        /**
3402
         * Returns the user making the request. This is the user whose credentials
3403
         * were supplied in the authorization header.
3404
         *
3405
         * @param req the HTTP request
3406
         * @return the user making the request
3407
         */
3408
        protected User getUser(HttpServletRequest req) {
3409
                return (User) req.getAttribute(USER_ATTRIBUTE);
3410
        }
3411

    
3412
        /**
3413
         * Retrieves the user who owns the requested namespace, as specified in the
3414
         * REST API.
3415
         *
3416
         * @param req the HTTP request
3417
         * @return the owner of the namespace
3418
         */
3419
        protected User getOwner(HttpServletRequest req) {
3420
                return (User) req.getAttribute(OWNER_ATTRIBUTE);
3421
        }
3422

    
3423
        /**
3424
         * Check if the if-modified-since condition is satisfied.
3425
         *
3426
         * @param request The servlet request we are processing
3427
         * @param response The servlet response we are creating
3428
         * @param folder the folder object
3429
         * @return boolean true if the resource meets the specified condition, and
3430
         *         false if the condition is not satisfied, in which case request
3431
         *         processing is stopped
3432
         */
3433
        public boolean checkIfModifiedSince(HttpServletRequest request,
3434
                                HttpServletResponse response, Folder folder) {
3435
                try {
3436
                        long headerValue = request.getDateHeader("If-Modified-Since");
3437
                        long lastModified = folder.getAuditInfo().getModificationDate().getTime();
3438
                        if (headerValue != -1)
3439
                                // If an If-None-Match header has been specified, if modified
3440
                                // since is ignored.
3441
                                if (request.getHeader("If-None-Match") == null && lastModified < headerValue + 1000) {
3442
                                        // The entity has not been modified since the date
3443
                                        // specified by the client. This is not an error case.
3444
                                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
3445
                                        return false;
3446
                                }
3447
                } catch (IllegalArgumentException illegalArgument) {
3448
                        return true;
3449
                }
3450
                return true;
3451
        }
3452

    
3453
}