Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (84.2 kB)

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

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

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

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

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

    
90

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

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

    
108
        private static final int TRACK_PROGRESS_PERCENT = 5;
109

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

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

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

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

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

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

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

    
145

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
432

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

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

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

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

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

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

    
520

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

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

    
569
                            if (contentType != null) {
570
                                    if (logger.isDebugEnabled())
571
                                            logger.debug("contentType='" + contentType + "'");
572
                                    resp.setContentType(contentType);
573
                            }
574

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

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

    
646
        /**
647
         * Return the filename of the specified file properly formatted for
648
         * including in the Content-Disposition header.
649
         */
650
        private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
651
                return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
652
        }
653

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

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

    
696
                        // Workaround for IE's broken caching behavior.
697
                    resp.setHeader("Expires", "-1");
698
                        return;
699
                } catch (RpcException e) {
700
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
701
                        return;
702
                } catch (JSONException e) {
703
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
704
                        return;
705
                }
706
        }
707

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

    
739
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
740
            if (!isValidResourceName(newName)) {
741
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
742
                    return;
743
            }
744
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
745
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
746
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
747
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
748
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
749
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
750

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

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

    
797
                try {
798
                        final FileHeaderDTO file = (FileHeaderDTO) resource;
799
                        final int oldVersion = Integer.parseInt(version);
800

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

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

    
839
            User owner = getOwner(request);
840
            boolean exists = true;
841
        Object resource = null;
842
        FileHeaderDTO file = null;
843
        try {
844
                resource = getService().getResourceAtPath(owner.getId(), path, false);
845
        } catch (ObjectNotFoundException e) {
846
            exists = false;
847
        } catch (RpcException e) {
848
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
849
                        return;
850
                }
851

    
852
        if (exists)
853
                        if (resource instanceof FileHeaderDTO) {
854
                            file = (FileHeaderDTO) resource;
855
                            if (file.isDeleted()) {
856
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
857
                                return;
858
                            }
859
                        } else {
860
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
861
                            return;
862
                }
863

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

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

    
904
                                        if (logger.isDebugEnabled())
905
                                                logger.debug(name + ":" + value);
906
                                } else {
907
                                        // Fetch the timestamp used to guard against replay attacks.
908
                                    if (dateParam == null) {
909
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
910
                                            return;
911
                                    }
912

    
913
                                    long timestamp;
914
                                        try {
915
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
916
                                        } catch (DateParseException e) {
917
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
918
                                            return;
919
                                        }
920
                                    if (!isTimeValid(timestamp)) {
921
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
922
                                            return;
923
                                    }
924

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

    
950
                                        // Remove the servlet path from the request URI.
951
                                        String p = request.getRequestURI();
952
                                        String servletPath = request.getContextPath() + request.getServletPath();
953
                                        p = p.substring(servletPath.length());
954
                                        // Validate the signature in the Authorization parameter.
955
                                        String data = request.getMethod() + dateParam + p;
956
                                        if (!isSignatureValid(signature, user, data)) {
957
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
958
                                            return;
959
                                    }
960

    
961
                                        progressListener.setUserId(user.getId());
962
                                        progressListener.setFilename(fileName);
963
                                        final String contentType = item.getContentType();
964

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

    
1015
                } catch (InsufficientPermissionsException e) {
1016
                        if (uploadedFile != null && uploadedFile.exists())
1017
                                uploadedFile.delete();
1018
                        String error = "You don't have the necessary permissions";
1019
                        logger.error(error, e);
1020
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1021

    
1022
                } catch (QuotaExceededException e) {
1023
                        if (uploadedFile != null && uploadedFile.exists())
1024
                                uploadedFile.delete();
1025
                        String error = "Not enough free space available";
1026
                        if (logger.isDebugEnabled())
1027
                                logger.debug(error, e);
1028
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1029

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

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

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

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

    
1118
                        }
1119
                } catch (InsufficientPermissionsException e) {
1120
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1121
                } catch (ObjectNotFoundException e) {
1122
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1123
                } catch (RpcException e) {
1124
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1125
                } catch (DuplicateNameException e) {
1126
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1127
                } catch (GSSIOException e) {
1128
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1129
                } catch (QuotaExceededException e) {
1130
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1131
                } catch (Exception e) {
1132
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1133
                }
1134
        }
1135

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

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

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

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

    
1270
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1271
                dest = dest.substring(slash + 1);
1272

    
1273
                // Chop the resource namespace part
1274
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1275

    
1276
            dest = dest.endsWith("/")? dest: dest + '/';
1277
                return dest;
1278
        }
1279

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1703
        InputStream resourceInputStream = null;
1704

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2058
            return json.toString();
2059
    }
2060

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

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

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

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

    
2123
                private long fileSize = -100;
2124

    
2125
                private Long userId;
2126

    
2127
                private String filename;
2128

    
2129
                private ExternalAPI service;
2130

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

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

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

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

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

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

    
2254
                sb.append("</h1>");
2255
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2256

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

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

    
2281
                                sb.append("<tr");
2282
                                if (shade)
2283
                                        sb.append(" bgcolor=\"#eeeeee\"");
2284
                                sb.append(">\r\n");
2285
                                shade = !shade;
2286
                                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2287
                                sb.append("<a href=\"");
2288
                                sb.append(rewrittenContextPath);
2289
                                sb.append(relativePathNoFolderName);
2290
                                if (!(folderPath.length() == 1) && !folderPath.equals("/"))
2291
                                        sb.append(rewriteUrl(folderPath + resourceName));
2292
                                else
2293
                                        sb.append(resourceName);
2294
                                sb.append("/");
2295
                                sb.append("\"><tt>");
2296
                                sb.append(RequestUtil.filter(resourceName));
2297
                                sb.append("/");
2298
                                sb.append("</tt></a></td>\r\n");
2299

    
2300
                                sb.append("<td align=\"right\"><tt>");
2301
                                sb.append("&nbsp;");
2302
                                sb.append("</tt></td>\r\n");
2303
                                sb.append("<td align=\"right\"><tt>");
2304
                                sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2305
                                sb.append("</tt></td>\r\n");
2306

    
2307
                                sb.append("</tr>\r\n");
2308
                                }
2309
                }
2310
                List<FileHeaderDTO> files;
2311
                try {
2312
                        files = getService().getFiles(user.getId(), folder.getId(), true);
2313
                } catch (ObjectNotFoundException e) {
2314
                        throw new ServletException(e.getMessage());
2315
                } catch (InsufficientPermissionsException e) {
2316
                        throw new ServletException(e.getMessage());
2317
                } catch (RpcException e) {
2318
                        throw new ServletException(e.getMessage());
2319
                }
2320
                for (FileHeaderDTO file : files)
2321
                        //Display only file resources that are marked as public
2322
                        if(file.isReadForAll()){
2323
                                String resourceName = file.getName();
2324
                                if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2325
                                        continue;
2326
                                String rewrittenResource = rewriteUrl(resourceName);
2327
                                sb.append("<tr");
2328
                                if (shade)
2329
                                        sb.append(" bgcolor=\"#eeeeee\"");
2330
                                sb.append(">\r\n");
2331
                                shade = !shade;
2332

    
2333
                                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2334
                                sb.append("<a href=\"");
2335
                                sb.append(rewrittenContextPath);
2336
                                sb.append(relativePath);
2337
                                if(!relativePath.endsWith("/"))
2338
                                        sb.append("/");
2339
                                sb.append(rewrittenResource);
2340
                                sb.append("\"><tt>");
2341
                                sb.append(RequestUtil.filter(resourceName));
2342
                                sb.append("</tt></a></td>\r\n");
2343

    
2344
                                sb.append("<td align=\"right\"><tt>");
2345
                                sb.append(renderSize(file.getFileSize()));
2346
                                sb.append("</tt></td>\r\n");
2347

    
2348
                                sb.append("<td align=\"right\"><tt>");
2349
                                sb.append(getLastModifiedHttp(file.getAuditInfo()));
2350
                                sb.append("</tt></td>\r\n");
2351

    
2352
                                sb.append("</tr>\r\n");
2353
                        }
2354

    
2355
                // Render the page footer
2356
                sb.append("</table>\r\n");
2357

    
2358
                sb.append("<HR size=\"1\" noshade=\"noshade\">");
2359

    
2360
                //sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2361
                sb.append("</body>\r\n");
2362
                sb.append("</html>\r\n");
2363

    
2364
                // Return an input stream to the underlying bytes
2365
                writer.write(sb.toString());
2366
                writer.flush();
2367
                return new ByteArrayInputStream(stream.toByteArray());
2368

    
2369
        }
2370
}