OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
ScriptsHttpRequests.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Net;
32 using System.Net.Mail;
33 using System.Net.Security;
34 using System.Text;
35 using System.Threading;
36 using System.Security.Cryptography.X509Certificates;
37 using Nini.Config;
38 using OpenMetaverse;
39 using OpenSim.Framework;
40 using OpenSim.Framework.Servers;
41 using OpenSim.Framework.Servers.HttpServer;
42 using OpenSim.Region.Framework.Interfaces;
43 using OpenSim.Region.Framework.Scenes;
44 using Mono.Addins;
45 using Amib.Threading;
46 
47 /*****************************************************
48  *
49  * ScriptsHttpRequests
50  *
51  * Implements the llHttpRequest and http_response
52  * callback.
53  *
54  * Some stuff was already in LSLLongCmdHandler, and then
55  * there was this file with a stub class in it. So,
56  * I am moving some of the objects and functions out of
57  * LSLLongCmdHandler, such as the HttpRequestClass, the
58  * start and stop methods, and setting up pending and
59  * completed queues. These are processed in the
60  * LSLLongCmdHandler polling loop. Similiar to the
61  * XMLRPCModule, since that seems to work.
62  *
63  * //TODO
64  *
65  * This probably needs some throttling mechanism but
66  * it's wide open right now. This applies to both
67  * number of requests and data volume.
68  *
69  * Linden puts all kinds of header fields in the requests.
70  * Not doing any of that:
71  * User-Agent
72  * X-SecondLife-Shard
73  * X-SecondLife-Object-Name
74  * X-SecondLife-Object-Key
75  * X-SecondLife-Region
76  * X-SecondLife-Local-Position
77  * X-SecondLife-Local-Velocity
78  * X-SecondLife-Local-Rotation
79  * X-SecondLife-Owner-Name
80  * X-SecondLife-Owner-Key
81  *
82  * HTTPS support
83  *
84  * Configurable timeout?
85  * Configurable max response size?
86  * Configurable
87  *
88  * **************************************************/
89 
90 namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
91 {
92  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")]
94  {
95 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
96 
97  private object HttpListLock = new object();
98  private int httpTimeout = 30000;
99  private string m_name = "HttpScriptRequests";
100 
101  private OutboundUrlFilter m_outboundUrlFilter;
102  private string m_proxyurl = "";
103  private string m_proxyexcepts = "";
104 
105  // <request id, HttpRequestClass>
106  private Dictionary<UUID, HttpRequestClass> m_pendingRequests;
107  private Scene m_scene;
108  // private Queue<HttpRequestClass> rpcQueue = new Queue<HttpRequestClass>();
109  public static SmartThreadPool ThreadPool = null;
110 
112  {
113  ServicePointManager.ServerCertificateValidationCallback +=ValidateServerCertificate;
114  }
115 
116  public static bool ValidateServerCertificate(
117  object sender,
118  X509Certificate certificate,
119  X509Chain chain,
120  SslPolicyErrors sslPolicyErrors)
121  {
122  // If this is a web request we need to check the headers first
123  // We may want to ignore SSL
124  if (sender is HttpWebRequest)
125  {
126  HttpWebRequest Request = (HttpWebRequest)sender;
127  ServicePoint sp = Request.ServicePoint;
128 
129  // We don't case about encryption, get out of here
130  if (Request.Headers.Get("NoVerifyCert") != null)
131  {
132  return true;
133  }
134 
135  // If there was an upstream cert verification error, bail
136  if ((((int)sslPolicyErrors) & ~4) != 0)
137  return false;
138 
139  // Check for policy and execute it if defined
140 #pragma warning disable 0618
141  if (ServicePointManager.CertificatePolicy != null)
142  {
143  return ServicePointManager.CertificatePolicy.CheckValidationResult (sp, certificate, Request, 0);
144  }
145 #pragma warning restore 0618
146 
147  return true;
148  }
149 
150  // If it's not HTTP, trust .NET to check it
151  if ((((int)sslPolicyErrors) & ~4) != 0)
152  return false;
153 
154  return true;
155  }
156  #region IHttpRequestModule Members
157 
158  public UUID MakeHttpRequest(string url, string parameters, string body)
159  {
160  return UUID.Zero;
161  }
162 
164  uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body,
165  out HttpInitialRequestStatus status)
166  {
167  UUID reqID = UUID.Random();
169 
170  // Partial implementation: support for parameter flags needed
171  // see http://wiki.secondlife.com/wiki/LlHTTPRequest
172  //
173  // Parameters are expected in {key, value, ... , key, value}
174  if (parameters != null)
175  {
176  string[] parms = parameters.ToArray();
177  for (int i = 0; i < parms.Length; i += 2)
178  {
179  switch (Int32.Parse(parms[i]))
180  {
181  case (int)HttpRequestConstants.HTTP_METHOD:
182 
183  htc.HttpMethod = parms[i + 1];
184  break;
185 
186  case (int)HttpRequestConstants.HTTP_MIMETYPE:
187 
188  htc.HttpMIMEType = parms[i + 1];
189  break;
190 
191  case (int)HttpRequestConstants.HTTP_BODY_MAXLENGTH:
192 
193  int len;
194  if(int.TryParse(parms[i + 1], out len))
195  {
197  len = HttpRequestClass.HttpBodyMaxLenMAX;
198  else if(len < 64) //???
199  len = 64;
200  htc.HttpBodyMaxLen = len;
201  }
202  break;
203 
204  case (int)HttpRequestConstants.HTTP_VERIFY_CERT:
205  htc.HttpVerifyCert = (int.Parse(parms[i + 1]) != 0);
206  break;
207 
208  case (int)HttpRequestConstants.HTTP_VERBOSE_THROTTLE:
209 
210  // TODO implement me
211  break;
212 
213  case (int)HttpRequestConstants.HTTP_CUSTOM_HEADER:
214  //Parameters are in pairs and custom header takes
215  //arguments in pairs so adjust for header marker.
216  ++i;
217 
218  //Maximum of 8 headers are allowed based on the
219  //Second Life documentation for llHTTPRequest.
220  for (int count = 1; count <= 8; ++count)
221  {
222  //Not enough parameters remaining for a header?
223  if (parms.Length - i < 2)
224  break;
225 
226  //Have we reached the end of the list of headers?
227  //End is marked by a string with a single digit.
228  //We already know we have at least one parameter
229  //so it is safe to do this check at top of loop.
230  if (Char.IsDigit(parms[i][0]))
231  break;
232 
233  if (htc.HttpCustomHeaders == null)
234  htc.HttpCustomHeaders = new List<string>();
235 
236  htc.HttpCustomHeaders.Add(parms[i]);
237  htc.HttpCustomHeaders.Add(parms[i+1]);
238 
239  i += 2;
240  }
241  break;
242 
243  case (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE:
244  htc.HttpPragmaNoCache = (int.Parse(parms[i + 1]) != 0);
245  break;
246  }
247  }
248  }
249 
250  htc.RequestModule = this;
251  htc.LocalID = localID;
252  htc.ItemID = itemID;
253  htc.Url = url;
254  htc.ReqID = reqID;
255  htc.HttpTimeout = httpTimeout;
256  htc.OutboundBody = body;
257  htc.ResponseHeaders = headers;
258  htc.proxyurl = m_proxyurl;
259  htc.proxyexcepts = m_proxyexcepts;
260 
261  // Same number as default HttpWebRequest.MaximumAutomaticRedirections
262  htc.MaxRedirects = 50;
263 
264  if (StartHttpRequest(htc))
265  {
266  status = HttpInitialRequestStatus.OK;
267  return htc.ReqID;
268  }
269  else
270  {
271  status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER;
272  return UUID.Zero;
273  }
274  }
275 
280  public bool CheckAllowed(Uri url)
281  {
282  return m_outboundUrlFilter.CheckAllowed(url);
283  }
284 
286  {
287  if (!CheckAllowed(new Uri(req.Url)))
288  return false;
289 
290  lock (HttpListLock)
291  {
292  m_pendingRequests.Add(req.ReqID, req);
293  }
294 
295  req.Process();
296 
297  return true;
298  }
299 
300  public void StopHttpRequest(uint m_localID, UUID m_itemID)
301  {
302  if (m_pendingRequests != null)
303  {
304  lock (HttpListLock)
305  {
306  HttpRequestClass tmpReq;
307  if (m_pendingRequests.TryGetValue(m_itemID, out tmpReq))
308  {
309  tmpReq.Stop();
310  m_pendingRequests.Remove(m_itemID);
311  }
312  }
313  }
314  }
315 
316  /*
317  * TODO
318  * Not sure how important ordering is is here - the next first
319  * one completed in the list is returned, based soley on its list
320  * position, not the order in which the request was started or
321  * finished. I thought about setting up a queue for this, but
322  * it will need some refactoring and this works 'enough' right now
323  */
324 
326  {
327  lock (HttpListLock)
328  {
329  foreach (UUID luid in m_pendingRequests.Keys)
330  {
331  HttpRequestClass tmpReq;
332 
333  if (m_pendingRequests.TryGetValue(luid, out tmpReq))
334  {
335  if (tmpReq.Finished)
336  {
337  return tmpReq;
338  }
339  }
340  }
341  }
342  return null;
343  }
344 
345  public void RemoveCompletedRequest(UUID id)
346  {
347  lock (HttpListLock)
348  {
349  HttpRequestClass tmpReq;
350  if (m_pendingRequests.TryGetValue(id, out tmpReq))
351  {
352  tmpReq.Stop();
353  tmpReq = null;
354  m_pendingRequests.Remove(id);
355  }
356  }
357  }
358 
359  #endregion
360 
361  #region ISharedRegionModule Members
362 
363  public void Initialise(IConfigSource config)
364  {
365  m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
366  m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
367 
368  HttpRequestClass.HttpBodyMaxLenMAX = config.Configs["Network"].GetInt("HttpBodyMaxLenMAX", 16384);
369 
370 
371  m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config);
372  int maxThreads = 15;
373 
374  IConfig httpConfig = config.Configs["HttpRequestModule"];
375  if (httpConfig != null)
376  {
377  maxThreads = httpConfig.GetInt("MaxPoolThreads", maxThreads);
378  }
379 
380  m_pendingRequests = new Dictionary<UUID, HttpRequestClass>();
381 
382  // First instance sets this up for all sims
383  if (ThreadPool == null)
384  {
385  STPStartInfo startInfo = new STPStartInfo();
386  startInfo.IdleTimeout = 20000;
387  startInfo.MaxWorkerThreads = maxThreads;
388  startInfo.MinWorkerThreads = 1;
389  startInfo.ThreadPriority = ThreadPriority.BelowNormal;
390  startInfo.StartSuspended = true;
391  startInfo.ThreadPoolName = "ScriptsHttpReq";
392 
393  ThreadPool = new SmartThreadPool(startInfo);
394  ThreadPool.Start();
395  }
396  }
397 
398  public void AddRegion(Scene scene)
399  {
400  m_scene = scene;
401 
402  m_scene.RegisterModuleInterface<IHttpRequestModule>(this);
403  }
404 
405  public void RemoveRegion(Scene scene)
406  {
407  scene.UnregisterModuleInterface<IHttpRequestModule>(this);
408  if (scene == m_scene)
409  m_scene = null;
410  }
411 
412  public void PostInitialise()
413  {
414  }
415 
416  public void RegionLoaded(Scene scene)
417  {
418  }
419 
420  public void Close()
421  {
422  }
423 
424  public string Name
425  {
426  get { return m_name; }
427  }
428 
429  public Type ReplaceableInterface
430  {
431  get { return null; }
432  }
433 
434  #endregion
435  }
436 
438  {
439  // Constants for parameters
440  // public const int HTTP_BODY_MAXLENGTH = 2;
441  // public const int HTTP_METHOD = 0;
442  // public const int HTTP_MIMETYPE = 1;
443  // public const int HTTP_VERIFY_CERT = 3;
444  // public const int HTTP_VERBOSE_THROTTLE = 4;
445  // public const int HTTP_CUSTOM_HEADER = 5;
446  // public const int HTTP_PRAGMA_NO_CACHE = 6;
447 
451  public HttpRequestModule RequestModule { get; set; }
452 
453  private bool _finished;
454  public bool Finished
455  {
456  get { return _finished; }
457  }
458 
459  public static int HttpBodyMaxLenMAX = 16384;
460 
461  // Parameter members and default values
462  public int HttpBodyMaxLen = 2048;
463  public string HttpMethod = "GET";
464  public string HttpMIMEType = "text/plain;charset=utf-8";
465  public int HttpTimeout;
466  public bool HttpVerifyCert = true;
467  public IWorkItemResult WorkItem = null;
468 
469  //public bool HttpVerboseThrottle = true; // not implemented
470  public List<string> HttpCustomHeaders = null;
471  public bool HttpPragmaNoCache = true;
472 
473  // Request info
474  private UUID _itemID;
475  public UUID ItemID
476  {
477  get { return _itemID; }
478  set { _itemID = value; }
479  }
480  private uint _localID;
481  public uint LocalID
482  {
483  get { return _localID; }
484  set { _localID = value; }
485  }
486  public DateTime Next;
487  public string proxyurl;
488  public string proxyexcepts;
489 
493  public int Redirects { get; private set; }
494 
498  public int MaxRedirects { get; set; }
499 
500  public string OutboundBody;
501  private UUID _reqID;
502  public UUID ReqID
503  {
504  get { return _reqID; }
505  set { _reqID = value; }
506  }
507  public HttpWebRequest Request;
508  public string ResponseBody;
509  public List<string> ResponseMetadata;
510  public Dictionary<string, string> ResponseHeaders;
511  public int Status;
512  public string Url;
513 
514  public void Process()
515  {
516  _finished = false;
517 
519  WorkItem = HttpRequestModule.ThreadPool.QueueWorkItem(new WorkItemCallback(StpSendWrapper), null);
520  }
521 
522  private object StpSendWrapper(object o)
523  {
524  SendRequest();
525  return null;
526  }
527 
528  /*
529  * TODO: More work on the response codes. Right now
530  * returning 200 for success or 499 for exception
531  */
532 
533  public void SendRequest()
534  {
535  HttpWebResponse response = null;
536  Stream resStream = null;
537  byte[] buf = new byte[HttpBodyMaxLenMAX + 16];
538  string tempString = null;
539  int count = 0;
540 
541  try
542  {
543  Request = (HttpWebRequest)WebRequest.Create(Url);
544  Request.AllowAutoRedirect = false;
545 
546  //This works around some buggy HTTP Servers like Lighttpd
547  Request.ServicePoint.Expect100Continue = false;
548 
549  Request.Method = HttpMethod;
550  Request.ContentType = HttpMIMEType;
551 
552  if (!HttpVerifyCert)
553  {
554  // We could hijack Connection Group Name to identify
555  // a desired security exception. But at the moment we'll use a dummy header instead.
556  Request.Headers.Add("NoVerifyCert", "true");
557  }
558 // else
559 // {
560 // Request.ConnectionGroupName="Verify";
561 // }
562 
563  if (!HttpPragmaNoCache)
564  {
565  Request.Headers.Add("Pragma", "no-cache");
566  }
567 
568  if (HttpCustomHeaders != null)
569  {
570  for (int i = 0; i < HttpCustomHeaders.Count; i += 2)
571  Request.Headers.Add(HttpCustomHeaders[i],
572  HttpCustomHeaders[i+1]);
573  }
574 
575  if (!string.IsNullOrEmpty(proxyurl))
576  {
577  if (!string.IsNullOrEmpty(proxyexcepts))
578  {
579  string[] elist = proxyexcepts.Split(';');
580  Request.Proxy = new WebProxy(proxyurl, true, elist);
581  }
582  else
583  {
584  Request.Proxy = new WebProxy(proxyurl, true);
585  }
586  }
587 
588  foreach (KeyValuePair<string, string> entry in ResponseHeaders)
589  if (entry.Key.ToLower().Equals("user-agent"))
590  Request.UserAgent = entry.Value;
591  else
592  Request.Headers[entry.Key] = entry.Value;
593 
594  // Encode outbound data
595  if (!string.IsNullOrEmpty(OutboundBody))
596  {
597  byte[] data = Util.UTF8.GetBytes(OutboundBody);
598 
599  Request.ContentLength = data.Length;
600  using (Stream bstream = Request.GetRequestStream())
601  bstream.Write(data, 0, data.Length);
602  }
603 
604  Request.Timeout = HttpTimeout;
605  try
606  {
607  // execute the request
608  response = (HttpWebResponse) Request.GetResponse();
609  }
610  catch (WebException e)
611  {
612  if (e.Status != WebExceptionStatus.ProtocolError)
613  {
614  throw;
615  }
616  response = (HttpWebResponse)e.Response;
617  }
618 
619  Status = (int)response.StatusCode;
620 
621  resStream = response.GetResponseStream();
622  int totalBodyBytes = 0;
623  int maxBytes = HttpBodyMaxLen;
624  if(maxBytes > buf.Length)
625  maxBytes = buf.Length;
626 
627  // we need to read all allowed or UFT8 conversion may fail
628  do
629  {
630  // fill the buffer with data
631  count = resStream.Read(buf, totalBodyBytes, maxBytes - totalBodyBytes);
632  totalBodyBytes += count;
633  if (totalBodyBytes >= maxBytes)
634  break;
635 
636  } while (count > 0); // any more data to read?
637 
638  if(totalBodyBytes > 0)
639  {
640  tempString = Util.UTF8.GetString(buf, 0, totalBodyBytes);
641  ResponseBody = tempString.Replace("\r", "");
642  }
643  else
644  ResponseBody = "";
645  }
646  catch (WebException e)
647  {
648  if (e.Status == WebExceptionStatus.ProtocolError)
649  {
650  HttpWebResponse webRsp = (HttpWebResponse)((WebException)e).Response;
651  Status = (int)webRsp.StatusCode;
652  try
653  {
654  using (Stream responseStream = webRsp.GetResponseStream())
655  {
656  using (StreamReader reader = new StreamReader(responseStream))
657  ResponseBody = reader.ReadToEnd();
658  }
659  }
660  catch
661  {
662  ResponseBody = webRsp.StatusDescription;
663  }
664  }
665  else
666  {
667  Status = (int)OSHttpStatusCode.ClientErrorJoker;
668  ResponseBody = e.Message;
669  }
670 
671  if (ResponseBody == null)
672  ResponseBody = String.Empty;
673 
674  _finished = true;
675  return;
676  }
677  catch (Exception e)
678  {
679  // Don't crash on anything else
680  }
681  finally
682  {
683  if (resStream != null)
684  resStream.Close();
685  if (response != null)
686  response.Close();
687 
688 
689  // We need to resubmit
690  if (
691  (Status == (int)HttpStatusCode.MovedPermanently
692  || Status == (int)HttpStatusCode.Found
693  || Status == (int)HttpStatusCode.SeeOther
694  || Status == (int)HttpStatusCode.TemporaryRedirect))
695  {
696  if (Redirects >= MaxRedirects)
697  {
698  Status = (int)OSHttpStatusCode.ClientErrorJoker;
699  ResponseBody = "Number of redirects exceeded max redirects";
700  _finished = true;
701  }
702  else
703  {
704  string location = response.Headers["Location"];
705 
706  if (location == null)
707  {
708  Status = (int)OSHttpStatusCode.ClientErrorJoker;
709  ResponseBody = "HTTP redirect code but no location header";
710  _finished = true;
711  }
712  else if (!RequestModule.CheckAllowed(new Uri(location)))
713  {
714  Status = (int)OSHttpStatusCode.ClientErrorJoker;
715  ResponseBody = "URL from HTTP redirect blocked: " + location;
716  _finished = true;
717  }
718  else
719  {
720  Status = 0;
721  Url = response.Headers["Location"];
722  Redirects++;
723  ResponseBody = null;
724 
725 // m_log.DebugFormat("Redirecting to [{0}]", Url);
726 
727  Process();
728  }
729  }
730  }
731  else
732  {
733  _finished = true;
734  }
735  }
736 
737  if (ResponseBody == null)
738  ResponseBody = String.Empty;
739 
740  _finished = true;
741  }
742 
743  public void Stop()
744  {
745  try
746  {
747  if (!WorkItem.Cancel())
748  {
749  WorkItem.Cancel(true);
750  }
751  }
752  catch (Exception)
753  {
754  }
755  }
756  }
757 }
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
void StopHttpRequest(uint m_localID, UUID m_itemID)
Stop and remove all http requests for the given script.
bool CheckAllowed(Uri url)
Would a caller to this module be allowed to make a request to the given URL?
UUID StartHttpRequest(uint localID, UUID itemID, string url, List< string > parameters, Dictionary< string, string > headers, string body, out HttpInitialRequestStatus status)
Starts the http request.
void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
UUID MakeHttpRequest(string url, string parameters, string body)
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
Interactive OpenSim region server
Definition: OpenSim.cs:55
HttpInitialRequestStatus
The initial status of the request before it is placed on the wire.
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
OSHttpStatusCode
HTTP status codes (almost) as defined by W3C in http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html and IETF in http://tools.ietf.org/html/rfc6585
static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)