diff options
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs')
-rw-r--r-- | OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs | 1465 |
1 files changed, 0 insertions, 1465 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs deleted file mode 100644 index 10f1a6e..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ /dev/null | |||
@@ -1,1465 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Security.Cryptography; | ||
32 | using System.Text; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using System.Xml; | ||
35 | using OpenSim.Framework; | ||
36 | using OpenSim.Framework.Servers; | ||
37 | using OpenSim.Framework.Servers.HttpServer; | ||
38 | using OpenSim.Services.Interfaces; | ||
39 | |||
40 | using OpenMetaverse; | ||
41 | |||
42 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
43 | { | ||
44 | |||
45 | /// <summary> | ||
46 | /// This class represents the current REST request. It | ||
47 | /// encapsulates the request/response state and takes care | ||
48 | /// of response generation without exposing the REST handler | ||
49 | /// to the actual mechanisms involved. | ||
50 | /// | ||
51 | /// This structure is created on entry to the Handler | ||
52 | /// method and is disposed of upon return. It is part of | ||
53 | /// the plug-in infrastructure, rather than the functionally | ||
54 | /// specific REST handler, and fundamental changes to | ||
55 | /// this should be reflected in the Rest HandlerVersion. The | ||
56 | /// object is instantiated, and may be extended by, any | ||
57 | /// given handler. See the inventory handler for an example | ||
58 | /// of this. | ||
59 | /// | ||
60 | /// If possible, the underlying request/response state is not | ||
61 | /// changed until the handler explicitly issues a Respond call. | ||
62 | /// This ensures that the request/response pair can be safely | ||
63 | /// processed by subsequent, unrelated, handlers even id the | ||
64 | /// agent handler had completed much of its processing. Think | ||
65 | /// of it as a transactional req/resp capability. | ||
66 | /// </summary> | ||
67 | |||
68 | public class RequestData | ||
69 | { | ||
70 | |||
71 | // HTTP Server interface data (Received values) | ||
72 | |||
73 | internal OSHttpRequest request = null; | ||
74 | internal OSHttpResponse response = null; | ||
75 | internal string qprefix = null; | ||
76 | |||
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(). | ||
89 | |||
90 | internal byte[] buffer = null; | ||
91 | internal string body = null; | ||
92 | internal string bodyType = "text/html"; | ||
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 | |||
104 | internal string path = null; | ||
105 | internal string method = null; | ||
106 | internal Uri uri = null; | ||
107 | internal string query = null; | ||
108 | internal string hostname = "localhost"; | ||
109 | internal int port = 80; | ||
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 | |||
142 | internal bool keepAlive = false; | ||
143 | internal bool chunked = false; | ||
144 | |||
145 | // XML related state | ||
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 | |||
170 | internal bool authenticated = false; | ||
171 | internal string scheme = Rest.Scheme; | ||
172 | internal string realm = Rest.Realm; | ||
173 | internal string domain = null; | ||
174 | internal string nonce = null; | ||
175 | internal string cnonce = null; | ||
176 | internal string qop = Rest.Qop_Auth; | ||
177 | internal string opaque = null; | ||
178 | internal string stale = null; | ||
179 | internal string algorithm = Rest.Digest_MD5; | ||
180 | internal string authParms = null; | ||
181 | internal string authPrefix = null; | ||
182 | internal string userName = String.Empty; | ||
183 | internal string userPass = String.Empty; | ||
184 | |||
185 | // Session related tables. These are only needed if QOP is set to "auth-sess" | ||
186 | // and for now at least, it is not. Session related authentication is of | ||
187 | // questionable merit in the context of REST anyway, but it is, arguably, more | ||
188 | // secure. | ||
189 | |||
190 | private static Dictionary<string,string> cntable = new Dictionary<string,string>(); | ||
191 | private static Dictionary<string,string> sktable = new Dictionary<string,string>(); | ||
192 | |||
193 | // This dictionary is used to keep track fo all of the parameters discovered | ||
194 | // when the authorisation header is anaylsed. | ||
195 | |||
196 | private Dictionary<string,string> authparms = new Dictionary<string,string>(); | ||
197 | |||
198 | // These regular expressions are used to decipher the various header entries. | ||
199 | |||
200 | private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*", | ||
201 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||
202 | |||
203 | private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", | ||
204 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||
205 | |||
206 | private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", | ||
207 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||
208 | |||
209 | private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", | ||
210 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||
211 | |||
212 | private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)", | ||
213 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | ||
214 | |||
215 | // For efficiency, we create static instances of these objects | ||
216 | |||
217 | private static MD5 md5hash = MD5.Create(); | ||
218 | |||
219 | private static StringComparer sc = StringComparer.OrdinalIgnoreCase; | ||
220 | |||
221 | #region properties | ||
222 | |||
223 | // Just for convenience... | ||
224 | |||
225 | internal string MsgId | ||
226 | { | ||
227 | get { return Rest.MsgId; } | ||
228 | } | ||
229 | |||
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> | ||
235 | |||
236 | internal bool IsAuthenticated | ||
237 | { | ||
238 | get | ||
239 | { | ||
240 | if (Rest.Authenticate) | ||
241 | { | ||
242 | if (!authenticated) | ||
243 | { | ||
244 | authenticate(); | ||
245 | } | ||
246 | |||
247 | return authenticated; | ||
248 | } | ||
249 | else return true; | ||
250 | } | ||
251 | } | ||
252 | |||
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 | |||
297 | if (encoding == null) | ||
298 | { | ||
299 | encoding = Rest.Encoding; | ||
300 | } | ||
301 | |||
302 | method = request.HttpMethod.ToLower(); | ||
303 | initUrl(); | ||
304 | |||
305 | initParameters(p_qprefix.Length); | ||
306 | |||
307 | } | ||
308 | |||
309 | #endregion constructors | ||
310 | |||
311 | #region authentication_common | ||
312 | |||
313 | /// <summary> | ||
314 | /// The REST handler has requested authentication. Authentication | ||
315 | /// is considered to be with respect to the current values for | ||
316 | /// Realm, domain, etc. | ||
317 | /// | ||
318 | /// This method checks to see if the current request is already | ||
319 | /// authenticated for this domain. If it is, then it returns | ||
320 | /// true. If it is not, then it issues a challenge to the client | ||
321 | /// and responds negatively to the request. | ||
322 | /// | ||
323 | /// As soon as authentication failure is detected the method calls | ||
324 | /// DoChallenge() which terminates the request with REST exception | ||
325 | /// for unauthroized access. | ||
326 | /// </summary> | ||
327 | |||
328 | private void authenticate() | ||
329 | { | ||
330 | |||
331 | string authdata = request.Headers.Get("Authorization"); | ||
332 | string reqscheme = String.Empty; | ||
333 | |||
334 | // If we don't have an authorization header, then this | ||
335 | // user is certainly not authorized. This is the typical | ||
336 | // pivot for the 1st request by a client. | ||
337 | |||
338 | if (authdata == null) | ||
339 | { | ||
340 | Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); | ||
341 | DoChallenge(); | ||
342 | } | ||
343 | |||
344 | // So, we have authentication data, now we have to check to | ||
345 | // see what we got and whether or not it is valid for the | ||
346 | // current domain. To do this we need to interpret the data | ||
347 | // provided in the Authorization header. First we need to | ||
348 | // identify the scheme being used and route accordingly. | ||
349 | |||
350 | MatchCollection matches = schema.Matches(authdata); | ||
351 | |||
352 | foreach (Match m in matches) | ||
353 | { | ||
354 | Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value); | ||
355 | reqscheme = m.Groups["scheme"].Value.ToLower(); | ||
356 | } | ||
357 | |||
358 | // If we want a specific authentication mechanism, make sure | ||
359 | // we get it. null indicates we don't care. non-null indicates | ||
360 | // a specific scheme requirement. | ||
361 | |||
362 | if (scheme != null && scheme.ToLower() != reqscheme) | ||
363 | { | ||
364 | Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId); | ||
365 | DoChallenge(); | ||
366 | } | ||
367 | |||
368 | // In the future, these could be made into plug-ins... | ||
369 | // But for now at least we have no reason to use anything other | ||
370 | // then MD5. TLS/SSL are taken care of elsewhere. | ||
371 | |||
372 | switch (reqscheme) | ||
373 | { | ||
374 | case "digest" : | ||
375 | Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); | ||
376 | DoDigest(authdata); | ||
377 | break; | ||
378 | |||
379 | case "basic" : | ||
380 | Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); | ||
381 | DoBasic(authdata); | ||
382 | break; | ||
383 | } | ||
384 | |||
385 | // If the current header is invalid, then a challenge is still needed. | ||
386 | |||
387 | if (!authenticated) | ||
388 | { | ||
389 | Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); | ||
390 | DoChallenge(); | ||
391 | } | ||
392 | |||
393 | } | ||
394 | |||
395 | /// <summary> | ||
396 | /// Construct the necessary WWW-Authenticate headers and fail the request | ||
397 | /// with a NOT AUTHORIZED response. The parameters are the union of values | ||
398 | /// required by the supported schemes. | ||
399 | /// </summary> | ||
400 | |||
401 | private void DoChallenge() | ||
402 | { | ||
403 | Flush(); | ||
404 | nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) | ||
405 | Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); | ||
406 | Fail(Rest.HttpStatusCodeNotAuthorized); | ||
407 | } | ||
408 | |||
409 | /// <summary> | ||
410 | /// The Flush() call is here to support a problem encountered with the | ||
411 | /// client where an authentication rejection was lost because the rejection | ||
412 | /// may flow before the clienthas finished sending us the inbound data stream, | ||
413 | /// in which case the client responds to the socket error on out put, and | ||
414 | /// never sees the authentication challenge. The client should be fixed, | ||
415 | /// because this solution leaves the server prone to DOS attacks. A message | ||
416 | /// will be issued whenever flushing occurs. It can be enabled/disabled from | ||
417 | /// the configuration file. | ||
418 | /// </summary> | ||
419 | |||
420 | private void Flush() | ||
421 | { | ||
422 | if (Rest.FlushEnabled) | ||
423 | { | ||
424 | byte[] dbuffer = new byte[8192]; | ||
425 | Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId); | ||
426 | while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0); | ||
427 | } | ||
428 | return; | ||
429 | } | ||
430 | |||
431 | // Indicate that authentication is required | ||
432 | |||
433 | private void Challenge(string scheme, string realm, string domain, string nonce, | ||
434 | string opaque, string stale, string alg, | ||
435 | string qop, string auth) | ||
436 | { | ||
437 | |||
438 | sbuilder.Length = 0; | ||
439 | |||
440 | // The service provider can force a particular scheme by | ||
441 | // assigning a value to scheme. | ||
442 | |||
443 | // Basic authentication is pretty simple. | ||
444 | // Just specify the realm in question. | ||
445 | |||
446 | if (scheme == null || scheme == Rest.AS_BASIC) | ||
447 | { | ||
448 | |||
449 | sbuilder.Append(Rest.AS_BASIC); | ||
450 | |||
451 | if (realm != null) | ||
452 | { | ||
453 | sbuilder.Append(" realm="); | ||
454 | sbuilder.Append(Rest.CS_DQUOTE); | ||
455 | sbuilder.Append(realm); | ||
456 | sbuilder.Append(Rest.CS_DQUOTE); | ||
457 | } | ||
458 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
459 | } | ||
460 | |||
461 | sbuilder.Length = 0; | ||
462 | |||
463 | // Digest authentication takes somewhat more | ||
464 | // to express. | ||
465 | |||
466 | if (scheme == null || scheme == Rest.AS_DIGEST) | ||
467 | { | ||
468 | |||
469 | sbuilder.Append(Rest.AS_DIGEST); | ||
470 | sbuilder.Append(" "); | ||
471 | |||
472 | // Specify the effective realm. This should | ||
473 | // never be null if we are uthenticating, as it is required for all | ||
474 | // authentication schemes. It defines, in conjunction with the | ||
475 | // absolute URI information, the domain to which the authentication | ||
476 | // applies. It is an arbitrary string. I *believe* this allows an | ||
477 | // authentication to apply to disjoint resources within the same | ||
478 | // server. | ||
479 | |||
480 | if (realm != null) | ||
481 | { | ||
482 | sbuilder.Append("realm="); | ||
483 | sbuilder.Append(Rest.CS_DQUOTE); | ||
484 | sbuilder.Append(realm); | ||
485 | sbuilder.Append(Rest.CS_DQUOTE); | ||
486 | sbuilder.Append(Rest.CS_COMMA); | ||
487 | } | ||
488 | |||
489 | // Share our nonce. This is *uniquely* generated each time a 401 is | ||
490 | // returned. We do not generate a very sophisticated nonce at the | ||
491 | // moment (it's simply a base64 encoded UUID). | ||
492 | |||
493 | if (nonce != null) | ||
494 | { | ||
495 | sbuilder.Append("nonce="); | ||
496 | sbuilder.Append(Rest.CS_DQUOTE); | ||
497 | sbuilder.Append(nonce); | ||
498 | sbuilder.Append(Rest.CS_DQUOTE); | ||
499 | sbuilder.Append(Rest.CS_COMMA); | ||
500 | } | ||
501 | |||
502 | // The opaque string should be returned by the client unchanged in all | ||
503 | // subsequent requests. | ||
504 | |||
505 | if (opaque != null) | ||
506 | { | ||
507 | sbuilder.Append("opaque="); | ||
508 | sbuilder.Append(Rest.CS_DQUOTE); | ||
509 | sbuilder.Append(opaque); | ||
510 | sbuilder.Append(Rest.CS_DQUOTE); | ||
511 | sbuilder.Append(Rest.CS_COMMA); | ||
512 | } | ||
513 | |||
514 | // This flag indicates that the authentication was rejected because the | ||
515 | // included nonce was stale. The server might use timestamp information | ||
516 | // in the nonce to determine this. We do not. | ||
517 | |||
518 | if (stale != null) | ||
519 | { | ||
520 | sbuilder.Append("stale="); | ||
521 | sbuilder.Append(Rest.CS_DQUOTE); | ||
522 | sbuilder.Append(stale); | ||
523 | sbuilder.Append(Rest.CS_DQUOTE); | ||
524 | sbuilder.Append(Rest.CS_COMMA); | ||
525 | } | ||
526 | |||
527 | // Identifies the algorithm used to produce the digest and checksum. | ||
528 | // The default is MD5. | ||
529 | |||
530 | if (alg != null) | ||
531 | { | ||
532 | sbuilder.Append("algorithm="); | ||
533 | sbuilder.Append(alg); | ||
534 | sbuilder.Append(Rest.CS_COMMA); | ||
535 | } | ||
536 | |||
537 | // Theoretically QOP is optional, but it is required by a compliant | ||
538 | // with current versions of the scheme. In fact IE requires that QOP | ||
539 | // be specified and will refuse to authenticate otherwise. | ||
540 | |||
541 | if (qop != String.Empty) | ||
542 | { | ||
543 | sbuilder.Append("qop="); | ||
544 | sbuilder.Append(Rest.CS_DQUOTE); | ||
545 | sbuilder.Append(qop); | ||
546 | sbuilder.Append(Rest.CS_DQUOTE); | ||
547 | sbuilder.Append(Rest.CS_COMMA); | ||
548 | } | ||
549 | |||
550 | // This parameter allows for arbitrary extensions to the protocol. | ||
551 | // Unrecognized values should be simply ignored. | ||
552 | |||
553 | if (auth != null) | ||
554 | { | ||
555 | sbuilder.Append(auth); | ||
556 | sbuilder.Append(Rest.CS_COMMA); | ||
557 | } | ||
558 | |||
559 | // We don't know the userid that will be used | ||
560 | // so we cannot make any authentication domain | ||
561 | // assumptions. So the prefix will determine | ||
562 | // this. | ||
563 | |||
564 | sbuilder.Append("domain="); | ||
565 | sbuilder.Append(Rest.CS_DQUOTE); | ||
566 | sbuilder.Append(qprefix); | ||
567 | sbuilder.Append(Rest.CS_DQUOTE); | ||
568 | |||
569 | // Generate the authenticate header and we're basically | ||
570 | // done. | ||
571 | |||
572 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
573 | |||
574 | } | ||
575 | |||
576 | } | ||
577 | |||
578 | #endregion authentication_common | ||
579 | |||
580 | #region authentication_basic | ||
581 | |||
582 | /// <summary> | ||
583 | /// Interpret a BASIC authorization claim. Some clients can only | ||
584 | /// understand this and also expect it to be the first one | ||
585 | /// offered. So we do. | ||
586 | /// OpenSim also needs this, as it is the only scheme that allows | ||
587 | /// authentication using the hashed passwords stored in the | ||
588 | /// user database. | ||
589 | /// </summary> | ||
590 | |||
591 | private void DoBasic(string authdata) | ||
592 | { | ||
593 | |||
594 | string response = null; | ||
595 | |||
596 | MatchCollection matches = basicParms.Matches(authdata); | ||
597 | |||
598 | // In the case of basic authentication there is | ||
599 | // only expected to be a single argument. | ||
600 | |||
601 | foreach (Match m in matches) | ||
602 | { | ||
603 | authparms.Add("response",m.Groups["pval"].Value); | ||
604 | Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", | ||
605 | MsgId, "response", m.Groups["pval"].Value); | ||
606 | } | ||
607 | |||
608 | // Did we get a valid response? | ||
609 | |||
610 | if (authparms.TryGetValue("response", out response)) | ||
611 | { | ||
612 | // Decode | ||
613 | response = Rest.Base64ToString(response); | ||
614 | Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response); | ||
615 | |||
616 | // Extract user & password | ||
617 | Match m = reuserPass.Match(response); | ||
618 | userName = m.Groups["user"].Value; | ||
619 | userPass = m.Groups["pass"].Value; | ||
620 | |||
621 | // Validate against user database | ||
622 | authenticated = Validate(userName,userPass); | ||
623 | } | ||
624 | |||
625 | } | ||
626 | |||
627 | /// <summary> | ||
628 | /// This method provides validation in support of the BASIC | ||
629 | /// authentication method. This is not normaly expected to be | ||
630 | /// used, but is included for completeness (and because I tried | ||
631 | /// it first). | ||
632 | /// </summary> | ||
633 | |||
634 | private bool Validate(string user, string pass) | ||
635 | { | ||
636 | |||
637 | Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); | ||
638 | |||
639 | // Both values are required | ||
640 | |||
641 | if (user == null || pass == null) | ||
642 | return false; | ||
643 | |||
644 | // Eliminate any leading or trailing spaces | ||
645 | user = user.Trim(); | ||
646 | |||
647 | return vetPassword(user, pass); | ||
648 | |||
649 | } | ||
650 | |||
651 | /// <summary> | ||
652 | /// This is used by the BASIC authentication scheme to calculate | ||
653 | /// the double hash used by OpenSim to encode user's passwords. | ||
654 | /// It returns true, if the supplied password is actually correct. | ||
655 | /// If the specified user-id is not recognized, but the password | ||
656 | /// matches the God password, then this is accepted as an admin | ||
657 | /// session. | ||
658 | /// </summary> | ||
659 | |||
660 | private bool vetPassword(string user, string pass) | ||
661 | { | ||
662 | |||
663 | int x; | ||
664 | string first; | ||
665 | string last; | ||
666 | |||
667 | // Distinguish the parts, if necessary | ||
668 | |||
669 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
670 | { | ||
671 | first = user.Substring(0,x); | ||
672 | last = user.Substring(x+1); | ||
673 | } | ||
674 | else | ||
675 | { | ||
676 | first = user; | ||
677 | last = String.Empty; | ||
678 | } | ||
679 | |||
680 | UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); | ||
681 | |||
682 | // If we don't recognize the user id, perhaps it is god? | ||
683 | if (account == null) | ||
684 | return pass == Rest.GodKey; | ||
685 | |||
686 | return (Rest.AuthServices.Authenticate(account.PrincipalID, pass, 1) != string.Empty); | ||
687 | |||
688 | } | ||
689 | |||
690 | #endregion authentication_basic | ||
691 | |||
692 | #region authentication_digest | ||
693 | |||
694 | /// <summary> | ||
695 | /// This is an RFC2617 compliant HTTP MD5 Digest authentication | ||
696 | /// implementation. It has been tested with Firefox, Java HTTP client, | ||
697 | /// and Microsoft's Internet Explorer V7. | ||
698 | /// </summary> | ||
699 | |||
700 | private void DoDigest(string authdata) | ||
701 | { | ||
702 | |||
703 | string response = null; | ||
704 | |||
705 | // Find all of the values of the for x = "y" | ||
706 | |||
707 | MatchCollection matches = digestParm1.Matches(authdata); | ||
708 | |||
709 | foreach (Match m in matches) | ||
710 | { | ||
711 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); | ||
712 | Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", | ||
713 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); | ||
714 | } | ||
715 | |||
716 | // Find all of the values of the for x = y | ||
717 | |||
718 | matches = digestParm2.Matches(authdata); | ||
719 | |||
720 | foreach (Match m in matches) | ||
721 | { | ||
722 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); | ||
723 | Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", | ||
724 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); | ||
725 | } | ||
726 | |||
727 | // A response string MUST be returned, otherwise we are | ||
728 | // NOT authenticated. | ||
729 | |||
730 | Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId); | ||
731 | |||
732 | if (authparms.TryGetValue("response", out response)) | ||
733 | { | ||
734 | |||
735 | string temp = null; | ||
736 | |||
737 | do | ||
738 | { | ||
739 | |||
740 | string nck = null; | ||
741 | string ncl = null; | ||
742 | |||
743 | // The userid is sent in clear text. Needed for the | ||
744 | // verification. | ||
745 | |||
746 | authparms.TryGetValue("username", out userName); | ||
747 | |||
748 | // All URI's of which this is a prefix are | ||
749 | // optimistically considered to be authenticated by the | ||
750 | // client. This is also needed to verify the response. | ||
751 | |||
752 | authparms.TryGetValue("uri", out authPrefix); | ||
753 | |||
754 | // There MUST be a nonce string present. We're not preserving any server | ||
755 | // side state and we can't validate the MD5 unless the client returns it | ||
756 | // to us, as it should. | ||
757 | |||
758 | if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) | ||
759 | { | ||
760 | Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); | ||
761 | break; | ||
762 | } | ||
763 | |||
764 | // If there is an opaque string present, it had better | ||
765 | // match what we sent. | ||
766 | |||
767 | if (authparms.TryGetValue("opaque", out temp)) | ||
768 | { | ||
769 | if (temp != opaque) | ||
770 | { | ||
771 | Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); | ||
772 | break; | ||
773 | } | ||
774 | } | ||
775 | |||
776 | // If an algorithm string is present, it had better | ||
777 | // match what we sent. | ||
778 | |||
779 | if (authparms.TryGetValue("algorithm", out temp)) | ||
780 | { | ||
781 | if (temp != algorithm) | ||
782 | { | ||
783 | Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); | ||
784 | break; | ||
785 | } | ||
786 | } | ||
787 | |||
788 | // Quality of protection considerations... | ||
789 | |||
790 | if (authparms.TryGetValue("qop", out temp)) | ||
791 | { | ||
792 | |||
793 | qop = temp.ToLower(); // replace with actual value used | ||
794 | |||
795 | // if QOP was specified then | ||
796 | // these MUST be present. | ||
797 | |||
798 | if (!authparms.ContainsKey("cnonce")) | ||
799 | { | ||
800 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); | ||
801 | Fail(Rest.HttpStatusCodeBadRequest); | ||
802 | break; | ||
803 | } | ||
804 | |||
805 | cnonce = authparms["cnonce"]; | ||
806 | |||
807 | if (!authparms.TryGetValue("nc", out nck) || nck == null) | ||
808 | { | ||
809 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); | ||
810 | Fail(Rest.HttpStatusCodeBadRequest); | ||
811 | break; | ||
812 | } | ||
813 | |||
814 | Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId); | ||
815 | |||
816 | if (cntable.TryGetValue(nonce, out ncl)) | ||
817 | { | ||
818 | Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl); | ||
819 | |||
820 | if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) | ||
821 | { | ||
822 | Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); | ||
823 | Fail(Rest.HttpStatusCodeBadRequest); | ||
824 | break; | ||
825 | } | ||
826 | cntable[nonce] = nck; | ||
827 | } | ||
828 | else | ||
829 | { | ||
830 | lock (cntable) cntable.Add(nonce, nck); | ||
831 | } | ||
832 | |||
833 | } | ||
834 | else | ||
835 | { | ||
836 | |||
837 | qop = String.Empty; | ||
838 | |||
839 | // if QOP was not specified then | ||
840 | // these MUST NOT be present. | ||
841 | if (authparms.ContainsKey("cnonce")) | ||
842 | { | ||
843 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); | ||
844 | Fail(Rest.HttpStatusCodeBadRequest); | ||
845 | break; | ||
846 | } | ||
847 | if (authparms.ContainsKey("nc")) | ||
848 | { | ||
849 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); | ||
850 | Fail(Rest.HttpStatusCodeBadRequest); | ||
851 | break; | ||
852 | } | ||
853 | } | ||
854 | |||
855 | // Validate the supplied userid/password info | ||
856 | |||
857 | authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); | ||
858 | |||
859 | } | ||
860 | while (false); | ||
861 | |||
862 | } | ||
863 | else | ||
864 | Fail(Rest.HttpStatusCodeBadRequest); | ||
865 | |||
866 | } | ||
867 | |||
868 | /// <summary> | ||
869 | /// This mechanism is used by the digest authentication mechanism | ||
870 | /// to return the user's password. In fact, because the OpenSim | ||
871 | /// user's passwords are already hashed, and the HTTP mechanism | ||
872 | /// does not supply an open password, the hashed passwords cannot | ||
873 | /// be used unless the client has used the same salting mechanism | ||
874 | /// to has the password before using it in the authentication | ||
875 | /// algorithn. This is not inconceivable... | ||
876 | /// </summary> | ||
877 | |||
878 | private string getPassword(string user) | ||
879 | { | ||
880 | |||
881 | int x; | ||
882 | string first; | ||
883 | string last; | ||
884 | |||
885 | // Distinguish the parts, if necessary | ||
886 | |||
887 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
888 | { | ||
889 | first = user.Substring(0,x); | ||
890 | last = user.Substring(x+1); | ||
891 | } | ||
892 | else | ||
893 | { | ||
894 | first = user; | ||
895 | last = String.Empty; | ||
896 | } | ||
897 | |||
898 | UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); | ||
899 | // If we don;t recognize the user id, perhaps it is god? | ||
900 | |||
901 | if (account == null) | ||
902 | { | ||
903 | Rest.Log.DebugFormat("{0} Administrator", MsgId); | ||
904 | return Rest.GodKey; | ||
905 | } | ||
906 | else | ||
907 | { | ||
908 | Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); | ||
909 | |||
910 | // !!! REFACTORING PROBLEM | ||
911 | // This is what it was. It doesn't work in 0.7 | ||
912 | // Nothing retrieves the password from the authentication service, there's only authentication. | ||
913 | //return udata.PasswordHash; | ||
914 | return string.Empty; | ||
915 | } | ||
916 | |||
917 | } | ||
918 | |||
919 | // Validate the request-digest | ||
920 | |||
921 | private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) | ||
922 | { | ||
923 | |||
924 | string patt = null; | ||
925 | string payl = String.Empty; | ||
926 | string KDS = null; | ||
927 | string HA1 = null; | ||
928 | string HA2 = null; | ||
929 | string pass = getPassword(user); | ||
930 | |||
931 | // Generate H(A1) | ||
932 | |||
933 | if (algorithm == Rest.Digest_MD5Sess) | ||
934 | { | ||
935 | if (!sktable.ContainsKey(cnonce)) | ||
936 | { | ||
937 | patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce); | ||
938 | HA1 = HashToString(patt); | ||
939 | sktable.Add(cnonce, HA1); | ||
940 | } | ||
941 | else | ||
942 | { | ||
943 | HA1 = sktable[cnonce]; | ||
944 | } | ||
945 | } | ||
946 | else | ||
947 | { | ||
948 | patt = String.Format("{0}:{1}:{2}", user, realm, pass); | ||
949 | HA1 = HashToString(patt); | ||
950 | } | ||
951 | |||
952 | // Generate H(A2) | ||
953 | |||
954 | if (qop == "auth-int") | ||
955 | { | ||
956 | patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl)); | ||
957 | } | ||
958 | else | ||
959 | { | ||
960 | patt = String.Format("{0}:{1}", request.HttpMethod, uri); | ||
961 | } | ||
962 | |||
963 | HA2 = HashToString(patt); | ||
964 | |||
965 | // Generate Digest | ||
966 | |||
967 | if (qop != String.Empty) | ||
968 | { | ||
969 | patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); | ||
970 | } | ||
971 | else | ||
972 | { | ||
973 | patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2); | ||
974 | } | ||
975 | |||
976 | KDS = HashToString(patt); | ||
977 | |||
978 | // Compare the generated sequence with the original | ||
979 | |||
980 | return (0 == sc.Compare(KDS, response)); | ||
981 | |||
982 | } | ||
983 | |||
984 | private string HashToString(string pattern) | ||
985 | { | ||
986 | |||
987 | Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); | ||
988 | |||
989 | byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); | ||
990 | |||
991 | sbuilder.Length = 0; | ||
992 | |||
993 | for (int i = 0; i < hash.Length; i++) | ||
994 | { | ||
995 | sbuilder.Append(hash[i].ToString("x2")); | ||
996 | } | ||
997 | |||
998 | Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); | ||
999 | |||
1000 | return sbuilder.ToString(); | ||
1001 | |||
1002 | } | ||
1003 | |||
1004 | #endregion authentication_digest | ||
1005 | |||
1006 | #region service_interface | ||
1007 | |||
1008 | /// <summary> | ||
1009 | /// Conditionally set a normal completion code. This allows a normal | ||
1010 | /// execution path to default. | ||
1011 | /// </summary> | ||
1012 | |||
1013 | internal void Complete() | ||
1014 | { | ||
1015 | if (statusCode == 0) | ||
1016 | { | ||
1017 | statusCode = Rest.HttpStatusCodeOK; | ||
1018 | } | ||
1019 | } | ||
1020 | |||
1021 | /// <summary> | ||
1022 | /// Indicate a functionally-dependent conclusion to the | ||
1023 | /// request. See Rest.cs for a list of possible values. | ||
1024 | /// </summary> | ||
1025 | |||
1026 | internal void Complete(int code) | ||
1027 | { | ||
1028 | statusCode = code; | ||
1029 | } | ||
1030 | |||
1031 | /// <summary> | ||
1032 | /// Indicate that a request should be redirected, using | ||
1033 | /// the HTTP completion codes. Permanent and temporary | ||
1034 | /// redirections may be indicated. The supplied URL is | ||
1035 | /// the new location of the resource. | ||
1036 | /// </summary> | ||
1037 | |||
1038 | internal void Redirect(string Url, bool temp) | ||
1039 | { | ||
1040 | |||
1041 | redirectLocation = Url; | ||
1042 | |||
1043 | if (temp) | ||
1044 | { | ||
1045 | statusCode = Rest.HttpStatusCodeTemporaryRedirect; | ||
1046 | } | ||
1047 | else | ||
1048 | { | ||
1049 | statusCode = Rest.HttpStatusCodePermanentRedirect; | ||
1050 | } | ||
1051 | |||
1052 | Fail(statusCode, String.Empty, true); | ||
1053 | |||
1054 | } | ||
1055 | |||
1056 | /// <summary> | ||
1057 | /// Fail for an arbitrary reason. Just a failure with | ||
1058 | /// headers. The supplied message will be returned in the | ||
1059 | /// message body. | ||
1060 | /// </summary> | ||
1061 | |||
1062 | internal void Fail(int code) | ||
1063 | { | ||
1064 | Fail(code, String.Empty, false); | ||
1065 | } | ||
1066 | |||
1067 | /// <summary> | ||
1068 | /// For the more adventurous. This failure also includes a | ||
1069 | /// specified entity to be appended to the code-related | ||
1070 | /// status string. | ||
1071 | /// </summary> | ||
1072 | |||
1073 | internal void Fail(int code, string addendum) | ||
1074 | { | ||
1075 | Fail(code, addendum, false); | ||
1076 | } | ||
1077 | |||
1078 | internal void Fail(int code, string addendum, bool reset) | ||
1079 | { | ||
1080 | |||
1081 | statusCode = code; | ||
1082 | appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code])); | ||
1083 | |||
1084 | // Add any final addendum to the status information | ||
1085 | |||
1086 | if (addendum != String.Empty) | ||
1087 | { | ||
1088 | appendStatus(String.Format(addendum)); | ||
1089 | } | ||
1090 | |||
1091 | // Help us understand why the request is being rejected | ||
1092 | |||
1093 | if (Rest.DEBUG) | ||
1094 | { | ||
1095 | Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); | ||
1096 | Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme); | ||
1097 | Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm); | ||
1098 | Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain); | ||
1099 | Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce); | ||
1100 | Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce); | ||
1101 | Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque); | ||
1102 | Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale); | ||
1103 | Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm); | ||
1104 | Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop); | ||
1105 | Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix); | ||
1106 | Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName); | ||
1107 | Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass); | ||
1108 | } | ||
1109 | |||
1110 | fail = true; | ||
1111 | |||
1112 | // Respond to the client's request, tag the response (for the | ||
1113 | // benefit of trace) to indicate the reason. | ||
1114 | |||
1115 | Respond(String.Format("Failure response: ({0}) : {1} ", | ||
1116 | code, Rest.HttpStatusDesc[code])); | ||
1117 | |||
1118 | // Finally initialize and the throw a RestException. All of the | ||
1119 | // handler's infrastructure knows that this is a "normal" | ||
1120 | // completion from a code point-of-view. | ||
1121 | |||
1122 | RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">"); | ||
1123 | |||
1124 | re.statusCode = code; | ||
1125 | re.statusDesc = Rest.HttpStatusDesc[code]; | ||
1126 | re.httpmethod = method; | ||
1127 | re.httppath = path; | ||
1128 | |||
1129 | throw re; | ||
1130 | |||
1131 | } | ||
1132 | |||
1133 | // Reject this request | ||
1134 | |||
1135 | internal void Reject() | ||
1136 | { | ||
1137 | Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)"); | ||
1138 | } | ||
1139 | |||
1140 | // This MUST be called by an agent handler before it returns | ||
1141 | // control to Handle, otherwise the request will be ignored. | ||
1142 | // This is called implciitly for the REST stream handlers and | ||
1143 | // is harmless if it is called twice. | ||
1144 | |||
1145 | internal virtual bool Respond(string reason) | ||
1146 | { | ||
1147 | |||
1148 | |||
1149 | Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); | ||
1150 | |||
1151 | // We do this to try and make multiple Respond requests harmless, | ||
1152 | // as it is sometimes convenient to isse a response without | ||
1153 | // certain knowledge that it has not previously been done. | ||
1154 | |||
1155 | if (!handled) | ||
1156 | { | ||
1157 | |||
1158 | Rest.Log.DebugFormat("{0} Generating Response", MsgId); | ||
1159 | Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); | ||
1160 | |||
1161 | // A Head request can NOT have a body! So don't waste time on | ||
1162 | // formatting if we're going to reject it anyway! | ||
1163 | |||
1164 | if (method != Rest.HEAD) | ||
1165 | { | ||
1166 | |||
1167 | Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); | ||
1168 | |||
1169 | // If the writer is non-null then we know that an XML | ||
1170 | // data component exists. Flush and close the writer and | ||
1171 | // then convert the result to the expected buffer format | ||
1172 | // unless the request has already been failed for some | ||
1173 | // reason. | ||
1174 | |||
1175 | if (writer != null) | ||
1176 | { | ||
1177 | Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId); | ||
1178 | Rest.Log.DebugFormat("{0} XML Response exists", MsgId); | ||
1179 | writer.Flush(); | ||
1180 | writer.Close(); | ||
1181 | if (!fail) | ||
1182 | { | ||
1183 | buffer = xmldata.ToArray(); | ||
1184 | AddHeader("Content-Type","application/xml"); | ||
1185 | } | ||
1186 | xmldata.Close(); | ||
1187 | Rest.Log.DebugFormat("{0} XML Response encoded", MsgId); | ||
1188 | Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); | ||
1189 | } | ||
1190 | |||
1191 | if (buffer == null && body != null) | ||
1192 | { | ||
1193 | buffer = encoding.GetBytes(body); | ||
1194 | AddHeader("Content-Type",bodyType); | ||
1195 | } | ||
1196 | |||
1197 | // OK, if the buffer contains something, regardless of how | ||
1198 | // it got there, set various response headers accordingly. | ||
1199 | |||
1200 | if (buffer != null) | ||
1201 | { | ||
1202 | Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); | ||
1203 | } | ||
1204 | else | ||
1205 | { | ||
1206 | if (statusBody != String.Empty) | ||
1207 | { | ||
1208 | statusBody += Rest.statusTail; | ||
1209 | buffer = encoding.GetBytes(statusBody); | ||
1210 | AddHeader("Content-Type","text/html"); | ||
1211 | } | ||
1212 | else | ||
1213 | { | ||
1214 | statusBody = Rest.statusHead; | ||
1215 | appendStatus(String.Format(": ({0}) {1}", | ||
1216 | statusCode, Rest.HttpStatusDesc[statusCode])); | ||
1217 | statusBody += Rest.statusTail; | ||
1218 | buffer = encoding.GetBytes(statusBody); | ||
1219 | AddHeader("Content-Type","text/html"); | ||
1220 | } | ||
1221 | } | ||
1222 | |||
1223 | response.ContentLength64 = buffer.Length; | ||
1224 | |||
1225 | if (response.ContentEncoding == null) | ||
1226 | response.ContentEncoding = encoding; | ||
1227 | |||
1228 | response.SendChunked = chunked; | ||
1229 | response.KeepAlive = keepAlive; | ||
1230 | |||
1231 | } | ||
1232 | |||
1233 | // Set the status code & description. If nothing has been stored, | ||
1234 | // we consider that a success. | ||
1235 | |||
1236 | if (statusCode == 0) | ||
1237 | { | ||
1238 | Complete(); | ||
1239 | } | ||
1240 | |||
1241 | // Set the response code in the actual carrier | ||
1242 | |||
1243 | response.StatusCode = statusCode; | ||
1244 | |||
1245 | // For a redirect we need to set the relocation header accordingly | ||
1246 | |||
1247 | if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || | ||
1248 | response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) | ||
1249 | { | ||
1250 | Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); | ||
1251 | response.RedirectLocation = redirectLocation; | ||
1252 | } | ||
1253 | |||
1254 | // And include the status description if provided. | ||
1255 | |||
1256 | response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode]; | ||
1257 | |||
1258 | // Finally we send back our response. | ||
1259 | |||
1260 | // We've left the setting of handled' until the | ||
1261 | // last minute because the header settings included | ||
1262 | // above are pretty harmless. But everything from | ||
1263 | // here on down probably leaves the response | ||
1264 | // element unusable by anyone else. | ||
1265 | |||
1266 | handled = true; | ||
1267 | |||
1268 | // DumpHeaders(); | ||
1269 | |||
1270 | // if (request.InputStream != null) | ||
1271 | // { | ||
1272 | // Rest.Log.DebugFormat("{0} Closing input stream", MsgId); | ||
1273 | // request.InputStream.Close(); | ||
1274 | // } | ||
1275 | |||
1276 | if (buffer != null && buffer.Length != 0) | ||
1277 | { | ||
1278 | Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length); | ||
1279 | // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", | ||
1280 | // MsgId, buffer.Length, encoding.GetString(buffer)); | ||
1281 | response.OutputStream.Write(buffer, 0, buffer.Length); | ||
1282 | } | ||
1283 | |||
1284 | // Closing the outputstream should complete the transmission process | ||
1285 | |||
1286 | Rest.Log.DebugFormat("{0} Sending response", MsgId); | ||
1287 | // response.OutputStream.Close(); | ||
1288 | response.Send(); | ||
1289 | |||
1290 | } | ||
1291 | |||
1292 | Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); | ||
1293 | |||
1294 | return handled; | ||
1295 | |||
1296 | } | ||
1297 | |||
1298 | /// <summary> | ||
1299 | /// These methods allow a service provider to manipulate the | ||
1300 | /// request/response headers. The DumpHeaders method is intended | ||
1301 | /// for problem diagnosis. | ||
1302 | /// </summary> | ||
1303 | |||
1304 | internal void AddHeader(string hdr, string data) | ||
1305 | { | ||
1306 | if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data); | ||
1307 | response.AddHeader(hdr, data); | ||
1308 | } | ||
1309 | |||
1310 | // internal void RemoveHeader(string hdr) | ||
1311 | // { | ||
1312 | // if (Rest.DEBUG) | ||
1313 | // { | ||
1314 | // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); | ||
1315 | // if (response.Headers.Get(hdr) == null) | ||
1316 | // { | ||
1317 | // Rest.Log.DebugFormat("{0} No such header existed", | ||
1318 | // MsgId, hdr); | ||
1319 | // } | ||
1320 | // } | ||
1321 | // response.Headers.Remove(hdr); | ||
1322 | // } | ||
1323 | |||
1324 | // internal void DumpHeaders() | ||
1325 | // { | ||
1326 | // if (Rest.DEBUG) | ||
1327 | // { | ||
1328 | // for (int i=0;i<response.Headers.Count;i++) | ||
1329 | // { | ||
1330 | // Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, | ||
1331 | // response.Headers.Get(i)); | ||
1332 | // } | ||
1333 | // } | ||
1334 | // } | ||
1335 | |||
1336 | // Setup the XML writer for output | ||
1337 | |||
1338 | internal void initXmlWriter() | ||
1339 | { | ||
1340 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
1341 | xmldata = new MemoryStream(); | ||
1342 | settings.Indent = true; | ||
1343 | settings.IndentChars = " "; | ||
1344 | settings.Encoding = encoding; | ||
1345 | settings.CloseOutput = false; | ||
1346 | settings.OmitXmlDeclaration = true; | ||
1347 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1348 | writer = XmlWriter.Create(xmldata, settings); | ||
1349 | } | ||
1350 | |||
1351 | internal void initXmlReader() | ||
1352 | { | ||
1353 | |||
1354 | XmlReaderSettings settings = new XmlReaderSettings(); | ||
1355 | |||
1356 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1357 | settings.IgnoreComments = true; | ||
1358 | settings.IgnoreWhitespace = true; | ||
1359 | settings.IgnoreProcessingInstructions = true; | ||
1360 | settings.ValidationType = ValidationType.None; | ||
1361 | |||
1362 | reader = XmlReader.Create(request.InputStream,settings); | ||
1363 | |||
1364 | } | ||
1365 | |||
1366 | internal void appendStatus(string msg) | ||
1367 | { | ||
1368 | if (statusBody == String.Empty) | ||
1369 | { | ||
1370 | statusBody = String.Format(Rest.statusHead, request.HttpMethod); | ||
1371 | } | ||
1372 | |||
1373 | statusBody = String.Format("{0} {1}", statusBody, msg); | ||
1374 | } | ||
1375 | |||
1376 | #endregion service_interface | ||
1377 | |||
1378 | #region internal_methods | ||
1379 | |||
1380 | /// <summary> | ||
1381 | /// Helper methods for deconstructing and reconstructing | ||
1382 | /// URI path data. | ||
1383 | /// </summary> | ||
1384 | |||
1385 | private void initUrl() | ||
1386 | { | ||
1387 | |||
1388 | uri = request.Url; | ||
1389 | |||
1390 | if (query == null) | ||
1391 | { | ||
1392 | query = uri.Query; | ||
1393 | } | ||
1394 | |||
1395 | // If the path has not been previously initialized, | ||
1396 | // do so now. | ||
1397 | |||
1398 | if (path == null) | ||
1399 | { | ||
1400 | path = uri.AbsolutePath; | ||
1401 | if (path.EndsWith(Rest.UrlPathSeparator)) | ||
1402 | path = path.Substring(0,path.Length-1); | ||
1403 | } | ||
1404 | |||
1405 | // If we succeeded in getting a path, perform any | ||
1406 | // additional pre-processing required. | ||
1407 | |||
1408 | if (path != null) | ||
1409 | { | ||
1410 | if (Rest.ExtendedEscape) | ||
1411 | { | ||
1412 | // Handle "+". Not a standard substitution, but | ||
1413 | // common enough... | ||
1414 | path = path.Replace(Rest.C_PLUS,Rest.C_SPACE); | ||
1415 | } | ||
1416 | pathNodes = path.Split(Rest.CA_PATHSEP); | ||
1417 | } | ||
1418 | else | ||
1419 | { | ||
1420 | pathNodes = EmptyPath; | ||
1421 | } | ||
1422 | |||
1423 | // Elimiate any %-escaped values. This is left until here | ||
1424 | // so that escaped "+' are not mistakenly replaced. | ||
1425 | |||
1426 | path = Uri.UnescapeDataString(path); | ||
1427 | |||
1428 | // Request server context info | ||
1429 | |||
1430 | hostname = uri.Host; | ||
1431 | port = uri.Port; | ||
1432 | |||
1433 | } | ||
1434 | |||
1435 | private int initParameters(int prfxlen) | ||
1436 | { | ||
1437 | |||
1438 | if (prfxlen < path.Length-1) | ||
1439 | { | ||
1440 | parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); | ||
1441 | } | ||
1442 | else | ||
1443 | { | ||
1444 | parameters = new string[0]; | ||
1445 | } | ||
1446 | |||
1447 | // Generate a debug list of the decoded parameters | ||
1448 | |||
1449 | if (Rest.DEBUG && prfxlen < path.Length-1) | ||
1450 | { | ||
1451 | Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); | ||
1452 | for (int i = 0; i < parameters.Length; i++) | ||
1453 | { | ||
1454 | Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); | ||
1455 | } | ||
1456 | } | ||
1457 | |||
1458 | return parameters.Length; | ||
1459 | |||
1460 | } | ||
1461 | |||
1462 | #endregion internal_methods | ||
1463 | |||
1464 | } | ||
1465 | } | ||