Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / webdav / Webdav.java @ b8e09949

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.webdav;
20

    
21
import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import gr.ebs.gss.client.exceptions.DuplicateNameException;
23
import gr.ebs.gss.client.exceptions.GSSIOException;
24
import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25
import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26
import gr.ebs.gss.client.exceptions.QuotaExceededException;
27
import gr.ebs.gss.client.exceptions.RpcException;
28
import gr.ebs.gss.server.domain.User;
29
import gr.ebs.gss.server.domain.dto.AuditInfoDTO;
30
import gr.ebs.gss.server.domain.dto.FileBodyDTO;
31
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
32
import gr.ebs.gss.server.domain.dto.FolderDTO;
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 org.apache.commons.httpclient.HttpStatus;
85
import org.apache.commons.logging.Log;
86
import org.apache.commons.logging.LogFactory;
87
import org.w3c.dom.Document;
88
import org.w3c.dom.Element;
89
import org.w3c.dom.Node;
90
import org.w3c.dom.NodeList;
91
import org.xml.sax.EntityResolver;
92
import org.xml.sax.InputSource;
93
import org.xml.sax.SAXException;
94

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

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

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

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

    
118
        /**
119
     *
120
     */
121
        protected static final String METHOD_GET = "GET";
122

    
123
        /**
124
     *
125
     */
126
        protected static final String METHOD_POST = "POST";
127

    
128
        /**
129
     *
130
     */
131
        protected static final String METHOD_PUT = "PUT";
132

    
133
        /**
134
     *
135
     */
136
        protected static final String METHOD_DELETE = "DELETE";
137

    
138
        /**
139
     *
140
     */
141
        protected static final String METHOD_HEAD = "HEAD";
142

    
143
        /**
144
     *
145
     */
146
        private static final String METHOD_OPTIONS = "OPTIONS";
147

    
148
        /**
149
     *
150
     */
151
        private static final String METHOD_PROPFIND = "PROPFIND";
152

    
153
        /**
154
     *
155
     */
156
        private static final String METHOD_PROPPATCH = "PROPPATCH";
157

    
158
        /**
159
     *
160
     */
161
        private static final String METHOD_MKCOL = "MKCOL";
162

    
163
        /**
164
     *
165
     */
166
        private static final String METHOD_COPY = "COPY";
167

    
168
        /**
169
     *
170
     */
171
        private static final String METHOD_MOVE = "MOVE";
172

    
173
        /**
174
     *
175
     */
176
        private static final String METHOD_LOCK = "LOCK";
177

    
178
        /**
179
     *
180
     */
181
        private static final String METHOD_UNLOCK = "UNLOCK";
182

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
269
        /**
270
         * The style sheet for displaying the directory listings.
271
         */
272
        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;}";
273

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

    
279

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

    
285
        /**
286
         * MD5 message digest provider.
287
         */
288
        protected static MessageDigest md5Helper;
289

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

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

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

    
313
                if (getServletConfig().getInitParameter("output") != null)
314
                        output = Integer.parseInt(getServletConfig().getInitParameter("output"));
315

    
316
                fileEncoding = getServletConfig().getInitParameter("fileEncoding");
317

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

    
326
                if (getServletConfig().getInitParameter("secret") != null)
327
                        secret = getServletConfig().getInitParameter("secret");
328

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

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

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

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

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

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

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

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

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

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

    
475
                String depthStr = req.getHeader("Depth");
476

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

    
486
                Node propNode = null;
487

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
715
                Range range = parseContentRange(req, resp);
716

    
717
                InputStream resourceInputStream = null;
718

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

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

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

    
804
        }
805

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

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

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

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

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

    
882
                int lockRequestType = LOCK_CREATION;
883
                Node lockInfoNode = null;
884
                DocumentBuilder documentBuilder = getDocumentBuilder();
885

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

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

    
903
                        Node lockScopeNode = null;
904
                        Node lockTypeNode = null;
905
                        Node lockOwnerNode = null;
906

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

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

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

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

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

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

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

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

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

    
1025
                        }
1026
                }
1027

    
1028
                if (lockRequestType == LOCK_REFRESH) {
1029

    
1030
                }
1031

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

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

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

    
1063
                String path = getRelativePath(req);
1064

    
1065
                if (copyResource(req, resp))
1066
                        deleteResource(path, req, resp, false);
1067
        }
1068

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

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

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

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

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

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

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

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

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

    
1258
        }
1259

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

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

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

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

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

    
1325
                FolderDTO folder = null;
1326
                FileHeaderDTO file = null;
1327
                if (resource instanceof FolderDTO)
1328
                        folder = (FolderDTO) resource;
1329
                else
1330
                        file = (FileHeaderDTO) resource;
1331
                // Retrieve the creation date.
1332
                long creation = 0;
1333
                if (folder != null)
1334
                        creation = folder.getAuditInfo().getCreationDate().getTime();
1335
                else
1336
                        creation = file.getAuditInfo().getCreationDate().getTime();
1337
                // Retrieve the modification date.
1338
                long modification = 0;
1339
                if (folder != null)
1340
                        modification = folder.getAuditInfo().getCreationDate().getTime();
1341
                else
1342
                        modification = file.getAuditInfo().getCreationDate().getTime();
1343

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

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

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

    
1358
                generatedXML.writeText(rewriteUrl(href));
1359

    
1360
                generatedXML.writeElement(null, "D:href", XMLWriter.CLOSING);
1361

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

    
1369
                switch (type) {
1370

    
1371
                        case FIND_ALL_PROP:
1372

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

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

    
1394
                                generatedXML.writeProperty(null, "D:source", "");
1395

    
1396
                                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>";
1397
                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.OPENING);
1398
                                generatedXML.writeText(supportedLocks);
1399
                                generatedXML.writeElement(null, "D:supportedlock", XMLWriter.CLOSING);
1400

    
1401
                                generateLockDiscovery(path, generatedXML);
1402

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

    
1409
                                break;
1410

    
1411
                        case FIND_PROPERTY_NAMES:
1412

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

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

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

    
1435
                                break;
1436

    
1437
                        case FIND_BY_PROPERTY:
1438

    
1439
                                Vector<String> propertiesNotFound = new Vector<String>();
1440

    
1441
                                // Parse the list of properties
1442

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

    
1446
                                Enumeration<String> properties = propertiesVector.elements();
1447

    
1448
                                while (properties.hasMoreElements()) {
1449

    
1450
                                        String property = properties.nextElement();
1451

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

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

    
1513
                                Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1514

    
1515
                                if (propertiesNotFoundList.hasMoreElements()) {
1516

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

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

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

    
1531
                                break;
1532

    
1533
                }
1534

    
1535
                generatedXML.writeElement(null, "D:response", XMLWriter.CLOSING);
1536

    
1537
        }
1538

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

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

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

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

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

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

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

    
1638
                if (!(object instanceof FolderDTO))
1639
                        methodsAllowed.append(", PUT");
1640

    
1641
                return methodsAllowed;
1642
        }
1643

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

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

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

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

    
1684
                if (rangeHeader == null)
1685
                        return null;
1686

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

    
1693
                rangeHeader = rangeHeader.substring(6).trim();
1694

    
1695
                int dashPos = rangeHeader.indexOf('-');
1696
                int slashPos = rangeHeader.indexOf('/');
1697

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

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

    
1708
                Range range = new Range();
1709

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

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

    
1724
                return range;
1725

    
1726
        }
1727

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

    
1754
                RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw");
1755

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

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

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

    
1777
                        bufOldRevStream.close();
1778
                }
1779

    
1780
                randAccessContentFile.setLength(range.length);
1781

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

    
1792
                return contentFile;
1793

    
1794
        }
1795

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

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

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

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

    
1837
                if (resource instanceof FolderDTO)
1838
                        folder = (FolderDTO) resource;
1839
                else
1840
                        file = (FileHeaderDTO) resource;
1841

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

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

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

    
1868
                ArrayList ranges = null;
1869
                long contentLength = -1L;
1870

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

    
1888
                ServletOutputStream ostream = null;
1889
                PrintWriter writer = null;
1890

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

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

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

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

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

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

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

    
1974
                        } else {
1975

    
1976
                                resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
1977

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

    
1990
                        }
1991

    
1992
                }
1993

    
1994
        }
1995

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

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

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

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

    
2054
                long fileLength = oldBody == null ? file.getFileSize() : oldBody.getFileSize();
2055
                if (fileLength == 0)
2056
                        return null;
2057

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

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

    
2071
                rangeHeader = rangeHeader.substring(6);
2072

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

    
2081
                        Range currentRange = new Range();
2082
                        currentRange.length = fileLength;
2083

    
2084
                        int dashPos = rangeDefinition.indexOf('-');
2085

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

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

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

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

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

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

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

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

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

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

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

    
2364
        /**
2365
         * Copy the contents of the specified input stream to the specified output
2366
         * stream, and ensure that both streams are closed before returning (even in
2367
         * the face of an exception).
2368
         *
2369
         * @param file
2370
         * @param is
2371
         * @param resourceInfo The resource info
2372
         * @param writer The writer to write to
2373
         * @param req the HTTP request
2374
         * @param oldBody the old version of the file, if requested
2375
         * @exception IOException if an input/output error occurs
2376
         * @throws RpcException
2377
         * @throws InsufficientPermissionsException
2378
         * @throws ObjectNotFoundException
2379
         */
2380
        protected void copy(FileHeaderDTO file, InputStream is, PrintWriter writer,
2381
                                HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2382
                                ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2383
                IOException exception = null;
2384

    
2385
                User user = getUser(req);
2386
                InputStream resourceInputStream = null;
2387
                if (file != null)
2388
                        resourceInputStream = oldBody == null ?
2389
                                                getService().getFileContents(user.getId(), file.getId()) :
2390
                                                getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2391
                else
2392
                        resourceInputStream = is;
2393

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

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

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

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

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

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

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

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

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

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

    
2677
                        // Printing content
2678
                        exception = copyRange(istream, ostream, currentRange.start, currentRange.end);
2679
                        istream.close();
2680
                }
2681

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

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

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

    
2745
                sb.append("</h1>");
2746
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2747

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

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

    
2771
                        sb.append("<tr");
2772
                        if (shade)
2773
                                sb.append(" bgcolor=\"#eeeeee\"");
2774
                        sb.append(">\r\n");
2775
                        shade = !shade;
2776

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

    
2787
                        sb.append("<td align=\"right\"><tt>");
2788
                        sb.append("&nbsp;");
2789
                        sb.append("</tt></td>\r\n");
2790

    
2791
                        sb.append("<td align=\"right\"><tt>");
2792
                        sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2793
                        sb.append("</tt></td>\r\n");
2794

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

    
2813
                        sb.append("<tr");
2814
                        if (shade)
2815
                                sb.append(" bgcolor=\"#eeeeee\"");
2816
                        sb.append(">\r\n");
2817
                        shade = !shade;
2818

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

    
2827
                        sb.append("<td align=\"right\"><tt>");
2828
                        sb.append(renderSize(file.getFileSize()));
2829
                        sb.append("</tt></td>\r\n");
2830

    
2831
                        sb.append("<td align=\"right\"><tt>");
2832
                        sb.append(getLastModifiedHttp(file.getAuditInfo()));
2833
                        sb.append("</tt></td>\r\n");
2834

    
2835
                        sb.append("</tr>\r\n");
2836
                }
2837

    
2838
                // Render the page footer
2839
                sb.append("</table>\r\n");
2840

    
2841
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2842

    
2843
                sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2844
                sb.append("</body>\r\n");
2845
                sb.append("</html>\r\n");
2846

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

    
2852
        }
2853

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

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

    
2884
                // Remove url encoding from destination
2885
                destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
2886

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

    
2901
                        int portIndex = destinationPath.indexOf(":");
2902
                        if (portIndex >= 0)
2903
                                destinationPath = destinationPath.substring(portIndex);
2904

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

    
2914
                // Normalize destination path (remove '.' and '..')
2915
                destinationPath = RequestUtil.normalize(destinationPath);
2916

    
2917
                String contextPath = req.getContextPath();
2918
                if (contextPath != null && destinationPath.startsWith(contextPath))
2919
                        destinationPath = destinationPath.substring(contextPath.length());
2920

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

    
2928
                if (logger.isDebugEnabled())
2929
                        logger.debug("Dest path :" + destinationPath);
2930

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

    
2936
                String path = getRelativePath(req);
2937

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

    
2943
                if (destinationPath.equals(path)) {
2944
                        resp.sendError(WebdavStatus.SC_FORBIDDEN);
2945
                        return false;
2946
                }
2947

    
2948
                // Parsing overwrite header
2949
                boolean overwrite = true;
2950
                String overwriteHeader = req.getHeader("Overwrite");
2951

    
2952
                if (overwriteHeader != null)
2953
                        if (overwriteHeader.equalsIgnoreCase("T"))
2954
                                overwrite = true;
2955
                        else
2956
                                overwrite = false;
2957

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

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

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

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

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

    
3018
                if (logger.isDebugEnabled())
3019
                        logger.debug("Copy: " + source + " To: " + dest);
3020

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

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

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

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

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

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

    
3155
                String lockTokenHeader = req.getHeader("Lock-Token");
3156
                if (lockTokenHeader == null)
3157
                        lockTokenHeader = "";
3158

    
3159
                if (isLocked(path, ifHeader + lockTokenHeader)) {
3160
                        resp.sendError(WebdavStatus.SC_LOCKED);
3161
                        return false;
3162
                }
3163

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

    
3176
                if (!exists) {
3177
                        resp.sendError(WebdavStatus.SC_NOT_FOUND);
3178
                        return false;
3179
                }
3180

    
3181
                FolderDTO folder = null;
3182
                FileHeaderDTO file = null;
3183
                if (object instanceof FolderDTO)
3184
                        folder = (FolderDTO) object;
3185
                else
3186
                        file = (FileHeaderDTO) object;
3187

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

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

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

    
3255
                if (logger.isDebugEnabled())
3256
                        logger.debug("Delete:" + path);
3257

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

    
3263
                String ifHeader = req.getHeader("If");
3264
                if (ifHeader == null)
3265
                        ifHeader = "";
3266

    
3267
                String lockTokenHeader = req.getHeader("Lock-Token");
3268
                if (lockTokenHeader == null)
3269
                        lockTokenHeader = "";
3270

    
3271
                Iterator iter = folder.getSubfolders().iterator();
3272
                while (iter.hasNext()) {
3273
                        FolderDTO subf = (FolderDTO) iter.next();
3274
                        String childName = path;
3275
                        if (!childName.equals("/"))
3276
                                childName += "/";
3277
                        childName += subf.getName();
3278

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

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

    
3334
                resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3335

    
3336
                String absoluteUri = req.getRequestURI();
3337
                String relativePath = getRelativePath(req);
3338

    
3339
                XMLWriter generatedXML = new XMLWriter();
3340
                generatedXML.writeXMLHeader();
3341

    
3342
                generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
3343

    
3344
                Enumeration pathList = errorList.keys();
3345
                while (pathList.hasMoreElements()) {
3346

    
3347
                        String errorPath = (String) pathList.nextElement();
3348
                        int errorCode = ((Integer) errorList.get(errorPath)).intValue();
3349

    
3350
                        generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3351

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

    
3362
                        generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
3363

    
3364
                }
3365

    
3366
                generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
3367

    
3368
                Writer writer = resp.getWriter();
3369
                writer.write(generatedXML.toString());
3370
                writer.close();
3371

    
3372
        }
3373

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

    
3383
                /**
3384
                 * A private copy of the servlet context.
3385
                 */
3386
                private ServletContext context;
3387

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

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

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

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

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

    
3456
}