diff options
author | David Walter Seikel | 2016-11-03 21:44:39 +1000 |
---|---|---|
committer | David Walter Seikel | 2016-11-03 21:44:39 +1000 |
commit | 134f86e8d5c414409631b25b8c6f0ee45fbd8631 (patch) | |
tree | 216b89d3fb89acfb81be1e440c25c41ab09fa96d /OpenSim/ApplicationPlugins/Rest/Inventory | |
parent | More changing to production grid. Double oops. (diff) | |
download | opensim-SC-134f86e8d5c414409631b25b8c6f0ee45fbd8631.zip opensim-SC-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.gz opensim-SC-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.bz2 opensim-SC-134f86e8d5c414409631b25b8c6f0ee45fbd8631.tar.xz |
Initial update to OpenSim 0.8.2.1 source code.
Diffstat (limited to '')
13 files changed, 42 insertions, 7251 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 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml b/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml deleted file mode 100644 index 777a2dc..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | <Addin id="OpenSim.ApplicationPlugins.Rest.Inventory" version="0.1"> | ||
2 | <Runtime> | ||
3 | <Import assembly="OpenSim.ApplicationPlugins.Rest.Inventory.dll"/> | ||
4 | </Runtime> | ||
5 | <Dependencies> | ||
6 | <Addin id="OpenSim" version="0.5" /> | ||
7 | </Dependencies> | ||
8 | <Extension path = "/OpenSim/Startup"> | ||
9 | <Plugin id="RestInventory" type="OpenSim.ApplicationPlugins.Rest.Inventory.RestHandler" /> | ||
10 | </Extension> | ||
11 | </Addin> | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs deleted file mode 100644 index 9755e73..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ /dev/null | |||
@@ -1,551 +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 | |||
29 | using System; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using log4net; | ||
34 | using Nini.Config; | ||
35 | using OpenSim.Framework; | ||
36 | using OpenSim.Framework.Communications; | ||
37 | using OpenSim.Services.Interfaces; | ||
38 | using IAvatarService = OpenSim.Services.Interfaces.IAvatarService; | ||
39 | |||
40 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
41 | { | ||
42 | public class Rest | ||
43 | { | ||
44 | internal static readonly ILog Log = | ||
45 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
46 | |||
47 | internal static bool DEBUG = Log.IsDebugEnabled; | ||
48 | |||
49 | /// <summary> | ||
50 | /// Supported authentication schemes | ||
51 | /// </summary> | ||
52 | |||
53 | public const string AS_BASIC = "Basic"; // simple user/password verification | ||
54 | public const string AS_DIGEST = "Digest"; // password safe authentication | ||
55 | |||
56 | /// Supported Digest algorithms | ||
57 | |||
58 | public const string Digest_MD5 = "MD5"; // assumed default if omitted | ||
59 | public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST? | ||
60 | |||
61 | public const string Qop_Auth = "auth"; // authentication only | ||
62 | public const string Qop_Int = "auth-int"; // TODO | ||
63 | |||
64 | /// <summary> | ||
65 | /// These values have a single value for the whole | ||
66 | /// domain and lifetime of the plugin handler. We | ||
67 | /// make them static for ease of reference within | ||
68 | /// the assembly. These are initialized by the | ||
69 | /// RestHandler class during start-up. | ||
70 | /// </summary> | ||
71 | |||
72 | internal static IRestHandler Plugin = null; | ||
73 | internal static OpenSimBase main = null; | ||
74 | internal static string Prefix = null; | ||
75 | internal static IConfig Config = null; | ||
76 | internal static string GodKey = null; | ||
77 | internal static bool Authenticate = true; | ||
78 | internal static bool Secure = true; | ||
79 | internal static bool ExtendedEscape = true; | ||
80 | internal static bool DumpAsset = false; | ||
81 | internal static bool Fill = true; | ||
82 | internal static bool FlushEnabled = true; | ||
83 | internal static string Realm = "OpenSim REST"; | ||
84 | internal static string Scheme = AS_BASIC; | ||
85 | internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 | ||
86 | |||
87 | /// <summary> | ||
88 | /// These are all dependent upon the Comms manager | ||
89 | /// being initialized. So they have to be properties | ||
90 | /// because the comms manager is now a module and is | ||
91 | /// not guaranteed to be there when the rest handler | ||
92 | /// initializes. | ||
93 | /// </summary> | ||
94 | |||
95 | internal static IInventoryService InventoryServices | ||
96 | { | ||
97 | get { return main.SceneManager.CurrentOrFirstScene.InventoryService; } | ||
98 | } | ||
99 | |||
100 | internal static IUserAccountService UserServices | ||
101 | { | ||
102 | get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; } | ||
103 | } | ||
104 | |||
105 | internal static IAuthenticationService AuthServices | ||
106 | { | ||
107 | get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; } | ||
108 | } | ||
109 | |||
110 | internal static IAvatarService AvatarServices | ||
111 | { | ||
112 | get { return main.SceneManager.CurrentOrFirstScene.AvatarService; } | ||
113 | } | ||
114 | |||
115 | internal static IAssetService AssetServices | ||
116 | { | ||
117 | get { return main.SceneManager.CurrentOrFirstScene.AssetService; } | ||
118 | } | ||
119 | |||
120 | /// <summary> | ||
121 | /// HTTP requires that status information be generated for PUT | ||
122 | /// and POST opertaions. This is in support of that. The | ||
123 | /// operation verb gets substituted into the first string, | ||
124 | /// and the completion code is inserted into the tail. The | ||
125 | /// strings are put here to encourage consistency. | ||
126 | /// </summary> | ||
127 | |||
128 | internal static string statusHead = "<html><body><title>{0} status</title><break>"; | ||
129 | internal static string statusTail = "</body></html>"; | ||
130 | |||
131 | internal static Dictionary<int,string> HttpStatusDesc; | ||
132 | |||
133 | static Rest() | ||
134 | { | ||
135 | HttpStatusDesc = new Dictionary<int,string>(); | ||
136 | if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length) | ||
137 | { | ||
138 | Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match"); | ||
139 | throw new Exception("HTTP Status array discrepancy"); | ||
140 | } | ||
141 | |||
142 | // Repackage the data into something more tractable. The sparse | ||
143 | // nature of HTTP return codes makes an array a bad choice. | ||
144 | |||
145 | for (int i=0; i<HttpStatusCodeArray.Length; i++) | ||
146 | { | ||
147 | HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | internal static int CreationDate | ||
152 | { | ||
153 | get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; } | ||
154 | } | ||
155 | |||
156 | internal static string MsgId | ||
157 | { | ||
158 | get { return Plugin.MsgId; } | ||
159 | } | ||
160 | |||
161 | internal static string RequestId | ||
162 | { | ||
163 | get { return Plugin.RequestId; } | ||
164 | } | ||
165 | |||
166 | internal static Encoding Encoding = Util.UTF8; | ||
167 | |||
168 | /// <summary> | ||
169 | /// Version control for REST implementation. This | ||
170 | /// refers to the overall infrastructure represented | ||
171 | /// by the following classes | ||
172 | /// RequestData | ||
173 | /// RequestInventoryPlugin | ||
174 | /// Rest | ||
175 | /// It does no describe implementation classes such as | ||
176 | /// RestInventoryServices, which may morph much more | ||
177 | /// often. Such classes ARE dependent upon this however | ||
178 | /// and should check it in their Initialize method. | ||
179 | /// </summary> | ||
180 | |||
181 | public static readonly float Version = 1.0F; | ||
182 | public const string Name = "REST 1.0"; | ||
183 | |||
184 | /// <summary> | ||
185 | /// Currently defined HTTP methods. | ||
186 | /// Only GET and HEAD are required to be | ||
187 | /// supported by all servers. See Respond | ||
188 | /// to see how these are handled. | ||
189 | /// </summary> | ||
190 | |||
191 | // REST AGENT 1.0 interpretations | ||
192 | public const string GET = "get"; // information retrieval - server state unchanged | ||
193 | public const string HEAD = "head"; // same as get except only the headers are returned. | ||
194 | public const string POST = "post"; // Replace the URI designated resource with the entity. | ||
195 | public const string PUT = "put"; // Add the entity to the context represented by the URI | ||
196 | public const string DELETE = "delete"; // Remove the URI designated resource from the server. | ||
197 | |||
198 | public const string OPTIONS = "options"; // | ||
199 | public const string TRACE = "trace"; // | ||
200 | public const string CONNECT = "connect"; // | ||
201 | |||
202 | // Define this in one place... | ||
203 | |||
204 | public const string UrlPathSeparator = "/"; | ||
205 | public const string UrlMethodSeparator = ":"; | ||
206 | |||
207 | // Redirection qualifications | ||
208 | |||
209 | public const bool PERMANENT = false; | ||
210 | public const bool TEMPORARY = true; | ||
211 | |||
212 | // Constant arrays used by String.Split | ||
213 | |||
214 | public static readonly char C_SPACE = ' '; | ||
215 | public static readonly char C_SLASH = '/'; | ||
216 | public static readonly char C_PATHSEP = '/'; | ||
217 | public static readonly char C_COLON = ':'; | ||
218 | public static readonly char C_PLUS = '+'; | ||
219 | public static readonly char C_PERIOD = '.'; | ||
220 | public static readonly char C_COMMA = ','; | ||
221 | public static readonly char C_DQUOTE = '"'; | ||
222 | |||
223 | public static readonly string CS_SPACE = " "; | ||
224 | public static readonly string CS_SLASH = "/"; | ||
225 | public static readonly string CS_PATHSEP = "/"; | ||
226 | public static readonly string CS_COLON = ":"; | ||
227 | public static readonly string CS_PLUS = "+"; | ||
228 | public static readonly string CS_PERIOD = "."; | ||
229 | public static readonly string CS_COMMA = ","; | ||
230 | public static readonly string CS_DQUOTE = "\""; | ||
231 | |||
232 | public static readonly char[] CA_SPACE = { C_SPACE }; | ||
233 | public static readonly char[] CA_SLASH = { C_SLASH }; | ||
234 | public static readonly char[] CA_PATHSEP = { C_PATHSEP }; | ||
235 | public static readonly char[] CA_COLON = { C_COLON }; | ||
236 | public static readonly char[] CA_PERIOD = { C_PERIOD }; | ||
237 | public static readonly char[] CA_PLUS = { C_PLUS }; | ||
238 | public static readonly char[] CA_COMMA = { C_COMMA }; | ||
239 | public static readonly char[] CA_DQUOTE = { C_DQUOTE }; | ||
240 | |||
241 | // HTTP Code Values (in value order) | ||
242 | |||
243 | public const int HttpStatusCodeContinue = 100; | ||
244 | public const int HttpStatusCodeSwitchingProtocols = 101; | ||
245 | |||
246 | public const int HttpStatusCodeOK = 200; | ||
247 | public const int HttpStatusCodeCreated = 201; | ||
248 | public const int HttpStatusCodeAccepted = 202; | ||
249 | public const int HttpStatusCodeNonAuthoritative = 203; | ||
250 | public const int HttpStatusCodeNoContent = 204; | ||
251 | public const int HttpStatusCodeResetContent = 205; | ||
252 | public const int HttpStatusCodePartialContent = 206; | ||
253 | |||
254 | public const int HttpStatusCodeMultipleChoices = 300; | ||
255 | public const int HttpStatusCodePermanentRedirect = 301; | ||
256 | public const int HttpStatusCodeFound = 302; | ||
257 | public const int HttpStatusCodeSeeOther = 303; | ||
258 | public const int HttpStatusCodeNotModified = 304; | ||
259 | public const int HttpStatusCodeUseProxy = 305; | ||
260 | public const int HttpStatusCodeReserved306 = 306; | ||
261 | public const int HttpStatusCodeTemporaryRedirect = 307; | ||
262 | |||
263 | public const int HttpStatusCodeBadRequest = 400; | ||
264 | public const int HttpStatusCodeNotAuthorized = 401; | ||
265 | public const int HttpStatusCodePaymentRequired = 402; | ||
266 | public const int HttpStatusCodeForbidden = 403; | ||
267 | public const int HttpStatusCodeNotFound = 404; | ||
268 | public const int HttpStatusCodeMethodNotAllowed = 405; | ||
269 | public const int HttpStatusCodeNotAcceptable = 406; | ||
270 | public const int HttpStatusCodeProxyAuthenticate = 407; | ||
271 | public const int HttpStatusCodeTimeOut = 408; | ||
272 | public const int HttpStatusCodeConflict = 409; | ||
273 | public const int HttpStatusCodeGone = 410; | ||
274 | public const int HttpStatusCodeLengthRequired = 411; | ||
275 | public const int HttpStatusCodePreconditionFailed = 412; | ||
276 | public const int HttpStatusCodeEntityTooLarge = 413; | ||
277 | public const int HttpStatusCodeUriTooLarge = 414; | ||
278 | public const int HttpStatusCodeUnsupportedMedia = 415; | ||
279 | public const int HttpStatusCodeRangeNotSatsified = 416; | ||
280 | public const int HttpStatusCodeExpectationFailed = 417; | ||
281 | |||
282 | public const int HttpStatusCodeServerError = 500; | ||
283 | public const int HttpStatusCodeNotImplemented = 501; | ||
284 | public const int HttpStatusCodeBadGateway = 502; | ||
285 | public const int HttpStatusCodeServiceUnavailable = 503; | ||
286 | public const int HttpStatusCodeGatewayTimeout = 504; | ||
287 | public const int HttpStatusCodeHttpVersionError = 505; | ||
288 | |||
289 | public static readonly int[] HttpStatusCodeArray = { | ||
290 | HttpStatusCodeContinue, | ||
291 | HttpStatusCodeSwitchingProtocols, | ||
292 | HttpStatusCodeOK, | ||
293 | HttpStatusCodeCreated, | ||
294 | HttpStatusCodeAccepted, | ||
295 | HttpStatusCodeNonAuthoritative, | ||
296 | HttpStatusCodeNoContent, | ||
297 | HttpStatusCodeResetContent, | ||
298 | HttpStatusCodePartialContent, | ||
299 | HttpStatusCodeMultipleChoices, | ||
300 | HttpStatusCodePermanentRedirect, | ||
301 | HttpStatusCodeFound, | ||
302 | HttpStatusCodeSeeOther, | ||
303 | HttpStatusCodeNotModified, | ||
304 | HttpStatusCodeUseProxy, | ||
305 | HttpStatusCodeReserved306, | ||
306 | HttpStatusCodeTemporaryRedirect, | ||
307 | HttpStatusCodeBadRequest, | ||
308 | HttpStatusCodeNotAuthorized, | ||
309 | HttpStatusCodePaymentRequired, | ||
310 | HttpStatusCodeForbidden, | ||
311 | HttpStatusCodeNotFound, | ||
312 | HttpStatusCodeMethodNotAllowed, | ||
313 | HttpStatusCodeNotAcceptable, | ||
314 | HttpStatusCodeProxyAuthenticate, | ||
315 | HttpStatusCodeTimeOut, | ||
316 | HttpStatusCodeConflict, | ||
317 | HttpStatusCodeGone, | ||
318 | HttpStatusCodeLengthRequired, | ||
319 | HttpStatusCodePreconditionFailed, | ||
320 | HttpStatusCodeEntityTooLarge, | ||
321 | HttpStatusCodeUriTooLarge, | ||
322 | HttpStatusCodeUnsupportedMedia, | ||
323 | HttpStatusCodeRangeNotSatsified, | ||
324 | HttpStatusCodeExpectationFailed, | ||
325 | HttpStatusCodeServerError, | ||
326 | HttpStatusCodeNotImplemented, | ||
327 | HttpStatusCodeBadGateway, | ||
328 | HttpStatusCodeServiceUnavailable, | ||
329 | HttpStatusCodeGatewayTimeout, | ||
330 | HttpStatusCodeHttpVersionError | ||
331 | }; | ||
332 | |||
333 | // HTTP Status Descriptions (in status code order) | ||
334 | // This array must be kept strictly consistent with respect | ||
335 | // to the status code array above. | ||
336 | |||
337 | public static readonly string[] HttpStatusDescArray = { | ||
338 | "Continue Request", | ||
339 | "Switching Protocols", | ||
340 | "OK", | ||
341 | "CREATED", | ||
342 | "ACCEPTED", | ||
343 | "NON-AUTHORITATIVE INFORMATION", | ||
344 | "NO CONTENT", | ||
345 | "RESET CONTENT", | ||
346 | "PARTIAL CONTENT", | ||
347 | "MULTIPLE CHOICES", | ||
348 | "PERMANENT REDIRECT", | ||
349 | "FOUND", | ||
350 | "SEE OTHER", | ||
351 | "NOT MODIFIED", | ||
352 | "USE PROXY", | ||
353 | "RESERVED CODE 306", | ||
354 | "TEMPORARY REDIRECT", | ||
355 | "BAD REQUEST", | ||
356 | "NOT AUTHORIZED", | ||
357 | "PAYMENT REQUIRED", | ||
358 | "FORBIDDEN", | ||
359 | "NOT FOUND", | ||
360 | "METHOD NOT ALLOWED", | ||
361 | "NOT ACCEPTABLE", | ||
362 | "PROXY AUTHENTICATION REQUIRED", | ||
363 | "TIMEOUT", | ||
364 | "CONFLICT", | ||
365 | "GONE", | ||
366 | "LENGTH REQUIRED", | ||
367 | "PRECONDITION FAILED", | ||
368 | "ENTITY TOO LARGE", | ||
369 | "URI TOO LARGE", | ||
370 | "UNSUPPORTED MEDIA", | ||
371 | "RANGE NOT SATISFIED", | ||
372 | "EXPECTATION FAILED", | ||
373 | "SERVER ERROR", | ||
374 | "NOT IMPLEMENTED", | ||
375 | "BAD GATEWAY", | ||
376 | "SERVICE UNAVAILABLE", | ||
377 | "GATEWAY TIMEOUT", | ||
378 | "HTTP VERSION NOT SUPPORTED" | ||
379 | }; | ||
380 | |||
381 | // HTTP Headers | ||
382 | |||
383 | public const string HttpHeaderAccept = "Accept"; | ||
384 | public const string HttpHeaderAcceptCharset = "Accept-Charset"; | ||
385 | public const string HttpHeaderAcceptEncoding = "Accept-Encoding"; | ||
386 | public const string HttpHeaderAcceptLanguage = "Accept-Language"; | ||
387 | public const string HttpHeaderAcceptRanges = "Accept-Ranges"; | ||
388 | public const string HttpHeaderAge = "Age"; | ||
389 | public const string HttpHeaderAllow = "Allow"; | ||
390 | public const string HttpHeaderAuthorization = "Authorization"; | ||
391 | public const string HttpHeaderCacheControl = "Cache-Control"; | ||
392 | public const string HttpHeaderConnection = "Connection"; | ||
393 | public const string HttpHeaderContentEncoding = "Content-Encoding"; | ||
394 | public const string HttpHeaderContentLanguage = "Content-Language"; | ||
395 | public const string HttpHeaderContentLength = "Content-Length"; | ||
396 | public const string HttpHeaderContentLocation = "Content-Location"; | ||
397 | public const string HttpHeaderContentMD5 = "Content-MD5"; | ||
398 | public const string HttpHeaderContentRange = "Content-Range"; | ||
399 | public const string HttpHeaderContentType = "Content-Type"; | ||
400 | public const string HttpHeaderDate = "Date"; | ||
401 | public const string HttpHeaderETag = "ETag"; | ||
402 | public const string HttpHeaderExpect = "Expect"; | ||
403 | public const string HttpHeaderExpires = "Expires"; | ||
404 | public const string HttpHeaderFrom = "From"; | ||
405 | public const string HttpHeaderHost = "Host"; | ||
406 | public const string HttpHeaderIfMatch = "If-Match"; | ||
407 | public const string HttpHeaderIfModifiedSince = "If-Modified-Since"; | ||
408 | public const string HttpHeaderIfNoneMatch = "If-None-Match"; | ||
409 | public const string HttpHeaderIfRange = "If-Range"; | ||
410 | public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since"; | ||
411 | public const string HttpHeaderLastModified = "Last-Modified"; | ||
412 | public const string HttpHeaderLocation = "Location"; | ||
413 | public const string HttpHeaderMaxForwards = "Max-Forwards"; | ||
414 | public const string HttpHeaderPragma = "Pragma"; | ||
415 | public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate"; | ||
416 | public const string HttpHeaderProxyAuthorization = "Proxy-Authorization"; | ||
417 | public const string HttpHeaderRange = "Range"; | ||
418 | public const string HttpHeaderReferer = "Referer"; | ||
419 | public const string HttpHeaderRetryAfter = "Retry-After"; | ||
420 | public const string HttpHeaderServer = "Server"; | ||
421 | public const string HttpHeaderTE = "TE"; | ||
422 | public const string HttpHeaderTrailer = "Trailer"; | ||
423 | public const string HttpHeaderTransferEncoding = "Transfer-Encoding"; | ||
424 | public const string HttpHeaderUpgrade = "Upgrade"; | ||
425 | public const string HttpHeaderUserAgent = "User-Agent"; | ||
426 | public const string HttpHeaderVary = "Vary"; | ||
427 | public const string HttpHeaderVia = "Via"; | ||
428 | public const string HttpHeaderWarning = "Warning"; | ||
429 | public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate"; | ||
430 | |||
431 | /// Utility routines | ||
432 | |||
433 | public static string StringToBase64(string str) | ||
434 | { | ||
435 | try | ||
436 | { | ||
437 | byte[] encData_byte = new byte[str.Length]; | ||
438 | encData_byte = Util.UTF8.GetBytes(str); | ||
439 | return Convert.ToBase64String(encData_byte); | ||
440 | } | ||
441 | catch | ||
442 | { | ||
443 | return String.Empty; | ||
444 | } | ||
445 | } | ||
446 | |||
447 | public static string Base64ToString(string str) | ||
448 | { | ||
449 | try | ||
450 | { | ||
451 | return Util.Base64ToString(str); | ||
452 | } | ||
453 | catch | ||
454 | { | ||
455 | return String.Empty; | ||
456 | } | ||
457 | } | ||
458 | |||
459 | private const string hvals = "0123456789abcdef"; | ||
460 | |||
461 | public static int Hex2Int(string hex) | ||
462 | { | ||
463 | int val = 0; | ||
464 | int sum = 0; | ||
465 | string tmp = null; | ||
466 | |||
467 | if (hex != null) | ||
468 | { | ||
469 | tmp = hex.ToLower(); | ||
470 | for (int i = 0; i < tmp.Length; i++) | ||
471 | { | ||
472 | val = hvals.IndexOf(tmp[i]); | ||
473 | if (val == -1) | ||
474 | break; | ||
475 | sum *= 16; | ||
476 | sum += val; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | return sum; | ||
481 | } | ||
482 | |||
483 | // Nonce management | ||
484 | |||
485 | public static string NonceGenerator() | ||
486 | { | ||
487 | return StringToBase64(CreationDate + Guid.NewGuid().ToString()); | ||
488 | } | ||
489 | |||
490 | // Dump the specified data stream | ||
491 | |||
492 | public static void Dump(byte[] data) | ||
493 | { | ||
494 | char[] buffer = new char[DumpLineSize]; | ||
495 | int cc = 0; | ||
496 | |||
497 | for (int i = 0; i < data.Length; i++) | ||
498 | { | ||
499 | if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); | ||
500 | |||
501 | if (i % 4 == 0) Console.Write(" "); | ||
502 | |||
503 | Console.Write("{0}",data[i].ToString("x2")); | ||
504 | |||
505 | if (data[i] < 127 && data[i] > 31) | ||
506 | buffer[i % DumpLineSize] = (char) data[i]; | ||
507 | else | ||
508 | buffer[i % DumpLineSize] = '.'; | ||
509 | |||
510 | cc++; | ||
511 | |||
512 | if (i != 0 && (i + 1) % DumpLineSize == 0) | ||
513 | { | ||
514 | Console.Write(" |"+(new String(buffer))+"|"); | ||
515 | cc = 0; | ||
516 | } | ||
517 | } | ||
518 | |||
519 | // Finish off any incomplete line | ||
520 | |||
521 | if (cc != 0) | ||
522 | { | ||
523 | for (int i = cc ; i < DumpLineSize; i++) | ||
524 | { | ||
525 | if (i % 4 == 0) Console.Write(" "); | ||
526 | Console.Write(" "); | ||
527 | buffer[i % DumpLineSize] = ' '; | ||
528 | } | ||
529 | Console.WriteLine(" |"+(new String(buffer))+"|"); | ||
530 | } | ||
531 | else | ||
532 | { | ||
533 | Console.Write("\n"); | ||
534 | } | ||
535 | } | ||
536 | } | ||
537 | |||
538 | // Local exception type | ||
539 | |||
540 | public class RestException : Exception | ||
541 | { | ||
542 | internal int statusCode; | ||
543 | internal string statusDesc; | ||
544 | internal string httpmethod; | ||
545 | internal string httppath; | ||
546 | |||
547 | public RestException(string msg) : base(msg) | ||
548 | { | ||
549 | } | ||
550 | } | ||
551 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs deleted file mode 100644 index 3cda984..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs +++ /dev/null | |||
@@ -1,860 +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; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Xml; | ||
32 | using OpenMetaverse; | ||
33 | using OpenSim.Framework; | ||
34 | using OpenSim.Framework.Servers; | ||
35 | using OpenSim.Framework.Servers.HttpServer; | ||
36 | using OpenSim.Services.Interfaces; | ||
37 | |||
38 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
39 | { | ||
40 | |||
41 | public class RestAppearanceServices : IRest | ||
42 | { | ||
43 | // private static readonly int PARM_USERID = 0; | ||
44 | |||
45 | // private static readonly int PARM_PATH = 1; | ||
46 | |||
47 | // private bool enabled = false; | ||
48 | private string qPrefix = "appearance"; | ||
49 | |||
50 | /// <summary> | ||
51 | /// The constructor makes sure that the service prefix is absolute | ||
52 | /// and the registers the service handler and the allocator. | ||
53 | /// </summary> | ||
54 | |||
55 | public RestAppearanceServices() | ||
56 | { | ||
57 | Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId); | ||
58 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
59 | |||
60 | // If a relative path was specified for the handler's domain, | ||
61 | // add the standard prefix to make it absolute, e.g. /admin | ||
62 | |||
63 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
64 | { | ||
65 | Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); | ||
66 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
67 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
68 | Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); | ||
69 | } | ||
70 | |||
71 | // Register interface using the absolute URI. | ||
72 | |||
73 | Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate); | ||
74 | |||
75 | // Activate if everything went OK | ||
76 | |||
77 | // enabled = true; | ||
78 | |||
79 | Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId); | ||
80 | } | ||
81 | |||
82 | /// <summary> | ||
83 | /// Post-construction, pre-enabled initialization opportunity | ||
84 | /// Not currently exploited. | ||
85 | /// </summary> | ||
86 | |||
87 | public void Initialize() | ||
88 | { | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// Called by the plug-in to halt service processing. Local processing is | ||
93 | /// disabled. | ||
94 | /// </summary> | ||
95 | |||
96 | public void Close() | ||
97 | { | ||
98 | // enabled = false; | ||
99 | Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId); | ||
100 | } | ||
101 | |||
102 | /// <summary> | ||
103 | /// This property is declared locally because it is used a lot and | ||
104 | /// brevity is nice. | ||
105 | /// </summary> | ||
106 | |||
107 | internal string MsgId | ||
108 | { | ||
109 | get { return Rest.MsgId; } | ||
110 | } | ||
111 | |||
112 | #region Interface | ||
113 | |||
114 | /// <summary> | ||
115 | /// The plugin (RestHandler) calls this method to allocate the request | ||
116 | /// state carrier for a new request. It is destroyed when the request | ||
117 | /// completes. All request-instance specific state is kept here. This | ||
118 | /// is registered when this service provider is registered. | ||
119 | /// </summary> | ||
120 | /// <param name=request>Inbound HTTP request information</param> | ||
121 | /// <param name=response>Outbound HTTP request information</param> | ||
122 | /// <param name=qPrefix>REST service domain prefix</param> | ||
123 | /// <returns>A RequestData instance suitable for this service</returns> | ||
124 | |||
125 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
126 | { | ||
127 | return (RequestData) new AppearanceRequestData(request, response, prefix); | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// This method is registered with the handler when this service provider | ||
132 | /// is initialized. It is called whenever the plug-in identifies this service | ||
133 | /// provider as the best match for a given request. | ||
134 | /// It handles all aspects of inventory REST processing, i.e. /admin/inventory | ||
135 | /// </summary> | ||
136 | /// <param name=hdata>A consolidated HTTP request work area</param> | ||
137 | |||
138 | private void DoAppearance(RequestData hdata) | ||
139 | { | ||
140 | // !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7 | ||
141 | |||
142 | //AppearanceRequestData rdata = (AppearanceRequestData) hdata; | ||
143 | |||
144 | //Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId); | ||
145 | |||
146 | //// If we're disabled, do nothing. | ||
147 | |||
148 | //if (!enabled) | ||
149 | //{ | ||
150 | // return; | ||
151 | //} | ||
152 | |||
153 | //// Now that we know this is a serious attempt to | ||
154 | //// access inventory data, we should find out who | ||
155 | //// is asking, and make sure they are authorized | ||
156 | //// to do so. We need to validate the caller's | ||
157 | //// identity before revealing anything about the | ||
158 | //// status quo. Authenticate throws an exception | ||
159 | //// via Fail if no identity information is present. | ||
160 | //// | ||
161 | //// With the present HTTP server we can't use the | ||
162 | //// builtin authentication mechanisms because they | ||
163 | //// would be enforced for all in-bound requests. | ||
164 | //// Instead we look at the headers ourselves and | ||
165 | //// handle authentication directly. | ||
166 | |||
167 | //try | ||
168 | //{ | ||
169 | // if (!rdata.IsAuthenticated) | ||
170 | // { | ||
171 | // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); | ||
172 | // } | ||
173 | //} | ||
174 | //catch (RestException e) | ||
175 | //{ | ||
176 | // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
177 | // { | ||
178 | // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
179 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
180 | // } | ||
181 | // else | ||
182 | // { | ||
183 | // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
184 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
185 | // } | ||
186 | // throw (e); | ||
187 | //} | ||
188 | |||
189 | //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | ||
190 | |||
191 | //// We can only get here if we are authorized | ||
192 | //// | ||
193 | //// The requestor may have specified an UUID or | ||
194 | //// a conjoined FirstName LastName string. We'll | ||
195 | //// try both. If we fail with the first, UUID, | ||
196 | //// attempt, we try the other. As an example, the | ||
197 | //// URI for a valid inventory request might be: | ||
198 | //// | ||
199 | //// http://<host>:<port>/admin/inventory/Arthur Dent | ||
200 | //// | ||
201 | //// Indicating that this is an inventory request for | ||
202 | //// an avatar named Arthur Dent. This is ALL that is | ||
203 | //// required to designate a GET for an entire | ||
204 | //// inventory. | ||
205 | //// | ||
206 | |||
207 | //// Do we have at least a user agent name? | ||
208 | |||
209 | //if (rdata.Parameters.Length < 1) | ||
210 | //{ | ||
211 | // Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId); | ||
212 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); | ||
213 | //} | ||
214 | |||
215 | //// The first parameter MUST be the agent identification, either an UUID | ||
216 | //// or a space-separated First-name Last-Name specification. We check for | ||
217 | //// an UUID first, if anyone names their character using a valid UUID | ||
218 | //// that identifies another existing avatar will cause this a problem... | ||
219 | |||
220 | //try | ||
221 | //{ | ||
222 | // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); | ||
223 | // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); | ||
224 | // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); | ||
225 | //} | ||
226 | //catch | ||
227 | //{ | ||
228 | // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); | ||
229 | // if (names.Length == 2) | ||
230 | // { | ||
231 | // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); | ||
232 | // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); | ||
233 | // } | ||
234 | // else | ||
235 | // { | ||
236 | // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | ||
237 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); | ||
238 | // } | ||
239 | //} | ||
240 | |||
241 | //// If the user profile is null then either the server is broken, or the | ||
242 | //// user is not known. We always assume the latter case. | ||
243 | |||
244 | //if (rdata.userProfile != null) | ||
245 | //{ | ||
246 | // Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}", | ||
247 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
248 | //} | ||
249 | //else | ||
250 | //{ | ||
251 | // Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path); | ||
252 | // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); | ||
253 | //} | ||
254 | |||
255 | //// If we get to here, then we have effectively validated the user's | ||
256 | |||
257 | //switch (rdata.method) | ||
258 | //{ | ||
259 | // case Rest.HEAD : // Do the processing, set the status code, suppress entity | ||
260 | // DoGet(rdata); | ||
261 | // rdata.buffer = null; | ||
262 | // break; | ||
263 | |||
264 | // case Rest.GET : // Do the processing, set the status code, return entity | ||
265 | // DoGet(rdata); | ||
266 | // break; | ||
267 | |||
268 | // case Rest.PUT : // Update named element | ||
269 | // DoUpdate(rdata); | ||
270 | // break; | ||
271 | |||
272 | // case Rest.POST : // Add new information to identified context. | ||
273 | // DoExtend(rdata); | ||
274 | // break; | ||
275 | |||
276 | // case Rest.DELETE : // Delete information | ||
277 | // DoDelete(rdata); | ||
278 | // break; | ||
279 | |||
280 | // default : | ||
281 | // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", | ||
282 | // MsgId, rdata.method, rdata.path); | ||
283 | // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | ||
284 | // String.Format("{0} not supported", rdata.method)); | ||
285 | // break; | ||
286 | //} | ||
287 | } | ||
288 | |||
289 | #endregion Interface | ||
290 | |||
291 | #region method-specific processing | ||
292 | |||
293 | /// <summary> | ||
294 | /// This method implements GET processing for user's appearance. | ||
295 | /// </summary> | ||
296 | /// <param name=rdata>HTTP service request work area</param> | ||
297 | |||
298 | // private void DoGet(AppearanceRequestData rdata) | ||
299 | // { | ||
300 | // AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); | ||
301 | // | ||
302 | // if (adata == null) | ||
303 | // { | ||
304 | // rdata.Fail(Rest.HttpStatusCodeNoContent, | ||
305 | // String.Format("appearance data not found for user {0} {1}", | ||
306 | // rdata.userProfile.FirstName, rdata.userProfile.SurName)); | ||
307 | // } | ||
308 | // rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID); | ||
309 | // | ||
310 | // rdata.initXmlWriter(); | ||
311 | // | ||
312 | // FormatUserAppearance(rdata); | ||
313 | // | ||
314 | // // Indicate a successful request | ||
315 | // | ||
316 | // rdata.Complete(); | ||
317 | // | ||
318 | // // Send the response to the user. The body will be implicitly | ||
319 | // // constructed from the result of the XML writer. | ||
320 | // | ||
321 | // rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method)); | ||
322 | // } | ||
323 | |||
324 | /// <summary> | ||
325 | /// POST adds NEW information to the user profile database. | ||
326 | /// This effectively resets the appearance before applying those | ||
327 | /// characteristics supplied in the request. | ||
328 | /// </summary> | ||
329 | |||
330 | // private void DoExtend(AppearanceRequestData rdata) | ||
331 | // { | ||
332 | // | ||
333 | // bool created = false; | ||
334 | // bool modified = false; | ||
335 | // string newnode = String.Empty; | ||
336 | // | ||
337 | // Rest.Log.DebugFormat("{0} POST ENTRY", MsgId); | ||
338 | // | ||
339 | // //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); | ||
340 | // | ||
341 | // rdata.userAppearance = new AvatarAppearance(); | ||
342 | // | ||
343 | // // Although the following behavior is admitted by HTTP I am becoming | ||
344 | // // increasingly doubtful that it is appropriate for REST. If I attempt to | ||
345 | // // add a new record, and it already exists, then it seems to me that the | ||
346 | // // attempt should fail, rather than update the existing record. | ||
347 | // AvatarData adata = null; | ||
348 | // if (GetUserAppearance(rdata)) | ||
349 | // { | ||
350 | // modified = rdata.userAppearance != null; | ||
351 | // created = !modified; | ||
352 | // adata = new AvatarData(rdata.userAppearance); | ||
353 | // Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); | ||
354 | // // Rest.UserServices.UpdateUserProfile(rdata.userProfile); | ||
355 | // } | ||
356 | // else | ||
357 | // { | ||
358 | // created = true; | ||
359 | // adata = new AvatarData(rdata.userAppearance); | ||
360 | // Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); | ||
361 | // // Rest.UserServices.UpdateUserProfile(rdata.userProfile); | ||
362 | // } | ||
363 | // | ||
364 | // if (created) | ||
365 | // { | ||
366 | // newnode = String.Format("{0} {1}", rdata.userProfile.FirstName, | ||
367 | // rdata.userProfile.SurName); | ||
368 | // // Must include a location header with a URI that identifies the new resource. | ||
369 | // | ||
370 | // rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}", | ||
371 | // rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode)); | ||
372 | // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
373 | // | ||
374 | // } | ||
375 | // else | ||
376 | // { | ||
377 | // if (modified) | ||
378 | // { | ||
379 | // rdata.Complete(Rest.HttpStatusCodeOK); | ||
380 | // } | ||
381 | // else | ||
382 | // { | ||
383 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
384 | // } | ||
385 | // } | ||
386 | // | ||
387 | // rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); | ||
388 | // | ||
389 | // } | ||
390 | |||
391 | /// <summary> | ||
392 | /// This updates the user's appearance. not all aspects need to be provided, | ||
393 | /// only those supplied will be changed. | ||
394 | /// </summary> | ||
395 | |||
396 | // private void DoUpdate(AppearanceRequestData rdata) | ||
397 | // { | ||
398 | // | ||
399 | // // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7 | ||
400 | // | ||
401 | // //bool created = false; | ||
402 | // //bool modified = false; | ||
403 | // | ||
404 | // | ||
405 | // //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); | ||
406 | // | ||
407 | // //// If the user exists then this is considered a modification regardless | ||
408 | // //// of what may, or may not be, specified in the payload. | ||
409 | // | ||
410 | // //if (rdata.userAppearance != null) | ||
411 | // //{ | ||
412 | // // modified = true; | ||
413 | // // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance); | ||
414 | // // Rest.UserServices.UpdateUserProfile(rdata.userProfile); | ||
415 | // //} | ||
416 | // | ||
417 | // //if (created) | ||
418 | // //{ | ||
419 | // // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
420 | // //} | ||
421 | // //else | ||
422 | // //{ | ||
423 | // // if (modified) | ||
424 | // // { | ||
425 | // // rdata.Complete(Rest.HttpStatusCodeOK); | ||
426 | // // } | ||
427 | // // else | ||
428 | // // { | ||
429 | // // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
430 | // // } | ||
431 | // //} | ||
432 | // | ||
433 | // rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); | ||
434 | // | ||
435 | // } | ||
436 | |||
437 | /// <summary> | ||
438 | /// Delete the specified user's appearance. This actually performs a reset | ||
439 | /// to the default avatar appearance, if the info is already there. | ||
440 | /// Existing ownership is preserved. All prior updates are lost and can not | ||
441 | /// be recovered. | ||
442 | /// </summary> | ||
443 | // private void DoDelete(AppearanceRequestData rdata) | ||
444 | // { | ||
445 | // AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); | ||
446 | // | ||
447 | // if (adata != null) | ||
448 | // { | ||
449 | // AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID); | ||
450 | // rdata.userAppearance = new AvatarAppearance(); | ||
451 | // rdata.userAppearance.Owner = old.Owner; | ||
452 | // adata = new AvatarData(rdata.userAppearance); | ||
453 | // | ||
454 | // Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); | ||
455 | // | ||
456 | // rdata.Complete(); | ||
457 | // } | ||
458 | // else | ||
459 | // { | ||
460 | // | ||
461 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
462 | // } | ||
463 | // | ||
464 | // rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); | ||
465 | // } | ||
466 | |||
467 | #endregion method-specific processing | ||
468 | |||
469 | private bool GetUserAppearance(AppearanceRequestData rdata) | ||
470 | { | ||
471 | |||
472 | XmlReader xml; | ||
473 | bool indata = false; | ||
474 | |||
475 | rdata.initXmlReader(); | ||
476 | xml = rdata.reader; | ||
477 | |||
478 | while (xml.Read()) | ||
479 | { | ||
480 | switch (xml.NodeType) | ||
481 | { | ||
482 | case XmlNodeType.Element : | ||
483 | switch (xml.Name) | ||
484 | { | ||
485 | case "Appearance" : | ||
486 | if (xml.MoveToAttribute("Height")) | ||
487 | { | ||
488 | rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value); | ||
489 | indata = true; | ||
490 | } | ||
491 | // if (xml.MoveToAttribute("Owner")) | ||
492 | // { | ||
493 | // rdata.userAppearance.Owner = (UUID)xml.Value; | ||
494 | // indata = true; | ||
495 | // } | ||
496 | if (xml.MoveToAttribute("Serial")) | ||
497 | { | ||
498 | rdata.userAppearance.Serial = Convert.ToInt32(xml.Value); | ||
499 | indata = true; | ||
500 | } | ||
501 | break; | ||
502 | /* | ||
503 | case "Body" : | ||
504 | if (xml.MoveToAttribute("Item")) | ||
505 | { | ||
506 | rdata.userAppearance.BodyItem = (UUID)xml.Value; | ||
507 | indata = true; | ||
508 | } | ||
509 | if (xml.MoveToAttribute("Asset")) | ||
510 | { | ||
511 | rdata.userAppearance.BodyAsset = (UUID)xml.Value; | ||
512 | indata = true; | ||
513 | } | ||
514 | break; | ||
515 | case "Skin" : | ||
516 | if (xml.MoveToAttribute("Item")) | ||
517 | { | ||
518 | rdata.userAppearance.SkinItem = (UUID)xml.Value; | ||
519 | indata = true; | ||
520 | } | ||
521 | if (xml.MoveToAttribute("Asset")) | ||
522 | { | ||
523 | rdata.userAppearance.SkinAsset = (UUID)xml.Value; | ||
524 | indata = true; | ||
525 | } | ||
526 | break; | ||
527 | case "Hair" : | ||
528 | if (xml.MoveToAttribute("Item")) | ||
529 | { | ||
530 | rdata.userAppearance.HairItem = (UUID)xml.Value; | ||
531 | indata = true; | ||
532 | } | ||
533 | if (xml.MoveToAttribute("Asset")) | ||
534 | { | ||
535 | rdata.userAppearance.HairAsset = (UUID)xml.Value; | ||
536 | indata = true; | ||
537 | } | ||
538 | break; | ||
539 | case "Eyes" : | ||
540 | if (xml.MoveToAttribute("Item")) | ||
541 | { | ||
542 | rdata.userAppearance.EyesItem = (UUID)xml.Value; | ||
543 | indata = true; | ||
544 | } | ||
545 | if (xml.MoveToAttribute("Asset")) | ||
546 | { | ||
547 | rdata.userAppearance.EyesAsset = (UUID)xml.Value; | ||
548 | indata = true; | ||
549 | } | ||
550 | break; | ||
551 | case "Shirt" : | ||
552 | if (xml.MoveToAttribute("Item")) | ||
553 | { | ||
554 | rdata.userAppearance.ShirtItem = (UUID)xml.Value; | ||
555 | indata = true; | ||
556 | } | ||
557 | if (xml.MoveToAttribute("Asset")) | ||
558 | { | ||
559 | rdata.userAppearance.ShirtAsset = (UUID)xml.Value; | ||
560 | indata = true; | ||
561 | } | ||
562 | break; | ||
563 | case "Pants" : | ||
564 | if (xml.MoveToAttribute("Item")) | ||
565 | { | ||
566 | rdata.userAppearance.PantsItem = (UUID)xml.Value; | ||
567 | indata = true; | ||
568 | } | ||
569 | if (xml.MoveToAttribute("Asset")) | ||
570 | { | ||
571 | rdata.userAppearance.PantsAsset = (UUID)xml.Value; | ||
572 | indata = true; | ||
573 | } | ||
574 | break; | ||
575 | case "Shoes" : | ||
576 | if (xml.MoveToAttribute("Item")) | ||
577 | { | ||
578 | rdata.userAppearance.ShoesItem = (UUID)xml.Value; | ||
579 | indata = true; | ||
580 | } | ||
581 | if (xml.MoveToAttribute("Asset")) | ||
582 | { | ||
583 | rdata.userAppearance.ShoesAsset = (UUID)xml.Value; | ||
584 | indata = true; | ||
585 | } | ||
586 | break; | ||
587 | case "Socks" : | ||
588 | if (xml.MoveToAttribute("Item")) | ||
589 | { | ||
590 | rdata.userAppearance.SocksItem = (UUID)xml.Value; | ||
591 | indata = true; | ||
592 | } | ||
593 | if (xml.MoveToAttribute("Asset")) | ||
594 | { | ||
595 | rdata.userAppearance.SocksAsset = (UUID)xml.Value; | ||
596 | indata = true; | ||
597 | } | ||
598 | break; | ||
599 | case "Jacket" : | ||
600 | if (xml.MoveToAttribute("Item")) | ||
601 | { | ||
602 | rdata.userAppearance.JacketItem = (UUID)xml.Value; | ||
603 | indata = true; | ||
604 | } | ||
605 | if (xml.MoveToAttribute("Asset")) | ||
606 | { | ||
607 | rdata.userAppearance.JacketAsset = (UUID)xml.Value; | ||
608 | indata = true; | ||
609 | } | ||
610 | break; | ||
611 | case "Gloves" : | ||
612 | if (xml.MoveToAttribute("Item")) | ||
613 | { | ||
614 | rdata.userAppearance.GlovesItem = (UUID)xml.Value; | ||
615 | indata = true; | ||
616 | } | ||
617 | if (xml.MoveToAttribute("Asset")) | ||
618 | { | ||
619 | rdata.userAppearance.GlovesAsset = (UUID)xml.Value; | ||
620 | indata = true; | ||
621 | } | ||
622 | break; | ||
623 | case "UnderShirt" : | ||
624 | if (xml.MoveToAttribute("Item")) | ||
625 | { | ||
626 | rdata.userAppearance.UnderShirtItem = (UUID)xml.Value; | ||
627 | indata = true; | ||
628 | } | ||
629 | if (xml.MoveToAttribute("Asset")) | ||
630 | { | ||
631 | rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value; | ||
632 | indata = true; | ||
633 | } | ||
634 | break; | ||
635 | case "UnderPants" : | ||
636 | if (xml.MoveToAttribute("Item")) | ||
637 | { | ||
638 | rdata.userAppearance.UnderPantsItem = (UUID)xml.Value; | ||
639 | indata = true; | ||
640 | } | ||
641 | if (xml.MoveToAttribute("Asset")) | ||
642 | { | ||
643 | rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value; | ||
644 | indata = true; | ||
645 | } | ||
646 | break; | ||
647 | case "Skirt" : | ||
648 | if (xml.MoveToAttribute("Item")) | ||
649 | { | ||
650 | rdata.userAppearance.SkirtItem = (UUID)xml.Value; | ||
651 | indata = true; | ||
652 | } | ||
653 | if (xml.MoveToAttribute("Asset")) | ||
654 | { | ||
655 | rdata.userAppearance.SkirtAsset = (UUID)xml.Value; | ||
656 | indata = true; | ||
657 | } | ||
658 | break; | ||
659 | */ | ||
660 | case "Attachment" : | ||
661 | { | ||
662 | |||
663 | int ap; | ||
664 | UUID asset; | ||
665 | UUID item; | ||
666 | |||
667 | if (xml.MoveToAttribute("AtPoint")) | ||
668 | { | ||
669 | ap = Convert.ToInt32(xml.Value); | ||
670 | if (xml.MoveToAttribute("Asset")) | ||
671 | { | ||
672 | asset = new UUID(xml.Value); | ||
673 | if (xml.MoveToAttribute("Asset")) | ||
674 | { | ||
675 | item = new UUID(xml.Value); | ||
676 | rdata.userAppearance.SetAttachment(ap, item, asset); | ||
677 | indata = true; | ||
678 | } | ||
679 | } | ||
680 | } | ||
681 | } | ||
682 | break; | ||
683 | case "Texture" : | ||
684 | if (xml.MoveToAttribute("Default")) | ||
685 | { | ||
686 | rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value)); | ||
687 | indata = true; | ||
688 | } | ||
689 | break; | ||
690 | case "Face" : | ||
691 | { | ||
692 | uint index; | ||
693 | if (xml.MoveToAttribute("Index")) | ||
694 | { | ||
695 | index = Convert.ToUInt32(xml.Value); | ||
696 | if (xml.MoveToAttribute("Id")) | ||
697 | { | ||
698 | rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value); | ||
699 | indata = true; | ||
700 | } | ||
701 | } | ||
702 | } | ||
703 | break; | ||
704 | case "VisualParameters" : | ||
705 | { | ||
706 | xml.ReadContentAsBase64(rdata.userAppearance.VisualParams, | ||
707 | 0, rdata.userAppearance.VisualParams.Length); | ||
708 | indata = true; | ||
709 | } | ||
710 | break; | ||
711 | } | ||
712 | break; | ||
713 | } | ||
714 | } | ||
715 | |||
716 | return indata; | ||
717 | |||
718 | } | ||
719 | |||
720 | private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset) | ||
721 | { | ||
722 | if (item != UUID.Zero || asset != UUID.Zero) | ||
723 | { | ||
724 | rdata.writer.WriteStartElement(part); | ||
725 | if (item != UUID.Zero) | ||
726 | { | ||
727 | rdata.writer.WriteAttributeString("Item",item.ToString()); | ||
728 | } | ||
729 | |||
730 | if (asset != UUID.Zero) | ||
731 | { | ||
732 | rdata.writer.WriteAttributeString("Asset",asset.ToString()); | ||
733 | } | ||
734 | rdata.writer.WriteEndElement(); | ||
735 | } | ||
736 | } | ||
737 | |||
738 | private void FormatUserAppearance(AppearanceRequestData rdata) | ||
739 | { | ||
740 | |||
741 | Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId); | ||
742 | |||
743 | if (rdata.userAppearance != null) | ||
744 | { | ||
745 | |||
746 | Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId); | ||
747 | rdata.writer.WriteStartElement("Appearance"); | ||
748 | |||
749 | rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString()); | ||
750 | // if (rdata.userAppearance.Owner != UUID.Zero) | ||
751 | // rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString()); | ||
752 | rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString()); | ||
753 | |||
754 | /* | ||
755 | FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset); | ||
756 | FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset); | ||
757 | FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset); | ||
758 | FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset); | ||
759 | |||
760 | FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset); | ||
761 | FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset); | ||
762 | FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset); | ||
763 | FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset); | ||
764 | FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset); | ||
765 | |||
766 | FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset); | ||
767 | FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset); | ||
768 | |||
769 | FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset); | ||
770 | FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset); | ||
771 | */ | ||
772 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId); | ||
773 | |||
774 | rdata.writer.WriteStartElement("Attachments"); | ||
775 | List<AvatarAttachment> attachments = rdata.userAppearance.GetAttachments(); | ||
776 | foreach (AvatarAttachment attach in attachments) | ||
777 | { | ||
778 | rdata.writer.WriteStartElement("Attachment"); | ||
779 | rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString()); | ||
780 | rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString()); | ||
781 | rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString()); | ||
782 | rdata.writer.WriteEndElement(); | ||
783 | } | ||
784 | rdata.writer.WriteEndElement(); | ||
785 | |||
786 | Primitive.TextureEntry texture = rdata.userAppearance.Texture; | ||
787 | |||
788 | if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null)) | ||
789 | { | ||
790 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId); | ||
791 | |||
792 | rdata.writer.WriteStartElement("Texture"); | ||
793 | |||
794 | if (texture.DefaultTexture != null) | ||
795 | { | ||
796 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId); | ||
797 | rdata.writer.WriteAttributeString("Default", | ||
798 | texture.DefaultTexture.TextureID.ToString()); | ||
799 | } | ||
800 | |||
801 | if (texture.FaceTextures != null) | ||
802 | { | ||
803 | |||
804 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId); | ||
805 | |||
806 | for (int i=0; i<texture.FaceTextures.Length;i++) | ||
807 | { | ||
808 | if (texture.FaceTextures[i] != null) | ||
809 | { | ||
810 | rdata.writer.WriteStartElement("Face"); | ||
811 | rdata.writer.WriteAttributeString("Index", i.ToString()); | ||
812 | rdata.writer.WriteAttributeString("Id", | ||
813 | texture.FaceTextures[i].TextureID.ToString()); | ||
814 | rdata.writer.WriteEndElement(); | ||
815 | } | ||
816 | } | ||
817 | } | ||
818 | |||
819 | rdata.writer.WriteEndElement(); | ||
820 | } | ||
821 | |||
822 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting visual parameters", MsgId); | ||
823 | |||
824 | rdata.writer.WriteStartElement("VisualParameters"); | ||
825 | rdata.writer.WriteBase64(rdata.userAppearance.VisualParams,0, | ||
826 | rdata.userAppearance.VisualParams.Length); | ||
827 | rdata.writer.WriteEndElement(); | ||
828 | rdata.writer.WriteFullEndElement(); | ||
829 | } | ||
830 | |||
831 | Rest.Log.DebugFormat("{0} FormatUserAppearance: completed", MsgId); | ||
832 | |||
833 | return; | ||
834 | } | ||
835 | |||
836 | #region appearance RequestData extension | ||
837 | |||
838 | internal class AppearanceRequestData : RequestData | ||
839 | { | ||
840 | |||
841 | /// <summary> | ||
842 | /// These are the inventory specific request/response state | ||
843 | /// extensions. | ||
844 | /// </summary> | ||
845 | |||
846 | internal UUID uuid = UUID.Zero; | ||
847 | internal UserProfileData userProfile = null; | ||
848 | internal AvatarAppearance userAppearance = null; | ||
849 | |||
850 | internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
851 | : base(request, response, prefix) | ||
852 | { | ||
853 | } | ||
854 | |||
855 | } | ||
856 | |||
857 | #endregion Appearance RequestData extension | ||
858 | |||
859 | } | ||
860 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs deleted file mode 100644 index 4ba3d77..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ /dev/null | |||
@@ -1,383 +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.Xml; | ||
30 | using OpenMetaverse; | ||
31 | using OpenSim.Framework; | ||
32 | using OpenSim.Framework.Servers; | ||
33 | using OpenSim.Framework.Servers.HttpServer; | ||
34 | |||
35 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
36 | { | ||
37 | public class RestAssetServices : IRest | ||
38 | { | ||
39 | private bool enabled = false; | ||
40 | private string qPrefix = "assets"; | ||
41 | |||
42 | // A simple constructor is used to handle any once-only | ||
43 | // initialization of working classes. | ||
44 | |||
45 | public RestAssetServices() | ||
46 | { | ||
47 | Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); | ||
48 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
49 | |||
50 | // If the handler specifies a relative path for its domain | ||
51 | // then we must add the standard absolute prefix, e.g. /admin | ||
52 | |||
53 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
54 | { | ||
55 | Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); | ||
56 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
57 | Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); | ||
58 | } | ||
59 | |||
60 | // Register interface using the fully-qualified prefix | ||
61 | |||
62 | Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); | ||
63 | |||
64 | // Activate if all went OK | ||
65 | |||
66 | enabled = true; | ||
67 | |||
68 | Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); | ||
69 | } | ||
70 | |||
71 | // Post-construction, pre-enabled initialization opportunity | ||
72 | // Not currently exploited. | ||
73 | |||
74 | public void Initialize() | ||
75 | { | ||
76 | } | ||
77 | |||
78 | // Called by the plug-in to halt REST processing. Local processing is | ||
79 | // disabled, and control blocks until all current processing has | ||
80 | // completed. No new processing will be started | ||
81 | |||
82 | public void Close() | ||
83 | { | ||
84 | enabled = false; | ||
85 | Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix); | ||
86 | } | ||
87 | |||
88 | // Properties | ||
89 | |||
90 | internal string MsgId | ||
91 | { | ||
92 | get { return Rest.MsgId; } | ||
93 | } | ||
94 | |||
95 | #region Interface | ||
96 | |||
97 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
98 | { | ||
99 | return (RequestData) new AssetRequestData(request, response, prefix); | ||
100 | } | ||
101 | |||
102 | // Asset Handler | ||
103 | |||
104 | private void DoAsset(RequestData rparm) | ||
105 | { | ||
106 | if (!enabled) return; | ||
107 | |||
108 | AssetRequestData rdata = (AssetRequestData) rparm; | ||
109 | |||
110 | Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix); | ||
111 | |||
112 | // Now that we know this is a serious attempt to | ||
113 | // access inventory data, we should find out who | ||
114 | // is asking, and make sure they are authorized | ||
115 | // to do so. We need to validate the caller's | ||
116 | // identity before revealing anything about the | ||
117 | // status quo. Authenticate throws an exception | ||
118 | // via Fail if no identity information is present. | ||
119 | // | ||
120 | // With the present HTTP server we can't use the | ||
121 | // builtin authentication mechanisms because they | ||
122 | // would be enforced for all in-bound requests. | ||
123 | // Instead we look at the headers ourselves and | ||
124 | // handle authentication directly. | ||
125 | |||
126 | try | ||
127 | { | ||
128 | if (!rdata.IsAuthenticated) | ||
129 | { | ||
130 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); | ||
131 | } | ||
132 | } | ||
133 | catch (RestException e) | ||
134 | { | ||
135 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
136 | { | ||
137 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
138 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | ||
139 | rdata.request.Headers.Get("Authorization")); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
144 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | ||
145 | rdata.request.Headers.Get("Authorization")); | ||
146 | } | ||
147 | throw (e); | ||
148 | } | ||
149 | |||
150 | // Remove the prefix and what's left are the parameters. If we don't have | ||
151 | // the parameters we need, fail the request. Parameters do NOT include | ||
152 | // any supplied query values. | ||
153 | |||
154 | if (rdata.Parameters.Length > 0) | ||
155 | { | ||
156 | switch (rdata.method) | ||
157 | { | ||
158 | case "get" : | ||
159 | DoGet(rdata); | ||
160 | break; | ||
161 | case "put" : | ||
162 | DoPut(rdata); | ||
163 | break; | ||
164 | case "post" : | ||
165 | DoPost(rdata); | ||
166 | break; | ||
167 | case "delete" : | ||
168 | default : | ||
169 | Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", | ||
170 | MsgId, rdata.method); | ||
171 | rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); | ||
172 | break; | ||
173 | } | ||
174 | } | ||
175 | else | ||
176 | { | ||
177 | Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); | ||
178 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); | ||
179 | } | ||
180 | |||
181 | Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); | ||
182 | } | ||
183 | |||
184 | #endregion Interface | ||
185 | |||
186 | /// <summary> | ||
187 | /// The only parameter we recognize is a UUID.If an asset with this identification is | ||
188 | /// found, it's content, base-64 encoded, is returned to the client. | ||
189 | /// </summary> | ||
190 | |||
191 | private void DoGet(AssetRequestData rdata) | ||
192 | { | ||
193 | Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
194 | |||
195 | if (rdata.Parameters.Length == 1) | ||
196 | { | ||
197 | UUID uuid = new UUID(rdata.Parameters[0]); | ||
198 | AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); | ||
199 | |||
200 | if (asset != null) | ||
201 | { | ||
202 | Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]); | ||
203 | |||
204 | rdata.initXmlWriter(); | ||
205 | |||
206 | rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty); | ||
207 | |||
208 | rdata.writer.WriteAttributeString("id", asset.ID); | ||
209 | rdata.writer.WriteAttributeString("name", asset.Name); | ||
210 | rdata.writer.WriteAttributeString("desc", asset.Description); | ||
211 | rdata.writer.WriteAttributeString("type", asset.Type.ToString()); | ||
212 | rdata.writer.WriteAttributeString("local", asset.Local.ToString()); | ||
213 | rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString()); | ||
214 | |||
215 | rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); | ||
216 | |||
217 | rdata.writer.WriteFullEndElement(); | ||
218 | |||
219 | } | ||
220 | else | ||
221 | { | ||
222 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | ||
223 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); | ||
224 | } | ||
225 | } | ||
226 | |||
227 | rdata.Complete(); | ||
228 | rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method)); | ||
229 | |||
230 | } | ||
231 | |||
232 | /// <summary> | ||
233 | /// UPDATE existing item, if it exists. URI identifies the item in question. | ||
234 | /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) | ||
235 | /// is decoded and stored in the database, identified by the supplied UUID. | ||
236 | /// </summary> | ||
237 | private void DoPut(AssetRequestData rdata) | ||
238 | { | ||
239 | bool modified = false; | ||
240 | bool created = false; | ||
241 | |||
242 | AssetBase asset = null; | ||
243 | |||
244 | Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
245 | |||
246 | if (rdata.Parameters.Length == 1) | ||
247 | { | ||
248 | |||
249 | rdata.initXmlReader(); | ||
250 | XmlReader xml = rdata.reader; | ||
251 | |||
252 | if (!xml.ReadToFollowing("Asset")) | ||
253 | { | ||
254 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); | ||
255 | rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); | ||
256 | } | ||
257 | |||
258 | UUID uuid = new UUID(rdata.Parameters[0]); | ||
259 | asset = Rest.AssetServices.Get(uuid.ToString()); | ||
260 | |||
261 | modified = (asset != null); | ||
262 | created = !modified; | ||
263 | |||
264 | asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); | ||
265 | asset.Description = xml.GetAttribute("desc"); | ||
266 | asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; | ||
267 | asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; | ||
268 | asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); | ||
269 | |||
270 | if (asset.ID != rdata.Parameters[0]) | ||
271 | { | ||
272 | Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}", | ||
273 | MsgId, rdata.Parameters[0], asset.ID); | ||
274 | } | ||
275 | |||
276 | Rest.AssetServices.Store(asset); | ||
277 | |||
278 | } | ||
279 | else | ||
280 | { | ||
281 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | ||
282 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); | ||
283 | } | ||
284 | |||
285 | if (created) | ||
286 | { | ||
287 | rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID)); | ||
288 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
289 | } | ||
290 | else | ||
291 | { | ||
292 | if (modified) | ||
293 | { | ||
294 | rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID)); | ||
295 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
296 | } | ||
297 | else | ||
298 | { | ||
299 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); | ||
304 | |||
305 | } | ||
306 | |||
307 | /// <summary> | ||
308 | /// CREATE new item, replace if it exists. URI identifies the context for the item in question. | ||
309 | /// No parameters are required for POST, just thepayload. | ||
310 | /// </summary> | ||
311 | |||
312 | private void DoPost(AssetRequestData rdata) | ||
313 | { | ||
314 | |||
315 | bool modified = false; | ||
316 | bool created = false; | ||
317 | |||
318 | Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
319 | |||
320 | if (rdata.Parameters.Length != 0) | ||
321 | { | ||
322 | Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path); | ||
323 | Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path); | ||
324 | } | ||
325 | |||
326 | rdata.initXmlReader(); | ||
327 | XmlReader xml = rdata.reader; | ||
328 | |||
329 | if (!xml.ReadToFollowing("Asset")) | ||
330 | { | ||
331 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); | ||
332 | rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); | ||
333 | } | ||
334 | |||
335 | UUID uuid = new UUID(xml.GetAttribute("id")); | ||
336 | AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); | ||
337 | |||
338 | modified = (asset != null); | ||
339 | created = !modified; | ||
340 | |||
341 | asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); | ||
342 | asset.Description = xml.GetAttribute("desc"); | ||
343 | asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; | ||
344 | asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; | ||
345 | asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); | ||
346 | |||
347 | Rest.AssetServices.Store(asset); | ||
348 | |||
349 | if (created) | ||
350 | { | ||
351 | rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID)); | ||
352 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
353 | } | ||
354 | else | ||
355 | { | ||
356 | if (modified) | ||
357 | { | ||
358 | rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID)); | ||
359 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
360 | } | ||
361 | else | ||
362 | { | ||
363 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
364 | } | ||
365 | } | ||
366 | |||
367 | rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); | ||
368 | |||
369 | } | ||
370 | |||
371 | /// <summary> | ||
372 | /// Asset processing has no special data area requirements. | ||
373 | /// </summary> | ||
374 | |||
375 | internal class AssetRequestData : RequestData | ||
376 | { | ||
377 | internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
378 | : base(request, response, prefix) | ||
379 | { | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs deleted file mode 100644 index e79d2bd..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs +++ /dev/null | |||
@@ -1,448 +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.Xml; | ||
30 | using System.IO; | ||
31 | using OpenMetaverse; | ||
32 | using OpenSim.Framework; | ||
33 | using OpenSim.Framework.Servers; | ||
34 | using OpenSim.Framework.Servers.HttpServer; | ||
35 | |||
36 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
37 | { | ||
38 | public class RestFileServices : IRest | ||
39 | { | ||
40 | private bool enabled = false; | ||
41 | private string qPrefix = "files"; | ||
42 | |||
43 | // A simple constructor is used to handle any once-only | ||
44 | // initialization of working classes. | ||
45 | |||
46 | public RestFileServices() | ||
47 | { | ||
48 | Rest.Log.InfoFormat("{0} File services initializing", MsgId); | ||
49 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
50 | |||
51 | // If the handler specifies a relative path for its domain | ||
52 | // then we must add the standard absolute prefix, e.g. /admin | ||
53 | |||
54 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
55 | { | ||
56 | Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); | ||
57 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
58 | Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); | ||
59 | } | ||
60 | |||
61 | // Register interface using the fully-qualified prefix | ||
62 | |||
63 | Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate); | ||
64 | |||
65 | // Activate if all went OK | ||
66 | |||
67 | enabled = true; | ||
68 | |||
69 | Rest.Log.InfoFormat("{0} File services initialization complete", MsgId); | ||
70 | } | ||
71 | |||
72 | // Post-construction, pre-enabled initialization opportunity | ||
73 | // Not currently exploited. | ||
74 | |||
75 | public void Initialize() | ||
76 | { | ||
77 | } | ||
78 | |||
79 | // Called by the plug-in to halt REST processing. Local processing is | ||
80 | // disabled, and control blocks until all current processing has | ||
81 | // completed. No new processing will be started | ||
82 | |||
83 | public void Close() | ||
84 | { | ||
85 | enabled = false; | ||
86 | Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix); | ||
87 | } | ||
88 | |||
89 | // Properties | ||
90 | |||
91 | internal string MsgId | ||
92 | { | ||
93 | get { return Rest.MsgId; } | ||
94 | } | ||
95 | |||
96 | #region Interface | ||
97 | |||
98 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
99 | { | ||
100 | return (RequestData) new FileRequestData(request, response, prefix); | ||
101 | } | ||
102 | |||
103 | // Asset Handler | ||
104 | |||
105 | private void DoFile(RequestData rparm) | ||
106 | { | ||
107 | if (!enabled) return; | ||
108 | |||
109 | FileRequestData rdata = (FileRequestData) rparm; | ||
110 | |||
111 | Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix); | ||
112 | |||
113 | // Now that we know this is a serious attempt to | ||
114 | // access file data, we should find out who | ||
115 | // is asking, and make sure they are authorized | ||
116 | // to do so. We need to validate the caller's | ||
117 | // identity before revealing anything about the | ||
118 | // status quo. Authenticate throws an exception | ||
119 | // via Fail if no identity information is present. | ||
120 | // | ||
121 | // With the present HTTP server we can't use the | ||
122 | // builtin authentication mechanisms because they | ||
123 | // would be enforced for all in-bound requests. | ||
124 | // Instead we look at the headers ourselves and | ||
125 | // handle authentication directly. | ||
126 | |||
127 | try | ||
128 | { | ||
129 | if (!rdata.IsAuthenticated) | ||
130 | { | ||
131 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); | ||
132 | } | ||
133 | } | ||
134 | catch (RestException e) | ||
135 | { | ||
136 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
137 | { | ||
138 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
139 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | ||
140 | rdata.request.Headers.Get("Authorization")); | ||
141 | } | ||
142 | else | ||
143 | { | ||
144 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
145 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | ||
146 | rdata.request.Headers.Get("Authorization")); | ||
147 | } | ||
148 | throw (e); | ||
149 | } | ||
150 | |||
151 | // Remove the prefix and what's left are the parameters. If we don't have | ||
152 | // the parameters we need, fail the request. Parameters do NOT include | ||
153 | // any supplied query values. | ||
154 | |||
155 | if (rdata.Parameters.Length > 0) | ||
156 | { | ||
157 | switch (rdata.method) | ||
158 | { | ||
159 | case "get" : | ||
160 | DoGet(rdata); | ||
161 | break; | ||
162 | case "put" : | ||
163 | DoPut(rdata); | ||
164 | break; | ||
165 | case "post" : | ||
166 | DoPost(rdata); | ||
167 | break; | ||
168 | case "delete" : | ||
169 | DoDelete(rdata); | ||
170 | break; | ||
171 | default : | ||
172 | Rest.Log.WarnFormat("{0} File: Method not supported: {1}", | ||
173 | MsgId, rdata.method); | ||
174 | rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); | ||
175 | break; | ||
176 | } | ||
177 | } | ||
178 | else | ||
179 | { | ||
180 | Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId); | ||
181 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); | ||
182 | } | ||
183 | |||
184 | Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId); | ||
185 | |||
186 | } | ||
187 | |||
188 | #endregion Interface | ||
189 | |||
190 | /// <summary> | ||
191 | /// The only parameter we recognize is a UUID.If an asset with this identification is | ||
192 | /// found, it's content, base-64 encoded, is returned to the client. | ||
193 | /// </summary> | ||
194 | |||
195 | private void DoGet(FileRequestData rdata) | ||
196 | { | ||
197 | |||
198 | string path = String.Empty; | ||
199 | |||
200 | Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
201 | |||
202 | if (rdata.Parameters.Length > 1) | ||
203 | { | ||
204 | try | ||
205 | { | ||
206 | path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); | ||
207 | if (File.Exists(path)) | ||
208 | { | ||
209 | Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path); | ||
210 | Byte[] data = File.ReadAllBytes(path); | ||
211 | rdata.initXmlWriter(); | ||
212 | rdata.writer.WriteStartElement(String.Empty,"File",String.Empty); | ||
213 | rdata.writer.WriteAttributeString("name", path); | ||
214 | rdata.writer.WriteBase64(data,0,data.Length); | ||
215 | rdata.writer.WriteFullEndElement(); | ||
216 | } | ||
217 | else | ||
218 | { | ||
219 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path); | ||
220 | rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path)); | ||
221 | } | ||
222 | } | ||
223 | catch (Exception e) | ||
224 | { | ||
225 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message); | ||
226 | rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", | ||
227 | path, e.Message)); | ||
228 | } | ||
229 | } | ||
230 | |||
231 | rdata.Complete(); | ||
232 | rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method)); | ||
233 | |||
234 | } | ||
235 | |||
236 | /// <summary> | ||
237 | /// UPDATE existing item, if it exists. URI identifies the item in question. | ||
238 | /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) | ||
239 | /// is decoded and stored in the database, identified by the supplied UUID. | ||
240 | /// </summary> | ||
241 | private void DoPut(FileRequestData rdata) | ||
242 | { | ||
243 | bool modified = false; | ||
244 | bool created = false; | ||
245 | string path = String.Empty; | ||
246 | |||
247 | Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
248 | |||
249 | if (rdata.Parameters.Length > 1) | ||
250 | { | ||
251 | try | ||
252 | { | ||
253 | path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); | ||
254 | bool maymod = File.Exists(path); | ||
255 | |||
256 | rdata.initXmlReader(); | ||
257 | XmlReader xml = rdata.reader; | ||
258 | |||
259 | if (!xml.ReadToFollowing("File")) | ||
260 | { | ||
261 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); | ||
262 | rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); | ||
263 | } | ||
264 | |||
265 | Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); | ||
266 | |||
267 | File.WriteAllBytes(path,data); | ||
268 | modified = maymod; | ||
269 | created = ! maymod; | ||
270 | } | ||
271 | catch (Exception e) | ||
272 | { | ||
273 | Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, | ||
274 | e.Message); | ||
275 | } | ||
276 | } | ||
277 | else | ||
278 | { | ||
279 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | ||
280 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); | ||
281 | } | ||
282 | |||
283 | if (created) | ||
284 | { | ||
285 | rdata.appendStatus(String.Format("<p> Created file {0} <p>", path)); | ||
286 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
287 | } | ||
288 | else | ||
289 | { | ||
290 | if (modified) | ||
291 | { | ||
292 | rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path)); | ||
293 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
294 | } | ||
295 | else | ||
296 | { | ||
297 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); | ||
302 | |||
303 | } | ||
304 | |||
305 | /// <summary> | ||
306 | /// CREATE new item, replace if it exists. URI identifies the context for the item in question. | ||
307 | /// No parameters are required for POST, just thepayload. | ||
308 | /// </summary> | ||
309 | |||
310 | private void DoPost(FileRequestData rdata) | ||
311 | { | ||
312 | |||
313 | bool modified = false; | ||
314 | bool created = false; | ||
315 | string path = String.Empty; | ||
316 | |||
317 | Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
318 | |||
319 | if (rdata.Parameters.Length > 1) | ||
320 | { | ||
321 | try | ||
322 | { | ||
323 | path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); | ||
324 | bool maymod = File.Exists(path); | ||
325 | |||
326 | rdata.initXmlReader(); | ||
327 | XmlReader xml = rdata.reader; | ||
328 | |||
329 | if (!xml.ReadToFollowing("File")) | ||
330 | { | ||
331 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); | ||
332 | rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); | ||
333 | } | ||
334 | |||
335 | Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); | ||
336 | |||
337 | File.WriteAllBytes(path,data); | ||
338 | modified = maymod; | ||
339 | created = ! maymod; | ||
340 | } | ||
341 | catch (Exception e) | ||
342 | { | ||
343 | Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, | ||
344 | e.Message); | ||
345 | } | ||
346 | } | ||
347 | else | ||
348 | { | ||
349 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | ||
350 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); | ||
351 | } | ||
352 | |||
353 | if (created) | ||
354 | { | ||
355 | rdata.appendStatus(String.Format("<p> Created file {0} <p>", path)); | ||
356 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
357 | } | ||
358 | else | ||
359 | { | ||
360 | if (modified) | ||
361 | { | ||
362 | rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path)); | ||
363 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
364 | } | ||
365 | else | ||
366 | { | ||
367 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
368 | } | ||
369 | } | ||
370 | |||
371 | rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); | ||
372 | |||
373 | } | ||
374 | |||
375 | /// <summary> | ||
376 | /// CREATE new item, replace if it exists. URI identifies the context for the item in question. | ||
377 | /// No parameters are required for POST, just thepayload. | ||
378 | /// </summary> | ||
379 | |||
380 | private void DoDelete(FileRequestData rdata) | ||
381 | { | ||
382 | |||
383 | bool modified = false; | ||
384 | bool created = false; | ||
385 | string path = String.Empty; | ||
386 | |||
387 | Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); | ||
388 | |||
389 | if (rdata.Parameters.Length > 1) | ||
390 | { | ||
391 | try | ||
392 | { | ||
393 | path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); | ||
394 | |||
395 | if (File.Exists(path)) | ||
396 | { | ||
397 | File.Delete(path); | ||
398 | } | ||
399 | } | ||
400 | catch (Exception e) | ||
401 | { | ||
402 | Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, | ||
403 | e.Message); | ||
404 | rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", | ||
405 | path, e.Message)); | ||
406 | } | ||
407 | } | ||
408 | else | ||
409 | { | ||
410 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | ||
411 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); | ||
412 | } | ||
413 | |||
414 | if (created) | ||
415 | { | ||
416 | rdata.appendStatus(String.Format("<p> Created file {0} <p>", path)); | ||
417 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
418 | } | ||
419 | else | ||
420 | { | ||
421 | if (modified) | ||
422 | { | ||
423 | rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path)); | ||
424 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
425 | } | ||
426 | else | ||
427 | { | ||
428 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
429 | } | ||
430 | } | ||
431 | |||
432 | rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); | ||
433 | |||
434 | } | ||
435 | |||
436 | /// <summary> | ||
437 | /// File processing has no special data area requirements. | ||
438 | /// </summary> | ||
439 | |||
440 | internal class FileRequestData : RequestData | ||
441 | { | ||
442 | internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
443 | : base(request, response, prefix) | ||
444 | { | ||
445 | } | ||
446 | } | ||
447 | } | ||
448 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs deleted file mode 100644 index 072bd6f..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ /dev/null | |||
@@ -1,662 +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.Reflection; | ||
31 | using OpenSim.Framework.Servers; | ||
32 | using OpenSim.Framework.Servers.HttpServer; | ||
33 | |||
34 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
35 | { | ||
36 | /// <remarks> | ||
37 | /// The class signature reveals the roles that RestHandler plays. | ||
38 | /// | ||
39 | /// [1] It is a sub-class of RestPlugin. It inherits and extends | ||
40 | /// the functionality of this class, constraining it to the | ||
41 | /// specific needs of this REST implementation. This relates | ||
42 | /// to the plug-in mechanism supported by OpenSim, the specifics | ||
43 | /// of which are mostly hidden by RestPlugin. | ||
44 | /// [2] IRestHandler describes the interface that this class | ||
45 | /// exports to service implementations. This is the services | ||
46 | /// management interface. | ||
47 | /// [3] IHttpAgentHandler describes the interface that is exported | ||
48 | /// to the BaseHttpServer in support of this particular HTTP | ||
49 | /// processing model. This is the request interface of the | ||
50 | /// handler. | ||
51 | /// </remarks> | ||
52 | |||
53 | public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler | ||
54 | { | ||
55 | // Handler tables: both stream and REST are supported. The path handlers and their | ||
56 | // respective allocators are stored in separate tables. | ||
57 | |||
58 | internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>(); | ||
59 | internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>(); | ||
60 | internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>(); | ||
61 | |||
62 | #region local static state | ||
63 | |||
64 | private static bool handlersLoaded = false; | ||
65 | private static List<Type> classes = new List<Type>(); | ||
66 | private static List<IRest> handlers = new List<IRest>(); | ||
67 | private static Type[] parms = new Type[0]; | ||
68 | private static Object[] args = new Object[0]; | ||
69 | |||
70 | /// <summary> | ||
71 | /// This static initializer scans the ASSEMBLY for classes that | ||
72 | /// export the IRest interface and builds a list of them. These | ||
73 | /// are later activated by the handler. To add a new handler it | ||
74 | /// is only necessary to create a new services class that implements | ||
75 | /// the IRest interface, and recompile the handler. This gives | ||
76 | /// all of the build-time flexibility of a modular approach | ||
77 | /// while not introducing yet-another module loader. Note that | ||
78 | /// multiple assembles can still be built, each with its own set | ||
79 | /// of handlers. Examples of services classes are RestInventoryServices | ||
80 | /// and RestSkeleton. | ||
81 | /// </summary> | ||
82 | |||
83 | static RestHandler() | ||
84 | { | ||
85 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); | ||
86 | |||
87 | foreach (Module m in mods) | ||
88 | { | ||
89 | Type[] types = m.GetTypes(); | ||
90 | foreach (Type t in types) | ||
91 | { | ||
92 | try | ||
93 | { | ||
94 | if (t.GetInterface("IRest") != null) | ||
95 | { | ||
96 | classes.Add(t); | ||
97 | } | ||
98 | } | ||
99 | catch (Exception) | ||
100 | { | ||
101 | Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t); | ||
102 | Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | #endregion local static state | ||
109 | |||
110 | #region local instance state | ||
111 | |||
112 | /// <summary> | ||
113 | /// This routine loads all of the handlers discovered during | ||
114 | /// instance initialization. | ||
115 | /// A table of all loaded and successfully constructed handlers | ||
116 | /// is built, and this table is then used by the constructor to | ||
117 | /// initialize each of the handlers in turn. | ||
118 | /// NOTE: The loading process does not automatically imply that | ||
119 | /// the handler has registered any kind of an interface, that | ||
120 | /// may be (optionally) done by the handler either during | ||
121 | /// construction, or during initialization. | ||
122 | /// | ||
123 | /// I was not able to make this code work within a constructor | ||
124 | /// so it is isolated within this method. | ||
125 | /// </summary> | ||
126 | |||
127 | private void LoadHandlers() | ||
128 | { | ||
129 | lock (handlers) | ||
130 | { | ||
131 | if (!handlersLoaded) | ||
132 | { | ||
133 | ConstructorInfo ci; | ||
134 | Object ht; | ||
135 | |||
136 | foreach (Type t in classes) | ||
137 | { | ||
138 | try | ||
139 | { | ||
140 | ci = t.GetConstructor(parms); | ||
141 | ht = ci.Invoke(args); | ||
142 | handlers.Add((IRest)ht); | ||
143 | } | ||
144 | catch (Exception e) | ||
145 | { | ||
146 | Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message); | ||
147 | } | ||
148 | } | ||
149 | handlersLoaded = true; | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
154 | #endregion local instance state | ||
155 | |||
156 | #region overriding properties | ||
157 | |||
158 | // These properties override definitions | ||
159 | // in the base class. | ||
160 | |||
161 | // Name is used to differentiate the message header. | ||
162 | |||
163 | public override string Name | ||
164 | { | ||
165 | get { return "HANDLER"; } | ||
166 | } | ||
167 | |||
168 | // Used to partition the .ini configuration space. | ||
169 | |||
170 | public override string ConfigName | ||
171 | { | ||
172 | get { return "RestHandler"; } | ||
173 | } | ||
174 | |||
175 | // We have to rename these because we want | ||
176 | // to be able to share the values with other | ||
177 | // classes in our assembly and the base | ||
178 | // names are protected. | ||
179 | |||
180 | public string MsgId | ||
181 | { | ||
182 | get { return base.MsgID; } | ||
183 | } | ||
184 | |||
185 | public string RequestId | ||
186 | { | ||
187 | get { return base.RequestID; } | ||
188 | } | ||
189 | |||
190 | #endregion overriding properties | ||
191 | |||
192 | #region overriding methods | ||
193 | |||
194 | /// <summary> | ||
195 | /// This method is called by OpenSimMain immediately after loading the | ||
196 | /// plugin and after basic server setup, but before running any server commands. | ||
197 | /// </summary> | ||
198 | /// <remarks> | ||
199 | /// Note that entries MUST be added to the active configuration files before | ||
200 | /// the plugin can be enabled. | ||
201 | /// </remarks> | ||
202 | |||
203 | public override void Initialise(OpenSimBase openSim) | ||
204 | { | ||
205 | try | ||
206 | { | ||
207 | // This plugin will only be enabled if the broader | ||
208 | // REST plugin mechanism is enabled. | ||
209 | |||
210 | //Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId); | ||
211 | |||
212 | base.Initialise(openSim); | ||
213 | |||
214 | // IsEnabled is implemented by the base class and | ||
215 | // reflects an overall RestPlugin status | ||
216 | |||
217 | if (!IsEnabled) | ||
218 | { | ||
219 | //Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); | ||
220 | return; | ||
221 | } | ||
222 | |||
223 | Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name); | ||
224 | Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName); | ||
225 | |||
226 | // These are stored in static variables to make | ||
227 | // them easy to reach from anywhere in the assembly. | ||
228 | |||
229 | Rest.main = openSim; | ||
230 | if (Rest.main == null) | ||
231 | throw new Exception("OpenSim base pointer is null"); | ||
232 | |||
233 | Rest.Plugin = this; | ||
234 | Rest.Config = Config; | ||
235 | Rest.Prefix = Prefix; | ||
236 | Rest.GodKey = GodKey; | ||
237 | Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate); | ||
238 | Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme); | ||
239 | Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure); | ||
240 | Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape); | ||
241 | Rest.Realm = Rest.Config.GetString("realm", Rest.Realm); | ||
242 | Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset); | ||
243 | Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill); | ||
244 | Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize); | ||
245 | Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled); | ||
246 | |||
247 | // Note: Odd spacing is required in the following strings | ||
248 | |||
249 | Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, | ||
250 | (Rest.Authenticate ? "" : "not ")); | ||
251 | |||
252 | Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId, | ||
253 | (Rest.Secure ? "" : "not ")); | ||
254 | |||
255 | Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId, | ||
256 | (Rest.ExtendedEscape ? "" : "not ")); | ||
257 | |||
258 | Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, | ||
259 | (Rest.DumpAsset ? "" : "not ")); | ||
260 | |||
261 | // The supplied prefix MUST be absolute | ||
262 | |||
263 | if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator) | ||
264 | { | ||
265 | Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix); | ||
266 | Rest.Log.InfoFormat("{0} Prefix changed to </{1}>", MsgId, Rest.Prefix); | ||
267 | Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix); | ||
268 | } | ||
269 | |||
270 | // If data dumping is requested, report on the chosen line | ||
271 | // length. | ||
272 | |||
273 | if (Rest.DumpAsset) | ||
274 | { | ||
275 | Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize); | ||
276 | } | ||
277 | |||
278 | // Load all of the handlers present in the | ||
279 | // assembly | ||
280 | |||
281 | // In principle, as we're an application plug-in, | ||
282 | // most of what needs to be done could be done using | ||
283 | // static resources, however the Open Sim plug-in | ||
284 | // model makes this an instance, so that's what we | ||
285 | // need to be. | ||
286 | // There is only one Communications manager per | ||
287 | // server, and by inference, only one each of the | ||
288 | // user, asset, and inventory servers. So we can cache | ||
289 | // those using a static initializer. | ||
290 | // We move all of this processing off to another | ||
291 | // services class to minimize overlap between function | ||
292 | // and infrastructure. | ||
293 | |||
294 | LoadHandlers(); | ||
295 | |||
296 | // The intention of a post construction initializer | ||
297 | // is to allow for setup that is dependent upon other | ||
298 | // activities outside of the agency. | ||
299 | |||
300 | foreach (IRest handler in handlers) | ||
301 | { | ||
302 | try | ||
303 | { | ||
304 | handler.Initialize(); | ||
305 | } | ||
306 | catch (Exception e) | ||
307 | { | ||
308 | Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message); | ||
309 | } | ||
310 | } | ||
311 | |||
312 | // Now that everything is setup we can proceed to | ||
313 | // add THIS agent to the HTTP server's handler list | ||
314 | |||
315 | // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will | ||
316 | // have to be handled through the AddHttpHandler interface. | ||
317 | // if (!AddAgentHandler(Rest.Name,this)) | ||
318 | // { | ||
319 | // Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId); | ||
320 | // foreach (IRest handler in handlers) | ||
321 | // { | ||
322 | // handler.Close(); | ||
323 | // } | ||
324 | // } | ||
325 | |||
326 | } | ||
327 | catch (Exception e) | ||
328 | { | ||
329 | Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | /// <summary> | ||
334 | /// In the interests of efficiency, and because we cannot determine whether | ||
335 | /// or not this instance will actually be harvested, we clobber the only | ||
336 | /// anchoring reference to the working state for this plug-in. What the | ||
337 | /// call to close does is irrelevant to this class beyond knowing that it | ||
338 | /// can nullify the reference when it returns. | ||
339 | /// To make sure everything is copacetic we make sure the primary interface | ||
340 | /// is disabled by deleting the handler from the HTTP server tables. | ||
341 | /// </summary> | ||
342 | |||
343 | public override void Close() | ||
344 | { | ||
345 | Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); | ||
346 | |||
347 | // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will | ||
348 | // have to be handled through the AddHttpHandler interface. | ||
349 | // try | ||
350 | // { | ||
351 | // RemoveAgentHandler(Rest.Name, this); | ||
352 | // } | ||
353 | // catch (KeyNotFoundException){} | ||
354 | |||
355 | foreach (IRest handler in handlers) | ||
356 | { | ||
357 | handler.Close(); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | #endregion overriding methods | ||
362 | |||
363 | #region interface methods | ||
364 | |||
365 | /// <summary> | ||
366 | /// This method is called by the HTTP server to match an incoming | ||
367 | /// request. It scans all of the strings registered by the | ||
368 | /// underlying handlers and looks for the best match. It returns | ||
369 | /// true if a match is found. | ||
370 | /// The matching process could be made arbitrarily complex. | ||
371 | /// Note: The match is case-insensitive. | ||
372 | /// </summary> | ||
373 | |||
374 | public bool Match(OSHttpRequest request, OSHttpResponse response) | ||
375 | { | ||
376 | |||
377 | string path = request.RawUrl.ToLower(); | ||
378 | |||
379 | // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); | ||
380 | |||
381 | try | ||
382 | { | ||
383 | foreach (string key in pathHandlers.Keys) | ||
384 | { | ||
385 | // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key); | ||
386 | |||
387 | // Note that Match will not necessarily find the handler that will | ||
388 | // actually be used - it does no test for the "closest" fit. It | ||
389 | // simply reflects that at least one possible handler exists. | ||
390 | |||
391 | if (path.StartsWith(key)) | ||
392 | { | ||
393 | // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); | ||
394 | |||
395 | // This apparently odd evaluation is needed to prevent a match | ||
396 | // on anything other than a URI token boundary. Otherwise we | ||
397 | // may match on URL's that were not intended for this handler. | ||
398 | |||
399 | return (path.Length == key.Length || | ||
400 | path.Substring(key.Length, 1) == Rest.UrlPathSeparator); | ||
401 | } | ||
402 | } | ||
403 | |||
404 | path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); | ||
405 | |||
406 | foreach (string key in streamHandlers.Keys) | ||
407 | { | ||
408 | // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); | ||
409 | |||
410 | // Note that Match will not necessarily find the handler that will | ||
411 | // actually be used - it does no test for the "closest" fit. It | ||
412 | // simply reflects that at least one possible handler exists. | ||
413 | |||
414 | if (path.StartsWith(key)) | ||
415 | { | ||
416 | // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); | ||
417 | |||
418 | // This apparently odd evaluation is needed to prevent a match | ||
419 | // on anything other than a URI token boundary. Otherwise we | ||
420 | // may match on URL's that were not intended for this handler. | ||
421 | |||
422 | return (path.Length == key.Length || | ||
423 | path.Substring(key.Length, 1) == Rest.UrlPathSeparator); | ||
424 | } | ||
425 | } | ||
426 | } | ||
427 | catch (Exception e) | ||
428 | { | ||
429 | Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message); | ||
430 | } | ||
431 | |||
432 | return false; | ||
433 | } | ||
434 | |||
435 | /// <summary> | ||
436 | /// This is called by the HTTP server once the handler has indicated | ||
437 | /// that it is able to handle the request. | ||
438 | /// Preconditions: | ||
439 | /// [1] request != null and is a valid request object | ||
440 | /// [2] response != null and is a valid response object | ||
441 | /// Behavior is undefined if preconditions are not satisfied. | ||
442 | /// </summary> | ||
443 | |||
444 | public bool Handle(OSHttpRequest request, OSHttpResponse response) | ||
445 | { | ||
446 | bool handled; | ||
447 | base.MsgID = base.RequestID; | ||
448 | |||
449 | // Debug only | ||
450 | |||
451 | if (Rest.DEBUG) | ||
452 | { | ||
453 | Rest.Log.DebugFormat("{0} ENTRY", MsgId); | ||
454 | Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent); | ||
455 | Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod); | ||
456 | |||
457 | for (int i = 0; i < request.Headers.Count; i++) | ||
458 | { | ||
459 | Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", | ||
460 | MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); | ||
461 | } | ||
462 | Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); | ||
463 | } | ||
464 | |||
465 | // If a path handler worked we're done, otherwise try any | ||
466 | // available stream handlers too. | ||
467 | |||
468 | try | ||
469 | { | ||
470 | handled = (FindPathHandler(request, response) || | ||
471 | FindStreamHandler(request, response)); | ||
472 | } | ||
473 | catch (Exception e) | ||
474 | { | ||
475 | // A raw exception indicates that something we weren't expecting has | ||
476 | // happened. This should always reflect a shortcoming in the plugin, | ||
477 | // or a failure to satisfy the preconditions. It should not reflect | ||
478 | // an error in the request itself. Under such circumstances the state | ||
479 | // of the request cannot be determined and we are obliged to mark it | ||
480 | // as 'handled'. | ||
481 | |||
482 | Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message); | ||
483 | handled = true; | ||
484 | } | ||
485 | |||
486 | Rest.Log.DebugFormat("{0} EXIT", MsgId); | ||
487 | |||
488 | return handled; | ||
489 | } | ||
490 | |||
491 | #endregion interface methods | ||
492 | |||
493 | /// <summary> | ||
494 | /// If there is a stream handler registered that can handle the | ||
495 | /// request, then fine. If the request is not matched, do | ||
496 | /// nothing. | ||
497 | /// Note: The selection is case-insensitive | ||
498 | /// </summary> | ||
499 | |||
500 | private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response) | ||
501 | { | ||
502 | RequestData rdata = new RequestData(request, response, String.Empty); | ||
503 | |||
504 | string bestMatch = String.Empty; | ||
505 | string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower(); | ||
506 | |||
507 | Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); | ||
508 | |||
509 | if (!IsEnabled) | ||
510 | { | ||
511 | return false; | ||
512 | } | ||
513 | |||
514 | foreach (string pattern in streamHandlers.Keys) | ||
515 | { | ||
516 | if (path.StartsWith(pattern)) | ||
517 | { | ||
518 | if (pattern.Length > bestMatch.Length) | ||
519 | { | ||
520 | bestMatch = pattern; | ||
521 | } | ||
522 | } | ||
523 | } | ||
524 | |||
525 | // Handle using the best match available | ||
526 | |||
527 | if (bestMatch.Length > 0) | ||
528 | { | ||
529 | Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch); | ||
530 | RestStreamHandler handler = streamHandlers[bestMatch]; | ||
531 | rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response); | ||
532 | rdata.AddHeader(rdata.response.ContentType,handler.ContentType); | ||
533 | rdata.Respond("FindStreamHandler Completion"); | ||
534 | } | ||
535 | |||
536 | return rdata.handled; | ||
537 | } | ||
538 | |||
539 | /// <summary> | ||
540 | /// Add a stream handler for the designated HTTP method and path prefix. | ||
541 | /// If the handler is not enabled, the request is ignored. If the path | ||
542 | /// does not start with the REST prefix, it is added. If method-qualified | ||
543 | /// path has not already been registered, the method is added to the active | ||
544 | /// handler table. | ||
545 | /// </summary> | ||
546 | public void AddStreamHandler(string httpMethod, string path, RestMethod method) | ||
547 | { | ||
548 | if (!IsEnabled) | ||
549 | { | ||
550 | return; | ||
551 | } | ||
552 | |||
553 | if (!path.StartsWith(Rest.Prefix)) | ||
554 | { | ||
555 | path = String.Format("{0}{1}", Rest.Prefix, path); | ||
556 | } | ||
557 | |||
558 | path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path); | ||
559 | |||
560 | // Conditionally add to the list | ||
561 | |||
562 | if (!streamHandlers.ContainsKey(path)) | ||
563 | { | ||
564 | streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); | ||
565 | Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path); | ||
566 | } | ||
567 | else | ||
568 | { | ||
569 | Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); | ||
570 | } | ||
571 | } | ||
572 | |||
573 | /// <summary> | ||
574 | /// Given the supplied request/response, if the handler is enabled, the inbound | ||
575 | /// information is used to match an entry in the active path handler tables, using | ||
576 | /// the method-qualified path information. If a match is found, then the handler is | ||
577 | /// invoked. The result is the boolean result of the handler, or false if no | ||
578 | /// handler was located. The boolean indicates whether or not the request has been | ||
579 | /// handled, not whether or not the request was successful - that information is in | ||
580 | /// the response. | ||
581 | /// Note: The selection process is case-insensitive | ||
582 | /// </summary> | ||
583 | |||
584 | internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) | ||
585 | { | ||
586 | RequestData rdata = null; | ||
587 | string bestMatch = null; | ||
588 | |||
589 | if (!IsEnabled) | ||
590 | { | ||
591 | return false; | ||
592 | } | ||
593 | |||
594 | // Conditionally add to the list | ||
595 | |||
596 | Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl); | ||
597 | |||
598 | foreach (string pattern in pathHandlers.Keys) | ||
599 | { | ||
600 | if (request.RawUrl.ToLower().StartsWith(pattern)) | ||
601 | { | ||
602 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) | ||
603 | { | ||
604 | bestMatch = pattern; | ||
605 | } | ||
606 | } | ||
607 | } | ||
608 | |||
609 | if (!String.IsNullOrEmpty(bestMatch)) | ||
610 | { | ||
611 | rdata = pathAllocators[bestMatch](request, response, bestMatch); | ||
612 | |||
613 | Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); | ||
614 | |||
615 | try | ||
616 | { | ||
617 | pathHandlers[bestMatch](rdata); | ||
618 | } | ||
619 | |||
620 | // A plugin generated error indicates a request-related error | ||
621 | // that has been handled by the plugin. | ||
622 | |||
623 | catch (RestException r) | ||
624 | { | ||
625 | Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); | ||
626 | } | ||
627 | } | ||
628 | |||
629 | return (rdata == null) ? false : rdata.handled; | ||
630 | } | ||
631 | |||
632 | /// <summary> | ||
633 | /// A method handler and a request allocator are stored using the designated | ||
634 | /// path as a key. If an entry already exists, it is replaced by the new one. | ||
635 | /// </summary> | ||
636 | |||
637 | public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) | ||
638 | { | ||
639 | if (!IsEnabled) | ||
640 | { | ||
641 | return; | ||
642 | } | ||
643 | |||
644 | if (pathHandlers.ContainsKey(path)) | ||
645 | { | ||
646 | Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path); | ||
647 | pathHandlers.Remove(path); | ||
648 | } | ||
649 | |||
650 | if (pathAllocators.ContainsKey(path)) | ||
651 | { | ||
652 | Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path); | ||
653 | pathAllocators.Remove(path); | ||
654 | } | ||
655 | |||
656 | Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path); | ||
657 | |||
658 | pathHandlers.Add(path, mh); | ||
659 | pathAllocators.Add(path, ra); | ||
660 | } | ||
661 | } | ||
662 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs deleted file mode 100644 index 536f167..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ /dev/null | |||
@@ -1,2343 +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.Drawing; | ||
31 | using System.Globalization; | ||
32 | using System.IO; | ||
33 | using System.Threading; | ||
34 | using System.Timers; | ||
35 | using System.Xml; | ||
36 | using OpenMetaverse; | ||
37 | using OpenMetaverse.Imaging; | ||
38 | using OpenSim.Framework; | ||
39 | |||
40 | using OpenSim.Framework.Servers; | ||
41 | using OpenSim.Framework.Servers.HttpServer; | ||
42 | using Timer=System.Timers.Timer; | ||
43 | |||
44 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
45 | { | ||
46 | public class RestInventoryServices : IRest | ||
47 | { | ||
48 | // private static readonly int PARM_USERID = 0; | ||
49 | // private static readonly int PARM_PATH = 1; | ||
50 | |||
51 | // private bool enabled = false; | ||
52 | private string qPrefix = "inventory"; | ||
53 | |||
54 | // private static readonly string PRIVATE_ROOT_NAME = "My Inventory"; | ||
55 | |||
56 | /// <summary> | ||
57 | /// The constructor makes sure that the service prefix is absolute | ||
58 | /// and the registers the service handler and the allocator. | ||
59 | /// </summary> | ||
60 | |||
61 | public RestInventoryServices() | ||
62 | { | ||
63 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); | ||
64 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
65 | |||
66 | // If a relative path was specified for the handler's domain, | ||
67 | // add the standard prefix to make it absolute, e.g. /admin | ||
68 | |||
69 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
70 | { | ||
71 | Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); | ||
72 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
73 | Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); | ||
74 | } | ||
75 | |||
76 | // Register interface using the absolute URI. | ||
77 | |||
78 | Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); | ||
79 | |||
80 | // Activate if everything went OK | ||
81 | |||
82 | // enabled = true; | ||
83 | |||
84 | Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); | ||
85 | } | ||
86 | |||
87 | /// <summary> | ||
88 | /// Post-construction, pre-enabled initialization opportunity | ||
89 | /// Not currently exploited. | ||
90 | /// </summary> | ||
91 | |||
92 | public void Initialize() | ||
93 | { | ||
94 | } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Called by the plug-in to halt service processing. Local processing is | ||
98 | /// disabled. | ||
99 | /// </summary> | ||
100 | |||
101 | public void Close() | ||
102 | { | ||
103 | // enabled = false; | ||
104 | Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); | ||
105 | } | ||
106 | |||
107 | /// <summary> | ||
108 | /// This property is declared locally because it is used a lot and | ||
109 | /// brevity is nice. | ||
110 | /// </summary> | ||
111 | internal string MsgId | ||
112 | { | ||
113 | get { return Rest.MsgId; } | ||
114 | } | ||
115 | |||
116 | #region Interface | ||
117 | |||
118 | /// <summary> | ||
119 | /// The plugin (RestHandler) calls this method to allocate the request | ||
120 | /// state carrier for a new request. It is destroyed when the request | ||
121 | /// completes. All request-instance specific state is kept here. This | ||
122 | /// is registered when this service provider is registered. | ||
123 | /// </summary> | ||
124 | /// <param name=request>Inbound HTTP request information</param> | ||
125 | /// <param name=response>Outbound HTTP request information</param> | ||
126 | /// <param name=qPrefix>REST service domain prefix</param> | ||
127 | /// <returns>A RequestData instance suitable for this service</returns> | ||
128 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
129 | { | ||
130 | return (RequestData) new InventoryRequestData(request, response, prefix); | ||
131 | } | ||
132 | |||
133 | /// <summary> | ||
134 | /// This method is registered with the handler when this service provider | ||
135 | /// is initialized. It is called whenever the plug-in identifies this service | ||
136 | /// provider as the best match for a given request. | ||
137 | /// It handles all aspects of inventory REST processing, i.e. /admin/inventory | ||
138 | /// </summary> | ||
139 | /// <param name=hdata>A consolidated HTTP request work area</param> | ||
140 | private void DoInventory(RequestData hdata) | ||
141 | { | ||
142 | // InventoryRequestData rdata = (InventoryRequestData) hdata; | ||
143 | |||
144 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); | ||
145 | |||
146 | // !!! REFACTORING PROBLEM | ||
147 | |||
148 | //// If we're disabled, do nothing. | ||
149 | |||
150 | //if (!enabled) | ||
151 | //{ | ||
152 | // return; | ||
153 | //} | ||
154 | |||
155 | //// Now that we know this is a serious attempt to | ||
156 | //// access inventory data, we should find out who | ||
157 | //// is asking, and make sure they are authorized | ||
158 | //// to do so. We need to validate the caller's | ||
159 | //// identity before revealing anything about the | ||
160 | //// status quo. Authenticate throws an exception | ||
161 | //// via Fail if no identity information is present. | ||
162 | //// | ||
163 | //// With the present HTTP server we can't use the | ||
164 | //// builtin authentication mechanisms because they | ||
165 | //// would be enforced for all in-bound requests. | ||
166 | //// Instead we look at the headers ourselves and | ||
167 | //// handle authentication directly. | ||
168 | |||
169 | //try | ||
170 | //{ | ||
171 | // if (!rdata.IsAuthenticated) | ||
172 | // { | ||
173 | // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); | ||
174 | // } | ||
175 | //} | ||
176 | //catch (RestException e) | ||
177 | //{ | ||
178 | // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
179 | // { | ||
180 | // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
181 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
182 | // } | ||
183 | // else | ||
184 | // { | ||
185 | // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
186 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
187 | // } | ||
188 | // throw (e); | ||
189 | //} | ||
190 | |||
191 | //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | ||
192 | |||
193 | //// We can only get here if we are authorized | ||
194 | //// | ||
195 | //// The requestor may have specified an UUID or | ||
196 | //// a conjoined FirstName LastName string. We'll | ||
197 | //// try both. If we fail with the first, UUID, | ||
198 | //// attempt, we try the other. As an example, the | ||
199 | //// URI for a valid inventory request might be: | ||
200 | //// | ||
201 | //// http://<host>:<port>/admin/inventory/Arthur Dent | ||
202 | //// | ||
203 | //// Indicating that this is an inventory request for | ||
204 | //// an avatar named Arthur Dent. This is ALL that is | ||
205 | //// required to designate a GET for an entire | ||
206 | //// inventory. | ||
207 | //// | ||
208 | |||
209 | |||
210 | //// Do we have at least a user agent name? | ||
211 | |||
212 | //if (rdata.Parameters.Length < 1) | ||
213 | //{ | ||
214 | // Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); | ||
215 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); | ||
216 | //} | ||
217 | |||
218 | //// The first parameter MUST be the agent identification, either an UUID | ||
219 | //// or a space-separated First-name Last-Name specification. We check for | ||
220 | //// an UUID first, if anyone names their character using a valid UUID | ||
221 | //// that identifies another existing avatar will cause this a problem... | ||
222 | |||
223 | //try | ||
224 | //{ | ||
225 | // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); | ||
226 | // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); | ||
227 | // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); | ||
228 | //} | ||
229 | //catch | ||
230 | //{ | ||
231 | // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); | ||
232 | // if (names.Length == 2) | ||
233 | // { | ||
234 | // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); | ||
235 | // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); | ||
236 | // } | ||
237 | // else | ||
238 | // { | ||
239 | // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | ||
240 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); | ||
241 | // } | ||
242 | //} | ||
243 | |||
244 | //// If the user profile is null then either the server is broken, or the | ||
245 | //// user is not known. We always assume the latter case. | ||
246 | |||
247 | //if (rdata.userProfile != null) | ||
248 | //{ | ||
249 | // Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", | ||
250 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
251 | //} | ||
252 | //else | ||
253 | //{ | ||
254 | // Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path); | ||
255 | // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); | ||
256 | //} | ||
257 | |||
258 | //// If we get to here, then we have effectively validated the user's | ||
259 | //// identity. Now we need to get the inventory. If the server does not | ||
260 | //// have the inventory, we reject the request with an appropriate explanation. | ||
261 | //// | ||
262 | //// Note that inventory retrieval is an asynchronous event, we use the rdata | ||
263 | //// class instance as the basis for our synchronization. | ||
264 | //// | ||
265 | |||
266 | //rdata.uuid = rdata.userProfile.ID; | ||
267 | |||
268 | //if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) | ||
269 | //{ | ||
270 | // rdata.root = Rest.InventoryServices.GetRootFolder(rdata.uuid); | ||
271 | |||
272 | // Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", | ||
273 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
274 | |||
275 | // Rest.InventoryServices.GetUserInventory(rdata.uuid, rdata.GetUserInventory); | ||
276 | |||
277 | // Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", | ||
278 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
279 | |||
280 | // lock (rdata) | ||
281 | // { | ||
282 | // if (!rdata.HaveInventory) | ||
283 | // { | ||
284 | // rdata.startWD(1000); | ||
285 | // rdata.timeout = false; | ||
286 | // Monitor.Wait(rdata); | ||
287 | // } | ||
288 | // } | ||
289 | |||
290 | // if (rdata.timeout) | ||
291 | // { | ||
292 | // Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.", | ||
293 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
294 | // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding"); | ||
295 | // } | ||
296 | |||
297 | // if (rdata.root == null) | ||
298 | // { | ||
299 | // Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}", | ||
300 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
301 | // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed"); | ||
302 | // } | ||
303 | |||
304 | //} | ||
305 | //else | ||
306 | //{ | ||
307 | // Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}", | ||
308 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
309 | // rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user"); | ||
310 | //} | ||
311 | |||
312 | //// If we get here, then we have successfully retrieved the user's information | ||
313 | //// and inventory information is now available locally. | ||
314 | |||
315 | //switch (rdata.method) | ||
316 | //{ | ||
317 | // case Rest.HEAD : // Do the processing, set the status code, suppress entity | ||
318 | // DoGet(rdata); | ||
319 | // rdata.buffer = null; | ||
320 | // break; | ||
321 | |||
322 | // case Rest.GET : // Do the processing, set the status code, return entity | ||
323 | // DoGet(rdata); | ||
324 | // break; | ||
325 | |||
326 | // case Rest.PUT : // Update named element | ||
327 | // DoUpdate(rdata); | ||
328 | // break; | ||
329 | |||
330 | // case Rest.POST : // Add new information to identified context. | ||
331 | // DoExtend(rdata); | ||
332 | // break; | ||
333 | |||
334 | // case Rest.DELETE : // Delete information | ||
335 | // DoDelete(rdata); | ||
336 | // break; | ||
337 | |||
338 | // default : | ||
339 | // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", | ||
340 | // MsgId, rdata.method, rdata.path); | ||
341 | // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | ||
342 | // String.Format("{0} not supported", rdata.method)); | ||
343 | // break; | ||
344 | //} | ||
345 | } | ||
346 | |||
347 | #endregion Interface | ||
348 | |||
349 | #region method-specific processing | ||
350 | |||
351 | /// <summary> | ||
352 | /// This method implements GET processing for inventory. | ||
353 | /// Any remaining parameters are used to locate the | ||
354 | /// corresponding subtree based upon node name. | ||
355 | /// </summary> | ||
356 | /// <param name=rdata>HTTP service request work area</param> | ||
357 | // private void DoGet(InventoryRequestData rdata) | ||
358 | // { | ||
359 | // rdata.initXmlWriter(); | ||
360 | // | ||
361 | // rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); | ||
362 | // | ||
363 | // // If there are additional parameters, then these represent | ||
364 | // // a path relative to the root of the inventory. This path | ||
365 | // // must be traversed before we format the sub-tree thus | ||
366 | // // identified. | ||
367 | // | ||
368 | // traverse(rdata, rdata.root, PARM_PATH); | ||
369 | // | ||
370 | // // Close all open elements | ||
371 | // | ||
372 | // rdata.writer.WriteFullEndElement(); | ||
373 | // | ||
374 | // // Indicate a successful request | ||
375 | // | ||
376 | // rdata.Complete(); | ||
377 | // | ||
378 | // // Send the response to the user. The body will be implicitly | ||
379 | // // constructed from the result of the XML writer. | ||
380 | // | ||
381 | // rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method)); | ||
382 | // } | ||
383 | |||
384 | /// <summary> | ||
385 | /// In the case of the inventory, and probably in general, | ||
386 | /// the distinction between PUT and POST is not always | ||
387 | /// easy to discern. The standard is badly worded in places, | ||
388 | /// and adding a node to a hierarchy can be viewed as | ||
389 | /// an addition, or as a modification to the inventory as | ||
390 | /// a whole. This is exacerbated by an unjustified lack of | ||
391 | /// consistency across different implementations. | ||
392 | /// | ||
393 | /// For OpenSim PUT is an update and POST is an addition. This | ||
394 | /// is the behavior required by the HTTP specification and | ||
395 | /// therefore as required by REST. | ||
396 | /// | ||
397 | /// The best way to explain the distinction is to | ||
398 | /// consider the relationship between the URI and the | ||
399 | /// enclosed entity. For PUT, the URI identifies the | ||
400 | /// actual entity to be modified or replaced, i.e. the | ||
401 | /// enclosed entity. | ||
402 | /// | ||
403 | /// If the operation is POST,then the URI describes the | ||
404 | /// context into which the new entity will be added. | ||
405 | /// | ||
406 | /// As an example, suppose the URI contains: | ||
407 | /// /admin/inventory/Clothing | ||
408 | /// | ||
409 | /// A PUT request will normally result in some modification of | ||
410 | /// the folder or item named "Clothing". Whereas a POST | ||
411 | /// request will normally add some new information into the | ||
412 | /// content identified by Clothing. It follows from this | ||
413 | /// that for POST, the element identified by the URI MUST | ||
414 | /// be a folder. | ||
415 | /// </summary> | ||
416 | |||
417 | /// <summary> | ||
418 | /// POST adds new information to the inventory in the | ||
419 | /// context identified by the URI. | ||
420 | /// </summary> | ||
421 | /// <param name=rdata>HTTP service request work area</param> | ||
422 | // private void DoExtend(InventoryRequestData rdata) | ||
423 | // { | ||
424 | // bool created = false; | ||
425 | // bool modified = false; | ||
426 | // string newnode = String.Empty; | ||
427 | // | ||
428 | // // Resolve the context node specified in the URI. Entity | ||
429 | // // data will be ADDED beneath this node. rdata already contains | ||
430 | // // information about the current content of the user's | ||
431 | // // inventory. | ||
432 | // | ||
433 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); | ||
434 | // | ||
435 | // // Processing depends upon the type of inventory node | ||
436 | // // identified in the URI. This is the CONTEXT for the | ||
437 | // // change. We either got a context or we threw an | ||
438 | // // exception. | ||
439 | // | ||
440 | // // It follows that we can only add information if the URI | ||
441 | // // has identified a folder. So only a type of folder is supported | ||
442 | // // in this case. | ||
443 | // | ||
444 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
445 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
446 | // { | ||
447 | // // Cast the context node appropriately. | ||
448 | // | ||
449 | // InventoryFolderBase context = (InventoryFolderBase) InventoryNode; | ||
450 | // | ||
451 | // Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", | ||
452 | // MsgId, rdata.method, rdata.path); | ||
453 | // | ||
454 | // // Reconstitute the inventory sub-tree from the XML supplied in the entity. | ||
455 | // // The result is a stand-alone inventory subtree, not yet integrated into the | ||
456 | // // existing tree. An inventory collection consists of three components: | ||
457 | // // [1] A (possibly empty) set of folders. | ||
458 | // // [2] A (possibly empty) set of items. | ||
459 | // // [3] A (possibly empty) set of assets. | ||
460 | // // If all of these are empty, then the POST is a harmless no-operation. | ||
461 | // | ||
462 | // XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
463 | // | ||
464 | // // Inlined assets can be included in entity. These must be incorporated into | ||
465 | // // the asset database before we attempt to update the inventory. If anything | ||
466 | // // fails, return a failure to requestor. | ||
467 | // | ||
468 | // if (entity.Assets.Count > 0) | ||
469 | // { | ||
470 | // Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
471 | // MsgId, entity.Assets.Count); | ||
472 | // | ||
473 | // foreach (AssetBase asset in entity.Assets) | ||
474 | // { | ||
475 | // Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | ||
476 | // MsgId, asset.ID, asset.Type, asset.Name); | ||
477 | // Rest.AssetServices.Store(asset); | ||
478 | // | ||
479 | // created = true; | ||
480 | // rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", | ||
481 | // asset.Name, asset.ID)); | ||
482 | // | ||
483 | // if (Rest.DEBUG && Rest.DumpAsset) | ||
484 | // { | ||
485 | // Rest.Dump(asset.Data); | ||
486 | // } | ||
487 | // } | ||
488 | // } | ||
489 | // | ||
490 | // // Modify the context using the collection of folders and items | ||
491 | // // returned in the XmlInventoryCollection. | ||
492 | // | ||
493 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
494 | // { | ||
495 | // InventoryFolderBase found; | ||
496 | // | ||
497 | // // If the parentID is zero, then this folder is going | ||
498 | // // into the root folder identified by the URI. The requestor | ||
499 | // // may have already set the parent ID explicitly, in which | ||
500 | // // case we don't have to do it here. | ||
501 | // | ||
502 | // if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID) | ||
503 | // { | ||
504 | // if (newnode != String.Empty) | ||
505 | // { | ||
506 | // Rest.Log.DebugFormat("{0} Too many resources", MsgId); | ||
507 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed"); | ||
508 | // } | ||
509 | // folder.ParentID = context.ID; | ||
510 | // newnode = folder.Name; | ||
511 | // } | ||
512 | // | ||
513 | // // Search the existing inventory for an existing entry. If | ||
514 | // // we have one, we need to decide if it has really changed. | ||
515 | // // It could just be present as (unnecessary) context, and we | ||
516 | // // don't want to waste time updating the database in that | ||
517 | // // case, OR, it could be being moved from another location | ||
518 | // // in which case an update is most certainly necessary. | ||
519 | // | ||
520 | // found = null; | ||
521 | // | ||
522 | // foreach (InventoryFolderBase xf in rdata.folders) | ||
523 | // { | ||
524 | // // Compare identifying attribute | ||
525 | // if (xf.ID == folder.ID) | ||
526 | // { | ||
527 | // found = xf; | ||
528 | // break; | ||
529 | // } | ||
530 | // } | ||
531 | // | ||
532 | // if (found != null && FolderHasChanged(folder,found)) | ||
533 | // { | ||
534 | // Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); | ||
535 | // Rest.InventoryServices.MoveFolder(folder); | ||
536 | // | ||
537 | // modified = true; | ||
538 | // rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1} <p>", | ||
539 | // folder.Name, folder.ID)); | ||
540 | // } | ||
541 | // else | ||
542 | // { | ||
543 | // Rest.Log.DebugFormat("{0} Adding new folder", MsgId); | ||
544 | // Rest.InventoryServices.AddFolder(folder); | ||
545 | // | ||
546 | // created = true; | ||
547 | // rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1} <p>", | ||
548 | // folder.Name, folder.ID)); | ||
549 | // } | ||
550 | // } | ||
551 | // | ||
552 | // // Now we repeat a similar process for the items included | ||
553 | // // in the entity. | ||
554 | // | ||
555 | // foreach (InventoryItemBase item in entity.Items) | ||
556 | // { | ||
557 | // InventoryItemBase found = null; | ||
558 | // | ||
559 | // // If the parentID is zero, then this is going | ||
560 | // // directly into the root identified by the URI. | ||
561 | // | ||
562 | // if (item.Folder == UUID.Zero) | ||
563 | // { | ||
564 | // item.Folder = context.ID; | ||
565 | // } | ||
566 | // | ||
567 | // // Determine whether this is a new item or a | ||
568 | // // replacement definition. | ||
569 | // | ||
570 | // foreach (InventoryItemBase xi in rdata.items) | ||
571 | // { | ||
572 | // // Compare identifying attribute | ||
573 | // if (xi.ID == item.ID) | ||
574 | // { | ||
575 | // found = xi; | ||
576 | // break; | ||
577 | // } | ||
578 | // } | ||
579 | // | ||
580 | // if (found != null && ItemHasChanged(item, found)) | ||
581 | // { | ||
582 | // Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", | ||
583 | // MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
584 | // Rest.InventoryServices.UpdateItem(item); | ||
585 | // modified = true; | ||
586 | // rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1} <p>", item.Name, item.ID)); | ||
587 | // } | ||
588 | // else | ||
589 | // { | ||
590 | // Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", | ||
591 | // MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
592 | // Rest.InventoryServices.AddItem(item); | ||
593 | // created = true; | ||
594 | // rdata.appendStatus(String.Format("<p> Created item {0}, UUID {1} <p>", item.Name, item.ID)); | ||
595 | // } | ||
596 | // } | ||
597 | // | ||
598 | // if (created) | ||
599 | // { | ||
600 | // // Must include a location header with a URI that identifies the new resource. | ||
601 | // rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}", | ||
602 | // rdata.hostname, rdata.port,rdata.path,newnode)); | ||
603 | // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
604 | // } | ||
605 | // else | ||
606 | // { | ||
607 | // if (modified) | ||
608 | // { | ||
609 | // rdata.Complete(Rest.HttpStatusCodeOK); | ||
610 | // } | ||
611 | // else | ||
612 | // { | ||
613 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
614 | // } | ||
615 | // } | ||
616 | // | ||
617 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
618 | // } | ||
619 | // else | ||
620 | // { | ||
621 | // Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", | ||
622 | // MsgId, rdata.method, rdata.path, InventoryNode.GetType()); | ||
623 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context"); | ||
624 | // } | ||
625 | // } | ||
626 | |||
627 | /// <summary> | ||
628 | /// PUT updates the URI-identified element in the inventory. This | ||
629 | /// is actually far more flexible than it might at first sound. For | ||
630 | /// PUT the URI serves two purposes: | ||
631 | /// [1] It identifies the user whose inventory is to be | ||
632 | /// processed. | ||
633 | /// [2] It optionally specifies a subtree of the inventory | ||
634 | /// that is to be used to resolve any relative subtree | ||
635 | /// specifications in the entity. If nothing is specified | ||
636 | /// then the whole of the private inventory is implied. | ||
637 | /// Please note that the subtree specified by the URI is only relevant | ||
638 | /// to an entity containing a URI relative specification, i.e. one or | ||
639 | /// more elements do not specify parent folder information. These | ||
640 | /// elements will be implicitly referenced within the context identified | ||
641 | /// by the URI. | ||
642 | /// If an element in the entity specifies an explicit parent folder, then | ||
643 | /// that parent is effective, regardless of any value specified in the | ||
644 | /// URI. If the parent does not exist, then the element, and any dependent | ||
645 | /// elements, are ignored. This case is actually detected and handled | ||
646 | /// during the reconstitution process. | ||
647 | /// </summary> | ||
648 | /// <param name=rdata>HTTP service request work area</param> | ||
649 | // private void DoUpdate(InventoryRequestData rdata) | ||
650 | // { | ||
651 | // int count = 0; | ||
652 | // bool created = false; | ||
653 | // bool modified = false; | ||
654 | // | ||
655 | // // Resolve the inventory node that is to be modified. | ||
656 | // // rdata already contains information about the current | ||
657 | // // content of the user's inventory. | ||
658 | // | ||
659 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); | ||
660 | // | ||
661 | // // As long as we have a node, then we have something | ||
662 | // // meaningful to do, unlike POST. So we reconstitute the | ||
663 | // // subtree before doing anything else. Note that we | ||
664 | // // etiher got a valid node or we threw an exception. | ||
665 | // | ||
666 | // XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
667 | // | ||
668 | // // Incorporate any inlined assets first. Any failures | ||
669 | // // will terminate the request. | ||
670 | // | ||
671 | // if (entity.Assets.Count > 0) | ||
672 | // { | ||
673 | // Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
674 | // MsgId, entity.Assets.Count); | ||
675 | // | ||
676 | // foreach (AssetBase asset in entity.Assets) | ||
677 | // { | ||
678 | // Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | ||
679 | // MsgId, asset.ID, asset.Type, asset.Name); | ||
680 | // | ||
681 | // // The asset was validated during the collection process | ||
682 | // | ||
683 | // Rest.AssetServices.Store(asset); | ||
684 | // | ||
685 | // created = true; | ||
686 | // rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.ID)); | ||
687 | // | ||
688 | // if (Rest.DEBUG && Rest.DumpAsset) | ||
689 | // { | ||
690 | // Rest.Dump(asset.Data); | ||
691 | // } | ||
692 | // } | ||
693 | // } | ||
694 | // | ||
695 | // // The URI specifies either a folder or an item to be updated. | ||
696 | // // | ||
697 | // // The root node in the entity will replace the node identified | ||
698 | // // by the URI. This means the parent will remain the same, but | ||
699 | // // any or all attributes associated with the named element | ||
700 | // // will change. | ||
701 | // // | ||
702 | // // If the inventory collection contains an element with a zero | ||
703 | // // parent ID, then this is taken to be the replacement for the | ||
704 | // // named node. The collection MAY also specify an explicit | ||
705 | // // parent ID, in this case it MAY identify the same parent as | ||
706 | // // the current node, or it MAY specify a different parent, | ||
707 | // // indicating that the folder is being moved in addition to any | ||
708 | // // other modifications being made. | ||
709 | // | ||
710 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
711 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
712 | // { | ||
713 | // bool rfound = false; | ||
714 | // InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; | ||
715 | // InventoryFolderBase xml = null; | ||
716 | // | ||
717 | // // If the entity to be replaced resolved to be the root | ||
718 | // // directory itself (My Inventory), then make sure that | ||
719 | // // the supplied data include as appropriately typed and | ||
720 | // // named folder. Note that we can;t rule out the possibility | ||
721 | // // of a sub-directory being called "My Inventory", so that | ||
722 | // // is anticipated. | ||
723 | // | ||
724 | // if (uri == rdata.root) | ||
725 | // { | ||
726 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
727 | // { | ||
728 | // if ((rfound = (folder.Name == PRIVATE_ROOT_NAME))) | ||
729 | // { | ||
730 | // if ((rfound = (folder.ParentID == UUID.Zero))) | ||
731 | // break; | ||
732 | // } | ||
733 | // } | ||
734 | // | ||
735 | // if (!rfound) | ||
736 | // { | ||
737 | // Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory", | ||
738 | // MsgId, rdata.method, rdata.path); | ||
739 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure"); | ||
740 | // } | ||
741 | // } | ||
742 | // | ||
743 | // // Scan the set of folders in the entity collection for an | ||
744 | // // entry that matches the context folder. It is assumed that | ||
745 | // // the only reliable indicator of this is a zero UUID (using | ||
746 | // // implicit context), or the parent's UUID matches that of the | ||
747 | // // URI designated node (explicit context). We don't allow | ||
748 | // // ambiguity in this case because this is POST and we are | ||
749 | // // supposed to be modifying a specific node. | ||
750 | // // We assign any element IDs required as an economy; we don't | ||
751 | // // want to iterate over the fodler set again if it can be | ||
752 | // // helped. | ||
753 | // | ||
754 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
755 | // { | ||
756 | // if (folder.ParentID == uri.ParentID || | ||
757 | // folder.ParentID == UUID.Zero) | ||
758 | // { | ||
759 | // folder.ParentID = uri.ParentID; | ||
760 | // xml = folder; | ||
761 | // count++; | ||
762 | // } | ||
763 | // } | ||
764 | // | ||
765 | // // More than one entry is ambiguous. Other folders should be | ||
766 | // // added using the POST verb. | ||
767 | // | ||
768 | // if (count > 1) | ||
769 | // { | ||
770 | // Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", | ||
771 | // MsgId, rdata.method, rdata.path); | ||
772 | // rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous"); | ||
773 | // } | ||
774 | // | ||
775 | // // Exactly one entry means we ARE replacing the node | ||
776 | // // identified by the URI. So we delete the old folder | ||
777 | // // by moving it to the trash and then purging it. | ||
778 | // // We then add all of the folders and items we | ||
779 | // // included in the entity. The subtree has been | ||
780 | // // modified. | ||
781 | // | ||
782 | // if (count == 1) | ||
783 | // { | ||
784 | // InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
785 | // | ||
786 | // // All went well, so we generate a UUID is one is | ||
787 | // // needed. | ||
788 | // | ||
789 | // if (xml.ID == UUID.Zero) | ||
790 | // { | ||
791 | // xml.ID = UUID.Random(); | ||
792 | // } | ||
793 | // | ||
794 | // uri.ParentID = TrashCan.ID; | ||
795 | // Rest.InventoryServices.MoveFolder(uri); | ||
796 | // Rest.InventoryServices.PurgeFolder(TrashCan); | ||
797 | // modified = true; | ||
798 | // } | ||
799 | // | ||
800 | // // Now, regardelss of what they represent, we | ||
801 | // // integrate all of the elements in the entity. | ||
802 | // | ||
803 | // foreach (InventoryFolderBase f in entity.Folders) | ||
804 | // { | ||
805 | // rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1} <p>", f.Name, f.ID)); | ||
806 | // Rest.InventoryServices.MoveFolder(f); | ||
807 | // } | ||
808 | // | ||
809 | // foreach (InventoryItemBase it in entity.Items) | ||
810 | // { | ||
811 | // rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", it.Name, it.ID)); | ||
812 | // Rest.InventoryServices.AddItem(it); | ||
813 | // } | ||
814 | // } | ||
815 | // | ||
816 | // /// <summary> | ||
817 | // /// URI specifies an item to be updated | ||
818 | // /// </summary> | ||
819 | // /// <remarks> | ||
820 | // /// The entity must contain a single item node to be | ||
821 | // /// updated. ID and Folder ID must be correct. | ||
822 | // /// </remarks> | ||
823 | // | ||
824 | // else | ||
825 | // { | ||
826 | // InventoryItemBase uri = (InventoryItemBase) InventoryNode; | ||
827 | // InventoryItemBase xml = null; | ||
828 | // | ||
829 | // if (entity.Folders.Count != 0) | ||
830 | // { | ||
831 | // Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", | ||
832 | // MsgId, rdata.method, rdata.path); | ||
833 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed"); | ||
834 | // } | ||
835 | // | ||
836 | // if (entity.Items.Count > 1) | ||
837 | // { | ||
838 | // Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", | ||
839 | // MsgId, rdata.method, rdata.path); | ||
840 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items"); | ||
841 | // } | ||
842 | // | ||
843 | // xml = entity.Items[0]; | ||
844 | // | ||
845 | // if (xml.ID == UUID.Zero) | ||
846 | // { | ||
847 | // xml.ID = UUID.Random(); | ||
848 | // } | ||
849 | // | ||
850 | // // If the folder reference has changed, then this item is | ||
851 | // // being moved. Otherwise we'll just delete the old, and | ||
852 | // // add in the new. | ||
853 | // | ||
854 | // // Delete the old item | ||
855 | // | ||
856 | // List<UUID> uuids = new List<UUID>(); | ||
857 | // uuids.Add(uri.ID); | ||
858 | // Rest.InventoryServices.DeleteItems(uri.Owner, uuids); | ||
859 | // | ||
860 | // // Add the new item to the inventory | ||
861 | // | ||
862 | // Rest.InventoryServices.AddItem(xml); | ||
863 | // | ||
864 | // rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", xml.Name, xml.ID)); | ||
865 | // } | ||
866 | // | ||
867 | // if (created) | ||
868 | // { | ||
869 | // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
870 | // } | ||
871 | // else | ||
872 | // { | ||
873 | // if (modified) | ||
874 | // { | ||
875 | // rdata.Complete(Rest.HttpStatusCodeOK); | ||
876 | // } | ||
877 | // else | ||
878 | // { | ||
879 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
880 | // } | ||
881 | // } | ||
882 | // | ||
883 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
884 | // } | ||
885 | |||
886 | /// <summary> | ||
887 | /// Arguably the most damaging REST interface. It deletes the inventory | ||
888 | /// item or folder identified by the URI. | ||
889 | /// | ||
890 | /// We only process if the URI identified node appears to exist | ||
891 | /// We do not test for success because we either get a context, | ||
892 | /// or an exception is thrown. | ||
893 | /// | ||
894 | /// Folders are deleted by moving them to another folder and then | ||
895 | /// purging that folder. We'll do that by creating a temporary | ||
896 | /// sub-folder in the TrashCan and purging that folder's | ||
897 | /// contents. If we can't can it, we don't delete it... | ||
898 | /// So, if no trashcan is available, the request does nothing. | ||
899 | /// Items are summarily deleted. | ||
900 | /// | ||
901 | /// In the interests of safety, a delete request should normally | ||
902 | /// be performed using UUID, as a name might identify several | ||
903 | /// elements. | ||
904 | /// </summary> | ||
905 | /// <param name=rdata>HTTP service request work area</param> | ||
906 | // private void DoDelete(InventoryRequestData rdata) | ||
907 | // { | ||
908 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false); | ||
909 | // | ||
910 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
911 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
912 | // { | ||
913 | // InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
914 | // | ||
915 | // InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; | ||
916 | // Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted", | ||
917 | // MsgId, rdata.method, rdata.path); | ||
918 | // folder.ParentID = TrashCan.ID; | ||
919 | // Rest.InventoryServices.MoveFolder(folder); | ||
920 | // Rest.InventoryServices.PurgeFolder(TrashCan); | ||
921 | // | ||
922 | // rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1} <p>", folder.Name, folder.ID)); | ||
923 | // } | ||
924 | // | ||
925 | // // Deleting items is much more straight forward. | ||
926 | // | ||
927 | // else | ||
928 | // { | ||
929 | // InventoryItemBase item = (InventoryItemBase) InventoryNode; | ||
930 | // Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", | ||
931 | // MsgId, rdata.method, rdata.path); | ||
932 | // List<UUID> uuids = new List<UUID>(); | ||
933 | // uuids.Add(item.ID); | ||
934 | // Rest.InventoryServices.DeleteItems(item.Owner, uuids); | ||
935 | // rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1} <p>", item.Name, item.ID)); | ||
936 | // } | ||
937 | // | ||
938 | // rdata.Complete(); | ||
939 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
940 | // } | ||
941 | |||
942 | #endregion method-specific processing | ||
943 | |||
944 | /// <summary> | ||
945 | /// This method is called to obtain the OpenSim inventory object identified | ||
946 | /// by the supplied URI. This may be either an Item or a Folder, so a suitably | ||
947 | /// ambiguous return type is employed (Object). This method recurses as | ||
948 | /// necessary to process the designated hierarchy. | ||
949 | /// | ||
950 | /// If we reach the end of the URI then we return the contextual folder to | ||
951 | /// our caller. | ||
952 | /// | ||
953 | /// If we are not yet at the end of the URI we attempt to find a child folder | ||
954 | /// and if we succeed we recurse. | ||
955 | /// | ||
956 | /// If this is the last node, then we look to see if this is an item. If it is, | ||
957 | /// we return that item. | ||
958 | /// | ||
959 | /// If we reach the end of an inventory path and the URI si not yet exhausted, | ||
960 | /// then if 'fill' is specified, we create the intermediate nodes. | ||
961 | /// | ||
962 | /// Otherwise we fail the request on the ground of an invalid URI. | ||
963 | /// | ||
964 | /// An ambiguous request causes the request to fail. | ||
965 | /// | ||
966 | /// </summary> | ||
967 | /// <param name=rdata>HTTP service request work area</param> | ||
968 | /// <param name=folder>The folder to be searched (parent)</param> | ||
969 | /// <param name=pi>URI parameter index</param> | ||
970 | /// <param name=fill>Should missing path members be created?</param> | ||
971 | |||
972 | private Object getInventoryNode(InventoryRequestData rdata, | ||
973 | InventoryFolderBase folder, | ||
974 | int pi, bool fill) | ||
975 | { | ||
976 | InventoryFolderBase foundf = null; | ||
977 | int fk = 0; | ||
978 | |||
979 | Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
980 | |||
981 | // We have just run off the end of the parameter sequence | ||
982 | |||
983 | if (pi >= rdata.Parameters.Length) | ||
984 | { | ||
985 | return folder; | ||
986 | } | ||
987 | |||
988 | // There are more names in the parameter sequence, | ||
989 | // look for the folder named by param[pi] as a | ||
990 | // child of the folder supplied as an argument. | ||
991 | // Note that a UUID may have been supplied as the | ||
992 | // identifier (it is the ONLY guaranteed unambiguous | ||
993 | // option. | ||
994 | |||
995 | if (rdata.folders != null) | ||
996 | { | ||
997 | foreach (InventoryFolderBase f in rdata.folders) | ||
998 | { | ||
999 | // Look for the present node in the directory list | ||
1000 | if (f.ParentID == folder.ID && | ||
1001 | (f.Name == rdata.Parameters[pi] || | ||
1002 | f.ID.ToString() == rdata.Parameters[pi])) | ||
1003 | { | ||
1004 | foundf = f; | ||
1005 | fk++; | ||
1006 | } | ||
1007 | } | ||
1008 | } | ||
1009 | |||
1010 | // If more than one node matched, then the path, as specified | ||
1011 | // is ambiguous. | ||
1012 | |||
1013 | if (fk > 1) | ||
1014 | { | ||
1015 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
1016 | MsgId, rdata.method, rdata.path); | ||
1017 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); | ||
1018 | } | ||
1019 | |||
1020 | // If we find a match, then the method | ||
1021 | // increment the parameter index, and calls itself | ||
1022 | // passing the found folder as the new context. | ||
1023 | |||
1024 | if (foundf != null) | ||
1025 | { | ||
1026 | return getInventoryNode(rdata, foundf, pi+1, fill); | ||
1027 | } | ||
1028 | |||
1029 | // No folders that match. Perhaps this parameter identifies an item? If | ||
1030 | // it does, then it MUST also be the last name in the sequence. | ||
1031 | |||
1032 | if (pi == rdata.Parameters.Length-1) | ||
1033 | { | ||
1034 | if (rdata.items != null) | ||
1035 | { | ||
1036 | int k = 0; | ||
1037 | InventoryItemBase li = null; | ||
1038 | foreach (InventoryItemBase i in rdata.items) | ||
1039 | { | ||
1040 | if (i.Folder == folder.ID && | ||
1041 | (i.Name == rdata.Parameters[pi] || | ||
1042 | i.ID.ToString() == rdata.Parameters[pi])) | ||
1043 | { | ||
1044 | li = i; | ||
1045 | k++; | ||
1046 | } | ||
1047 | } | ||
1048 | if (k == 1) | ||
1049 | { | ||
1050 | return li; | ||
1051 | } | ||
1052 | else if (k > 1) | ||
1053 | { | ||
1054 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
1055 | MsgId, rdata.method, rdata.path); | ||
1056 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); | ||
1057 | } | ||
1058 | } | ||
1059 | } | ||
1060 | |||
1061 | // If fill is enabled, then we must create the missing intermediate nodes. | ||
1062 | // And of course, even this is not straightforward. All intermediate nodes | ||
1063 | // are obviously folders, but the last node may be a folder or an item. | ||
1064 | |||
1065 | if (fill) | ||
1066 | { | ||
1067 | } | ||
1068 | |||
1069 | // No fill, so abandon the request | ||
1070 | |||
1071 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", | ||
1072 | MsgId, rdata.method, rdata.path); | ||
1073 | rdata.Fail(Rest.HttpStatusCodeNotFound, | ||
1074 | String.Format("resource {0}:{1} not found", rdata.method, rdata.path)); | ||
1075 | |||
1076 | return null; /* Never reached */ | ||
1077 | } | ||
1078 | |||
1079 | /// <summary> | ||
1080 | /// This routine traverse the inventory's structure until the end-point identified | ||
1081 | /// in the URI is reached, the remainder of the inventory (if any) is then formatted | ||
1082 | /// and returned to the requestor. | ||
1083 | /// | ||
1084 | /// Note that this method is only interested in those folder that match elements of | ||
1085 | /// the URI supplied by the requestor, so once a match is fund, the processing does | ||
1086 | /// not need to consider any further elements. | ||
1087 | /// | ||
1088 | /// Only the last element in the URI should identify an item. | ||
1089 | /// </summary> | ||
1090 | /// <param name=rdata>HTTP service request work area</param> | ||
1091 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1092 | /// <param name=pi>URI parameter index</param> | ||
1093 | |||
1094 | private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
1095 | { | ||
1096 | Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
1097 | |||
1098 | if (rdata.folders != null) | ||
1099 | { | ||
1100 | // If there was only one parameter (avatar name), then the entire | ||
1101 | // inventory is being requested. | ||
1102 | |||
1103 | if (rdata.Parameters.Length == 1) | ||
1104 | { | ||
1105 | formatInventory(rdata, rdata.root, String.Empty); | ||
1106 | } | ||
1107 | |||
1108 | // Has the client specified the root directory name explicitly? | ||
1109 | // if yes, then we just absorb the reference, because the folder | ||
1110 | // we start looking in for a match *is* the root directory. If there | ||
1111 | // are more parameters remaining we tarverse, otehrwise it's time | ||
1112 | // to format. Otherwise,we consider the "My Inventory" to be implied | ||
1113 | // and we just traverse normally. | ||
1114 | |||
1115 | else if (folder.ID.ToString() == rdata.Parameters[pi] || | ||
1116 | folder.Name == rdata.Parameters[pi]) | ||
1117 | { | ||
1118 | // Length is -1 because the avatar name is a parameter | ||
1119 | if (pi<(rdata.Parameters.Length-1)) | ||
1120 | { | ||
1121 | traverseInventory(rdata, folder, pi+1); | ||
1122 | } | ||
1123 | else | ||
1124 | { | ||
1125 | formatInventory(rdata, folder, String.Empty); | ||
1126 | } | ||
1127 | } | ||
1128 | else | ||
1129 | { | ||
1130 | traverseInventory(rdata, folder, pi); | ||
1131 | } | ||
1132 | |||
1133 | return; | ||
1134 | } | ||
1135 | } | ||
1136 | |||
1137 | /// <summary> | ||
1138 | /// This is the recursive method. I've separated them in this way so that | ||
1139 | /// we do not have to waste cycles on any first-case-only processing. | ||
1140 | /// </summary> | ||
1141 | |||
1142 | private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
1143 | { | ||
1144 | int fk = 0; | ||
1145 | InventoryFolderBase ffound = null; | ||
1146 | InventoryItemBase ifound = null; | ||
1147 | |||
1148 | Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
1149 | |||
1150 | foreach (InventoryFolderBase f in rdata.folders) | ||
1151 | { | ||
1152 | if (f.ParentID == folder.ID && | ||
1153 | (f.Name == rdata.Parameters[pi] || | ||
1154 | f.ID.ToString() == rdata.Parameters[pi])) | ||
1155 | { | ||
1156 | fk++; | ||
1157 | ffound = f; | ||
1158 | } | ||
1159 | } | ||
1160 | |||
1161 | // If this is the last element in the parameter sequence, then | ||
1162 | // it is reasonable to check for an item. All intermediate nodes | ||
1163 | // MUST be folders. | ||
1164 | |||
1165 | if (pi == rdata.Parameters.Length-1) | ||
1166 | { | ||
1167 | // Only if there are any items, and there pretty much always are. | ||
1168 | |||
1169 | if (rdata.items != null) | ||
1170 | { | ||
1171 | foreach (InventoryItemBase i in rdata.items) | ||
1172 | { | ||
1173 | if (i.Folder == folder.ID && | ||
1174 | (i.Name == rdata.Parameters[pi] || | ||
1175 | i.ID.ToString() == rdata.Parameters[pi])) | ||
1176 | { | ||
1177 | fk++; | ||
1178 | ifound = i; | ||
1179 | } | ||
1180 | } | ||
1181 | } | ||
1182 | } | ||
1183 | |||
1184 | if (fk == 1) | ||
1185 | { | ||
1186 | if (ffound != null) | ||
1187 | { | ||
1188 | if (pi < rdata.Parameters.Length-1) | ||
1189 | { | ||
1190 | traverseInventory(rdata, ffound, pi+1); | ||
1191 | } | ||
1192 | else | ||
1193 | { | ||
1194 | formatInventory(rdata, ffound, String.Empty); | ||
1195 | } | ||
1196 | return; | ||
1197 | } | ||
1198 | else | ||
1199 | { | ||
1200 | // Fetching an Item has a special significance. In this | ||
1201 | // case we also want to fetch the associated asset. | ||
1202 | // To make it interesting, we'll do this via redirection. | ||
1203 | string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port, | ||
1204 | "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString()); | ||
1205 | rdata.Redirect(asseturl,Rest.PERMANENT); | ||
1206 | Rest.Log.DebugFormat("{0} Never Reached", MsgId); | ||
1207 | } | ||
1208 | } | ||
1209 | else if (fk > 1) | ||
1210 | { | ||
1211 | rdata.Fail(Rest.HttpStatusCodeConflict, | ||
1212 | String.Format("ambiguous element ({0}) in path specified: <{1}>", | ||
1213 | pi, rdata.path)); | ||
1214 | } | ||
1215 | |||
1216 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | ||
1217 | MsgId, rdata.path); | ||
1218 | rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}", | ||
1219 | rdata.Parameters[pi])); | ||
1220 | |||
1221 | } | ||
1222 | |||
1223 | /// <summary> | ||
1224 | /// This method generates XML that describes an instance of InventoryFolderBase. | ||
1225 | /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem | ||
1226 | /// to generate XML for any items encountered along the way. | ||
1227 | /// The indentation parameter is solely for the benefit of trace record | ||
1228 | /// formatting. | ||
1229 | /// </summary> | ||
1230 | /// <param name=rdata>HTTP service request work area</param> | ||
1231 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1232 | /// <param name=indent>pretty print indentation</param> | ||
1233 | private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) | ||
1234 | { | ||
1235 | if (Rest.DEBUG) | ||
1236 | { | ||
1237 | Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}", | ||
1238 | MsgId, folder.ID, indent, folder.Name, folder.Type); | ||
1239 | indent += "\t"; | ||
1240 | } | ||
1241 | |||
1242 | // Start folder item | ||
1243 | |||
1244 | rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); | ||
1245 | rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); | ||
1246 | rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); | ||
1247 | rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString()); | ||
1248 | rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); | ||
1249 | rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); | ||
1250 | rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); | ||
1251 | |||
1252 | if (rdata.folders != null) | ||
1253 | { | ||
1254 | foreach (InventoryFolderBase f in rdata.folders) | ||
1255 | { | ||
1256 | if (f.ParentID == folder.ID) | ||
1257 | { | ||
1258 | formatInventory(rdata, f, indent); | ||
1259 | } | ||
1260 | } | ||
1261 | } | ||
1262 | |||
1263 | if (rdata.items != null) | ||
1264 | { | ||
1265 | foreach (InventoryItemBase i in rdata.items) | ||
1266 | { | ||
1267 | if (i.Folder == folder.ID) | ||
1268 | { | ||
1269 | formatItem(rdata, i, indent); | ||
1270 | } | ||
1271 | } | ||
1272 | } | ||
1273 | |||
1274 | // End folder item | ||
1275 | |||
1276 | rdata.writer.WriteEndElement(); | ||
1277 | } | ||
1278 | |||
1279 | /// <summary> | ||
1280 | /// This method generates XML that describes an instance of InventoryItemBase. | ||
1281 | /// </summary> | ||
1282 | /// <param name="rdata">HTTP service request work area</param> | ||
1283 | /// <param name="i">The item to be formatted</param> | ||
1284 | /// <param name="indent">Pretty print indentation</param> | ||
1285 | private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) | ||
1286 | { | ||
1287 | Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}", | ||
1288 | MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType); | ||
1289 | |||
1290 | rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty); | ||
1291 | |||
1292 | rdata.writer.WriteAttributeString("name", String.Empty, i.Name); | ||
1293 | rdata.writer.WriteAttributeString("desc", String.Empty, i.Description); | ||
1294 | rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString()); | ||
1295 | rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString()); | ||
1296 | rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString()); | ||
1297 | rdata.writer.WriteAttributeString("creator", String.Empty, i.CreatorId); | ||
1298 | rdata.writer.WriteAttributeString("creatordata", String.Empty, i.CreatorData); | ||
1299 | rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString()); | ||
1300 | rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString()); | ||
1301 | rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString()); | ||
1302 | rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString()); | ||
1303 | rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString()); | ||
1304 | rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString()); | ||
1305 | rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString()); | ||
1306 | rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString()); | ||
1307 | |||
1308 | rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty); | ||
1309 | rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X")); | ||
1310 | rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X")); | ||
1311 | rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X")); | ||
1312 | rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X")); | ||
1313 | rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X")); | ||
1314 | rdata.writer.WriteEndElement(); | ||
1315 | |||
1316 | rdata.writer.WriteElementString("Asset", i.AssetID.ToString()); | ||
1317 | |||
1318 | rdata.writer.WriteEndElement(); | ||
1319 | } | ||
1320 | |||
1321 | /// <summary> | ||
1322 | /// This method creates a "trashcan" folder to support folder and item | ||
1323 | /// deletions by this interface. The xisting trash folder is found and | ||
1324 | /// this folder is created within it. It is called "tmp" to indicate to | ||
1325 | /// the client that it is OK to delete this folder. The REST interface | ||
1326 | /// will recreate the folder on an as-required basis. | ||
1327 | /// If the trash can cannot be created, then by implication the request | ||
1328 | /// that required it cannot be completed, and it fails accordingly. | ||
1329 | /// </summary> | ||
1330 | /// <param name=rdata>HTTP service request work area</param> | ||
1331 | private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) | ||
1332 | { | ||
1333 | InventoryFolderBase TrashCan = null; | ||
1334 | |||
1335 | foreach (InventoryFolderBase f in rdata.folders) | ||
1336 | { | ||
1337 | if (f.Name == "Trash") | ||
1338 | { | ||
1339 | foreach (InventoryFolderBase t in rdata.folders) | ||
1340 | { | ||
1341 | if (t.Name == "tmp") | ||
1342 | { | ||
1343 | TrashCan = t; | ||
1344 | } | ||
1345 | } | ||
1346 | if (TrashCan == null) | ||
1347 | { | ||
1348 | TrashCan = new InventoryFolderBase(); | ||
1349 | TrashCan.Name = "tmp"; | ||
1350 | TrashCan.ID = UUID.Random(); | ||
1351 | TrashCan.Version = 1; | ||
1352 | TrashCan.Type = (short) AssetType.TrashFolder; | ||
1353 | TrashCan.ParentID = f.ID; | ||
1354 | TrashCan.Owner = f.Owner; | ||
1355 | Rest.InventoryServices.AddFolder(TrashCan); | ||
1356 | } | ||
1357 | } | ||
1358 | } | ||
1359 | |||
1360 | if (TrashCan == null) | ||
1361 | { | ||
1362 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); | ||
1363 | rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can"); | ||
1364 | } | ||
1365 | |||
1366 | return TrashCan; | ||
1367 | } | ||
1368 | |||
1369 | /// <summary> | ||
1370 | /// Make sure that an unchanged folder is not unnecessarily | ||
1371 | /// processed. | ||
1372 | /// </summary> | ||
1373 | /// <param name=newf>Folder obtained from enclosed entity</param> | ||
1374 | /// <param name=oldf>Folder obtained from the user's inventory</param> | ||
1375 | private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) | ||
1376 | { | ||
1377 | return (newf.Name != oldf.Name | ||
1378 | || newf.ParentID != oldf.ParentID | ||
1379 | || newf.Owner != oldf.Owner | ||
1380 | || newf.Type != oldf.Type | ||
1381 | || newf.Version != oldf.Version | ||
1382 | ); | ||
1383 | } | ||
1384 | |||
1385 | /// <summary> | ||
1386 | /// Make sure that an unchanged item is not unnecessarily | ||
1387 | /// processed. | ||
1388 | /// </summary> | ||
1389 | /// <param name=newf>Item obtained from enclosed entity</param> | ||
1390 | /// <param name=oldf>Item obtained from the user's inventory</param> | ||
1391 | private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) | ||
1392 | { | ||
1393 | return (newf.Name != oldf.Name | ||
1394 | || newf.Folder != oldf.Folder | ||
1395 | || newf.Description != oldf.Description | ||
1396 | || newf.Owner != oldf.Owner | ||
1397 | || newf.CreatorId != oldf.CreatorId | ||
1398 | || newf.AssetID != oldf.AssetID | ||
1399 | || newf.GroupID != oldf.GroupID | ||
1400 | || newf.GroupOwned != oldf.GroupOwned | ||
1401 | || newf.InvType != oldf.InvType | ||
1402 | || newf.AssetType != oldf.AssetType | ||
1403 | ); | ||
1404 | } | ||
1405 | |||
1406 | /// <summary> | ||
1407 | /// This method is called by PUT and POST to create an XmlInventoryCollection | ||
1408 | /// instance that reflects the content of the entity supplied on the request. | ||
1409 | /// Any elements in the completed collection whose UUID is zero, are | ||
1410 | /// considered to be located relative to the end-point identified int he | ||
1411 | /// URI. In this way, an entire sub-tree can be conveyed in a single REST | ||
1412 | /// PUT or POST request. | ||
1413 | /// | ||
1414 | /// A new instance of XmlInventoryCollection is created and, if the request | ||
1415 | /// has an entity, it is more completely initialized. thus, if no entity was | ||
1416 | /// provided the collection is valid, but empty. | ||
1417 | /// | ||
1418 | /// The entity is then scanned and each tag is processed to produce the | ||
1419 | /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection | ||
1420 | /// will reflect the subtree described by the entity. | ||
1421 | /// | ||
1422 | /// This is a very flexible mechanism, the entity may contain arbitrary, | ||
1423 | /// discontiguous tree fragments, or may contain single element. The caller is | ||
1424 | /// responsible for integrating this collection (and ensuring that any | ||
1425 | /// missing parent IDs are resolved). | ||
1426 | /// </summary> | ||
1427 | /// <param name=rdata>HTTP service request work area</param> | ||
1428 | internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) | ||
1429 | { | ||
1430 | Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); | ||
1431 | |||
1432 | XmlInventoryCollection ic = new XmlInventoryCollection(); | ||
1433 | |||
1434 | if (rdata.request.HasEntityBody) | ||
1435 | { | ||
1436 | Rest.Log.DebugFormat("{0} Entity present", MsgId); | ||
1437 | |||
1438 | ic.init(rdata); | ||
1439 | |||
1440 | try | ||
1441 | { | ||
1442 | while (ic.xml.Read()) | ||
1443 | { | ||
1444 | switch (ic.xml.NodeType) | ||
1445 | { | ||
1446 | case XmlNodeType.Element: | ||
1447 | Rest.Log.DebugFormat("{0} StartElement: <{1}>", | ||
1448 | MsgId, ic.xml.Name); | ||
1449 | |||
1450 | switch (ic.xml.Name) | ||
1451 | { | ||
1452 | case "Folder": | ||
1453 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1454 | MsgId, ic.xml.Name); | ||
1455 | CollectFolder(ic); | ||
1456 | break; | ||
1457 | case "Item": | ||
1458 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1459 | MsgId, ic.xml.Name); | ||
1460 | CollectItem(ic); | ||
1461 | break; | ||
1462 | case "Asset": | ||
1463 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1464 | MsgId, ic.xml.Name); | ||
1465 | CollectAsset(ic); | ||
1466 | break; | ||
1467 | case "Permissions": | ||
1468 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1469 | MsgId, ic.xml.Name); | ||
1470 | CollectPermissions(ic); | ||
1471 | break; | ||
1472 | default: | ||
1473 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1474 | MsgId, ic.xml.Name); | ||
1475 | break; | ||
1476 | } | ||
1477 | |||
1478 | // This stinks, but the ReadElement call above not only reads | ||
1479 | // the imbedded data, but also consumes the end tag for Asset | ||
1480 | // and moves the element pointer on to the containing Item's | ||
1481 | // element-end, however, if there was a permissions element | ||
1482 | // following, it would get us to the start of that.. | ||
1483 | if (ic.xml.NodeType == XmlNodeType.EndElement && | ||
1484 | ic.xml.Name == "Item") | ||
1485 | { | ||
1486 | Validate(ic); | ||
1487 | } | ||
1488 | break; | ||
1489 | |||
1490 | case XmlNodeType.EndElement : | ||
1491 | switch (ic.xml.Name) | ||
1492 | { | ||
1493 | case "Folder": | ||
1494 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1495 | MsgId, ic.xml.Name); | ||
1496 | ic.Pop(); | ||
1497 | break; | ||
1498 | case "Item": | ||
1499 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1500 | MsgId, ic.xml.Name); | ||
1501 | Validate(ic); | ||
1502 | break; | ||
1503 | case "Asset": | ||
1504 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1505 | MsgId, ic.xml.Name); | ||
1506 | break; | ||
1507 | case "Permissions": | ||
1508 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1509 | MsgId, ic.xml.Name); | ||
1510 | break; | ||
1511 | default: | ||
1512 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1513 | MsgId, ic.xml.Name); | ||
1514 | break; | ||
1515 | } | ||
1516 | break; | ||
1517 | |||
1518 | default: | ||
1519 | Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>", | ||
1520 | MsgId, ic.xml.NodeType, ic.xml.Value); | ||
1521 | break; | ||
1522 | } | ||
1523 | } | ||
1524 | } | ||
1525 | catch (XmlException e) | ||
1526 | { | ||
1527 | Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); | ||
1528 | throw e; | ||
1529 | } | ||
1530 | catch (Exception e) | ||
1531 | { | ||
1532 | Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); | ||
1533 | throw e; | ||
1534 | } | ||
1535 | } | ||
1536 | else | ||
1537 | { | ||
1538 | Rest.Log.DebugFormat("{0} Entity absent", MsgId); | ||
1539 | } | ||
1540 | |||
1541 | if (Rest.DEBUG) | ||
1542 | { | ||
1543 | Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); | ||
1544 | Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); | ||
1545 | Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); | ||
1546 | Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); | ||
1547 | } | ||
1548 | |||
1549 | return ic; | ||
1550 | } | ||
1551 | |||
1552 | /// <summary> | ||
1553 | /// This method creates an inventory Folder from the | ||
1554 | /// information supplied in the request's entity. | ||
1555 | /// A folder instance is created and initialized to reflect | ||
1556 | /// default values. These values are then overridden | ||
1557 | /// by information supplied in the entity. | ||
1558 | /// If context was not explicitly provided, then the | ||
1559 | /// appropriate ID values are determined. | ||
1560 | /// </summary> | ||
1561 | |||
1562 | private void CollectFolder(XmlInventoryCollection ic) | ||
1563 | { | ||
1564 | Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); | ||
1565 | |||
1566 | InventoryFolderBase result = new InventoryFolderBase(); | ||
1567 | |||
1568 | // Default values | ||
1569 | |||
1570 | result.Name = String.Empty; | ||
1571 | result.ID = UUID.Zero; | ||
1572 | result.Owner = ic.UserID; | ||
1573 | result.ParentID = UUID.Zero; // Context | ||
1574 | result.Type = (short) AssetType.Folder; | ||
1575 | result.Version = 1; | ||
1576 | |||
1577 | if (ic.xml.HasAttributes) | ||
1578 | { | ||
1579 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1580 | { | ||
1581 | ic.xml.MoveToAttribute(i); | ||
1582 | switch (ic.xml.Name) | ||
1583 | { | ||
1584 | case "name": | ||
1585 | result.Name = ic.xml.Value; | ||
1586 | break; | ||
1587 | case "uuid": | ||
1588 | result.ID = new UUID(ic.xml.Value); | ||
1589 | break; | ||
1590 | case "parent": | ||
1591 | result.ParentID = new UUID(ic.xml.Value); | ||
1592 | break; | ||
1593 | case "owner": | ||
1594 | result.Owner = new UUID(ic.xml.Value); | ||
1595 | break; | ||
1596 | case "type": | ||
1597 | result.Type = Int16.Parse(ic.xml.Value); | ||
1598 | break; | ||
1599 | case "version": | ||
1600 | result.Version = UInt16.Parse(ic.xml.Value); | ||
1601 | break; | ||
1602 | default: | ||
1603 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", | ||
1604 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1605 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", | ||
1606 | ic.xml.Name)); | ||
1607 | break; | ||
1608 | } | ||
1609 | } | ||
1610 | } | ||
1611 | |||
1612 | ic.xml.MoveToElement(); | ||
1613 | |||
1614 | // The client is relying upon the reconstitution process | ||
1615 | // to determine the parent's UUID based upon context. This | ||
1616 | // is necessary where a new folder may have been | ||
1617 | // introduced. | ||
1618 | |||
1619 | if (result.ParentID == UUID.Zero) | ||
1620 | { | ||
1621 | result.ParentID = ic.Parent(); | ||
1622 | } | ||
1623 | else | ||
1624 | { | ||
1625 | bool found = false; | ||
1626 | |||
1627 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
1628 | { | ||
1629 | if (parent.ID == result.ParentID) | ||
1630 | { | ||
1631 | found = true; | ||
1632 | break; | ||
1633 | } | ||
1634 | } | ||
1635 | |||
1636 | if (!found) | ||
1637 | { | ||
1638 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", | ||
1639 | MsgId, ic.Item.Folder, result.ID); | ||
1640 | ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); | ||
1641 | } | ||
1642 | } | ||
1643 | |||
1644 | // This is a new folder, so no existing UUID is available | ||
1645 | // or appropriate | ||
1646 | |||
1647 | if (result.ID == UUID.Zero) | ||
1648 | { | ||
1649 | result.ID = UUID.Random(); | ||
1650 | } | ||
1651 | |||
1652 | // Treat this as a new context. Any other information is | ||
1653 | // obsolete as a consequence. | ||
1654 | |||
1655 | ic.Push(result); | ||
1656 | } | ||
1657 | |||
1658 | /// <summary> | ||
1659 | /// This method is called to handle the construction of an Item | ||
1660 | /// instance from the supplied request entity. It is called | ||
1661 | /// whenever an Item start tag is detected. | ||
1662 | /// An instance of an Item is created and initialized to default | ||
1663 | /// values. These values are then overridden from values supplied | ||
1664 | /// as attributes to the Item element. | ||
1665 | /// This item is then stored in the XmlInventoryCollection and | ||
1666 | /// will be verified by Validate. | ||
1667 | /// All context is reset whenever the effective folder changes | ||
1668 | /// or an item is successfully validated. | ||
1669 | /// </summary> | ||
1670 | private void CollectItem(XmlInventoryCollection ic) | ||
1671 | { | ||
1672 | Rest.Log.DebugFormat("{0} Interpret item element", MsgId); | ||
1673 | |||
1674 | InventoryItemBase result = new InventoryItemBase(); | ||
1675 | |||
1676 | result.Name = String.Empty; | ||
1677 | result.Description = String.Empty; | ||
1678 | result.ID = UUID.Zero; | ||
1679 | result.Folder = UUID.Zero; | ||
1680 | result.Owner = ic.UserID; | ||
1681 | result.CreatorId = ic.UserID.ToString(); | ||
1682 | result.AssetID = UUID.Zero; | ||
1683 | result.GroupID = UUID.Zero; | ||
1684 | result.GroupOwned = false; | ||
1685 | result.InvType = (int) InventoryType.Unknown; | ||
1686 | result.AssetType = (int) AssetType.Unknown; | ||
1687 | |||
1688 | if (ic.xml.HasAttributes) | ||
1689 | { | ||
1690 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1691 | { | ||
1692 | ic.xml.MoveToAttribute(i); | ||
1693 | |||
1694 | switch (ic.xml.Name) | ||
1695 | { | ||
1696 | case "name": | ||
1697 | result.Name = ic.xml.Value; | ||
1698 | break; | ||
1699 | case "desc": | ||
1700 | result.Description = ic.xml.Value; | ||
1701 | break; | ||
1702 | case "uuid": | ||
1703 | result.ID = new UUID(ic.xml.Value); | ||
1704 | break; | ||
1705 | case "folder": | ||
1706 | result.Folder = new UUID(ic.xml.Value); | ||
1707 | break; | ||
1708 | case "owner": | ||
1709 | result.Owner = new UUID(ic.xml.Value); | ||
1710 | break; | ||
1711 | case "invtype": | ||
1712 | result.InvType = Int32.Parse(ic.xml.Value); | ||
1713 | break; | ||
1714 | case "creator": | ||
1715 | result.CreatorId = ic.xml.Value; | ||
1716 | break; | ||
1717 | case "assettype": | ||
1718 | result.AssetType = Int32.Parse(ic.xml.Value); | ||
1719 | break; | ||
1720 | case "groupowned": | ||
1721 | result.GroupOwned = Boolean.Parse(ic.xml.Value); | ||
1722 | break; | ||
1723 | case "groupid": | ||
1724 | result.GroupID = new UUID(ic.xml.Value); | ||
1725 | break; | ||
1726 | case "flags": | ||
1727 | result.Flags = UInt32.Parse(ic.xml.Value); | ||
1728 | break; | ||
1729 | case "creationdate": | ||
1730 | result.CreationDate = Int32.Parse(ic.xml.Value); | ||
1731 | break; | ||
1732 | case "saletype": | ||
1733 | result.SaleType = Byte.Parse(ic.xml.Value); | ||
1734 | break; | ||
1735 | case "saleprice": | ||
1736 | result.SalePrice = Int32.Parse(ic.xml.Value); | ||
1737 | break; | ||
1738 | |||
1739 | default: | ||
1740 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", | ||
1741 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1742 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", | ||
1743 | ic.xml.Name)); | ||
1744 | break; | ||
1745 | } | ||
1746 | } | ||
1747 | } | ||
1748 | |||
1749 | ic.xml.MoveToElement(); | ||
1750 | |||
1751 | ic.Push(result); | ||
1752 | } | ||
1753 | |||
1754 | /// <summary> | ||
1755 | /// This method assembles an asset instance from the | ||
1756 | /// information supplied in the request's entity. It is | ||
1757 | /// called as a result of detecting a start tag for a | ||
1758 | /// type of Asset. | ||
1759 | /// The information is collected locally, and an asset | ||
1760 | /// instance is created only if the basic XML parsing | ||
1761 | /// completes successfully. | ||
1762 | /// Default values for all parts of the asset are | ||
1763 | /// established before overriding them from the supplied | ||
1764 | /// XML. | ||
1765 | /// If an asset has inline=true as an attribute, then | ||
1766 | /// the element contains the data representing the | ||
1767 | /// asset. This is saved as the data component. | ||
1768 | /// inline=false means that the element's payload is | ||
1769 | /// simply the UUID of the asset referenced by the | ||
1770 | /// item being constructed. | ||
1771 | /// An asset, if created is stored in the | ||
1772 | /// XmlInventoryCollection | ||
1773 | /// </summary> | ||
1774 | private void CollectAsset(XmlInventoryCollection ic) | ||
1775 | { | ||
1776 | Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); | ||
1777 | |||
1778 | string name = String.Empty; | ||
1779 | string desc = String.Empty; | ||
1780 | sbyte type = (sbyte) AssetType.Unknown; | ||
1781 | bool temp = false; | ||
1782 | bool local = false; | ||
1783 | |||
1784 | // This is not a persistent attribute | ||
1785 | bool inline = false; | ||
1786 | |||
1787 | UUID uuid = UUID.Zero; | ||
1788 | |||
1789 | // Attribute is optional | ||
1790 | if (ic.xml.HasAttributes) | ||
1791 | { | ||
1792 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1793 | { | ||
1794 | ic.xml.MoveToAttribute(i); | ||
1795 | switch (ic.xml.Name) | ||
1796 | { | ||
1797 | case "name" : | ||
1798 | name = ic.xml.Value; | ||
1799 | break; | ||
1800 | |||
1801 | case "type" : | ||
1802 | type = SByte.Parse(ic.xml.Value); | ||
1803 | break; | ||
1804 | |||
1805 | case "description" : | ||
1806 | desc = ic.xml.Value; | ||
1807 | break; | ||
1808 | |||
1809 | case "temporary" : | ||
1810 | temp = Boolean.Parse(ic.xml.Value); | ||
1811 | break; | ||
1812 | |||
1813 | case "uuid" : | ||
1814 | uuid = new UUID(ic.xml.Value); | ||
1815 | break; | ||
1816 | |||
1817 | case "inline" : | ||
1818 | inline = Boolean.Parse(ic.xml.Value); | ||
1819 | break; | ||
1820 | |||
1821 | case "local" : | ||
1822 | local = Boolean.Parse(ic.xml.Value); | ||
1823 | break; | ||
1824 | |||
1825 | default : | ||
1826 | Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", | ||
1827 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1828 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1829 | String.Format("unrecognized attribute <{0}>", ic.xml.Name)); | ||
1830 | break; | ||
1831 | } | ||
1832 | } | ||
1833 | } | ||
1834 | |||
1835 | ic.xml.MoveToElement(); | ||
1836 | |||
1837 | // If this is a reference to an existing asset, just store the | ||
1838 | // asset ID into the item. | ||
1839 | |||
1840 | if (!inline) | ||
1841 | { | ||
1842 | if (ic.Item != null) | ||
1843 | { | ||
1844 | ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString()); | ||
1845 | Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); | ||
1846 | } | ||
1847 | else | ||
1848 | { | ||
1849 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); | ||
1850 | ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); | ||
1851 | } | ||
1852 | } | ||
1853 | |||
1854 | // Otherwise, generate an asset ID, store that into the item, and | ||
1855 | // create an entry in the asset list for the inlined asset. But | ||
1856 | // only if the size is non-zero. | ||
1857 | |||
1858 | else | ||
1859 | { | ||
1860 | AssetBase asset = null; | ||
1861 | string b64string = null; | ||
1862 | |||
1863 | // Generate a UUID if none were given, and generally none should | ||
1864 | // be. Ever. | ||
1865 | |||
1866 | if (uuid == UUID.Zero) | ||
1867 | { | ||
1868 | uuid = UUID.Random(); | ||
1869 | } | ||
1870 | |||
1871 | // Create AssetBase entity to hold the inlined asset | ||
1872 | |||
1873 | asset = new AssetBase(uuid, name, type, UUID.Zero.ToString()); | ||
1874 | |||
1875 | asset.Description = desc; | ||
1876 | asset.Local = local; | ||
1877 | asset.Temporary = temp; | ||
1878 | |||
1879 | b64string = ic.xml.ReadElementContentAsString(); | ||
1880 | |||
1881 | Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); | ||
1882 | Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, | ||
1883 | b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); | ||
1884 | |||
1885 | asset.Data = Convert.FromBase64String(b64string); | ||
1886 | |||
1887 | // Ensure the asset always has some kind of data component | ||
1888 | |||
1889 | if (asset.Data == null) | ||
1890 | { | ||
1891 | asset.Data = new byte[1]; | ||
1892 | } | ||
1893 | |||
1894 | // If this is in the context of an item, establish | ||
1895 | // a link with the item in context. | ||
1896 | |||
1897 | if (ic.Item != null && ic.Item.AssetID == UUID.Zero) | ||
1898 | { | ||
1899 | ic.Item.AssetID = uuid; | ||
1900 | } | ||
1901 | |||
1902 | ic.Push(asset); | ||
1903 | } | ||
1904 | } | ||
1905 | |||
1906 | /// <summary> | ||
1907 | /// Store any permissions information provided by the request. | ||
1908 | /// This overrides the default permissions set when the | ||
1909 | /// XmlInventoryCollection object was created. | ||
1910 | /// </summary> | ||
1911 | private void CollectPermissions(XmlInventoryCollection ic) | ||
1912 | { | ||
1913 | if (ic.xml.HasAttributes) | ||
1914 | { | ||
1915 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1916 | { | ||
1917 | ic.xml.MoveToAttribute(i); | ||
1918 | switch (ic.xml.Name) | ||
1919 | { | ||
1920 | case "current": | ||
1921 | ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1922 | break; | ||
1923 | case "next": | ||
1924 | ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1925 | break; | ||
1926 | case "group": | ||
1927 | ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1928 | break; | ||
1929 | case "everyone": | ||
1930 | ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1931 | break; | ||
1932 | case "base": | ||
1933 | ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1934 | break; | ||
1935 | default: | ||
1936 | Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", | ||
1937 | MsgId,ic.xml.Name, ic.xml.Value); | ||
1938 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1939 | String.Format("invalid attribute <{0}>", ic.xml.Name)); | ||
1940 | break; | ||
1941 | } | ||
1942 | } | ||
1943 | } | ||
1944 | |||
1945 | ic.xml.MoveToElement(); | ||
1946 | } | ||
1947 | |||
1948 | /// <summary> | ||
1949 | /// This method is called whenever an Item has been successfully | ||
1950 | /// reconstituted from the request's entity. | ||
1951 | /// It uses the information curren tin the XmlInventoryCollection | ||
1952 | /// to complete the item's specification, including any implied | ||
1953 | /// context and asset associations. | ||
1954 | /// It fails the request if any necessary item or asset information | ||
1955 | /// is missing. | ||
1956 | /// </summary> | ||
1957 | |||
1958 | private void Validate(XmlInventoryCollection ic) | ||
1959 | { | ||
1960 | // There really should be an item present if we've | ||
1961 | // called validate. So fail if there is not. | ||
1962 | |||
1963 | if (ic.Item == null) | ||
1964 | { | ||
1965 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); | ||
1966 | ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); | ||
1967 | } | ||
1968 | |||
1969 | // Every item is required to have a name (via REST anyway) | ||
1970 | |||
1971 | if (ic.Item.Name == String.Empty) | ||
1972 | { | ||
1973 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); | ||
1974 | ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); | ||
1975 | } | ||
1976 | |||
1977 | // An item MUST have an asset ID. AssetID should never be zero | ||
1978 | // here. It should always get set from the information stored | ||
1979 | // when the Asset element was processed. | ||
1980 | |||
1981 | if (ic.Item.AssetID == UUID.Zero) | ||
1982 | { | ||
1983 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); | ||
1984 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); | ||
1985 | ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); | ||
1986 | } | ||
1987 | |||
1988 | // If the item is new, then assign it an ID | ||
1989 | |||
1990 | if (ic.Item.ID == UUID.Zero) | ||
1991 | { | ||
1992 | ic.Item.ID = UUID.Random(); | ||
1993 | } | ||
1994 | |||
1995 | // If the context is being implied, obtain the current | ||
1996 | // folder item's ID. If it was specified explicitly, make | ||
1997 | // sure that theparent folder exists. | ||
1998 | |||
1999 | if (ic.Item.Folder == UUID.Zero) | ||
2000 | { | ||
2001 | ic.Item.Folder = ic.Parent(); | ||
2002 | } | ||
2003 | else | ||
2004 | { | ||
2005 | bool found = false; | ||
2006 | |||
2007 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
2008 | { | ||
2009 | if (parent.ID == ic.Item.Folder) | ||
2010 | { | ||
2011 | found = true; | ||
2012 | break; | ||
2013 | } | ||
2014 | } | ||
2015 | |||
2016 | if (!found) | ||
2017 | { | ||
2018 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", | ||
2019 | MsgId, ic.Item.Folder, ic.Item.ID); | ||
2020 | ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); | ||
2021 | } | ||
2022 | } | ||
2023 | |||
2024 | // If this is an inline asset being constructed in the context | ||
2025 | // of a new Item, then use the itm's name here too. | ||
2026 | |||
2027 | if (ic.Asset != null) | ||
2028 | { | ||
2029 | if (ic.Asset.Name == String.Empty) | ||
2030 | ic.Asset.Name = ic.Item.Name; | ||
2031 | if (ic.Asset.Description == String.Empty) | ||
2032 | ic.Asset.Description = ic.Item.Description; | ||
2033 | } | ||
2034 | |||
2035 | // Assign permissions | ||
2036 | |||
2037 | ic.Item.CurrentPermissions = ic.CurrentPermissions; | ||
2038 | ic.Item.EveryOnePermissions = ic.EveryOnePermissions; | ||
2039 | ic.Item.BasePermissions = ic.BasePermissions; | ||
2040 | ic.Item.GroupPermissions = ic.GroupPermissions; | ||
2041 | ic.Item.NextPermissions = ic.NextPermissions; | ||
2042 | |||
2043 | // If no type was specified for this item, we can attempt to | ||
2044 | // infer something from the file type maybe. This is NOT as | ||
2045 | // good as having type be specified in the XML. | ||
2046 | |||
2047 | if (ic.Item.AssetType == (int) AssetType.Unknown || | ||
2048 | ic.Item.InvType == (int) InventoryType.Unknown) | ||
2049 | { | ||
2050 | Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); | ||
2051 | |||
2052 | string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); | ||
2053 | |||
2054 | if (Rest.DEBUG) | ||
2055 | { | ||
2056 | for (int i = 0; i < parts.Length; i++) | ||
2057 | { | ||
2058 | Rest.Log.DebugFormat("{0} Name part {1} : {2}", | ||
2059 | MsgId, i, parts[i]); | ||
2060 | } | ||
2061 | } | ||
2062 | |||
2063 | // If the associated item name is multi-part, then maybe | ||
2064 | // the last part will indicate the item type - if we're | ||
2065 | // lucky. | ||
2066 | |||
2067 | if (parts.Length > 1) | ||
2068 | { | ||
2069 | Rest.Log.DebugFormat("{0} File type is {1}", | ||
2070 | MsgId, parts[parts.Length - 1]); | ||
2071 | switch (parts[parts.Length - 1]) | ||
2072 | { | ||
2073 | case "jpeg2000" : | ||
2074 | case "jpeg-2000" : | ||
2075 | case "jpg2000" : | ||
2076 | case "jpg-2000" : | ||
2077 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
2078 | MsgId, parts[parts.Length-1]); | ||
2079 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2080 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
2081 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2082 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2083 | break; | ||
2084 | case "jpg" : | ||
2085 | case "jpeg" : | ||
2086 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
2087 | MsgId, parts[parts.Length - 1]); | ||
2088 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2089 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
2090 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2091 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2092 | break; | ||
2093 | case "tga" : | ||
2094 | if (parts[parts.Length - 2].IndexOf("_texture") != -1) | ||
2095 | { | ||
2096 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2097 | ic.Item.AssetType = (int) AssetType.TextureTGA; | ||
2098 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
2099 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2100 | } | ||
2101 | else | ||
2102 | { | ||
2103 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2104 | ic.Item.AssetType = (int) AssetType.ImageTGA; | ||
2105 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2106 | ic.Item.InvType = (int) InventoryType.Snapshot; | ||
2107 | } | ||
2108 | break; | ||
2109 | default : | ||
2110 | Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}", | ||
2111 | MsgId,ic.Item.Name); | ||
2112 | break; | ||
2113 | } | ||
2114 | } | ||
2115 | } | ||
2116 | |||
2117 | /// If this is a TGA remember the fact | ||
2118 | |||
2119 | if (ic.Item.AssetType == (int) AssetType.TextureTGA || | ||
2120 | ic.Item.AssetType == (int) AssetType.ImageTGA) | ||
2121 | { | ||
2122 | Bitmap temp; | ||
2123 | Stream tgadata = new MemoryStream(ic.Asset.Data); | ||
2124 | |||
2125 | temp = LoadTGAClass.LoadTGA(tgadata); | ||
2126 | try | ||
2127 | { | ||
2128 | ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true); | ||
2129 | } | ||
2130 | catch (DllNotFoundException) | ||
2131 | { | ||
2132 | Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", ic.Item.Name); | ||
2133 | ic.Asset.Data = new Byte[0]; | ||
2134 | } | ||
2135 | catch (IndexOutOfRangeException) | ||
2136 | { | ||
2137 | Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); | ||
2138 | ic.Asset.Data = new Byte[0]; | ||
2139 | } | ||
2140 | catch (Exception) | ||
2141 | { | ||
2142 | Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); | ||
2143 | ic.Asset.Data = new Byte[0]; | ||
2144 | } | ||
2145 | } | ||
2146 | |||
2147 | ic.reset(); | ||
2148 | } | ||
2149 | |||
2150 | #region Inventory RequestData extension | ||
2151 | |||
2152 | internal class InventoryRequestData : RequestData | ||
2153 | { | ||
2154 | /// <summary> | ||
2155 | /// These are the inventory specific request/response state | ||
2156 | /// extensions. | ||
2157 | /// </summary> | ||
2158 | |||
2159 | internal UUID uuid = UUID.Zero; | ||
2160 | internal bool HaveInventory = false; | ||
2161 | internal ICollection<InventoryFolderImpl> folders = null; | ||
2162 | internal ICollection<InventoryItemBase> items = null; | ||
2163 | internal UserProfileData userProfile = null; | ||
2164 | internal InventoryFolderBase root = null; | ||
2165 | internal bool timeout = false; | ||
2166 | internal Timer watchDog = new Timer(); | ||
2167 | |||
2168 | internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
2169 | : base(request, response, prefix) | ||
2170 | { | ||
2171 | } | ||
2172 | |||
2173 | internal void startWD(double interval) | ||
2174 | { | ||
2175 | Rest.Log.DebugFormat("{0} Setting watchdog", MsgId); | ||
2176 | watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut); | ||
2177 | watchDog.Interval = interval; | ||
2178 | watchDog.AutoReset = false; | ||
2179 | watchDog.Enabled = true; | ||
2180 | lock (watchDog) | ||
2181 | watchDog.Start(); | ||
2182 | |||
2183 | } | ||
2184 | |||
2185 | internal void stopWD() | ||
2186 | { | ||
2187 | Rest.Log.DebugFormat("{0} Reset watchdog", MsgId); | ||
2188 | lock (watchDog) | ||
2189 | watchDog.Stop(); | ||
2190 | } | ||
2191 | |||
2192 | /// <summary> | ||
2193 | /// This is the callback method required by the inventory watchdog. The | ||
2194 | /// requestor issues an inventory request and then blocks until the | ||
2195 | /// request completes, or this method signals the monitor. | ||
2196 | /// </summary> | ||
2197 | |||
2198 | private void OnTimeOut(object sender, ElapsedEventArgs args) | ||
2199 | { | ||
2200 | Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId); | ||
2201 | // InventoryRequestData rdata = (InventoryRequestData) sender; | ||
2202 | lock (this) | ||
2203 | { | ||
2204 | this.folders = null; | ||
2205 | this.items = null; | ||
2206 | this.HaveInventory = false; | ||
2207 | this.timeout = true; | ||
2208 | Monitor.Pulse(this); | ||
2209 | } | ||
2210 | } | ||
2211 | |||
2212 | /// <summary> | ||
2213 | /// This is the callback method required by inventory services. The | ||
2214 | /// requestor issues an inventory request and then blocks until this | ||
2215 | /// method signals the monitor. | ||
2216 | /// </summary> | ||
2217 | |||
2218 | internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items) | ||
2219 | { | ||
2220 | Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); | ||
2221 | lock (this) | ||
2222 | { | ||
2223 | if (watchDog.Enabled) | ||
2224 | { | ||
2225 | this.stopWD(); | ||
2226 | } | ||
2227 | this.folders = folders; | ||
2228 | this.items = items; | ||
2229 | this.HaveInventory = true; | ||
2230 | this.timeout = false; | ||
2231 | Monitor.Pulse(this); | ||
2232 | } | ||
2233 | } | ||
2234 | } | ||
2235 | |||
2236 | #endregion Inventory RequestData extension | ||
2237 | |||
2238 | /// <summary> | ||
2239 | /// This class is used to record and manage the hierarchy | ||
2240 | /// constructed from the entity supplied in the request for | ||
2241 | /// PUT and POST. | ||
2242 | /// </summary> | ||
2243 | |||
2244 | internal class XmlInventoryCollection : InventoryCollection | ||
2245 | { | ||
2246 | internal InventoryRequestData rdata; | ||
2247 | private Stack<InventoryFolderBase> stk; | ||
2248 | |||
2249 | internal List<AssetBase> Assets; | ||
2250 | |||
2251 | internal InventoryItemBase Item; | ||
2252 | internal AssetBase Asset; | ||
2253 | internal XmlReader xml; | ||
2254 | |||
2255 | internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF; | ||
2256 | internal /*static*/ const uint DefaultNext = 0x82000; | ||
2257 | internal /*static*/ const uint DefaultBase = 0x7FFFFFFF; | ||
2258 | internal /*static*/ const uint DefaultEveryOne = 0x0; | ||
2259 | internal /*static*/ const uint DefaultGroup = 0x0; | ||
2260 | |||
2261 | internal uint CurrentPermissions = 0x00; | ||
2262 | internal uint NextPermissions = 0x00; | ||
2263 | internal uint BasePermissions = 0x00; | ||
2264 | internal uint EveryOnePermissions = 0x00; | ||
2265 | internal uint GroupPermissions = 0x00; | ||
2266 | |||
2267 | internal XmlInventoryCollection() | ||
2268 | { | ||
2269 | Folders = new List<InventoryFolderBase>(); | ||
2270 | Items = new List<InventoryItemBase>(); | ||
2271 | Assets = new List<AssetBase>(); | ||
2272 | } | ||
2273 | |||
2274 | internal void init(InventoryRequestData p_rdata) | ||
2275 | { | ||
2276 | rdata = p_rdata; | ||
2277 | UserID = rdata.uuid; | ||
2278 | stk = new Stack<InventoryFolderBase>(); | ||
2279 | rdata.initXmlReader(); | ||
2280 | xml = rdata.reader; | ||
2281 | initPermissions(); | ||
2282 | } | ||
2283 | |||
2284 | internal void initPermissions() | ||
2285 | { | ||
2286 | CurrentPermissions = DefaultCurrent; | ||
2287 | NextPermissions = DefaultNext; | ||
2288 | BasePermissions = DefaultBase; | ||
2289 | GroupPermissions = DefaultGroup; | ||
2290 | EveryOnePermissions = DefaultEveryOne; | ||
2291 | } | ||
2292 | |||
2293 | internal UUID Parent() | ||
2294 | { | ||
2295 | if (stk.Count != 0) | ||
2296 | { | ||
2297 | return stk.Peek().ID; | ||
2298 | } | ||
2299 | else | ||
2300 | { | ||
2301 | return UUID.Zero; | ||
2302 | } | ||
2303 | } | ||
2304 | |||
2305 | internal void Push(InventoryFolderBase folder) | ||
2306 | { | ||
2307 | stk.Push(folder); | ||
2308 | Folders.Add(folder); | ||
2309 | reset(); | ||
2310 | } | ||
2311 | |||
2312 | internal void Push(InventoryItemBase item) | ||
2313 | { | ||
2314 | Item = item; | ||
2315 | Items.Add(item); | ||
2316 | } | ||
2317 | |||
2318 | internal void Push(AssetBase asset) | ||
2319 | { | ||
2320 | Asset = asset; | ||
2321 | Assets.Add(asset); | ||
2322 | } | ||
2323 | |||
2324 | internal void Pop() | ||
2325 | { | ||
2326 | stk.Pop(); | ||
2327 | reset(); | ||
2328 | } | ||
2329 | |||
2330 | internal void reset() | ||
2331 | { | ||
2332 | Item = null; | ||
2333 | Asset = null; | ||
2334 | initPermissions(); | ||
2335 | } | ||
2336 | |||
2337 | internal void Fail(int code, string addendum) | ||
2338 | { | ||
2339 | rdata.Fail(code, addendum); | ||
2340 | } | ||
2341 | } | ||
2342 | } | ||
2343 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs deleted file mode 100644 index 81596a3..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs +++ /dev/null | |||
@@ -1,246 +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 | |||
29 | using System; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Reflection; | ||
32 | using OpenSim.Framework.Servers; | ||
33 | using OpenSim.Framework.Servers.HttpServer; | ||
34 | |||
35 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
36 | { | ||
37 | public class RestTestServices : IRest | ||
38 | { | ||
39 | private bool enabled = false; | ||
40 | private string qPrefix = "test"; | ||
41 | |||
42 | // A simple constructor is used to handle any once-only | ||
43 | // initialization of working classes. | ||
44 | |||
45 | public RestTestServices() | ||
46 | { | ||
47 | Rest.Log.InfoFormat("{0} Test services initializing", MsgId); | ||
48 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
49 | |||
50 | // If a relative path was specified, make it absolute by adding | ||
51 | // the standard prefix, e.g. /admin | ||
52 | |||
53 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
54 | { | ||
55 | Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); | ||
56 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
57 | Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); | ||
58 | } | ||
59 | |||
60 | // Load test cases | ||
61 | |||
62 | loadTests(); | ||
63 | foreach (ITest test in tests) | ||
64 | { | ||
65 | test.Initialize(); | ||
66 | } | ||
67 | |||
68 | // Register interface | ||
69 | |||
70 | Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate); | ||
71 | |||
72 | // Activate | ||
73 | |||
74 | enabled = true; | ||
75 | |||
76 | Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId); | ||
77 | } | ||
78 | |||
79 | // Post-construction, pre-enabled initialization opportunity | ||
80 | // Not currently exploited. | ||
81 | |||
82 | public void Initialize() | ||
83 | { | ||
84 | } | ||
85 | |||
86 | // Called by the plug-in to halt REST processing. Local processing is | ||
87 | // disabled, and control blocks until all current processing has | ||
88 | // completed. No new processing will be started | ||
89 | |||
90 | public void Close() | ||
91 | { | ||
92 | enabled = false; | ||
93 | foreach (ITest test in tests) | ||
94 | { | ||
95 | test.Close(); | ||
96 | } | ||
97 | Rest.Log.InfoFormat("{0} Test services closing down", MsgId); | ||
98 | } | ||
99 | |||
100 | // Properties | ||
101 | |||
102 | internal string MsgId | ||
103 | { | ||
104 | get { return Rest.MsgId; } | ||
105 | } | ||
106 | |||
107 | #region Interface | ||
108 | |||
109 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
110 | { | ||
111 | return new RequestData(request, response, prefix); | ||
112 | } | ||
113 | |||
114 | // Inventory Handler | ||
115 | |||
116 | private void DoTests(RequestData rdata) | ||
117 | { | ||
118 | if (!enabled) | ||
119 | return; | ||
120 | |||
121 | // Now that we know this is a serious attempt to | ||
122 | // access inventory data, we should find out who | ||
123 | // is asking, and make sure they are authorized | ||
124 | // to do so. We need to validate the caller's | ||
125 | // identity before revealing anything about the | ||
126 | // status quo. Authenticate throws an exception | ||
127 | // via Fail if no identity information is present. | ||
128 | // | ||
129 | // With the present HTTP server we can't use the | ||
130 | // builtin authentication mechanisms because they | ||
131 | // would be enforced for all in-bound requests. | ||
132 | // Instead we look at the headers ourselves and | ||
133 | // handle authentication directly. | ||
134 | |||
135 | try | ||
136 | { | ||
137 | if (!rdata.IsAuthenticated) | ||
138 | { | ||
139 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, | ||
140 | String.Format("user \"{0}\" could not be authenticated", rdata.userName)); | ||
141 | } | ||
142 | } | ||
143 | catch (RestException e) | ||
144 | { | ||
145 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
146 | { | ||
147 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
148 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
149 | } | ||
150 | else | ||
151 | { | ||
152 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
153 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
154 | } | ||
155 | throw (e); | ||
156 | } | ||
157 | |||
158 | // Check that a test was specified | ||
159 | |||
160 | if (rdata.Parameters.Length < 1) | ||
161 | { | ||
162 | Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); | ||
163 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters"); | ||
164 | } | ||
165 | |||
166 | // Select the test | ||
167 | |||
168 | foreach (ITest test in tests) | ||
169 | { | ||
170 | if (!rdata.handled) | ||
171 | test.Execute(rdata); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | #endregion Interface | ||
176 | |||
177 | private static bool testsLoaded = false; | ||
178 | private static List<Type> classes = new List<Type>(); | ||
179 | private static List<ITest> tests = new List<ITest>(); | ||
180 | private static Type[] parms = new Type[0]; | ||
181 | private static Object[] args = new Object[0]; | ||
182 | |||
183 | static RestTestServices() | ||
184 | { | ||
185 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); | ||
186 | foreach (Module m in mods) | ||
187 | { | ||
188 | Type[] types = m.GetTypes(); | ||
189 | foreach (Type t in types) | ||
190 | { | ||
191 | try | ||
192 | { | ||
193 | if (t.GetInterface("ITest") != null) | ||
194 | { | ||
195 | classes.Add(t); | ||
196 | } | ||
197 | } | ||
198 | catch (Exception e) | ||
199 | { | ||
200 | Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message); | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | /// <summary> | ||
207 | /// This routine loads all of the handlers discovered during | ||
208 | /// instance initialization. Each handler is responsible for | ||
209 | /// registering itself with this handler. | ||
210 | /// I was not able to make this code work in a constructor. | ||
211 | /// </summary> | ||
212 | |||
213 | private void loadTests() | ||
214 | { | ||
215 | lock (tests) | ||
216 | { | ||
217 | if (!testsLoaded) | ||
218 | { | ||
219 | |||
220 | ConstructorInfo ci; | ||
221 | Object ht; | ||
222 | |||
223 | foreach (Type t in classes) | ||
224 | { | ||
225 | try | ||
226 | { | ||
227 | if (t.GetInterface("ITest") != null) | ||
228 | { | ||
229 | ci = t.GetConstructor(parms); | ||
230 | ht = ci.Invoke(args); | ||
231 | tests.Add((ITest)ht); | ||
232 | Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t); | ||
233 | } | ||
234 | } | ||
235 | catch (Exception e) | ||
236 | { | ||
237 | Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message); | ||
238 | } | ||
239 | } | ||
240 | testsLoaded = true; | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | |||
245 | } | ||
246 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs deleted file mode 100644 index eafc154..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs +++ /dev/null | |||
@@ -1,46 +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 | |||
29 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
30 | { | ||
31 | |||
32 | /// <summary> | ||
33 | /// This interface represents the boundary between the general purpose | ||
34 | /// REST plugin handling, and the functionally specific handlers. The | ||
35 | /// handler knows only to initialzie and terminate all such handlers | ||
36 | /// that it finds. | ||
37 | /// </summary> | ||
38 | |||
39 | internal interface ITest | ||
40 | { | ||
41 | void Initialize(); | ||
42 | void Execute(RequestData rdata); | ||
43 | void Close(); | ||
44 | } | ||
45 | |||
46 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs deleted file mode 100644 index 1c1afd0..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs +++ /dev/null | |||
@@ -1,204 +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 OpenMetaverse; | ||
30 | using OpenSim.Region.Framework.Scenes; | ||
31 | |||
32 | namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests | ||
33 | { | ||
34 | public class Remote : ITest | ||
35 | { | ||
36 | private static readonly int PARM_TESTID = 0; | ||
37 | private static readonly int PARM_COMMAND = 1; | ||
38 | |||
39 | private static readonly int PARM_MOVE_AVATAR = 2; | ||
40 | private static readonly int PARM_MOVE_X = 3; | ||
41 | private static readonly int PARM_MOVE_Y = 4; | ||
42 | private static readonly int PARM_MOVE_Z = 5; | ||
43 | |||
44 | private bool enabled = false; | ||
45 | |||
46 | // No constructor code is required. | ||
47 | |||
48 | public Remote() | ||
49 | { | ||
50 | Rest.Log.InfoFormat("{0} Remote services constructor", MsgId); | ||
51 | } | ||
52 | |||
53 | // Post-construction, pre-enabled initialization opportunity | ||
54 | // Not currently exploited. | ||
55 | |||
56 | public void Initialize() | ||
57 | { | ||
58 | enabled = true; | ||
59 | Rest.Log.InfoFormat("{0} Remote services initialized", MsgId); | ||
60 | } | ||
61 | |||
62 | // Called by the plug-in to halt REST processing. Local processing is | ||
63 | // disabled, and control blocks until all current processing has | ||
64 | // completed. No new processing will be started | ||
65 | |||
66 | public void Close() | ||
67 | { | ||
68 | enabled = false; | ||
69 | Rest.Log.InfoFormat("{0} Remote services closing down", MsgId); | ||
70 | } | ||
71 | |||
72 | // Properties | ||
73 | |||
74 | internal string MsgId | ||
75 | { | ||
76 | get { return Rest.MsgId; } | ||
77 | } | ||
78 | |||
79 | // Remote Handler | ||
80 | // Key information of interest here is the Parameters array, each | ||
81 | // entry represents an element of the URI, with element zero being | ||
82 | // the | ||
83 | |||
84 | public void Execute(RequestData rdata) | ||
85 | { | ||
86 | if (!enabled) return; | ||
87 | |||
88 | // If we can't relate to what's there, leave it for others. | ||
89 | |||
90 | if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote") | ||
91 | return; | ||
92 | |||
93 | Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId); | ||
94 | |||
95 | // Remove the prefix and what's left are the parameters. If we don't have | ||
96 | // the parameters we need, fail the request. Parameters do NOT include | ||
97 | // any supplied query values. | ||
98 | |||
99 | if (rdata.Parameters.Length > 1) | ||
100 | { | ||
101 | switch (rdata.Parameters[PARM_COMMAND].ToLower()) | ||
102 | { | ||
103 | case "move" : | ||
104 | DoMove(rdata); | ||
105 | break; | ||
106 | default : | ||
107 | DoHelp(rdata); | ||
108 | break; | ||
109 | } | ||
110 | } | ||
111 | else | ||
112 | { | ||
113 | DoHelp(rdata); | ||
114 | } | ||
115 | } | ||
116 | |||
117 | private void DoHelp(RequestData rdata) | ||
118 | { | ||
119 | rdata.body = Help; | ||
120 | rdata.Complete(); | ||
121 | rdata.Respond("Help"); | ||
122 | } | ||
123 | |||
124 | private void DoMove(RequestData rdata) | ||
125 | { | ||
126 | if (rdata.Parameters.Length < 6) | ||
127 | { | ||
128 | Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId); | ||
129 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided"); | ||
130 | } | ||
131 | else | ||
132 | { | ||
133 | string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE); | ||
134 | ScenePresence presence = null; | ||
135 | Scene scene = null; | ||
136 | |||
137 | if (names.Length != 2) | ||
138 | { | ||
139 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
140 | String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR])); | ||
141 | } | ||
142 | |||
143 | Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}", | ||
144 | MsgId, rdata.Parameters[0], names[0], names[1]); | ||
145 | |||
146 | // The first parameter should be an avatar name, look for the | ||
147 | // avatar in the known regions first. | ||
148 | |||
149 | Rest.main.SceneManager.ForEachScene(delegate(Scene s) | ||
150 | { | ||
151 | s.ForEachRootScenePresence(delegate(ScenePresence sp) | ||
152 | { | ||
153 | if (sp.Firstname == names[0] && sp.Lastname == names[1]) | ||
154 | { | ||
155 | scene = s; | ||
156 | presence = sp; | ||
157 | } | ||
158 | }); | ||
159 | }); | ||
160 | |||
161 | if (presence != null) | ||
162 | { | ||
163 | Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}", | ||
164 | MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName); | ||
165 | |||
166 | try | ||
167 | { | ||
168 | float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]); | ||
169 | float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]); | ||
170 | float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]); | ||
171 | Vector3 vector = new Vector3(x, y, z); | ||
172 | presence.MoveToTarget(vector, false, false); | ||
173 | } | ||
174 | catch (Exception e) | ||
175 | { | ||
176 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
177 | String.Format("invalid parameters: {0}", e.Message)); | ||
178 | } | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
183 | String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR])); | ||
184 | } | ||
185 | |||
186 | rdata.Complete(); | ||
187 | rdata.Respond("OK"); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | private static readonly string Help = | ||
192 | "<html>" | ||
193 | + "<head><title>Remote Command Usage</title></head>" | ||
194 | + "<body>" | ||
195 | + "<p>Supported commands are:</p>" | ||
196 | + "<dl>" | ||
197 | + "<dt>move/avatar-name/x/y/z</dt>" | ||
198 | + "<dd>moves the specified avatar to another location</dd>" | ||
199 | + "</dl>" | ||
200 | + "</body>" | ||
201 | + "</html>" | ||
202 | ; | ||
203 | } | ||
204 | } | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs index 8b43d42..6417663 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs +++ b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs | |||
@@ -25,19 +25,24 @@ | |||
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 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 28 | using System; |
29 | |||
30 | namespace OpenSim.Framework.Console | ||
29 | { | 31 | { |
30 | /// <summary> | 32 | /// <summary> |
31 | /// This interface represents the boundary between the general purpose | 33 | /// This will be a set of typical column sizes to allow greater consistency between console commands. |
32 | /// REST plugin handling, and the functionally specific handlers. The | ||
33 | /// handler knows only to initialize and terminate all such handlers | ||
34 | /// that it finds. Implementing this interface identifies the class as | ||
35 | /// a REST handler implementation. | ||
36 | /// </summary> | 34 | /// </summary> |
37 | 35 | public static class ConsoleDisplayUtil | |
38 | internal interface IRest | ||
39 | { | 36 | { |
40 | void Initialize(); | 37 | public const int CoordTupleSize = 11; |
41 | void Close(); | 38 | public const int PortSize = 5; |
39 | |||
40 | public const int EstateNameSize = 20; | ||
41 | public const int ParcelNameSize = 40; | ||
42 | public const int RegionNameSize = 20; | ||
43 | public const int UserNameSize = 35; | ||
44 | |||
45 | public const int UuidSize = 36; | ||
46 | public const int VectorSize = 15; | ||
42 | } | 47 | } |
43 | } | 48 | } \ No newline at end of file |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs b/OpenSim/Server/Handlers/AgentPreferences/AgentPreferencesServiceConnector.cs index a88fe88..a581ea2 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs +++ b/OpenSim/Server/Handlers/AgentPreferences/AgentPreferencesServiceConnector.cs | |||
@@ -25,35 +25,40 @@ | |||
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 | |||
29 | using System; | ||
30 | using Nini.Config; | ||
31 | using OpenSim.Server.Base; | ||
32 | using OpenSim.Services.Interfaces; | ||
33 | using OpenSim.Framework.ServiceAuth; | ||
28 | using OpenSim.Framework.Servers.HttpServer; | 34 | using OpenSim.Framework.Servers.HttpServer; |
35 | using OpenSim.Server.Handlers.Base; | ||
29 | 36 | ||
30 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 37 | namespace OpenSim.Server.Handlers.AgentPreferences |
31 | { | 38 | { |
39 | public class AgentPreferencesServiceConnector : ServiceConnector | ||
40 | { | ||
41 | private IAgentPreferencesService m_AgentPreferencesService; | ||
42 | private string m_ConfigName = "AgentPreferencesService"; | ||
32 | 43 | ||
33 | /// <remarks> | 44 | public AgentPreferencesServiceConnector(IConfigSource config, IHttpServer server, string configName) : |
34 | /// The handler delegates are not noteworthy. The allocator allows | 45 | base(config, server, configName) |
35 | /// a given handler to optionally subclass the base RequestData | 46 | { |
36 | /// structure to carry any locally required per-request state | 47 | IConfig serverConfig = config.Configs[m_ConfigName]; |
37 | /// needed. | 48 | if (serverConfig == null) |
38 | /// </remarks> | 49 | throw new Exception(String.Format("No section {0} in config file", m_ConfigName)); |
39 | 50 | ||
40 | public delegate void RestMethodHandler(RequestData rdata); | 51 | string service = serverConfig.GetString("LocalServiceModule", String.Empty); |
41 | public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path); | ||
42 | 52 | ||
43 | /// <summary> | 53 | if (String.IsNullOrWhiteSpace(service)) |
44 | /// This interface exports the generic plugin-handling services | 54 | throw new Exception("No LocalServiceModule in config file"); |
45 | /// available to each loaded REST services module (IRest implementation) | ||
46 | /// </summary> | ||
47 | 55 | ||
48 | internal interface IRestHandler | 56 | Object[] args = new Object[] { config }; |
49 | { | 57 | m_AgentPreferencesService = ServerUtils.LoadPlugin<IAgentPreferencesService>(service, args); |
50 | 58 | ||
51 | string MsgId { get; } | 59 | IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName); ; |
52 | string RequestId { get; } | ||
53 | |||
54 | void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma); | ||
55 | void AddStreamHandler(string httpMethod, string path, RestMethod method); | ||
56 | 60 | ||
61 | server.AddStreamHandler(new AgentPreferencesServerPostHandler(m_AgentPreferencesService, auth)); | ||
62 | } | ||
57 | } | 63 | } |
58 | |||
59 | } | 64 | } |