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