Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (84.7 kB)

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

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

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

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

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

    
88

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

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

    
106
        private static final int TRACK_PROGRESS_PERCENT = 5;
107

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

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

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

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

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

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

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

    
143

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

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

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

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

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

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

    
224
            if (resource instanceof Folder)
225
                    folder = (Folder) resource;
226
            else
227
                    file = (FileHeader) resource;        // Note that file will be null, if (!exists).
228

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

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

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

    
350
            // Workaround for IE's broken caching behavior.
351
            if (folder != null)
352
                    resp.setHeader("Expires", "-1");
353

    
354
            // A request for upload progress.
355
            if (progress != null && content) {
356
                    serveProgress(req, resp, progress, user, file);
357
                        return;
358
            }
359

    
360
                // Fetch the version to retrieve, if specified.
361
                String verStr = req.getParameter(VERSION_PARAM);
362
                int version = 0;
363
                FileBody oldBody = null;
364
                if (verStr != null && file != null)
365
                        try {
366
                                version = Integer.valueOf(verStr);
367
                        } catch (NumberFormatException e) {
368
                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
369
                            return;
370
                        }
371
                if (version > 0)
372
                        try {
373
                                oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
374
                        } catch (RpcException e) {
375
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
376
                                return;
377
                        } catch (ObjectNotFoundException e) {
378
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
379
                            return;
380
                        } catch (InsufficientPermissionsException e) {
381
                            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
382
                            return;
383
                        }
384

    
385
            // Check if the conditions specified in the optional If headers are
386
            // satisfied. Doing this for folders would require recursive checking
387
            // for all of their children, which in turn would defy the purpose of
388
            // the optimization.
389
            if (folder == null){
390
                        // Checking If headers.
391
                    if (!checkIfHeaders(req, resp, file, oldBody))
392
                                return;
393
            }
394
            else if(!checkIfModifiedSince(req, resp, folder))
395
                    return;
396

    
397
            // Find content type.
398
            String contentType = null;
399
            boolean isContentHtml = false;
400
            boolean expectJSON = false;
401

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

    
431

    
432
            ArrayList ranges = null;
433
            long contentLength = -1L;
434

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

    
467
            ServletOutputStream ostream = null;
468
            PrintWriter writer = null;
469

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

    
496
                                else
497
                                        // Set the content-length as String to be able to use a long
498
                                    resp.setHeader("content-length", "" + contentLength);
499
                    }
500

    
501
                    InputStream renderResult = null;
502
                    String relativePath = getRelativePath(req);
503
                    String contextPath = req.getContextPath();
504
                    String servletPath = req.getServletPath();
505
                    String contextServletPath = contextPath + servletPath;
506
                    if (folder != null && content)
507
                            // Serve the directory browser for a public folder
508
                            if (isContentHtml && !expectJSON) {
509
                    try {
510
                        folder = getService().expandFolder(folder);
511
                    }
512
                    catch (ObjectNotFoundException e) {
513
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
514
                        return;
515
                    }
516
                    catch (RpcException e) {
517
                        //We send 500 instead of 404 because this folder has been loaded before in this method and it is
518
                        //impossible to not be found now
519
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
520
                                    return;
521
                            }
522
                    renderResult = renderHtml(contextServletPath, relativePath, folder,user);
523
                }
524
                            // Serve the directory for an ordinary folder or for fireGSS client
525
                            else
526
                                    try {
527
                                            renderResult = renderJson(user, folder);
528
                                            } catch (InsufficientPermissionsException e) {
529
                                                    resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
530
                                                    return;
531
                                            }
532

    
533

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

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

    
582
                            if (contentType != null) {
583
                                    if (logger.isDebugEnabled())
584
                                            logger.debug("contentType='" + contentType + "'");
585
                                    resp.setContentType(contentType);
586
                            }
587

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

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

    
659
        /**
660
         * Return the filename of the specified file properly formatted for
661
         * including in the Content-Disposition header.
662
         */
663
        private String getDispositionFilename(FileHeader file) throws UnsupportedEncodingException {
664
                return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
665
        }
666

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

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

    
709
                        // Workaround for IE's broken caching behavior.
710
                    resp.setHeader("Expires", "-1");
711
                        return;
712
                } catch (RpcException e) {
713
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
714
                        return;
715
                } catch (JSONException e) {
716
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
717
                        return;
718
                }
719
        }
720

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

    
752
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
753

    
754
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
755
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
756
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
757
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
758
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
759
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
760

    
761
            if (newName != null){
762
                if (!isValidResourceName(newName)) {
763
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
764
                        return;
765
                }
766
                        createFolder(req, resp, path, newName);
767
            }
768
            else if (hasUpdateParam)
769
                        updateResource(req, resp, path);
770
                else if (hasTrashParam)
771
                        trashResource(req, resp, path);
772
                else if (hasRestoreParam)
773
                        restoreResource(req, resp, path);
774
                else if (copyTo != null)
775
                        copyResource(req, resp, path, copyTo);
776
                else if (moveTo != null)
777
                        moveResource(req, resp, path, moveTo);
778
                else if (restoreVersion != null)
779
                        restoreVersion(req, resp, path, restoreVersion);
780
                else
781
                        // IE with Gears uses POST for multiple uploads.
782
                        putResource(req, resp);
783
        }
784

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

    
812
                try {
813
                        final FileHeader file = (FileHeader) resource;
814
                        final int oldVersion = Integer.parseInt(version);
815

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

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

    
854
            User owner = getOwner(request);
855
            boolean exists = true;
856
        Object resource = null;
857
        FileHeader file = null;
858
        try {
859
                resource = getService().getResourceAtPath(owner.getId(), path, false);
860
        } catch (ObjectNotFoundException e) {
861
            exists = false;
862
        } catch (RpcException e) {
863
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
864
                        return;
865
                }
866

    
867
        if (exists)
868
                        if (resource instanceof FileHeader) {
869
                            file = (FileHeader) resource;
870
                            if (file.isDeleted()) {
871
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
872
                                return;
873
                            }
874
                        } else {
875
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
876
                            return;
877
                }
878

    
879
            Object parent;
880
            String parentPath = null;
881
                try {
882
                        parentPath = getParentPath(path);
883
                        parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
884
                } catch (ObjectNotFoundException e) {
885
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
886
                    return;
887
                } catch (RpcException e) {
888
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
889
                        return;
890
                }
891
            if (!(parent instanceof Folder)) {
892
                    response.sendError(HttpServletResponse.SC_CONFLICT);
893
                    return;
894
            }
895
            final Folder folderLocal = (Folder) parent;
896
            final String fileName = getLastElement(path);
897

    
898
            if (!isValidResourceName(fileName)) {
899
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST);
900
                    return;
901
            }
902

    
903
                FileItemIterator iter;
904
                File uploadedFile = null;
905
                try {
906
                        // Create a new file upload handler.
907
                        ServletFileUpload upload = new ServletFileUpload();
908
                        StatusProgressListener progressListener = new StatusProgressListener(getService());
909
                        upload.setProgressListener(progressListener);
910
                        iter = upload.getItemIterator(request);
911
                        String dateParam = null;
912
                        String auth = null;
913
                        while (iter.hasNext()) {
914
                                FileItemStream item = iter.next();
915
                                String name = item.getFieldName();
916
                                InputStream stream = item.openStream();
917
                                if (item.isFormField()) {
918
                                        final String value = Streams.asString(stream);
919
                                        if (name.equals(DATE_PARAMETER))
920
                                                dateParam = value;
921
                                        else if (name.equals(AUTHORIZATION_PARAMETER))
922
                                                auth = value;
923

    
924
                                        if (logger.isDebugEnabled())
925
                                                logger.debug(name + ":" + value);
926
                                } else {
927
                                        // Fetch the timestamp used to guard against replay attacks.
928
                                    if (dateParam == null) {
929
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
930
                                            return;
931
                                    }
932

    
933
                                    long timestamp;
934
                                        try {
935
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
936
                                        } catch (DateParseException e) {
937
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
938
                                            return;
939
                                        }
940
                                    if (!isTimeValid(timestamp)) {
941
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
942
                                            return;
943
                                    }
944

    
945
                                        // Fetch the Authorization parameter and find the user specified in it.
946
                                    if (auth == null) {
947
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
948
                                            return;
949
                                    }
950
                                        String[] authParts = auth.split(" ");
951
                                        if (authParts.length != 2) {
952
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
953
                                            return;
954
                                    }
955
                                        String username = authParts[0];
956
                                        String signature = authParts[1];
957
                                        User user = null;
958
                                        try {
959
                                                user = getService().findUser(username);
960
                                        } catch (RpcException e) {
961
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
962
                                                return;
963
                                        }
964
                                        if (user == null) {
965
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
966
                                            return;
967
                                    }
968
                                        request.setAttribute(USER_ATTRIBUTE, user);
969

    
970
                                        // Remove the servlet path from the request URI.
971
                                        String p = request.getRequestURI();
972
                                        String servletPath = request.getContextPath() + request.getServletPath();
973
                                        p = p.substring(servletPath.length());
974
                                        // Validate the signature in the Authorization parameter.
975
                                        String data = request.getMethod() + dateParam + p;
976
                                        if (!isSignatureValid(signature, user, data)) {
977
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
978
                                            return;
979
                                    }
980

    
981
                                        progressListener.setUserId(user.getId());
982
                                        progressListener.setFilename(fileName);
983
                                        final String contentType = item.getContentType();
984

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

    
1035
                } catch (InsufficientPermissionsException e) {
1036
                        if (uploadedFile != null && uploadedFile.exists())
1037
                                uploadedFile.delete();
1038
                        String error = "You don't have the necessary permissions";
1039
                        logger.error(error, e);
1040
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1041

    
1042
                } catch (QuotaExceededException e) {
1043
                        if (uploadedFile != null && uploadedFile.exists())
1044
                                uploadedFile.delete();
1045
                        String error = "Not enough free space available";
1046
                        if (logger.isDebugEnabled())
1047
                                logger.debug(error, e);
1048
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1049

    
1050
                } catch (ObjectNotFoundException e) {
1051
                        if (uploadedFile != null && uploadedFile.exists())
1052
                                uploadedFile.delete();
1053
                        String error = "A specified object was not found";
1054
                        logger.error(error, e);
1055
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1056
                } catch (RpcException e) {
1057
                        if (uploadedFile != null && uploadedFile.exists())
1058
                                uploadedFile.delete();
1059
                        String error = "An error occurred while communicating with the service";
1060
                        logger.error(error, e);
1061
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1062
                } catch (Exception e) {
1063
                        if (uploadedFile != null && uploadedFile.exists())
1064
                                uploadedFile.delete();
1065
                        String error = "An internal server error occurred";
1066
                        logger.error(error, e);
1067
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1068
                }
1069
        }
1070

    
1071
        /**
1072
         * Move the resource in the specified path to the specified destination.
1073
         *
1074
         * @param req the HTTP request
1075
         * @param resp the HTTP response
1076
         * @param path the path of the resource
1077
         * @param moveTo the destination of the move procedure
1078
         * @throws IOException if an input/output error occurs
1079
         */
1080
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1081
                final User user = getUser(req);
1082
                User owner = getOwner(req);
1083
                Object resource = null;
1084
                try {
1085
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1086
                } catch (ObjectNotFoundException e) {
1087
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1088
                        return;
1089
                } catch (RpcException e) {
1090
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1091
                        return;
1092
                }
1093

    
1094
        String destination = null;
1095
        User destOwner = null;
1096
                boolean exists = true;
1097
                try {
1098
                        destination = getDestinationPath(req, encodePath(moveTo));
1099
                        destination = URLDecoder.decode(destination, "UTF-8");
1100
                        destOwner = getDestinationOwner(req);
1101
                        getService().getResourceAtPath(destOwner.getId(), destination, true);
1102
                } catch (ObjectNotFoundException e) {
1103
                        exists = false;
1104
                } catch (URISyntaxException e) {
1105
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1106
                        return;
1107
                } catch (RpcException e) {
1108
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1109
                        return;
1110
                }
1111
                if (exists) {
1112
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1113
                        return;
1114
                }
1115

    
1116
                try {
1117
                        final User dOwner = destOwner;
1118
                        final String dest = destination;
1119
                        if (resource instanceof Folder) {
1120
                                final Folder folderLocal = (Folder) resource;
1121
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1122
                                        @Override
1123
                                        public Void call() throws Exception {
1124
                                                getService().moveFolderToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1125
                                                return null;
1126
                                        }
1127
                                });
1128
                        } else {
1129
                                final FileHeader fileLocal = (FileHeader) resource;
1130
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1131
                                        @Override
1132
                                        public Void call() throws Exception {
1133
                                                getService().moveFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1134
                                                return null;
1135
                                        }
1136
                                });
1137

    
1138
                        }
1139
                } catch (InsufficientPermissionsException e) {
1140
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1141
                } catch (ObjectNotFoundException e) {
1142
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1143
                } catch (RpcException e) {
1144
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1145
                } catch (DuplicateNameException e) {
1146
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1147
                } catch (GSSIOException e) {
1148
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1149
                } catch (QuotaExceededException e) {
1150
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1151
                } catch (Exception e) {
1152
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1153
                }
1154
        }
1155

    
1156
        /**
1157
         * Copy the resource in the specified path to the specified destination.
1158
         *
1159
         * @param req the HTTP request
1160
         * @param resp the HTTP response
1161
         * @param path the path of the resource
1162
         * @param copyTo the destination of the copy procedure
1163
         * @throws IOException if an input/output error occurs
1164
         */
1165
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1166
                final User user = getUser(req);
1167
                User owner = getOwner(req);
1168
                Object resource = null;
1169
                try {
1170
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1171
                } catch (ObjectNotFoundException e) {
1172
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1173
                        return;
1174
                } catch (RpcException e) {
1175
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1176
                        return;
1177
                }
1178

    
1179
        String destination = null;
1180
        User destOwner = null;
1181
                boolean exists = true;
1182
                try {
1183
                        String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1184
                        destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1185
                        destOwner = getDestinationOwner(req);
1186
                        getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1187
                } catch (ObjectNotFoundException e) {
1188
                        exists = false;
1189
                } catch (URISyntaxException e) {
1190
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1191
                        return;
1192
                } catch (RpcException e) {
1193
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1194
                        return;
1195
                }
1196
                if (exists) {
1197
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1198
                        return;
1199
                }
1200

    
1201
                try {
1202
                        final User dOwner = destOwner;
1203
                        final String dest = destination;
1204
                        if (resource instanceof Folder) {
1205
                                final Folder folderLocal = (Folder) resource;
1206
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1207
                                        @Override
1208
                                        public Void call() throws Exception {
1209
                                                getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1210
                                                return null;
1211
                                        }
1212
                                });
1213
                        } else {
1214
                                final FileHeader fileLocal = (FileHeader) resource;
1215
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1216
                                        @Override
1217
                                        public Void call() throws Exception {
1218
                                                getService().copyFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1219
                                                return null;
1220
                                        }
1221
                                });
1222
                        }
1223
                } catch (InsufficientPermissionsException e) {
1224
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1225
                } catch (ObjectNotFoundException e) {
1226
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1227
                } catch (RpcException e) {
1228
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1229
                } catch (DuplicateNameException e) {
1230
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1231
                } catch (GSSIOException e) {
1232
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1233
                } catch (QuotaExceededException e) {
1234
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1235
                } catch (Exception e) {
1236
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1237
                }
1238
        }
1239

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

    
1290
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1291
                dest = dest.substring(slash + 1);
1292

    
1293
                // Chop the resource namespace part
1294
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1295

    
1296
            dest = dest.endsWith("/")? dest: dest + '/';
1297
                return dest;
1298
        }
1299

    
1300
        /**
1301
         * Move the resource in the specified path to the trash bin.
1302
         *
1303
         * @param req the HTTP request
1304
         * @param resp the HTTP response
1305
         * @param path the path of the resource
1306
         * @throws IOException if an input/output error occurs
1307
         */
1308
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1309
                final User user = getUser(req);
1310
                User owner = getOwner(req);
1311
                Object resource = null;
1312
                try {
1313
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1314
                } catch (ObjectNotFoundException e) {
1315
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1316
                        return;
1317
                } catch (RpcException e) {
1318
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1319
                        return;
1320
                }
1321

    
1322
                try {
1323
                        if (resource instanceof Folder) {
1324
                                final Folder folderLocal = (Folder) resource;
1325
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1326
                                        @Override
1327
                                        public Void call() throws Exception {
1328
                                                getService().moveFolderToTrash(user.getId(), folderLocal.getId());
1329
                                                return null;
1330
                                        }
1331
                                });
1332
                        } else {
1333
                                final FileHeader fileLocal = (FileHeader) resource;
1334
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1335
                                        @Override
1336
                                        public Void call() throws Exception {
1337
                                                getService().moveFileToTrash(user.getId(), fileLocal.getId());
1338
                                                return null;
1339
                                        }
1340
                                });
1341
                        }
1342
                } catch (InsufficientPermissionsException e) {
1343
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1344
                } catch (ObjectNotFoundException e) {
1345
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1346
                } catch (RpcException e) {
1347
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1348
                } catch (Exception e) {
1349
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1350
                }
1351
        }
1352

    
1353
        /**
1354
         * Restore the resource in the specified path from the trash bin.
1355
         *
1356
         * @param req the HTTP request
1357
         * @param resp the HTTP response
1358
         * @param path the path of the resource
1359
         * @throws IOException if an input/output error occurs
1360
         */
1361
        private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1362
                final User user = getUser(req);
1363
                User owner = getOwner(req);
1364
                Object resource = null;
1365
                try {
1366
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1367
                } catch (ObjectNotFoundException e) {
1368
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1369
                        return;
1370
                } catch (RpcException e) {
1371
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1372
                        return;
1373
                }
1374

    
1375
                try {
1376
                        if (resource instanceof Folder) {
1377
                                final Folder folderLocal = (Folder) resource;
1378
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1379
                                        @Override
1380
                                        public Void call() throws Exception {
1381
                                                getService().removeFolderFromTrash(user.getId(), folderLocal.getId());
1382
                                                return null;
1383
                                        }
1384
                                });
1385
                        } else {
1386
                                final FileHeader fileLocal = (FileHeader) resource;
1387
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1388
                                        @Override
1389
                                        public Void call() throws Exception {
1390
                                                getService().removeFileFromTrash(user.getId(), fileLocal.getId());
1391
                                                return null;
1392
                                        }
1393
                                });
1394
                        }
1395
                } catch (InsufficientPermissionsException e) {
1396
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1397
                } catch (ObjectNotFoundException e) {
1398
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1399
                } catch (RpcException e) {
1400
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1401
                } catch (Exception e) {
1402
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1403
                }
1404
        }
1405

    
1406
        /**
1407
         * Update the resource in the specified path.
1408
         *
1409
         * @param req the HTTP request
1410
         * @param resp the HTTP response
1411
         * @param path the path of the resource
1412
         * @throws IOException if an input/output error occurs
1413
         */
1414
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1415
                final User user = getUser(req);
1416
                User owner = getOwner(req);
1417
                Object resource = null;
1418

    
1419
                try {
1420
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1421
                } catch (ObjectNotFoundException e) {
1422
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1423
                        return;
1424
                } catch (RpcException e) {
1425
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1426
                        return;
1427
                }
1428
                StringBuffer input = new StringBuffer();
1429
                JSONObject json = null;
1430
                if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1431
                        input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1432
                else {
1433
                        // Assume application/json
1434
                        BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1435
                        String line = null;
1436
                        while ((line = reader.readLine()) != null)
1437
                                input.append(line);
1438
                        reader.close();
1439
                }
1440
                try {
1441
                        json = new JSONObject(input.toString());
1442
                        if (logger.isDebugEnabled())
1443
                                logger.debug("JSON update: " + json);
1444
                        if (resource instanceof Folder) {
1445
                                final Folder folderLocal = (Folder) resource;
1446
                                String name = json.optString("name");
1447
                                if (!isValidResourceName(name)) {
1448
                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1449
                                return;
1450
                        }
1451
                                JSONArray permissions = json.optJSONArray("permissions");
1452
                                Set<Permission> perms = null;
1453
                                if (permissions != null)
1454
                                        perms = parsePermissions(user, permissions);
1455
                                Boolean readForAll = null;
1456
                                if (json.opt("readForAll") != null)
1457
                                        readForAll = json.optBoolean("readForAll");
1458
                                if (!name.isEmpty() || permissions != null || readForAll != null) {
1459
                                        final String fName = name.isEmpty()? null: name;
1460
                                        final Boolean freadForAll =  readForAll;
1461
                                        final Set<Permission> fPerms = perms;
1462
                                        Folder folderUpdated = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1463
                                                @Override
1464
                                                public Folder call() throws Exception {
1465
                                                        return getService().updateFolder(user.getId(), folderLocal.getId(), fName, freadForAll, fPerms);
1466
                                                }
1467

    
1468
                                        });
1469
                                        resp.getWriter().println(getNewUrl(req, folderUpdated));
1470
                                }
1471
                        } else {
1472
                                final FileHeader fileLocal = (FileHeader) resource;
1473
                                String name = null;
1474
                                if (json.opt("name") != null)
1475
                                        name = json.optString("name");
1476
                                if (name != null)
1477
                                        if (!isValidResourceName(name)) {
1478
                                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1479
                                        return;
1480
                                }
1481
                                Long modificationDate = null;
1482
                                if (json.optLong("modificationDate") != 0)
1483
                                        modificationDate = json.optLong("modificationDate");
1484
                                Boolean versioned = null;
1485
                                if (json.opt("versioned") != null)
1486
                                        versioned = json.getBoolean("versioned");
1487
                                JSONArray tagset = json.optJSONArray("tags");
1488
                                String tags = null;
1489
                                StringBuffer t = new StringBuffer();
1490
                                if (tagset != null) {
1491
                                        for (int i = 0; i < tagset.length(); i++)
1492
                                                t.append(tagset.getString(i) + ',');
1493
                                        tags = t.toString();
1494
                                }
1495
                                JSONArray permissions = json.optJSONArray("permissions");
1496
                                Set<Permission> perms = null;
1497
                                if (permissions != null)
1498
                                        perms = parsePermissions(user, permissions);
1499
                                Boolean readForAll = null;
1500
                                if (json.opt("readForAll") != null)
1501
                                        readForAll = json.optBoolean("readForAll");
1502
                                if (name != null || tags != null || modificationDate != null
1503
                                                        || versioned != null || perms != null
1504
                                                        || readForAll != null) {
1505
                                        final String fName = name;
1506
                                        final String fTags = tags;
1507
                                        final Date mDate = modificationDate != null? new Date(modificationDate): null;
1508
                                        final Boolean fVersioned = versioned;
1509
                                        final Boolean fReadForAll = readForAll;
1510
                                        final Set<Permission> fPerms = perms;
1511
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1512
                                                @Override
1513
                                                public Object call() throws Exception {
1514
                                                        getService().updateFile(user.getId(), fileLocal.getId(),
1515
                                                                                fName, fTags, mDate, fVersioned,
1516
                                                                                fReadForAll, fPerms);
1517
                                                        return null;
1518
                                                }
1519

    
1520
                                        });
1521
                                }
1522
                        }
1523
                } catch (JSONException e) {
1524
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1525
                } catch (InsufficientPermissionsException e) {
1526
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1527
                } catch (ObjectNotFoundException e) {
1528
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1529
                } catch (DuplicateNameException e) {
1530
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1531
                } catch (RpcException e) {
1532
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1533
                } catch (Exception e) {
1534
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1535
                        return;
1536
                }
1537
        }
1538

    
1539
        /**
1540
         * Returns the new URL of an updated folder.
1541
         */
1542
        private String getNewUrl(HttpServletRequest req, Folder folder) throws UnsupportedEncodingException {
1543
                String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1544
                String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1545
                if (parentUrl.indexOf(fpath) != -1)
1546
                        parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1547
                if(!parentUrl.endsWith("/"))
1548
                        parentUrl = parentUrl+"/";
1549
                parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1550
                return parentUrl;
1551
        }
1552

    
1553
        /**
1554
         * Helper method to convert a JSON array of permissions into a set of
1555
         * Permission objects.
1556
         *
1557
         * @param user the current user
1558
         * @param permissions the JSON array to parse
1559
         * @return the parsed set of permissions
1560
         * @throws JSONException if there was an error parsing the JSON object
1561
         * @throws RpcException if there was an error communicating with the EJB
1562
         * @throws ObjectNotFoundException if the user could not be found
1563
         * @throws UnsupportedEncodingException
1564
         */
1565
        private Set<Permission> parsePermissions(User user, JSONArray permissions)
1566
                        throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1567
                if (permissions == null)
1568
                        return null;
1569
                Set<Permission> perms = new HashSet<Permission>();
1570
                for (int i = 0; i < permissions.length(); i++) {
1571
                        JSONObject j = permissions.getJSONObject(i);
1572
                        Permission perm = new Permission();
1573
                        perm.setModifyACL(j.optBoolean("modifyACL"));
1574
                        perm.setRead(j.optBoolean("read"));
1575
                        perm.setWrite(j.optBoolean("write"));
1576
                        String permUser = j.optString("user");
1577
                        if (!permUser.isEmpty()) {
1578
                                User u = getService().findUser(permUser);
1579
                                if (u == null)
1580
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1581
                                perm.setUser(u);
1582
                        }
1583
                        // 31/8/2009: Add optional groupUri which takes priority if it exists
1584
                        String permGroupUri = j.optString("groupUri");
1585
                        String permGroup = j.optString("group");
1586
                        if (!permGroupUri.isEmpty()) {
1587
                                String[] names = permGroupUri.split("/");
1588
                                String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1589
                                String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1590
                                User u = getService().findUser(usr);
1591
                                if (u == null)
1592
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1593
                                Group g = getService().getGroup(u.getId(), grp);
1594
                                perm.setGroup(g);
1595
                        }
1596
                        else if (!permGroup.isEmpty()) {
1597
                                Group g = getService().getGroup(user.getId(), permGroup);
1598
                                perm.setGroup(g);
1599
                        }
1600
                        if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1601
                                throw new JSONException("A permission must correspond to either a user or a group");
1602
                        perms.add(perm);
1603
                }
1604
                return perms;
1605
        }
1606

    
1607
        /**
1608
         * Creates a new folder with the specified name under the folder in the provided path.
1609
         *
1610
         * @param req the HTTP request
1611
         * @param resp the HTTP response
1612
         * @param path the parent folder path
1613
         * @param folderName the name of the new folder
1614
         * @throws IOException if an input/output error occurs
1615
         */
1616
        private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1617
                if (logger.isDebugEnabled())
1618
                           logger.debug("Creating folder " + folderName + " in '" + path);
1619

    
1620
            final User user = getUser(req);
1621
            User owner = getOwner(req);
1622
        boolean exists = true;
1623
        try {
1624
                getService().getResourceAtPath(owner.getId(), path + folderName, false);
1625
        } catch (ObjectNotFoundException e) {
1626
            exists = false;
1627
        } catch (RpcException e) {
1628
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1629
                        return;
1630
                }
1631

    
1632
        if (exists) {
1633
            resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1634
                                    ", " + METHOD_HEAD);
1635
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1636
            return;
1637
        }
1638

    
1639
                Object parent;
1640
                try {
1641
                        parent = getService().getResourceAtPath(owner.getId(), path, true);
1642
                } catch (ObjectNotFoundException e) {
1643
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1644
                        return;
1645
                } catch (RpcException e) {
1646
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1647
                        return;
1648
                }
1649
                try {
1650
                        if (parent instanceof Folder) {
1651
                                final Folder folderLocal = (Folder) parent;
1652
                                Folder newFolder = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1653
                                        @Override
1654
                                        public Folder call() throws Exception {
1655
                                                return getService().createFolder(user.getId(), folderLocal.getId(), folderName);
1656
                                        }
1657

    
1658
                                });
1659
                        String newResource = getApiRoot() + newFolder.getURI();
1660
                        resp.setHeader("Location", newResource);
1661
                        resp.setContentType("text/plain");
1662
                        PrintWriter out = resp.getWriter();
1663
                        out.println(newResource);
1664
                        } else {
1665
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1666
                            return;
1667
                        }
1668
                } catch (DuplicateNameException e) {
1669
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1670
                    return;
1671
                } catch (InsufficientPermissionsException e) {
1672
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1673
                    return;
1674
                } catch (ObjectNotFoundException e) {
1675
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1676
                        return;
1677
                } catch (RpcException e) {
1678
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1679
                        return;
1680
                } catch (Exception e) {
1681
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1682
                        return;
1683
                }
1684
            resp.setStatus(HttpServletResponse.SC_CREATED);
1685
        }
1686

    
1687
        /**
1688
         * @param req
1689
         * @param resp
1690
         * @throws IOException
1691
         * @throws FileNotFoundException
1692
         */
1693
        void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1694
        String path = getInnerPath(req, PATH_FILES);
1695
                try {
1696
                    path = URLDecoder.decode(path, "UTF-8");
1697
                } catch (IllegalArgumentException e) {
1698
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1699
                        return;
1700
                }
1701
            if (logger.isDebugEnabled())
1702
                           logger.debug("Updating resource: " + path);
1703

    
1704
            final User user = getUser(req);
1705
            User owner = getOwner(req);
1706
            boolean exists = true;
1707
        Object resource = null;
1708
        FileHeader fileLocal = null;
1709
        try {
1710
                resource = getService().getResourceAtPath(owner.getId(), path, false);
1711
        } catch (ObjectNotFoundException e) {
1712
            exists = false;
1713
        } catch (RpcException e) {
1714
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1715
                        return;
1716
                }
1717

    
1718
        if (exists)
1719
                        if (resource instanceof FileHeader)
1720
                            fileLocal = (FileHeader) resource;
1721
                        else {
1722
                        resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1723
                            return;
1724
                }
1725
        boolean result = true;
1726

    
1727
        // Temporary content file used to support partial PUT.
1728
        File contentFile = null;
1729

    
1730
        Range range = parseContentRange(req, resp);
1731

    
1732
        InputStream resourceInputStream = null;
1733

    
1734
        // Append data specified in ranges to existing content for this
1735
        // resource - create a temporary file on the local filesystem to
1736
        // perform this operation.
1737
        // Assume just one range is specified for now
1738
        if (range != null) {
1739
            try {
1740
                                contentFile = executePartialPut(req, range, path);
1741
                        } catch (RpcException e) {
1742
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1743
                                return;
1744
                        } catch (ObjectNotFoundException e) {
1745
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
1746
                        return;
1747
                        } catch (InsufficientPermissionsException e) {
1748
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1749
                        return;
1750
                        }
1751
            resourceInputStream = new FileInputStream(contentFile);
1752
        } else
1753
                        resourceInputStream = req.getInputStream();
1754

    
1755
        try {
1756
                Folder folderLocal = null;
1757
                Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1758
                if (!(parent instanceof Folder)) {
1759
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1760
                        return;
1761
                }
1762
                       folderLocal = (Folder) parent;
1763
                final String name = getLastElement(path);
1764
                final String mimeType = context.getMimeType(name);
1765
                File uploadedFile = null;
1766
                try {
1767
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1768
                        } catch (IOException ex) {
1769
                                throw new GSSIOException(ex, false);
1770
                        }
1771
                FileHeader fileTemp = null;
1772
                final File uploadedf = uploadedFile;
1773
                        final Folder parentf = folderLocal;
1774
                        final FileHeader f = fileLocal;
1775
            if (exists)
1776
                    fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1777
                                        @Override
1778
                                        public FileHeader call() throws Exception {
1779
                                                return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1780
                                        }
1781
                                });
1782
                        else
1783
                                fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1784
                                        @Override
1785
                                        public FileHeader call() throws Exception {
1786
                                                return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1787
                                        }
1788

    
1789
                                });
1790
            updateAccounting(owner, new Date(), fileTemp.getCurrentBody().getFileSize());
1791
                        getService().removeFileUploadProgress(user.getId(), fileTemp.getName());
1792
        } catch(ObjectNotFoundException e) {
1793
            result = false;
1794
        } catch (RpcException e) {
1795
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1796
                        return;
1797
        } catch (IOException e) {
1798
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1799
                        return;
1800
                } catch (GSSIOException e) {
1801
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1802
                        return;
1803
                } catch (DuplicateNameException e) {
1804
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1805
                    return;
1806
                } catch (InsufficientPermissionsException e) {
1807
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1808
                    return;
1809
                } catch (QuotaExceededException e) {
1810
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1811
                    return;
1812
                } catch (Exception e) {
1813
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1814
                        return;
1815
                }
1816

    
1817
        if (result) {
1818
            if (exists)
1819
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1820
                        else
1821
                                resp.setStatus(HttpServletResponse.SC_CREATED);
1822
        } else
1823
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1824
        }
1825

    
1826
    /**
1827
     * Delete a resource.
1828
     *
1829
     * @param req The servlet request we are processing
1830
     * @param resp The servlet response we are processing
1831
         * @throws IOException if the response cannot be sent
1832
     */
1833
    void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1834
        String path = getInnerPath(req, PATH_FILES);
1835
            if (logger.isDebugEnabled())
1836
                           logger.debug("Deleting resource '" + path);
1837
            path = URLDecoder.decode(path, "UTF-8");
1838
            final User user = getUser(req);
1839
            User owner = getOwner(req);
1840
            boolean exists = true;
1841
            Object object = null;
1842
            try {
1843
                    object = getService().getResourceAtPath(owner.getId(), path, false);
1844
            } catch (ObjectNotFoundException e) {
1845
                    exists = false;
1846
            } catch (RpcException e) {
1847
                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1848
                        return;
1849
                }
1850

    
1851
            if (!exists) {
1852
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1853
                    return;
1854
            }
1855

    
1856
            Folder folderLocal = null;
1857
            FileHeader fileLocal = null;
1858
            if (object instanceof Folder)
1859
                    folderLocal = (Folder) object;
1860
            else
1861
                    fileLocal = (FileHeader) object;
1862

    
1863
            if (fileLocal != null)
1864
                        try {
1865
                                final FileHeader f = fileLocal;
1866
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1867
                                        @Override
1868
                                        public Void call() throws Exception {
1869
                                                getService().deleteFile(user.getId(), f.getId());
1870
                                                return null;
1871
                                        }
1872
                                });
1873
                } catch (InsufficientPermissionsException e) {
1874
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1875
                                return;
1876
                    } catch (ObjectNotFoundException e) {
1877
                            // Although we had already found the object, it was
1878
                            // probably deleted from another thread.
1879
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1880
                            return;
1881
                    } catch (RpcException e) {
1882
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1883
                            return;
1884
                    } catch (Exception e) {
1885
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1886
                            return;
1887
                    }
1888
                else if (folderLocal != null)
1889
                        try {
1890
                                final Folder fo = folderLocal;
1891
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1892
                                        @Override
1893
                                        public Void call() throws Exception {
1894
                                                getService().deleteFolder(user.getId(), fo.getId());
1895
                                                return null;
1896
                                        }
1897
                                });
1898
                } catch (InsufficientPermissionsException e) {
1899
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1900
                        return;
1901
                    } catch (ObjectNotFoundException e) {
1902
                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1903
                        return;
1904
                    } catch (RpcException e) {
1905
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1906
                        return;
1907
                    } catch (Exception e) {
1908
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1909
                        return;
1910
                    }
1911
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1912
            return;
1913
    }
1914

    
1915
        /**
1916
     * Return an InputStream to a JSON representation of the contents
1917
     * of this directory.
1918
     *
1919
         * @param user the user that made the request
1920
     * @param folder the specified directory
1921
     * @return an input stream with the rendered contents
1922
         * @throws IOException if the response cannot be sent
1923
     * @throws ServletException
1924
         * @throws InsufficientPermissionsException if the user does not have
1925
         *                         the necessary privileges to read the directory
1926
     */
1927
    private InputStream renderJson(User user, Folder folder) throws IOException,
1928
                    ServletException, InsufficientPermissionsException {
1929
            try {
1930
                        folder = getService().expandFolder(folder);
1931
                } catch (ObjectNotFoundException e1) {
1932
                        // TODO Auto-generated catch block
1933
                        e1.printStackTrace();
1934
                } catch (RpcException e1) {
1935
                        // TODO Auto-generated catch block
1936
                        e1.printStackTrace();
1937
                }
1938
            JSONObject json = new JSONObject();
1939
            try {
1940
                        json.put("name", folder.getName()).
1941
                                        put("owner", folder.getOwner().getUsername()).
1942
                                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1943
                                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1944
                                        put("deleted", folder.isDeleted()).
1945
                                        put("shared", folder.getShared()).
1946
                                        put("readForAll", folder.isReadForAll());
1947

    
1948
                        if (folder.getAuditInfo().getModifiedBy() != null)
1949
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1950
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1951
                        if (folder.getParent() != null) {
1952
                                JSONObject j = new JSONObject();
1953
                                j.put("uri", getApiRoot() + folder.getParent().getURI());
1954
                                j.put("name", folder.getParent().getName());
1955
                                json.put("parent", j);
1956
                        }
1957
                    List<JSONObject> subfolders = new ArrayList<JSONObject>();
1958
                    for (Folder f: folder.getSubfolders())
1959
                                if (!f.isDeleted()) {
1960
                                        JSONObject j = new JSONObject();
1961
                                        j.put("name", f.getName()).
1962
                                                put("uri", getApiRoot() + f.getURI()).
1963
                                                put("shared", f.getShared());
1964
                                        subfolders.add(j);
1965
                                }
1966
                    json.put("folders", subfolders);
1967
                    List<JSONObject> files = new ArrayList<JSONObject>();
1968
                    List<FileHeader> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1969
                    for (FileHeader f: fileHeaders) {
1970
                            JSONObject j = new JSONObject();
1971
                                j.put("name", f.getName()).
1972
                                        put("owner", f.getOwner().getUsername()).
1973
                                        put("deleted", f.isDeleted()).
1974
                                        put("version", f.getCurrentBody().getVersion()).
1975
                                        put("content", f.getCurrentBody().getMimeType()).
1976
                                        put("size", f.getCurrentBody().getFileSize()).
1977
                                        put("shared", f.getShared()).
1978
                                        put("versioned",f.isVersioned()).
1979
                                        put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1980
                                        put("path", f.getFolder().getPath()).
1981
                                        put("uri", getApiRoot() + f.getURI());
1982
                                if (f.getAuditInfo().getModificationDate() != null)
1983
                                        j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1984
                                files.add(j);
1985
                    }
1986
                    json.put("files", files);
1987
                    Set<Permission> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1988
                    json.put("permissions", renderJson(perms));
1989
                } catch (JSONException e) {
1990
                        throw new ServletException(e);
1991
                } catch (ObjectNotFoundException e) {
1992
                        throw new ServletException(e);
1993
                } catch (RpcException e) {
1994
                        throw new ServletException(e);
1995
                }
1996

    
1997
            // Prepare a writer to a buffered area
1998
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
1999
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2000
            PrintWriter writer = new PrintWriter(osWriter);
2001

    
2002
            // Return an input stream to the underlying bytes
2003
            writer.write(json.toString());
2004
            writer.flush();
2005
            return new ByteArrayInputStream(stream.toByteArray());
2006
    }
2007

    
2008
        /**
2009
     * Return a String with a JSON representation of the metadata
2010
     * of the specified folder.
2011
         * @throws RpcException
2012
         * @throws InsufficientPermissionsException
2013
         * @throws ObjectNotFoundException
2014
     */
2015
    private String renderJsonMetadata(User user, Folder folder)
2016
                    throws ServletException, InsufficientPermissionsException {
2017
            // Check if the user has read permission.
2018
                try {
2019
                        if (!getService().canReadFolder(user.getId(), folder.getId()))
2020
                                throw new InsufficientPermissionsException();
2021
                } catch (ObjectNotFoundException e) {
2022
                        throw new ServletException(e);
2023
                } catch (RpcException e) {
2024
                        throw new ServletException(e);
2025
                }
2026

    
2027
            JSONObject json = new JSONObject();
2028
            try {
2029
                        json.put("name", URLEncoder.encode(folder.getName(), "UTF-8")).
2030
                        put("owner", folder.getOwner().getUsername()).
2031
                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
2032
                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
2033
                        put("deleted", folder.isDeleted());
2034
                        if (folder.getAuditInfo().getModifiedBy() != null)
2035
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2036
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2037
                } catch (JSONException e) {
2038
                        throw new ServletException(e);
2039
                }
2040
        catch (UnsupportedEncodingException e) {
2041
            throw new ServletException(e);
2042
        }
2043
        return json.toString();
2044
    }
2045

    
2046
        /**
2047
     * Return a String with a JSON representation of the metadata
2048
     * of the specified file. If an old file body is provided, then
2049
     * the metadata of that particular version will be returned.
2050
     *
2051
         * @param user the user that made the request
2052
     * @param file the specified file header
2053
     * @param oldBody the version number
2054
     * @return the JSON-encoded file
2055
     * @throws ServletException
2056
         * @throws InsufficientPermissionsException if the user does not have
2057
         *                         the necessary privileges to read the directory
2058
     */
2059
    private String renderJson(User user, FileHeader file, FileBody oldBody)
2060
                    throws ServletException, InsufficientPermissionsException {
2061
            JSONObject json = new JSONObject();
2062
            try {
2063
                    file=getService().expandFile(file);
2064
                    // Need to encode file name in order to properly display it in the web client.
2065
                        json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2066
                                        put("owner", file.getOwner().getUsername()).
2067
                                        put("versioned", file.isVersioned()).
2068
                                        put("version", oldBody != null ? oldBody.getVersion() : file.getCurrentBody().getVersion()).
2069
                                        put("readForAll", file.isReadForAll()).
2070
                                        put("shared", file.getShared()).
2071
                                        put("tags", renderJson(file.getFileTagsAsStrings())).
2072
                                        put("path", file.getFolder().getPath()).
2073
                                    put("uri", getApiRoot() + file.getURI()).
2074
                                        put("deleted", file.isDeleted());
2075
                        JSONObject j = new JSONObject();
2076
                        j.put("uri", getApiRoot() + file.getFolder().getURI()).
2077
                                        put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2078
                        json.put("folder", j);
2079
                        if (oldBody != null)
2080
                                json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2081
                                                put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2082
                                                put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2083
                                                put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2084
                                                put("content", oldBody.getMimeType()).
2085
                                                put("size", oldBody.getFileSize());
2086
                        else
2087
                                json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2088
                                                put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2089
                                                put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2090
                                                put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2091
                                                put("content", file.getCurrentBody().getMimeType()).
2092
                                                put("size", file.getCurrentBody().getFileSize());
2093
                    Set<Permission> perms = getService().getFilePermissions(user.getId(), file.getId());
2094
                    json.put("permissions", renderJson(perms));
2095
                } catch (JSONException e) {
2096
                        throw new ServletException(e);
2097
                } catch (ObjectNotFoundException e) {
2098
                        throw new ServletException(e);
2099
                } catch (RpcException e) {
2100
                        throw new ServletException(e);
2101
                } catch (UnsupportedEncodingException e) {
2102
                        throw new ServletException(e);
2103
                }
2104

    
2105
            return json.toString();
2106
    }
2107

    
2108
        /**
2109
         * Return a String with a JSON representation of the
2110
         * specified set of permissions.
2111
     *
2112
         * @param permissions the set of permissions
2113
         * @return the JSON-encoded object
2114
         * @throws JSONException
2115
         * @throws UnsupportedEncodingException
2116
         */
2117
        private JSONArray renderJson(Set<Permission> permissions) throws JSONException, UnsupportedEncodingException {
2118
                JSONArray perms = new JSONArray();
2119
                for (Permission p: permissions) {
2120
                        JSONObject permission = new JSONObject();
2121
                        permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2122
                        if (p.getUser() != null)
2123
                                permission.put("user", p.getUser().getUsername());
2124
                        if (p.getGroup() != null) {
2125
                                Group group = p.getGroup();
2126
                                permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2127
                                permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2128
                        }
2129
                        perms.put(permission);
2130
                }
2131
                return perms;
2132
        }
2133

    
2134
        /**
2135
         * Return a String with a JSON representation of the
2136
         * specified collection of tags.
2137
     *
2138
         * @param tags the collection of tags
2139
         * @return the JSON-encoded object
2140
         * @throws JSONException
2141
         * @throws UnsupportedEncodingException
2142
         */
2143
        private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2144
                JSONArray tagArray = new JSONArray();
2145
                for (String t: tags)
2146
                        tagArray.put(URLEncoder.encode(t,"UTF-8"));
2147
                return tagArray;
2148
        }
2149

    
2150
        /**
2151
         * Retrieves the user who owns the destination namespace, for a
2152
         * copy or move request.
2153
         *
2154
         * @param req the HTTP request
2155
         * @return the owner of the namespace
2156
         */
2157
        protected User getDestinationOwner(HttpServletRequest req) {
2158
                return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2159
        }
2160

    
2161
        /**
2162
         * A helper inner class for updating the progress status of a file upload.
2163
         *
2164
         * @author kman
2165
         */
2166
        public static class StatusProgressListener implements ProgressListener {
2167
                private int percentLogged = 0;
2168
                private long bytesTransferred = 0;
2169

    
2170
                private long fileSize = -100;
2171

    
2172
                private Long userId;
2173

    
2174
                private String filename;
2175

    
2176
                private ExternalAPI service;
2177

    
2178
                public StatusProgressListener(ExternalAPI aService) {
2179
                        service = aService;
2180
                }
2181

    
2182
                /**
2183
                 * Modify the userId.
2184
                 *
2185
                 * @param aUserId the userId to set
2186
                 */
2187
                public void setUserId(Long aUserId) {
2188
                        userId = aUserId;
2189
                }
2190

    
2191
                /**
2192
                 * Modify the filename.
2193
                 *
2194
                 * @param aFilename the filename to set
2195
                 */
2196
                public void setFilename(String aFilename) {
2197
                        filename = aFilename;
2198
                }
2199

    
2200
                @Override
2201
                public void update(long bytesRead, long contentLength, int items) {
2202
                        //monitoring per percent of bytes uploaded
2203
                        bytesTransferred = bytesRead;
2204
                        if (fileSize != contentLength)
2205
                                fileSize = contentLength;
2206
                        int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2207
                        if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2208
                                if (percent != percentLogged){
2209
                                        percentLogged = percent;
2210
                                        try {
2211
                                                if (userId != null && filename != null)
2212
                                                        service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2213
                                        } catch (ObjectNotFoundException e) {
2214
                                                // Swallow the exception since it is going to be caught
2215
                                                // by previously called methods
2216
                                        }
2217
                                }
2218
                }
2219
        }
2220

    
2221
        /**
2222
         * Return an InputStream to an HTML representation of the contents of this
2223
         * directory.
2224
         *
2225
         * @param contextPath Context path to which our internal paths are relative
2226
         * @param path the requested path to the resource
2227
         * @param folder the specified directory
2228
         * @param user the specified user
2229
         * @return an input stream with the rendered contents
2230
         * @throws IOException
2231
         * @throws ServletException
2232
         */
2233
        private InputStream renderHtml(String contextPath, String path, Folder folder, User user)
2234
                throws IOException, ServletException {
2235
                String name = folder.getName();
2236
                // Prepare a writer to a buffered area
2237
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
2238
                OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2239
                PrintWriter writer = new PrintWriter(osWriter);
2240
                StringBuffer sb = new StringBuffer();
2241
                // rewriteUrl(contextPath) is expensive. cache result for later reuse
2242
                String rewrittenContextPath = rewriteUrl(contextPath);
2243
                // Render the page header
2244
                sb.append("<html>\r\n");
2245
                sb.append("<head>\r\n");
2246
                sb.append("<title>");
2247
                sb.append("Index of " + name);
2248
                sb.append("</title>\r\n");
2249
                sb.append("<STYLE><!--");
2250
                sb.append(GSS_CSS);
2251
                sb.append("--></STYLE> ");
2252
                sb.append("</head>\r\n");
2253
                sb.append("<body>");
2254
                sb.append("<h1>");
2255
                sb.append("Index of " + name);
2256

    
2257
                // Render the link to our parent (if required)
2258
                String parentDirectory = path;
2259
                if (parentDirectory.endsWith("/"))
2260
                        parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2261
                int slash = parentDirectory.lastIndexOf('/');
2262
                if (slash >= 0) {
2263
                        String parent = path.substring(0, slash);
2264
                        sb.append(" - <a href=\"");
2265
                        sb.append(rewrittenContextPath);
2266
                        if (parent.equals(""))
2267
                                parent = "/";
2268
                        sb.append(parent);
2269
                        if (!parent.endsWith("/"))
2270
                                sb.append("/");
2271
                        sb.append("\">");
2272
                        sb.append("<b>");
2273
                        sb.append("Up To " + parent);
2274
                        sb.append("</b>");
2275
                        sb.append("</a>");
2276
                }
2277

    
2278
                sb.append("</h1>");
2279
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2280

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

    
2283
                // Render the column headings
2284
                sb.append("<tr>\r\n");
2285
                sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2286
                sb.append("Name");
2287
                sb.append("</strong></font></td>\r\n");
2288
                sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2289
                sb.append("Size");
2290
                sb.append("</strong></font></td>\r\n");
2291
                sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2292
                sb.append("Last modified");
2293
                sb.append("</strong></font></td>\r\n");
2294
                sb.append("</tr>");
2295
                // Render the directory entries within this directory
2296
                boolean shade = false;
2297
                Iterator iter = folder.getSubfolders().iterator();
2298
                while (iter.hasNext()) {
2299
                        Folder subf = (Folder) iter.next();
2300
                        if(subf.isReadForAll() && !subf.isDeleted()){
2301
                                String resourceName = subf.getName();
2302
                                if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2303
                                        continue;
2304

    
2305
                                sb.append("<tr");
2306
                                if (shade)
2307
                                        sb.append(" bgcolor=\"#eeeeee\"");
2308
                                sb.append(">\r\n");
2309
                                shade = !shade;
2310

    
2311
                                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2312
                                sb.append("<a href=\"");
2313
                                sb.append(rewrittenContextPath+path);
2314
                                sb.append(rewriteUrl(resourceName));
2315
                                sb.append("/");
2316
                                sb.append("\"><tt>");
2317
                                sb.append(RequestUtil.filter(resourceName));
2318
                                sb.append("/");
2319
                                sb.append("</tt></a></td>\r\n");
2320

    
2321
                                sb.append("<td align=\"right\"><tt>");
2322
                                sb.append("&nbsp;");
2323
                                sb.append("</tt></td>\r\n");
2324

    
2325
                                sb.append("<td align=\"right\"><tt>");
2326
                                sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2327
                                sb.append("</tt></td>\r\n");
2328

    
2329
                                sb.append("</tr>\r\n");
2330

    
2331
                        }
2332
                }
2333
                List<FileHeader> files;
2334
                try {
2335
                        files = getService().getFiles(user.getId(), folder.getId(), true);
2336
                } catch (ObjectNotFoundException e) {
2337
                        throw new ServletException(e.getMessage());
2338
                } catch (InsufficientPermissionsException e) {
2339
                        throw new ServletException(e.getMessage());
2340
                } catch (RpcException e) {
2341
                        throw new ServletException(e.getMessage());
2342
                }
2343
                for (FileHeader file : files)
2344
                //Display only file resources that are marked as public and are not deleted
2345
                        if(file.isReadForAll() && !file.isDeleted()){
2346
                                String resourceName = file.getName();
2347
                                if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2348
                                        continue;
2349

    
2350
                                sb.append("<tr");
2351
                                if (shade)
2352
                                        sb.append(" bgcolor=\"#eeeeee\"");
2353
                                sb.append(">\r\n");
2354
                                shade = !shade;
2355

    
2356
                                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2357
                                sb.append("<a href=\"");
2358
                                sb.append(rewrittenContextPath + path);
2359
                                sb.append(rewriteUrl(resourceName));
2360
                                sb.append("\"><tt>");
2361
                                sb.append(RequestUtil.filter(resourceName));
2362
                                sb.append("</tt></a></td>\r\n");
2363

    
2364
                                sb.append("<td align=\"right\"><tt>");
2365
                                sb.append(renderSize(file.getCurrentBody().getFileSize()));
2366
                                sb.append("</tt></td>\r\n");
2367

    
2368
                                sb.append("<td align=\"right\"><tt>");
2369
                                sb.append(getLastModifiedHttp(file.getAuditInfo()));
2370
                                sb.append("</tt></td>\r\n");
2371

    
2372
                                sb.append("</tr>\r\n");
2373
                        }
2374

    
2375
                // Render the page footer
2376
                sb.append("</table>\r\n");
2377

    
2378
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2379
                sb.append("</body>\r\n");
2380
                sb.append("</html>\r\n");
2381

    
2382
                // Return an input stream to the underlying bytes
2383
                writer.write(sb.toString());
2384
                writer.flush();
2385
                return new ByteArrayInputStream(stream.toByteArray());
2386

    
2387
        }
2388
}