opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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.Threading;
30 using System.Collections.Generic;
31 using System.Collections;
32 using System.Reflection;
33 using log4net;
34 using Mono.Addins;
35 using Nini.Config;
36 using OpenMetaverse;
37 using OpenSim.Framework;
38 using OpenSim.Framework.Servers;
39 using OpenSim.Framework.Servers.HttpServer;
40 using OpenSim.Region.Framework.Interfaces;
41 using OpenSim.Region.Framework.Scenes;
42  
43 namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
44 {
45 /// <summary>
46 /// Data describing an external URL set up by a script.
47 /// </summary>
48 public class UrlData
49 {
50 /// <summary>
51 /// Scene object part hosting the script
52 /// </summary>
53 public UUID hostID;
54  
55 /// <summary>
56 /// The item ID of the script that requested the URL.
57 /// </summary>
58 public UUID itemID;
59  
60 /// <summary>
61 /// The script engine that runs the script.
62 /// </summary>
63 public IScriptModule engine;
64  
65 /// <summary>
66 /// The generated URL.
67 /// </summary>
68 public string url;
69  
70 /// <summary>
71 /// The random UUID component of the generated URL.
72 /// </summary>
73 public UUID urlcode;
74  
75 /// <summary>
76 /// The external requests currently being processed or awaiting retrieval for this URL.
77 /// </summary>
78 public Dictionary<UUID, RequestData> requests;
79 }
80  
81 public class RequestData
82 {
83 public UUID requestID;
84 public Dictionary<string, string> headers;
85 public string body;
86 public int responseCode;
87 public string responseBody;
88 public string responseType = "text/plain";
89 //public ManualResetEvent ev;
90 public bool requestDone;
91 public int startTime;
92 public string uri;
93 }
94  
95 /// <summary>
96 /// This module provides external URLs for in-world scripts.
97 /// </summary>
98 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UrlModule")]
99 public class UrlModule : ISharedRegionModule, IUrlModule
100 {
101 private static readonly ILog m_log =
102 LogManager.GetLogger(
103 MethodBase.GetCurrentMethod().DeclaringType);
104  
105 /// <summary>
106 /// Indexs the URL request metadata (which script requested it, outstanding requests, etc.) by the request ID
107 /// randomly generated when a request is received for this URL.
108 /// </summary>
109 /// <remarks>
110 /// Manipulation or retrieval from this dictionary must be locked on m_UrlMap to preserve consistency with
111 /// m_UrlMap
112 /// </remarks>
113 private Dictionary<UUID, UrlData> m_RequestMap = new Dictionary<UUID, UrlData>();
114  
115 /// <summary>
116 /// Indexs the URL request metadata (which script requested it, outstanding requests, etc.) by the full URL
117 /// </summary>
118 private Dictionary<string, UrlData> m_UrlMap = new Dictionary<string, UrlData>();
119  
120 private uint m_HttpsPort = 0;
121 private IHttpServer m_HttpServer = null;
122 private IHttpServer m_HttpsServer = null;
123  
124 public string ExternalHostNameForLSL { get; private set; }
125  
126 /// <summary>
127 /// The default maximum number of urls
128 /// </summary>
129 public const int DefaultTotalUrls = 100;
130  
131 /// <summary>
132 /// Maximum number of external urls that can be set up by this module.
133 /// </summary>
134 public int TotalUrls { get; set; }
135  
136 public Type ReplaceableInterface
137 {
138 get { return null; }
139 }
140  
141 public string Name
142 {
143 get { return "UrlModule"; }
144 }
145  
146 public void Initialise(IConfigSource config)
147 {
148 IConfig networkConfig = config.Configs["Network"];
149  
150 if (networkConfig != null)
151 {
152 ExternalHostNameForLSL = config.Configs["Network"].GetString("ExternalHostNameForLSL", null);
153  
154 bool ssl_enabled = config.Configs["Network"].GetBoolean("https_listener", false);
155  
156 if (ssl_enabled)
157 m_HttpsPort = (uint)config.Configs["Network"].GetInt("https_port", (int)m_HttpsPort);
158 }
159  
160 if (ExternalHostNameForLSL == null)
161 ExternalHostNameForLSL = System.Environment.MachineName;
162  
163 IConfig llFunctionsConfig = config.Configs["LL-Functions"];
164  
165 if (llFunctionsConfig != null)
166 TotalUrls = llFunctionsConfig.GetInt("max_external_urls_per_simulator", DefaultTotalUrls);
167 else
168 TotalUrls = DefaultTotalUrls;
169 }
170  
171 public void PostInitialise()
172 {
173 }
174  
175 public void AddRegion(Scene scene)
176 {
177 if (m_HttpServer == null)
178 {
179 // There can only be one
180 //
181 m_HttpServer = MainServer.Instance;
182 //
183 // We can use the https if it is enabled
184 if (m_HttpsPort > 0)
185 {
186 m_HttpsServer = MainServer.GetHttpServer(m_HttpsPort);
187 }
188 }
189  
190 scene.RegisterModuleInterface<IUrlModule>(this);
191  
192 scene.EventManager.OnScriptReset += OnScriptReset;
193 }
194  
195 public void RegionLoaded(Scene scene)
196 {
197 IScriptModule[] scriptModules = scene.RequestModuleInterfaces<IScriptModule>();
198 foreach (IScriptModule scriptModule in scriptModules)
199 {
200 scriptModule.OnScriptRemoved += ScriptRemoved;
201 scriptModule.OnObjectRemoved += ObjectRemoved;
202 }
203 }
204  
205 public void RemoveRegion(Scene scene)
206 {
207 }
208  
209 public void Close()
210 {
211 }
212  
213 public UUID RequestURL(IScriptModule engine, SceneObjectPart host, UUID itemID)
214 {
215 UUID urlcode = UUID.Random();
216  
217 lock (m_UrlMap)
218 {
219 if (m_UrlMap.Count >= TotalUrls)
220 {
221 engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" });
222 return urlcode;
223 }
224 string url = "http://" + ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + "/lslhttp/" + urlcode.ToString() + "/";
225  
226 UrlData urlData = new UrlData();
227 urlData.hostID = host.UUID;
228 urlData.itemID = itemID;
229 urlData.engine = engine;
230 urlData.url = url;
231 urlData.urlcode = urlcode;
232 urlData.requests = new Dictionary<UUID, RequestData>();
233  
234 m_UrlMap[url] = urlData;
235  
236 string uri = "/lslhttp/" + urlcode.ToString() + "/";
237  
238 PollServiceEventArgs args
239 = new PollServiceEventArgs(HttpRequestHandler, uri, HasEvents, GetEvents, NoEvents, urlcode, 25000);
240 args.Type = PollServiceEventArgs.EventType.LslHttp;
241 m_HttpServer.AddPollServiceHTTPHandler(uri, args);
242  
243 m_log.DebugFormat(
244 "[URL MODULE]: Set up incoming request url {0} for {1} in {2} {3}",
245 uri, itemID, host.Name, host.LocalId);
246  
247 engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_GRANTED", url });
248 }
249  
250 return urlcode;
251 }
252  
253 public UUID RequestSecureURL(IScriptModule engine, SceneObjectPart host, UUID itemID)
254 {
255 UUID urlcode = UUID.Random();
256  
257 if (m_HttpsServer == null)
258 {
259 engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" });
260 return urlcode;
261 }
262  
263 lock (m_UrlMap)
264 {
265 if (m_UrlMap.Count >= TotalUrls)
266 {
267 engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" });
268 return urlcode;
269 }
270 string url = "https://" + ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + "/lslhttps/" + urlcode.ToString() + "/";
271  
272 UrlData urlData = new UrlData();
273 urlData.hostID = host.UUID;
274 urlData.itemID = itemID;
275 urlData.engine = engine;
276 urlData.url = url;
277 urlData.urlcode = urlcode;
278 urlData.requests = new Dictionary<UUID, RequestData>();
279  
280 m_UrlMap[url] = urlData;
281  
282 string uri = "/lslhttps/" + urlcode.ToString() + "/";
283  
284 PollServiceEventArgs args
285 = new PollServiceEventArgs(HttpRequestHandler, uri, HasEvents, GetEvents, NoEvents, urlcode, 25000);
286 args.Type = PollServiceEventArgs.EventType.LslHttp;
287 m_HttpsServer.AddPollServiceHTTPHandler(uri, args);
288  
289 m_log.DebugFormat(
290 "[URL MODULE]: Set up incoming secure request url {0} for {1} in {2} {3}",
291 uri, itemID, host.Name, host.LocalId);
292  
293 engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_GRANTED", url });
294 }
295  
296 return urlcode;
297 }
298  
299 public void ReleaseURL(string url)
300 {
301 lock (m_UrlMap)
302 {
303 UrlData data;
304  
305 if (!m_UrlMap.TryGetValue(url, out data))
306 {
307 return;
308 }
309  
310 foreach (UUID req in data.requests.Keys)
311 m_RequestMap.Remove(req);
312  
313 m_log.DebugFormat(
314 "[URL MODULE]: Releasing url {0} for {1} in {2}",
315 url, data.itemID, data.hostID);
316  
317 RemoveUrl(data);
318 m_UrlMap.Remove(url);
319 }
320 }
321  
322 public void HttpContentType(UUID request, string type)
323 {
324 lock (m_UrlMap)
325 {
326 if (m_RequestMap.ContainsKey(request))
327 {
328 UrlData urlData = m_RequestMap[request];
329 urlData.requests[request].responseType = type;
330 }
331 else
332 {
333 m_log.Info("[HttpRequestHandler] There is no http-in request with id " + request.ToString());
334 }
335 }
336 }
337  
338 public void HttpResponse(UUID request, int status, string body)
339 {
340 lock (m_UrlMap)
341 {
342 if (m_RequestMap.ContainsKey(request))
343 {
344 UrlData urlData = m_RequestMap[request];
345 string responseBody = body;
346 if (urlData.requests[request].responseType.Equals("text/plain"))
347 {
348 string value;
349 if (urlData.requests[request].headers.TryGetValue("user-agent", out value))
350 {
351 if (value != null && value.IndexOf("MSIE") >= 0)
352 {
353 // wrap the html escaped response if the target client is IE
354 // It ignores "text/plain" if the body is html
355 responseBody = "<html>" + System.Web.HttpUtility.HtmlEncode(body) + "</html>";
356 }
357 }
358 }
359 urlData.requests[request].responseCode = status;
360 urlData.requests[request].responseBody = responseBody;
361 //urlData.requests[request].ev.Set();
362 urlData.requests[request].requestDone =true;
363 }
364 else
365 {
366 m_log.Info("[HttpRequestHandler] There is no http-in request with id " + request.ToString());
367 }
368 }
369 }
370  
371 public string GetHttpHeader(UUID requestId, string header)
372 {
373 lock (m_UrlMap)
374 {
375 if (m_RequestMap.ContainsKey(requestId))
376 {
377 UrlData urlData = m_RequestMap[requestId];
378 string value;
379 if (urlData.requests[requestId].headers.TryGetValue(header, out value))
380 return value;
381 }
382 else
383 {
384 m_log.Warn("[HttpRequestHandler] There was no http-in request with id " + requestId);
385 }
386 }
387  
388 return String.Empty;
389 }
390  
391 public int GetFreeUrls()
392 {
393 lock (m_UrlMap)
394 return TotalUrls - m_UrlMap.Count;
395 }
396  
397 public void ScriptRemoved(UUID itemID)
398 {
399 // m_log.DebugFormat("[URL MODULE]: Removing script {0}", itemID);
400  
401 lock (m_UrlMap)
402 {
403 List<string> removeURLs = new List<string>();
404  
405 foreach (KeyValuePair<string, UrlData> url in m_UrlMap)
406 {
407 if (url.Value.itemID == itemID)
408 {
409 RemoveUrl(url.Value);
410 removeURLs.Add(url.Key);
411 foreach (UUID req in url.Value.requests.Keys)
412 m_RequestMap.Remove(req);
413 }
414 }
415  
416 foreach (string urlname in removeURLs)
417 m_UrlMap.Remove(urlname);
418 }
419 }
420  
421 public void ObjectRemoved(UUID objectID)
422 {
423 lock (m_UrlMap)
424 {
425 List<string> removeURLs = new List<string>();
426  
427 foreach (KeyValuePair<string, UrlData> url in m_UrlMap)
428 {
429 if (url.Value.hostID == objectID)
430 {
431 RemoveUrl(url.Value);
432 removeURLs.Add(url.Key);
433  
434 foreach (UUID req in url.Value.requests.Keys)
435 m_RequestMap.Remove(req);
436 }
437 }
438  
439 foreach (string urlname in removeURLs)
440 m_UrlMap.Remove(urlname);
441 }
442 }
443  
444 private void RemoveUrl(UrlData data)
445 {
446 m_HttpServer.RemoveHTTPHandler("", "/lslhttp/" + data.urlcode.ToString() + "/");
447 }
448  
449 private Hashtable NoEvents(UUID requestID, UUID sessionID)
450 {
451 Hashtable response = new Hashtable();
452 UrlData urlData;
453  
454 lock (m_UrlMap)
455 {
456 // We need to return a 404 here in case the request URL was removed at exactly the same time that a
457 // request was made. In this case, the request thread can outrace llRemoveURL() and still be polling
458 // for the request ID.
459 if (!m_RequestMap.ContainsKey(requestID))
460 {
461 response["int_response_code"] = 404;
462 response["str_response_string"] = "";
463 response["keepalive"] = false;
464 response["reusecontext"] = false;
465  
466 return response;
467 }
468  
469 urlData = m_RequestMap[requestID];
470  
471 if (System.Environment.TickCount - urlData.requests[requestID].startTime > 25000)
472 {
473 response["int_response_code"] = 500;
474 response["str_response_string"] = "Script timeout";
475 response["content_type"] = "text/plain";
476 response["keepalive"] = false;
477 response["reusecontext"] = false;
478  
479 //remove from map
480 urlData.requests.Remove(requestID);
481 m_RequestMap.Remove(requestID);
482  
483 return response;
484 }
485 }
486  
487 return response;
488 }
489  
490 private bool HasEvents(UUID requestID, UUID sessionID)
491 {
492 lock (m_UrlMap)
493 {
494 // We return true here because an external URL request that happened at the same time as an llRemoveURL()
495 // can still make it through to HttpRequestHandler(). That will return without setting up a request
496 // when it detects that the URL has been removed. The poller, however, will continue to ask for
497 // events for that request, so here we will signal that there are events and in GetEvents we will
498 // return a 404.
499 if (!m_RequestMap.ContainsKey(requestID))
500 {
501 return true;
502 }
503  
504 UrlData urlData = m_RequestMap[requestID];
505  
506 if (!urlData.requests.ContainsKey(requestID))
507 {
508 return true;
509 }
510  
511 // Trigger return of timeout response.
512 if (System.Environment.TickCount - urlData.requests[requestID].startTime > 25000)
513 {
514 return true;
515 }
516  
517 return urlData.requests[requestID].requestDone;
518 }
519 }
520  
521 private Hashtable GetEvents(UUID requestID, UUID sessionID)
522 {
523 Hashtable response;
524  
525 lock (m_UrlMap)
526 {
527 UrlData url = null;
528 RequestData requestData = null;
529  
530 if (!m_RequestMap.ContainsKey(requestID))
531 return NoEvents(requestID, sessionID);
532  
533 url = m_RequestMap[requestID];
534 requestData = url.requests[requestID];
535  
536 if (!requestData.requestDone)
537 return NoEvents(requestID, sessionID);
538  
539 response = new Hashtable();
540  
541 if (System.Environment.TickCount - requestData.startTime > 25000)
542 {
543 response["int_response_code"] = 500;
544 response["str_response_string"] = "Script timeout";
545 response["content_type"] = "text/plain";
546 response["keepalive"] = false;
547 response["reusecontext"] = false;
548 return response;
549 }
550  
551 //put response
552 response["int_response_code"] = requestData.responseCode;
553 response["str_response_string"] = requestData.responseBody;
554 response["content_type"] = requestData.responseType;
555 // response["content_type"] = "text/plain";
556 response["keepalive"] = false;
557 response["reusecontext"] = false;
558  
559 //remove from map
560 url.requests.Remove(requestID);
561 m_RequestMap.Remove(requestID);
562 }
563  
564 return response;
565 }
566  
567 public void HttpRequestHandler(UUID requestID, Hashtable request)
568 {
569 string uri = request["uri"].ToString();
570 bool is_ssl = uri.Contains("lslhttps");
571  
572 try
573 {
574 Hashtable headers = (Hashtable)request["headers"];
575  
576 // string uri_full = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri;// "/lslhttp/" + urlcode.ToString() + "/";
577  
578 int pos1 = uri.IndexOf("/");// /lslhttp
579 int pos2 = uri.IndexOf("/", pos1 + 1);// /lslhttp/
580 int pos3 = uri.IndexOf("/", pos2 + 1);// /lslhttp/<UUID>/
581 string uri_tmp = uri.Substring(0, pos3 + 1);
582 //HTTP server code doesn't provide us with QueryStrings
583 string pathInfo;
584 string queryString;
585 queryString = "";
586  
587 pathInfo = uri.Substring(pos3);
588  
589 UrlData urlData = null;
590  
591 lock (m_UrlMap)
592 {
593 string url;
594  
595 if (is_ssl)
596 url = "https://" + ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp;
597 else
598 url = "http://" + ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp;
599  
600 // Avoid a race - the request URL may have been released via llRequestUrl() whilst this
601 // request was being processed.
602 if (!m_UrlMap.TryGetValue(url, out urlData))
603 return;
604  
605 //for llGetHttpHeader support we need to store original URI here
606 //to make x-path-info / x-query-string / x-script-url / x-remote-ip headers
607 //as per http://wiki.secondlife.com/wiki/LlGetHTTPHeader
608  
609 RequestData requestData = new RequestData();
610 requestData.requestID = requestID;
611 requestData.requestDone = false;
612 requestData.startTime = System.Environment.TickCount;
613 requestData.uri = uri;
614 if (requestData.headers == null)
615 requestData.headers = new Dictionary<string, string>();
616  
617 foreach (DictionaryEntry header in headers)
618 {
619 string key = (string)header.Key;
620 string value = (string)header.Value;
621 requestData.headers.Add(key, value);
622 }
623  
624 foreach (DictionaryEntry de in request)
625 {
626 if (de.Key.ToString() == "querystringkeys")
627 {
628 System.String[] keys = (System.String[])de.Value;
629 foreach (String key in keys)
630 {
631 if (request.ContainsKey(key))
632 {
633 string val = (String)request[key];
634 queryString = queryString + key + "=" + val + "&";
635 }
636 }
637  
638 if (queryString.Length > 1)
639 queryString = queryString.Substring(0, queryString.Length - 1);
640 }
641 }
642  
643 //if this machine is behind DNAT/port forwarding, currently this is being
644 //set to address of port forwarding router
645 requestData.headers["x-remote-ip"] = requestData.headers["remote_addr"];
646 requestData.headers["x-path-info"] = pathInfo;
647 requestData.headers["x-query-string"] = queryString;
648 requestData.headers["x-script-url"] = urlData.url;
649  
650 urlData.requests.Add(requestID, requestData);
651 m_RequestMap.Add(requestID, urlData);
652 }
653  
654 urlData.engine.PostScriptEvent(
655 urlData.itemID,
656 "http_request",
657 new Object[] { requestID.ToString(), request["http-method"].ToString(), request["body"].ToString() });
658 }
659 catch (Exception we)
660 {
661 //Hashtable response = new Hashtable();
662 m_log.Warn("[HttpRequestHandler]: http-in request failed");
663 m_log.Warn(we.Message);
664 m_log.Warn(we.StackTrace);
665 }
666 }
667  
668 private void OnScriptReset(uint localID, UUID itemID)
669 {
670 ScriptRemoved(itemID);
671 }
672 }
673 }