PageRenderTime 47ms CodeModel.GetById 1ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llmessage/llhttpclient.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 626 lines | 458 code | 90 blank | 78 comment | 47 complexity | 7a771359a2e1b9257a003c6a270a836b MD5 | raw file
  1/** 
  2 * @file llhttpclient.cpp
  3 * @brief Implementation of classes for making HTTP requests.
  4 *
  5 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "linden_common.h"
 28#include <openssl/x509_vfy.h>
 29#include "llhttpclient.h"
 30
 31#include "llassetstorage.h"
 32#include "lliopipe.h"
 33#include "llurlrequest.h"
 34#include "llbufferstream.h"
 35#include "llsdserialize.h"
 36#include "llvfile.h"
 37#include "llvfs.h"
 38#include "lluri.h"
 39
 40#include "message.h"
 41#include <curl/curl.h>
 42
 43
 44const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
 45LLURLRequest::SSLCertVerifyCallback LLHTTPClient::mCertVerifyCallback = NULL;
 46
 47////////////////////////////////////////////////////////////////////////////
 48
 49// Responder class moved to LLCurl
 50
 51namespace
 52{
 53	class LLHTTPClientURLAdaptor : public LLURLRequestComplete
 54	{
 55	public:
 56		LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder)
 57			: LLURLRequestComplete(), mResponder(responder), mStatus(499),
 58			  mReason("LLURLRequest complete w/no status")
 59		{
 60		}
 61		
 62		~LLHTTPClientURLAdaptor()
 63		{
 64		}
 65
 66		virtual void httpStatus(U32 status, const std::string& reason)
 67		{
 68			LLURLRequestComplete::httpStatus(status,reason);
 69
 70			mStatus = status;
 71			mReason = reason;
 72		}
 73
 74		virtual void complete(const LLChannelDescriptors& channels,
 75							  const buffer_ptr_t& buffer)
 76		{
 77			if (mResponder.get())
 78			{
 79				// Allow clients to parse headers before we attempt to parse
 80				// the body and provide completed/result/error calls.
 81				mResponder->completedHeader(mStatus, mReason, mHeaderOutput);
 82				mResponder->completedRaw(mStatus, mReason, channels, buffer);
 83			}
 84		}
 85		virtual void header(const std::string& header, const std::string& value)
 86		{
 87			mHeaderOutput[header] = value;
 88		}
 89
 90	private:
 91		LLCurl::ResponderPtr mResponder;
 92		U32 mStatus;
 93		std::string mReason;
 94		LLSD mHeaderOutput;
 95	};
 96	
 97	class Injector : public LLIOPipe
 98	{
 99	public:
100		virtual const char* contentType() = 0;
101	};
102
103	class LLSDInjector : public Injector
104	{
105	public:
106		LLSDInjector(const LLSD& sd) : mSD(sd) {}
107		virtual ~LLSDInjector() {}
108
109		const char* contentType() { return "application/llsd+xml"; }
110
111		virtual EStatus process_impl(const LLChannelDescriptors& channels,
112			buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
113		{
114			LLBufferStream ostream(channels, buffer.get());
115			LLSDSerialize::toXML(mSD, ostream);
116			eos = true;
117			return STATUS_DONE;
118		}
119
120		const LLSD mSD;
121	};
122
123	class RawInjector : public Injector
124	{
125	public:
126		RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {}
127		virtual ~RawInjector() {delete mData;}
128
129		const char* contentType() { return "application/octet-stream"; }
130
131		virtual EStatus process_impl(const LLChannelDescriptors& channels,
132			buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
133		{
134			LLBufferStream ostream(channels, buffer.get());
135			ostream.write((const char *)mData, mSize);  // hopefully chars are always U8s
136			eos = true;
137			return STATUS_DONE;
138		}
139
140		const U8* mData;
141		S32 mSize;
142	};
143	
144	class FileInjector : public Injector
145	{
146	public:
147		FileInjector(const std::string& filename) : mFilename(filename) {}
148		virtual ~FileInjector() {}
149
150		const char* contentType() { return "application/octet-stream"; }
151
152		virtual EStatus process_impl(const LLChannelDescriptors& channels,
153			buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
154		{
155			LLBufferStream ostream(channels, buffer.get());
156
157			llifstream fstream(mFilename, std::iostream::binary | std::iostream::out);
158			if(fstream.is_open())
159			{
160				fstream.seekg(0, std::ios::end);
161				U32 fileSize = fstream.tellg();
162				fstream.seekg(0, std::ios::beg);
163				std::vector<char> fileBuffer(fileSize);
164				fstream.read(&fileBuffer[0], fileSize);
165				ostream.write(&fileBuffer[0], fileSize);
166				fstream.close();
167				eos = true;
168				return STATUS_DONE;
169			}
170			
171			return STATUS_ERROR;
172		}
173
174		const std::string mFilename;
175	};
176	
177	class VFileInjector : public Injector
178	{
179	public:
180		VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {}
181		virtual ~VFileInjector() {}
182
183		const char* contentType() { return "application/octet-stream"; }
184
185		virtual EStatus process_impl(const LLChannelDescriptors& channels,
186			buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
187		{
188			LLBufferStream ostream(channels, buffer.get());
189			
190			LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ);
191			S32 fileSize = vfile.getSize();
192			U8* fileBuffer;
193			fileBuffer = new U8 [fileSize];
194            vfile.read(fileBuffer, fileSize);
195            ostream.write((char*)fileBuffer, fileSize);
196			delete [] fileBuffer;
197			eos = true;
198			return STATUS_DONE;
199		}
200
201		const LLUUID mUUID;
202		LLAssetType::EType mAssetType;
203	};
204
205	
206	LLPumpIO* theClientPump = NULL;
207}
208
209void LLHTTPClient::setCertVerifyCallback(LLURLRequest::SSLCertVerifyCallback callback)
210{
211	LLHTTPClient::mCertVerifyCallback = callback;
212}
213
214static void request(
215	const std::string& url,
216	LLURLRequest::ERequestAction method,
217	Injector* body_injector,
218	LLCurl::ResponderPtr responder,
219	const F32 timeout = HTTP_REQUEST_EXPIRY_SECS,
220	const LLSD& headers = LLSD()
221    )
222{
223	if (!LLHTTPClient::hasPump())
224	{
225		responder->completed(U32_MAX, "No pump", LLSD());
226		return;
227	}
228	LLPumpIO::chain_t chain;
229
230	LLURLRequest* req = new LLURLRequest(method, url);
231	if(!req->isValid())//failed
232	{
233		delete req ;
234		return ;
235	}
236
237	req->setSSLVerifyCallback(LLHTTPClient::getCertVerifyCallback(), (void *)req);
238
239	
240	lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " "
241		<< headers << llendl;
242
243	// Insert custom headers if the caller sent any
244	if (headers.isMap())
245	{
246		if (headers.has("Cookie"))
247		{
248			req->allowCookies();
249		}
250
251        LLSD::map_const_iterator iter = headers.beginMap();
252        LLSD::map_const_iterator end  = headers.endMap();
253
254        for (; iter != end; ++iter)
255        {
256            std::ostringstream header;
257            //if the header is "Pragma" with no value
258            //the caller intends to force libcurl to drop
259            //the Pragma header it so gratuitously inserts
260            //Before inserting the header, force libcurl
261            //to not use the proxy (read: llurlrequest.cpp)
262			static const std::string PRAGMA("Pragma");
263			if ((iter->first == PRAGMA) && (iter->second.asString().empty()))
264            {
265                req->useProxy(false);
266            }
267            header << iter->first << ": " << iter->second.asString() ;
268            lldebugs << "header = " << header.str() << llendl;
269            req->addHeader(header.str().c_str());
270        }
271    }
272
273	// Check to see if we have already set Accept or not. If no one
274	// set it, set it to application/llsd+xml since that's what we
275	// almost always want.
276	if( method != LLURLRequest::HTTP_PUT && method != LLURLRequest::HTTP_POST )
277	{
278		static const std::string ACCEPT("Accept");
279		if(!headers.has(ACCEPT))
280		{
281			req->addHeader("Accept: application/llsd+xml");
282		}
283	}
284
285	if (responder)
286	{
287		responder->setURL(url);
288	}
289
290	req->setCallback(new LLHTTPClientURLAdaptor(responder));
291
292	if (method == LLURLRequest::HTTP_POST  &&  gMessageSystem)
293	{
294		req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d",
295								gMessageSystem->mPort).c_str());
296   	}
297
298	if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST)
299	{
300		static const std::string CONTENT_TYPE("Content-Type");
301		if(!headers.has(CONTENT_TYPE))
302		{
303			// If the Content-Type header was passed in, it has
304			// already been added as a header through req->addHeader
305			// in the loop above. We defer to the caller's wisdom, but
306			// if they did not specify a Content-Type, then ask the
307			// injector.
308			req->addHeader(
309				llformat(
310					"Content-Type: %s",
311					body_injector->contentType()).c_str());
312		}
313   		chain.push_back(LLIOPipe::ptr_t(body_injector));
314	}
315
316	chain.push_back(LLIOPipe::ptr_t(req));
317
318	theClientPump->addChain(chain, timeout);
319}
320
321
322void LLHTTPClient::getByteRange(
323	const std::string& url,
324	S32 offset,
325	S32 bytes,
326	ResponderPtr responder,
327	const LLSD& hdrs,
328	const F32 timeout)
329{
330	LLSD headers = hdrs;
331	if(offset > 0 || bytes > 0)
332	{
333		std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1);
334		headers["Range"] = range;
335	}
336    request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers);
337}
338
339void LLHTTPClient::head(
340	const std::string& url,
341	ResponderPtr responder,
342	const LLSD& headers,
343	const F32 timeout)
344{
345	request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers);
346}
347
348void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
349{
350	request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers);
351}
352void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
353{
354	request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers);
355}
356void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout)
357{
358	getHeaderOnly(url, responder, LLSD(), timeout);
359}
360
361void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout)
362{
363	LLURI uri;
364	
365	uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query);
366	get(uri.asString(), responder, headers, timeout);
367}
368
369// A simple class for managing data returned from a curl http request.
370class LLHTTPBuffer
371{
372public:
373	LLHTTPBuffer() { }
374
375	static size_t curl_write( void *ptr, size_t size, size_t nmemb, void *user_data)
376	{
377		LLHTTPBuffer* self = (LLHTTPBuffer*)user_data;
378		
379		size_t bytes = (size * nmemb);
380		self->mBuffer.append((char*)ptr,bytes);
381		return nmemb;
382	}
383
384	LLSD asLLSD()
385	{
386		LLSD content;
387
388		if (mBuffer.empty()) return content;
389		
390		std::istringstream istr(mBuffer);
391		LLSDSerialize::fromXML(content, istr);
392		return content;
393	}
394
395	std::string asString()
396	{
397		return mBuffer;
398	}
399
400private:
401	std::string mBuffer;
402};
403
404// These calls are blocking! This is usually bad, unless you're a dataserver. Then it's awesome.
405
406/**
407	@brief does a blocking request on the url, returning the data or bad status.
408
409	@param url URI to verb on.
410	@param method the verb to hit the URI with.
411	@param body the body of the call (if needed - for instance not used for GET and DELETE, but is for POST and PUT)
412	@param headers HTTP headers to use for the request.
413	@param timeout Curl timeout to use. Defaults to 5. Rationale:
414	Without this timeout, blockingGet() calls have been observed to take
415	up to 90 seconds to complete.  Users of blockingGet() already must 
416	check the HTTP return code for validity, so this will not introduce
417	new errors.  A 5 second timeout will succeed > 95% of the time (and 
418	probably > 99% of the time) based on my statistics. JC
419
420	@returns an LLSD map: {status: integer, body: map}
421  */
422static LLSD blocking_request(
423	const std::string& url,
424	LLURLRequest::ERequestAction method,
425	const LLSD& body,
426	const LLSD& headers = LLSD(),
427	const F32 timeout = 5
428)
429{
430	lldebugs << "blockingRequest of " << url << llendl;
431	char curl_error_buffer[CURL_ERROR_SIZE] = "\0";
432	CURL* curlp = LLCurl::newEasyHandle();
433	llassert_always(curlp != NULL) ;
434
435	LLHTTPBuffer http_buffer;
436	std::string body_str;
437	
438	// other request method checks root cert first, we skip?
439
440	// Apply configured proxy settings
441	LLProxy::getInstance()->applyProxySettings(curlp);
442	
443	// * Set curl handle options
444	curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1);	// don't use SIGALRM for timeouts
445	curl_easy_setopt(curlp, CURLOPT_TIMEOUT, timeout);	// seconds, see warning at top of function.
446	curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
447	curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
448	curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
449	curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
450
451	// * Setup headers (don't forget to free them after the call!)
452	curl_slist* headers_list = NULL;
453	if (headers.isMap())
454	{
455		LLSD::map_const_iterator iter = headers.beginMap();
456		LLSD::map_const_iterator end  = headers.endMap();
457		for (; iter != end; ++iter)
458		{
459			std::ostringstream header;
460			header << iter->first << ": " << iter->second.asString() ;
461			lldebugs << "header = " << header.str() << llendl;
462			headers_list = curl_slist_append(headers_list, header.str().c_str());
463		}
464	}
465	
466	// * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy)
467	if (method == LLURLRequest::HTTP_GET)
468	{
469		curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1);
470	}
471	else if (method == LLURLRequest::HTTP_POST)
472	{
473		curl_easy_setopt(curlp, CURLOPT_POST, 1);
474		//serialize to ostr then copy to str - need to because ostr ptr is unstable :(
475		std::ostringstream ostr;
476		LLSDSerialize::toXML(body, ostr);
477		body_str = ostr.str();
478		curl_easy_setopt(curlp, CURLOPT_POSTFIELDS, body_str.c_str());
479		//copied from PHP libs, correct?
480		headers_list = curl_slist_append(headers_list, "Content-Type: application/llsd+xml");
481
482		// copied from llurlrequest.cpp
483		// it appears that apache2.2.3 or django in etch is busted. If
484		// we do not clear the expect header, we get a 500. May be
485		// limited to django/mod_wsgi.
486		headers_list = curl_slist_append(headers_list, "Expect:");
487	}
488	
489	// * Do the action using curl, handle results
490	lldebugs << "HTTP body: " << body_str << llendl;
491	headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml");
492	CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, headers_list);
493	if ( curl_result != CURLE_OK )
494	{
495		llinfos << "Curl is hosed - can't add headers" << llendl;
496	}
497
498	LLSD response = LLSD::emptyMap();
499	S32 curl_success = curl_easy_perform(curlp);
500	S32 http_status = 499;
501	curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &http_status);
502	response["status"] = http_status;
503	// if we get a non-404 and it's not a 200 OR maybe it is but you have error bits,
504	if ( http_status != 404 && (http_status != 200 || curl_success != 0) )
505	{
506		// We expect 404s, don't spam for them.
507		llwarns << "CURL REQ URL: " << url << llendl;
508		llwarns << "CURL REQ METHOD TYPE: " << method << llendl;
509		llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl;
510		llwarns << "CURL REQ BODY: " << body_str << llendl;
511		llwarns << "CURL HTTP_STATUS: " << http_status << llendl;
512		llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
513		llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl;
514		response["body"] = http_buffer.asString();
515	}
516	else
517	{
518		response["body"] = http_buffer.asLLSD();
519		lldebugs << "CURL response: " << http_buffer.asString() << llendl;
520	}
521	
522	if(headers_list)
523	{	// free the header list  
524		curl_slist_free_all(headers_list); 
525	}
526
527	// * Cleanup
528	LLCurl::deleteEasyHandle(curlp);
529	return response;
530}
531
532LLSD LLHTTPClient::blockingGet(const std::string& url)
533{
534	return blocking_request(url, LLURLRequest::HTTP_GET, LLSD());
535}
536
537LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body)
538{
539	return blocking_request(url, LLURLRequest::HTTP_POST, body);
540}
541
542void LLHTTPClient::put(
543	const std::string& url,
544	const LLSD& body,
545	ResponderPtr responder,
546	const LLSD& headers,
547	const F32 timeout)
548{
549	request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout, headers);
550}
551
552void LLHTTPClient::post(
553	const std::string& url,
554	const LLSD& body,
555	ResponderPtr responder,
556	const LLSD& headers,
557	const F32 timeout)
558{
559	request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout, headers);
560}
561
562void LLHTTPClient::postRaw(
563	const std::string& url,
564	const U8* data,
565	S32 size,
566	ResponderPtr responder,
567	const LLSD& headers,
568	const F32 timeout)
569{
570	request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout, headers);
571}
572
573void LLHTTPClient::postFile(
574	const std::string& url,
575	const std::string& filename,
576	ResponderPtr responder,
577	const LLSD& headers,
578	const F32 timeout)
579{
580	request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout, headers);
581}
582
583void LLHTTPClient::postFile(
584	const std::string& url,
585	const LLUUID& uuid,
586	LLAssetType::EType asset_type,
587	ResponderPtr responder,
588	const LLSD& headers,
589	const F32 timeout)
590{
591	request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout, headers);
592}
593
594// static
595void LLHTTPClient::del(
596	const std::string& url,
597	ResponderPtr responder,
598	const LLSD& headers,
599	const F32 timeout)
600{
601	request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout, headers);
602}
603
604// static
605void LLHTTPClient::move(
606	const std::string& url,
607	const std::string& destination,
608	ResponderPtr responder,
609	const LLSD& hdrs,
610	const F32 timeout)
611{
612	LLSD headers = hdrs;
613	headers["Destination"] = destination;
614	request(url, LLURLRequest::HTTP_MOVE, NULL, responder, timeout, headers);
615}
616
617
618void LLHTTPClient::setPump(LLPumpIO& pump)
619{
620	theClientPump = &pump;
621}
622
623bool LLHTTPClient::hasPump()
624{
625	return theClientPump != NULL;
626}