Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / webdav / Webdav.java @ 623:66f69a7348ed

History | View | Annotate | Download (112.5 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
        private 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
                InputStream resourceInputStream = oldBody == null ?
2591
                                        getService().getFileContents(user.getId(), file.getId()) :
2592
                                        getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2593
                InputStream istream = new BufferedInputStream(resourceInputStream, input);
2594
                exception = copyRange(istream, ostream, range.start, range.end);
2595
                // Clean up the input stream
2596
                istream.close();
2597
                // Rethrow any exception that has occurred
2598
                if (exception != null)
2599
                        throw exception;
2600
        }
2601

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2850
        }
2851

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

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

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

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

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

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

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

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

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

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

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

    
2934
                String path = getRelativePath(req);
2935

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3332
                resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3333

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

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

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

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

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

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

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

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

    
3362
                }
3363

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

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

    
3370
        }
3371

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

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

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

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

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

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

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

    
3454
}