aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
diff options
context:
space:
mode:
authorDr Scofield2008-07-02 09:02:30 +0000
committerDr Scofield2008-07-02 09:02:30 +0000
commitd40bea4a8e09be1f8e87cf41405aaa60fa8826cb (patch)
tree91ef9356d8b284ac6fa5f0d588fedebe723b69ad /OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
parentMantis#1643. Thank you Melanie for a patch that: (diff)
downloadopensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.zip
opensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.gz
opensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.bz2
opensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.xz
From: Alan M Webb <awebb@vnet.ibm.com>
This adds REST services for inventory access. It also allows inventory uploads.
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1201
1 files changed, 1201 insertions, 0 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
new file mode 100644
index 0000000..3de9f36
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
@@ -0,0 +1,1201 @@
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 OpenSim 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
29using System;
30using System.IO;
31using System.Reflection;
32using System.Text;
33using System.Security.Cryptography;
34using System.Text.RegularExpressions;
35using System.Collections.Generic;
36using System.Collections.Specialized;
37using OpenSim.Framework.Servers;
38using libsecondlife;
39using System.Xml;
40
41namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{
43
44 /// <summary>
45 /// This class represents the current REST request. It
46 /// encapsulates the request/response state and takes care
47 /// of response generation without exposing the REST handler
48 /// to the actual mechanisms involved.
49 ///
50 /// This structure is created on entry to the Handler
51 /// method and is disposed of upon return. It is part of
52 /// the plug-in infrastructure, rather than the functionally
53 /// specifici REST handler, and fundamental changes to
54 /// this should be reflected in the Rest HandlerVersion. The
55 /// object is instantiated, and may be extended by, any
56 /// given handler. See the inventory handler for an example
57 /// of this.
58 ///
59 /// If possible, the underlying request/response state is not
60 /// changed until the handler explicitly issues a Respond call.
61 /// This ensures that the request/response pair can be safely
62 /// processed by subsequent, unrelated, handlers even id the
63 /// agent handler had completed much of its processing. Think
64 /// of it as a transactional req/resp capability.
65 /// </summary>
66
67 internal class RequestData
68 {
69
70 // HTTP Server interface data
71
72 internal OSHttpRequest request = null;
73 internal OSHttpResponse response = null;
74
75 // Request lifetime values
76
77 internal NameValueCollection headers = null;
78 internal List<string> removed_headers = null;
79 internal byte[] buffer = null;
80 internal string body = null;
81 internal string html = null;
82 internal string entity = null;
83 internal string path = null;
84 internal string method = null;
85 internal string statusDescription = null;
86 internal string redirectLocation = null;
87 internal string[] pathNodes = null;
88 internal string[] parameters = null;
89 internal int statusCode = 0;
90 internal bool handled = false;
91 internal LLUUID uuid = LLUUID.Zero;
92 internal Encoding encoding = Rest.Encoding;
93 internal Uri uri = null;
94 internal string query = null;
95 internal bool fail = false;
96 internal string hostname = "localhost";
97 internal int port = 80;
98 internal string prefix = Rest.UrlPathSeparator;
99
100 // Authentication related state
101
102 internal bool authenticated = false;
103 internal string scheme = Rest.AS_DIGEST;
104 internal string realm = Rest.Realm;
105 internal string domain = null;
106 internal string nonce = null;
107 internal string cnonce = null;
108 internal string qop = Rest.Qop_Auth;
109 internal string opaque = null;
110 internal string stale = null;
111 internal string algorithm = Rest.Digest_MD5;
112 internal string authParms = null;
113 internal string authPrefix = null;
114 internal string userName = String.Empty;
115 internal string userPass = String.Empty;
116 internal LLUUID client = LLUUID.Zero;
117
118 // XML related state
119
120 internal XmlWriter writer = null;
121 internal XmlReader reader = null;
122
123 // Internal working state
124
125 private StringBuilder sbuilder = new StringBuilder(1024);
126 private MemoryStream xmldata = null;
127
128 private static readonly string[] EmptyPath = { String.Empty };
129
130 // Session related tables. These are only needed if QOP is set to "auth-sess"
131 // and for now at least, it is not. Session related authentication is of
132 // questionable merit in the context of REST anyway, but it is, arguably, more
133 // secure.
134
135 private static Dictionary<string,string> cntable = new Dictionary<string,string>();
136 private static Dictionary<string,string> sktable = new Dictionary<string,string>();
137
138 // This dictionary is used to keep track fo all of the parameters discovered
139 // when the authorisation header is anaylsed.
140
141 private Dictionary<string,string> authparms = new Dictionary<string,string>();
142
143 // These regular expressions are used to decipher the various header entries.
144
145 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
146 RegexOptions.Compiled | RegexOptions.IgnoreCase);
147
148 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
149 RegexOptions.Compiled | RegexOptions.IgnoreCase);
150
151 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>\\S+)\"",
152 RegexOptions.Compiled | RegexOptions.IgnoreCase);
153
154 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
155 RegexOptions.Compiled | RegexOptions.IgnoreCase);
156
157 private static Regex reuserPass = new Regex("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\S*)",
158 RegexOptions.Compiled | RegexOptions.IgnoreCase);
159
160 // For efficiency, we create static instances of these objects
161
162 private static MD5 md5hash = MD5.Create();
163
164 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
165
166 // Constructor
167
168 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix)
169 {
170
171 request = p_request;
172 response = p_response;
173
174 sbuilder.Length = 0;
175
176 encoding = request.ContentEncoding;
177 if (encoding == null)
178 {
179 encoding = Rest.Encoding;
180 }
181
182 method = request.HttpMethod.ToLower();
183 initUrl();
184
185 initParameters(qprefix.Length);
186
187 }
188
189 // Just for convenience...
190
191 internal string MsgId
192 {
193 get { return Rest.MsgId; }
194 }
195
196 // Defer authentication check until requested
197
198 internal bool IsAuthenticated
199 {
200 get
201 {
202 if (Rest.Authenticate)
203 {
204 if (!authenticated)
205 {
206 authenticate();
207 }
208
209 return authenticated;
210 }
211 else return true;
212 }
213 }
214
215 /// <summary>
216 /// The REST handler has requested authentication. Authentication
217 /// is considered to be with respect to the current values for
218 /// Realm, domain, etc.
219 ///
220 /// This method checks to see if the current request is already
221 /// authenticated for this domain. If it is, then it returns
222 /// true. If it is not, then it issues a challenge to the client
223 /// and responds negatively to the request.
224 /// </summary>
225
226 private void authenticate()
227 {
228
229 string authdata = request.Headers.Get("Authorization");
230 string reqscheme = String.Empty;
231
232 // If we don't have an authorization header, then this
233 // user is certainly not authorized. This is the typical
234 // pivot for the 1st request by a client.
235
236 if (authdata == null)
237 {
238 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
239 DoChallenge();
240 }
241
242 // So, we have authentication data, now we have to check to
243 // see what we got and whether or not it is valid for the
244 // current domain. To do this we need to interpret the data
245 // provided in the Authorization header. First we need to
246 // identify the scheme being used and route accordingly.
247
248 MatchCollection matches = schema.Matches(authdata);
249
250 foreach (Match m in matches)
251 {
252 Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
253 reqscheme = m.Groups["scheme"].Value.ToLower();
254 }
255
256 // If we want a specific authentication mechanism, make sure
257 // we get it.
258
259 if (scheme != null && scheme.ToLower() != reqscheme)
260 {
261 Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", MsgId);
262 DoChallenge();
263 }
264
265 // In the future, these could be made into plug-ins...
266 // But for now at least we have no reason to use anything other
267 // then MD5. TLS/SSL are taken care of elsewhere.
268
269 switch (reqscheme)
270 {
271 case "digest" :
272 Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
273 DoDigest(authdata);
274 break;
275
276 case "basic" :
277 Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
278 DoBasic(authdata);
279 break;
280 }
281
282 // If the current header is invalid, then a challenge is still needed.
283
284 if (!authenticated)
285 {
286 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
287 DoChallenge();
288 }
289
290 }
291
292 /// <summary>
293 /// Construct the necessary WWW-Authenticate headers and fail the request
294 /// with a NOT AUTHORIZED response. The parameters are the union of values
295 /// required by the supported schemes.
296 /// </summary>
297
298 private void DoChallenge()
299 {
300 Flush();
301 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
302 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
303 Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized);
304 }
305
306 /// <summary>
307 /// Interpret a BASIC authorization claim
308 /// This is here for completeness, it is not used.
309 /// </summary>
310
311 private void DoBasic(string authdata)
312 {
313
314 string response = null;
315
316 MatchCollection matches = basicParms.Matches(authdata);
317
318 // In the case of basic authentication there is
319 // only expected to be a single argument.
320
321 foreach (Match m in matches)
322 {
323 authparms.Add("response",m.Groups["pval"].Value);
324 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
325 MsgId, "response", m.Groups["pval"].Value);
326 }
327
328 // Did we get a valid response?
329
330 if (authparms.TryGetValue("response", out response))
331 {
332 // Decode
333 response = Rest.Base64ToString(response);
334 Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
335
336 // Extract user & password
337 Match m = reuserPass.Match(response);
338 userName = m.Groups["user"].Value;
339 userPass = m.Groups["pass"].Value;
340
341 // Validate against user database
342 authenticated = Validate(userName,userPass);
343 }
344
345 }
346
347 /// <summary>
348 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
349 /// implementation. It has been tested with Firefox, Java HTTP client,
350 /// and Miscrosoft's Internet Explorer V7.
351 /// </summary>
352
353 private void DoDigest(string authdata)
354 {
355
356 string response = null;
357
358 MatchCollection matches = digestParm1.Matches(authdata);
359
360 // Collect all of the supplied parameters and store them
361 // in a dictionary (for ease of access)
362
363 foreach (Match m in matches)
364 {
365 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
366 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
367 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
368 }
369
370 // And pick up any tokens too
371
372 matches = digestParm2.Matches(authdata);
373
374 foreach (Match m in matches)
375 {
376 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
377 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
378 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
379 }
380
381 // A response string MUST be returned, otherwise we are
382 // NOT authenticated.
383
384 Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
385
386 if (authparms.TryGetValue("response", out response))
387 {
388
389 string temp = null;
390
391 do
392 {
393
394 string nck = null;
395 string ncl = null;
396
397 // The userid is sent in clear text. Needed for the
398 // verification.
399
400 authparms.TryGetValue("username", out userName);
401
402 // All URI's of which this is a prefix are
403 // optimistically considered to be authenticated by the
404 // client. This is also needed to verify the response.
405
406 authparms.TryGetValue("uri", out authPrefix);
407
408 // There MUST be a nonce string present. We're not preserving any server
409 // side state and we can;t validate the MD5 unless the lcient returns it
410 // to us, as it should.
411
412 if (!authparms.TryGetValue("nonce", out nonce))
413 {
414 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
415 break;
416 }
417
418 // If there is an opaque string present, it had better
419 // match what we sent.
420
421 if (authparms.TryGetValue("opaque", out temp))
422 {
423 if (temp != opaque)
424 {
425 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
426 break;
427 }
428 }
429
430 // If an algorithm string is present, it had better
431 // match what we sent.
432
433 if (authparms.TryGetValue("algorithm", out temp))
434 {
435 if (temp != algorithm)
436 {
437 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
438 break;
439 }
440 }
441
442 // Quality of protection considerations...
443
444 if (authparms.TryGetValue("qop", out temp))
445 {
446
447 qop = temp.ToLower(); // replace with actual value used
448
449 // if QOP was specified then
450 // these MUST be present.
451
452 if (!authparms.ContainsKey("cnonce"))
453 {
454 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
455 break;
456 }
457
458 cnonce = authparms["cnonce"];
459
460 if (!authparms.ContainsKey("nc"))
461 {
462 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
463 break;
464 }
465
466 nck = authparms["nc"];
467
468 if (cntable.TryGetValue(cnonce, out ncl))
469 {
470 if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck))
471 {
472 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
473 break;
474 }
475 cntable[cnonce] = nck;
476 }
477 else
478 {
479 lock(cntable) cntable.Add(cnonce, nck);
480 }
481
482 }
483 else
484 {
485
486 qop = String.Empty;
487
488 // if QOP was not specified then
489 // these MUST NOT be present.
490 if (authparms.ContainsKey("cnonce"))
491 {
492 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
493 break;
494 }
495 if (authparms.ContainsKey("nc"))
496 {
497 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
498 break;
499 }
500 }
501
502 // Validate the supplied userid/password info
503
504 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
505
506 }
507 while (false);
508
509 }
510
511 }
512
513 // Indicate that authentication is required
514
515 internal void Challenge(string scheme, string realm, string domain, string nonce,
516 string opaque, string stale, string alg,
517 string qop, string auth)
518 {
519
520 sbuilder.Length = 0;
521
522 if (scheme == null || scheme == Rest.AS_DIGEST)
523 {
524
525 sbuilder.Append(Rest.AS_DIGEST);
526 sbuilder.Append(" ");
527
528 if (realm != null)
529 {
530 sbuilder.Append("realm=");
531 sbuilder.Append(Rest.CS_DQUOTE);
532 sbuilder.Append(realm);
533 sbuilder.Append(Rest.CS_DQUOTE);
534 sbuilder.Append(Rest.CS_COMMA);
535 }
536
537 if (nonce != null)
538 {
539 sbuilder.Append("nonce=");
540 sbuilder.Append(Rest.CS_DQUOTE);
541 sbuilder.Append(nonce);
542 sbuilder.Append(Rest.CS_DQUOTE);
543 sbuilder.Append(Rest.CS_COMMA);
544 }
545
546 if (opaque != null)
547 {
548 sbuilder.Append("opaque=");
549 sbuilder.Append(Rest.CS_DQUOTE);
550 sbuilder.Append(opaque);
551 sbuilder.Append(Rest.CS_DQUOTE);
552 sbuilder.Append(Rest.CS_COMMA);
553 }
554
555 if (stale != null)
556 {
557 sbuilder.Append("stale=");
558 sbuilder.Append(Rest.CS_DQUOTE);
559 sbuilder.Append(stale);
560 sbuilder.Append(Rest.CS_DQUOTE);
561 sbuilder.Append(Rest.CS_COMMA);
562 }
563
564 if (alg != null)
565 {
566 sbuilder.Append("algorithm=");
567 sbuilder.Append(alg);
568 sbuilder.Append(Rest.CS_COMMA);
569 }
570
571 if (qop != String.Empty)
572 {
573 sbuilder.Append("qop=");
574 sbuilder.Append(Rest.CS_DQUOTE);
575 sbuilder.Append(qop);
576 sbuilder.Append(Rest.CS_DQUOTE);
577 sbuilder.Append(Rest.CS_COMMA);
578 }
579
580 if (auth != null)
581 {
582 sbuilder.Append(auth);
583 sbuilder.Append(Rest.CS_COMMA);
584 }
585
586 if (Rest.Domains.Count != 0)
587 {
588 sbuilder.Append("domain=");
589 sbuilder.Append(Rest.CS_DQUOTE);
590 foreach (string dom in Rest.Domains.Values)
591 {
592 sbuilder.Append(dom);
593 sbuilder.Append(Rest.CS_SPACE);
594 }
595 if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE)
596 {
597 sbuilder.Length = sbuilder.Length-1;
598 }
599 sbuilder.Append(Rest.CS_DQUOTE);
600 sbuilder.Append(Rest.CS_COMMA);
601 }
602
603 if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA)
604 {
605 sbuilder.Length = sbuilder.Length-1;
606 }
607
608 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
609
610 }
611
612 if (scheme == null || scheme == Rest.AS_BASIC)
613 {
614
615 sbuilder.Append(Rest.AS_BASIC);
616
617 if (realm != null)
618 {
619 sbuilder.Append(" realm=\"");
620 sbuilder.Append(realm);
621 sbuilder.Append("\"");
622 }
623 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
624 }
625
626 }
627
628 private bool Validate(string user, string pass)
629 {
630 Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
631 return user == "awebb" && pass == getPassword(user);
632 }
633
634 private string getPassword(string user)
635 {
636 return Rest.GodKey;
637 }
638
639 // Validate the request-digest
640 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
641 {
642
643 string patt = null;
644 string payl = String.Empty;
645 string KDS = null;
646 string HA1 = null;
647 string HA2 = null;
648 string pass = getPassword(user);
649
650 // Generate H(A1)
651
652 if (algorithm == Rest.Digest_MD5Sess)
653 {
654 if (!sktable.ContainsKey(cnonce))
655 {
656 patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
657 HA1 = HashToString(patt);
658 sktable.Add(cnonce, HA1);
659 }
660 else
661 {
662 HA1 = sktable[cnonce];
663 }
664 }
665 else
666 {
667 patt = String.Format("{0}:{1}:{2}", user, realm, pass);
668 HA1 = HashToString(patt);
669 }
670
671 // Generate H(A2)
672
673 if (qop == "auth-int")
674 {
675 patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
676 }
677 else
678 {
679 patt = String.Format("{0}:{1}", request.HttpMethod, uri);
680 }
681
682 HA2 = HashToString(patt);
683
684 // Generate Digest
685
686 if (qop != String.Empty)
687 {
688 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
689 }
690 else
691 {
692 patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
693 }
694
695 KDS = HashToString(patt);
696
697 // Compare the generated sequence with the original
698
699 return (0 == sc.Compare(KDS, response));
700
701 }
702
703 private string HashToString(string pattern)
704 {
705
706 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
707
708 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
709
710 sbuilder.Length = 0;
711
712 for (int i = 0; i < hash.Length; i++)
713 {
714 sbuilder.Append(hash[i].ToString("x2"));
715 }
716
717 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
718
719 return sbuilder.ToString();
720
721 }
722
723 internal void Complete()
724 {
725 statusCode = Rest.HttpStatusCodeOK;
726 statusDescription = Rest.HttpStatusDescOK;
727 }
728
729 internal void Redirect(string Url, bool temp)
730 {
731
732 redirectLocation = Url;
733
734 if (temp)
735 {
736 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
737 statusDescription = Rest.HttpStatusDescTemporaryRedirect;
738 }
739 else
740 {
741 statusCode = Rest.HttpStatusCodePermanentRedirect;
742 statusDescription = Rest.HttpStatusDescPermanentRedirect;
743 }
744
745 Fail(statusCode, statusDescription, true);
746
747 }
748
749 // Fail for an arbitrary reason. Just a failure with
750 // headers.
751
752 internal void Fail(int code, string message)
753 {
754 Fail(code, message, true);
755 }
756
757 // More adventurous. This failure also includes a
758 // specified entity.
759
760 internal void Fail(int code, string message, string data)
761 {
762 buffer = null;
763 body = data;
764 Fail(code, message, false);
765 }
766
767 internal void Fail(int code, string message, bool reset)
768 {
769
770 statusCode = code;
771 statusDescription = message;
772
773 if (reset)
774 {
775 buffer = null;
776 body = null;
777 }
778
779 if (Rest.DEBUG)
780 {
781 Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
782 Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
783 Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
784 Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
785 Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
786 Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
787 Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
788 Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
789 Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
790 Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
791 Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
792 Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
793 }
794
795 fail = true;
796
797 Respond("Failure response");
798
799 RestException re = new RestException(message+" <"+code+">");
800
801 re.statusCode = code;
802 re.statusDesc = message;
803 re.httpmethod = method;
804 re.httppath = path;
805
806 throw re;
807
808 }
809
810 // Reject this request
811
812 internal void Reject()
813 {
814 Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented);
815 }
816
817 // This MUST be called by an agent handler before it returns
818 // control to Handle, otherwise the request will be ignored.
819 // This is called implciitly for the REST stream handlers and
820 // is harmless if it is called twice.
821
822 internal virtual bool Respond(string reason)
823 {
824
825 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
826
827 if (!handled)
828 {
829
830 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
831
832 // Process any arbitrary headers collected
833
834 BuildHeaders();
835
836 // A Head request can NOT have a body!
837 if (method != Rest.HEAD)
838 {
839
840 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
841
842 if (writer != null)
843 {
844 Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
845 Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
846 writer.Flush();
847 writer.Close();
848 if (!fail)
849 {
850 buffer = xmldata.ToArray();
851 AddHeader("Content-Type","application/xml");
852 }
853 xmldata.Close();
854 Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
855 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
856 }
857
858 // If buffer != null, then we assume that
859 // this has already been done some other
860 // way. For example, transfer encoding might
861 // have been done.
862
863 if (buffer == null)
864 {
865 if (body != null && body.Length > 0)
866 {
867 Rest.Log.DebugFormat("{0} String-based entity", MsgId);
868 buffer = encoding.GetBytes(body);
869 }
870 }
871
872 if (buffer != null)
873 {
874 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
875 if (response.Headers.Get("Content-Encoding") == null)
876 response.ContentEncoding = encoding;
877 response.ContentLength64 = buffer.Length;
878 response.SendChunked = false;
879 response.KeepAlive = false;
880 }
881
882 }
883
884 // Set the status code & description. If nothing
885 // has been stored, we consider that a success
886
887 if (statusCode == 0)
888 {
889 Complete();
890 }
891
892 response.StatusCode = statusCode;
893
894 if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily ||
895 response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
896 {
897 response.RedirectLocation = redirectLocation;
898 }
899
900 if (statusDescription != null)
901 {
902 response.StatusDescription = statusDescription;
903 }
904
905 // Finally we send back our response, consuming
906 // any exceptions that doing so might produce.
907
908 // We've left the setting of handled' until the
909 // last minute because the header settings included
910 // above are pretty harmless. But everything from
911 // here on down probably leaves the response
912 // element unusable by anyone else.
913
914 handled = true;
915
916 if (buffer != null && buffer.Length != 0)
917 {
918 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
919 MsgId, buffer.Length, encoding.GetString(buffer));
920 response.OutputStream.Write(buffer, 0, buffer.Length);
921 }
922
923 response.OutputStream.Close();
924
925 if (request.InputStream != null)
926 {
927 request.InputStream.Close();
928 }
929
930 }
931
932 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
933
934 return handled;
935
936 }
937
938 // Add a header to the table. If the header
939 // already exists, it is replaced.
940
941 internal void AddHeader(string hdr, string data)
942 {
943
944 if (headers == null)
945 {
946 headers = new NameValueCollection();
947 }
948
949 headers[hdr] = data;
950
951 }
952
953 // Keep explicit track of any headers which
954 // are to be removed.
955
956 internal void RemoveHeader(string hdr)
957 {
958
959 if (removed_headers == null)
960 {
961 removed_headers = new List<string>();
962 }
963
964 removed_headers.Add(hdr);
965
966 if (headers != null)
967 {
968 headers.Remove(hdr);
969 }
970
971 }
972
973 // Should it prove necessary, we could always
974 // restore the header collection from a cloned
975 // copy, but for now we'll assume that that is
976 // not necessary.
977
978 private void BuildHeaders()
979 {
980 if (removed_headers != null)
981 {
982 foreach (string h in removed_headers)
983 {
984 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
985 response.Headers.Remove(h);
986 }
987 }
988 if (headers!= null)
989 {
990 for (int i = 0; i < headers.Count; i++)
991 {
992 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
993 MsgId, headers.GetKey(i), headers.Get(i));
994 response.Headers.Add(headers.GetKey(i), headers.Get(i));
995 }
996 }
997 }
998
999 /// <summary>
1000 /// Helper methods for deconstructing and reconstructing
1001 /// URI path data.
1002 /// </summary>
1003
1004 private void initUrl()
1005 {
1006
1007 uri = request.Url;
1008
1009 if (query == null)
1010 {
1011 query = uri.Query;
1012 }
1013
1014 // If the path has not been previously initialized,
1015 // do so now.
1016
1017 if (path == null)
1018 {
1019 path = uri.AbsolutePath;
1020 if (path.EndsWith(Rest.UrlPathSeparator))
1021 path = path.Substring(0,path.Length-1);
1022 path = Uri.UnescapeDataString(path);
1023 }
1024
1025 // If we succeeded in getting a path, perform any
1026 // additional pre-processing required.
1027
1028 if (path != null)
1029 {
1030 if (Rest.ExtendedEscape)
1031 {
1032 // Handle "+". Not a standard substitution, but
1033 // common enough...
1034 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1035 }
1036 pathNodes = path.Split(Rest.CA_PATHSEP);
1037 }
1038 else
1039 {
1040 pathNodes = EmptyPath;
1041 }
1042
1043 // Request server context info
1044
1045 hostname = uri.Host;
1046 port = uri.Port;
1047
1048 }
1049
1050 internal int initParameters(int prfxlen)
1051 {
1052
1053 if (prfxlen < path.Length-1)
1054 {
1055 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
1056 }
1057 else
1058 {
1059 parameters = new string[0];
1060 }
1061
1062 // Generate a debug list of the decoded parameters
1063
1064 if (Rest.DEBUG && prfxlen < path.Length-1)
1065 {
1066 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1067 for (int i = 0; i < parameters.Length; i++)
1068 {
1069 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1070 }
1071 }
1072
1073 return parameters.Length;
1074
1075 }
1076
1077 internal string[] PathNodes
1078 {
1079 get
1080 {
1081 if (pathNodes == null)
1082 {
1083 initUrl();
1084 }
1085 return pathNodes;
1086 }
1087 }
1088
1089 internal string BuildUrl(int first, int last)
1090 {
1091
1092 if (pathNodes == null)
1093 {
1094 initUrl();
1095 }
1096
1097 if (first < 0)
1098 {
1099 first = first + pathNodes.Length;
1100 }
1101
1102 if (last < 0)
1103 {
1104 last = last + pathNodes.Length;
1105 if (last < 0)
1106 {
1107 return Rest.UrlPathSeparator;
1108 }
1109 }
1110
1111 sbuilder.Length = 0;
1112 sbuilder.Append(Rest.UrlPathSeparator);
1113
1114 if (first <= last)
1115 {
1116 for (int i = first; i <= last; i++)
1117 {
1118 sbuilder.Append(pathNodes[i]);
1119 sbuilder.Append(Rest.UrlPathSeparator);
1120 }
1121 }
1122 else
1123 {
1124 for (int i = last; i >= first; i--)
1125 {
1126 sbuilder.Append(pathNodes[i]);
1127 sbuilder.Append(Rest.UrlPathSeparator);
1128 }
1129 }
1130
1131 return sbuilder.ToString();
1132
1133 }
1134
1135 // Setup the XML writer for output
1136
1137 internal void initXmlWriter()
1138 {
1139 XmlWriterSettings settings = new XmlWriterSettings();
1140 xmldata = new MemoryStream();
1141 settings.Indent = true;
1142 settings.IndentChars = " ";
1143 settings.Encoding = encoding;
1144 settings.CloseOutput = false;
1145 settings.OmitXmlDeclaration = true;
1146 settings.ConformanceLevel = ConformanceLevel.Fragment;
1147 writer = XmlWriter.Create(xmldata, settings);
1148 }
1149
1150 internal void initXmlReader()
1151 {
1152 XmlReaderSettings settings = new XmlReaderSettings();
1153 settings.ConformanceLevel = ConformanceLevel.Fragment;
1154 settings.IgnoreComments = true;
1155 settings.IgnoreWhitespace = true;
1156 settings.IgnoreProcessingInstructions = true;
1157 settings.ValidationType = ValidationType.None;
1158 // reader = XmlReader.Create(new StringReader(entity),settings);
1159 reader = XmlReader.Create(request.InputStream,settings);
1160 }
1161
1162 private void Flush()
1163 {
1164 byte[] dbuffer = new byte[8192];
1165 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
1166 return;
1167 }
1168
1169 // This allows us to make errors a bit more apparent in REST
1170
1171 internal void SendHtml(string text)
1172 {
1173 SendHtml("OpenSim REST Interface 1.0", text);
1174 }
1175
1176 internal void SendHtml(string title, string text)
1177 {
1178
1179 AddHeader(Rest.HttpHeaderContentType, "text/html");
1180 sbuilder.Length = 0;
1181
1182 sbuilder.Append("<html>");
1183 sbuilder.Append("<head>");
1184 sbuilder.Append("<title>");
1185 sbuilder.Append(title);
1186 sbuilder.Append("</title>");
1187 sbuilder.Append("</head>");
1188
1189 sbuilder.Append("<body>");
1190 sbuilder.Append("<br />");
1191 sbuilder.Append("<p>");
1192 sbuilder.Append(text);
1193 sbuilder.Append("</p>");
1194 sbuilder.Append("</body>");
1195 sbuilder.Append("</html>");
1196
1197 html = sbuilder.ToString();
1198
1199 }
1200 }
1201}