xref: /trunk/main/ucb/source/ucp/webdav/CurlRequest.cxx (revision 51ba086b)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_webdav.hxx"
26 
27 #include "CurlRequest.hxx"
28 
29 using namespace ::com::sun::star;
30 using namespace http_dav_ucp;
31 
32 CurlRequest::CurlRequest( CURL *curl )
33     : curl( curl )
34     , curlUrl( NULL )
35     , requestHeaders( NULL )
36     , requestBody( NULL )
37     , requestBodyOffset( 0 )
38     , requestBodySize( 0 )
39     , useChunkedEncoding( false )
40     , provideCredentialsCallback( NULL )
41     , provideCredentialsUserdata( NULL )
42     , statusCode( 0 )
43     , responseBodyInputStream( new CurlInputStream() )
44 {
45     curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, Curl_HeaderReceived );
46     curl_easy_setopt( curl, CURLOPT_HEADERDATA, this );
47     curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, Curl_MoreBodyReceived );
48     curl_easy_setopt( curl, CURLOPT_WRITEDATA, this );
49 }
50 
51 CurlRequest::~CurlRequest()
52 {
53     if ( curlUrl != NULL )
54         curl_url_cleanup( curlUrl );
55     curl_easy_setopt( curl, CURLOPT_CURLU, NULL );
56 
57     curl_easy_setopt( curl, CURLOPT_HTTPHEADER, NULL );
58     if ( requestHeaders != NULL )
59         curl_slist_free_all( requestHeaders );
60     curl_easy_setopt( curl, CURLOPT_READFUNCTION, NULL );
61     curl_easy_setopt( curl, CURLOPT_READDATA, NULL );
62     curl_easy_setopt( curl, CURLOPT_INFILESIZE, -1 );
63     curl_easy_setopt( curl, CURLOPT_SEEKFUNCTION, NULL );
64     curl_easy_setopt( curl, CURLOPT_SEEKDATA, NULL );
65     curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, NULL );
66     curl_easy_setopt( curl, CURLOPT_HEADERDATA, NULL );
67     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, NULL );
68     curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, NULL );
69     curl_easy_setopt( curl, CURLOPT_WRITEDATA, NULL );
70 }
71 
72 void CurlRequest::addHeader( const rtl::OString &name, const rtl::OString &value) throw (DAVException)
73 {
74     rtl::OString line = name + ": " + value;
75     struct curl_slist *appended = curl_slist_append( requestHeaders, line.getStr() );
76     if ( appended != NULL )
77     {
78         requestHeaders = appended;
79         curl_easy_setopt( curl, CURLOPT_HTTPHEADER, requestHeaders );
80     }
81     else
82         throw DAVException( DAVException::DAV_SESSION_CREATE, rtl::OUString::createFromAscii( "Out of memory" ) );
83 }
84 
85 void CurlRequest::setRequestBody( const char *body, size_t size )
86 {
87     requestBody = body;
88     requestBodyOffset = 0;
89     requestBodySize = size;
90 
91     curl_easy_setopt( curl, CURLOPT_READFUNCTION, Curl_SendMoreBody );
92     curl_easy_setopt( curl, CURLOPT_READDATA, this );
93     curl_easy_setopt( curl, CURLOPT_SEEKFUNCTION, Curl_SeekCallback );
94     curl_easy_setopt( curl, CURLOPT_SEEKDATA, this );
95     if ( useChunkedEncoding )
96         addHeader( "Transfer-Encoding", "chunked" );
97     else
98         curl_easy_setopt( curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)size );
99 }
100 
101 int CurlRequest::Curl_SeekCallback( void *userdata, curl_off_t offset, int origin )
102 {
103     CurlRequest *request = static_cast< CurlRequest* >( userdata );
104     if ( origin == SEEK_SET )
105         request->requestBodyOffset = (size_t) offset;
106     else if ( origin == SEEK_CUR )
107         request->requestBodyOffset += (size_t) offset;
108     else if ( origin == SEEK_END )
109         request->requestBodyOffset += (size_t) ((curl_off_t)request->requestBodySize + offset);
110     else
111         return CURL_SEEKFUNC_CANTSEEK;
112     if ( request->requestBodyOffset > request->requestBodySize )
113         request->requestBodyOffset = request->requestBodySize;
114     return CURL_SEEKFUNC_OK;
115 }
116 
117 size_t CurlRequest::Curl_SendMoreBody( char *buffer, size_t size, size_t nitems, void *userdata )
118 {
119     CurlRequest *request = static_cast< CurlRequest* >( userdata );
120     OSL_TRACE("Curl_SendMoreBody");
121     return request->curlSendMoreBody( buffer, nitems );
122 }
123 
124 size_t CurlRequest::curlSendMoreBody( char *buffer, size_t len )
125 {
126     size_t bytesToSend = requestBodySize - requestBodyOffset;
127     if ( bytesToSend > len )
128         bytesToSend = len;
129     memcpy( buffer, requestBody, bytesToSend );
130     requestBodyOffset += bytesToSend;
131     return bytesToSend;
132 }
133 
134 void CurlRequest::setProvideCredentialsCallback( bool (*callback)(long statusCode, void *userdata) throw (DAVException), void *userdata )
135 {
136     provideCredentialsCallback = callback;
137     provideCredentialsUserdata = userdata;
138 }
139 
140 void CurlRequest::setURI( CurlUri uri, rtl::OUString path ) throw (DAVException)
141 {
142     if ( curlUrl != NULL )
143     {
144         curl_url_cleanup( curlUrl );
145         curlUrl = NULL;
146     }
147 
148     curlUrl = curl_url();
149     if ( curlUrl == NULL )
150         throw DAVException( DAVException::DAV_SESSION_CREATE, rtl::OUString::createFromAscii( "Out of memory" ) );
151 
152     if ( CurlUri( path ).GetHost().isEmpty() )
153     {
154         // "path" is really a path, not a URI
155         curl_url_set( curlUrl, CURLUPART_URL, rtl::OUStringToOString( uri.GetURI(), RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
156         curl_url_set( curlUrl, CURLUPART_PATH, rtl::OUStringToOString( path, RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
157     }
158     else
159     {
160         // The "path" is a full URI
161         curl_url_set( curlUrl, CURLUPART_URL, rtl::OUStringToOString( path, RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
162     }
163     curl_easy_setopt( curl, CURLOPT_CURLU, curlUrl );
164 }
165 
166 CURLcode CurlRequest::copy( CurlUri uri, rtl::OUString path ) throw(DAVException)
167 {
168     setURI( uri, path );
169     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
170     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "COPY" );
171     return perform();
172 }
173 
174 CURLcode CurlRequest::delete_( CurlUri uri, rtl::OUString path ) throw (DAVException)
175 {
176     setURI( uri, path );
177     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
178     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "DELETE" );
179     return perform();
180 }
181 
182 CURLcode CurlRequest::get( CurlUri uri, rtl::OUString path ) throw(DAVException)
183 {
184     setURI( uri, path );
185     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
186     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "GET" );
187     return perform();
188 }
189 
190 CURLcode CurlRequest::head( CurlUri uri, rtl::OUString path ) throw (DAVException)
191 {
192     setURI( uri, path );
193     curl_easy_setopt( curl, CURLOPT_NOBODY, 1L );
194     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "HEAD" );
195     return perform();
196 }
197 
198 CURLcode CurlRequest::lock( CurlUri uri, rtl::OUString path ) throw (DAVException)
199 {
200     setURI( uri, path );
201     curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
202     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "LOCK" );
203     return perform();
204 }
205 
206 CURLcode CurlRequest::mkcol( CurlUri uri, rtl::OUString path ) throw (DAVException)
207 {
208     setURI( uri, path );
209     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
210     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "MKCOL" );
211     return perform();
212 }
213 
214 CURLcode CurlRequest::move( CurlUri uri, rtl::OUString path ) throw (DAVException)
215 {
216     setURI( uri, path );
217     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
218     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "MOVE" );
219     return perform();
220 }
221 
222 CURLcode CurlRequest::post( CurlUri uri, rtl::OUString path ) throw (DAVException)
223 {
224     setURI( uri, path );
225     curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
226     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "POST" );
227     return perform();
228 }
229 
230 CURLcode CurlRequest::propfind( CurlUri uri, rtl::OUString path ) throw (DAVException)
231 {
232     setURI( uri, path );
233     curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
234     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PROPFIND" );
235     return perform();
236 }
237 
238 CURLcode CurlRequest::proppatch( CurlUri uri, rtl::OUString path ) throw (DAVException)
239 {
240     setURI( uri, path );
241     curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
242     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PROPPATCH" );
243     return perform();
244 }
245 
246 CURLcode CurlRequest::put( CurlUri uri, rtl::OUString path ) throw (DAVException)
247 {
248     setURI( uri, path );
249     curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
250     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PUT" );
251     return perform();
252 }
253 
254 CURLcode CurlRequest::unlock( CurlUri uri, rtl::OUString path ) throw (DAVException)
255 {
256     setURI( uri, path );
257     curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
258     curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "UNLOCK" );
259     return perform();
260 }
261 
262 CURLcode CurlRequest::perform() throw (DAVException)
263 {
264     CURLcode rc = curl_easy_perform( curl );
265     long statusCode = 0;
266     curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &statusCode );
267     if ( ( statusCode == 401 || statusCode == 407 ) && provideCredentialsCallback != NULL )
268     {
269         bool haveCredentials = provideCredentialsCallback( statusCode, provideCredentialsUserdata );
270         if ( haveCredentials )
271         {
272             // rewind body:
273             requestBodyOffset = 0;
274             // retry with credentials:
275             rc = curl_easy_perform( curl );
276             // If this was to authenticate with the proxy, we may need to authenticate
277             // with the destination host too:
278             if ( statusCode == 407 )
279             {
280                 curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &statusCode );
281                 if ( statusCode == 401 && provideCredentialsCallback != NULL )
282                 {
283                     haveCredentials = provideCredentialsCallback( statusCode, provideCredentialsUserdata );
284                     if ( haveCredentials )
285                     {
286                         // rewind body:
287                         requestBodyOffset = 0;
288                         // retry with credentials:
289                         rc = curl_easy_perform( curl );
290                     }
291                 }
292             }
293         }
294     }
295     return rc;
296 }
297 
298 size_t CurlRequest::Curl_HeaderReceived( char *buffer, size_t size, size_t nitems, void *userdata )
299 {
300     CurlRequest *request = static_cast< CurlRequest* >( userdata );
301     OSL_TRACE("Curl_HeaderReceived");
302     request->curlHeaderReceived( buffer, nitems );
303     return nitems;
304 }
305 
306 void CurlRequest::curlHeaderReceived( const char *buffer, size_t size )
307 {
308     rtl::OString lineCrLf( buffer, size );
309     sal_Int32 cr = lineCrLf.indexOf( "\r" );
310     if ( cr < 0 )
311         return;
312 
313     rtl::OString line = lineCrLf.copy( 0, cr );
314     if ( line.indexOf( "HTTP/" ) == 0 )
315     {
316         // Status line
317         // Throw away any response headers from a prior response:
318         responseHeaders.clear();
319         sal_Int32 idxFirstSpace = line.indexOf( ' ' );
320         if ( idxFirstSpace > 0 )
321         {
322             int idxSecondSpace = line.indexOf( ' ', idxFirstSpace + 1 );
323             if ( idxSecondSpace > 0 )
324             {
325                 reasonPhrase = line.copy( idxSecondSpace + 1 );
326                 statusCode = line.copy( idxFirstSpace + 1, idxSecondSpace - idxFirstSpace - 1 ).toInt32();
327             }
328             else
329             {
330                 reasonPhrase = "";
331                 statusCode = line.copy( idxFirstSpace + 1 ).toInt32();
332             }
333         }
334     }
335     else
336     {
337         // Header line
338         if ( line.getLength() == 0 )
339         {
340             // End of HTTP header
341             // Discard any intermediate body from 100 Trying or 401 Authentication required:
342             responseBodyInputStream = new CurlInputStream();
343             return;
344         }
345         sal_Int32 colon = line.indexOf( ':' );
346         if ( colon < 0 )
347         {
348             OSL_TRACE("Non-empty HTTP line without a ':'. Folded header deprecated by RFC 7230?");
349             return;
350         }
351         Header header;
352         header.name = line.copy( 0, colon ).toAsciiLowerCase();
353         header.value = line.copy( colon + 1 ).trim();
354         responseHeaders.push_back(header);
355     }
356 }
357 
358 const CurlRequest::Header *CurlRequest::findResponseHeader( const rtl::OString &name )
359 {
360     std::vector< CurlRequest::Header >::const_iterator it( responseHeaders.begin() );
361     const std::vector< CurlRequest::Header >::const_iterator end( responseHeaders.end() );
362     for ( ; it != end; it++ )
363     {
364         if ( name.equalsIgnoreAsciiCase( it->name ) )
365             return &(*it);
366     }
367     return NULL;
368 }
369 
370 void CurlRequest::saveResponseBodyTo( const uno::Reference< io::XOutputStream > & xOutStream)
371 {
372     xOutputStream = xOutStream;
373 }
374 
375 size_t CurlRequest::Curl_MoreBodyReceived( char *buffer, size_t size, size_t nitems, void *userdata )
376 {
377     CurlRequest *request = static_cast< CurlRequest* >( userdata );
378     request->curlMoreBodyReceived( buffer, nitems );
379     return nitems;
380 }
381 
382 void CurlRequest::curlMoreBodyReceived( const char *buffer, size_t size )
383 {
384     if ( xOutputStream.is() )
385     {
386         const uno::Sequence< sal_Int8 > aDataSeq( (sal_Int8 *)buffer, size );
387         xOutputStream->writeBytes( aDataSeq );
388     }
389     else if ( responseBodyInputStream.is() )
390         responseBodyInputStream->AddToStream( buffer, size );
391 }