1// 2// ======================================================================== 3// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4// ------------------------------------------------------------------------ 5// All rights reserved. This program and the accompanying materials 6// are made available under the terms of the Eclipse Public License v1.0 7// and Apache License v2.0 which accompanies this distribution. 8// 9// The Eclipse Public License is available at 10// http://www.eclipse.org/legal/epl-v10.html 11// 12// The Apache License v2.0 is available at 13// http://www.opensource.org/licenses/apache2.0.php 14// 15// You may elect to redistribute this code under either of these licenses. 16// ======================================================================== 17// 18 19 20package org.eclipse.jetty.client.webdav; 21 22import java.io.IOException; 23 24import org.eclipse.jetty.client.HttpDestination; 25import org.eclipse.jetty.client.HttpEventListenerWrapper; 26import org.eclipse.jetty.client.HttpExchange; 27import org.eclipse.jetty.client.security.SecurityListener; 28import org.eclipse.jetty.http.HttpMethods; 29import org.eclipse.jetty.http.HttpStatus; 30import org.eclipse.jetty.io.Buffer; 31import org.eclipse.jetty.util.URIUtil; 32import org.eclipse.jetty.util.log.Log; 33import org.eclipse.jetty.util.log.Logger; 34 35/** 36 * WebdavListener 37 * 38 * 39 * 40 * 41 */ 42public class WebdavListener extends HttpEventListenerWrapper 43{ 44 private static final Logger LOG = Log.getLogger(WebdavListener.class); 45 46 private HttpDestination _destination; 47 private HttpExchange _exchange; 48 private boolean _requestComplete; 49 private boolean _responseComplete; 50 private boolean _webdavEnabled; 51 private boolean _needIntercept; 52 53 public WebdavListener(HttpDestination destination, HttpExchange ex) 54 { 55 // Start of sending events through to the wrapped listener 56 // Next decision point is the onResponseStatus 57 super(ex.getEventListener(),true); 58 _destination=destination; 59 _exchange=ex; 60 61 // We'll only enable webdav if this is a PUT request 62 if ( HttpMethods.PUT.equalsIgnoreCase( _exchange.getMethod() ) ) 63 { 64 _webdavEnabled = true; 65 } 66 } 67 68 @Override 69 public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException 70 { 71 if ( !_webdavEnabled ) 72 { 73 _needIntercept = false; 74 super.onResponseStatus(version, status, reason); 75 return; 76 } 77 78 if (LOG.isDebugEnabled()) 79 LOG.debug("WebdavListener:Response Status: " + status ); 80 81 // The dav spec says that CONFLICT should be returned when the parent collection doesn't exist but I am seeing 82 // FORBIDDEN returned instead so running with that. 83 if ( status == HttpStatus.FORBIDDEN_403 || status == HttpStatus.CONFLICT_409 ) 84 { 85 if ( _webdavEnabled ) 86 { 87 if (LOG.isDebugEnabled()) 88 LOG.debug("WebdavListener:Response Status: dav enabled, taking a stab at resolving put issue" ); 89 setDelegatingResponses( false ); // stop delegating, we can try and fix this request 90 _needIntercept = true; 91 } 92 else 93 { 94 if (LOG.isDebugEnabled()) 95 LOG.debug("WebdavListener:Response Status: Webdav Disabled" ); 96 setDelegatingResponses( true ); // just make sure we delegate 97 setDelegatingRequests( true ); 98 _needIntercept = false; 99 } 100 } 101 else 102 { 103 _needIntercept = false; 104 setDelegatingResponses( true ); 105 setDelegatingRequests( true ); 106 } 107 108 super.onResponseStatus(version, status, reason); 109 } 110 111 @Override 112 public void onResponseComplete() throws IOException 113 { 114 _responseComplete = true; 115 if (_needIntercept) 116 { 117 if ( _requestComplete && _responseComplete) 118 { 119 try 120 { 121 // we have some work to do before retrying this 122 if ( resolveCollectionIssues() ) 123 { 124 setDelegatingRequests( true ); 125 setDelegatingResponses(true); 126 _requestComplete = false; 127 _responseComplete = false; 128 _destination.resend(_exchange); 129 } 130 else 131 { 132 // admit defeat but retry because someone else might have 133 setDelegationResult(false); 134 setDelegatingRequests( true ); 135 setDelegatingResponses(true); 136 super.onResponseComplete(); 137 } 138 } 139 catch ( IOException ioe ) 140 { 141 LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate"); 142 super.onResponseComplete(); 143 } 144 } 145 else 146 { 147 if (LOG.isDebugEnabled()) 148 LOG.debug("WebdavListener:Not ready, calling super"); 149 super.onResponseComplete(); 150 } 151 } 152 else 153 { 154 super.onResponseComplete(); 155 } 156 } 157 158 159 160 @Override 161 public void onRequestComplete () throws IOException 162 { 163 _requestComplete = true; 164 if (_needIntercept) 165 { 166 if ( _requestComplete && _responseComplete) 167 { 168 try 169 { 170 // we have some work to do before retrying this 171 if ( resolveCollectionIssues() ) 172 { 173 setDelegatingRequests( true ); 174 setDelegatingResponses(true); 175 _requestComplete = false; 176 _responseComplete = false; 177 _destination.resend(_exchange); 178 } 179 else 180 { 181 // admit defeat but retry because someone else might have 182 setDelegatingRequests( true ); 183 setDelegatingResponses(true); 184 super.onRequestComplete(); 185 } 186 } 187 catch ( IOException ioe ) 188 { 189 LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate"); 190 super.onRequestComplete(); 191 } 192 } 193 else 194 { 195 if (LOG.isDebugEnabled()) 196 LOG.debug("WebdavListener:Not ready, calling super"); 197 super.onRequestComplete(); 198 } 199 } 200 else 201 { 202 super.onRequestComplete(); 203 } 204 } 205 206 207 208 209 /** 210 * walk through the steps to try and resolve missing parent collection issues via webdav 211 * 212 * TODO this really ought to use URI itself for this resolution 213 * 214 * @return 215 * @throws IOException 216 */ 217 private boolean resolveCollectionIssues() throws IOException 218 { 219 220 String uri = _exchange.getURI(); 221 String[] uriCollection = _exchange.getURI().split("/"); 222 int checkNum = uriCollection.length; 223 int rewind = 0; 224 225 String parentUri = URIUtil.parentPath( uri ); 226 while ( parentUri != null && !checkExists(parentUri) ) 227 { 228 ++rewind; 229 parentUri = URIUtil.parentPath( parentUri ); 230 } 231 232 // confirm webdav is supported for this collection 233 if ( checkWebdavSupported() ) 234 { 235 for (int i = 0; i < rewind;) 236 { 237 makeCollection(parentUri + "/" + uriCollection[checkNum - rewind - 1]); 238 parentUri = parentUri + "/" + uriCollection[checkNum - rewind - 1]; 239 --rewind; 240 } 241 } 242 else 243 { 244 return false; 245 } 246 247 return true; 248 } 249 250 private boolean checkExists( String uri ) throws IOException 251 { 252 if (uri == null) 253 { 254 System.out.println("have failed miserably"); 255 return false; 256 } 257 258 PropfindExchange propfindExchange = new PropfindExchange(); 259 propfindExchange.setAddress( _exchange.getAddress() ); 260 propfindExchange.setMethod( HttpMethods.GET ); // PROPFIND acts wonky, just use get 261 propfindExchange.setScheme( _exchange.getScheme() ); 262 propfindExchange.setEventListener( new SecurityListener( _destination, propfindExchange ) ); 263 propfindExchange.setConfigureListeners( false ); 264 propfindExchange.setRequestURI( uri ); 265 266 _destination.send( propfindExchange ); 267 268 try 269 { 270 propfindExchange.waitForDone(); 271 272 return propfindExchange.exists(); 273 } 274 catch ( InterruptedException ie ) 275 { 276 LOG.ignore( ie ); 277 return false; 278 } 279 } 280 281 private boolean makeCollection( String uri ) throws IOException 282 { 283 MkcolExchange mkcolExchange = new MkcolExchange(); 284 mkcolExchange.setAddress( _exchange.getAddress() ); 285 mkcolExchange.setMethod( "MKCOL " + uri + " HTTP/1.1" ); 286 mkcolExchange.setScheme( _exchange.getScheme() ); 287 mkcolExchange.setEventListener( new SecurityListener( _destination, mkcolExchange ) ); 288 mkcolExchange.setConfigureListeners( false ); 289 mkcolExchange.setRequestURI( uri ); 290 291 _destination.send( mkcolExchange ); 292 293 try 294 { 295 mkcolExchange.waitForDone(); 296 297 return mkcolExchange.exists(); 298 } 299 catch ( InterruptedException ie ) 300 { 301 LOG.ignore( ie ); 302 return false; 303 } 304 } 305 306 307 private boolean checkWebdavSupported() throws IOException 308 { 309 WebdavSupportedExchange supportedExchange = new WebdavSupportedExchange(); 310 supportedExchange.setAddress( _exchange.getAddress() ); 311 supportedExchange.setMethod( HttpMethods.OPTIONS ); 312 supportedExchange.setScheme( _exchange.getScheme() ); 313 supportedExchange.setEventListener( new SecurityListener( _destination, supportedExchange ) ); 314 supportedExchange.setConfigureListeners( false ); 315 supportedExchange.setRequestURI( _exchange.getURI() ); 316 317 _destination.send( supportedExchange ); 318 319 try 320 { 321 supportedExchange.waitTilCompletion(); 322 return supportedExchange.isWebdavSupported(); 323 } 324 catch (InterruptedException ie ) 325 { 326 LOG.ignore( ie ); 327 return false; 328 } 329 330 } 331 332} 333