Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (75.5 kB)

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

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

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

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

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

    
88

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

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

    
106
        private static final int TRACK_PROGRESS_PERCENT = 5;
107

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

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

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

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

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

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

    
138
        /**
139
         * @param servletContext
140
         */
141
        public FilesHandler(ServletContext servletContext) {
142
                context = servletContext;
143
        }
144

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

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

    
190
            if (logger.isDebugEnabled())
191
                        if (content)
192
                            logger.debug("Serving resource '" +        path + "' headers and data");
193
                    else
194
                            logger.debug("Serving resource '" +        path + "' headers only");
195

    
196
            User user = getUser(req);
197
            User owner = getOwner(req);
198
            if (user == null) user = owner;
199
        boolean exists = true;
200
        Object resource = null;
201
        FileHeaderDTO file = null;
202
        FolderDTO folder = null;
203
        try {
204
                resource = getService().getResourceAtPath(owner.getId(), path, false);
205
        } catch (ObjectNotFoundException e) {
206
            exists = false;
207
        } catch (RpcException e) {
208
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
209
                        return;
210
                }
211

    
212
            if (!exists) {
213
                        if (authDeferred) {
214
                                // We do not want to leak information if the request
215
                                // was not authenticated.
216
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
217
                                return;
218
                        }
219
                    // A request for upload progress.
220
                    if (progress != null && content) {
221
                            serveProgress(req, resp, progress, user, null);
222
                                return;
223
                    }
224

    
225
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
226
                    return;
227
            }
228

    
229
            if (resource instanceof FolderDTO)
230
                    folder = (FolderDTO) resource;
231
            else
232
                    file = (FileHeaderDTO) resource;
233

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

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

    
321
                                        // Remove the servlet path from the request URI.
322
                                        String p = req.getRequestURI();
323
                                        String servletPath = req.getContextPath() + req.getServletPath();
324
                                        p = p.substring(servletPath.length());
325
                                        // Validate the signature in the Authorization parameter.
326
                                        String data = req.getMethod() + dateParam + p;
327
                                        if (!isSignatureValid(signature, user, data)) {
328
                                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
329
                                            return;
330
                                    }
331
                                }
332
                        } else if (file != null && !file.isReadForAll() || file == null) {
333
                                // Check for a read-for-all file request.
334
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
335
                                return;
336
                        }
337

    
338
            // If the resource is not a collection, and the resource path
339
            // ends with "/" or "\", return NOT FOUND.
340
            if (folder == null)
341
                        if (path.endsWith("/") || path.endsWith("\\")) {
342
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
343
                            return;
344
                    }
345

    
346
            // Workaround for IE's broken caching behavior.
347
            if (folder != null)
348
                    resp.setHeader("Expires", "-1");
349

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

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

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

    
397
            // Find content type.
398
            String contentType = null;
399
            if (file != null) {
400
                contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
401
                if (contentType == null) {
402
                        contentType = context.getMimeType(file.getName());
403
                        file.setMimeType(contentType);
404
                }
405
            } else
406
                        contentType = "application/json;charset=UTF-8";
407

    
408
            ArrayList ranges = null;
409
            long contentLength = -1L;
410

    
411
            if (file != null) {
412
                    // Parse range specifier.
413
                    ranges = parseRange(req, resp, file, oldBody);
414
                    // ETag header
415
                    resp.setHeader("ETag", getETag(file, oldBody));
416
                    // Last-Modified header.
417
                    String lastModified = oldBody == null ?
418
                                            getLastModifiedHttp(file.getAuditInfo()) :
419
                                            getLastModifiedHttp(oldBody.getAuditInfo());
420
                    resp.setHeader("Last-Modified", lastModified);
421
                    // X-GSS-Metadata header.
422
                    try {
423
                                resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
424
                        } catch (InsufficientPermissionsException e) {
425
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
426
                        return;
427
                }
428
                    // Get content length.
429
                    contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
430
                    // Special case for zero length files, which would cause a
431
                    // (silent) ISE when setting the output buffer size.
432
                    if (contentLength == 0L)
433
                                content = false;
434
            } else
435
                    // Set the folder X-GSS-Metadata header.
436
                    try {
437
                                resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
438
                        } catch (InsufficientPermissionsException e) {
439
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
440
                        return;
441
                }
442

    
443
            ServletOutputStream ostream = null;
444
            PrintWriter writer = null;
445

    
446
            if (content)
447
                        try {
448
                            ostream = resp.getOutputStream();
449
                    } catch (IllegalStateException e) {
450
                            // If it fails, we try to get a Writer instead if we're
451
                            // trying to serve a text file
452
                            if ( contentType == null
453
                                                    || contentType.startsWith("text")
454
                                                    || contentType.endsWith("xml") )
455
                                        writer = resp.getWriter();
456
                                else
457
                                        throw e;
458
                    }
459

    
460
            if (folder != null
461
                                    || (ranges == null || ranges.isEmpty())
462
                                                            && req.getHeader("Range") == null
463
                                                            || ranges == FULL) {
464
                    // Set the appropriate output headers
465
                    if (contentType != null) {
466
                            if (logger.isDebugEnabled())
467
                                    logger.debug("contentType='" + contentType + "'");
468
                            resp.setContentType(contentType);
469
                    }
470
                    if (file != null && contentLength >= 0) {
471
                            if (logger.isDebugEnabled())
472
                                    logger.debug("contentLength=" + contentLength);
473
                            if (contentLength < Integer.MAX_VALUE)
474
                                        resp.setContentLength((int) contentLength);
475
                                else
476
                                        // Set the content-length as String to be able to use a long
477
                                    resp.setHeader("content-length", "" + contentLength);
478
                    }
479

    
480
                    InputStream renderResult = null;
481
                    if (folder != null)
482
                                if (content)
483
                                        // Serve the directory browser
484
                                    try {
485
                                                renderResult = renderJson(user, folder);
486
                                        } catch (InsufficientPermissionsException e) {
487
                                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
488
                                        return;
489
                                        }
490
                    // Copy the input stream to our output stream (if requested)
491
                    if (content) {
492
                            try {
493
                                    resp.setBufferSize(output);
494
                            } catch (IllegalStateException e) {
495
                                    // Silent catch
496
                            }
497
                            try {
498
                                    if(file != null)
499
                                                if (needsContentDisposition(req))
500
                                                    resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
501
                                            else
502
                                                    resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
503
                                    if (ostream != null)
504
                                                copy(file, renderResult, ostream, req, oldBody);
505
                                        else
506
                                                copy(file, renderResult, writer, req, oldBody);
507
                                    if (file!=null) updateAccounting(owner, new Date(), contentLength);
508
                        } catch (ObjectNotFoundException e) {
509
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
510
                                return;
511
                        } catch (InsufficientPermissionsException e) {
512
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
513
                                return;
514
                        } catch (RpcException e) {
515
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
516
                                return;
517
                            }
518
                    }
519
            } else {
520
                    if (ranges == null || ranges.isEmpty())
521
                            return;
522
                    // Partial content response.
523
                    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
524

    
525
                    if (ranges.size() == 1) {
526
                            Range range = (Range) ranges.get(0);
527
                            resp.addHeader("Content-Range", "bytes "
528
                                                    + range.start
529
                                                    + "-" + range.end + "/"
530
                                                    + range.length);
531
                            long length = range.end - range.start + 1;
532
                            if (length < Integer.MAX_VALUE)
533
                                        resp.setContentLength((int) length);
534
                                else
535
                                        // Set the content-length as String to be able to use a long
536
                                    resp.setHeader("content-length", "" + length);
537

    
538
                            if (contentType != null) {
539
                                    if (logger.isDebugEnabled())
540
                                            logger.debug("contentType='" + contentType + "'");
541
                                    resp.setContentType(contentType);
542
                            }
543

    
544
                            if (content) {
545
                                    try {
546
                                            resp.setBufferSize(output);
547
                                    } catch (IllegalStateException e) {
548
                                            // Silent catch
549
                                    }
550
                                    try {
551
                                            if (ostream != null)
552
                                                        copy(file, ostream, range, req, oldBody);
553
                                                else
554
                                                        copy(file, writer, range, req, oldBody);
555
                                            updateAccounting(owner, new Date(), contentLength);
556
                                } catch (ObjectNotFoundException e) {
557
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
558
                                        return;
559
                                } catch (InsufficientPermissionsException e) {
560
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
561
                                        return;
562
                                } catch (RpcException e) {
563
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
564
                                        return;
565
                                }
566
                            }
567
                    } else {
568
                            resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
569
                            if (content) {
570
                                    try {
571
                                            resp.setBufferSize(output);
572
                                    } catch (IllegalStateException e) {
573
                                            // Silent catch
574
                                    }
575
                                    try {
576
                                            if (ostream != null)
577
                                                        copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
578
                                                else
579
                                                        copy(file, writer, ranges.iterator(), contentType, req, oldBody);
580
                                            updateAccounting(owner, new Date(), contentLength);
581
                                } catch (ObjectNotFoundException e) {
582
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
583
                                        return;
584
                                } catch (InsufficientPermissionsException e) {
585
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
586
                                        return;
587
                                } catch (RpcException e) {
588
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
589
                                        return;
590
                                }
591
                            }
592
                    }
593
            }
594
    }
595

    
596
        /**
597
         * Handles an authentication failure. If no Authorization or Date request
598
         * parameters and no Authorization, Date or X-GSS-Date headers were present,
599
         * this is a browser request, so redirect to login and then let the user get
600
         * back to the file. Otherwise it's a bogus client request and Forbidden is
601
         * returned.
602
         */
603
        private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
604
                if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
605
                                req.getParameter(DATE_PARAMETER) == null &&
606
                                req.getHeader(AUTHORIZATION_HEADER) == null &&
607
                                req.getDateHeader(DATE_HEADER) == -1 &&
608
                                req.getDateHeader(GSS_DATE_HEADER) == -1)
609
                        resp.sendRedirect(getConfiguration().getString("loginUrl") +
610
                                        "?next=" + req.getRequestURL().toString());
611
                else
612
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
613
        }
614

    
615
        /**
616
         * Return the filename of the specified file properly formatted for
617
         * including in the Content-Disposition header.
618
         */
619
        private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
620
                return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
621
        }
622

    
623
        /**
624
         * Determines whether the user agent needs the Content-Disposition
625
         * header to be set, in order to properly download a file.
626
         *
627
         * @param req the HTTP request
628
         * @return true if the Content-Disposition HTTP header must be set
629
         */
630
        private boolean needsContentDisposition(HttpServletRequest req) {
631
                /*String agent = req.getHeader("user-agent");
632
                if (agent != null && agent.contains("MSIE"))
633
                        return true;*/
634
                String dl = req.getParameter("dl");
635
                if ("1".equals(dl))
636
                        return true;
637
                return false;
638
        }
639

    
640
        /**
641
         * Sends a progress update on the amount of bytes received until now for
642
         * a file that the current user is currently uploading.
643
         *
644
         * @param req the HTTP request
645
         * @param resp the HTTP response
646
         * @param parameter the value for the progress request parameter
647
         * @param user the current user
648
         * @param file the file being uploaded, or null if the request is about a new file
649
         * @throws IOException if an I/O error occurs
650
         */
651
        private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
652
                                String parameter, User user, FileHeaderDTO file)        throws IOException {
653
                String filename = file == null ? parameter : file.getName();
654
                try {
655
                        FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
656
                        if (status == null) {
657
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
658
                                return;
659
                        }
660
                        JSONObject json = new JSONObject();
661
                        json.put("bytesUploaded", status.getBytesUploaded()).
662
                                put("bytesTotal", status.getFileSize());
663
                        sendJson(req, resp, json.toString());
664

    
665
                        // Workaround for IE's broken caching behavior.
666
                    resp.setHeader("Expires", "-1");
667
                        return;
668
                } catch (RpcException e) {
669
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
670
                        return;
671
                } catch (JSONException e) {
672
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
673
                        return;
674
                }
675
        }
676

    
677
        /**
678
         * Server a POST request to create/modify a file or folder.
679
         *
680
         * @param req the HTTP request
681
         * @param resp the HTTP response
682
     * @exception IOException if an input/output error occurs
683
         */
684
        void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
685
                boolean authDeferred = getAuthDeferred(req);
686
            if (!authDeferred && req.getParameterMap().size() > 1) {
687
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
688
                    return;
689
            }
690
        String path = getInnerPath(req, PATH_FILES);
691
            path = path.endsWith("/")? path: path + '/';
692
                try {
693
                    path = URLDecoder.decode(path, "UTF-8");
694
                } catch (IllegalArgumentException e) {
695
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
696
                        return;
697
                }
698
            // We only defer authenticating multipart POST requests.
699
            if (authDeferred) {
700
                        if (!ServletFileUpload.isMultipartContent(req)) {
701
                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
702
                            return;
703
                    }
704
                        handleMultipart(req, resp, path);
705
                        return;
706
                }
707

    
708
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
709
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
710
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
711
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
712
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
713
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
714
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
715

    
716
            if (newName != null)
717
                        createFolder(req, resp, path, newName);
718
            else if (hasUpdateParam)
719
                        updateResource(req, resp, path);
720
                else if (hasTrashParam)
721
                        trashResource(req, resp, path);
722
                else if (hasRestoreParam)
723
                        restoreResource(req, resp, path);
724
                else if (copyTo != null)
725
                        copyResource(req, resp, path, copyTo);
726
                else if (moveTo != null)
727
                        moveResource(req, resp, path, moveTo);
728
                else if (restoreVersion != null)
729
                        restoreVersion(req, resp, path, restoreVersion);
730
                else
731
                        // IE with Gears uses POST for multiple uploads.
732
                        putResource(req, resp);
733
        }
734

    
735
        /**
736
         * Restores a previous version for a file.
737
         *
738
         * @param req the HTTP request
739
         * @param resp the HTTP response
740
         * @param path the resource path
741
         * @param version the version number to restore
742
         * @throws IOException if an I/O error occurs
743
         */
744
        private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
745
                final User user = getUser(req);
746
                User owner = getOwner(req);
747
                Object resource = null;
748
                try {
749
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
750
                } catch (ObjectNotFoundException e) {
751
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
752
                        return;
753
                } catch (RpcException e) {
754
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
755
                        return;
756
                }
757
                if (resource instanceof FolderDTO) {
758
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
759
                        return;
760
                }
761

    
762
                try {
763
                        final FileHeaderDTO file = (FileHeaderDTO) resource;
764
                        final int oldVersion = Integer.parseInt(version);
765

    
766
                        new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
767
                                @Override
768
                                public Void call() throws Exception {
769
                                        getService().restoreVersion(user.getId(), file.getId(), oldVersion);
770
                                        return null;
771
                                }
772
                        });
773
                } catch (InsufficientPermissionsException e) {
774
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
775
                } catch (ObjectNotFoundException e) {
776
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
777
                } catch (RpcException e) {
778
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
779
                } catch (GSSIOException e) {
780
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
781
                } catch (QuotaExceededException e) {
782
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
783
                } catch (NumberFormatException e) {
784
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
785
                } catch (Exception e) {
786
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
787
                }
788
        }
789

    
790
        /**
791
         * A method for handling multipart POST requests for uploading
792
         * files from browser-based JavaScript clients.
793
         *
794
         * @param request the HTTP request
795
         * @param response the HTTP response
796
         * @param path the resource path
797
         * @throws IOException in case an error occurs writing to the
798
         *                 response stream
799
         */
800
        private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
801
            if (logger.isDebugEnabled())
802
                           logger.debug("Multipart POST for resource: " + path);
803

    
804
            User owner = getOwner(request);
805
            boolean exists = true;
806
        Object resource = null;
807
        FileHeaderDTO file = null;
808
        try {
809
                resource = getService().getResourceAtPath(owner.getId(), path, false);
810
        } catch (ObjectNotFoundException e) {
811
            exists = false;
812
        } catch (RpcException e) {
813
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
814
                        return;
815
                }
816

    
817
        if (exists)
818
                        if (resource instanceof FileHeaderDTO) {
819
                            file = (FileHeaderDTO) resource;
820
                            if (file.isDeleted()) {
821
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
822
                                return;
823
                            }
824
                        } else {
825
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
826
                            return;
827
                }
828

    
829
            Object parent;
830
            String parentPath = null;
831
                try {
832
                        parentPath = getParentPath(path);
833
                        parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
834
                } catch (ObjectNotFoundException e) {
835
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
836
                    return;
837
                } catch (RpcException e) {
838
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
839
                        return;
840
                }
841
            if (!(parent instanceof FolderDTO)) {
842
                    response.sendError(HttpServletResponse.SC_CONFLICT);
843
                    return;
844
            }
845
            final FolderDTO folder = (FolderDTO) parent;
846
            final String fileName = getLastElement(path);
847

    
848
                FileItemIterator iter;
849
                File uploadedFile = null;
850
                try {
851
                        // Create a new file upload handler.
852
                        ServletFileUpload upload = new ServletFileUpload();
853
                        StatusProgressListener progressListener = new StatusProgressListener(getService());
854
                        upload.setProgressListener(progressListener);
855
                        iter = upload.getItemIterator(request);
856
                        String dateParam = null;
857
                        String auth = null;
858
                        while (iter.hasNext()) {
859
                                FileItemStream item = iter.next();
860
                                String name = item.getFieldName();
861
                                InputStream stream = item.openStream();
862
                                if (item.isFormField()) {
863
                                        final String value = Streams.asString(stream);
864
                                        if (name.equals(DATE_PARAMETER))
865
                                                dateParam = value;
866
                                        else if (name.equals(AUTHORIZATION_PARAMETER))
867
                                                auth = value;
868

    
869
                                        if (logger.isDebugEnabled())
870
                                                logger.debug(name + ":" + value);
871
                                } else {
872
                                        // Fetch the timestamp used to guard against replay attacks.
873
                                    if (dateParam == null) {
874
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
875
                                            return;
876
                                    }
877

    
878
                                    long timestamp;
879
                                        try {
880
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
881
                                        } catch (DateParseException e) {
882
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
883
                                            return;
884
                                        }
885
                                    if (!isTimeValid(timestamp)) {
886
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
887
                                            return;
888
                                    }
889

    
890
                                        // Fetch the Authorization parameter and find the user specified in it.
891
                                    if (auth == null) {
892
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
893
                                            return;
894
                                    }
895
                                        String[] authParts = auth.split(" ");
896
                                        if (authParts.length != 2) {
897
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
898
                                            return;
899
                                    }
900
                                        String username = authParts[0];
901
                                        String signature = authParts[1];
902
                                        User user = null;
903
                                        try {
904
                                                user = getService().findUser(username);
905
                                        } catch (RpcException e) {
906
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
907
                                                return;
908
                                        }
909
                                        if (user == null) {
910
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
911
                                            return;
912
                                    }
913
                                        request.setAttribute(USER_ATTRIBUTE, user);
914

    
915
                                        // Remove the servlet path from the request URI.
916
                                        String p = request.getRequestURI();
917
                                        String servletPath = request.getContextPath() + request.getServletPath();
918
                                        p = p.substring(servletPath.length());
919
                                        // Validate the signature in the Authorization parameter.
920
                                        String data = request.getMethod() + dateParam + p;
921
                                        if (!isSignatureValid(signature, user, data)) {
922
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
923
                                            return;
924
                                    }
925

    
926
                                        progressListener.setUserId(user.getId());
927
                                        progressListener.setFilename(fileName);
928
                                        final String contentType = item.getContentType();
929

    
930
                                        try {
931
                                                uploadedFile = getService().uploadFile(stream, user.getId());
932
                                        } catch (IOException ex) {
933
                                                throw new GSSIOException(ex, false);
934
                                        }
935
                                        FileHeaderDTO fileDTO = null;
936
                                        final File upf = uploadedFile;
937
                                        final FileHeaderDTO f = file;
938
                                        final User u = user;
939
                                        if (file == null)
940
                                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
941
                                                        @Override
942
                                                        public FileHeaderDTO call() throws Exception {
943
                                                                return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
944
                                                        }
945
                                                });
946
                                        else
947
                                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
948
                                                        @Override
949
                                                        public FileHeaderDTO call() throws Exception {
950
                                                                return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
951
                                                        }
952
                                                });
953
                                        updateAccounting(owner, new Date(), fileDTO.getFileSize());
954
                                        getService().removeFileUploadProgress(user.getId(), fileName);
955
                                }
956
                        }
957
                        // We can't return 204 here since GWT's onSubmitComplete won't fire.
958
                        response.setContentType("text/html");
959
            response.getWriter().print("<pre></pre>");
960
                } catch (FileUploadException e) {
961
                        String error = "Error while uploading file";
962
                        logger.error(error, e);
963
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
964
                } catch (GSSIOException e) {
965
                        if (uploadedFile != null && uploadedFile.exists())
966
                                uploadedFile.delete();
967
                        String error = "Error while uploading file";
968
                        if (e.logAsError())
969
                                logger.error(error, e);
970
                        else
971
                                logger.debug(error, e);
972
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
973
                } catch (DuplicateNameException e) {
974
                        if (uploadedFile != null && uploadedFile.exists())
975
                                uploadedFile.delete();
976
                        String error = "The specified file name already exists in this folder";
977
                        logger.error(error, e);
978
                        response.sendError(HttpServletResponse.SC_CONFLICT, error);
979

    
980
                } catch (InsufficientPermissionsException e) {
981
                        if (uploadedFile != null && uploadedFile.exists())
982
                                uploadedFile.delete();
983
                        String error = "You don't have the necessary permissions";
984
                        logger.error(error, e);
985
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
986

    
987
                } catch (QuotaExceededException e) {
988
                        if (uploadedFile != null && uploadedFile.exists())
989
                                uploadedFile.delete();
990
                        String error = "Not enough free space available";
991
                        if (logger.isDebugEnabled())
992
                                logger.debug(error, e);
993
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
994

    
995
                } catch (ObjectNotFoundException e) {
996
                        if (uploadedFile != null && uploadedFile.exists())
997
                                uploadedFile.delete();
998
                        String error = "A specified object was not found";
999
                        logger.error(error, e);
1000
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1001
                } catch (RpcException e) {
1002
                        if (uploadedFile != null && uploadedFile.exists())
1003
                                uploadedFile.delete();
1004
                        String error = "An error occurred while communicating with the service";
1005
                        logger.error(error, e);
1006
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1007
                } catch (Exception e) {
1008
                        if (uploadedFile != null && uploadedFile.exists())
1009
                                uploadedFile.delete();
1010
                        String error = "An internal server error occurred";
1011
                        logger.error(error, e);
1012
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1013
                }
1014
        }
1015

    
1016
        /**
1017
         * Move the resource in the specified path to the specified destination.
1018
         *
1019
         * @param req the HTTP request
1020
         * @param resp the HTTP response
1021
         * @param path the path of the resource
1022
         * @param moveTo the destination of the move procedure
1023
         * @throws IOException if an input/output error occurs
1024
         */
1025
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1026
                final User user = getUser(req);
1027
                User owner = getOwner(req);
1028
                Object resource = null;
1029
                try {
1030
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1031
                } catch (ObjectNotFoundException e) {
1032
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1033
                        return;
1034
                } catch (RpcException e) {
1035
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1036
                        return;
1037
                }
1038

    
1039
        String destination = null;
1040
        User destOwner = null;
1041
                boolean exists = true;
1042
                try {
1043
                        destination = getDestinationPath(req, encodePath(moveTo));
1044
                        destination = URLDecoder.decode(destination, "UTF-8");
1045
                        destOwner = getDestinationOwner(req);
1046
                        getService().getResourceAtPath(destOwner.getId(), destination, true);
1047
                } catch (ObjectNotFoundException e) {
1048
                        exists = false;
1049
                } catch (URISyntaxException e) {
1050
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1051
                        return;
1052
                } catch (RpcException e) {
1053
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1054
                        return;
1055
                }
1056
                if (exists) {
1057
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1058
                        return;
1059
                }
1060

    
1061
                try {
1062
                        final User dOwner = destOwner;
1063
                        final String dest = destination;
1064
                        if (resource instanceof FolderDTO) {
1065
                                final FolderDTO folder = (FolderDTO) resource;
1066
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1067
                                        @Override
1068
                                        public Void call() throws Exception {
1069
                                                getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1070
                                                return null;
1071
                                        }
1072
                                });
1073
                        } else {
1074
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1075
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1076
                                        @Override
1077
                                        public Void call() throws Exception {
1078
                                                getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1079
                                                return null;
1080
                                        }
1081
                                });
1082

    
1083
                        }
1084
                } catch (InsufficientPermissionsException e) {
1085
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1086
                } catch (ObjectNotFoundException e) {
1087
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1088
                } catch (RpcException e) {
1089
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1090
                } catch (DuplicateNameException e) {
1091
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1092
                } catch (GSSIOException e) {
1093
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1094
                } catch (QuotaExceededException e) {
1095
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1096
                } catch (Exception e) {
1097
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1098
                }
1099
        }
1100

    
1101
        /**
1102
         * Copy the resource in the specified path to the specified destination.
1103
         *
1104
         * @param req the HTTP request
1105
         * @param resp the HTTP response
1106
         * @param path the path of the resource
1107
         * @param copyTo the destination of the copy procedure
1108
         * @throws IOException if an input/output error occurs
1109
         */
1110
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1111
                final User user = getUser(req);
1112
                User owner = getOwner(req);
1113
                Object resource = null;
1114
                try {
1115
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1116
                } catch (ObjectNotFoundException e) {
1117
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1118
                        return;
1119
                } catch (RpcException e) {
1120
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1121
                        return;
1122
                }
1123

    
1124
        String destination = null;
1125
        User destOwner = null;
1126
                boolean exists = true;
1127
                try {
1128
                        String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1129
                        destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1130
                        destOwner = getDestinationOwner(req);
1131
                        getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1132
                } catch (ObjectNotFoundException e) {
1133
                        exists = false;
1134
                } catch (URISyntaxException e) {
1135
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1136
                        return;
1137
                } catch (RpcException e) {
1138
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1139
                        return;
1140
                }
1141
                if (exists) {
1142
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1143
                        return;
1144
                }
1145

    
1146
                try {
1147
                        final User dOwner = destOwner;
1148
                        final String dest = destination;
1149
                        if (resource instanceof FolderDTO) {
1150
                                final FolderDTO folder = (FolderDTO) resource;
1151
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1152
                                        @Override
1153
                                        public Void call() throws Exception {
1154
                                                getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1155
                                                return null;
1156
                                        }
1157
                                });
1158
                        } else {
1159
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1160
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1161
                                        @Override
1162
                                        public Void call() throws Exception {
1163
                                                getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1164
                                                return null;
1165
                                        }
1166
                                });
1167
                        }
1168
                } catch (InsufficientPermissionsException e) {
1169
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1170
                } catch (ObjectNotFoundException e) {
1171
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1172
                } catch (RpcException e) {
1173
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1174
                } catch (DuplicateNameException e) {
1175
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1176
                } catch (GSSIOException e) {
1177
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1178
                } catch (QuotaExceededException e) {
1179
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1180
                } catch (Exception e) {
1181
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1182
                }
1183
        }
1184

    
1185
        private String encodePath(String path) throws UnsupportedEncodingException{
1186
                StringTokenizer str = new StringTokenizer(path, "/:", true);
1187
                String result = new String();
1188
                while(str.hasMoreTokens()){
1189
                        String token = str.nextToken();
1190
                        if(!token.equals("/") && !token.equals(":"))
1191
                                token = URLEncoder.encode(token,"UTF-8");
1192
                        result = result + token;
1193
                }
1194
                return result;
1195
        }
1196
        /**
1197
         * A helper method that extracts the relative resource path,
1198
         * after removing the 'files' namespace.
1199
         * The path returned is <i>not</i> URL-decoded.
1200
         *
1201
         * @param req the HTTP request
1202
         * @param path the specified path
1203
         * @return the path relative to the root folder
1204
         * @throws URISyntaxException
1205
         * @throws RpcException in case an error occurs while communicating
1206
         *                                                 with the backend
1207
         * @throws UnsupportedEncodingException
1208
         */
1209
        private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1210
                URI uri = new URI(path);
1211
                String dest = uri.getRawPath();
1212
                // Remove the context path from the destination URI.
1213
                String contextPath = req.getContextPath();
1214
                if (!dest.startsWith(contextPath))
1215
                        throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1216
                dest = dest.substring(contextPath.length());
1217
                // Remove the servlet path from the destination URI.
1218
                String servletPath = req.getServletPath();
1219
                if (!dest.startsWith(servletPath))
1220
                        throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1221
                dest = dest.substring(servletPath.length());
1222
            // Strip the username part
1223
                if (dest.length() < 2)
1224
                        throw new URISyntaxException(dest, "No username in the destination URI");
1225
                int slash = dest.substring(1).indexOf('/');
1226
                if (slash == -1)
1227
                        throw new URISyntaxException(dest, "No username in the destination URI");
1228
                // Decode the user to get the proper characters (mainly the @)
1229
                String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1230
                User o;
1231
                o = getService().findUser(owner);
1232
                if (o == null)
1233
                        throw new URISyntaxException(dest, "User " + owner + " not found");
1234

    
1235
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1236
                dest = dest.substring(slash + 1);
1237

    
1238
                // Chop the resource namespace part
1239
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1240

    
1241
            dest = dest.endsWith("/")? dest: dest + '/';
1242
                return dest;
1243
        }
1244

    
1245
        /**
1246
         * Move the resource in the specified path to the trash bin.
1247
         *
1248
         * @param req the HTTP request
1249
         * @param resp the HTTP response
1250
         * @param path the path of the resource
1251
         * @throws IOException if an input/output error occurs
1252
         */
1253
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1254
                final User user = getUser(req);
1255
                User owner = getOwner(req);
1256
                Object resource = null;
1257
                try {
1258
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1259
                } catch (ObjectNotFoundException e) {
1260
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1261
                        return;
1262
                } catch (RpcException e) {
1263
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1264
                        return;
1265
                }
1266

    
1267
                try {
1268
                        if (resource instanceof FolderDTO) {
1269
                                final FolderDTO folder = (FolderDTO) resource;
1270
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1271
                                        @Override
1272
                                        public Void call() throws Exception {
1273
                                                getService().moveFolderToTrash(user.getId(), folder.getId());
1274
                                                return null;
1275
                                        }
1276
                                });
1277
                        } else {
1278
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1279
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1280
                                        @Override
1281
                                        public Void call() throws Exception {
1282
                                                getService().moveFileToTrash(user.getId(), file.getId());
1283
                                                return null;
1284
                                        }
1285
                                });
1286
                        }
1287
                } catch (InsufficientPermissionsException e) {
1288
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1289
                } catch (ObjectNotFoundException e) {
1290
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1291
                } catch (RpcException e) {
1292
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1293
                } catch (Exception e) {
1294
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1295
                }
1296
        }
1297

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

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

    
1351
        /**
1352
         * Update the resource in the specified path.
1353
         *
1354
         * @param req the HTTP request
1355
         * @param resp the HTTP response
1356
         * @param path the path of the resource
1357
         * @throws IOException if an input/output error occurs
1358
         */
1359
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1360
                final User user = getUser(req);
1361
                User owner = getOwner(req);
1362
                Object resource = null;
1363
                try {
1364
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1365
                } catch (ObjectNotFoundException e) {
1366
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1367
                        return;
1368
                } catch (RpcException e) {
1369
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1370
                        return;
1371
                }
1372
                StringBuffer input = new StringBuffer();
1373
                JSONObject json = null;
1374
                if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1375
                        input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1376
                else {
1377
                        // Assume application/json
1378
                        BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1379
                        String line = null;
1380
                        while ((line = reader.readLine()) != null)
1381
                                input.append(line);
1382
                        reader.close();
1383
                }
1384
                try {
1385
                        json = new JSONObject(input.toString());
1386
                        if (logger.isDebugEnabled())
1387
                                logger.debug("JSON update: " + json);
1388
                        if (resource instanceof FolderDTO) {
1389
                                final FolderDTO folder = (FolderDTO) resource;
1390
                                String name = json.optString("name");
1391
                                if (!name.isEmpty())
1392
                                        try {
1393
                                                name = URLDecoder.decode(name, "UTF-8");
1394
                                        } catch (IllegalArgumentException e) {
1395
                                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1396
                                                return;
1397
                                        }
1398
                                JSONArray permissions = json.optJSONArray("permissions");
1399
                                Set<PermissionDTO> perms = null;
1400
                                if (permissions != null)
1401
                                        perms = parsePermissions(user, permissions);
1402
                                if (!name.isEmpty() || permissions != null) {
1403
                                        final String fName = name.isEmpty()? null: name;
1404
                                        final Set<PermissionDTO> fPerms = perms;
1405
                                        FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1406
                                                @Override
1407
                                                public FolderDTO call() throws Exception {
1408
                                                        return getService().updateFolder(user.getId(), folder.getId(), fName, fPerms);
1409
                                                }
1410

    
1411
                                        });
1412
                                        resp.getWriter().println(getNewUrl(req, folderUpdated));
1413
                                }
1414
                        } else {
1415
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1416
                                String name = null;
1417
                                if (json.opt("name") != null)
1418
                                        name = json.optString("name");
1419
                                Long modificationDate = null;
1420
                                if (json.optLong("modificationDate") != 0)
1421
                                        modificationDate = json.optLong("modificationDate");
1422
                                Boolean versioned = null;
1423
                                if (json.opt("versioned") != null)
1424
                                        versioned = json.getBoolean("versioned");
1425
                                JSONArray tagset = json.optJSONArray("tags");
1426
                                String tags = null;
1427
                                StringBuffer t = new StringBuffer();
1428
                                if (tagset != null) {
1429
                                        for (int i = 0; i < tagset.length(); i++)
1430
                                                t.append(tagset.getString(i) + ',');
1431
                                        tags = t.toString();
1432
                                }
1433
                                JSONArray permissions = json.optJSONArray("permissions");
1434
                                Set<PermissionDTO> perms = null;
1435
                                if (permissions != null)
1436
                                        perms = parsePermissions(user, permissions);
1437
                                Boolean readForAll = null;
1438
                                if (json.opt("readForAll") != null)
1439
                                        readForAll = json.optBoolean("readForAll");
1440
                                if (name != null || tags != null || modificationDate != null
1441
                                                        || versioned != null || perms != null
1442
                                                        || readForAll != null) {
1443
                                        final String fName = name;
1444
                                        final String fTags = tags;
1445
                                        final Date mDate = modificationDate != null? new Date(modificationDate): null;
1446
                                        final Boolean fVersioned = versioned;
1447
                                        final Boolean fReadForAll = readForAll;
1448
                                        final Set<PermissionDTO> fPerms = perms;
1449
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1450
                                                @Override
1451
                                                public Object call() throws Exception {
1452
                                                        getService().updateFile(user.getId(), file.getId(),
1453
                                                                                fName, fTags, mDate, fVersioned,
1454
                                                                                fReadForAll, fPerms);
1455
                                                        return null;
1456
                                                }
1457

    
1458
                                        });
1459
                                }
1460
                        }
1461
                } catch (JSONException e) {
1462
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1463
                } catch (InsufficientPermissionsException e) {
1464
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1465
                } catch (ObjectNotFoundException e) {
1466
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1467
                } catch (DuplicateNameException e) {
1468
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1469
                } catch (RpcException e) {
1470
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1471
                } catch (Exception e) {
1472
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1473
                        return;
1474
                }
1475
        }
1476

    
1477
        /**
1478
         * Returns the new URL of an updated folder.
1479
         */
1480
        private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1481
                String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1482
                String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1483
                if (parentUrl.indexOf(fpath) != -1)
1484
                        parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1485
                if(!parentUrl.endsWith("/"))
1486
                        parentUrl = parentUrl+"/";
1487
                parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1488
                return parentUrl;
1489
        }
1490

    
1491
        /**
1492
         * Helper method to convert a JSON array of permissions into a set of
1493
         * PermissionDTO objects.
1494
         *
1495
         * @param user the current user
1496
         * @param permissions the JSON array to parse
1497
         * @return the parsed set of permissions
1498
         * @throws JSONException if there was an error parsing the JSON object
1499
         * @throws RpcException if there was an error communicating with the EJB
1500
         * @throws ObjectNotFoundException if the user could not be found
1501
         * @throws UnsupportedEncodingException
1502
         */
1503
        private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1504
                        throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1505
                if (permissions == null)
1506
                        return null;
1507
                Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1508
                for (int i = 0; i < permissions.length(); i++) {
1509
                        JSONObject j = permissions.getJSONObject(i);
1510
                        PermissionDTO perm = new PermissionDTO();
1511
                        perm.setModifyACL(j.optBoolean("modifyACL"));
1512
                        perm.setRead(j.optBoolean("read"));
1513
                        perm.setWrite(j.optBoolean("write"));
1514
                        String permUser = j.optString("user");
1515
                        if (!permUser.isEmpty()) {
1516
                                User u = getService().findUser(permUser);
1517
                                if (u == null)
1518
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1519
                                perm.setUser(u.getDTO());
1520
                        }
1521
                        // 31/8/2009: Add optional groupUri which takes priority if it exists
1522
                        String permGroupUri = j.optString("groupUri");
1523
                        String permGroup = j.optString("group");
1524
                        if (!permGroupUri.isEmpty()) {
1525
                                String[] names = permGroupUri.split("/");
1526
                                String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1527
                                String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1528
                                User u = getService().findUser(usr);
1529
                                if (u == null)
1530
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1531
                                GroupDTO g = getService().getGroup(u.getId(), grp);
1532
                                perm.setGroup(g);
1533
                        }
1534
                        else if (!permGroup.isEmpty()) {
1535
                                GroupDTO g = getService().getGroup(user.getId(), permGroup);
1536
                                perm.setGroup(g);
1537
                        }
1538
                        if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1539
                                throw new JSONException("A permission must correspond to either a user or a group");
1540
                        perms.add(perm);
1541
                }
1542
                return perms;
1543
        }
1544

    
1545
        /**
1546
         * Creates a new folder with the specified name under the folder in the provided path.
1547
         *
1548
         * @param req the HTTP request
1549
         * @param resp the HTTP response
1550
         * @param path the parent folder path
1551
         * @param folderName the name of the new folder
1552
         * @throws IOException if an input/output error occurs
1553
         */
1554
        private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1555
                if (logger.isDebugEnabled())
1556
                           logger.debug("Creating folder " + folderName + " in '" + path);
1557

    
1558
            final User user = getUser(req);
1559
            User owner = getOwner(req);
1560
        boolean exists = true;
1561
        try {
1562
                getService().getResourceAtPath(owner.getId(), path + folderName, false);
1563
        } catch (ObjectNotFoundException e) {
1564
            exists = false;
1565
        } catch (RpcException e) {
1566
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1567
                        return;
1568
                }
1569

    
1570
        if (exists) {
1571
            resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1572
                                    ", " + METHOD_HEAD);
1573
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1574
            return;
1575
        }
1576

    
1577
                Object parent;
1578
                try {
1579
                        parent = getService().getResourceAtPath(owner.getId(), path, true);
1580
                } catch (ObjectNotFoundException e) {
1581
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1582
                        return;
1583
                } catch (RpcException e) {
1584
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1585
                        return;
1586
                }
1587
                try {
1588
                        if (parent instanceof FolderDTO) {
1589
                                final FolderDTO folder = (FolderDTO) parent;
1590
                                FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1591
                                        @Override
1592
                                        public FolderDTO call() throws Exception {
1593
                                                return getService().createFolder(user.getId(), folder.getId(), folderName);
1594
                                        }
1595

    
1596
                                });
1597
                        String newResource = getApiRoot() + newFolder.getURI();
1598
                        resp.setHeader("Location", newResource);
1599
                        resp.setContentType("text/plain");
1600
                        PrintWriter out = resp.getWriter();
1601
                        out.println(newResource);
1602
                        } else {
1603
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1604
                            return;
1605
                        }
1606
                } catch (DuplicateNameException e) {
1607
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1608
                    return;
1609
                } catch (InsufficientPermissionsException e) {
1610
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1611
                    return;
1612
                } catch (ObjectNotFoundException e) {
1613
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1614
                        return;
1615
                } catch (RpcException e) {
1616
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1617
                        return;
1618
                } catch (Exception e) {
1619
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1620
                        return;
1621
                }
1622
            resp.setStatus(HttpServletResponse.SC_CREATED);
1623
        }
1624

    
1625
        /**
1626
         * @param req
1627
         * @param resp
1628
         * @throws IOException
1629
         * @throws FileNotFoundException
1630
         */
1631
        void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1632
        String path = getInnerPath(req, PATH_FILES);
1633
                try {
1634
                    path = URLDecoder.decode(path, "UTF-8");
1635
                } catch (IllegalArgumentException e) {
1636
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1637
                        return;
1638
                }
1639
            if (logger.isDebugEnabled())
1640
                           logger.debug("Updating resource: " + path);
1641

    
1642
            final User user = getUser(req);
1643
            User owner = getOwner(req);
1644
            boolean exists = true;
1645
        Object resource = null;
1646
        FileHeaderDTO file = null;
1647
        try {
1648
                resource = getService().getResourceAtPath(owner.getId(), path, false);
1649
        } catch (ObjectNotFoundException e) {
1650
            exists = false;
1651
        } catch (RpcException e) {
1652
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1653
                        return;
1654
                }
1655

    
1656
        if (exists)
1657
                        if (resource instanceof FileHeaderDTO)
1658
                            file = (FileHeaderDTO) resource;
1659
                        else {
1660
                        resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1661
                            return;
1662
                }
1663
        boolean result = true;
1664

    
1665
        // Temporary content file used to support partial PUT.
1666
        File contentFile = null;
1667

    
1668
        Range range = parseContentRange(req, resp);
1669

    
1670
        InputStream resourceInputStream = null;
1671

    
1672
        // Append data specified in ranges to existing content for this
1673
        // resource - create a temporary file on the local filesystem to
1674
        // perform this operation.
1675
        // Assume just one range is specified for now
1676
        if (range != null) {
1677
            try {
1678
                                contentFile = executePartialPut(req, range, path);
1679
                        } catch (RpcException e) {
1680
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1681
                                return;
1682
                        } catch (ObjectNotFoundException e) {
1683
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
1684
                        return;
1685
                        } catch (InsufficientPermissionsException e) {
1686
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1687
                        return;
1688
                        }
1689
            resourceInputStream = new FileInputStream(contentFile);
1690
        } else
1691
                        resourceInputStream = req.getInputStream();
1692

    
1693
        try {
1694
                FolderDTO folder = null;
1695
                Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1696
                if (!(parent instanceof FolderDTO)) {
1697
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1698
                        return;
1699
                }
1700
                       folder = (FolderDTO) parent;
1701
                final String name = getLastElement(path);
1702
                final String mimeType = context.getMimeType(name);
1703
                File uploadedFile = null;
1704
                try {
1705
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1706
                        } catch (IOException ex) {
1707
                                throw new GSSIOException(ex, false);
1708
                        }
1709
                FileHeaderDTO fileDTO = null;
1710
                final File uploadedf = uploadedFile;
1711
                        final FolderDTO parentf = folder;
1712
                        final FileHeaderDTO f = file;
1713
            if (exists)
1714
                    fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1715
                                        @Override
1716
                                        public FileHeaderDTO call() throws Exception {
1717
                                                return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1718
                                        }
1719
                                });
1720
                        else
1721
                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1722
                                        @Override
1723
                                        public FileHeaderDTO call() throws Exception {
1724
                                                return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1725
                                        }
1726

    
1727
                                });
1728
            updateAccounting(owner, new Date(), fileDTO.getFileSize());
1729
                        getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1730
        } catch(ObjectNotFoundException e) {
1731
            result = false;
1732
        } catch (RpcException e) {
1733
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1734
                        return;
1735
        } catch (IOException e) {
1736
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1737
                        return;
1738
                } catch (GSSIOException e) {
1739
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1740
                        return;
1741
                } catch (DuplicateNameException e) {
1742
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1743
                    return;
1744
                } catch (InsufficientPermissionsException e) {
1745
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1746
                    return;
1747
                } catch (QuotaExceededException e) {
1748
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1749
                    return;
1750
                } catch (Exception e) {
1751
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1752
                        return;
1753
                }
1754

    
1755
        if (result) {
1756
            if (exists)
1757
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1758
                        else
1759
                                resp.setStatus(HttpServletResponse.SC_CREATED);
1760
        } else
1761
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1762
        }
1763

    
1764
    /**
1765
     * Delete a resource.
1766
     *
1767
     * @param req The servlet request we are processing
1768
     * @param resp The servlet response we are processing
1769
         * @throws IOException if the response cannot be sent
1770
     */
1771
    void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1772
        String path = getInnerPath(req, PATH_FILES);
1773
            if (logger.isDebugEnabled())
1774
                           logger.debug("Deleting resource '" + path);
1775
            path = URLDecoder.decode(path, "UTF-8");
1776
            final User user = getUser(req);
1777
            User owner = getOwner(req);
1778
            boolean exists = true;
1779
            Object object = null;
1780
            try {
1781
                    object = getService().getResourceAtPath(owner.getId(), path, false);
1782
            } catch (ObjectNotFoundException e) {
1783
                    exists = false;
1784
            } catch (RpcException e) {
1785
                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1786
                        return;
1787
                }
1788

    
1789
            if (!exists) {
1790
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1791
                    return;
1792
            }
1793

    
1794
            FolderDTO folder = null;
1795
            FileHeaderDTO file = null;
1796
            if (object instanceof FolderDTO)
1797
                    folder = (FolderDTO) object;
1798
            else
1799
                    file = (FileHeaderDTO) object;
1800

    
1801
            if (file != null)
1802
                        try {
1803
                                final FileHeaderDTO f = file;
1804
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1805
                                        @Override
1806
                                        public Void call() throws Exception {
1807
                                                getService().deleteFile(user.getId(), f.getId());
1808
                                                return null;
1809
                                        }
1810
                                });
1811
                } catch (InsufficientPermissionsException e) {
1812
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1813
                                return;
1814
                    } catch (ObjectNotFoundException e) {
1815
                            // Although we had already found the object, it was
1816
                            // probably deleted from another thread.
1817
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1818
                            return;
1819
                    } catch (RpcException e) {
1820
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1821
                            return;
1822
                    } catch (Exception e) {
1823
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1824
                            return;
1825
                    }
1826
                else if (folder != null)
1827
                        try {
1828
                                final FolderDTO fo = folder;
1829
                                new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1830
                                        @Override
1831
                                        public Void call() throws Exception {
1832
                                                getService().deleteFolder(user.getId(), fo.getId());
1833
                                                return null;
1834
                                        }
1835
                                });
1836
                } catch (InsufficientPermissionsException e) {
1837
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1838
                        return;
1839
                    } catch (ObjectNotFoundException e) {
1840
                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1841
                        return;
1842
                    } catch (RpcException e) {
1843
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1844
                        return;
1845
                    } catch (Exception e) {
1846
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1847
                        return;
1848
                    }
1849
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1850
            return;
1851
    }
1852

    
1853
        /**
1854
     * Return an InputStream to a JSON representation of the contents
1855
     * of this directory.
1856
     *
1857
         * @param user the user that made the request
1858
     * @param folder the specified directory
1859
     * @return an input stream with the rendered contents
1860
         * @throws IOException if the response cannot be sent
1861
     * @throws ServletException
1862
         * @throws InsufficientPermissionsException if the user does not have
1863
         *                         the necessary privileges to read the directory
1864
     */
1865
    private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1866
                    ServletException, InsufficientPermissionsException {
1867
            JSONObject json = new JSONObject();
1868
            try {
1869
                        json.put("name", folder.getName()).
1870
                                        put("owner", folder.getOwner().getUsername()).
1871
                                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1872
                                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1873
                                        put("deleted", folder.isDeleted());
1874
                        if (folder.getAuditInfo().getModifiedBy() != null)
1875
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1876
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1877
                        if (folder.getParent() != null) {
1878
                                JSONObject j = new JSONObject();
1879
                                j.put("uri", getApiRoot() + folder.getParent().getURI());
1880
                                j.put("name", folder.getParent().getName());
1881
                                json.put("parent", j);
1882
                        }
1883
                    List<JSONObject> subfolders = new ArrayList<JSONObject>();
1884
                    for (FolderDTO f: folder.getSubfolders())
1885
                                if (!f.isDeleted()) {
1886
                                        JSONObject j = new JSONObject();
1887
                                        j.put("name", f.getName()).
1888
                                                put("uri", getApiRoot() + f.getURI());
1889
                                        subfolders.add(j);
1890
                                }
1891
                    json.put("folders", subfolders);
1892
                    List<JSONObject> files = new ArrayList<JSONObject>();
1893
                    List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1894
                    for (FileHeaderDTO f: fileHeaders) {
1895
                            JSONObject j = new JSONObject();
1896
                                j.put("name", f.getName()).
1897
                                        put("owner", f.getOwner().getUsername()).
1898
                                        put("deleted", f.isDeleted()).
1899
                                        put("version", f.getVersion()).
1900
                                        put("content", f.getMimeType()).
1901
                                        put("size", f.getFileSize()).
1902
                                        put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1903
                                        put("path", f.getFolder().getPath()).
1904
                                        put("uri", getApiRoot() + f.getURI());
1905
                                if (f.getAuditInfo().getModificationDate() != null)
1906
                                        j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1907
                                files.add(j);
1908
                    }
1909
                    json.put("files", files);
1910
                    Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1911
                    json.put("permissions", renderJson(perms));
1912
                } catch (JSONException e) {
1913
                        throw new ServletException(e);
1914
                } catch (ObjectNotFoundException e) {
1915
                        throw new ServletException(e);
1916
                } catch (RpcException e) {
1917
                        throw new ServletException(e);
1918
                }
1919

    
1920
            // Prepare a writer to a buffered area
1921
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
1922
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1923
            PrintWriter writer = new PrintWriter(osWriter);
1924

    
1925
            // Return an input stream to the underlying bytes
1926
            writer.write(json.toString());
1927
            writer.flush();
1928
            return new ByteArrayInputStream(stream.toByteArray());
1929
    }
1930

    
1931
        /**
1932
     * Return a String with a JSON representation of the metadata
1933
     * of the specified folder.
1934
         * @throws RpcException
1935
         * @throws InsufficientPermissionsException
1936
         * @throws ObjectNotFoundException
1937
     */
1938
    private String renderJsonMetadata(User user, FolderDTO folder)
1939
                    throws ServletException, InsufficientPermissionsException {
1940
            // Check if the user has read permission.
1941
                try {
1942
                        if (!getService().canReadFolder(user.getId(), folder.getId()))
1943
                                throw new InsufficientPermissionsException();
1944
                } catch (ObjectNotFoundException e) {
1945
                        throw new ServletException(e);
1946
                } catch (RpcException e) {
1947
                        throw new ServletException(e);
1948
                }
1949

    
1950
            JSONObject json = new JSONObject();
1951
            try {
1952
                        json.put("name", folder.getName()).
1953
                        put("owner", folder.getOwner().getUsername()).
1954
                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1955
                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1956
                        put("deleted", folder.isDeleted());
1957
                        if (folder.getAuditInfo().getModifiedBy() != null)
1958
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1959
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1960
                } catch (JSONException e) {
1961
                        throw new ServletException(e);
1962
                }
1963
            return json.toString();
1964
    }
1965

    
1966
        /**
1967
     * Return a String with a JSON representation of the metadata
1968
     * of the specified file. If an old file body is provided, then
1969
     * the metadata of that particular version will be returned.
1970
     *
1971
         * @param user the user that made the request
1972
     * @param file the specified file header
1973
     * @param oldBody the version number
1974
     * @return the JSON-encoded file
1975
     * @throws ServletException
1976
         * @throws InsufficientPermissionsException if the user does not have
1977
         *                         the necessary privileges to read the directory
1978
     */
1979
    private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1980
                    throws ServletException, InsufficientPermissionsException {
1981
            JSONObject json = new JSONObject();
1982
            try {
1983
                    // Need to encode file name in order to properly display it in the web client.
1984
                        json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1985
                                        put("owner", file.getOwner().getUsername()).
1986
                                        put("versioned", file.isVersioned()).
1987
                                        put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1988
                                        put("readForAll", file.isReadForAll()).
1989
                                        put("tags", renderJson(file.getTags())).
1990
                                        put("path", file.getFolder().getPath()).
1991
                                    put("uri", getApiRoot() + file.getURI()).
1992
                                        put("deleted", file.isDeleted());
1993
                        JSONObject j = new JSONObject();
1994
                        j.put("uri", getApiRoot() + file.getFolder().getURI()).
1995
                                        put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1996
                        json.put("folder", j);
1997
                        if (oldBody != null)
1998
                                json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1999
                                                put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2000
                                                put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2001
                                                put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2002
                                                put("content", oldBody.getMimeType()).
2003
                                                put("size", oldBody.getFileSize());
2004
                        else
2005
                                json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2006
                                                put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2007
                                                put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2008
                                                put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2009
                                                put("content", file.getMimeType()).
2010
                                                put("size", file.getFileSize());
2011
                    Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2012
                    json.put("permissions", renderJson(perms));
2013
                } catch (JSONException e) {
2014
                        throw new ServletException(e);
2015
                } catch (ObjectNotFoundException e) {
2016
                        throw new ServletException(e);
2017
                } catch (RpcException e) {
2018
                        throw new ServletException(e);
2019
                } catch (UnsupportedEncodingException e) {
2020
                        throw new ServletException(e);
2021
                }
2022

    
2023
            return json.toString();
2024
    }
2025

    
2026
        /**
2027
         * Return a String with a JSON representation of the
2028
         * specified set of permissions.
2029
     *
2030
         * @param permissions the set of permissions
2031
         * @return the JSON-encoded object
2032
         * @throws JSONException
2033
         * @throws UnsupportedEncodingException
2034
         */
2035
        private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2036
                JSONArray perms = new JSONArray();
2037
                for (PermissionDTO p: permissions) {
2038
                        JSONObject permission = new JSONObject();
2039
                        permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2040
                        if (p.getUser() != null)
2041
                                permission.put("user", p.getUser().getUsername());
2042
                        if (p.getGroup() != null) {
2043
                                GroupDTO group = p.getGroup();
2044
                                permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2045
                                permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2046
                        }
2047
                        perms.put(permission);
2048
                }
2049
                return perms;
2050
        }
2051

    
2052
        /**
2053
         * Return a String with a JSON representation of the
2054
         * specified collection of tags.
2055
     *
2056
         * @param tags the collection of tags
2057
         * @return the JSON-encoded object
2058
         * @throws JSONException
2059
         * @throws UnsupportedEncodingException
2060
         */
2061
        private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2062
                JSONArray tagArray = new JSONArray();
2063
                for (String t: tags)
2064
                        tagArray.put(URLEncoder.encode(t,"UTF-8"));
2065
                return tagArray;
2066
        }
2067

    
2068
        /**
2069
         * Retrieves the user who owns the destination namespace, for a
2070
         * copy or move request.
2071
         *
2072
         * @param req the HTTP request
2073
         * @return the owner of the namespace
2074
         */
2075
        protected User getDestinationOwner(HttpServletRequest req) {
2076
                return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2077
        }
2078

    
2079
        /**
2080
         * A helper inner class for updating the progress status of a file upload.
2081
         *
2082
         * @author kman
2083
         */
2084
        public static class StatusProgressListener implements ProgressListener {
2085
                private int percentLogged = 0;
2086
                private long bytesTransferred = 0;
2087

    
2088
                private long fileSize = -100;
2089

    
2090
                private Long userId;
2091

    
2092
                private String filename;
2093

    
2094
                private ExternalAPI service;
2095

    
2096
                public StatusProgressListener(ExternalAPI aService) {
2097
                        service = aService;
2098
                }
2099

    
2100
                /**
2101
                 * Modify the userId.
2102
                 *
2103
                 * @param aUserId the userId to set
2104
                 */
2105
                public void setUserId(Long aUserId) {
2106
                        userId = aUserId;
2107
                }
2108

    
2109
                /**
2110
                 * Modify the filename.
2111
                 *
2112
                 * @param aFilename the filename to set
2113
                 */
2114
                public void setFilename(String aFilename) {
2115
                        filename = aFilename;
2116
                }
2117

    
2118
                @Override
2119
                public void update(long bytesRead, long contentLength, int items) {
2120
                        //monitoring per percent of bytes uploaded
2121
                        bytesTransferred = bytesRead;
2122
                        if (fileSize != contentLength)
2123
                                fileSize = contentLength;
2124
                        int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2125

    
2126
                        if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2127
                                if (percent != percentLogged){
2128
                                        percentLogged = percent;
2129
                                        try {
2130
                                                if (userId != null && filename != null)
2131
                                                        service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2132
                                        } catch (ObjectNotFoundException e) {
2133
                                                // Swallow the exception since it is going to be caught
2134
                                                // by previously called methods
2135
                                        }
2136
                                }
2137
                }
2138
        }
2139
}