diff options
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs')
-rw-r--r-- | OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs | 1076 |
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 | ||
28 | using System; | 29 | using System; |
@@ -40,16 +41,17 @@ using System.Xml; | |||
40 | 41 | ||
41 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 42 | namespace 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 | } |