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