Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (82.5 kB)

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

    
21
import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import gr.ebs.gss.client.exceptions.DuplicateNameException;
23
import gr.ebs.gss.client.exceptions.GSSIOException;
24
import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25
import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26
import gr.ebs.gss.client.exceptions.QuotaExceededException;
27
import gr.ebs.gss.client.exceptions.RpcException;
28
import gr.ebs.gss.server.Login;
29
import gr.ebs.gss.server.domain.FileUploadStatus;
30
import gr.ebs.gss.server.domain.User;
31
import gr.ebs.gss.server.domain.dto.FileBodyDTO;
32
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
33
import gr.ebs.gss.server.domain.dto.FolderDTO;
34
import gr.ebs.gss.server.domain.dto.GroupDTO;
35
import gr.ebs.gss.server.domain.dto.PermissionDTO;
36
import gr.ebs.gss.server.ejb.ExternalAPI;
37
import gr.ebs.gss.server.ejb.TransactionHelper;
38
import gr.ebs.gss.server.webdav.Range;
39
import gr.ebs.gss.server.webdav.RequestUtil;
40

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

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

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

    
90

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

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

    
108
        private static final int TRACK_PROGRESS_PERCENT = 5;
109

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

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

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

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

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

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

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

    
145

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

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

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

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

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

    
220
            if (!exists) {
221
                        if (authDeferred) {
222
                                // We do not want to leak information if the request
223
                                // was not authenticated.
224
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
225
                                return;
226
                        }
227
                    // A request for upload progress.
228
                    if (progress != null && content) {
229
                            serveProgress(req, resp, progress, user, null);
230
                                return;
231
                    }
232

    
233
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
234
                    return;
235
            }
236

    
237
            if (resource instanceof FolderDTO)
238
                    folder = (FolderDTO) resource;
239
            else
240
                    file = (FileHeaderDTO) resource;
241

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

    
308
                                        // Fetch the Authorization parameter and find the user specified in it.
309
                                        String[] authParts = auth.split(" ");
310
                                        if (authParts.length != 2) {
311
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
312
                                            return;
313
                                    }
314
                                        String username = authParts[0];
315
                                        String signature = authParts[1];
316
                                        user = null;
317
                                        try {
318
                                                user = getService().findUser(username);
319
                                        } catch (RpcException e) {
320
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
321
                                                return;
322
                                        }
323
                                        if (user == null) {
324
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
325
                                            return;
326
                                    }
327
                                        req.setAttribute(USER_ATTRIBUTE, user);
328

    
329
                                        // Remove the servlet path from the request URI.
330
                                        String p = req.getRequestURI();
331
                                        String servletPath = req.getContextPath() + req.getServletPath();
332
                                        p = p.substring(servletPath.length());
333
                                        // Validate the signature in the Authorization parameter.
334
                                        String data = req.getMethod() + dateParam + p;
335
                                        if (!isSignatureValid(signature, user, data)) {
336
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
337
                                            return;
338
                                    }
339
                                }
340
                        } else if (file != null && !file.isReadForAll() || folder != null && !folder.isReadForAll()) {
341
                                // Check for a read-for-all file request.
342
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
343
                                return;
344
                        }
345

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

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

    
358
            // A request for upload progress.
359
            if (progress != null && content) {
360
                    if (file == null) {
361
                            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
362
                            return;
363
                    }
364
                    serveProgress(req, resp, progress, user, file);
365
                        return;
366
            }
367

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

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

    
405
            // Find content type.
406
            String contentType = null;
407
            boolean isContentHtml = false;
408

    
409
            if (file != null) {
410
                contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
411
                if (contentType == null) {
412
                        contentType = context.getMimeType(file.getName());
413
                        file.setMimeType(contentType);
414
                }
415
            } else { // folder != null
416
                    String accept = req.getHeader("Accept");
417
                    // The order in this conditional pessimizes the common API case,
418
                    // but is important for backwards compatibility with existing
419
                    // clients who send no accept header and expect a JSON response.
420
                    if (accept != null && accept.contains("text/html")) {
421
                            contentType = "text/html;charset=UTF-8";
422
                            isContentHtml = true;
423
                    } else
424
                            contentType = "application/json;charset=UTF-8";
425
                }
426

    
427

    
428
            ArrayList ranges = null;
429
            long contentLength = -1L;
430

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

    
463
            ServletOutputStream ostream = null;
464
            PrintWriter writer = null;
465

    
466
            if (content)
467
                        try {
468
                            ostream = resp.getOutputStream();
469
                    } catch (IllegalStateException e) {
470
                            // If it fails, we try to get a Writer instead if we're
471
                            // trying to serve a text file
472
                            if ( contentType == null
473
                                                    || contentType.startsWith("text")
474
                                                    || contentType.endsWith("xml") )
475
                                        writer = resp.getWriter();
476
                                else
477
                                        throw e;
478
                    }
479

    
480
            if (folder != null
481
                                    || (ranges == null || ranges.isEmpty())
482
                                                            && req.getHeader("Range") == null
483
                                                            || 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
                                else
496
                                        // Set the content-length as String to be able to use a long
497
                                    resp.setHeader("content-length", "" + contentLength);
498
                    }
499

    
500
                    InputStream renderResult = null;
501
                    if (content)
502
                            // Serve the directory browser for a public folder
503
                            if (isContentHtml)
504
                                    renderResult = renderHtml(req.getContextPath(), path, folder,user);
505
                            // Serve the directory for an ordinary folder
506
                            else
507
                                    try {
508
                                            renderResult = renderJson(user, folder);
509
                                            } catch (InsufficientPermissionsException e) {
510
                                                    resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
511
                                                    return;
512
                                            }
513

    
514

    
515
                    // Copy the input stream to our output stream (if requested)
516
                    if (content) {
517
                            try {
518
                                    resp.setBufferSize(output);
519
                            } catch (IllegalStateException e) {
520
                                    // Silent catch
521
                            }
522
                            try {
523
                                    if(file != null)
524
                                                if (needsContentDisposition(req))
525
                                                    resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
526
                                            else
527
                                                    resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
528
                                    if (ostream != null)
529
                                                copy(file, renderResult, ostream, req, oldBody);
530
                                        else
531
                                                copy(file, renderResult, writer, req, oldBody);
532
                                    if (file!=null) updateAccounting(owner, new Date(), contentLength);
533
                        } catch (ObjectNotFoundException e) {
534
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
535
                                return;
536
                        } catch (InsufficientPermissionsException e) {
537
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
538
                                return;
539
                        } catch (RpcException e) {
540
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
541
                                return;
542
                            }
543
                    }
544
            } else {
545
                    if (ranges == null || ranges.isEmpty())
546
                            return;
547
                    // Partial content response.
548
                    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
549

    
550
                    if (ranges.size() == 1) {
551
                            Range range = (Range) ranges.get(0);
552
                            resp.addHeader("Content-Range", "bytes "
553
                                                    + range.start
554
                                                    + "-" + range.end + "/"
555
                                                    + range.length);
556
                            long length = range.end - range.start + 1;
557
                            if (length < Integer.MAX_VALUE)
558
                                        resp.setContentLength((int) length);
559
                                else
560
                                        // Set the content-length as String to be able to use a long
561
                                    resp.setHeader("content-length", "" + length);
562

    
563
                            if (contentType != null) {
564
                                    if (logger.isDebugEnabled())
565
                                            logger.debug("contentType='" + contentType + "'");
566
                                    resp.setContentType(contentType);
567
                            }
568

    
569
                            if (content) {
570
                                    try {
571
                                            resp.setBufferSize(output);
572
                                    } catch (IllegalStateException e) {
573
                                            // Silent catch
574
                                    }
575
                                    try {
576
                                            if (ostream != null)
577
                                                        copy(file, ostream, range, req, oldBody);
578
                                                else
579
                                                        copy(file, writer, range, req, oldBody);
580
                                            updateAccounting(owner, new Date(), contentLength);
581
                                } catch (ObjectNotFoundException e) {
582
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
583
                                        return;
584
                                } catch (InsufficientPermissionsException e) {
585
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
586
                                        return;
587
                                } catch (RpcException e) {
588
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
589
                                        return;
590
                                }
591
                            }
592
                    } else {
593
                            resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
594
                            if (content) {
595
                                    try {
596
                                            resp.setBufferSize(output);
597
                                    } catch (IllegalStateException e) {
598
                                            // Silent catch
599
                                    }
600
                                    try {
601
                                            if (ostream != null)
602
                                                        copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
603
                                                else
604
                                                        copy(file, writer, ranges.iterator(), contentType, req, oldBody);
605
                                            updateAccounting(owner, new Date(), contentLength);
606
                                } catch (ObjectNotFoundException e) {
607
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
608
                                        return;
609
                                } catch (InsufficientPermissionsException e) {
610
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
611
                                        return;
612
                                } catch (RpcException e) {
613
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
614
                                        return;
615
                                }
616
                            }
617
                    }
618
            }
619
    }
620

    
621
        /**
622
         * Handles an authentication failure. If no Authorization or Date request
623
         * parameters and no Authorization, Date or X-GSS-Date headers were present,
624
         * this is a browser request, so redirect to login and then let the user get
625
         * back to the file. Otherwise it's a bogus client request and Forbidden is
626
         * returned.
627
         */
628
        private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
629
                if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
630
                                req.getParameter(DATE_PARAMETER) == null &&
631
                                req.getHeader(AUTHORIZATION_HEADER) == null &&
632
                                req.getDateHeader(DATE_HEADER) == -1 &&
633
                                req.getDateHeader(GSS_DATE_HEADER) == -1)
634
                        resp.sendRedirect(getConfiguration().getString("loginUrl") +
635
                                        "?next=" + req.getRequestURL().toString());
636
                else
637
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
638
        }
639

    
640
        /**
641
         * Return the filename of the specified file properly formatted for
642
         * including in the Content-Disposition header.
643
         */
644
        private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
645
                return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
646
        }
647

    
648
        /**
649
         * Determines whether the user agent needs the Content-Disposition
650
         * header to be set, in order to properly download a file.
651
         *
652
         * @param req the HTTP request
653
         * @return true if the Content-Disposition HTTP header must be set
654
         */
655
        private boolean needsContentDisposition(HttpServletRequest req) {
656
                /*String agent = req.getHeader("user-agent");
657
                if (agent != null && agent.contains("MSIE"))
658
                        return true;*/
659
                String dl = req.getParameter("dl");
660
                if ("1".equals(dl))
661
                        return true;
662
                return false;
663
        }
664

    
665
        /**
666
         * Sends a progress update on the amount of bytes received until now for
667
         * a file that the current user is currently uploading.
668
         *
669
         * @param req the HTTP request
670
         * @param resp the HTTP response
671
         * @param parameter the value for the progress request parameter
672
         * @param user the current user
673
         * @param file the file being uploaded, or null if the request is about a new file
674
         * @throws IOException if an I/O error occurs
675
         */
676
        private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
677
                                String parameter, User user, FileHeaderDTO file)        throws IOException {
678
                String filename = file == null ? parameter : file.getName();
679
                try {
680
                        FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
681
                        if (status == null) {
682
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
683
                                return;
684
                        }
685
                        JSONObject json = new JSONObject();
686
                        json.put("bytesUploaded", status.getBytesUploaded()).
687
                                put("bytesTotal", status.getFileSize());
688
                        sendJson(req, resp, json.toString());
689

    
690
                        // Workaround for IE's broken caching behavior.
691
                    resp.setHeader("Expires", "-1");
692
                        return;
693
                } catch (RpcException e) {
694
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
695
                        return;
696
                } catch (JSONException e) {
697
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
698
                        return;
699
                }
700
        }
701

    
702
        /**
703
         * Server a POST request to create/modify a file or folder.
704
         *
705
         * @param req the HTTP request
706
         * @param resp the HTTP response
707
     * @exception IOException if an input/output error occurs
708
         */
709
        void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
710
                boolean authDeferred = getAuthDeferred(req);
711
            if (!authDeferred && req.getParameterMap().size() > 1) {
712
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
713
                    return;
714
            }
715
        String path = getInnerPath(req, PATH_FILES);
716
            path = path.endsWith("/")? path: path + '/';
717
                try {
718
                    path = URLDecoder.decode(path, "UTF-8");
719
                } catch (IllegalArgumentException e) {
720
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
721
                        return;
722
                }
723
            // We only defer authenticating multipart POST requests.
724
            if (authDeferred) {
725
                        if (!ServletFileUpload.isMultipartContent(req)) {
726
                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
727
                            return;
728
                    }
729
                        handleMultipart(req, resp, path);
730
                        return;
731
                }
732

    
733
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
734
            if (!isValidResourceName(newName)) {
735
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
736
                    return;
737
            }
738
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
739
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
740
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
741
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
742
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
743
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
744

    
745
            if (newName != null)
746
                        createFolder(req, resp, path, newName);
747
            else if (hasUpdateParam)
748
                        updateResource(req, resp, path);
749
                else if (hasTrashParam)
750
                        trashResource(req, resp, path);
751
                else if (hasRestoreParam)
752
                        restoreResource(req, resp, path);
753
                else if (copyTo != null)
754
                        copyResource(req, resp, path, copyTo);
755
                else if (moveTo != null)
756
                        moveResource(req, resp, path, moveTo);
757
                else if (restoreVersion != null)
758
                        restoreVersion(req, resp, path, restoreVersion);
759
                else
760
                        // IE with Gears uses POST for multiple uploads.
761
                        putResource(req, resp);
762
        }
763

    
764
        /**
765
         * Restores a previous version for a file.
766
         *
767
         * @param req the HTTP request
768
         * @param resp the HTTP response
769
         * @param path the resource path
770
         * @param version the version number to restore
771
         * @throws IOException if an I/O error occurs
772
         */
773
        private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
774
                final User user = getUser(req);
775
                User owner = getOwner(req);
776
                Object resource = null;
777
                try {
778
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
779
                } catch (ObjectNotFoundException e) {
780
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
781
                        return;
782
                } catch (RpcException e) {
783
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
784
                        return;
785
                }
786
                if (resource instanceof FolderDTO) {
787
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
788
                        return;
789
                }
790

    
791
                try {
792
                        final FileHeaderDTO file = (FileHeaderDTO) resource;
793
                        final int oldVersion = Integer.parseInt(version);
794

    
795
                        new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
796
                                @Override
797
                                public Void call() throws Exception {
798
                                        getService().restoreVersion(user.getId(), file.getId(), oldVersion);
799
                                        return null;
800
                                }
801
                        });
802
                } catch (InsufficientPermissionsException e) {
803
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
804
                } catch (ObjectNotFoundException e) {
805
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
806
                } catch (RpcException e) {
807
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
808
                } catch (GSSIOException e) {
809
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
810
                } catch (QuotaExceededException e) {
811
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
812
                } catch (NumberFormatException e) {
813
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
814
                } catch (Exception e) {
815
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
816
                }
817
        }
818

    
819
        /**
820
         * A method for handling multipart POST requests for uploading
821
         * files from browser-based JavaScript clients.
822
         *
823
         * @param request the HTTP request
824
         * @param response the HTTP response
825
         * @param path the resource path
826
         * @throws IOException in case an error occurs writing to the
827
         *                 response stream
828
         */
829
        private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
830
            if (logger.isDebugEnabled())
831
                           logger.debug("Multipart POST for resource: " + path);
832

    
833
            User owner = getOwner(request);
834
            boolean exists = true;
835
        Object resource = null;
836
        FileHeaderDTO file = null;
837
        try {
838
                resource = getService().getResourceAtPath(owner.getId(), path, false);
839
        } catch (ObjectNotFoundException e) {
840
            exists = false;
841
        } catch (RpcException e) {
842
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
843
                        return;
844
                }
845

    
846
        if (exists)
847
                        if (resource instanceof FileHeaderDTO) {
848
                            file = (FileHeaderDTO) resource;
849
                            if (file.isDeleted()) {
850
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
851
                                return;
852
                            }
853
                        } else {
854
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
855
                            return;
856
                }
857

    
858
            Object parent;
859
            String parentPath = null;
860
                try {
861
                        parentPath = getParentPath(path);
862
                        parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
863
                } catch (ObjectNotFoundException e) {
864
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
865
                    return;
866
                } catch (RpcException e) {
867
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
868
                        return;
869
                }
870
            if (!(parent instanceof FolderDTO)) {
871
                    response.sendError(HttpServletResponse.SC_CONFLICT);
872
                    return;
873
            }
874
            final FolderDTO folder = (FolderDTO) parent;
875
            final String fileName = getLastElement(path);
876

    
877
                FileItemIterator iter;
878
                File uploadedFile = null;
879
                try {
880
                        // Create a new file upload handler.
881
                        ServletFileUpload upload = new ServletFileUpload();
882
                        StatusProgressListener progressListener = new StatusProgressListener(getService());
883
                        upload.setProgressListener(progressListener);
884
                        iter = upload.getItemIterator(request);
885
                        String dateParam = null;
886
                        String auth = null;
887
                        while (iter.hasNext()) {
888
                                FileItemStream item = iter.next();
889
                                String name = item.getFieldName();
890
                                InputStream stream = item.openStream();
891
                                if (item.isFormField()) {
892
                                        final String value = Streams.asString(stream);
893
                                        if (name.equals(DATE_PARAMETER))
894
                                                dateParam = value;
895
                                        else if (name.equals(AUTHORIZATION_PARAMETER))
896
                                                auth = value;
897

    
898
                                        if (logger.isDebugEnabled())
899
                                                logger.debug(name + ":" + value);
900
                                } else {
901
                                        // Fetch the timestamp used to guard against replay attacks.
902
                                    if (dateParam == null) {
903
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
904
                                            return;
905
                                    }
906

    
907
                                    long timestamp;
908
                                        try {
909
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
910
                                        } catch (DateParseException e) {
911
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
912
                                            return;
913
                                        }
914
                                    if (!isTimeValid(timestamp)) {
915
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
916
                                            return;
917
                                    }
918

    
919
                                        // Fetch the Authorization parameter and find the user specified in it.
920
                                    if (auth == null) {
921
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
922
                                            return;
923
                                    }
924
                                        String[] authParts = auth.split(" ");
925
                                        if (authParts.length != 2) {
926
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
927
                                            return;
928
                                    }
929
                                        String username = authParts[0];
930
                                        String signature = authParts[1];
931
                                        User user = null;
932
                                        try {
933
                                                user = getService().findUser(username);
934
                                        } catch (RpcException e) {
935
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
936
                                                return;
937
                                        }
938
                                        if (user == null) {
939
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
940
                                            return;
941
                                    }
942
                                        request.setAttribute(USER_ATTRIBUTE, user);
943

    
944
                                        // Remove the servlet path from the request URI.
945
                                        String p = request.getRequestURI();
946
                                        String servletPath = request.getContextPath() + request.getServletPath();
947
                                        p = p.substring(servletPath.length());
948
                                        // Validate the signature in the Authorization parameter.
949
                                        String data = request.getMethod() + dateParam + p;
950
                                        if (!isSignatureValid(signature, user, data)) {
951
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
952
                                            return;
953
                                    }
954

    
955
                                        progressListener.setUserId(user.getId());
956
                                        progressListener.setFilename(fileName);
957
                                        final String contentType = item.getContentType();
958

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

    
1009
                } catch (InsufficientPermissionsException e) {
1010
                        if (uploadedFile != null && uploadedFile.exists())
1011
                                uploadedFile.delete();
1012
                        String error = "You don't have the necessary permissions";
1013
                        logger.error(error, e);
1014
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1015

    
1016
                } catch (QuotaExceededException e) {
1017
                        if (uploadedFile != null && uploadedFile.exists())
1018
                                uploadedFile.delete();
1019
                        String error = "Not enough free space available";
1020
                        if (logger.isDebugEnabled())
1021
                                logger.debug(error, e);
1022
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1023

    
1024
                } catch (ObjectNotFoundException e) {
1025
                        if (uploadedFile != null && uploadedFile.exists())
1026
                                uploadedFile.delete();
1027
                        String error = "A specified object was not found";
1028
                        logger.error(error, e);
1029
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1030
                } catch (RpcException e) {
1031
                        if (uploadedFile != null && uploadedFile.exists())
1032
                                uploadedFile.delete();
1033
                        String error = "An error occurred while communicating with the service";
1034
                        logger.error(error, e);
1035
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1036
                } catch (Exception e) {
1037
                        if (uploadedFile != null && uploadedFile.exists())
1038
                                uploadedFile.delete();
1039
                        String error = "An internal server error occurred";
1040
                        logger.error(error, e);
1041
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1042
                }
1043
        }
1044

    
1045
        /**
1046
         * Move the resource in the specified path to the specified destination.
1047
         *
1048
         * @param req the HTTP request
1049
         * @param resp the HTTP response
1050
         * @param path the path of the resource
1051
         * @param moveTo the destination of the move procedure
1052
         * @throws IOException if an input/output error occurs
1053
         */
1054
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1055
                final User user = getUser(req);
1056
                User owner = getOwner(req);
1057
                Object resource = null;
1058
                try {
1059
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1060
                } catch (ObjectNotFoundException e) {
1061
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1062
                        return;
1063
                } catch (RpcException e) {
1064
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1065
                        return;
1066
                }
1067

    
1068
        String destination = null;
1069
        User destOwner = null;
1070
                boolean exists = true;
1071
                try {
1072
                        destination = getDestinationPath(req, encodePath(moveTo));
1073
                        destination = URLDecoder.decode(destination, "UTF-8");
1074
                        destOwner = getDestinationOwner(req);
1075
                        getService().getResourceAtPath(destOwner.getId(), destination, true);
1076
                } catch (ObjectNotFoundException e) {
1077
                        exists = false;
1078
                } catch (URISyntaxException e) {
1079
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1080
                        return;
1081
                } catch (RpcException e) {
1082
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1083
                        return;
1084
                }
1085
                if (exists) {
1086
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1087
                        return;
1088
                }
1089

    
1090
                try {
1091
                        final User dOwner = destOwner;
1092
                        final String dest = destination;
1093
                        if (resource instanceof FolderDTO) {
1094
                                final FolderDTO folder = (FolderDTO) resource;
1095
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1096
                                        @Override
1097
                                        public Void call() throws Exception {
1098
                                                getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1099
                                                return null;
1100
                                        }
1101
                                });
1102
                        } else {
1103
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1104
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1105
                                        @Override
1106
                                        public Void call() throws Exception {
1107
                                                getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1108
                                                return null;
1109
                                        }
1110
                                });
1111

    
1112
                        }
1113
                } catch (InsufficientPermissionsException e) {
1114
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1115
                } catch (ObjectNotFoundException e) {
1116
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1117
                } catch (RpcException e) {
1118
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1119
                } catch (DuplicateNameException e) {
1120
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1121
                } catch (GSSIOException e) {
1122
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1123
                } catch (QuotaExceededException e) {
1124
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1125
                } catch (Exception e) {
1126
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1127
                }
1128
        }
1129

    
1130
        /**
1131
         * Copy the resource in the specified path to the specified destination.
1132
         *
1133
         * @param req the HTTP request
1134
         * @param resp the HTTP response
1135
         * @param path the path of the resource
1136
         * @param copyTo the destination of the copy procedure
1137
         * @throws IOException if an input/output error occurs
1138
         */
1139
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1140
                final User user = getUser(req);
1141
                User owner = getOwner(req);
1142
                Object resource = null;
1143
                try {
1144
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1145
                } catch (ObjectNotFoundException e) {
1146
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1147
                        return;
1148
                } catch (RpcException e) {
1149
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1150
                        return;
1151
                }
1152

    
1153
        String destination = null;
1154
        User destOwner = null;
1155
                boolean exists = true;
1156
                try {
1157
                        String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1158
                        destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1159
                        destOwner = getDestinationOwner(req);
1160
                        getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1161
                } catch (ObjectNotFoundException e) {
1162
                        exists = false;
1163
                } catch (URISyntaxException e) {
1164
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1165
                        return;
1166
                } catch (RpcException e) {
1167
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1168
                        return;
1169
                }
1170
                if (exists) {
1171
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1172
                        return;
1173
                }
1174

    
1175
                try {
1176
                        final User dOwner = destOwner;
1177
                        final String dest = destination;
1178
                        if (resource instanceof FolderDTO) {
1179
                                final FolderDTO folder = (FolderDTO) resource;
1180
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1181
                                        @Override
1182
                                        public Void call() throws Exception {
1183
                                                getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1184
                                                return null;
1185
                                        }
1186
                                });
1187
                        } else {
1188
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1189
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1190
                                        @Override
1191
                                        public Void call() throws Exception {
1192
                                                getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1193
                                                return null;
1194
                                        }
1195
                                });
1196
                        }
1197
                } catch (InsufficientPermissionsException e) {
1198
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1199
                } catch (ObjectNotFoundException e) {
1200
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1201
                } catch (RpcException e) {
1202
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1203
                } catch (DuplicateNameException e) {
1204
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1205
                } catch (GSSIOException e) {
1206
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1207
                } catch (QuotaExceededException e) {
1208
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1209
                } catch (Exception e) {
1210
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1211
                }
1212
        }
1213

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

    
1264
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1265
                dest = dest.substring(slash + 1);
1266

    
1267
                // Chop the resource namespace part
1268
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1269

    
1270
            dest = dest.endsWith("/")? dest: dest + '/';
1271
                return dest;
1272
        }
1273

    
1274
        /**
1275
         * Move the resource in the specified path to the trash bin.
1276
         *
1277
         * @param req the HTTP request
1278
         * @param resp the HTTP response
1279
         * @param path the path of the resource
1280
         * @throws IOException if an input/output error occurs
1281
         */
1282
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1283
                final User user = getUser(req);
1284
                User owner = getOwner(req);
1285
                Object resource = null;
1286
                try {
1287
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1288
                } catch (ObjectNotFoundException e) {
1289
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1290
                        return;
1291
                } catch (RpcException e) {
1292
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1293
                        return;
1294
                }
1295

    
1296
                try {
1297
                        if (resource instanceof FolderDTO) {
1298
                                final FolderDTO folder = (FolderDTO) resource;
1299
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1300
                                        @Override
1301
                                        public Void call() throws Exception {
1302
                                                getService().moveFolderToTrash(user.getId(), folder.getId());
1303
                                                return null;
1304
                                        }
1305
                                });
1306
                        } else {
1307
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1308
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1309
                                        @Override
1310
                                        public Void call() throws Exception {
1311
                                                getService().moveFileToTrash(user.getId(), file.getId());
1312
                                                return null;
1313
                                        }
1314
                                });
1315
                        }
1316
                } catch (InsufficientPermissionsException e) {
1317
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1318
                } catch (ObjectNotFoundException e) {
1319
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1320
                } catch (RpcException e) {
1321
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1322
                } catch (Exception e) {
1323
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1324
                }
1325
        }
1326

    
1327
        /**
1328
         * Restore the resource in the specified path from the trash bin.
1329
         *
1330
         * @param req the HTTP request
1331
         * @param resp the HTTP response
1332
         * @param path the path of the resource
1333
         * @throws IOException if an input/output error occurs
1334
         */
1335
        private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1336
                final User user = getUser(req);
1337
                User owner = getOwner(req);
1338
                Object resource = null;
1339
                try {
1340
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1341
                } catch (ObjectNotFoundException e) {
1342
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1343
                        return;
1344
                } catch (RpcException e) {
1345
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1346
                        return;
1347
                }
1348

    
1349
                try {
1350
                        if (resource instanceof FolderDTO) {
1351
                                final FolderDTO folder = (FolderDTO) resource;
1352
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1353
                                        @Override
1354
                                        public Void call() throws Exception {
1355
                                                getService().removeFolderFromTrash(user.getId(), folder.getId());
1356
                                                return null;
1357
                                        }
1358
                                });
1359
                        } else {
1360
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1361
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1362
                                        @Override
1363
                                        public Void call() throws Exception {
1364
                                                getService().removeFileFromTrash(user.getId(), file.getId());
1365
                                                return null;
1366
                                        }
1367
                                });
1368
                        }
1369
                } catch (InsufficientPermissionsException e) {
1370
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1371
                } catch (ObjectNotFoundException e) {
1372
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1373
                } catch (RpcException e) {
1374
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1375
                } catch (Exception e) {
1376
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1377
                }
1378
        }
1379

    
1380
        /**
1381
         * Update the resource in the specified path.
1382
         *
1383
         * @param req the HTTP request
1384
         * @param resp the HTTP response
1385
         * @param path the path of the resource
1386
         * @throws IOException if an input/output error occurs
1387
         */
1388
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1389
                final User user = getUser(req);
1390
                User owner = getOwner(req);
1391
                Object resource = null;
1392

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

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

    
1492
                                        });
1493
                                }
1494
                        }
1495
                } catch (JSONException e) {
1496
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1497
                } catch (InsufficientPermissionsException e) {
1498
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1499
                } catch (ObjectNotFoundException e) {
1500
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1501
                } catch (DuplicateNameException e) {
1502
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1503
                } catch (RpcException e) {
1504
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1505
                } catch (Exception e) {
1506
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1507
                        return;
1508
                }
1509
        }
1510

    
1511
        /**
1512
         * Returns the new URL of an updated folder.
1513
         */
1514
        private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1515
                String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1516
                String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1517
                if (parentUrl.indexOf(fpath) != -1)
1518
                        parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1519
                if(!parentUrl.endsWith("/"))
1520
                        parentUrl = parentUrl+"/";
1521
                parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1522
                return parentUrl;
1523
        }
1524

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

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

    
1592
            final User user = getUser(req);
1593
            User owner = getOwner(req);
1594
        boolean exists = true;
1595
        try {
1596
                getService().getResourceAtPath(owner.getId(), path + folderName, false);
1597
        } catch (ObjectNotFoundException e) {
1598
            exists = false;
1599
        } catch (RpcException e) {
1600
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1601
                        return;
1602
                }
1603

    
1604
        if (exists) {
1605
            resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1606
                                    ", " + METHOD_HEAD);
1607
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1608
            return;
1609
        }
1610

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

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

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

    
1676
            final User user = getUser(req);
1677
            User owner = getOwner(req);
1678
            boolean exists = true;
1679
        Object resource = null;
1680
        FileHeaderDTO file = null;
1681
        try {
1682
                resource = getService().getResourceAtPath(owner.getId(), path, false);
1683
        } catch (ObjectNotFoundException e) {
1684
            exists = false;
1685
        } catch (RpcException e) {
1686
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1687
                        return;
1688
                }
1689

    
1690
        if (exists)
1691
                        if (resource instanceof FileHeaderDTO)
1692
                            file = (FileHeaderDTO) resource;
1693
                        else {
1694
                        resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1695
                            return;
1696
                }
1697
        boolean result = true;
1698

    
1699
        // Temporary content file used to support partial PUT.
1700
        File contentFile = null;
1701

    
1702
        Range range = parseContentRange(req, resp);
1703

    
1704
        InputStream resourceInputStream = null;
1705

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

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

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

    
1789
        if (result) {
1790
            if (exists)
1791
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1792
                        else
1793
                                resp.setStatus(HttpServletResponse.SC_CREATED);
1794
        } else
1795
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1796
        }
1797

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

    
1823
            if (!exists) {
1824
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1825
                    return;
1826
            }
1827

    
1828
            FolderDTO folder = null;
1829
            FileHeaderDTO file = null;
1830
            if (object instanceof FolderDTO)
1831
                    folder = (FolderDTO) object;
1832
            else
1833
                    file = (FileHeaderDTO) object;
1834

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

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

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

    
1956
            // Prepare a writer to a buffered area
1957
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
1958
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1959
            PrintWriter writer = new PrintWriter(osWriter);
1960

    
1961
            // Return an input stream to the underlying bytes
1962
            writer.write(json.toString());
1963
            writer.flush();
1964
            return new ByteArrayInputStream(stream.toByteArray());
1965
    }
1966

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

    
1986
            JSONObject json = new JSONObject();
1987
            try {
1988
                        json.put("name", folder.getName()).
1989
                        put("owner", folder.getOwner().getUsername()).
1990
                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1991
                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1992
                        put("deleted", folder.isDeleted());
1993
                        if (folder.getAuditInfo().getModifiedBy() != null)
1994
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1995
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1996
                } catch (JSONException e) {
1997
                        throw new ServletException(e);
1998
                }
1999
            return json.toString();
2000
    }
2001

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

    
2059
            return json.toString();
2060
    }
2061

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

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

    
2104
        /**
2105
         * Retrieves the user who owns the destination namespace, for a
2106
         * copy or move request.
2107
         *
2108
         * @param req the HTTP request
2109
         * @return the owner of the namespace
2110
         */
2111
        protected User getDestinationOwner(HttpServletRequest req) {
2112
                return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2113
        }
2114

    
2115
        /**
2116
         * A helper inner class for updating the progress status of a file upload.
2117
         *
2118
         * @author kman
2119
         */
2120
        public static class StatusProgressListener implements ProgressListener {
2121
                private int percentLogged = 0;
2122
                private long bytesTransferred = 0;
2123

    
2124
                private long fileSize = -100;
2125

    
2126
                private Long userId;
2127

    
2128
                private String filename;
2129

    
2130
                private ExternalAPI service;
2131

    
2132
                public StatusProgressListener(ExternalAPI aService) {
2133
                        service = aService;
2134
                }
2135

    
2136
                /**
2137
                 * Modify the userId.
2138
                 *
2139
                 * @param aUserId the userId to set
2140
                 */
2141
                public void setUserId(Long aUserId) {
2142
                        userId = aUserId;
2143
                }
2144

    
2145
                /**
2146
                 * Modify the filename.
2147
                 *
2148
                 * @param aFilename the filename to set
2149
                 */
2150
                public void setFilename(String aFilename) {
2151
                        filename = aFilename;
2152
                }
2153

    
2154
                @Override
2155
                public void update(long bytesRead, long contentLength, int items) {
2156
                        //monitoring per percent of bytes uploaded
2157
                        bytesTransferred = bytesRead;
2158
                        if (fileSize != contentLength)
2159
                                fileSize = contentLength;
2160
                        int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2161

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

    
2210
                // Render the link to our parent (if required)
2211
                String parentDirectory = path;
2212
                if (parentDirectory.endsWith("/"))
2213
                        parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2214
                int slash = parentDirectory.lastIndexOf('/');
2215
                if (slash >= 0) {
2216
                        String parent = path.substring(0, slash);
2217
                        sb.append(" - <a href=\"");
2218
                        sb.append(rewrittenContextPath);
2219
                        if (parent.equals(""))
2220
                                parent = "/";
2221
                        sb.append(rewriteUrl(parent));
2222
                        if (!parent.endsWith("/"))
2223
                                sb.append("/");
2224
                        sb.append("\">");
2225
                        sb.append("<b>");
2226
                        sb.append("Up To " + parent);
2227
                        sb.append("</b>");
2228
                        sb.append("</a>");
2229
                }
2230

    
2231
                sb.append("</h1>");
2232
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2233

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

    
2236
                // Render the column headings
2237
                sb.append("<tr>\r\n");
2238
                sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2239
                sb.append("Name");
2240
                sb.append("</strong></font></td>\r\n");
2241
                sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2242
                sb.append("Size");
2243
                sb.append("</strong></font></td>\r\n");
2244
                sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2245
                sb.append("Last modified");
2246
                sb.append("</strong></font></td>\r\n");
2247
                sb.append("</tr>");
2248
                // Render the directory entries within this directory
2249
                boolean shade = false;
2250
                Iterator iter = folder.getSubfolders().iterator();
2251
                while (iter.hasNext()) {
2252
                        FolderDTO subf = (FolderDTO) iter.next();
2253
                        String resourceName = subf.getName();
2254
                        if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2255
                                continue;
2256

    
2257
                        sb.append("<tr");
2258
                        if (shade)
2259
                                sb.append(" bgcolor=\"#eeeeee\"");
2260
                        sb.append(">\r\n");
2261
                        shade = !shade;
2262

    
2263
                        sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2264
                        sb.append("<a href=\"");
2265
                        sb.append(rewrittenContextPath);
2266
                        sb.append(rewriteUrl(path + resourceName));
2267
                        sb.append("/");
2268
                        sb.append("\"><tt>");
2269
                        sb.append(RequestUtil.filter(resourceName));
2270
                        sb.append("/");
2271
                        sb.append("</tt></a></td>\r\n");
2272

    
2273
                        sb.append("<td align=\"right\"><tt>");
2274
                        sb.append("&nbsp;");
2275
                        sb.append("</tt></td>\r\n");
2276

    
2277
                        sb.append("<td align=\"right\"><tt>");
2278
                        sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2279
                        sb.append("</tt></td>\r\n");
2280

    
2281
                        sb.append("</tr>\r\n");
2282
                }
2283
                List<FileHeaderDTO> files;
2284
                try {
2285
                        files = getService().getFiles(user.getId(), folder.getId(), true);
2286
                } catch (ObjectNotFoundException e) {
2287
                        throw new ServletException(e.getMessage());
2288
                } catch (InsufficientPermissionsException e) {
2289
                        throw new ServletException(e.getMessage());
2290
                } catch (RpcException e) {
2291
                        throw new ServletException(e.getMessage());
2292
                }
2293
                for (FileHeaderDTO file : files) {
2294
                        String resourceName = file.getName();
2295
                        if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2296
                                continue;
2297

    
2298
                        sb.append("<tr");
2299
                        if (shade)
2300
                                sb.append(" bgcolor=\"#eeeeee\"");
2301
                        sb.append(">\r\n");
2302
                        shade = !shade;
2303

    
2304
                        sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2305
                        sb.append("<a href=\"");
2306
                        sb.append(rewrittenContextPath);
2307
                        sb.append(rewriteUrl(path + resourceName));
2308
                        sb.append("\"><tt>");
2309
                        sb.append(RequestUtil.filter(resourceName));
2310
                        sb.append("</tt></a></td>\r\n");
2311

    
2312
                        sb.append("<td align=\"right\"><tt>");
2313
                        sb.append(renderSize(file.getFileSize()));
2314
                        sb.append("</tt></td>\r\n");
2315

    
2316
                        sb.append("<td align=\"right\"><tt>");
2317
                        sb.append(getLastModifiedHttp(file.getAuditInfo()));
2318
                        sb.append("</tt></td>\r\n");
2319

    
2320
                        sb.append("</tr>\r\n");
2321
                }
2322

    
2323
                // Render the page footer
2324
                sb.append("</table>\r\n");
2325

    
2326
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2327

    
2328
                //sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2329
                sb.append("</body>\r\n");
2330
                sb.append("</html>\r\n");
2331

    
2332
                // Return an input stream to the underlying bytes
2333
                writer.write(sb.toString());
2334
                writer.flush();
2335
                return new ByteArrayInputStream(stream.toByteArray());
2336

    
2337
        }
2338
}