aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
diff options
context:
space:
mode:
authorDr Scofield2008-08-20 10:11:11 +0000
committerDr Scofield2008-08-20 10:11:11 +0000
commit5e83a758157520d48b15c725f5be2b196d2414e3 (patch)
tree42ab07bde2a984d06632abd30e6ad59877021f3f /OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
parentAdding unit test for issue 2006, though it's commented out as those compiler (diff)
downloadopensim-SC_OLD-5e83a758157520d48b15c725f5be2b196d2414e3.zip
opensim-SC_OLD-5e83a758157520d48b15c725f5be2b196d2414e3.tar.gz
opensim-SC_OLD-5e83a758157520d48b15c725f5be2b196d2414e3.tar.bz2
opensim-SC_OLD-5e83a758157520d48b15c725f5be2b196d2414e3.tar.xz
From: Alan Webb <alan_webb@us.ibm.com>
cleanups of the REST inventory code.
Diffstat (limited to '')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1076
1 files changed, 632 insertions, 444 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
index d08f830..6742402 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using System; 29using System;
@@ -40,16 +41,17 @@ using System.Xml;
40 41
41namespace OpenSim.ApplicationPlugins.Rest.Inventory 42namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{ 43{
44
43 /// <summary> 45 /// <summary>
44 /// This class represents the current REST request. It 46 /// This class represents the current REST request. It
45 /// encapsulates the request/response state and takes care 47 /// encapsulates the request/response state and takes care
46 /// of response generation without exposing the REST handler 48 /// of response generation without exposing the REST handler
47 /// to the actual mechanisms involved. 49 /// to the actual mechanisms involved.
48 /// 50 ///
49 /// This structure is created on entry to the Handler 51 /// This structure is created on entry to the Handler
50 /// method and is disposed of upon return. It is part of 52 /// method and is disposed of upon return. It is part of
51 /// the plug-in infrastructure, rather than the functionally 53 /// the plug-in infrastructure, rather than the functionally
52 /// specific REST handler, and fundamental changes to 54 /// specific REST handler, and fundamental changes to
53 /// this should be reflected in the Rest HandlerVersion. The 55 /// this should be reflected in the Rest HandlerVersion. The
54 /// object is instantiated, and may be extended by, any 56 /// object is instantiated, and may be extended by, any
55 /// given handler. See the inventory handler for an example 57 /// given handler. See the inventory handler for an example
@@ -63,44 +65,109 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
63 /// of it as a transactional req/resp capability. 65 /// of it as a transactional req/resp capability.
64 /// </summary> 66 /// </summary>
65 67
66 internal class RequestData 68 public class RequestData
67 { 69 {
68 // HTTP Server interface data 70
71 // HTTP Server interface data (Received values)
69 72
70 internal OSHttpRequest request = null; 73 internal OSHttpRequest request = null;
71 internal OSHttpResponse response = null; 74 internal OSHttpResponse response = null;
72 internal string qprefix = null; 75 internal string qprefix = null;
73 76
74 // Request lifetime values 77 // Request lifetime values
78 // buffer is global because it is referenced by the handler
79 // in supported of streamed requests.
80 // If a service provider wants to construct the message
81 // body explicitly it can use body to do this. The value
82 // in body is used if the buffer is still null when a response
83 // is generated.
84 // Storing information in body will suppress the return of
85 // statusBody which is only intended to report status on
86 // requests which do not themselves ordinarily generate
87 // an informational response. All of this is handled in
88 // Respond().
75 89
76 internal byte[] buffer = null; 90 internal byte[] buffer = null;
77 internal string body = null; 91 internal string body = null;
78 internal string html = null; 92 internal string bodyType = "text/html";
79 internal string entity = null; 93
94 // The encoding in effect is set to a server default. It may
95 // subsequently be overridden by a Content header. This
96 // value is established during construction and is used
97 // wherever encoding services are needed.
98
99 internal Encoding encoding = Rest.Encoding;
100
101 // These values are derived from the supplied URL. They
102 // are initialized during construction.
103
80 internal string path = null; 104 internal string path = null;
81 internal string method = null; 105 internal string method = null;
82 internal string statusDescription = null;
83 internal string redirectLocation = null;
84 internal string[] pathNodes = null;
85 internal string[] parameters = null;
86 internal int statusCode = 0;
87 internal bool handled = false;
88 internal LLUUID uuid = LLUUID.Zero;
89 internal Encoding encoding = Rest.Encoding;
90 internal Uri uri = null; 106 internal Uri uri = null;
91 internal string query = null; 107 internal string query = null;
92 internal bool fail = false;
93 internal string hostname = "localhost"; 108 internal string hostname = "localhost";
94 internal int port = 80; 109 internal int port = 80;
95 internal string prefix = Rest.UrlPathSeparator; 110
111 // The path part of the URI is decomposed. pathNodes
112 // is an array of every element in the URI. Parameters
113 // is an array that contains only those nodes that
114 // are not a part of the authority prefix
115
116 private string[] pathNodes = null;
117 private string[] parameters = null;
118 private static readonly string[] EmptyPath = { String.Empty };
119
120 // The status code gets set during the course of processing
121 // and is the HTTP completion code. The status body is
122 // initialized during construction, is appended to during the
123 // course of execution, and is finalized during Respond
124 // processing.
125 //
126 // Fail processing marks the request as failed and this is
127 // then used to inhibit processing during Response processing.
128
129 internal int statusCode = 0;
130 internal string statusBody = String.Empty;
131 internal bool fail = false;
132
133 // This carries the URL to which the client should be redirected.
134 // It is set by the service provider using the Redirect call.
135
136 internal string redirectLocation = null;
137
138 // These values influence response processing. They can be set by
139 // service providers according to need. The defaults are generally
140 // good.
141
96 internal bool keepAlive = false; 142 internal bool keepAlive = false;
97 internal bool chunked = false; 143 internal bool chunked = false;
98 144
99 // Authentication related state 145 // XML related state
100 146
147 internal XmlWriter writer = null;
148 internal XmlReader reader = null;
149
150 // Internal working state
151
152 private StringBuilder sbuilder = new StringBuilder(1024);
153 private MemoryStream xmldata = null;
154
155 // This is used to make the response mechanism idempotent.
156
157 internal bool handled = false;
158
159 // Authentication related state
160 //
161 // Two supported authentication mechanisms are:
162 // scheme = Rest.AS_BASIC;
163 // scheme = Rest.AS_DIGEST;
164 // Presented in that order (as required by spec)
165 // A service provider can set the scheme variable to
166 // force selection of a particular authentication model
167 // (choosing from amongst those supported of course)
168 //
169
101 internal bool authenticated = false; 170 internal bool authenticated = false;
102 // internal string scheme = Rest.AS_DIGEST;
103 // internal string scheme = Rest.AS_BASIC;
104 internal string scheme = null; 171 internal string scheme = null;
105 internal string realm = Rest.Realm; 172 internal string realm = Rest.Realm;
106 internal string domain = null; 173 internal string domain = null;
@@ -114,22 +181,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
114 internal string authPrefix = null; 181 internal string authPrefix = null;
115 internal string userName = String.Empty; 182 internal string userName = String.Empty;
116 internal string userPass = String.Empty; 183 internal string userPass = String.Empty;
117 internal LLUUID client = LLUUID.Zero;
118
119 // XML related state
120
121 internal XmlWriter writer = null;
122 internal XmlReader reader = null;
123
124 // Internal working state
125
126 private StringBuilder sbuilder = new StringBuilder(1024);
127 private MemoryStream xmldata = null;
128
129 private static readonly string[] EmptyPath = { String.Empty };
130 184
131 // Session related tables. These are only needed if QOP is set to "auth-sess" 185 // Session related tables. These are only needed if QOP is set to "auth-sess"
132 // and for now at least, it is not. Session related authentication is of 186 // and for now at least, it is not. Session related authentication is of
133 // questionable merit in the context of REST anyway, but it is, arguably, more 187 // questionable merit in the context of REST anyway, but it is, arguably, more
134 // secure. 188 // secure.
135 189
@@ -145,46 +199,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
145 199
146 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*", 200 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
147 RegexOptions.Compiled | RegexOptions.IgnoreCase); 201 RegexOptions.Compiled | RegexOptions.IgnoreCase);
148 202
149 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", 203 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
150 RegexOptions.Compiled | RegexOptions.IgnoreCase); 204 RegexOptions.Compiled | RegexOptions.IgnoreCase);
151 205
152 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", 206 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
153 RegexOptions.Compiled | RegexOptions.IgnoreCase); 207 RegexOptions.Compiled | RegexOptions.IgnoreCase);
154 208
155 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", 209 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
156 RegexOptions.Compiled | RegexOptions.IgnoreCase); 210 RegexOptions.Compiled | RegexOptions.IgnoreCase);
157 211
158 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)", 212 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
159 RegexOptions.Compiled | RegexOptions.IgnoreCase); 213 RegexOptions.Compiled | RegexOptions.IgnoreCase);
160 214
161 // For efficiency, we create static instances of these objects 215 // For efficiency, we create static instances of these objects
162 216
163 private static MD5 md5hash = MD5.Create(); 217 private static MD5 md5hash = MD5.Create();
164 218
165 private static StringComparer sc = StringComparer.OrdinalIgnoreCase; 219 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
166 220
167 // Constructor 221#region properties
168
169 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
170 {
171 request = p_request;
172 response = p_response;
173 qprefix = p_qprefix;
174
175 sbuilder.Length = 0;
176
177 encoding = request.ContentEncoding;
178 if (encoding == null)
179 {
180 encoding = Rest.Encoding;
181 }
182
183 method = request.HttpMethod.ToLower();
184 initUrl();
185
186 initParameters(p_qprefix.Length);
187 }
188 222
189 // Just for convenience... 223 // Just for convenience...
190 224
@@ -193,12 +227,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
193 get { return Rest.MsgId; } 227 get { return Rest.MsgId; }
194 } 228 }
195 229
196 // Defer authentication check until requested 230 /// <summary>
231 /// Return a boolean indication of whether or no an authenticated user is
232 /// associated with this request. This could be wholly integrated, but
233 /// that would make authentication mandatory.
234 /// </summary>
197 235
198 internal bool IsAuthenticated 236 internal bool IsAuthenticated
199 { 237 {
200 get 238 get
201 { 239 {
202 if (Rest.Authenticate) 240 if (Rest.Authenticate)
203 { 241 {
204 if (!authenticated) 242 if (!authenticated)
@@ -208,24 +246,87 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
208 246
209 return authenticated; 247 return authenticated;
210 } 248 }
211 else 249 else return true;
212 return true;
213 } 250 }
214 } 251 }
215 252
216 /// <summary> 253 /// <summary>
254 /// Access to all 'nodes' in the supplied URI as an
255 /// array of strings.
256 /// </summary>
257
258 internal string[] PathNodes
259 {
260 get
261 {
262 return pathNodes;
263 }
264 }
265
266 /// <summary>
267 /// Access to all non-prefix 'nodes' in the supplied URI as an
268 /// array of strings. These identify a specific resource that
269 /// is managed by the authority (the prefix).
270 /// </summary>
271
272 internal string[] Parameters
273 {
274 get
275 {
276 return parameters;
277 }
278 }
279
280#endregion properties
281
282#region constructors
283
284 // Constructor
285
286 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
287 {
288
289 request = p_request;
290 response = p_response;
291 qprefix = p_qprefix;
292
293 sbuilder.Length = 0;
294
295 encoding = request.ContentEncoding;
296 if (encoding == null)
297 {
298 encoding = Rest.Encoding;
299 }
300
301 method = request.HttpMethod.ToLower();
302 initUrl();
303
304 initParameters(p_qprefix.Length);
305
306 }
307
308#endregion constructors
309
310#region authentication_common
311
312 /// <summary>
217 /// The REST handler has requested authentication. Authentication 313 /// The REST handler has requested authentication. Authentication
218 /// is considered to be with respect to the current values for 314 /// is considered to be with respect to the current values for
219 /// Realm, domain, etc. 315 /// Realm, domain, etc.
220 /// 316 ///
221 /// This method checks to see if the current request is already 317 /// This method checks to see if the current request is already
222 /// authenticated for this domain. If it is, then it returns 318 /// authenticated for this domain. If it is, then it returns
223 /// true. If it is not, then it issues a challenge to the client 319 /// true. If it is not, then it issues a challenge to the client
224 /// and responds negatively to the request. 320 /// and responds negatively to the request.
321 ///
322 /// As soon as authentication failure is detected the method calls
323 /// DoChallenge() which terminates the request with REST exception
324 /// for unauthroized access.
225 /// </summary> 325 /// </summary>
226 326
227 private void authenticate() 327 private void authenticate()
228 { 328 {
329
229 string authdata = request.Headers.Get("Authorization"); 330 string authdata = request.Headers.Get("Authorization");
230 string reqscheme = String.Empty; 331 string reqscheme = String.Empty;
231 332
@@ -238,7 +339,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
238 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); 339 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
239 DoChallenge(); 340 DoChallenge();
240 } 341 }
241 342
242 // So, we have authentication data, now we have to check to 343 // 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 344 // 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 345 // current domain. To do this we need to interpret the data
@@ -287,6 +388,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
287 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); 388 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
288 DoChallenge(); 389 DoChallenge();
289 } 390 }
391
290 } 392 }
291 393
292 /// <summary> 394 /// <summary>
@@ -300,16 +402,193 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
300 Flush(); 402 Flush();
301 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) 403 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
302 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); 404 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
303 Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); 405 Fail(Rest.HttpStatusCodeNotAuthorized);
304 } 406 }
305 407
306 /// <summary> 408 /// <summary>
307 /// Interpret a BASIC authorization claim 409 /// The Flush() call is here to support a problem encountered with the
308 /// This is here for completeness, it is not used. 410 /// client where an authentication rejection was lost because the rejection
411 /// may flow before the clienthas finished sending us the inbound data stream,
412 /// in which case the client responds to the socket error on out put, and
413 /// never sees the authentication challenge. The client should be fixed,
414 /// because this solution leaves the server prone to DOS attacks. A message
415 /// will be issued whenever flushing occurs. It can be enabled/disabled from
416 /// the configuration file.
417 /// </summary>
418
419 private void Flush()
420 {
421 if (Rest.FlushEnabled)
422 {
423 byte[] dbuffer = new byte[8192];
424 Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
425 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
426 }
427 return;
428 }
429
430 // Indicate that authentication is required
431
432 private void Challenge(string scheme, string realm, string domain, string nonce,
433 string opaque, string stale, string alg,
434 string qop, string auth)
435 {
436
437 sbuilder.Length = 0;
438
439 // The service provider can force a particular scheme by
440 // assigning a value to scheme.
441
442 // Basic authentication is pretty simple.
443 // Just specify the realm in question.
444
445 if (scheme == null || scheme == Rest.AS_BASIC)
446 {
447
448 sbuilder.Append(Rest.AS_BASIC);
449
450 if (realm != null)
451 {
452 sbuilder.Append(" realm=\"");
453 sbuilder.Append(realm);
454 sbuilder.Append("\"");
455 }
456 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
457 }
458
459 sbuilder.Length = 0;
460
461 // Digest authentication takes somewhat more
462 // to express.
463
464 if (scheme == null || scheme == Rest.AS_DIGEST)
465 {
466
467 sbuilder.Append(Rest.AS_DIGEST);
468 sbuilder.Append(" ");
469
470 // Specify the effective realm. This should
471 // never be null if we are uthenticating, as it is required for all
472 // authentication schemes. It defines, in conjunction with the
473 // absolute URI information, the domain to which the authentication
474 // applies. It is an arbitrary string. I *believe* this allows an
475 // authentication to apply to disjoint resources within the same
476 // server.
477
478 if (realm != null)
479 {
480 sbuilder.Append("realm=");
481 sbuilder.Append(Rest.CS_DQUOTE);
482 sbuilder.Append(realm);
483 sbuilder.Append(Rest.CS_DQUOTE);
484 sbuilder.Append(Rest.CS_COMMA);
485 }
486
487 // Share our nonce. This is *uniquely* generated each time a 401 is
488 // returned. We do not generate a very sophisticated nonce at the
489 // moment (it's simply a base64 encoded UUID).
490
491 if (nonce != null)
492 {
493 sbuilder.Append("nonce=");
494 sbuilder.Append(Rest.CS_DQUOTE);
495 sbuilder.Append(nonce);
496 sbuilder.Append(Rest.CS_DQUOTE);
497 sbuilder.Append(Rest.CS_COMMA);
498 }
499
500 // The opaque string should be returned by the client unchanged in all
501 // subsequent requests.
502
503 if (opaque != null)
504 {
505 sbuilder.Append("opaque=");
506 sbuilder.Append(Rest.CS_DQUOTE);
507 sbuilder.Append(opaque);
508 sbuilder.Append(Rest.CS_DQUOTE);
509 sbuilder.Append(Rest.CS_COMMA);
510 }
511
512 // This flag indicates that the authentication was rejected because the
513 // included nonce was stale. The server might use timestamp information
514 // in the nonce to determine this. We do not.
515
516 if (stale != null)
517 {
518 sbuilder.Append("stale=");
519 sbuilder.Append(Rest.CS_DQUOTE);
520 sbuilder.Append(stale);
521 sbuilder.Append(Rest.CS_DQUOTE);
522 sbuilder.Append(Rest.CS_COMMA);
523 }
524
525 // Identifies the algorithm used to produce the digest and checksum.
526 // The default is MD5.
527
528 if (alg != null)
529 {
530 sbuilder.Append("algorithm=");
531 sbuilder.Append(alg);
532 sbuilder.Append(Rest.CS_COMMA);
533 }
534
535 // Theoretically QOP is optional, but it is required by a compliant
536 // with current versions of the scheme. In fact IE requires that QOP
537 // be specified and will refuse to authenticate otherwise.
538
539 if (qop != String.Empty)
540 {
541 sbuilder.Append("qop=");
542 sbuilder.Append(Rest.CS_DQUOTE);
543 sbuilder.Append(qop);
544 sbuilder.Append(Rest.CS_DQUOTE);
545 sbuilder.Append(Rest.CS_COMMA);
546 }
547
548 // This parameter allows for arbitrary extensions to the protocol.
549 // Unrecognized values should be simply ignored.
550
551 if (auth != null)
552 {
553 sbuilder.Append(auth);
554 sbuilder.Append(Rest.CS_COMMA);
555 }
556
557 // We don't know the userid that will be used
558 // so we cannot make any authentication domain
559 // assumptions. So the prefix will determine
560 // this.
561
562 sbuilder.Append("domain=");
563 sbuilder.Append(Rest.CS_DQUOTE);
564 sbuilder.Append(qprefix);
565 sbuilder.Append(Rest.CS_DQUOTE);
566
567 // Generate the authenticate header and we're basically
568 // done.
569
570 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
571
572 }
573
574 }
575
576#endregion authentication_common
577
578#region authentication_basic
579
580 /// <summary>
581 /// Interpret a BASIC authorization claim. Some clients can only
582 /// understand this and also expect it to be the first one
583 /// offered. So we do.
584 /// OpenSim also needs this, as it is the only scheme that allows
585 /// authentication using the hashed passwords stored in the
586 /// user database.
309 /// </summary> 587 /// </summary>
310 588
311 private void DoBasic(string authdata) 589 private void DoBasic(string authdata)
312 { 590 {
591
313 string response = null; 592 string response = null;
314 593
315 MatchCollection matches = basicParms.Matches(authdata); 594 MatchCollection matches = basicParms.Matches(authdata);
@@ -320,7 +599,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
320 foreach (Match m in matches) 599 foreach (Match m in matches)
321 { 600 {
322 authparms.Add("response",m.Groups["pval"].Value); 601 authparms.Add("response",m.Groups["pval"].Value);
323 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", 602 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
324 MsgId, "response", m.Groups["pval"].Value); 603 MsgId, "response", m.Groups["pval"].Value);
325 } 604 }
326 605
@@ -340,38 +619,111 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
340 // Validate against user database 619 // Validate against user database
341 authenticated = Validate(userName,userPass); 620 authenticated = Validate(userName,userPass);
342 } 621 }
622
623 }
624
625 /// <summary>
626 /// This method provides validation in support of the BASIC
627 /// authentication method. This is not normaly expected to be
628 /// used, but is included for completeness (and because I tried
629 /// it first).
630 /// </summary>
631
632 private bool Validate(string user, string pass)
633 {
634
635 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
636
637 // Both values are required
638
639 if (user == null || pass == null)
640 return false;
641
642 // Eliminate any leading or trailing spaces
643 user = user.Trim();
644
645 return vetPassword(user, pass);
646
343 } 647 }
344 648
345 /// <summary> 649 /// <summary>
650 /// This is used by the BASIC authentication scheme to calculate
651 /// the double hash used by OpenSim to encode user's passwords.
652 /// It returns true, if the supplied password is actually correct.
653 /// If the specified user-id is not recognized, but the password
654 /// matches the God password, then this is accepted as an admin
655 /// session.
656 /// </summary>
657
658 private bool vetPassword(string user, string pass)
659 {
660
661 int x;
662 string HA1;
663 string first;
664 string last;
665
666 // Distinguish the parts, if necessary
667
668 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
669 {
670 first = user.Substring(0,x);
671 last = user.Substring(x+1);
672 }
673 else
674 {
675 first = user;
676 last = String.Empty;
677 }
678
679 UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
680
681 // If we don;t recognize the user id, perhaps it is god?
682
683 if (udata == null)
684 return pass == Rest.GodKey;
685
686 HA1 = HashToString(pass);
687 HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
688
689 return (0 == sc.Compare(HA1, udata.PasswordHash));
690
691 }
692
693#endregion authentication_basic
694
695#region authentication_digest
696
697 /// <summary>
346 /// This is an RFC2617 compliant HTTP MD5 Digest authentication 698 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
347 /// implementation. It has been tested with Firefox, Java HTTP client, 699 /// implementation. It has been tested with Firefox, Java HTTP client,
348 /// and Miscrosoft's Internet Explorer V7. 700 /// and Microsoft's Internet Explorer V7.
349 /// </summary> 701 /// </summary>
350 702
351 private void DoDigest(string authdata) 703 private void DoDigest(string authdata)
352 { 704 {
705
353 string response = null; 706 string response = null;
354 707
355 MatchCollection matches = digestParm1.Matches(authdata); 708 // Find all of the values of the for x = "y"
356 709
357 // Collect all of the supplied parameters and store them 710 MatchCollection matches = digestParm1.Matches(authdata);
358 // in a dictionary (for ease of access)
359 711
360 foreach (Match m in matches) 712 foreach (Match m in matches)
361 { 713 {
362 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); 714 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
363 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", 715 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
364 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); 716 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
365 } 717 }
366 718
367 // And pick up any tokens too 719 // Find all of the values of the for x = y
368 720
369 matches = digestParm2.Matches(authdata); 721 matches = digestParm2.Matches(authdata);
370 722
371 foreach (Match m in matches) 723 foreach (Match m in matches)
372 { 724 {
373 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); 725 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
374 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", 726 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
375 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); 727 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
376 } 728 }
377 729
@@ -382,10 +734,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
382 734
383 if (authparms.TryGetValue("response", out response)) 735 if (authparms.TryGetValue("response", out response))
384 { 736 {
737
385 string temp = null; 738 string temp = null;
386 739
387 do 740 do
388 { 741 {
742
389 string nck = null; 743 string nck = null;
390 string ncl = null; 744 string ncl = null;
391 745
@@ -406,7 +760,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
406 760
407 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) 761 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
408 { 762 {
409 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); 763 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
410 break; 764 break;
411 } 765 }
412 766
@@ -417,7 +771,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
417 { 771 {
418 if (temp != opaque) 772 if (temp != opaque)
419 { 773 {
420 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); 774 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
421 break; 775 break;
422 } 776 }
423 } 777 }
@@ -429,7 +783,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
429 { 783 {
430 if (temp != algorithm) 784 if (temp != algorithm)
431 { 785 {
432 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); 786 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
433 break; 787 break;
434 } 788 }
435 } 789 }
@@ -438,6 +792,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
438 792
439 if (authparms.TryGetValue("qop", out temp)) 793 if (authparms.TryGetValue("qop", out temp))
440 { 794 {
795
441 qop = temp.ToLower(); // replace with actual value used 796 qop = temp.ToLower(); // replace with actual value used
442 797
443 // if QOP was specified then 798 // if QOP was specified then
@@ -445,7 +800,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
445 800
446 if (!authparms.ContainsKey("cnonce")) 801 if (!authparms.ContainsKey("cnonce"))
447 { 802 {
448 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); 803 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
449 break; 804 break;
450 } 805 }
451 806
@@ -453,7 +808,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
453 808
454 if (!authparms.TryGetValue("nc", out nck) || nck == null) 809 if (!authparms.TryGetValue("nc", out nck) || nck == null)
455 { 810 {
456 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); 811 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
457 break; 812 break;
458 } 813 }
459 814
@@ -465,7 +820,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
465 820
466 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) 821 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
467 { 822 {
468 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); 823 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
469 break; 824 break;
470 } 825 }
471 cntable[nonce] = nck; 826 cntable[nonce] = nck;
@@ -474,21 +829,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
474 { 829 {
475 lock (cntable) cntable.Add(nonce, nck); 830 lock (cntable) cntable.Add(nonce, nck);
476 } 831 }
832
477 } 833 }
478 else 834 else
479 { 835 {
836
480 qop = String.Empty; 837 qop = String.Empty;
481 838
482 // if QOP was not specified then 839 // if QOP was not specified then
483 // these MUST NOT be present. 840 // these MUST NOT be present.
484 if (authparms.ContainsKey("cnonce")) 841 if (authparms.ContainsKey("cnonce"))
485 { 842 {
486 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); 843 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
487 break; 844 break;
488 } 845 }
489 if (authparms.ContainsKey("nc")) 846 if (authparms.ContainsKey("nc"))
490 { 847 {
491 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); 848 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
492 break; 849 break;
493 } 850 }
494 } 851 }
@@ -496,157 +853,38 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
496 // Validate the supplied userid/password info 853 // Validate the supplied userid/password info
497 854
498 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); 855 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
499 }
500 while (false);
501 }
502 }
503
504 // Indicate that authentication is required
505
506 internal void Challenge(string scheme, string realm, string domain, string nonce,
507 string opaque, string stale, string alg,
508 string qop, string auth)
509 {
510 sbuilder.Length = 0;
511
512 if (scheme == null || scheme == Rest.AS_BASIC)
513 {
514
515 sbuilder.Append(Rest.AS_BASIC);
516
517 if (realm != null)
518 {
519 sbuilder.Append(" realm=\"");
520 sbuilder.Append(realm);
521 sbuilder.Append("\"");
522 }
523 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
524 }
525
526 sbuilder.Length = 0;
527
528 if (scheme == null || scheme == Rest.AS_DIGEST)
529 {
530 sbuilder.Append(Rest.AS_DIGEST);
531 sbuilder.Append(" ");
532
533 if (realm != null)
534 {
535 sbuilder.Append("realm=");
536 sbuilder.Append(Rest.CS_DQUOTE);
537 sbuilder.Append(realm);
538 sbuilder.Append(Rest.CS_DQUOTE);
539 sbuilder.Append(Rest.CS_COMMA);
540 }
541
542 if (nonce != null)
543 {
544 sbuilder.Append("nonce=");
545 sbuilder.Append(Rest.CS_DQUOTE);
546 sbuilder.Append(nonce);
547 sbuilder.Append(Rest.CS_DQUOTE);
548 sbuilder.Append(Rest.CS_COMMA);
549 }
550
551 if (opaque != null)
552 {
553 sbuilder.Append("opaque=");
554 sbuilder.Append(Rest.CS_DQUOTE);
555 sbuilder.Append(opaque);
556 sbuilder.Append(Rest.CS_DQUOTE);
557 sbuilder.Append(Rest.CS_COMMA);
558 }
559
560 if (stale != null)
561 {
562 sbuilder.Append("stale=");
563 sbuilder.Append(Rest.CS_DQUOTE);
564 sbuilder.Append(stale);
565 sbuilder.Append(Rest.CS_DQUOTE);
566 sbuilder.Append(Rest.CS_COMMA);
567 }
568
569 if (alg != null)
570 {
571 sbuilder.Append("algorithm=");
572 sbuilder.Append(alg);
573 sbuilder.Append(Rest.CS_COMMA);
574 }
575 856
576 if (qop != String.Empty) 857 }
577 { 858 while (false);
578 sbuilder.Append("qop=");
579 sbuilder.Append(Rest.CS_DQUOTE);
580 sbuilder.Append(qop);
581 sbuilder.Append(Rest.CS_DQUOTE);
582 sbuilder.Append(Rest.CS_COMMA);
583 }
584
585 if (auth != null)
586 {
587 sbuilder.Append(auth);
588 sbuilder.Append(Rest.CS_COMMA);
589 }
590
591 // We don;t know the userid that will be used
592 // so we cannot make any authentication domain
593 // assumptions. So the prefix will determine
594 // this.
595
596 sbuilder.Append("domain=");
597 sbuilder.Append(Rest.CS_DQUOTE);
598 sbuilder.Append(qprefix);
599 sbuilder.Append(Rest.CS_DQUOTE);
600 859
601 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
602 } 860 }
603 }
604
605 /// <summary>
606 /// This method provides validation in support of the BASIC
607 /// authentication method. This is not normaly expected to be
608 /// used, but is included for completeness (and because I tried
609 /// it first).
610 /// </summary>
611
612 private bool Validate(string user, string pass)
613 {
614 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
615
616 // Both values are required
617 861
618 if (user == null || pass == null)
619 return false;
620
621 // Eliminate any leading or trailing spaces
622 user = user.Trim();
623
624 return vetPassword(user, pass);
625 } 862 }
626 863
627 /// <summary> 864 /// <summary>
628 /// This mechanism is used by the digest authetnication mechanism 865 /// This mechanism is used by the digest authentication mechanism
629 /// to return the user's password. In fact, because the OpenSim 866 /// to return the user's password. In fact, because the OpenSim
630 /// user's passwords are already hashed, and the HTTP mechanism 867 /// user's passwords are already hashed, and the HTTP mechanism
631 /// does not supply an open password, the hashed passwords cannot 868 /// does not supply an open password, the hashed passwords cannot
632 /// be used unless the cliemt has used the same salting mechanism 869 /// be used unless the client has used the same salting mechanism
633 /// to has the password before using it in the authentication 870 /// to has the password before using it in the authentication
634 /// algorithm. This is not inconceivable... 871 /// algorithn. This is not inconceivable...
635 /// </summary> 872 /// </summary>
636 873
637 private string getPassword(string user) 874 private string getPassword(string user)
638 { 875 {
876
639 int x; 877 int x;
640 string first; 878 string first;
641 string last; 879 string last;
642 880
643 // Distinguish the parts, if necessary 881 // Distinguish the parts, if necessary
644 882
645 if ((x=user.IndexOf(Rest.C_SPACE)) != -1) 883 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
646 { 884 {
647 first = user.Substring(0,x); 885 first = user.Substring(0,x);
648 last = user.Substring(x+1); 886 last = user.Substring(x+1);
649 } 887 }
650 else 888 else
651 { 889 {
652 first = user; 890 first = user;
@@ -667,53 +905,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
667 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); 905 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
668 return udata.PasswordHash; 906 return udata.PasswordHash;
669 } 907 }
670 }
671 908
672 /// <summary>
673 /// This is used by the BASIC authentication scheme to calculate
674 /// the double hash used by OpenSim to encode user's passwords.
675 /// It returns true, if the supplied password is actually correct.
676 /// If the specified user-id is not recognized, but the password
677 /// matches the God password, then this is accepted as an admin
678 /// session.
679 /// </summary>
680
681 private bool vetPassword(string user, string pass)
682 {
683 int x;
684 string HA1;
685 string first;
686 string last;
687
688 // Distinguish the parts, if necessary
689
690 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
691 {
692 first = user.Substring(0,x);
693 last = user.Substring(x+1);
694 }
695 else
696 {
697 first = user;
698 last = String.Empty;
699 }
700
701 UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
702
703 // If we don;t recognize the user id, perhaps it is god?
704
705 if (udata == null)
706 return pass == Rest.GodKey;
707
708 HA1 = HashToString(pass);
709 HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
710
711 return (0 == sc.Compare(HA1, udata.PasswordHash));
712 } 909 }
713 910
714 // Validate the request-digest 911 // Validate the request-digest
912
715 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) 913 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
716 { 914 {
915
717 string patt = null; 916 string patt = null;
718 string payl = String.Empty; 917 string payl = String.Empty;
719 string KDS = null; 918 string KDS = null;
@@ -756,7 +955,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
756 HA2 = HashToString(patt); 955 HA2 = HashToString(patt);
757 956
758 // Generate Digest 957 // Generate Digest
759 958
760 if (qop != String.Empty) 959 if (qop != String.Empty)
761 { 960 {
762 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); 961 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
@@ -771,10 +970,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
771 // Compare the generated sequence with the original 970 // Compare the generated sequence with the original
772 971
773 return (0 == sc.Compare(KDS, response)); 972 return (0 == sc.Compare(KDS, response));
973
774 } 974 }
775 975
776 private string HashToString(string pattern) 976 private string HashToString(string pattern)
777 { 977 {
978
778 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); 979 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
779 980
780 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); 981 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
@@ -789,62 +990,98 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
789 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); 990 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
790 991
791 return sbuilder.ToString(); 992 return sbuilder.ToString();
993
792 } 994 }
793 995
996#endregion authentication_digest
997
998#region service_interface
999
1000 /// <summary>
1001 /// Conditionally set a normal completion code. This allows a normal
1002 /// execution path to default.
1003 /// </summary>
1004
794 internal void Complete() 1005 internal void Complete()
795 { 1006 {
796 statusCode = Rest.HttpStatusCodeOK; 1007 if (statusCode == 0)
797 statusDescription = Rest.HttpStatusDescOK; 1008 {
1009 statusCode = Rest.HttpStatusCodeOK;
1010 }
798 } 1011 }
799 1012
1013 /// <summary>
1014 /// Indicate a functionally-dependent conclusion to the
1015 /// request. See Rest.cs for a list of possible values.
1016 /// </summary>
1017
1018 internal void Complete(int code)
1019 {
1020 statusCode = code;
1021 }
1022
1023 /// <summary>
1024 /// Indicate that a request should be redirected, using
1025 /// the HTTP completion codes. Permanent and temporary
1026 /// redirections may be indicated. The supplied URL is
1027 /// the new location of the resource.
1028 /// </summary>
1029
800 internal void Redirect(string Url, bool temp) 1030 internal void Redirect(string Url, bool temp)
801 { 1031 {
1032
802 redirectLocation = Url; 1033 redirectLocation = Url;
803 1034
804 if (temp) 1035 if (temp)
805 { 1036 {
806 statusCode = Rest.HttpStatusCodeTemporaryRedirect; 1037 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
807 statusDescription = Rest.HttpStatusDescTemporaryRedirect;
808 } 1038 }
809 else 1039 else
810 { 1040 {
811 statusCode = Rest.HttpStatusCodePermanentRedirect; 1041 statusCode = Rest.HttpStatusCodePermanentRedirect;
812 statusDescription = Rest.HttpStatusDescPermanentRedirect;
813 } 1042 }
814 1043
815 Fail(statusCode, statusDescription, true); 1044 Fail(statusCode, String.Empty, true);
1045
816 } 1046 }
817 1047
818 // Fail for an arbitrary reason. Just a failure with 1048 /// <summary>
819 // headers. 1049 /// Fail for an arbitrary reason. Just a failure with
1050 /// headers. The supplied message will be returned in the
1051 /// message body.
1052 /// </summary>
820 1053
821 internal void Fail(int code, string message) 1054 internal void Fail(int code)
822 { 1055 {
823 Fail(code, message, true); 1056 Fail(code, String.Empty, false);
824 } 1057 }
825 1058
826 // More adventurous. This failure also includes a 1059 /// <summary>
827 // specified entity. 1060 /// For the more adventurous. This failure also includes a
1061 /// specified entity to be appended to the code-related
1062 /// status string.
1063 /// </summary>
828 1064
829 internal void Fail(int code, string message, string data) 1065 internal void Fail(int code, string addendum)
830 { 1066 {
831 buffer = null; 1067 Fail(code, addendum, false);
832 body = data;
833 Fail(code, message, false);
834 } 1068 }
835 1069
836 internal void Fail(int code, string message, bool reset) 1070 internal void Fail(int code, string addendum, bool reset)
837 { 1071 {
1072
838 statusCode = code; 1073 statusCode = code;
839 statusDescription = message; 1074 appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));
1075
1076 // Add any final addendum to the status information
840 1077
841 if (reset) 1078 if (addendum != String.Empty)
842 { 1079 {
843 buffer = null; 1080 appendStatus(String.Format(addendum));
844 SendHtml(message);
845 body = html;
846 } 1081 }
847 1082
1083 // Help us understand why the request is being rejected
1084
848 if (Rest.DEBUG) 1085 if (Rest.DEBUG)
849 { 1086 {
850 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); 1087 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
@@ -864,42 +1101,61 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
864 1101
865 fail = true; 1102 fail = true;
866 1103
867 Respond("Failure response"); 1104 // Respond to the client's request, tag the response (for the
1105 // benefit of trace) to indicate the reason.
1106
1107 Respond(String.Format("Failure response: ({0}) : {1}",
1108 code, Rest.HttpStatusDesc[code]));
1109
1110 // Finally initialize and the throw a RestException. All of the
1111 // handler's infrastructure knows that this is a "normal"
1112 // completion from a code point-of-view.
868 1113
869 RestException re = new RestException(message+" <"+code+">"); 1114 RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");
870 1115
871 re.statusCode = code; 1116 re.statusCode = code;
872 re.statusDesc = message; 1117 re.statusDesc = Rest.HttpStatusDesc[code];
873 re.httpmethod = method; 1118 re.httpmethod = method;
874 re.httppath = path; 1119 re.httppath = path;
875 1120
876 throw re; 1121 throw re;
1122
877 } 1123 }
878 1124
879 // Reject this request 1125 // Reject this request
880 1126
881 internal void Reject() 1127 internal void Reject()
882 { 1128 {
883 Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented); 1129 Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
884 } 1130 }
885 1131
886 // This MUST be called by an agent handler before it returns 1132 // This MUST be called by an agent handler before it returns
887 // control to Handle, otherwise the request will be ignored. 1133 // control to Handle, otherwise the request will be ignored.
888 // This is called implciitly for the REST stream handlers and 1134 // This is called implciitly for the REST stream handlers and
889 // is harmless if it is called twice. 1135 // is harmless if it is called twice.
890 1136
891 internal virtual bool Respond(string reason) 1137 internal virtual bool Respond(string reason)
892 { 1138 {
1139
1140
893 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); 1141 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
894 1142
1143 // We do this to try and make multiple Respond requests harmless,
1144 // as it is sometimes convenient to isse a response without
1145 // certain knowledge that it has not previously been done.
1146
895 if (!handled) 1147 if (!handled)
896 { 1148 {
1149
897 Rest.Log.DebugFormat("{0} Generating Response", MsgId); 1150 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
898 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); 1151 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
899 1152
900 // A Head request can NOT have a body! 1153 // A Head request can NOT have a body! So don't waste time on
1154 // formatting if we're going to reject it anyway!
1155
901 if (method != Rest.HEAD) 1156 if (method != Rest.HEAD)
902 { 1157 {
1158
903 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); 1159 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
904 1160
905 // If the writer is non-null then we know that an XML 1161 // If the writer is non-null then we know that an XML
@@ -924,18 +1180,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
924 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); 1180 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
925 } 1181 }
926 1182
927 // If buffer != null, then we assume that 1183 if (buffer == null && body != null)
928 // this has already been done some other
929 // way. For example, transfer encoding might
930 // have been done.
931
932 if (buffer == null)
933 { 1184 {
934 if (body != null && body.Length > 0) 1185 buffer = encoding.GetBytes(body);
935 { 1186 AddHeader("Content-Type",bodyType);
936 Rest.Log.DebugFormat("{0} String-based entity", MsgId);
937 buffer = encoding.GetBytes(body);
938 }
939 } 1187 }
940 1188
941 // OK, if the buffer contains something, regardless of how 1189 // OK, if the buffer contains something, regardless of how
@@ -944,21 +1192,37 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
944 if (buffer != null) 1192 if (buffer != null)
945 { 1193 {
946 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); 1194 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
947 response.ContentLength64 = buffer.Length;
948 } 1195 }
949 else 1196 else
950 { 1197 {
951 response.ContentLength64 = 0; 1198 if (statusBody != String.Empty)
1199 {
1200 statusBody += Rest.statusTail;
1201 buffer = encoding.GetBytes(statusBody);
1202 AddHeader("Content-Type","text/html");
1203 }
1204 else
1205 {
1206 statusBody = Rest.statusHead;
1207 appendStatus(String.Format(": ({0}) {1}",
1208 statusCode, Rest.HttpStatusDesc[statusCode]));
1209 statusBody += Rest.statusTail;
1210 buffer = encoding.GetBytes(statusBody);
1211 AddHeader("Content-Type","text/html");
1212 }
952 } 1213 }
953 1214
1215 response.ContentLength64 = buffer.Length;
1216
954 if (response.Headers.Get("Content-Encoding") == null) 1217 if (response.Headers.Get("Content-Encoding") == null)
955 response.ContentEncoding = encoding; 1218 response.ContentEncoding = encoding;
956 1219
957 response.SendChunked = chunked; 1220 response.SendChunked = chunked;
958 response.KeepAlive = keepAlive; 1221 response.KeepAlive = keepAlive;
1222
959 } 1223 }
960 1224
961 // Set the status code & description. If nothing has been stored, 1225 // Set the status code & description. If nothing has been stored,
962 // we consider that a success. 1226 // we consider that a success.
963 1227
964 if (statusCode == 0) 1228 if (statusCode == 0)
@@ -972,7 +1236,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
972 1236
973 // For a redirect we need to set the relocation header accordingly 1237 // For a redirect we need to set the relocation header accordingly
974 1238
975 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || 1239 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
976 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) 1240 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
977 { 1241 {
978 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); 1242 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
@@ -981,18 +1245,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
981 1245
982 // And include the status description if provided. 1246 // And include the status description if provided.
983 1247
984 if (statusDescription != null) 1248 response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];
985 {
986 Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription);
987 response.StatusDescription = statusDescription;
988 }
989 1249
990 // Finally we send back our response. 1250 // Finally we send back our response.
991 1251
992 // We've left the setting of handled' until the 1252 // We've left the setting of handled' until the
993 // last minute because the header settings included 1253 // last minute because the header settings included
994 // above are pretty harmless. But everything from 1254 // above are pretty harmless. But everything from
995 // here on down probably leaves the response 1255 // here on down probably leaves the response
996 // element unusable by anyone else. 1256 // element unusable by anyone else.
997 1257
998 handled = true; 1258 handled = true;
@@ -1007,7 +1267,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1007 1267
1008 if (buffer != null && buffer.Length != 0) 1268 if (buffer != null && buffer.Length != 0)
1009 { 1269 {
1010 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", 1270 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
1011 MsgId, buffer.Length, encoding.GetString(buffer)); 1271 MsgId, buffer.Length, encoding.GetString(buffer));
1012 response.OutputStream.Write(buffer, 0, buffer.Length); 1272 response.OutputStream.Write(buffer, 0, buffer.Length);
1013 } 1273 }
@@ -1016,35 +1276,36 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1016 1276
1017 Rest.Log.DebugFormat("{0} Closing output stream", MsgId); 1277 Rest.Log.DebugFormat("{0} Closing output stream", MsgId);
1018 response.OutputStream.Close(); 1278 response.OutputStream.Close();
1279
1019 } 1280 }
1020 1281
1021 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); 1282 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
1022 1283
1023 return handled; 1284 return handled;
1285
1024 } 1286 }
1025 1287
1026 // Add a header to the table. We need to allow 1288 /// <summary>
1027 // multiple instances of many of the headers. 1289 /// These methods allow a service provider to manipulate the
1028 // If the 1290 /// request/response headers. The DumpHeaders method is intended
1291 /// for problem diagnosis.
1292 /// </summary>
1029 1293
1030 internal void AddHeader(string hdr, string data) 1294 internal void AddHeader(string hdr, string data)
1031 { 1295 {
1032 if (Rest.DEBUG) 1296 if (Rest.DEBUG)
1033 { 1297 {
1034 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", 1298 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
1035 MsgId, hdr, data); 1299 MsgId, hdr, data);
1036 if (response.Headers.Get(hdr) != null) 1300 if (response.Headers.Get(hdr) != null)
1037 { 1301 {
1038 Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>", 1302 Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>",
1039 MsgId, hdr); 1303 MsgId, hdr);
1040 } 1304 }
1041 } 1305 }
1042 response.Headers.Add(hdr, data); 1306 response.Headers.Add(hdr, data);
1043 } 1307 }
1044 1308
1045 // Keep explicit track of any headers which
1046 // are to be removed.
1047
1048 internal void RemoveHeader(string hdr) 1309 internal void RemoveHeader(string hdr)
1049 { 1310 {
1050 if (Rest.DEBUG) 1311 if (Rest.DEBUG)
@@ -1052,29 +1313,69 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1052 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); 1313 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
1053 if (response.Headers.Get(hdr) == null) 1314 if (response.Headers.Get(hdr) == null)
1054 { 1315 {
1055 Rest.Log.DebugFormat("{0} No such header existed", 1316 Rest.Log.DebugFormat("{0} No such header existed",
1056 MsgId, hdr); 1317 MsgId, hdr);
1057 } 1318 }
1058 } 1319 }
1059 response.Headers.Remove(hdr); 1320 response.Headers.Remove(hdr);
1060 } 1321 }
1061 1322
1062 /// <summary>
1063 /// Dump headers that will be generated in the response
1064 /// </summary>
1065
1066 internal void DumpHeaders() 1323 internal void DumpHeaders()
1067 { 1324 {
1068 if (Rest.DEBUG) 1325 if (Rest.DEBUG)
1069 { 1326 {
1070 for (int i=0;i<response.Headers.Count;i++) 1327 for (int i=0;i<response.Headers.Count;i++)
1071 { 1328 {
1072 Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, 1329 Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
1073 response.Headers.Get(i)); 1330 response.Headers.Get(i));
1074 } 1331 }
1075 } 1332 }
1076 } 1333 }
1077 1334
1335 // Setup the XML writer for output
1336
1337 internal void initXmlWriter()
1338 {
1339 XmlWriterSettings settings = new XmlWriterSettings();
1340 xmldata = new MemoryStream();
1341 settings.Indent = true;
1342 settings.IndentChars = " ";
1343 settings.Encoding = encoding;
1344 settings.CloseOutput = false;
1345 settings.OmitXmlDeclaration = true;
1346 settings.ConformanceLevel = ConformanceLevel.Fragment;
1347 writer = XmlWriter.Create(xmldata, settings);
1348 }
1349
1350 internal void initXmlReader()
1351 {
1352
1353 XmlReaderSettings settings = new XmlReaderSettings();
1354
1355 settings.ConformanceLevel = ConformanceLevel.Fragment;
1356 settings.IgnoreComments = true;
1357 settings.IgnoreWhitespace = true;
1358 settings.IgnoreProcessingInstructions = true;
1359 settings.ValidationType = ValidationType.None;
1360
1361 reader = XmlReader.Create(request.InputStream,settings);
1362
1363 }
1364
1365 internal void appendStatus(string msg)
1366 {
1367 if (statusBody == String.Empty)
1368 {
1369 statusBody = String.Format(Rest.statusHead, request.HttpMethod);
1370 }
1371
1372 statusBody = String.Format("{0} {1}", statusBody, msg);
1373 }
1374
1375#endregion service_interface
1376
1377#region internal_methods
1378
1078 /// <summary> 1379 /// <summary>
1079 /// Helper methods for deconstructing and reconstructing 1380 /// Helper methods for deconstructing and reconstructing
1080 /// URI path data. 1381 /// URI path data.
@@ -1082,6 +1383,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1082 1383
1083 private void initUrl() 1384 private void initUrl()
1084 { 1385 {
1386
1085 uri = request.Url; 1387 uri = request.Url;
1086 1388
1087 if (query == null) 1389 if (query == null)
@@ -1096,19 +1398,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1096 { 1398 {
1097 path = uri.AbsolutePath; 1399 path = uri.AbsolutePath;
1098 if (path.EndsWith(Rest.UrlPathSeparator)) 1400 if (path.EndsWith(Rest.UrlPathSeparator))
1099 path = path.Substring(0, path.Length-1); 1401 path = path.Substring(0,path.Length-1);
1100 } 1402 }
1101 1403
1102 // If we succeeded in getting a path, perform any 1404 // If we succeeded in getting a path, perform any
1103 // additional pre-processing required. 1405 // additional pre-processing required.
1104 1406
1105 if (path != null) 1407 if (path != null)
1106 { 1408 {
1107 if (Rest.ExtendedEscape) 1409 if (Rest.ExtendedEscape)
1108 { 1410 {
1109 // Handle "+". Not a standard substitution, but 1411 // Handle "+". Not a standard substitution, but
1110 // common enough... 1412 // common enough...
1111 path = path.Replace(Rest.C_PLUS, Rest.C_SPACE); 1413 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1112 } 1414 }
1113 pathNodes = path.Split(Rest.CA_PATHSEP); 1415 pathNodes = path.Split(Rest.CA_PATHSEP);
1114 } 1416 }
@@ -1126,10 +1428,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1126 1428
1127 hostname = uri.Host; 1429 hostname = uri.Host;
1128 port = uri.Port; 1430 port = uri.Port;
1431
1129 } 1432 }
1130 1433
1131 internal int initParameters(int prfxlen) 1434 private int initParameters(int prfxlen)
1132 { 1435 {
1436
1133 if (prfxlen < path.Length-1) 1437 if (prfxlen < path.Length-1)
1134 { 1438 {
1135 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); 1439 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
@@ -1138,139 +1442,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1138 { 1442 {
1139 parameters = new string[0]; 1443 parameters = new string[0];
1140 } 1444 }
1141 1445
1142 // Generate a debug list of the decoded parameters 1446 // Generate a debug list of the decoded parameters
1143 1447
1144 if (Rest.DEBUG && prfxlen < path.Length-1) 1448 if (Rest.DEBUG && prfxlen < path.Length-1)
1145 { 1449 {
1146 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); 1450 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1147 for (int i = 0; i < parameters.Length; i++) 1451 for (int i = 0; i < parameters.Length; i++)
1148 { 1452 {
1149 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); 1453 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1150 } 1454 }
1151 } 1455 }
1152 1456
1153 return parameters.Length; 1457 return parameters.Length;
1154 }
1155 1458
1156 internal string[] PathNodes
1157 {
1158 get
1159 {
1160 if (pathNodes == null)
1161 {
1162 initUrl();
1163 }
1164 return pathNodes;
1165 }
1166 } 1459 }
1167 1460
1168 internal string BuildUrl(int first, int last) 1461#endregion internal_methods
1169 {
1170 if (pathNodes == null)
1171 {
1172 initUrl();
1173 }
1174
1175 if (first < 0)
1176 {
1177 first = first + pathNodes.Length;
1178 }
1179 1462
1180 if (last < 0)
1181 {
1182 last = last + pathNodes.Length;
1183 if (last < 0)
1184 {
1185 return Rest.UrlPathSeparator;
1186 }
1187 }
1188
1189 sbuilder.Length = 0;
1190 sbuilder.Append(Rest.UrlPathSeparator);
1191
1192 if (first <= last)
1193 {
1194 for (int i = first; i <= last; i++)
1195 {
1196 sbuilder.Append(pathNodes[i]);
1197 sbuilder.Append(Rest.UrlPathSeparator);
1198 }
1199 }
1200 else
1201 {
1202 for (int i = last; i >= first; i--)
1203 {
1204 sbuilder.Append(pathNodes[i]);
1205 sbuilder.Append(Rest.UrlPathSeparator);
1206 }
1207 }
1208
1209 return sbuilder.ToString();
1210 }
1211
1212 // Setup the XML writer for output
1213
1214 internal void initXmlWriter()
1215 {
1216 XmlWriterSettings settings = new XmlWriterSettings();
1217 xmldata = new MemoryStream();
1218 settings.Indent = true;
1219 settings.IndentChars = " ";
1220 settings.Encoding = encoding;
1221 settings.CloseOutput = false;
1222 settings.OmitXmlDeclaration = true;
1223 settings.ConformanceLevel = ConformanceLevel.Fragment;
1224 writer = XmlWriter.Create(xmldata, settings);
1225 }
1226
1227 internal void initXmlReader()
1228 {
1229 XmlReaderSettings settings = new XmlReaderSettings();
1230
1231 settings.ConformanceLevel = ConformanceLevel.Fragment;
1232 settings.IgnoreComments = true;
1233 settings.IgnoreWhitespace = true;
1234 settings.IgnoreProcessingInstructions = true;
1235 settings.ValidationType = ValidationType.None;
1236
1237 reader = XmlReader.Create(request.InputStream,settings);
1238 }
1239
1240 private void Flush()
1241 {
1242 byte[] dbuffer = new byte[8192];
1243 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
1244 }
1245
1246 // This allows us to make errors a bit more apparent in REST
1247
1248 internal void SendHtml(string text)
1249 {
1250 SendHtml("OpenSim REST Interface 1.0", text);
1251 }
1252
1253 internal void SendHtml(string title, string text)
1254 {
1255 AddHeader(Rest.HttpHeaderContentType, "text/html");
1256 sbuilder.Length = 0;
1257
1258 sbuilder.Append("<html>");
1259 sbuilder.Append("<head>");
1260 sbuilder.Append("<title>");
1261 sbuilder.Append(title);
1262 sbuilder.Append("</title>");
1263 sbuilder.Append("</head>");
1264
1265 sbuilder.Append("<body>");
1266 sbuilder.Append("<br />");
1267 sbuilder.Append("<p>");
1268 sbuilder.Append(text);
1269 sbuilder.Append("</p>");
1270 sbuilder.Append("</body>");
1271 sbuilder.Append("</html>");
1272
1273 html = sbuilder.ToString();
1274 }
1275 } 1463 }
1276} 1464}