View Javadoc

1   package com.bradmcevoy.http.webdav;
2   
3   import com.bradmcevoy.http.*;
4   import com.bradmcevoy.http.AuthenticationService.AuthStatus;
5   import com.bradmcevoy.http.exceptions.BadRequestException;
6   import com.bradmcevoy.http.exceptions.ConflictException;
7   import com.bradmcevoy.http.exceptions.NotAuthorizedException;
8   import com.bradmcevoy.http.webdav.PropPatchRequestParser.ParseResult;
9   import java.io.IOException;
10  import java.io.InputStream;
11  import java.util.ArrayList;
12  import java.util.Iterator;
13  import java.util.List;
14  
15  import org.slf4j.Logger;
16  import org.slf4j.LoggerFactory;
17  
18  import com.bradmcevoy.http.Request.Method;
19  import com.bradmcevoy.http.Response.Status;
20  import com.bradmcevoy.io.ReadingException;
21  import com.bradmcevoy.io.WritingException;
22  import com.bradmcevoy.property.DefaultPropertyAuthoriser;
23  import com.bradmcevoy.property.PropertyHandler;
24  import com.bradmcevoy.property.PropertyAuthoriser;
25  import com.ettrema.event.PropPatchEvent;
26  import java.util.HashSet;
27  import java.util.Set;
28  import javax.xml.namespace.QName;
29  
30  /**
31   * Example request (from ms office)
32   *
33   * PROPPATCH /Documents/test.docx HTTP/1.1
34  content-length: 371
35  cache-control: no-cache
36  connection: Keep-Alive
37  host: milton:8080
38  user-agent: Microsoft-WebDAV-MiniRedir/6.0.6001
39  pragma: no-cache
40  translate: f
41  if: (<opaquelocktoken:900f718e-801c-4152-ae8e-f9395fe45d71>)
42  content-type: text/xml; charset="utf-8"
43  <?xml version="1.0" encoding="utf-8" ?>
44   * <D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:">
45   *  <D:set>
46   *  <D:prop>
47   *  <Z:Win32LastAccessTime>Wed, 10 Dec 2008 21:55:22 GMT</Z:Win32LastAccessTime>
48   *  <Z:Win32LastModifiedTime>Wed, 10 Dec 2008 21:55:22 GMT</Z:Win32LastModifiedTime>
49   *  <Z:Win32FileAttributes>00000020</Z:Win32FileAttributes>
50   * </D:prop>
51   * </D:set>
52   * </D:propertyupdate>
53   *
54   *
55   * And another example request (from spec)
56   *
57   *    <?xml version="1.0" encoding="utf-8" ?>
58  <D:propertyupdate xmlns:D="DAV:"
59  xmlns:Z="http://www.w3.com/standards/z39.50/">
60  <D:set>
61  <D:prop>
62  <Z:authors>
63  <Z:Author>Jim Whitehead</Z:Author>
64  <Z:Author>Roy Fielding</Z:Author>
65  </Z:authors>
66  </D:prop>
67  </D:set>
68  <D:remove>
69  <D:prop><Z:Copyright-Owner/></D:prop>
70  </D:remove>
71  </D:propertyupdate>
72  
73   *
74   *
75   * Here is an example response (from the spec)
76   *
77   *    HTTP/1.1 207 Multi-Status
78  Content-Type: text/xml; charset="utf-8"
79  Content-Length: xxxx
80  
81  <?xml version="1.0" encoding="utf-8" ?>
82  <D:multistatus xmlns:D="DAV:" xmlns:Z="http://www.w3.com/standards/z39.50">
83  <D:response>
84  <D:href>http://www.foo.com/bar.html</D:href>
85  <D:propstat>
86  <D:prop><Z:Authors/></D:prop>
87  <D:status>HTTP/1.1 424 Failed Dependency</D:status>
88  </D:propstat>
89  <D:propstat>
90  <D:prop><Z:Copyright-Owner/></D:prop>
91  <D:status>HTTP/1.1 409 Conflict</D:status>
92  </D:propstat>
93  <D:responsedescription> Copyright Owner can not be deleted or altered.</D:responsedescription>
94  </D:response>
95  </D:multistatus>
96  
97   *
98   *
99   * @author brad
100  */
101 public class PropPatchHandler implements ExistingEntityHandler, PropertyHandler {
102 
103     private final static Logger log = LoggerFactory.getLogger( PropPatchHandler.class );
104     private final ResourceHandlerHelper resourceHandlerHelper;
105     private final PropPatchRequestParser requestParser;
106     private final PropPatchSetter patchSetter;
107     private final WebDavResponseHandler responseHandler;
108     private PropertyAuthoriser permissionService = new DefaultPropertyAuthoriser();
109 
110     public PropPatchHandler( ResourceHandlerHelper resourceHandlerHelper, WebDavResponseHandler responseHandler, PropPatchSetter propPatchSetter ) {
111         this.resourceHandlerHelper = resourceHandlerHelper;
112         this.requestParser = new DefaultPropPatchParser();
113         patchSetter = propPatchSetter;
114         this.responseHandler = responseHandler;
115     }
116 
117     public PropPatchHandler( ResourceHandlerHelper resourceHandlerHelper, PropPatchRequestParser requestParser, PropPatchSetter patchSetter, WebDavResponseHandler responseHandler ) {
118         this.resourceHandlerHelper = resourceHandlerHelper;
119         this.requestParser = requestParser;
120         this.patchSetter = patchSetter;
121         this.responseHandler = responseHandler;
122     }
123 
124     public String[] getMethods() {
125         return new String[]{Method.PROPPATCH.code};
126     }
127 
128     public boolean isCompatible( Resource r ) {
129         return patchSetter.supports( r );
130     }
131 
132     public void process( HttpManager httpManager, Request request, Response response ) throws ConflictException, NotAuthorizedException, BadRequestException {
133         resourceHandlerHelper.process( httpManager, request, response, this );
134     }
135 
136     public void processResource( HttpManager manager, Request request, Response response, Resource resource ) throws NotAuthorizedException, ConflictException, BadRequestException {
137         long t = System.currentTimeMillis();
138         try {
139 
140             manager.onProcessResourceStart( request, response, resource );
141 
142             if( resourceHandlerHelper.isNotCompatible( resource, request.getMethod() ) || !isCompatible( resource ) ) {
143                 log.debug( "resource not compatible. Resource class: " + resource.getClass() + " handler: " + getClass() );
144                 responseHandler.respondMethodNotImplemented( resource, response, request );
145                 return;
146             }
147 
148             AuthStatus authStatus = resourceHandlerHelper.checkAuthentication( manager, resource, request );
149             if( authStatus != null && authStatus.loginFailed ) {
150                 log.debug( "authentication failed. respond with: " + responseHandler.getClass().getCanonicalName() + " resource: " + resource.getClass().getCanonicalName() );
151                 responseHandler.respondUnauthorised( resource, response, request );
152                 return;
153             }
154 
155             if( request.getMethod().isWrite ) {
156                 if( resourceHandlerHelper.isLockedOut( request, resource ) ) {
157                     response.setStatus( Status.SC_LOCKED ); // replace with responsehandler method
158                     return;
159                 }
160             }
161 
162             processExistingResource( manager, request, response, resource );
163         } finally {
164             t = System.currentTimeMillis() - t;
165             manager.onProcessResourceFinish( request, response, resource, t );
166         }
167     }
168 
169     public void processExistingResource( HttpManager manager, Request request, Response response, Resource resource ) throws NotAuthorizedException, BadRequestException, ConflictException {
170         // todo: check if token header
171         try {
172             PropFindResponse resp = doPropPatch( request, resource);
173 
174             manager.getEventManager().fireEvent( new PropPatchEvent( resource, resp ) );
175             List<PropFindResponse> responses = new ArrayList<PropFindResponse>();
176             responses.add( resp );
177             responseHandler.respondPropFind( responses, response, request, resource );
178         } catch( NotAuthorizedException e ) {
179             responseHandler.respondUnauthorised( resource, response, request );
180         } catch( WritingException ex ) {
181             throw new RuntimeException( ex );
182         } catch( ReadingException ex ) {
183             throw new RuntimeException( ex );
184         } catch( IOException ex ) {
185             throw new RuntimeException( ex );
186         }
187     }
188 
189     public PropFindResponse doPropPatch(Request request, Resource resource) throws NotAuthorizedException, IOException {
190         InputStream in = request.getInputStream();
191         ParseResult parseResult = requestParser.getRequestedFields(in);
192         // Check that the current user has permission to write requested fields
193         Set<QName> allFields = getAllFields(parseResult);
194         if (log.isTraceEnabled()) {
195             log.trace("check permissions with: " + permissionService.getClass().getCanonicalName());
196         }
197         Set<PropertyAuthoriser.CheckResult> errorFields = permissionService.checkPermissions(request, request.getMethod(), PropertyAuthoriser.PropertyPermission.WRITE, allFields, resource);
198         if (errorFields != null && errorFields.size() > 0) {
199             throw new NotAuthorizedException(resource);
200         }
201         String href = request.getAbsoluteUrl();
202         PropFindResponse resp = patchSetter.setProperties(href, parseResult, resource);
203         return resp;
204     }
205 
206     private Set<QName> getAllFields( ParseResult parseResult ) {
207         Set<QName> set = new HashSet<QName>();
208         if( parseResult.getFieldsToRemove() != null ) {
209             set.addAll( parseResult.getFieldsToRemove() );
210         }
211         if( parseResult.getFieldsToSet() != null ) {
212             set.addAll( parseResult.getFieldsToSet().keySet() );
213         }
214         return set;
215     }
216 
217     public PropertyAuthoriser getPermissionService() {
218         return permissionService;
219     }
220 
221     public void setPermissionService( PropertyAuthoriser permissionService ) {
222         this.permissionService = permissionService;
223     }
224 
225     public static class Field {
226 
227         public final String name;
228         String namespaceUri;
229 
230         public Field( String name ) {
231             this.name = name;
232         }
233 
234         public void setNamespaceUri( String namespaceUri ) {
235             this.namespaceUri = namespaceUri;
236         }
237 
238         public String getNamespaceUri() {
239             return namespaceUri;
240         }
241     }
242 
243     public static class SetField extends Field {
244 
245         public final String value;
246 
247         public SetField( String name, String value ) {
248             super( name );
249             this.value = value;
250         }
251     }
252 
253     public static class Fields implements Iterable<Field> {
254 
255         /**
256          * fields to remove
257          */
258         public final List<Field> removeFields = new ArrayList<Field>();
259         /**
260          * fields to set to a value
261          */
262         public final List<SetField> setFields = new ArrayList<PropPatchHandler.SetField>();
263 
264         private int size() {
265             return removeFields.size() + setFields.size();
266         }
267 
268         public Iterator<Field> iterator() {
269             List<Field> list = new ArrayList<Field>( removeFields );
270             list.addAll( setFields );
271             return list.iterator();
272         }
273     }
274 }