Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / rest / FilesHandler.java @ d235f94f

History | View | Annotate | Download (84.1 kB)

1
/*
2
 * Copyright 2008, 2009 Electronic Business Systems Ltd.
3
 *
4
 * This file is part of GSS.
5
 *
6
 * GSS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GSS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with GSS.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
package gr.ebs.gss.server.rest;
20

    
21
import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import gr.ebs.gss.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.Login;
29
import gr.ebs.gss.server.domain.FileUploadStatus;
30
import gr.ebs.gss.server.domain.User;
31
import gr.ebs.gss.server.domain.dto.FileBodyDTO;
32
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
33
import gr.ebs.gss.server.domain.dto.FolderDTO;
34
import gr.ebs.gss.server.domain.dto.GroupDTO;
35
import gr.ebs.gss.server.domain.dto.PermissionDTO;
36
import gr.ebs.gss.server.ejb.ExternalAPI;
37
import gr.ebs.gss.server.ejb.TransactionHelper;
38
import gr.ebs.gss.server.webdav.Range;
39
import gr.ebs.gss.server.webdav.RequestUtil;
40

    
41
import java.io.BufferedReader;
42
import java.io.ByteArrayInputStream;
43
import java.io.ByteArrayOutputStream;
44
import java.io.File;
45
import java.io.FileInputStream;
46
import java.io.FileNotFoundException;
47
import java.io.IOException;
48
import java.io.InputStream;
49
import java.io.InputStreamReader;
50
import java.io.OutputStreamWriter;
51
import java.io.PrintWriter;
52
import java.io.UnsupportedEncodingException;
53
import java.net.URI;
54
import java.net.URISyntaxException;
55
import java.net.URLDecoder;
56
import java.net.URLEncoder;
57
import java.util.ArrayList;
58
import java.util.Arrays;
59
import java.util.Collection;
60
import java.util.Date;
61
import java.util.HashSet;
62
import java.util.Iterator;
63
import java.util.List;
64
import java.util.Set;
65
import java.util.StringTokenizer;
66
import java.util.concurrent.Callable;
67

    
68
import javax.servlet.ServletContext;
69
import javax.servlet.ServletException;
70
import javax.servlet.ServletOutputStream;
71
import javax.servlet.http.Cookie;
72
import javax.servlet.http.HttpServletRequest;
73
import javax.servlet.http.HttpServletResponse;
74

    
75
import org.apache.commons.codec.binary.Base64;
76
import org.apache.commons.fileupload.FileItemIterator;
77
import org.apache.commons.fileupload.FileItemStream;
78
import org.apache.commons.fileupload.FileUploadException;
79
import org.apache.commons.fileupload.ProgressListener;
80
import org.apache.commons.fileupload.servlet.ServletFileUpload;
81
import org.apache.commons.fileupload.util.Streams;
82
import org.apache.commons.httpclient.util.DateParseException;
83
import org.apache.commons.httpclient.util.DateUtil;
84
import org.apache.commons.logging.Log;
85
import org.apache.commons.logging.LogFactory;
86
import org.json.JSONArray;
87
import org.json.JSONException;
88
import org.json.JSONObject;
89

    
90

    
91
/**
92
 * A class that handles operations on the 'files' namespace.
93
 *
94
 * @author past
95
 */
96
public class FilesHandler extends RequestHandler {
97
        /**
98
         * The request parameter name for fetching a different version.
99
         */
100
        private static final String VERSION_PARAM = "version";
101

    
102
        /**
103
         * The request attribute containing the owner of the destination URI
104
         * in a copy or move request.
105
         */
106
        private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
107

    
108
        private static final int TRACK_PROGRESS_PERCENT = 5;
109

    
110
        /**
111
         * The form parameter name that contains the signature in a browser POST upload.
112
         */
113
        private static final String AUTHORIZATION_PARAMETER = "Authorization";
114

    
115
        /**
116
         * The form parameter name that contains the date in a browser POST upload.
117
         */
118
        private static final String DATE_PARAMETER = "Date";
119

    
120
        /**
121
         * The request parameter name for making an upload progress request.
122
         */
123
        private static final String PROGRESS_PARAMETER = "progress";
124

    
125
        /**
126
         * The request parameter name for restoring a previous version of a file.
127
         */
128
        private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
129

    
130
        /**
131
         * The logger.
132
         */
133
        private static Log logger = LogFactory.getLog(FilesHandler.class);
134

    
135
        /**
136
         * The servlet context provided by the call site.
137
         */
138
        private ServletContext context;
139

    
140
        /**
141
         * The style sheet for displaying the directory listings.
142
         */
143
        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;}";
144

    
145

    
146
        /**
147
         * @param servletContext
148
         */
149
        public FilesHandler(ServletContext servletContext) {
150
                context = servletContext;
151
        }
152

    
153
        private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
154
                try {
155
                        new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
156
                                @Override
157
                                public Void call() throws Exception {
158
                                        getService().updateAccounting(user, date, bandwidthDiff);
159
                                        return null;
160
                                }
161
                        });
162
                } catch (RuntimeException e) {
163
                        throw e;
164
                } catch (Exception e) {
165
                        // updateAccounting() doesn't throw any checked exceptions
166
                        assert false;
167
                }
168
        }
169

    
170
        /**
171
     * Serve the specified resource, optionally including the data content.
172
     *
173
     * @param req The servlet request we are processing
174
     * @param resp The servlet response we are creating
175
     * @param content Should the content be included?
176
     *
177
     * @exception IOException if an input/output error occurs
178
     * @exception ServletException if a servlet-specified error occurs
179
     * @throws RpcException
180
     * @throws InsufficientPermissionsException
181
     * @throws ObjectNotFoundException
182
     */
183
        @Override
184
        protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
185
                    throws IOException, ServletException {
186
                boolean authDeferred = getAuthDeferred(req);
187
        String path = getInnerPath(req, PATH_FILES);
188
                if (path.equals(""))
189
                        path = "/";
190
                try {
191
                        path = URLDecoder.decode(path, "UTF-8");
192
                } catch (IllegalArgumentException e) {
193
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
194
                        return;
195
                }
196
            String progress = req.getParameter(PROGRESS_PARAMETER);
197

    
198
            if (logger.isDebugEnabled())                    
199
                        if (content)
200
                            logger.debug("Serving resource '" +        path + "' headers and data");
201
                    else
202
                            logger.debug("Serving resource '" +        path + "' headers only");
203

    
204
            User user = getUser(req);
205
            User owner = getOwner(req);
206
        boolean exists = true;
207
        Object resource = null;
208
        FileHeaderDTO file = null;
209
        FolderDTO folder = null;
210
        try {
211
                resource = getService().getResourceAtPath(owner.getId(), path, false);
212
        } catch (ObjectNotFoundException e) {
213
            exists = false;
214
        } catch (RpcException e) {
215
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
216
                        return;
217
                }
218

    
219
            if (!exists && authDeferred) {
220
                    // We do not want to leak information if the request
221
                    // was not authenticated.
222
                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
223
                    return;
224
            }
225

    
226
            if (resource instanceof FolderDTO)
227
                    folder = (FolderDTO) resource;
228
            else
229
                    file = (FileHeaderDTO) resource;        // Note that file will be null, if (!exists).
230

    
231
            // Now it's time to perform the deferred authentication check.
232
                // Since regular signature checking was already performed,
233
                // we need to check the read-all flag or the signature-in-parameters.
234
                if (authDeferred)
235
                        if (file != null && !file.isReadForAll() && content) {
236
                                logger.debug("this case refers to a file with no public privileges");
237
                                // Check for GET with the signature in the request parameters.
238
                                String auth = req.getParameter(AUTHORIZATION_PARAMETER);
239
                                String dateParam = req.getParameter(DATE_PARAMETER);
240
                                if (auth == null || dateParam == null) {
241
                                        // Check for a valid authentication cookie.
242
                                        if (req.getCookies() != null) {
243
                                                boolean found = false;
244
                                                for (Cookie cookie : req.getCookies())
245
                                                        if (Login.AUTH_COOKIE.equals(cookie.getName())) {
246
                                                                String cookieauth = cookie.getValue();
247
                                                                int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
248
                                                                if (sepIndex == -1) {
249
                                                                        handleAuthFailure(req, resp);
250
                                                                        return;
251
                                                                }
252
                                                                String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
253
                                                                user = null;
254
                                                                try {
255
                                                                        user = getService().findUser(username);
256
                                                                } catch (RpcException e) {
257
                                                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
258
                                                                        return;
259
                                                                }
260
                                                                if (user == null) {
261
                                                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
262
                                                                    return;
263
                                                            }
264
                                                                req.setAttribute(USER_ATTRIBUTE, user);
265
                                                                String token = cookieauth.substring(sepIndex + 1);
266
                                                                if (user.getAuthToken() == null) {
267
                                                                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
268
                                                                        return;
269
                                                                }
270
                                                                if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
271
                                                                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
272
                                                                        return;
273
                                                                }
274
                                                                found = true;
275
                                                                break;
276
                                                        }
277
                                                if (!found) {
278
                                                        handleAuthFailure(req, resp);
279
                                                        return;
280
                                                }
281
                                        } else {
282
                                                handleAuthFailure(req, resp);
283
                                                return;
284
                                        }
285
                                } else {
286
                                    long timestamp;
287
                                        try {
288
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
289
                                        } catch (DateParseException e) {
290
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
291
                                            return;
292
                                        }
293
                                    if (!isTimeValid(timestamp)) {
294
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
295
                                            return;
296
                                    }
297

    
298
                                        // Fetch the Authorization parameter and find the user specified in it.
299
                                        String[] authParts = auth.split(" ");
300
                                        if (authParts.length != 2) {
301
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
302
                                            return;
303
                                    }
304
                                        String username = authParts[0];
305
                                        String signature = authParts[1];
306
                                        user = null;
307
                                        try {
308
                                                user = getService().findUser(username);
309
                                        } catch (RpcException e) {
310
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
311
                                                return;
312
                                        }
313
                                        if (user == null) {
314
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
315
                                            return;
316
                                    }
317
                                        req.setAttribute(USER_ATTRIBUTE, user);
318

    
319
                                        // Remove the servlet path from the request URI.
320
                                        String p = req.getRequestURI();
321
                                        String servletPath = req.getContextPath() + req.getServletPath();
322
                                        p = p.substring(servletPath.length());
323
                                        // Validate the signature in the Authorization parameter.
324
                                        String data = req.getMethod() + dateParam + p;
325
                                        if (!isSignatureValid(signature, user, data)) {
326
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
327
                                            return;
328
                                    }
329
                                }
330
                        }
331
                else if(folder != null && folder.isReadForAll() || file != null && file.isReadForAll()){
332
                        //This case refers to a folder or file with public privileges
333
                        //For a read-for-all folder request, pretend the owner is making it.
334
                        logger.debug("*********this case refers to a folder or file with public privileges");
335
                        user = owner;
336
                        req.setAttribute(USER_ATTRIBUTE, user);
337
                }else if(folder != null && !folder.isReadForAll()){
338
                        //this case refers to a folder with no public privileges
339
                        logger.debug("*********this case refers to a folder with no public privileges");
340
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
341
                        return;
342
                }
343
                else{
344
                        logger.debug("*********ANY other case");
345
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
346
                        return;
347
                }
348

    
349
            // If the resource is not a collection, and the resource path
350
            // ends with "/" or "\", return NOT FOUND.
351
            if (folder == null)
352
                        if (path.endsWith("/") || path.endsWith("\\")) {
353
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
354
                            return;
355
                    }
356

    
357
            // Workaround for IE's broken caching behavior.
358
            if (folder != null)
359
                    resp.setHeader("Expires", "-1");
360

    
361
            // A request for upload progress.
362
            if (progress != null && content) {
363
                    if (file == null) {
364
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
365
                        return;
366
                    }
367
                    serveProgress(req, resp, progress, user, file);
368
                        return;
369
            }
370

    
371
                // Fetch the version to retrieve, if specified.
372
                String verStr = req.getParameter(VERSION_PARAM);
373
                int version = 0;
374
                FileBodyDTO oldBody = null;
375
                if (verStr != null && file != null)
376
                        try {
377
                                version = Integer.valueOf(verStr);
378
                        } catch (NumberFormatException e) {
379
                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
380
                            return;
381
                        }
382
                if (version > 0)
383
                        try {
384
                                oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
385
                        } catch (RpcException e) {
386
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
387
                                return;
388
                        } catch (ObjectNotFoundException e) {
389
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
390
                            return;
391
                        } catch (InsufficientPermissionsException e) {
392
                            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
393
                            return;
394
                        }
395

    
396
            // Check if the conditions specified in the optional If headers are
397
            // satisfied. Doing this for folders would require recursive checking
398
            // for all of their children, which in turn would defy the purpose of
399
            // the optimization.
400
            if (folder == null){
401
                        // Checking If headers.
402
                    if (!checkIfHeaders(req, resp, file, oldBody))
403
                                return;
404
            }
405
            else if(!checkIfModifiedSince(req, resp, folder))
406
                    return;
407

    
408
            // Find content type.
409
            String contentType = null;
410
            boolean isContentHtml = false;
411
            boolean expectJSON = false;
412

    
413
            if (file != null) {
414
                contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
415
                if (contentType == null) {
416
                        contentType = context.getMimeType(file.getName());
417
                        file.setMimeType(contentType);
418
                }
419
            } else { // folder != null
420
                    String accept = req.getHeader("Accept");
421
                    // The order in this conditional pessimizes the common API case,
422
                    // but is important for backwards compatibility with existing
423
                    // clients who send no accept header and expect a JSON response.
424
                    if (accept != null && accept.contains("text/html")) {
425
                            contentType = "text/html;charset=UTF-8";
426
                            isContentHtml = true;
427
                    }else if (accept != null && accept.contains("text/html") && !authDeferred){
428
                            //this is the case when clients send the appropriate headers, the contentType is "text/html"
429
                            //and expect a JSON response. The above check applies to FireGSS client
430
                            contentType = "text/html;charset=UTF-8";
431
                            isContentHtml = true;
432
                            expectJSON = true;
433
                    }
434
                    else{
435
                            contentType = "application/json;charset=UTF-8";
436
                            expectJSON = true;
437
                    }
438
                }
439

    
440

    
441
            ArrayList ranges = null;
442
            long contentLength = -1L;
443

    
444
            if (file != null) {
445
                    // Parse range specifier.
446
                    ranges = parseRange(req, resp, file, oldBody);
447
                    // ETag header
448
                    resp.setHeader("ETag", getETag(file, oldBody));
449
                    // Last-Modified header.
450
                    String lastModified = oldBody == null ?
451
                                            getLastModifiedHttp(file.getAuditInfo()) :
452
                                            getLastModifiedHttp(oldBody.getAuditInfo());
453
                    resp.setHeader("Last-Modified", lastModified);
454
                    // X-GSS-Metadata header.
455
                    try {
456
                                resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
457
                        } catch (InsufficientPermissionsException e) {
458
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
459
                        return;
460
                }
461
                    // Get content length.
462
                    contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
463
                    // Special case for zero length files, which would cause a
464
                    // (silent) ISE when setting the output buffer size.
465
                    if (contentLength == 0L)
466
                                content = false;
467
            } else
468
                    // Set the folder X-GSS-Metadata header.
469
                    try {
470
                                resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
471
                        } catch (InsufficientPermissionsException e) {
472
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
473
                        return;
474
                }
475

    
476
            ServletOutputStream ostream = null;
477
            PrintWriter writer = null;
478

    
479
            if (content)
480
                        try {
481
                            ostream = resp.getOutputStream();
482
                    } catch (IllegalStateException e) {
483
                            // If it fails, we try to get a Writer instead if we're
484
                            // trying to serve a text file
485
                            if ( contentType == null
486
                                                    || contentType.startsWith("text")
487
                                                    || contentType.endsWith("xml") )
488
                                        writer = resp.getWriter();
489
                                else
490
                                        throw e;
491
                    }
492
            if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
493
                    // Set the appropriate output headers
494
                    if (contentType != null) {
495
                            if (logger.isDebugEnabled())
496
                                    logger.debug("contentType='" + contentType + "'");
497
                            resp.setContentType(contentType);
498
                    }
499
                    if (file != null && contentLength >= 0) {
500
                            if (logger.isDebugEnabled())
501
                                    logger.debug("contentLength=" + contentLength);
502
                            if (contentLength < Integer.MAX_VALUE)
503
                                        resp.setContentLength((int) contentLength);
504

    
505
                                else
506
                                        // Set the content-length as String to be able to use a long
507
                                    resp.setHeader("content-length", "" + contentLength);
508
                    }
509

    
510
                    InputStream renderResult = null;
511
                    String relativePath = getRelativePath(req);
512
                    String contextPath = req.getContextPath();
513
                    String servletPath = req.getServletPath();
514
                    String contextServletPath = contextPath + servletPath;
515
                    if (folder != null && content)
516
                            // Serve the directory browser for a public folder
517
                            if (isContentHtml && !expectJSON)
518
                                    renderResult = renderHtml(contextServletPath, relativePath, folder,user);
519
                            // Serve the directory for an ordinary folder or for fireGSS client
520
                            else
521
                                    try {
522
                                            renderResult = renderJson(user, folder);
523
                                            } catch (InsufficientPermissionsException e) {
524
                                                    resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
525
                                                    return;
526
                                            }
527

    
528

    
529
                    // Copy the input stream to our output stream (if requested)
530
                    if (content) {
531
                            try {
532
                                    resp.setBufferSize(output);
533
                            } catch (IllegalStateException e) {
534
                                    // Silent catch
535
                            }
536
                            try {
537
                                    if(file != null)
538
                                                if (needsContentDisposition(req))
539
                                                    resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
540
                                            else
541
                                                    resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
542
                                    if (ostream != null)
543
                                                copy(file, renderResult, ostream, req, oldBody);
544
                                        else
545
                                                copy(file, renderResult, writer, req, oldBody);
546
                                    if (file!=null) updateAccounting(owner, new Date(), contentLength);
547
                        } catch (ObjectNotFoundException e) {
548
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
549
                                return;
550
                        } catch (InsufficientPermissionsException e) {
551
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
552
                                return;
553
                        } catch (RpcException e) {
554
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
555
                                return;
556
                            }
557
                    }
558
            } else {
559
                    if (ranges == null || ranges.isEmpty())
560
                            return;
561
                    // Partial content response.
562
                    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
563

    
564
                    if (ranges.size() == 1) {
565
                            Range range = (Range) ranges.get(0);
566
                            resp.addHeader("Content-Range", "bytes "
567
                                                    + range.start
568
                                                    + "-" + range.end + "/"
569
                                                    + range.length);
570
                            long length = range.end - range.start + 1;
571
                            if (length < Integer.MAX_VALUE)
572
                                        resp.setContentLength((int) length);
573
                                else
574
                                        // Set the content-length as String to be able to use a long
575
                                    resp.setHeader("content-length", "" + length);
576

    
577
                            if (contentType != null) {
578
                                    if (logger.isDebugEnabled())
579
                                            logger.debug("contentType='" + contentType + "'");
580
                                    resp.setContentType(contentType);
581
                            }
582

    
583
                            if (content) {
584
                                    try {
585
                                            resp.setBufferSize(output);
586
                                    } catch (IllegalStateException e) {
587
                                            // Silent catch
588
                                    }
589
                                    try {
590
                                            if (ostream != null)
591
                                                        copy(file, ostream, range, req, oldBody);
592
                                                else
593
                                                        copy(file, writer, range, req, oldBody);
594
                                            updateAccounting(owner, new Date(), contentLength);
595
                                } catch (ObjectNotFoundException e) {
596
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
597
                                        return;
598
                                } catch (InsufficientPermissionsException e) {
599
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
600
                                        return;
601
                                } catch (RpcException e) {
602
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
603
                                        return;
604
                                }
605
                            }
606
                    } else {
607
                            resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
608
                            if (content) {
609
                                    try {
610
                                            resp.setBufferSize(output);
611
                                    } catch (IllegalStateException e) {
612
                                            // Silent catch
613
                                    }
614
                                    try {
615
                                            if (ostream != null)
616
                                                        copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
617
                                                else
618
                                                        copy(file, writer, ranges.iterator(), contentType, req, oldBody);
619
                                            updateAccounting(owner, new Date(), contentLength);
620
                                } catch (ObjectNotFoundException e) {
621
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
622
                                        return;
623
                                } catch (InsufficientPermissionsException e) {
624
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
625
                                        return;
626
                                } catch (RpcException e) {
627
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
628
                                        return;
629
                                }
630
                            }
631
                    }
632
            }
633
    }
634

    
635
        /**
636
         * Handles an authentication failure. If no Authorization or Date request
637
         * parameters and no Authorization, Date or X-GSS-Date headers were present,
638
         * this is a browser request, so redirect to login and then let the user get
639
         * back to the file. Otherwise it's a bogus client request and Forbidden is
640
         * returned.
641
         */
642
        private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
643
                if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
644
                                req.getParameter(DATE_PARAMETER) == null &&
645
                                req.getHeader(AUTHORIZATION_HEADER) == null &&
646
                                req.getDateHeader(DATE_HEADER) == -1 &&
647
                                req.getDateHeader(GSS_DATE_HEADER) == -1)
648
                        resp.sendRedirect(getConfiguration().getString("loginUrl") +
649
                                        "?next=" + req.getRequestURL().toString());
650
                else
651
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
652
        }
653

    
654
        /**
655
         * Return the filename of the specified file properly formatted for
656
         * including in the Content-Disposition header.
657
         */
658
        private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
659
                return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
660
        }
661

    
662
        /**
663
         * Determines whether the user agent needs the Content-Disposition
664
         * header to be set, in order to properly download a file.
665
         *
666
         * @param req the HTTP request
667
         * @return true if the Content-Disposition HTTP header must be set
668
         */
669
        private boolean needsContentDisposition(HttpServletRequest req) {
670
                /*String agent = req.getHeader("user-agent");
671
                if (agent != null && agent.contains("MSIE"))
672
                        return true;*/
673
                String dl = req.getParameter("dl");
674
                if ("1".equals(dl))
675
                        return true;
676
                return false;
677
        }
678

    
679
        /**
680
         * Sends a progress update on the amount of bytes received until now for
681
         * a file that the current user is currently uploading.
682
         *
683
         * @param req the HTTP request
684
         * @param resp the HTTP response
685
         * @param parameter the value for the progress request parameter
686
         * @param user the current user
687
         * @param file the file being uploaded, or null if the request is about a new file
688
         * @throws IOException if an I/O error occurs
689
         */
690
        private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
691
                                String parameter, User user, FileHeaderDTO file)        throws IOException {
692
                String filename = file == null ? parameter : file.getName();
693
                try {
694
                        FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
695
                        if (status == null) {
696
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
697
                                return;
698
                        }
699
                        JSONObject json = new JSONObject();
700
                        json.put("bytesUploaded", status.getBytesUploaded()).
701
                                put("bytesTotal", status.getFileSize());
702
                        sendJson(req, resp, json.toString());
703

    
704
                        // Workaround for IE's broken caching behavior.
705
                    resp.setHeader("Expires", "-1");
706
                        return;
707
                } catch (RpcException e) {
708
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
709
                        return;
710
                } catch (JSONException e) {
711
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
712
                        return;
713
                }
714
        }
715

    
716
        /**
717
         * Server a POST request to create/modify a file or folder.
718
         *
719
         * @param req the HTTP request
720
         * @param resp the HTTP response
721
     * @exception IOException if an input/output error occurs
722
         */
723
        void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
724
                boolean authDeferred = getAuthDeferred(req);
725
            if (!authDeferred && req.getParameterMap().size() > 1) {
726
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
727
                    return;
728
            }
729
        String path = getInnerPath(req, PATH_FILES);
730
            path = path.endsWith("/")? path: path + '/';
731
                try {
732
                    path = URLDecoder.decode(path, "UTF-8");
733
                } catch (IllegalArgumentException e) {
734
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
735
                        return;
736
                }
737
            // We only defer authenticating multipart POST requests.
738
            if (authDeferred) {
739
                        if (!ServletFileUpload.isMultipartContent(req)) {
740
                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
741
                            return;
742
                    }
743
                        handleMultipart(req, resp, path);
744
                        return;
745
                }
746

    
747
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
748
            if (!isValidResourceName(newName)) {
749
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
750
                    return;
751
            }
752
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
753
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
754
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
755
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
756
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
757
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
758

    
759
            if (newName != null)
760
                        createFolder(req, resp, path, newName);
761
            else if (hasUpdateParam)
762
                        updateResource(req, resp, path);
763
                else if (hasTrashParam)
764
                        trashResource(req, resp, path);
765
                else if (hasRestoreParam)
766
                        restoreResource(req, resp, path);
767
                else if (copyTo != null)
768
                        copyResource(req, resp, path, copyTo);
769
                else if (moveTo != null)
770
                        moveResource(req, resp, path, moveTo);
771
                else if (restoreVersion != null)
772
                        restoreVersion(req, resp, path, restoreVersion);
773
                else
774
                        // IE with Gears uses POST for multiple uploads.
775
                        putResource(req, resp);
776
        }
777

    
778
        /**
779
         * Restores a previous version for a file.
780
         *
781
         * @param req the HTTP request
782
         * @param resp the HTTP response
783
         * @param path the resource path
784
         * @param version the version number to restore
785
         * @throws IOException if an I/O error occurs
786
         */
787
        private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
788
                final User user = getUser(req);
789
                User owner = getOwner(req);
790
                Object resource = null;
791
                try {
792
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
793
                } catch (ObjectNotFoundException e) {
794
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
795
                        return;
796
                } catch (RpcException e) {
797
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
798
                        return;
799
                }
800
                if (resource instanceof FolderDTO) {
801
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
802
                        return;
803
                }
804

    
805
                try {
806
                        final FileHeaderDTO file = (FileHeaderDTO) resource;
807
                        final int oldVersion = Integer.parseInt(version);
808

    
809
                        new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
810
                                @Override
811
                                public Void call() throws Exception {
812
                                        getService().restoreVersion(user.getId(), file.getId(), oldVersion);
813
                                        return null;
814
                                }
815
                        });
816
                } catch (InsufficientPermissionsException e) {
817
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
818
                } catch (ObjectNotFoundException e) {
819
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
820
                } catch (RpcException e) {
821
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
822
                } catch (GSSIOException e) {
823
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
824
                } catch (QuotaExceededException e) {
825
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
826
                } catch (NumberFormatException e) {
827
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
828
                } catch (Exception e) {
829
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
830
                }
831
        }
832

    
833
        /**
834
         * A method for handling multipart POST requests for uploading
835
         * files from browser-based JavaScript clients.
836
         *
837
         * @param request the HTTP request
838
         * @param response the HTTP response
839
         * @param path the resource path
840
         * @throws IOException in case an error occurs writing to the
841
         *                 response stream
842
         */
843
        private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
844
            if (logger.isDebugEnabled())
845
                           logger.debug("Multipart POST for resource: " + path);
846

    
847
            User owner = getOwner(request);
848
            boolean exists = true;
849
        Object resource = null;
850
        FileHeaderDTO file = null;
851
        try {
852
                resource = getService().getResourceAtPath(owner.getId(), path, false);
853
        } catch (ObjectNotFoundException e) {
854
            exists = false;
855
        } catch (RpcException e) {
856
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
857
                        return;
858
                }
859

    
860
        if (exists)
861
                        if (resource instanceof FileHeaderDTO) {
862
                            file = (FileHeaderDTO) resource;
863
                            if (file.isDeleted()) {
864
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
865
                                return;
866
                            }
867
                        } else {
868
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
869
                            return;
870
                }
871

    
872
            Object parent;
873
            String parentPath = null;
874
                try {
875
                        parentPath = getParentPath(path);
876
                        parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
877
                } catch (ObjectNotFoundException e) {
878
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
879
                    return;
880
                } catch (RpcException e) {
881
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
882
                        return;
883
                }
884
            if (!(parent instanceof FolderDTO)) {
885
                    response.sendError(HttpServletResponse.SC_CONFLICT);
886
                    return;
887
            }
888
            final FolderDTO folder = (FolderDTO) parent;
889
            final String fileName = getLastElement(path);
890

    
891
                FileItemIterator iter;
892
                File uploadedFile = null;
893
                try {
894
                        // Create a new file upload handler.
895
                        ServletFileUpload upload = new ServletFileUpload();
896
                        StatusProgressListener progressListener = new StatusProgressListener(getService());
897
                        upload.setProgressListener(progressListener);
898
                        iter = upload.getItemIterator(request);
899
                        String dateParam = null;
900
                        String auth = null;
901
                        while (iter.hasNext()) {
902
                                FileItemStream item = iter.next();
903
                                String name = item.getFieldName();
904
                                InputStream stream = item.openStream();
905
                                if (item.isFormField()) {
906
                                        final String value = Streams.asString(stream);
907
                                        if (name.equals(DATE_PARAMETER))
908
                                                dateParam = value;
909
                                        else if (name.equals(AUTHORIZATION_PARAMETER))
910
                                                auth = value;
911

    
912
                                        if (logger.isDebugEnabled())
913
                                                logger.debug(name + ":" + value);
914
                                } else {
915
                                        // Fetch the timestamp used to guard against replay attacks.
916
                                    if (dateParam == null) {
917
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
918
                                            return;
919
                                    }
920

    
921
                                    long timestamp;
922
                                        try {
923
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
924
                                        } catch (DateParseException e) {
925
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
926
                                            return;
927
                                        }
928
                                    if (!isTimeValid(timestamp)) {
929
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
930
                                            return;
931
                                    }
932

    
933
                                        // Fetch the Authorization parameter and find the user specified in it.
934
                                    if (auth == null) {
935
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
936
                                            return;
937
                                    }
938
                                        String[] authParts = auth.split(" ");
939
                                        if (authParts.length != 2) {
940
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
941
                                            return;
942
                                    }
943
                                        String username = authParts[0];
944
                                        String signature = authParts[1];
945
                                        User user = null;
946
                                        try {
947
                                                user = getService().findUser(username);
948
                                        } catch (RpcException e) {
949
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
950
                                                return;
951
                                        }
952
                                        if (user == null) {
953
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
954
                                            return;
955
                                    }
956
                                        request.setAttribute(USER_ATTRIBUTE, user);
957

    
958
                                        // Remove the servlet path from the request URI.
959
                                        String p = request.getRequestURI();
960
                                        String servletPath = request.getContextPath() + request.getServletPath();
961
                                        p = p.substring(servletPath.length());
962
                                        // Validate the signature in the Authorization parameter.
963
                                        String data = request.getMethod() + dateParam + p;
964
                                        if (!isSignatureValid(signature, user, data)) {
965
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
966
                                            return;
967
                                    }
968

    
969
                                        progressListener.setUserId(user.getId());
970
                                        progressListener.setFilename(fileName);
971
                                        final String contentType = item.getContentType();
972

    
973
                                        try {
974
                                                uploadedFile = getService().uploadFile(stream, user.getId());
975
                                        } catch (IOException ex) {
976
                                                throw new GSSIOException(ex, false);
977
                                        }
978
                                        FileHeaderDTO fileDTO = null;
979
                                        final File upf = uploadedFile;
980
                                        final FileHeaderDTO f = file;
981
                                        final User u = user;
982
                                        if (file == null)
983
                                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
984
                                                        @Override
985
                                                        public FileHeaderDTO call() throws Exception {
986
                                                                return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
987
                                                        }
988
                                                });
989
                                        else
990
                                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
991
                                                        @Override
992
                                                        public FileHeaderDTO call() throws Exception {
993
                                                                return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
994
                                                        }
995
                                                });
996
                                        updateAccounting(owner, new Date(), fileDTO.getFileSize());
997
                                        getService().removeFileUploadProgress(user.getId(), fileName);
998
                                }
999
                        }
1000
                        // We can't return 204 here since GWT's onSubmitComplete won't fire.
1001
                        response.setContentType("text/html");
1002
            response.getWriter().print("<pre></pre>");
1003
                } catch (FileUploadException e) {
1004
                        String error = "Error while uploading file";
1005
                        logger.error(error, e);
1006
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1007
                } catch (GSSIOException e) {
1008
                        if (uploadedFile != null && uploadedFile.exists())
1009
                                uploadedFile.delete();
1010
                        String error = "Error while uploading file";
1011
                        if (e.logAsError())
1012
                                logger.error(error, e);
1013
                        else
1014
                                logger.debug(error, e);
1015
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1016
                } catch (DuplicateNameException e) {
1017
                        if (uploadedFile != null && uploadedFile.exists())
1018
                                uploadedFile.delete();
1019
                        String error = "The specified file name already exists in this folder";
1020
                        logger.error(error, e);
1021
                        response.sendError(HttpServletResponse.SC_CONFLICT, error);
1022

    
1023
                } catch (InsufficientPermissionsException e) {
1024
                        if (uploadedFile != null && uploadedFile.exists())
1025
                                uploadedFile.delete();
1026
                        String error = "You don't have the necessary permissions";
1027
                        logger.error(error, e);
1028
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1029

    
1030
                } catch (QuotaExceededException e) {
1031
                        if (uploadedFile != null && uploadedFile.exists())
1032
                                uploadedFile.delete();
1033
                        String error = "Not enough free space available";
1034
                        if (logger.isDebugEnabled())
1035
                                logger.debug(error, e);
1036
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1037

    
1038
                } catch (ObjectNotFoundException e) {
1039
                        if (uploadedFile != null && uploadedFile.exists())
1040
                                uploadedFile.delete();
1041
                        String error = "A specified object was not found";
1042
                        logger.error(error, e);
1043
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1044
                } catch (RpcException e) {
1045
                        if (uploadedFile != null && uploadedFile.exists())
1046
                                uploadedFile.delete();
1047
                        String error = "An error occurred while communicating with the service";
1048
                        logger.error(error, e);
1049
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1050
                } catch (Exception e) {
1051
                        if (uploadedFile != null && uploadedFile.exists())
1052
                                uploadedFile.delete();
1053
                        String error = "An internal server error occurred";
1054
                        logger.error(error, e);
1055
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1056
                }
1057
        }
1058

    
1059
        /**
1060
         * Move the resource in the specified path to the specified destination.
1061
         *
1062
         * @param req the HTTP request
1063
         * @param resp the HTTP response
1064
         * @param path the path of the resource
1065
         * @param moveTo the destination of the move procedure
1066
         * @throws IOException if an input/output error occurs
1067
         */
1068
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1069
                final User user = getUser(req);
1070
                User owner = getOwner(req);
1071
                Object resource = null;
1072
                try {
1073
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1074
                } catch (ObjectNotFoundException e) {
1075
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1076
                        return;
1077
                } catch (RpcException e) {
1078
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1079
                        return;
1080
                }
1081

    
1082
        String destination = null;
1083
        User destOwner = null;
1084
                boolean exists = true;
1085
                try {
1086
                        destination = getDestinationPath(req, encodePath(moveTo));
1087
                        destination = URLDecoder.decode(destination, "UTF-8");
1088
                        destOwner = getDestinationOwner(req);
1089
                        getService().getResourceAtPath(destOwner.getId(), destination, true);
1090
                } catch (ObjectNotFoundException e) {
1091
                        exists = false;
1092
                } catch (URISyntaxException e) {
1093
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1094
                        return;
1095
                } catch (RpcException e) {
1096
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1097
                        return;
1098
                }
1099
                if (exists) {
1100
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1101
                        return;
1102
                }
1103

    
1104
                try {
1105
                        final User dOwner = destOwner;
1106
                        final String dest = destination;
1107
                        if (resource instanceof FolderDTO) {
1108
                                final FolderDTO folder = (FolderDTO) resource;
1109
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1110
                                        @Override
1111
                                        public Void call() throws Exception {
1112
                                                getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1113
                                                return null;
1114
                                        }
1115
                                });
1116
                        } else {
1117
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1118
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1119
                                        @Override
1120
                                        public Void call() throws Exception {
1121
                                                getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1122
                                                return null;
1123
                                        }
1124
                                });
1125

    
1126
                        }
1127
                } catch (InsufficientPermissionsException e) {
1128
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1129
                } catch (ObjectNotFoundException e) {
1130
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1131
                } catch (RpcException e) {
1132
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1133
                } catch (DuplicateNameException e) {
1134
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1135
                } catch (GSSIOException e) {
1136
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1137
                } catch (QuotaExceededException e) {
1138
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1139
                } catch (Exception e) {
1140
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1141
                }
1142
        }
1143

    
1144
        /**
1145
         * Copy the resource in the specified path to the specified destination.
1146
         *
1147
         * @param req the HTTP request
1148
         * @param resp the HTTP response
1149
         * @param path the path of the resource
1150
         * @param copyTo the destination of the copy procedure
1151
         * @throws IOException if an input/output error occurs
1152
         */
1153
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1154
                final User user = getUser(req);
1155
                User owner = getOwner(req);
1156
                Object resource = null;
1157
                try {
1158
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1159
                } catch (ObjectNotFoundException e) {
1160
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1161
                        return;
1162
                } catch (RpcException e) {
1163
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1164
                        return;
1165
                }
1166

    
1167
        String destination = null;
1168
        User destOwner = null;
1169
                boolean exists = true;
1170
                try {
1171
                        String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1172
                        destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1173
                        destOwner = getDestinationOwner(req);
1174
                        getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1175
                } catch (ObjectNotFoundException e) {
1176
                        exists = false;
1177
                } catch (URISyntaxException e) {
1178
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1179
                        return;
1180
                } catch (RpcException e) {
1181
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1182
                        return;
1183
                }
1184
                if (exists) {
1185
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1186
                        return;
1187
                }
1188

    
1189
                try {
1190
                        final User dOwner = destOwner;
1191
                        final String dest = destination;
1192
                        if (resource instanceof FolderDTO) {
1193
                                final FolderDTO folder = (FolderDTO) resource;
1194
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1195
                                        @Override
1196
                                        public Void call() throws Exception {
1197
                                                getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1198
                                                return null;
1199
                                        }
1200
                                });
1201
                        } else {
1202
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1203
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1204
                                        @Override
1205
                                        public Void call() throws Exception {
1206
                                                getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1207
                                                return null;
1208
                                        }
1209
                                });
1210
                        }
1211
                } catch (InsufficientPermissionsException e) {
1212
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1213
                } catch (ObjectNotFoundException e) {
1214
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1215
                } catch (RpcException e) {
1216
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1217
                } catch (DuplicateNameException e) {
1218
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1219
                } catch (GSSIOException e) {
1220
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1221
                } catch (QuotaExceededException e) {
1222
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1223
                } catch (Exception e) {
1224
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1225
                }
1226
        }
1227

    
1228
        private String encodePath(String path) throws UnsupportedEncodingException{
1229
                StringTokenizer str = new StringTokenizer(path, "/:", true);
1230
                String result = new String();
1231
                while(str.hasMoreTokens()){
1232
                        String token = str.nextToken();
1233
                        if(!token.equals("/") && !token.equals(":"))
1234
                                token = URLEncoder.encode(token,"UTF-8");
1235
                        result = result + token;
1236
                }
1237
                return result;
1238
        }
1239
        /**
1240
         * A helper method that extracts the relative resource path,
1241
         * after removing the 'files' namespace.
1242
         * The path returned is <i>not</i> URL-decoded.
1243
         *
1244
         * @param req the HTTP request
1245
         * @param path the specified path
1246
         * @return the path relative to the root folder
1247
         * @throws URISyntaxException
1248
         * @throws RpcException in case an error occurs while communicating
1249
         *                                                 with the backend
1250
         * @throws UnsupportedEncodingException
1251
         */
1252
        private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1253
                URI uri = new URI(path);
1254
                String dest = uri.getRawPath();
1255
                // Remove the context path from the destination URI.
1256
                String contextPath = req.getContextPath();
1257
                if (!dest.startsWith(contextPath))
1258
                        throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1259
                dest = dest.substring(contextPath.length());
1260
                // Remove the servlet path from the destination URI.
1261
                String servletPath = req.getServletPath();
1262
                if (!dest.startsWith(servletPath))
1263
                        throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1264
                dest = dest.substring(servletPath.length());
1265
            // Strip the username part
1266
                if (dest.length() < 2)
1267
                        throw new URISyntaxException(dest, "No username in the destination URI");
1268
                int slash = dest.substring(1).indexOf('/');
1269
                if (slash == -1)
1270
                        throw new URISyntaxException(dest, "No username in the destination URI");
1271
                // Decode the user to get the proper characters (mainly the @)
1272
                String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1273
                User o;
1274
                o = getService().findUser(owner);
1275
                if (o == null)
1276
                        throw new URISyntaxException(dest, "User " + owner + " not found");
1277

    
1278
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1279
                dest = dest.substring(slash + 1);
1280

    
1281
                // Chop the resource namespace part
1282
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1283

    
1284
            dest = dest.endsWith("/")? dest: dest + '/';
1285
                return dest;
1286
        }
1287

    
1288
        /**
1289
         * Move the resource in the specified path to the trash bin.
1290
         *
1291
         * @param req the HTTP request
1292
         * @param resp the HTTP response
1293
         * @param path the path of the resource
1294
         * @throws IOException if an input/output error occurs
1295
         */
1296
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1297
                final User user = getUser(req);
1298
                User owner = getOwner(req);
1299
                Object resource = null;
1300
                try {
1301
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1302
                } catch (ObjectNotFoundException e) {
1303
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1304
                        return;
1305
                } catch (RpcException e) {
1306
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1307
                        return;
1308
                }
1309

    
1310
                try {
1311
                        if (resource instanceof FolderDTO) {
1312
                                final FolderDTO folder = (FolderDTO) resource;
1313
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1314
                                        @Override
1315
                                        public Void call() throws Exception {
1316
                                                getService().moveFolderToTrash(user.getId(), folder.getId());
1317
                                                return null;
1318
                                        }
1319
                                });
1320
                        } else {
1321
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1322
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1323
                                        @Override
1324
                                        public Void call() throws Exception {
1325
                                                getService().moveFileToTrash(user.getId(), file.getId());
1326
                                                return null;
1327
                                        }
1328
                                });
1329
                        }
1330
                } catch (InsufficientPermissionsException e) {
1331
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1332
                } catch (ObjectNotFoundException e) {
1333
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1334
                } catch (RpcException e) {
1335
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1336
                } catch (Exception e) {
1337
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1338
                }
1339
        }
1340

    
1341
        /**
1342
         * Restore the resource in the specified path from the trash bin.
1343
         *
1344
         * @param req the HTTP request
1345
         * @param resp the HTTP response
1346
         * @param path the path of the resource
1347
         * @throws IOException if an input/output error occurs
1348
         */
1349
        private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1350
                final User user = getUser(req);
1351
                User owner = getOwner(req);
1352
                Object resource = null;
1353
                try {
1354
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1355
                } catch (ObjectNotFoundException e) {
1356
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1357
                        return;
1358
                } catch (RpcException e) {
1359
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1360
                        return;
1361
                }
1362

    
1363
                try {
1364
                        if (resource instanceof FolderDTO) {
1365
                                final FolderDTO folder = (FolderDTO) resource;
1366
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1367
                                        @Override
1368
                                        public Void call() throws Exception {
1369
                                                getService().removeFolderFromTrash(user.getId(), folder.getId());
1370
                                                return null;
1371
                                        }
1372
                                });
1373
                        } else {
1374
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1375
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1376
                                        @Override
1377
                                        public Void call() throws Exception {
1378
                                                getService().removeFileFromTrash(user.getId(), file.getId());
1379
                                                return null;
1380
                                        }
1381
                                });
1382
                        }
1383
                } catch (InsufficientPermissionsException e) {
1384
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1385
                } catch (ObjectNotFoundException e) {
1386
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1387
                } catch (RpcException e) {
1388
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1389
                } catch (Exception e) {
1390
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1391
                }
1392
        }
1393

    
1394
        /**
1395
         * Update the resource in the specified path.
1396
         *
1397
         * @param req the HTTP request
1398
         * @param resp the HTTP response
1399
         * @param path the path of the resource
1400
         * @throws IOException if an input/output error occurs
1401
         */
1402
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1403
                final User user = getUser(req);
1404
                User owner = getOwner(req);
1405
                Object resource = null;
1406

    
1407
                try {
1408
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1409
                } catch (ObjectNotFoundException e) {
1410
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1411
                        return;
1412
                } catch (RpcException e) {
1413
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1414
                        return;
1415
                }
1416
                StringBuffer input = new StringBuffer();
1417
                JSONObject json = null;
1418
                if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1419
                        input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1420
                else {
1421
                        // Assume application/json
1422
                        BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1423
                        String line = null;
1424
                        while ((line = reader.readLine()) != null)
1425
                                input.append(line);
1426
                        reader.close();
1427
                }
1428
                try {
1429
                        json = new JSONObject(input.toString());
1430
                        if (logger.isDebugEnabled())
1431
                                logger.debug("JSON update: " + json);
1432
                        if (resource instanceof FolderDTO) {
1433
                                final FolderDTO folder = (FolderDTO) resource;
1434
                                String name = json.optString("name");
1435
                                JSONArray permissions = json.optJSONArray("permissions");
1436
                                Set<PermissionDTO> perms = null;
1437
                                if (permissions != null)
1438
                                        perms = parsePermissions(user, permissions);
1439
                                Boolean readForAll = null;
1440
                                if (json.opt("readForAll") != null)
1441
                                        readForAll = json.optBoolean("readForAll");
1442
                                if (!name.isEmpty() || permissions != null || readForAll != null) {
1443
                                        final String fName = name.isEmpty()? null: name;
1444
                                        final Boolean freadForAll =  readForAll;
1445
                                        final Set<PermissionDTO> fPerms = perms;
1446
                                        FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1447
                                                @Override
1448
                                                public FolderDTO call() throws Exception {
1449
                                                        return getService().updateFolder(user.getId(), folder.getId(), fName, freadForAll, fPerms);
1450
                                                }
1451

    
1452
                                        });
1453
                                        resp.getWriter().println(getNewUrl(req, folderUpdated));
1454
                                }
1455
                        } else {
1456
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1457
                                String name = null;
1458
                                if (json.opt("name") != null)
1459
                                        name = json.optString("name");
1460
                                Long modificationDate = null;
1461
                                if (json.optLong("modificationDate") != 0)
1462
                                        modificationDate = json.optLong("modificationDate");
1463
                                Boolean versioned = null;
1464
                                if (json.opt("versioned") != null)
1465
                                        versioned = json.getBoolean("versioned");
1466
                                JSONArray tagset = json.optJSONArray("tags");
1467
                                String tags = null;
1468
                                StringBuffer t = new StringBuffer();
1469
                                if (tagset != null) {
1470
                                        for (int i = 0; i < tagset.length(); i++)
1471
                                                t.append(tagset.getString(i) + ',');
1472
                                        tags = t.toString();
1473
                                }
1474
                                JSONArray permissions = json.optJSONArray("permissions");
1475
                                Set<PermissionDTO> perms = null;
1476
                                if (permissions != null)
1477
                                        perms = parsePermissions(user, permissions);
1478
                                Boolean readForAll = null;
1479
                                if (json.opt("readForAll") != null)
1480
                                        readForAll = json.optBoolean("readForAll");
1481
                                if (name != null || tags != null || modificationDate != null
1482
                                                        || versioned != null || perms != null
1483
                                                        || readForAll != null) {
1484
                                        final String fName = name;
1485
                                        final String fTags = tags;
1486
                                        final Date mDate = modificationDate != null? new Date(modificationDate): null;
1487
                                        final Boolean fVersioned = versioned;
1488
                                        final Boolean fReadForAll = readForAll;
1489
                                        final Set<PermissionDTO> fPerms = perms;
1490
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1491
                                                @Override
1492
                                                public Object call() throws Exception {
1493
                                                        getService().updateFile(user.getId(), file.getId(),
1494
                                                                                fName, fTags, mDate, fVersioned,
1495
                                                                                fReadForAll, fPerms);
1496
                                                        return null;
1497
                                                }
1498

    
1499
                                        });
1500
                                }
1501
                        }
1502
                } catch (JSONException e) {
1503
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1504
                } catch (InsufficientPermissionsException e) {
1505
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1506
                } catch (ObjectNotFoundException e) {
1507
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1508
                } catch (DuplicateNameException e) {
1509
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1510
                } catch (RpcException e) {
1511
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1512
                } catch (Exception e) {
1513
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1514
                        return;
1515
                }
1516
        }
1517

    
1518
        /**
1519
         * Returns the new URL of an updated folder.
1520
         */
1521
        private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1522
                String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1523
                String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1524
                if (parentUrl.indexOf(fpath) != -1)
1525
                        parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1526
                if(!parentUrl.endsWith("/"))
1527
                        parentUrl = parentUrl+"/";
1528
                parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1529
                return parentUrl;
1530
        }
1531

    
1532
        /**
1533
         * Helper method to convert a JSON array of permissions into a set of
1534
         * PermissionDTO objects.
1535
         *
1536
         * @param user the current user
1537
         * @param permissions the JSON array to parse
1538
         * @return the parsed set of permissions
1539
         * @throws JSONException if there was an error parsing the JSON object
1540
         * @throws RpcException if there was an error communicating with the EJB
1541
         * @throws ObjectNotFoundException if the user could not be found
1542
         * @throws UnsupportedEncodingException
1543
         */
1544
        private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1545
                        throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1546
                if (permissions == null)
1547
                        return null;
1548
                Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1549
                for (int i = 0; i < permissions.length(); i++) {
1550
                        JSONObject j = permissions.getJSONObject(i);
1551
                        PermissionDTO perm = new PermissionDTO();
1552
                        perm.setModifyACL(j.optBoolean("modifyACL"));
1553
                        perm.setRead(j.optBoolean("read"));
1554
                        perm.setWrite(j.optBoolean("write"));
1555
                        String permUser = j.optString("user");
1556
                        if (!permUser.isEmpty()) {
1557
                                User u = getService().findUser(permUser);
1558
                                if (u == null)
1559
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1560
                                perm.setUser(u.getDTO());
1561
                        }
1562
                        // 31/8/2009: Add optional groupUri which takes priority if it exists
1563
                        String permGroupUri = j.optString("groupUri");
1564
                        String permGroup = j.optString("group");
1565
                        if (!permGroupUri.isEmpty()) {
1566
                                String[] names = permGroupUri.split("/");
1567
                                String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1568
                                String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1569
                                User u = getService().findUser(usr);
1570
                                if (u == null)
1571
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1572
                                GroupDTO g = getService().getGroup(u.getId(), grp);
1573
                                perm.setGroup(g);
1574
                        }
1575
                        else if (!permGroup.isEmpty()) {
1576
                                GroupDTO g = getService().getGroup(user.getId(), permGroup);
1577
                                perm.setGroup(g);
1578
                        }
1579
                        if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1580
                                throw new JSONException("A permission must correspond to either a user or a group");
1581
                        perms.add(perm);
1582
                }
1583
                return perms;
1584
        }
1585

    
1586
        /**
1587
         * Creates a new folder with the specified name under the folder in the provided path.
1588
         *
1589
         * @param req the HTTP request
1590
         * @param resp the HTTP response
1591
         * @param path the parent folder path
1592
         * @param folderName the name of the new folder
1593
         * @throws IOException if an input/output error occurs
1594
         */
1595
        private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1596
                if (logger.isDebugEnabled())
1597
                           logger.debug("Creating folder " + folderName + " in '" + path);
1598

    
1599
            final User user = getUser(req);
1600
            User owner = getOwner(req);
1601
        boolean exists = true;
1602
        try {
1603
                getService().getResourceAtPath(owner.getId(), path + folderName, false);
1604
        } catch (ObjectNotFoundException e) {
1605
            exists = false;
1606
        } catch (RpcException e) {
1607
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1608
                        return;
1609
                }
1610

    
1611
        if (exists) {
1612
            resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1613
                                    ", " + METHOD_HEAD);
1614
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1615
            return;
1616
        }
1617

    
1618
                Object parent;
1619
                try {
1620
                        parent = getService().getResourceAtPath(owner.getId(), path, true);
1621
                } catch (ObjectNotFoundException e) {
1622
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1623
                        return;
1624
                } catch (RpcException e) {
1625
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1626
                        return;
1627
                }
1628
                try {
1629
                        if (parent instanceof FolderDTO) {
1630
                                final FolderDTO folder = (FolderDTO) parent;
1631
                                FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1632
                                        @Override
1633
                                        public FolderDTO call() throws Exception {
1634
                                                return getService().createFolder(user.getId(), folder.getId(), folderName);
1635
                                        }
1636

    
1637
                                });
1638
                        String newResource = getApiRoot() + newFolder.getURI();
1639
                        resp.setHeader("Location", newResource);
1640
                        resp.setContentType("text/plain");
1641
                        PrintWriter out = resp.getWriter();
1642
                        out.println(newResource);
1643
                        } else {
1644
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1645
                            return;
1646
                        }
1647
                } catch (DuplicateNameException e) {
1648
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1649
                    return;
1650
                } catch (InsufficientPermissionsException e) {
1651
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1652
                    return;
1653
                } catch (ObjectNotFoundException e) {
1654
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1655
                        return;
1656
                } catch (RpcException e) {
1657
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1658
                        return;
1659
                } catch (Exception e) {
1660
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1661
                        return;
1662
                }
1663
            resp.setStatus(HttpServletResponse.SC_CREATED);
1664
        }
1665

    
1666
        /**
1667
         * @param req
1668
         * @param resp
1669
         * @throws IOException
1670
         * @throws FileNotFoundException
1671
         */
1672
        void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1673
        String path = getInnerPath(req, PATH_FILES);
1674
                try {
1675
                    path = URLDecoder.decode(path, "UTF-8");
1676
                } catch (IllegalArgumentException e) {
1677
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1678
                        return;
1679
                }
1680
            if (logger.isDebugEnabled())
1681
                           logger.debug("Updating resource: " + path);
1682

    
1683
            final User user = getUser(req);
1684
            User owner = getOwner(req);
1685
            boolean exists = true;
1686
        Object resource = null;
1687
        FileHeaderDTO file = null;
1688
        try {
1689
                resource = getService().getResourceAtPath(owner.getId(), path, false);
1690
        } catch (ObjectNotFoundException e) {
1691
            exists = false;
1692
        } catch (RpcException e) {
1693
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1694
                        return;
1695
                }
1696

    
1697
        if (exists)
1698
                        if (resource instanceof FileHeaderDTO)
1699
                            file = (FileHeaderDTO) resource;
1700
                        else {
1701
                        resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1702
                            return;
1703
                }
1704
        boolean result = true;
1705

    
1706
        // Temporary content file used to support partial PUT.
1707
        File contentFile = null;
1708

    
1709
        Range range = parseContentRange(req, resp);
1710

    
1711
        InputStream resourceInputStream = null;
1712

    
1713
        // Append data specified in ranges to existing content for this
1714
        // resource - create a temporary file on the local filesystem to
1715
        // perform this operation.
1716
        // Assume just one range is specified for now
1717
        if (range != null) {
1718
            try {
1719
                                contentFile = executePartialPut(req, range, path);
1720
                        } catch (RpcException e) {
1721
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1722
                                return;
1723
                        } catch (ObjectNotFoundException e) {
1724
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
1725
                        return;
1726
                        } catch (InsufficientPermissionsException e) {
1727
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1728
                        return;
1729
                        }
1730
            resourceInputStream = new FileInputStream(contentFile);
1731
        } else
1732
                        resourceInputStream = req.getInputStream();
1733

    
1734
        try {
1735
                FolderDTO folder = null;
1736
                Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1737
                if (!(parent instanceof FolderDTO)) {
1738
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1739
                        return;
1740
                }
1741
                       folder = (FolderDTO) parent;
1742
                final String name = getLastElement(path);
1743
                final String mimeType = context.getMimeType(name);
1744
                File uploadedFile = null;
1745
                try {
1746
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1747
                        } catch (IOException ex) {
1748
                                throw new GSSIOException(ex, false);
1749
                        }
1750
                FileHeaderDTO fileDTO = null;
1751
                final File uploadedf = uploadedFile;
1752
                        final FolderDTO parentf = folder;
1753
                        final FileHeaderDTO f = file;
1754
            if (exists)
1755
                    fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1756
                                        @Override
1757
                                        public FileHeaderDTO call() throws Exception {
1758
                                                return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1759
                                        }
1760
                                });
1761
                        else
1762
                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1763
                                        @Override
1764
                                        public FileHeaderDTO call() throws Exception {
1765
                                                return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1766
                                        }
1767

    
1768
                                });
1769
            updateAccounting(owner, new Date(), fileDTO.getFileSize());
1770
                        getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1771
        } catch(ObjectNotFoundException e) {
1772
            result = false;
1773
        } catch (RpcException e) {
1774
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1775
                        return;
1776
        } catch (IOException e) {
1777
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1778
                        return;
1779
                } catch (GSSIOException e) {
1780
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1781
                        return;
1782
                } catch (DuplicateNameException e) {
1783
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1784
                    return;
1785
                } catch (InsufficientPermissionsException e) {
1786
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1787
                    return;
1788
                } catch (QuotaExceededException e) {
1789
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1790
                    return;
1791
                } catch (Exception e) {
1792
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1793
                        return;
1794
                }
1795

    
1796
        if (result) {
1797
            if (exists)
1798
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1799
                        else
1800
                                resp.setStatus(HttpServletResponse.SC_CREATED);
1801
        } else
1802
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1803
        }
1804

    
1805
    /**
1806
     * Delete a resource.
1807
     *
1808
     * @param req The servlet request we are processing
1809
     * @param resp The servlet response we are processing
1810
         * @throws IOException if the response cannot be sent
1811
     */
1812
    void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1813
        String path = getInnerPath(req, PATH_FILES);
1814
            if (logger.isDebugEnabled())
1815
                           logger.debug("Deleting resource '" + path);
1816
            path = URLDecoder.decode(path, "UTF-8");
1817
            final User user = getUser(req);
1818
            User owner = getOwner(req);
1819
            boolean exists = true;
1820
            Object object = null;
1821
            try {
1822
                    object = getService().getResourceAtPath(owner.getId(), path, false);
1823
            } catch (ObjectNotFoundException e) {
1824
                    exists = false;
1825
            } catch (RpcException e) {
1826
                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1827
                        return;
1828
                }
1829

    
1830
            if (!exists) {
1831
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1832
                    return;
1833
            }
1834

    
1835
            FolderDTO folder = null;
1836
            FileHeaderDTO file = null;
1837
            if (object instanceof FolderDTO)
1838
                    folder = (FolderDTO) object;
1839
            else
1840
                    file = (FileHeaderDTO) object;
1841

    
1842
            if (file != null)
1843
                        try {
1844
                                final FileHeaderDTO f = file;
1845
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1846
                                        @Override
1847
                                        public Void call() throws Exception {
1848
                                                getService().deleteFile(user.getId(), f.getId());
1849
                                                return null;
1850
                                        }
1851
                                });
1852
                } catch (InsufficientPermissionsException e) {
1853
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1854
                                return;
1855
                    } catch (ObjectNotFoundException e) {
1856
                            // Although we had already found the object, it was
1857
                            // probably deleted from another thread.
1858
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1859
                            return;
1860
                    } catch (RpcException e) {
1861
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1862
                            return;
1863
                    } catch (Exception e) {
1864
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1865
                            return;
1866
                    }
1867
                else if (folder != null)
1868
                        try {
1869
                                final FolderDTO fo = folder;
1870
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1871
                                        @Override
1872
                                        public Void call() throws Exception {
1873
                                                getService().deleteFolder(user.getId(), fo.getId());
1874
                                                return null;
1875
                                        }
1876
                                });
1877
                } catch (InsufficientPermissionsException e) {
1878
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1879
                        return;
1880
                    } catch (ObjectNotFoundException e) {
1881
                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1882
                        return;
1883
                    } catch (RpcException e) {
1884
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1885
                        return;
1886
                    } catch (Exception e) {
1887
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1888
                        return;
1889
                    }
1890
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1891
            return;
1892
    }
1893

    
1894
        /**
1895
     * Return an InputStream to a JSON representation of the contents
1896
     * of this directory.
1897
     *
1898
         * @param user the user that made the request
1899
     * @param folder the specified directory
1900
     * @return an input stream with the rendered contents
1901
         * @throws IOException if the response cannot be sent
1902
     * @throws ServletException
1903
         * @throws InsufficientPermissionsException if the user does not have
1904
         *                         the necessary privileges to read the directory
1905
     */
1906
    private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1907
                    ServletException, InsufficientPermissionsException {
1908
            JSONObject json = new JSONObject();
1909
            try {
1910
                        json.put("name", folder.getName()).
1911
                                        put("owner", folder.getOwner().getUsername()).
1912
                                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1913
                                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1914
                                        put("deleted", folder.isDeleted()).
1915
                                        put("readForAll", folder.isReadForAll());
1916

    
1917
                        if (folder.getAuditInfo().getModifiedBy() != null)
1918
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1919
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1920
                        if (folder.getParent() != null) {
1921
                                JSONObject j = new JSONObject();
1922
                                j.put("uri", getApiRoot() + folder.getParent().getURI());
1923
                                j.put("name", folder.getParent().getName());
1924
                                json.put("parent", j);
1925
                        }
1926
                    List<JSONObject> subfolders = new ArrayList<JSONObject>();
1927
                    for (FolderDTO f: folder.getSubfolders())
1928
                                if (!f.isDeleted()) {
1929
                                        JSONObject j = new JSONObject();
1930
                                        j.put("name", f.getName()).
1931
                                                put("uri", getApiRoot() + f.getURI());
1932
                                        subfolders.add(j);
1933
                                }
1934
                    json.put("folders", subfolders);
1935
                    List<JSONObject> files = new ArrayList<JSONObject>();
1936
                    List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1937
                    for (FileHeaderDTO f: fileHeaders) {
1938
                            JSONObject j = new JSONObject();
1939
                                j.put("name", f.getName()).
1940
                                        put("owner", f.getOwner().getUsername()).
1941
                                        put("deleted", f.isDeleted()).
1942
                                        put("version", f.getVersion()).
1943
                                        put("content", f.getMimeType()).
1944
                                        put("size", f.getFileSize()).
1945
                                        put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1946
                                        put("path", f.getFolder().getPath()).
1947
                                        put("uri", getApiRoot() + f.getURI());
1948
                                if (f.getAuditInfo().getModificationDate() != null)
1949
                                        j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1950
                                files.add(j);
1951
                    }
1952
                    json.put("files", files);
1953
                    Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1954
                    json.put("permissions", renderJson(perms));
1955
                } catch (JSONException e) {
1956
                        throw new ServletException(e);
1957
                } catch (ObjectNotFoundException e) {
1958
                        throw new ServletException(e);
1959
                } catch (RpcException e) {
1960
                        throw new ServletException(e);
1961
                }
1962

    
1963
            // Prepare a writer to a buffered area
1964
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
1965
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1966
            PrintWriter writer = new PrintWriter(osWriter);
1967

    
1968
            // Return an input stream to the underlying bytes
1969
            writer.write(json.toString());
1970
            writer.flush();
1971
            return new ByteArrayInputStream(stream.toByteArray());
1972
    }
1973

    
1974
        /**
1975
     * Return a String with a JSON representation of the metadata
1976
     * of the specified folder.
1977
         * @throws RpcException
1978
         * @throws InsufficientPermissionsException
1979
         * @throws ObjectNotFoundException
1980
     */
1981
    private String renderJsonMetadata(User user, FolderDTO folder)
1982
                    throws ServletException, InsufficientPermissionsException {
1983
            // Check if the user has read permission.
1984
                try {
1985
                        if (!getService().canReadFolder(user.getId(), folder.getId()))
1986
                                throw new InsufficientPermissionsException();
1987
                } catch (ObjectNotFoundException e) {
1988
                        throw new ServletException(e);
1989
                } catch (RpcException e) {
1990
                        throw new ServletException(e);
1991
                }
1992

    
1993
            JSONObject json = new JSONObject();
1994
            try {
1995
                        json.put("name", folder.getName()).
1996
                        put("owner", folder.getOwner().getUsername()).
1997
                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1998
                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1999
                        put("deleted", folder.isDeleted());
2000
                        if (folder.getAuditInfo().getModifiedBy() != null)
2001
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2002
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2003
                } catch (JSONException e) {
2004
                        throw new ServletException(e);
2005
                }
2006
            return json.toString();
2007
    }
2008

    
2009
        /**
2010
     * Return a String with a JSON representation of the metadata
2011
     * of the specified file. If an old file body is provided, then
2012
     * the metadata of that particular version will be returned.
2013
     *
2014
         * @param user the user that made the request
2015
     * @param file the specified file header
2016
     * @param oldBody the version number
2017
     * @return the JSON-encoded file
2018
     * @throws ServletException
2019
         * @throws InsufficientPermissionsException if the user does not have
2020
         *                         the necessary privileges to read the directory
2021
     */
2022
    private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
2023
                    throws ServletException, InsufficientPermissionsException {
2024
            JSONObject json = new JSONObject();
2025
            try {
2026
                    // Need to encode file name in order to properly display it in the web client.
2027
                        json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2028
                                        put("owner", file.getOwner().getUsername()).
2029
                                        put("versioned", file.isVersioned()).
2030
                                        put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
2031
                                        put("readForAll", file.isReadForAll()).
2032
                                        put("tags", renderJson(file.getTags())).
2033
                                        put("path", file.getFolder().getPath()).
2034
                                    put("uri", getApiRoot() + file.getURI()).
2035
                                        put("deleted", file.isDeleted());
2036
                        JSONObject j = new JSONObject();
2037
                        j.put("uri", getApiRoot() + file.getFolder().getURI()).
2038
                                        put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2039
                        json.put("folder", j);
2040
                        if (oldBody != null)
2041
                                json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2042
                                                put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2043
                                                put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2044
                                                put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2045
                                                put("content", oldBody.getMimeType()).
2046
                                                put("size", oldBody.getFileSize());
2047
                        else
2048
                                json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2049
                                                put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2050
                                                put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2051
                                                put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2052
                                                put("content", file.getMimeType()).
2053
                                                put("size", file.getFileSize());
2054
                    Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2055
                    json.put("permissions", renderJson(perms));
2056
                } catch (JSONException e) {
2057
                        throw new ServletException(e);
2058
                } catch (ObjectNotFoundException e) {
2059
                        throw new ServletException(e);
2060
                } catch (RpcException e) {
2061
                        throw new ServletException(e);
2062
                } catch (UnsupportedEncodingException e) {
2063
                        throw new ServletException(e);
2064
                }
2065

    
2066
            return json.toString();
2067
    }
2068

    
2069
        /**
2070
         * Return a String with a JSON representation of the
2071
         * specified set of permissions.
2072
     *
2073
         * @param permissions the set of permissions
2074
         * @return the JSON-encoded object
2075
         * @throws JSONException
2076
         * @throws UnsupportedEncodingException
2077
         */
2078
        private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2079
                JSONArray perms = new JSONArray();
2080
                for (PermissionDTO p: permissions) {
2081
                        JSONObject permission = new JSONObject();
2082
                        permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2083
                        if (p.getUser() != null)
2084
                                permission.put("user", p.getUser().getUsername());
2085
                        if (p.getGroup() != null) {
2086
                                GroupDTO group = p.getGroup();
2087
                                permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2088
                                permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2089
                        }
2090
                        perms.put(permission);
2091
                }
2092
                return perms;
2093
        }
2094

    
2095
        /**
2096
         * Return a String with a JSON representation of the
2097
         * specified collection of tags.
2098
     *
2099
         * @param tags the collection of tags
2100
         * @return the JSON-encoded object
2101
         * @throws JSONException
2102
         * @throws UnsupportedEncodingException
2103
         */
2104
        private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2105
                JSONArray tagArray = new JSONArray();
2106
                for (String t: tags)
2107
                        tagArray.put(URLEncoder.encode(t,"UTF-8"));
2108
                return tagArray;
2109
        }
2110

    
2111
        /**
2112
         * Retrieves the user who owns the destination namespace, for a
2113
         * copy or move request.
2114
         *
2115
         * @param req the HTTP request
2116
         * @return the owner of the namespace
2117
         */
2118
        protected User getDestinationOwner(HttpServletRequest req) {
2119
                return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2120
        }
2121

    
2122
        /**
2123
         * A helper inner class for updating the progress status of a file upload.
2124
         *
2125
         * @author kman
2126
         */
2127
        public static class StatusProgressListener implements ProgressListener {
2128
                private int percentLogged = 0;
2129
                private long bytesTransferred = 0;
2130

    
2131
                private long fileSize = -100;
2132

    
2133
                private Long userId;
2134

    
2135
                private String filename;
2136

    
2137
                private ExternalAPI service;
2138

    
2139
                public StatusProgressListener(ExternalAPI aService) {
2140
                        service = aService;
2141
                }
2142

    
2143
                /**
2144
                 * Modify the userId.
2145
                 *
2146
                 * @param aUserId the userId to set
2147
                 */
2148
                public void setUserId(Long aUserId) {
2149
                        userId = aUserId;
2150
                }
2151

    
2152
                /**
2153
                 * Modify the filename.
2154
                 *
2155
                 * @param aFilename the filename to set
2156
                 */
2157
                public void setFilename(String aFilename) {
2158
                        filename = aFilename;
2159
                }
2160

    
2161
                @Override
2162
                public void update(long bytesRead, long contentLength, int items) {
2163
                        //monitoring per percent of bytes uploaded
2164
                        bytesTransferred = bytesRead;
2165
                        if (fileSize != contentLength)
2166
                                fileSize = contentLength;
2167
                        int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2168

    
2169
                        if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2170
                                if (percent != percentLogged){
2171
                                        percentLogged = percent;
2172
                                        try {
2173
                                                if (userId != null && filename != null)
2174
                                                        service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2175
                                        } catch (ObjectNotFoundException e) {
2176
                                                // Swallow the exception since it is going to be caught
2177
                                                // by previously called methods
2178
                                        }
2179
                                }
2180
                }
2181
        }
2182
        /**
2183
         * Return an InputStream to an HTML representation of the contents of this
2184
         * directory.
2185
         *
2186
         * @param contextPath Context path to which our internal paths are relative
2187
         * @param relativePath the requested relative path to the resource
2188
         * @param folder the specified directory
2189
         * @param req the HTTP request
2190
         * @return an input stream with the rendered contents
2191
         * @throws IOException
2192
         * @throws ServletException
2193
         */
2194
        private InputStream renderHtml(String contextPath, String relativePath, FolderDTO folder, User user) throws IOException, ServletException {
2195
                String name = folder.getName();
2196
                // Prepare a writer to a buffered area
2197
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
2198
                OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2199
                PrintWriter writer = new PrintWriter(osWriter);
2200
                StringBuffer sb = new StringBuffer();
2201
                // rewriteUrl(contextPath) is expensive. cache result for later reuse
2202
                String rewrittenContextPath = rewriteUrl(contextPath);
2203
                // Render the page header
2204
                sb.append("<html>\r\n");
2205
                sb.append("<head>\r\n");
2206
                sb.append("<title>");
2207
                sb.append("Index of " + name);
2208
                sb.append("</title>\r\n");
2209
                sb.append("<STYLE><!--");
2210
                sb.append(GSS_CSS);
2211
                sb.append("--></STYLE> ");
2212
                sb.append("</head>\r\n");
2213
                sb.append("<body>");
2214
                sb.append("<h1>");
2215
                sb.append("Index of " + name);
2216

    
2217
                // Render the link to our parent (if required)
2218
                String folderPath = folder.getPath();
2219
                int indexFolderPath = relativePath.indexOf(folderPath);
2220
                String relativePathNoFolderName = null;
2221
                if(indexFolderPath != 0)
2222
                        relativePathNoFolderName = relativePath.substring(0, indexFolderPath);
2223
                else
2224
                        relativePathNoFolderName = relativePath;
2225
                String parentDirectory = folderPath;
2226
                //To-do: further search in encoding folder names with special characters
2227
                //String rewrittenParentDirectory = rewriteUrl(parentDirectory);
2228
                if (parentDirectory.endsWith("/"))
2229
                        parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2230
                int slash = parentDirectory.lastIndexOf('/');
2231
                parentDirectory = parentDirectory.substring(0,slash);
2232
                if (slash >= 0) {
2233
                        sb.append(" - <a href=\"");
2234
                        sb.append(rewrittenContextPath);
2235
                        sb.append(relativePathNoFolderName);
2236
                        sb.append(parentDirectory);
2237
                        if (!parentDirectory.endsWith("/"))
2238
                                sb.append("/");
2239
                        sb.append("\">");
2240
                        sb.append("<b>");
2241
                        sb.append("Up To ");
2242
                        if (parentDirectory.equals(""))
2243
                                parentDirectory = "/";
2244
                        sb.append(parentDirectory);
2245
                        sb.append("</b>");
2246
                        sb.append("</a>");
2247
                }
2248

    
2249
                sb.append("</h1>");
2250
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2251

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

    
2254
                // Render the column headings
2255
                sb.append("<tr>\r\n");
2256
                sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2257
                sb.append("Name");
2258
                sb.append("</strong></font></td>\r\n");
2259
                sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2260
                sb.append("Size");
2261
                sb.append("</strong></font></td>\r\n");
2262
                sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2263
                sb.append("Last modified");
2264
                sb.append("</strong></font></td>\r\n");
2265
                sb.append("</tr>");
2266
                // Render the directory entries within this directory
2267
                boolean shade = false;
2268
                Iterator iter = folder.getSubfolders().iterator();
2269
                while (iter.hasNext()) {
2270
                        FolderDTO subf = (FolderDTO) iter.next();
2271
                        String resourceName = subf.getName();
2272
                        if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2273
                                continue;
2274

    
2275
                        sb.append("<tr");
2276
                        if (shade)
2277
                                sb.append(" bgcolor=\"#eeeeee\"");
2278
                        sb.append(">\r\n");
2279
                        shade = !shade;
2280

    
2281
                        sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2282
                        sb.append("<a href=\"");
2283
                        sb.append(rewrittenContextPath);
2284
                        sb.append(relativePathNoFolderName);
2285
                        sb.append(folderPath + resourceName);
2286
                        sb.append("/");
2287
                        sb.append("\"><tt>");
2288
                        sb.append(RequestUtil.filter(resourceName));
2289
                        sb.append("/");
2290
                        sb.append("</tt></a></td>\r\n");
2291

    
2292
                        sb.append("<td align=\"right\"><tt>");
2293
                        sb.append("&nbsp;");
2294
                        sb.append("</tt></td>\r\n");
2295

    
2296
                        sb.append("<td align=\"right\"><tt>");
2297
                        sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2298
                        sb.append("</tt></td>\r\n");
2299

    
2300
                        sb.append("</tr>\r\n");
2301
                }
2302
                List<FileHeaderDTO> files;
2303
                try {
2304
                        files = getService().getFiles(user.getId(), folder.getId(), true);
2305
                } catch (ObjectNotFoundException e) {
2306
                        throw new ServletException(e.getMessage());
2307
                } catch (InsufficientPermissionsException e) {
2308
                        throw new ServletException(e.getMessage());
2309
                } catch (RpcException e) {
2310
                        throw new ServletException(e.getMessage());
2311
                }
2312
                for (FileHeaderDTO file : files)
2313
                        //Display only file resources that are marked as public
2314
                        if(file.isReadForAll()){
2315
                                String resourceName = file.getName();
2316
                                if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2317
                                        continue;
2318

    
2319
                                sb.append("<tr");
2320
                                if (shade)
2321
                                        sb.append(" bgcolor=\"#eeeeee\"");
2322
                                sb.append(">\r\n");
2323
                                shade = !shade;
2324

    
2325
                                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2326
                                sb.append("<a href=\"");
2327
                                sb.append(rewrittenContextPath);
2328
                                sb.append(relativePath);
2329
                                if(!relativePath.endsWith("/"))
2330
                                        sb.append("/");
2331
                                sb.append(rewriteUrl(resourceName));
2332
                                sb.append("\"><tt>");
2333
                                sb.append(RequestUtil.filter(resourceName));
2334
                                sb.append("</tt></a></td>\r\n");
2335

    
2336
                                sb.append("<td align=\"right\"><tt>");
2337
                                sb.append(renderSize(file.getFileSize()));
2338
                                sb.append("</tt></td>\r\n");
2339

    
2340
                                sb.append("<td align=\"right\"><tt>");
2341
                                sb.append(getLastModifiedHttp(file.getAuditInfo()));
2342
                                sb.append("</tt></td>\r\n");
2343

    
2344
                                sb.append("</tr>\r\n");
2345
                        }
2346

    
2347
                // Render the page footer
2348
                sb.append("</table>\r\n");
2349

    
2350
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2351

    
2352
                //sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2353
                sb.append("</body>\r\n");
2354
                sb.append("</html>\r\n");
2355

    
2356
                // Return an input stream to the underlying bytes
2357
                writer.write(sb.toString());
2358
                writer.flush();
2359
                return new ByteArrayInputStream(stream.toByteArray());
2360

    
2361
        }
2362
}