diff options
Diffstat (limited to 'OpenSim')
6 files changed, 1573 insertions, 895 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs index d08f830..6742402 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs | |||
@@ -23,6 +23,7 @@ | |||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | ||
26 | */ | 27 | */ |
27 | 28 | ||
28 | using System; | 29 | using System; |
@@ -40,16 +41,17 @@ using System.Xml; | |||
40 | 41 | ||
41 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 42 | namespace OpenSim.ApplicationPlugins.Rest.Inventory |
42 | { | 43 | { |
44 | |||
43 | /// <summary> | 45 | /// <summary> |
44 | /// This class represents the current REST request. It | 46 | /// This class represents the current REST request. It |
45 | /// encapsulates the request/response state and takes care | 47 | /// encapsulates the request/response state and takes care |
46 | /// of response generation without exposing the REST handler | 48 | /// of response generation without exposing the REST handler |
47 | /// to the actual mechanisms involved. | 49 | /// to the actual mechanisms involved. |
48 | /// | 50 | /// |
49 | /// This structure is created on entry to the Handler | 51 | /// This structure is created on entry to the Handler |
50 | /// method and is disposed of upon return. It is part of | 52 | /// method and is disposed of upon return. It is part of |
51 | /// the plug-in infrastructure, rather than the functionally | 53 | /// the plug-in infrastructure, rather than the functionally |
52 | /// specific REST handler, and fundamental changes to | 54 | /// specific REST handler, and fundamental changes to |
53 | /// this should be reflected in the Rest HandlerVersion. The | 55 | /// this should be reflected in the Rest HandlerVersion. The |
54 | /// object is instantiated, and may be extended by, any | 56 | /// object is instantiated, and may be extended by, any |
55 | /// given handler. See the inventory handler for an example | 57 | /// given handler. See the inventory handler for an example |
@@ -63,44 +65,109 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
63 | /// of it as a transactional req/resp capability. | 65 | /// of it as a transactional req/resp capability. |
64 | /// </summary> | 66 | /// </summary> |
65 | 67 | ||
66 | internal class RequestData | 68 | public class RequestData |
67 | { | 69 | { |
68 | // HTTP Server interface data | 70 | |
71 | // HTTP Server interface data (Received values) | ||
69 | 72 | ||
70 | internal OSHttpRequest request = null; | 73 | internal OSHttpRequest request = null; |
71 | internal OSHttpResponse response = null; | 74 | internal OSHttpResponse response = null; |
72 | internal string qprefix = null; | 75 | internal string qprefix = null; |
73 | 76 | ||
74 | // Request lifetime values | 77 | // Request lifetime values |
78 | // buffer is global because it is referenced by the handler | ||
79 | // in supported of streamed requests. | ||
80 | // If a service provider wants to construct the message | ||
81 | // body explicitly it can use body to do this. The value | ||
82 | // in body is used if the buffer is still null when a response | ||
83 | // is generated. | ||
84 | // Storing information in body will suppress the return of | ||
85 | // statusBody which is only intended to report status on | ||
86 | // requests which do not themselves ordinarily generate | ||
87 | // an informational response. All of this is handled in | ||
88 | // Respond(). | ||
75 | 89 | ||
76 | internal byte[] buffer = null; | 90 | internal byte[] buffer = null; |
77 | internal string body = null; | 91 | internal string body = null; |
78 | internal string html = null; | 92 | internal string bodyType = "text/html"; |
79 | internal string entity = null; | 93 | |
94 | // The encoding in effect is set to a server default. It may | ||
95 | // subsequently be overridden by a Content header. This | ||
96 | // value is established during construction and is used | ||
97 | // wherever encoding services are needed. | ||
98 | |||
99 | internal Encoding encoding = Rest.Encoding; | ||
100 | |||
101 | // These values are derived from the supplied URL. They | ||
102 | // are initialized during construction. | ||
103 | |||
80 | internal string path = null; | 104 | internal string path = null; |
81 | internal string method = null; | 105 | internal string method = null; |
82 | internal string statusDescription = null; | ||
83 | internal string redirectLocation = null; | ||
84 | internal string[] pathNodes = null; | ||
85 | internal string[] parameters = null; | ||
86 | internal int statusCode = 0; | ||
87 | internal bool handled = false; | ||
88 | internal LLUUID uuid = LLUUID.Zero; | ||
89 | internal Encoding encoding = Rest.Encoding; | ||
90 | internal Uri uri = null; | 106 | internal Uri uri = null; |
91 | internal string query = null; | 107 | internal string query = null; |
92 | internal bool fail = false; | ||
93 | internal string hostname = "localhost"; | 108 | internal string hostname = "localhost"; |
94 | internal int port = 80; | 109 | internal int port = 80; |
95 | internal string prefix = Rest.UrlPathSeparator; | 110 | |
111 | // The path part of the URI is decomposed. pathNodes | ||
112 | // is an array of every element in the URI. Parameters | ||
113 | // is an array that contains only those nodes that | ||
114 | // are not a part of the authority prefix | ||
115 | |||
116 | private string[] pathNodes = null; | ||
117 | private string[] parameters = null; | ||
118 | private static readonly string[] EmptyPath = { String.Empty }; | ||
119 | |||
120 | // The status code gets set during the course of processing | ||
121 | // and is the HTTP completion code. The status body is | ||
122 | // initialized during construction, is appended to during the | ||
123 | // course of execution, and is finalized during Respond | ||
124 | // processing. | ||
125 | // | ||
126 | // Fail processing marks the request as failed and this is | ||
127 | // then used to inhibit processing during Response processing. | ||
128 | |||
129 | internal int statusCode = 0; | ||
130 | internal string statusBody = String.Empty; | ||
131 | internal bool fail = false; | ||
132 | |||
133 | // This carries the URL to which the client should be redirected. | ||
134 | // It is set by the service provider using the Redirect call. | ||
135 | |||
136 | internal string redirectLocation = null; | ||
137 | |||
138 | // These values influence response processing. They can be set by | ||
139 | // service providers according to need. The defaults are generally | ||
140 | // good. | ||
141 | |||
96 | internal bool keepAlive = false; | 142 | internal bool keepAlive = false; |
97 | internal bool chunked = false; | 143 | internal bool chunked = false; |
98 | 144 | ||
99 | // Authentication related state | 145 | // XML related state |
100 | 146 | ||
147 | internal XmlWriter writer = null; | ||
148 | internal XmlReader reader = null; | ||
149 | |||
150 | // Internal working state | ||
151 | |||
152 | private StringBuilder sbuilder = new StringBuilder(1024); | ||
153 | private MemoryStream xmldata = null; | ||
154 | |||
155 | // This is used to make the response mechanism idempotent. | ||
156 | |||
157 | internal bool handled = false; | ||
158 | |||
159 | // Authentication related state | ||
160 | // | ||
161 | // Two supported authentication mechanisms are: | ||
162 | // scheme = Rest.AS_BASIC; | ||
163 | // scheme = Rest.AS_DIGEST; | ||
164 | // Presented in that order (as required by spec) | ||
165 | // A service provider can set the scheme variable to | ||
166 | // force selection of a particular authentication model | ||
167 | // (choosing from amongst those supported of course) | ||
168 | // | ||
169 | |||
101 | internal bool authenticated = false; | 170 | internal bool authenticated = false; |
102 | // internal string scheme = Rest.AS_DIGEST; | ||
103 | // internal string scheme = Rest.AS_BASIC; | ||
104 | internal string scheme = null; | 171 | internal string scheme = null; |
105 | internal string realm = Rest.Realm; | 172 | internal string realm = Rest.Realm; |
106 | internal string domain = null; | 173 | internal string domain = null; |
@@ -114,22 +181,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
114 | internal string authPrefix = null; | 181 | internal string authPrefix = null; |
115 | internal string userName = String.Empty; | 182 | internal string userName = String.Empty; |
116 | internal string userPass = String.Empty; | 183 | internal string userPass = String.Empty; |
117 | internal LLUUID client = LLUUID.Zero; | ||
118 | |||
119 | // XML related state | ||
120 | |||
121 | internal XmlWriter writer = null; | ||
122 | internal XmlReader reader = null; | ||
123 | |||
124 | // Internal working state | ||
125 | |||
126 | private StringBuilder sbuilder = new StringBuilder(1024); | ||
127 | private MemoryStream xmldata = null; | ||
128 | |||
129 | private static readonly string[] EmptyPath = { String.Empty }; | ||
130 | 184 | ||
131 | // Session related tables. These are only needed if QOP is set to "auth-sess" | 185 | // Session related tables. These are only needed if QOP is set to "auth-sess" |
132 | // and for now at least, it is not. Session related authentication is of | 186 | // and for now at least, it is not. Session related authentication is of |
133 | // questionable merit in the context of REST anyway, but it is, arguably, more | 187 | // questionable merit in the context of REST anyway, but it is, arguably, more |
134 | // secure. | 188 | // secure. |
135 | 189 | ||
@@ -145,46 +199,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
145 | 199 | ||
146 | private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*", | 200 | private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*", |
147 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 201 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
148 | 202 | ||
149 | private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", | 203 | private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", |
150 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 204 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
151 | 205 | ||
152 | private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", | 206 | private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", |
153 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 207 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
154 | 208 | ||
155 | private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", | 209 | private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", |
156 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 210 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
157 | 211 | ||
158 | private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)", | 212 | private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)", |
159 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 213 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
160 | 214 | ||
161 | // For efficiency, we create static instances of these objects | 215 | // For efficiency, we create static instances of these objects |
162 | 216 | ||
163 | private static MD5 md5hash = MD5.Create(); | 217 | private static MD5 md5hash = MD5.Create(); |
164 | 218 | ||
165 | private static StringComparer sc = StringComparer.OrdinalIgnoreCase; | 219 | private static StringComparer sc = StringComparer.OrdinalIgnoreCase; |
166 | 220 | ||
167 | // Constructor | 221 | #region properties |
168 | |||
169 | internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix) | ||
170 | { | ||
171 | request = p_request; | ||
172 | response = p_response; | ||
173 | qprefix = p_qprefix; | ||
174 | |||
175 | sbuilder.Length = 0; | ||
176 | |||
177 | encoding = request.ContentEncoding; | ||
178 | if (encoding == null) | ||
179 | { | ||
180 | encoding = Rest.Encoding; | ||
181 | } | ||
182 | |||
183 | method = request.HttpMethod.ToLower(); | ||
184 | initUrl(); | ||
185 | |||
186 | initParameters(p_qprefix.Length); | ||
187 | } | ||
188 | 222 | ||
189 | // Just for convenience... | 223 | // Just for convenience... |
190 | 224 | ||
@@ -193,12 +227,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
193 | get { return Rest.MsgId; } | 227 | get { return Rest.MsgId; } |
194 | } | 228 | } |
195 | 229 | ||
196 | // Defer authentication check until requested | 230 | /// <summary> |
231 | /// Return a boolean indication of whether or no an authenticated user is | ||
232 | /// associated with this request. This could be wholly integrated, but | ||
233 | /// that would make authentication mandatory. | ||
234 | /// </summary> | ||
197 | 235 | ||
198 | internal bool IsAuthenticated | 236 | internal bool IsAuthenticated |
199 | { | 237 | { |
200 | get | 238 | get |
201 | { | 239 | { |
202 | if (Rest.Authenticate) | 240 | if (Rest.Authenticate) |
203 | { | 241 | { |
204 | if (!authenticated) | 242 | if (!authenticated) |
@@ -208,24 +246,87 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
208 | 246 | ||
209 | return authenticated; | 247 | return authenticated; |
210 | } | 248 | } |
211 | else | 249 | else return true; |
212 | return true; | ||
213 | } | 250 | } |
214 | } | 251 | } |
215 | 252 | ||
216 | /// <summary> | 253 | /// <summary> |
254 | /// Access to all 'nodes' in the supplied URI as an | ||
255 | /// array of strings. | ||
256 | /// </summary> | ||
257 | |||
258 | internal string[] PathNodes | ||
259 | { | ||
260 | get | ||
261 | { | ||
262 | return pathNodes; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | /// <summary> | ||
267 | /// Access to all non-prefix 'nodes' in the supplied URI as an | ||
268 | /// array of strings. These identify a specific resource that | ||
269 | /// is managed by the authority (the prefix). | ||
270 | /// </summary> | ||
271 | |||
272 | internal string[] Parameters | ||
273 | { | ||
274 | get | ||
275 | { | ||
276 | return parameters; | ||
277 | } | ||
278 | } | ||
279 | |||
280 | #endregion properties | ||
281 | |||
282 | #region constructors | ||
283 | |||
284 | // Constructor | ||
285 | |||
286 | internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix) | ||
287 | { | ||
288 | |||
289 | request = p_request; | ||
290 | response = p_response; | ||
291 | qprefix = p_qprefix; | ||
292 | |||
293 | sbuilder.Length = 0; | ||
294 | |||
295 | encoding = request.ContentEncoding; | ||
296 | if (encoding == null) | ||
297 | { | ||
298 | encoding = Rest.Encoding; | ||
299 | } | ||
300 | |||
301 | method = request.HttpMethod.ToLower(); | ||
302 | initUrl(); | ||
303 | |||
304 | initParameters(p_qprefix.Length); | ||
305 | |||
306 | } | ||
307 | |||
308 | #endregion constructors | ||
309 | |||
310 | #region authentication_common | ||
311 | |||
312 | /// <summary> | ||
217 | /// The REST handler has requested authentication. Authentication | 313 | /// The REST handler has requested authentication. Authentication |
218 | /// is considered to be with respect to the current values for | 314 | /// is considered to be with respect to the current values for |
219 | /// Realm, domain, etc. | 315 | /// Realm, domain, etc. |
220 | /// | 316 | /// |
221 | /// This method checks to see if the current request is already | 317 | /// This method checks to see if the current request is already |
222 | /// authenticated for this domain. If it is, then it returns | 318 | /// authenticated for this domain. If it is, then it returns |
223 | /// true. If it is not, then it issues a challenge to the client | 319 | /// true. If it is not, then it issues a challenge to the client |
224 | /// and responds negatively to the request. | 320 | /// and responds negatively to the request. |
321 | /// | ||
322 | /// As soon as authentication failure is detected the method calls | ||
323 | /// DoChallenge() which terminates the request with REST exception | ||
324 | /// for unauthroized access. | ||
225 | /// </summary> | 325 | /// </summary> |
226 | 326 | ||
227 | private void authenticate() | 327 | private void authenticate() |
228 | { | 328 | { |
329 | |||
229 | string authdata = request.Headers.Get("Authorization"); | 330 | string authdata = request.Headers.Get("Authorization"); |
230 | string reqscheme = String.Empty; | 331 | string reqscheme = String.Empty; |
231 | 332 | ||
@@ -238,7 +339,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
238 | Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); | 339 | Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); |
239 | DoChallenge(); | 340 | DoChallenge(); |
240 | } | 341 | } |
241 | 342 | ||
242 | // So, we have authentication data, now we have to check to | 343 | // So, we have authentication data, now we have to check to |
243 | // see what we got and whether or not it is valid for the | 344 | // see what we got and whether or not it is valid for the |
244 | // current domain. To do this we need to interpret the data | 345 | // current domain. To do this we need to interpret the data |
@@ -287,6 +388,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
287 | Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); | 388 | Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); |
288 | DoChallenge(); | 389 | DoChallenge(); |
289 | } | 390 | } |
391 | |||
290 | } | 392 | } |
291 | 393 | ||
292 | /// <summary> | 394 | /// <summary> |
@@ -300,16 +402,193 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
300 | Flush(); | 402 | Flush(); |
301 | nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) | 403 | nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) |
302 | Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); | 404 | Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); |
303 | Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); | 405 | Fail(Rest.HttpStatusCodeNotAuthorized); |
304 | } | 406 | } |
305 | 407 | ||
306 | /// <summary> | 408 | /// <summary> |
307 | /// Interpret a BASIC authorization claim | 409 | /// The Flush() call is here to support a problem encountered with the |
308 | /// This is here for completeness, it is not used. | 410 | /// client where an authentication rejection was lost because the rejection |
411 | /// may flow before the clienthas finished sending us the inbound data stream, | ||
412 | /// in which case the client responds to the socket error on out put, and | ||
413 | /// never sees the authentication challenge. The client should be fixed, | ||
414 | /// because this solution leaves the server prone to DOS attacks. A message | ||
415 | /// will be issued whenever flushing occurs. It can be enabled/disabled from | ||
416 | /// the configuration file. | ||
417 | /// </summary> | ||
418 | |||
419 | private void Flush() | ||
420 | { | ||
421 | if (Rest.FlushEnabled) | ||
422 | { | ||
423 | byte[] dbuffer = new byte[8192]; | ||
424 | Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId); | ||
425 | while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0); | ||
426 | } | ||
427 | return; | ||
428 | } | ||
429 | |||
430 | // Indicate that authentication is required | ||
431 | |||
432 | private void Challenge(string scheme, string realm, string domain, string nonce, | ||
433 | string opaque, string stale, string alg, | ||
434 | string qop, string auth) | ||
435 | { | ||
436 | |||
437 | sbuilder.Length = 0; | ||
438 | |||
439 | // The service provider can force a particular scheme by | ||
440 | // assigning a value to scheme. | ||
441 | |||
442 | // Basic authentication is pretty simple. | ||
443 | // Just specify the realm in question. | ||
444 | |||
445 | if (scheme == null || scheme == Rest.AS_BASIC) | ||
446 | { | ||
447 | |||
448 | sbuilder.Append(Rest.AS_BASIC); | ||
449 | |||
450 | if (realm != null) | ||
451 | { | ||
452 | sbuilder.Append(" realm=\""); | ||
453 | sbuilder.Append(realm); | ||
454 | sbuilder.Append("\""); | ||
455 | } | ||
456 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
457 | } | ||
458 | |||
459 | sbuilder.Length = 0; | ||
460 | |||
461 | // Digest authentication takes somewhat more | ||
462 | // to express. | ||
463 | |||
464 | if (scheme == null || scheme == Rest.AS_DIGEST) | ||
465 | { | ||
466 | |||
467 | sbuilder.Append(Rest.AS_DIGEST); | ||
468 | sbuilder.Append(" "); | ||
469 | |||
470 | // Specify the effective realm. This should | ||
471 | // never be null if we are uthenticating, as it is required for all | ||
472 | // authentication schemes. It defines, in conjunction with the | ||
473 | // absolute URI information, the domain to which the authentication | ||
474 | // applies. It is an arbitrary string. I *believe* this allows an | ||
475 | // authentication to apply to disjoint resources within the same | ||
476 | // server. | ||
477 | |||
478 | if (realm != null) | ||
479 | { | ||
480 | sbuilder.Append("realm="); | ||
481 | sbuilder.Append(Rest.CS_DQUOTE); | ||
482 | sbuilder.Append(realm); | ||
483 | sbuilder.Append(Rest.CS_DQUOTE); | ||
484 | sbuilder.Append(Rest.CS_COMMA); | ||
485 | } | ||
486 | |||
487 | // Share our nonce. This is *uniquely* generated each time a 401 is | ||
488 | // returned. We do not generate a very sophisticated nonce at the | ||
489 | // moment (it's simply a base64 encoded UUID). | ||
490 | |||
491 | if (nonce != null) | ||
492 | { | ||
493 | sbuilder.Append("nonce="); | ||
494 | sbuilder.Append(Rest.CS_DQUOTE); | ||
495 | sbuilder.Append(nonce); | ||
496 | sbuilder.Append(Rest.CS_DQUOTE); | ||
497 | sbuilder.Append(Rest.CS_COMMA); | ||
498 | } | ||
499 | |||
500 | // The opaque string should be returned by the client unchanged in all | ||
501 | // subsequent requests. | ||
502 | |||
503 | if (opaque != null) | ||
504 | { | ||
505 | sbuilder.Append("opaque="); | ||
506 | sbuilder.Append(Rest.CS_DQUOTE); | ||
507 | sbuilder.Append(opaque); | ||
508 | sbuilder.Append(Rest.CS_DQUOTE); | ||
509 | sbuilder.Append(Rest.CS_COMMA); | ||
510 | } | ||
511 | |||
512 | // This flag indicates that the authentication was rejected because the | ||
513 | // included nonce was stale. The server might use timestamp information | ||
514 | // in the nonce to determine this. We do not. | ||
515 | |||
516 | if (stale != null) | ||
517 | { | ||
518 | sbuilder.Append("stale="); | ||
519 | sbuilder.Append(Rest.CS_DQUOTE); | ||
520 | sbuilder.Append(stale); | ||
521 | sbuilder.Append(Rest.CS_DQUOTE); | ||
522 | sbuilder.Append(Rest.CS_COMMA); | ||
523 | } | ||
524 | |||
525 | // Identifies the algorithm used to produce the digest and checksum. | ||
526 | // The default is MD5. | ||
527 | |||
528 | if (alg != null) | ||
529 | { | ||
530 | sbuilder.Append("algorithm="); | ||
531 | sbuilder.Append(alg); | ||
532 | sbuilder.Append(Rest.CS_COMMA); | ||
533 | } | ||
534 | |||
535 | // Theoretically QOP is optional, but it is required by a compliant | ||
536 | // with current versions of the scheme. In fact IE requires that QOP | ||
537 | // be specified and will refuse to authenticate otherwise. | ||
538 | |||
539 | if (qop != String.Empty) | ||
540 | { | ||
541 | sbuilder.Append("qop="); | ||
542 | sbuilder.Append(Rest.CS_DQUOTE); | ||
543 | sbuilder.Append(qop); | ||
544 | sbuilder.Append(Rest.CS_DQUOTE); | ||
545 | sbuilder.Append(Rest.CS_COMMA); | ||
546 | } | ||
547 | |||
548 | // This parameter allows for arbitrary extensions to the protocol. | ||
549 | // Unrecognized values should be simply ignored. | ||
550 | |||
551 | if (auth != null) | ||
552 | { | ||
553 | sbuilder.Append(auth); | ||
554 | sbuilder.Append(Rest.CS_COMMA); | ||
555 | } | ||
556 | |||
557 | // We don't know the userid that will be used | ||
558 | // so we cannot make any authentication domain | ||
559 | // assumptions. So the prefix will determine | ||
560 | // this. | ||
561 | |||
562 | sbuilder.Append("domain="); | ||
563 | sbuilder.Append(Rest.CS_DQUOTE); | ||
564 | sbuilder.Append(qprefix); | ||
565 | sbuilder.Append(Rest.CS_DQUOTE); | ||
566 | |||
567 | // Generate the authenticate header and we're basically | ||
568 | // done. | ||
569 | |||
570 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
571 | |||
572 | } | ||
573 | |||
574 | } | ||
575 | |||
576 | #endregion authentication_common | ||
577 | |||
578 | #region authentication_basic | ||
579 | |||
580 | /// <summary> | ||
581 | /// Interpret a BASIC authorization claim. Some clients can only | ||
582 | /// understand this and also expect it to be the first one | ||
583 | /// offered. So we do. | ||
584 | /// OpenSim also needs this, as it is the only scheme that allows | ||
585 | /// authentication using the hashed passwords stored in the | ||
586 | /// user database. | ||
309 | /// </summary> | 587 | /// </summary> |
310 | 588 | ||
311 | private void DoBasic(string authdata) | 589 | private void DoBasic(string authdata) |
312 | { | 590 | { |
591 | |||
313 | string response = null; | 592 | string response = null; |
314 | 593 | ||
315 | MatchCollection matches = basicParms.Matches(authdata); | 594 | MatchCollection matches = basicParms.Matches(authdata); |
@@ -320,7 +599,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
320 | foreach (Match m in matches) | 599 | foreach (Match m in matches) |
321 | { | 600 | { |
322 | authparms.Add("response",m.Groups["pval"].Value); | 601 | authparms.Add("response",m.Groups["pval"].Value); |
323 | Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", | 602 | Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", |
324 | MsgId, "response", m.Groups["pval"].Value); | 603 | MsgId, "response", m.Groups["pval"].Value); |
325 | } | 604 | } |
326 | 605 | ||
@@ -340,38 +619,111 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
340 | // Validate against user database | 619 | // Validate against user database |
341 | authenticated = Validate(userName,userPass); | 620 | authenticated = Validate(userName,userPass); |
342 | } | 621 | } |
622 | |||
623 | } | ||
624 | |||
625 | /// <summary> | ||
626 | /// This method provides validation in support of the BASIC | ||
627 | /// authentication method. This is not normaly expected to be | ||
628 | /// used, but is included for completeness (and because I tried | ||
629 | /// it first). | ||
630 | /// </summary> | ||
631 | |||
632 | private bool Validate(string user, string pass) | ||
633 | { | ||
634 | |||
635 | Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); | ||
636 | |||
637 | // Both values are required | ||
638 | |||
639 | if (user == null || pass == null) | ||
640 | return false; | ||
641 | |||
642 | // Eliminate any leading or trailing spaces | ||
643 | user = user.Trim(); | ||
644 | |||
645 | return vetPassword(user, pass); | ||
646 | |||
343 | } | 647 | } |
344 | 648 | ||
345 | /// <summary> | 649 | /// <summary> |
650 | /// This is used by the BASIC authentication scheme to calculate | ||
651 | /// the double hash used by OpenSim to encode user's passwords. | ||
652 | /// It returns true, if the supplied password is actually correct. | ||
653 | /// If the specified user-id is not recognized, but the password | ||
654 | /// matches the God password, then this is accepted as an admin | ||
655 | /// session. | ||
656 | /// </summary> | ||
657 | |||
658 | private bool vetPassword(string user, string pass) | ||
659 | { | ||
660 | |||
661 | int x; | ||
662 | string HA1; | ||
663 | string first; | ||
664 | string last; | ||
665 | |||
666 | // Distinguish the parts, if necessary | ||
667 | |||
668 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
669 | { | ||
670 | first = user.Substring(0,x); | ||
671 | last = user.Substring(x+1); | ||
672 | } | ||
673 | else | ||
674 | { | ||
675 | first = user; | ||
676 | last = String.Empty; | ||
677 | } | ||
678 | |||
679 | UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); | ||
680 | |||
681 | // If we don;t recognize the user id, perhaps it is god? | ||
682 | |||
683 | if (udata == null) | ||
684 | return pass == Rest.GodKey; | ||
685 | |||
686 | HA1 = HashToString(pass); | ||
687 | HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt)); | ||
688 | |||
689 | return (0 == sc.Compare(HA1, udata.PasswordHash)); | ||
690 | |||
691 | } | ||
692 | |||
693 | #endregion authentication_basic | ||
694 | |||
695 | #region authentication_digest | ||
696 | |||
697 | /// <summary> | ||
346 | /// This is an RFC2617 compliant HTTP MD5 Digest authentication | 698 | /// This is an RFC2617 compliant HTTP MD5 Digest authentication |
347 | /// implementation. It has been tested with Firefox, Java HTTP client, | 699 | /// implementation. It has been tested with Firefox, Java HTTP client, |
348 | /// and Miscrosoft's Internet Explorer V7. | 700 | /// and Microsoft's Internet Explorer V7. |
349 | /// </summary> | 701 | /// </summary> |
350 | 702 | ||
351 | private void DoDigest(string authdata) | 703 | private void DoDigest(string authdata) |
352 | { | 704 | { |
705 | |||
353 | string response = null; | 706 | string response = null; |
354 | 707 | ||
355 | MatchCollection matches = digestParm1.Matches(authdata); | 708 | // Find all of the values of the for x = "y" |
356 | 709 | ||
357 | // Collect all of the supplied parameters and store them | 710 | MatchCollection matches = digestParm1.Matches(authdata); |
358 | // in a dictionary (for ease of access) | ||
359 | 711 | ||
360 | foreach (Match m in matches) | 712 | foreach (Match m in matches) |
361 | { | 713 | { |
362 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); | 714 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); |
363 | Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", | 715 | Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", |
364 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); | 716 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); |
365 | } | 717 | } |
366 | 718 | ||
367 | // And pick up any tokens too | 719 | // Find all of the values of the for x = y |
368 | 720 | ||
369 | matches = digestParm2.Matches(authdata); | 721 | matches = digestParm2.Matches(authdata); |
370 | 722 | ||
371 | foreach (Match m in matches) | 723 | foreach (Match m in matches) |
372 | { | 724 | { |
373 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); | 725 | authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); |
374 | Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", | 726 | Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", |
375 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); | 727 | MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); |
376 | } | 728 | } |
377 | 729 | ||
@@ -382,10 +734,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
382 | 734 | ||
383 | if (authparms.TryGetValue("response", out response)) | 735 | if (authparms.TryGetValue("response", out response)) |
384 | { | 736 | { |
737 | |||
385 | string temp = null; | 738 | string temp = null; |
386 | 739 | ||
387 | do | 740 | do |
388 | { | 741 | { |
742 | |||
389 | string nck = null; | 743 | string nck = null; |
390 | string ncl = null; | 744 | string ncl = null; |
391 | 745 | ||
@@ -406,7 +760,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
406 | 760 | ||
407 | if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) | 761 | if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) |
408 | { | 762 | { |
409 | Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); | 763 | Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); |
410 | break; | 764 | break; |
411 | } | 765 | } |
412 | 766 | ||
@@ -417,7 +771,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
417 | { | 771 | { |
418 | if (temp != opaque) | 772 | if (temp != opaque) |
419 | { | 773 | { |
420 | Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); | 774 | Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); |
421 | break; | 775 | break; |
422 | } | 776 | } |
423 | } | 777 | } |
@@ -429,7 +783,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
429 | { | 783 | { |
430 | if (temp != algorithm) | 784 | if (temp != algorithm) |
431 | { | 785 | { |
432 | Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); | 786 | Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); |
433 | break; | 787 | break; |
434 | } | 788 | } |
435 | } | 789 | } |
@@ -438,6 +792,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
438 | 792 | ||
439 | if (authparms.TryGetValue("qop", out temp)) | 793 | if (authparms.TryGetValue("qop", out temp)) |
440 | { | 794 | { |
795 | |||
441 | qop = temp.ToLower(); // replace with actual value used | 796 | qop = temp.ToLower(); // replace with actual value used |
442 | 797 | ||
443 | // if QOP was specified then | 798 | // if QOP was specified then |
@@ -445,7 +800,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
445 | 800 | ||
446 | if (!authparms.ContainsKey("cnonce")) | 801 | if (!authparms.ContainsKey("cnonce")) |
447 | { | 802 | { |
448 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); | 803 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); |
449 | break; | 804 | break; |
450 | } | 805 | } |
451 | 806 | ||
@@ -453,7 +808,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
453 | 808 | ||
454 | if (!authparms.TryGetValue("nc", out nck) || nck == null) | 809 | if (!authparms.TryGetValue("nc", out nck) || nck == null) |
455 | { | 810 | { |
456 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); | 811 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); |
457 | break; | 812 | break; |
458 | } | 813 | } |
459 | 814 | ||
@@ -465,7 +820,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
465 | 820 | ||
466 | if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) | 821 | if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) |
467 | { | 822 | { |
468 | Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); | 823 | Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); |
469 | break; | 824 | break; |
470 | } | 825 | } |
471 | cntable[nonce] = nck; | 826 | cntable[nonce] = nck; |
@@ -474,21 +829,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
474 | { | 829 | { |
475 | lock (cntable) cntable.Add(nonce, nck); | 830 | lock (cntable) cntable.Add(nonce, nck); |
476 | } | 831 | } |
832 | |||
477 | } | 833 | } |
478 | else | 834 | else |
479 | { | 835 | { |
836 | |||
480 | qop = String.Empty; | 837 | qop = String.Empty; |
481 | 838 | ||
482 | // if QOP was not specified then | 839 | // if QOP was not specified then |
483 | // these MUST NOT be present. | 840 | // these MUST NOT be present. |
484 | if (authparms.ContainsKey("cnonce")) | 841 | if (authparms.ContainsKey("cnonce")) |
485 | { | 842 | { |
486 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); | 843 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); |
487 | break; | 844 | break; |
488 | } | 845 | } |
489 | if (authparms.ContainsKey("nc")) | 846 | if (authparms.ContainsKey("nc")) |
490 | { | 847 | { |
491 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); | 848 | Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); |
492 | break; | 849 | break; |
493 | } | 850 | } |
494 | } | 851 | } |
@@ -496,157 +853,38 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
496 | // Validate the supplied userid/password info | 853 | // Validate the supplied userid/password info |
497 | 854 | ||
498 | authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); | 855 | authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); |
499 | } | ||
500 | while (false); | ||
501 | } | ||
502 | } | ||
503 | |||
504 | // Indicate that authentication is required | ||
505 | |||
506 | internal void Challenge(string scheme, string realm, string domain, string nonce, | ||
507 | string opaque, string stale, string alg, | ||
508 | string qop, string auth) | ||
509 | { | ||
510 | sbuilder.Length = 0; | ||
511 | |||
512 | if (scheme == null || scheme == Rest.AS_BASIC) | ||
513 | { | ||
514 | |||
515 | sbuilder.Append(Rest.AS_BASIC); | ||
516 | |||
517 | if (realm != null) | ||
518 | { | ||
519 | sbuilder.Append(" realm=\""); | ||
520 | sbuilder.Append(realm); | ||
521 | sbuilder.Append("\""); | ||
522 | } | ||
523 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
524 | } | ||
525 | |||
526 | sbuilder.Length = 0; | ||
527 | |||
528 | if (scheme == null || scheme == Rest.AS_DIGEST) | ||
529 | { | ||
530 | sbuilder.Append(Rest.AS_DIGEST); | ||
531 | sbuilder.Append(" "); | ||
532 | |||
533 | if (realm != null) | ||
534 | { | ||
535 | sbuilder.Append("realm="); | ||
536 | sbuilder.Append(Rest.CS_DQUOTE); | ||
537 | sbuilder.Append(realm); | ||
538 | sbuilder.Append(Rest.CS_DQUOTE); | ||
539 | sbuilder.Append(Rest.CS_COMMA); | ||
540 | } | ||
541 | |||
542 | if (nonce != null) | ||
543 | { | ||
544 | sbuilder.Append("nonce="); | ||
545 | sbuilder.Append(Rest.CS_DQUOTE); | ||
546 | sbuilder.Append(nonce); | ||
547 | sbuilder.Append(Rest.CS_DQUOTE); | ||
548 | sbuilder.Append(Rest.CS_COMMA); | ||
549 | } | ||
550 | |||
551 | if (opaque != null) | ||
552 | { | ||
553 | sbuilder.Append("opaque="); | ||
554 | sbuilder.Append(Rest.CS_DQUOTE); | ||
555 | sbuilder.Append(opaque); | ||
556 | sbuilder.Append(Rest.CS_DQUOTE); | ||
557 | sbuilder.Append(Rest.CS_COMMA); | ||
558 | } | ||
559 | |||
560 | if (stale != null) | ||
561 | { | ||
562 | sbuilder.Append("stale="); | ||
563 | sbuilder.Append(Rest.CS_DQUOTE); | ||
564 | sbuilder.Append(stale); | ||
565 | sbuilder.Append(Rest.CS_DQUOTE); | ||
566 | sbuilder.Append(Rest.CS_COMMA); | ||
567 | } | ||
568 | |||
569 | if (alg != null) | ||
570 | { | ||
571 | sbuilder.Append("algorithm="); | ||
572 | sbuilder.Append(alg); | ||
573 | sbuilder.Append(Rest.CS_COMMA); | ||
574 | } | ||
575 | 856 | ||
576 | if (qop != String.Empty) | 857 | } |
577 | { | 858 | while (false); |
578 | sbuilder.Append("qop="); | ||
579 | sbuilder.Append(Rest.CS_DQUOTE); | ||
580 | sbuilder.Append(qop); | ||
581 | sbuilder.Append(Rest.CS_DQUOTE); | ||
582 | sbuilder.Append(Rest.CS_COMMA); | ||
583 | } | ||
584 | |||
585 | if (auth != null) | ||
586 | { | ||
587 | sbuilder.Append(auth); | ||
588 | sbuilder.Append(Rest.CS_COMMA); | ||
589 | } | ||
590 | |||
591 | // We don;t know the userid that will be used | ||
592 | // so we cannot make any authentication domain | ||
593 | // assumptions. So the prefix will determine | ||
594 | // this. | ||
595 | |||
596 | sbuilder.Append("domain="); | ||
597 | sbuilder.Append(Rest.CS_DQUOTE); | ||
598 | sbuilder.Append(qprefix); | ||
599 | sbuilder.Append(Rest.CS_DQUOTE); | ||
600 | 859 | ||
601 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
602 | } | 860 | } |
603 | } | ||
604 | |||
605 | /// <summary> | ||
606 | /// This method provides validation in support of the BASIC | ||
607 | /// authentication method. This is not normaly expected to be | ||
608 | /// used, but is included for completeness (and because I tried | ||
609 | /// it first). | ||
610 | /// </summary> | ||
611 | |||
612 | private bool Validate(string user, string pass) | ||
613 | { | ||
614 | Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); | ||
615 | |||
616 | // Both values are required | ||
617 | 861 | ||
618 | if (user == null || pass == null) | ||
619 | return false; | ||
620 | |||
621 | // Eliminate any leading or trailing spaces | ||
622 | user = user.Trim(); | ||
623 | |||
624 | return vetPassword(user, pass); | ||
625 | } | 862 | } |
626 | 863 | ||
627 | /// <summary> | 864 | /// <summary> |
628 | /// This mechanism is used by the digest authetnication mechanism | 865 | /// This mechanism is used by the digest authentication mechanism |
629 | /// to return the user's password. In fact, because the OpenSim | 866 | /// to return the user's password. In fact, because the OpenSim |
630 | /// user's passwords are already hashed, and the HTTP mechanism | 867 | /// user's passwords are already hashed, and the HTTP mechanism |
631 | /// does not supply an open password, the hashed passwords cannot | 868 | /// does not supply an open password, the hashed passwords cannot |
632 | /// be used unless the cliemt has used the same salting mechanism | 869 | /// be used unless the client has used the same salting mechanism |
633 | /// to has the password before using it in the authentication | 870 | /// to has the password before using it in the authentication |
634 | /// algorithm. This is not inconceivable... | 871 | /// algorithn. This is not inconceivable... |
635 | /// </summary> | 872 | /// </summary> |
636 | 873 | ||
637 | private string getPassword(string user) | 874 | private string getPassword(string user) |
638 | { | 875 | { |
876 | |||
639 | int x; | 877 | int x; |
640 | string first; | 878 | string first; |
641 | string last; | 879 | string last; |
642 | 880 | ||
643 | // Distinguish the parts, if necessary | 881 | // Distinguish the parts, if necessary |
644 | 882 | ||
645 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | 883 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) |
646 | { | 884 | { |
647 | first = user.Substring(0,x); | 885 | first = user.Substring(0,x); |
648 | last = user.Substring(x+1); | 886 | last = user.Substring(x+1); |
649 | } | 887 | } |
650 | else | 888 | else |
651 | { | 889 | { |
652 | first = user; | 890 | first = user; |
@@ -667,53 +905,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
667 | Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); | 905 | Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); |
668 | return udata.PasswordHash; | 906 | return udata.PasswordHash; |
669 | } | 907 | } |
670 | } | ||
671 | 908 | ||
672 | /// <summary> | ||
673 | /// This is used by the BASIC authentication scheme to calculate | ||
674 | /// the double hash used by OpenSim to encode user's passwords. | ||
675 | /// It returns true, if the supplied password is actually correct. | ||
676 | /// If the specified user-id is not recognized, but the password | ||
677 | /// matches the God password, then this is accepted as an admin | ||
678 | /// session. | ||
679 | /// </summary> | ||
680 | |||
681 | private bool vetPassword(string user, string pass) | ||
682 | { | ||
683 | int x; | ||
684 | string HA1; | ||
685 | string first; | ||
686 | string last; | ||
687 | |||
688 | // Distinguish the parts, if necessary | ||
689 | |||
690 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
691 | { | ||
692 | first = user.Substring(0,x); | ||
693 | last = user.Substring(x+1); | ||
694 | } | ||
695 | else | ||
696 | { | ||
697 | first = user; | ||
698 | last = String.Empty; | ||
699 | } | ||
700 | |||
701 | UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); | ||
702 | |||
703 | // If we don;t recognize the user id, perhaps it is god? | ||
704 | |||
705 | if (udata == null) | ||
706 | return pass == Rest.GodKey; | ||
707 | |||
708 | HA1 = HashToString(pass); | ||
709 | HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt)); | ||
710 | |||
711 | return (0 == sc.Compare(HA1, udata.PasswordHash)); | ||
712 | } | 909 | } |
713 | 910 | ||
714 | // Validate the request-digest | 911 | // Validate the request-digest |
912 | |||
715 | private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) | 913 | private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) |
716 | { | 914 | { |
915 | |||
717 | string patt = null; | 916 | string patt = null; |
718 | string payl = String.Empty; | 917 | string payl = String.Empty; |
719 | string KDS = null; | 918 | string KDS = null; |
@@ -756,7 +955,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
756 | HA2 = HashToString(patt); | 955 | HA2 = HashToString(patt); |
757 | 956 | ||
758 | // Generate Digest | 957 | // Generate Digest |
759 | 958 | ||
760 | if (qop != String.Empty) | 959 | if (qop != String.Empty) |
761 | { | 960 | { |
762 | patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); | 961 | patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); |
@@ -771,10 +970,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
771 | // Compare the generated sequence with the original | 970 | // Compare the generated sequence with the original |
772 | 971 | ||
773 | return (0 == sc.Compare(KDS, response)); | 972 | return (0 == sc.Compare(KDS, response)); |
973 | |||
774 | } | 974 | } |
775 | 975 | ||
776 | private string HashToString(string pattern) | 976 | private string HashToString(string pattern) |
777 | { | 977 | { |
978 | |||
778 | Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); | 979 | Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); |
779 | 980 | ||
780 | byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); | 981 | byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); |
@@ -789,62 +990,98 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
789 | Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); | 990 | Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); |
790 | 991 | ||
791 | return sbuilder.ToString(); | 992 | return sbuilder.ToString(); |
993 | |||
792 | } | 994 | } |
793 | 995 | ||
996 | #endregion authentication_digest | ||
997 | |||
998 | #region service_interface | ||
999 | |||
1000 | /// <summary> | ||
1001 | /// Conditionally set a normal completion code. This allows a normal | ||
1002 | /// execution path to default. | ||
1003 | /// </summary> | ||
1004 | |||
794 | internal void Complete() | 1005 | internal void Complete() |
795 | { | 1006 | { |
796 | statusCode = Rest.HttpStatusCodeOK; | 1007 | if (statusCode == 0) |
797 | statusDescription = Rest.HttpStatusDescOK; | 1008 | { |
1009 | statusCode = Rest.HttpStatusCodeOK; | ||
1010 | } | ||
798 | } | 1011 | } |
799 | 1012 | ||
1013 | /// <summary> | ||
1014 | /// Indicate a functionally-dependent conclusion to the | ||
1015 | /// request. See Rest.cs for a list of possible values. | ||
1016 | /// </summary> | ||
1017 | |||
1018 | internal void Complete(int code) | ||
1019 | { | ||
1020 | statusCode = code; | ||
1021 | } | ||
1022 | |||
1023 | /// <summary> | ||
1024 | /// Indicate that a request should be redirected, using | ||
1025 | /// the HTTP completion codes. Permanent and temporary | ||
1026 | /// redirections may be indicated. The supplied URL is | ||
1027 | /// the new location of the resource. | ||
1028 | /// </summary> | ||
1029 | |||
800 | internal void Redirect(string Url, bool temp) | 1030 | internal void Redirect(string Url, bool temp) |
801 | { | 1031 | { |
1032 | |||
802 | redirectLocation = Url; | 1033 | redirectLocation = Url; |
803 | 1034 | ||
804 | if (temp) | 1035 | if (temp) |
805 | { | 1036 | { |
806 | statusCode = Rest.HttpStatusCodeTemporaryRedirect; | 1037 | statusCode = Rest.HttpStatusCodeTemporaryRedirect; |
807 | statusDescription = Rest.HttpStatusDescTemporaryRedirect; | ||
808 | } | 1038 | } |
809 | else | 1039 | else |
810 | { | 1040 | { |
811 | statusCode = Rest.HttpStatusCodePermanentRedirect; | 1041 | statusCode = Rest.HttpStatusCodePermanentRedirect; |
812 | statusDescription = Rest.HttpStatusDescPermanentRedirect; | ||
813 | } | 1042 | } |
814 | 1043 | ||
815 | Fail(statusCode, statusDescription, true); | 1044 | Fail(statusCode, String.Empty, true); |
1045 | |||
816 | } | 1046 | } |
817 | 1047 | ||
818 | // Fail for an arbitrary reason. Just a failure with | 1048 | /// <summary> |
819 | // headers. | 1049 | /// Fail for an arbitrary reason. Just a failure with |
1050 | /// headers. The supplied message will be returned in the | ||
1051 | /// message body. | ||
1052 | /// </summary> | ||
820 | 1053 | ||
821 | internal void Fail(int code, string message) | 1054 | internal void Fail(int code) |
822 | { | 1055 | { |
823 | Fail(code, message, true); | 1056 | Fail(code, String.Empty, false); |
824 | } | 1057 | } |
825 | 1058 | ||
826 | // More adventurous. This failure also includes a | 1059 | /// <summary> |
827 | // specified entity. | 1060 | /// For the more adventurous. This failure also includes a |
1061 | /// specified entity to be appended to the code-related | ||
1062 | /// status string. | ||
1063 | /// </summary> | ||
828 | 1064 | ||
829 | internal void Fail(int code, string message, string data) | 1065 | internal void Fail(int code, string addendum) |
830 | { | 1066 | { |
831 | buffer = null; | 1067 | Fail(code, addendum, false); |
832 | body = data; | ||
833 | Fail(code, message, false); | ||
834 | } | 1068 | } |
835 | 1069 | ||
836 | internal void Fail(int code, string message, bool reset) | 1070 | internal void Fail(int code, string addendum, bool reset) |
837 | { | 1071 | { |
1072 | |||
838 | statusCode = code; | 1073 | statusCode = code; |
839 | statusDescription = message; | 1074 | appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code])); |
1075 | |||
1076 | // Add any final addendum to the status information | ||
840 | 1077 | ||
841 | if (reset) | 1078 | if (addendum != String.Empty) |
842 | { | 1079 | { |
843 | buffer = null; | 1080 | appendStatus(String.Format(addendum)); |
844 | SendHtml(message); | ||
845 | body = html; | ||
846 | } | 1081 | } |
847 | 1082 | ||
1083 | // Help us understand why the request is being rejected | ||
1084 | |||
848 | if (Rest.DEBUG) | 1085 | if (Rest.DEBUG) |
849 | { | 1086 | { |
850 | Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); | 1087 | Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); |
@@ -864,42 +1101,61 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
864 | 1101 | ||
865 | fail = true; | 1102 | fail = true; |
866 | 1103 | ||
867 | Respond("Failure response"); | 1104 | // Respond to the client's request, tag the response (for the |
1105 | // benefit of trace) to indicate the reason. | ||
1106 | |||
1107 | Respond(String.Format("Failure response: ({0}) : {1}", | ||
1108 | code, Rest.HttpStatusDesc[code])); | ||
1109 | |||
1110 | // Finally initialize and the throw a RestException. All of the | ||
1111 | // handler's infrastructure knows that this is a "normal" | ||
1112 | // completion from a code point-of-view. | ||
868 | 1113 | ||
869 | RestException re = new RestException(message+" <"+code+">"); | 1114 | RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">"); |
870 | 1115 | ||
871 | re.statusCode = code; | 1116 | re.statusCode = code; |
872 | re.statusDesc = message; | 1117 | re.statusDesc = Rest.HttpStatusDesc[code]; |
873 | re.httpmethod = method; | 1118 | re.httpmethod = method; |
874 | re.httppath = path; | 1119 | re.httppath = path; |
875 | 1120 | ||
876 | throw re; | 1121 | throw re; |
1122 | |||
877 | } | 1123 | } |
878 | 1124 | ||
879 | // Reject this request | 1125 | // Reject this request |
880 | 1126 | ||
881 | internal void Reject() | 1127 | internal void Reject() |
882 | { | 1128 | { |
883 | Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented); | 1129 | Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)"); |
884 | } | 1130 | } |
885 | 1131 | ||
886 | // This MUST be called by an agent handler before it returns | 1132 | // This MUST be called by an agent handler before it returns |
887 | // control to Handle, otherwise the request will be ignored. | 1133 | // control to Handle, otherwise the request will be ignored. |
888 | // This is called implciitly for the REST stream handlers and | 1134 | // This is called implciitly for the REST stream handlers and |
889 | // is harmless if it is called twice. | 1135 | // is harmless if it is called twice. |
890 | 1136 | ||
891 | internal virtual bool Respond(string reason) | 1137 | internal virtual bool Respond(string reason) |
892 | { | 1138 | { |
1139 | |||
1140 | |||
893 | Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); | 1141 | Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); |
894 | 1142 | ||
1143 | // We do this to try and make multiple Respond requests harmless, | ||
1144 | // as it is sometimes convenient to isse a response without | ||
1145 | // certain knowledge that it has not previously been done. | ||
1146 | |||
895 | if (!handled) | 1147 | if (!handled) |
896 | { | 1148 | { |
1149 | |||
897 | Rest.Log.DebugFormat("{0} Generating Response", MsgId); | 1150 | Rest.Log.DebugFormat("{0} Generating Response", MsgId); |
898 | Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); | 1151 | Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); |
899 | 1152 | ||
900 | // A Head request can NOT have a body! | 1153 | // A Head request can NOT have a body! So don't waste time on |
1154 | // formatting if we're going to reject it anyway! | ||
1155 | |||
901 | if (method != Rest.HEAD) | 1156 | if (method != Rest.HEAD) |
902 | { | 1157 | { |
1158 | |||
903 | Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); | 1159 | Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); |
904 | 1160 | ||
905 | // If the writer is non-null then we know that an XML | 1161 | // If the writer is non-null then we know that an XML |
@@ -924,18 +1180,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
924 | Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); | 1180 | Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); |
925 | } | 1181 | } |
926 | 1182 | ||
927 | // If buffer != null, then we assume that | 1183 | if (buffer == null && body != null) |
928 | // this has already been done some other | ||
929 | // way. For example, transfer encoding might | ||
930 | // have been done. | ||
931 | |||
932 | if (buffer == null) | ||
933 | { | 1184 | { |
934 | if (body != null && body.Length > 0) | 1185 | buffer = encoding.GetBytes(body); |
935 | { | 1186 | AddHeader("Content-Type",bodyType); |
936 | Rest.Log.DebugFormat("{0} String-based entity", MsgId); | ||
937 | buffer = encoding.GetBytes(body); | ||
938 | } | ||
939 | } | 1187 | } |
940 | 1188 | ||
941 | // OK, if the buffer contains something, regardless of how | 1189 | // OK, if the buffer contains something, regardless of how |
@@ -944,21 +1192,37 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
944 | if (buffer != null) | 1192 | if (buffer != null) |
945 | { | 1193 | { |
946 | Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); | 1194 | Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); |
947 | response.ContentLength64 = buffer.Length; | ||
948 | } | 1195 | } |
949 | else | 1196 | else |
950 | { | 1197 | { |
951 | response.ContentLength64 = 0; | 1198 | if (statusBody != String.Empty) |
1199 | { | ||
1200 | statusBody += Rest.statusTail; | ||
1201 | buffer = encoding.GetBytes(statusBody); | ||
1202 | AddHeader("Content-Type","text/html"); | ||
1203 | } | ||
1204 | else | ||
1205 | { | ||
1206 | statusBody = Rest.statusHead; | ||
1207 | appendStatus(String.Format(": ({0}) {1}", | ||
1208 | statusCode, Rest.HttpStatusDesc[statusCode])); | ||
1209 | statusBody += Rest.statusTail; | ||
1210 | buffer = encoding.GetBytes(statusBody); | ||
1211 | AddHeader("Content-Type","text/html"); | ||
1212 | } | ||
952 | } | 1213 | } |
953 | 1214 | ||
1215 | response.ContentLength64 = buffer.Length; | ||
1216 | |||
954 | if (response.Headers.Get("Content-Encoding") == null) | 1217 | if (response.Headers.Get("Content-Encoding") == null) |
955 | response.ContentEncoding = encoding; | 1218 | response.ContentEncoding = encoding; |
956 | 1219 | ||
957 | response.SendChunked = chunked; | 1220 | response.SendChunked = chunked; |
958 | response.KeepAlive = keepAlive; | 1221 | response.KeepAlive = keepAlive; |
1222 | |||
959 | } | 1223 | } |
960 | 1224 | ||
961 | // Set the status code & description. If nothing has been stored, | 1225 | // Set the status code & description. If nothing has been stored, |
962 | // we consider that a success. | 1226 | // we consider that a success. |
963 | 1227 | ||
964 | if (statusCode == 0) | 1228 | if (statusCode == 0) |
@@ -972,7 +1236,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
972 | 1236 | ||
973 | // For a redirect we need to set the relocation header accordingly | 1237 | // For a redirect we need to set the relocation header accordingly |
974 | 1238 | ||
975 | if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || | 1239 | if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || |
976 | response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) | 1240 | response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) |
977 | { | 1241 | { |
978 | Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); | 1242 | Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); |
@@ -981,18 +1245,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
981 | 1245 | ||
982 | // And include the status description if provided. | 1246 | // And include the status description if provided. |
983 | 1247 | ||
984 | if (statusDescription != null) | 1248 | response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode]; |
985 | { | ||
986 | Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription); | ||
987 | response.StatusDescription = statusDescription; | ||
988 | } | ||
989 | 1249 | ||
990 | // Finally we send back our response. | 1250 | // Finally we send back our response. |
991 | 1251 | ||
992 | // We've left the setting of handled' until the | 1252 | // We've left the setting of handled' until the |
993 | // last minute because the header settings included | 1253 | // last minute because the header settings included |
994 | // above are pretty harmless. But everything from | 1254 | // above are pretty harmless. But everything from |
995 | // here on down probably leaves the response | 1255 | // here on down probably leaves the response |
996 | // element unusable by anyone else. | 1256 | // element unusable by anyone else. |
997 | 1257 | ||
998 | handled = true; | 1258 | handled = true; |
@@ -1007,7 +1267,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1007 | 1267 | ||
1008 | if (buffer != null && buffer.Length != 0) | 1268 | if (buffer != null && buffer.Length != 0) |
1009 | { | 1269 | { |
1010 | Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", | 1270 | Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", |
1011 | MsgId, buffer.Length, encoding.GetString(buffer)); | 1271 | MsgId, buffer.Length, encoding.GetString(buffer)); |
1012 | response.OutputStream.Write(buffer, 0, buffer.Length); | 1272 | response.OutputStream.Write(buffer, 0, buffer.Length); |
1013 | } | 1273 | } |
@@ -1016,35 +1276,36 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1016 | 1276 | ||
1017 | Rest.Log.DebugFormat("{0} Closing output stream", MsgId); | 1277 | Rest.Log.DebugFormat("{0} Closing output stream", MsgId); |
1018 | response.OutputStream.Close(); | 1278 | response.OutputStream.Close(); |
1279 | |||
1019 | } | 1280 | } |
1020 | 1281 | ||
1021 | Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); | 1282 | Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); |
1022 | 1283 | ||
1023 | return handled; | 1284 | return handled; |
1285 | |||
1024 | } | 1286 | } |
1025 | 1287 | ||
1026 | // Add a header to the table. We need to allow | 1288 | /// <summary> |
1027 | // multiple instances of many of the headers. | 1289 | /// These methods allow a service provider to manipulate the |
1028 | // If the | 1290 | /// request/response headers. The DumpHeaders method is intended |
1291 | /// for problem diagnosis. | ||
1292 | /// </summary> | ||
1029 | 1293 | ||
1030 | internal void AddHeader(string hdr, string data) | 1294 | internal void AddHeader(string hdr, string data) |
1031 | { | 1295 | { |
1032 | if (Rest.DEBUG) | 1296 | if (Rest.DEBUG) |
1033 | { | 1297 | { |
1034 | Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", | 1298 | Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", |
1035 | MsgId, hdr, data); | 1299 | MsgId, hdr, data); |
1036 | if (response.Headers.Get(hdr) != null) | 1300 | if (response.Headers.Get(hdr) != null) |
1037 | { | 1301 | { |
1038 | Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>", | 1302 | Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>", |
1039 | MsgId, hdr); | 1303 | MsgId, hdr); |
1040 | } | 1304 | } |
1041 | } | 1305 | } |
1042 | response.Headers.Add(hdr, data); | 1306 | response.Headers.Add(hdr, data); |
1043 | } | 1307 | } |
1044 | 1308 | ||
1045 | // Keep explicit track of any headers which | ||
1046 | // are to be removed. | ||
1047 | |||
1048 | internal void RemoveHeader(string hdr) | 1309 | internal void RemoveHeader(string hdr) |
1049 | { | 1310 | { |
1050 | if (Rest.DEBUG) | 1311 | if (Rest.DEBUG) |
@@ -1052,29 +1313,69 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1052 | Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); | 1313 | Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); |
1053 | if (response.Headers.Get(hdr) == null) | 1314 | if (response.Headers.Get(hdr) == null) |
1054 | { | 1315 | { |
1055 | Rest.Log.DebugFormat("{0} No such header existed", | 1316 | Rest.Log.DebugFormat("{0} No such header existed", |
1056 | MsgId, hdr); | 1317 | MsgId, hdr); |
1057 | } | 1318 | } |
1058 | } | 1319 | } |
1059 | response.Headers.Remove(hdr); | 1320 | response.Headers.Remove(hdr); |
1060 | } | 1321 | } |
1061 | 1322 | ||
1062 | /// <summary> | ||
1063 | /// Dump headers that will be generated in the response | ||
1064 | /// </summary> | ||
1065 | |||
1066 | internal void DumpHeaders() | 1323 | internal void DumpHeaders() |
1067 | { | 1324 | { |
1068 | if (Rest.DEBUG) | 1325 | if (Rest.DEBUG) |
1069 | { | 1326 | { |
1070 | for (int i=0;i<response.Headers.Count;i++) | 1327 | for (int i=0;i<response.Headers.Count;i++) |
1071 | { | 1328 | { |
1072 | Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, | 1329 | Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, |
1073 | response.Headers.Get(i)); | 1330 | response.Headers.Get(i)); |
1074 | } | 1331 | } |
1075 | } | 1332 | } |
1076 | } | 1333 | } |
1077 | 1334 | ||
1335 | // Setup the XML writer for output | ||
1336 | |||
1337 | internal void initXmlWriter() | ||
1338 | { | ||
1339 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
1340 | xmldata = new MemoryStream(); | ||
1341 | settings.Indent = true; | ||
1342 | settings.IndentChars = " "; | ||
1343 | settings.Encoding = encoding; | ||
1344 | settings.CloseOutput = false; | ||
1345 | settings.OmitXmlDeclaration = true; | ||
1346 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1347 | writer = XmlWriter.Create(xmldata, settings); | ||
1348 | } | ||
1349 | |||
1350 | internal void initXmlReader() | ||
1351 | { | ||
1352 | |||
1353 | XmlReaderSettings settings = new XmlReaderSettings(); | ||
1354 | |||
1355 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1356 | settings.IgnoreComments = true; | ||
1357 | settings.IgnoreWhitespace = true; | ||
1358 | settings.IgnoreProcessingInstructions = true; | ||
1359 | settings.ValidationType = ValidationType.None; | ||
1360 | |||
1361 | reader = XmlReader.Create(request.InputStream,settings); | ||
1362 | |||
1363 | } | ||
1364 | |||
1365 | internal void appendStatus(string msg) | ||
1366 | { | ||
1367 | if (statusBody == String.Empty) | ||
1368 | { | ||
1369 | statusBody = String.Format(Rest.statusHead, request.HttpMethod); | ||
1370 | } | ||
1371 | |||
1372 | statusBody = String.Format("{0} {1}", statusBody, msg); | ||
1373 | } | ||
1374 | |||
1375 | #endregion service_interface | ||
1376 | |||
1377 | #region internal_methods | ||
1378 | |||
1078 | /// <summary> | 1379 | /// <summary> |
1079 | /// Helper methods for deconstructing and reconstructing | 1380 | /// Helper methods for deconstructing and reconstructing |
1080 | /// URI path data. | 1381 | /// URI path data. |
@@ -1082,6 +1383,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1082 | 1383 | ||
1083 | private void initUrl() | 1384 | private void initUrl() |
1084 | { | 1385 | { |
1386 | |||
1085 | uri = request.Url; | 1387 | uri = request.Url; |
1086 | 1388 | ||
1087 | if (query == null) | 1389 | if (query == null) |
@@ -1096,19 +1398,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1096 | { | 1398 | { |
1097 | path = uri.AbsolutePath; | 1399 | path = uri.AbsolutePath; |
1098 | if (path.EndsWith(Rest.UrlPathSeparator)) | 1400 | if (path.EndsWith(Rest.UrlPathSeparator)) |
1099 | path = path.Substring(0, path.Length-1); | 1401 | path = path.Substring(0,path.Length-1); |
1100 | } | 1402 | } |
1101 | 1403 | ||
1102 | // If we succeeded in getting a path, perform any | 1404 | // If we succeeded in getting a path, perform any |
1103 | // additional pre-processing required. | 1405 | // additional pre-processing required. |
1104 | 1406 | ||
1105 | if (path != null) | 1407 | if (path != null) |
1106 | { | 1408 | { |
1107 | if (Rest.ExtendedEscape) | 1409 | if (Rest.ExtendedEscape) |
1108 | { | 1410 | { |
1109 | // Handle "+". Not a standard substitution, but | 1411 | // Handle "+". Not a standard substitution, but |
1110 | // common enough... | 1412 | // common enough... |
1111 | path = path.Replace(Rest.C_PLUS, Rest.C_SPACE); | 1413 | path = path.Replace(Rest.C_PLUS,Rest.C_SPACE); |
1112 | } | 1414 | } |
1113 | pathNodes = path.Split(Rest.CA_PATHSEP); | 1415 | pathNodes = path.Split(Rest.CA_PATHSEP); |
1114 | } | 1416 | } |
@@ -1126,10 +1428,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1126 | 1428 | ||
1127 | hostname = uri.Host; | 1429 | hostname = uri.Host; |
1128 | port = uri.Port; | 1430 | port = uri.Port; |
1431 | |||
1129 | } | 1432 | } |
1130 | 1433 | ||
1131 | internal int initParameters(int prfxlen) | 1434 | private int initParameters(int prfxlen) |
1132 | { | 1435 | { |
1436 | |||
1133 | if (prfxlen < path.Length-1) | 1437 | if (prfxlen < path.Length-1) |
1134 | { | 1438 | { |
1135 | parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); | 1439 | parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); |
@@ -1138,139 +1442,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1138 | { | 1442 | { |
1139 | parameters = new string[0]; | 1443 | parameters = new string[0]; |
1140 | } | 1444 | } |
1141 | 1445 | ||
1142 | // Generate a debug list of the decoded parameters | 1446 | // Generate a debug list of the decoded parameters |
1143 | 1447 | ||
1144 | if (Rest.DEBUG && prfxlen < path.Length-1) | 1448 | if (Rest.DEBUG && prfxlen < path.Length-1) |
1145 | { | 1449 | { |
1146 | Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); | 1450 | Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); |
1147 | for (int i = 0; i < parameters.Length; i++) | 1451 | for (int i = 0; i < parameters.Length; i++) |
1148 | { | 1452 | { |
1149 | Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); | 1453 | Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); |
1150 | } | 1454 | } |
1151 | } | 1455 | } |
1152 | 1456 | ||
1153 | return parameters.Length; | 1457 | return parameters.Length; |
1154 | } | ||
1155 | 1458 | ||
1156 | internal string[] PathNodes | ||
1157 | { | ||
1158 | get | ||
1159 | { | ||
1160 | if (pathNodes == null) | ||
1161 | { | ||
1162 | initUrl(); | ||
1163 | } | ||
1164 | return pathNodes; | ||
1165 | } | ||
1166 | } | 1459 | } |
1167 | 1460 | ||
1168 | internal string BuildUrl(int first, int last) | 1461 | #endregion internal_methods |
1169 | { | ||
1170 | if (pathNodes == null) | ||
1171 | { | ||
1172 | initUrl(); | ||
1173 | } | ||
1174 | |||
1175 | if (first < 0) | ||
1176 | { | ||
1177 | first = first + pathNodes.Length; | ||
1178 | } | ||
1179 | 1462 | ||
1180 | if (last < 0) | ||
1181 | { | ||
1182 | last = last + pathNodes.Length; | ||
1183 | if (last < 0) | ||
1184 | { | ||
1185 | return Rest.UrlPathSeparator; | ||
1186 | } | ||
1187 | } | ||
1188 | |||
1189 | sbuilder.Length = 0; | ||
1190 | sbuilder.Append(Rest.UrlPathSeparator); | ||
1191 | |||
1192 | if (first <= last) | ||
1193 | { | ||
1194 | for (int i = first; i <= last; i++) | ||
1195 | { | ||
1196 | sbuilder.Append(pathNodes[i]); | ||
1197 | sbuilder.Append(Rest.UrlPathSeparator); | ||
1198 | } | ||
1199 | } | ||
1200 | else | ||
1201 | { | ||
1202 | for (int i = last; i >= first; i--) | ||
1203 | { | ||
1204 | sbuilder.Append(pathNodes[i]); | ||
1205 | sbuilder.Append(Rest.UrlPathSeparator); | ||
1206 | } | ||
1207 | } | ||
1208 | |||
1209 | return sbuilder.ToString(); | ||
1210 | } | ||
1211 | |||
1212 | // Setup the XML writer for output | ||
1213 | |||
1214 | internal void initXmlWriter() | ||
1215 | { | ||
1216 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
1217 | xmldata = new MemoryStream(); | ||
1218 | settings.Indent = true; | ||
1219 | settings.IndentChars = " "; | ||
1220 | settings.Encoding = encoding; | ||
1221 | settings.CloseOutput = false; | ||
1222 | settings.OmitXmlDeclaration = true; | ||
1223 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1224 | writer = XmlWriter.Create(xmldata, settings); | ||
1225 | } | ||
1226 | |||
1227 | internal void initXmlReader() | ||
1228 | { | ||
1229 | XmlReaderSettings settings = new XmlReaderSettings(); | ||
1230 | |||
1231 | settings.ConformanceLevel = ConformanceLevel.Fragment; | ||
1232 | settings.IgnoreComments = true; | ||
1233 | settings.IgnoreWhitespace = true; | ||
1234 | settings.IgnoreProcessingInstructions = true; | ||
1235 | settings.ValidationType = ValidationType.None; | ||
1236 | |||
1237 | reader = XmlReader.Create(request.InputStream,settings); | ||
1238 | } | ||
1239 | |||
1240 | private void Flush() | ||
1241 | { | ||
1242 | byte[] dbuffer = new byte[8192]; | ||
1243 | while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0); | ||
1244 | } | ||
1245 | |||
1246 | // This allows us to make errors a bit more apparent in REST | ||
1247 | |||
1248 | internal void SendHtml(string text) | ||
1249 | { | ||
1250 | SendHtml("OpenSim REST Interface 1.0", text); | ||
1251 | } | ||
1252 | |||
1253 | internal void SendHtml(string title, string text) | ||
1254 | { | ||
1255 | AddHeader(Rest.HttpHeaderContentType, "text/html"); | ||
1256 | sbuilder.Length = 0; | ||
1257 | |||
1258 | sbuilder.Append("<html>"); | ||
1259 | sbuilder.Append("<head>"); | ||
1260 | sbuilder.Append("<title>"); | ||
1261 | sbuilder.Append(title); | ||
1262 | sbuilder.Append("</title>"); | ||
1263 | sbuilder.Append("</head>"); | ||
1264 | |||
1265 | sbuilder.Append("<body>"); | ||
1266 | sbuilder.Append("<br />"); | ||
1267 | sbuilder.Append("<p>"); | ||
1268 | sbuilder.Append(text); | ||
1269 | sbuilder.Append("</p>"); | ||
1270 | sbuilder.Append("</body>"); | ||
1271 | sbuilder.Append("</html>"); | ||
1272 | |||
1273 | html = sbuilder.ToString(); | ||
1274 | } | ||
1275 | } | 1463 | } |
1276 | } | 1464 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs index e8c0ee8..fa22481 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs | |||
@@ -23,7 +23,7 @@ | |||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | 26 | * |
27 | */ | 27 | */ |
28 | 28 | ||
29 | using System; | 29 | using System; |
@@ -38,9 +38,11 @@ using Nini.Config; | |||
38 | 38 | ||
39 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 39 | namespace OpenSim.ApplicationPlugins.Rest.Inventory |
40 | { | 40 | { |
41 | |||
41 | public class Rest | 42 | public class Rest |
42 | { | 43 | { |
43 | internal static readonly log4net.ILog Log = | 44 | |
45 | internal static readonly log4net.ILog Log = | ||
44 | log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 46 | log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
45 | 47 | ||
46 | internal static bool DEBUG = Log.IsDebugEnabled; | 48 | internal static bool DEBUG = Log.IsDebugEnabled; |
@@ -53,7 +55,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
53 | /// RestHandler class during start-up. | 55 | /// RestHandler class during start-up. |
54 | /// </summary> | 56 | /// </summary> |
55 | 57 | ||
56 | internal static RestHandler Plugin = null; | 58 | internal static IRestHandler Plugin = null; |
57 | internal static OpenSimBase main = null; | 59 | internal static OpenSimBase main = null; |
58 | internal static CommunicationsManager Comms = null; | 60 | internal static CommunicationsManager Comms = null; |
59 | internal static IInventoryServices InventoryServices = null; | 61 | internal static IInventoryServices InventoryServices = null; |
@@ -66,10 +68,47 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
66 | internal static bool Secure = true; | 68 | internal static bool Secure = true; |
67 | internal static bool ExtendedEscape = true; | 69 | internal static bool ExtendedEscape = true; |
68 | internal static bool DumpAsset = false; | 70 | internal static bool DumpAsset = false; |
71 | internal static bool Fill = true; | ||
72 | internal static bool FlushEnabled = true; | ||
69 | internal static string Realm = "REST"; | 73 | internal static string Realm = "REST"; |
70 | internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; | ||
71 | internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 | 74 | internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 |
72 | 75 | ||
76 | /// <summary> | ||
77 | /// HTTP requires that status information be generated for PUT | ||
78 | /// and POST opertaions. This is in support of that. The | ||
79 | /// operation verb gets substituted into the first string, | ||
80 | /// and the completion code is inserted into the tail. The | ||
81 | /// strings are put here to encourage consistency. | ||
82 | /// </summary> | ||
83 | |||
84 | internal static string statusHead = "<html><body><title>{0} status</title><break>"; | ||
85 | internal static string statusTail = "</body></html>"; | ||
86 | |||
87 | internal static Dictionary<int,string> HttpStatusDesc; | ||
88 | |||
89 | static Rest() | ||
90 | { | ||
91 | HttpStatusDesc = new Dictionary<int,string>(); | ||
92 | if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length) | ||
93 | { | ||
94 | Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match"); | ||
95 | throw new Exception("HTTP Status array discrepancy"); | ||
96 | } | ||
97 | |||
98 | // Repackage the data into something more tractable. The sparse | ||
99 | // nature of HTTP return codes makes an array a bad choice. | ||
100 | |||
101 | for (int i=0; i<HttpStatusCodeArray.Length; i++) | ||
102 | { | ||
103 | HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | internal static int CreationDate | ||
108 | { | ||
109 | get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; } | ||
110 | } | ||
111 | |||
73 | internal static string MsgId | 112 | internal static string MsgId |
74 | { | 113 | { |
75 | get { return Plugin.MsgId; } | 114 | get { return Plugin.MsgId; } |
@@ -104,7 +143,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
104 | /// supported by all servers. See Respond | 143 | /// supported by all servers. See Respond |
105 | /// to see how these are handled. | 144 | /// to see how these are handled. |
106 | /// </summary> | 145 | /// </summary> |
107 | 146 | ||
108 | // REST AGENT 1.0 interpretations | 147 | // REST AGENT 1.0 interpretations |
109 | public const string GET = "get"; // information retrieval - server state unchanged | 148 | public const string GET = "get"; // information retrieval - server state unchanged |
110 | public const string HEAD = "head"; // same as get except only the headers are returned. | 149 | public const string HEAD = "head"; // same as get except only the headers are returned. |
@@ -136,7 +175,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
136 | public static readonly char C_PERIOD = '.'; | 175 | public static readonly char C_PERIOD = '.'; |
137 | public static readonly char C_COMMA = ','; | 176 | public static readonly char C_COMMA = ','; |
138 | public static readonly char C_DQUOTE = '"'; | 177 | public static readonly char C_DQUOTE = '"'; |
139 | 178 | ||
140 | public static readonly string CS_SPACE = " "; | 179 | public static readonly string CS_SPACE = " "; |
141 | public static readonly string CS_SLASH = "/"; | 180 | public static readonly string CS_SLASH = "/"; |
142 | public static readonly string CS_PATHSEP = "/"; | 181 | public static readonly string CS_PATHSEP = "/"; |
@@ -145,7 +184,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
145 | public static readonly string CS_PERIOD = "."; | 184 | public static readonly string CS_PERIOD = "."; |
146 | public static readonly string CS_COMMA = ","; | 185 | public static readonly string CS_COMMA = ","; |
147 | public static readonly string CS_DQUOTE = "\""; | 186 | public static readonly string CS_DQUOTE = "\""; |
148 | 187 | ||
149 | public static readonly char[] CA_SPACE = { C_SPACE }; | 188 | public static readonly char[] CA_SPACE = { C_SPACE }; |
150 | public static readonly char[] CA_SLASH = { C_SLASH }; | 189 | public static readonly char[] CA_SLASH = { C_SLASH }; |
151 | public static readonly char[] CA_PATHSEP = { C_PATHSEP }; | 190 | public static readonly char[] CA_PATHSEP = { C_PATHSEP }; |
@@ -203,53 +242,97 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
203 | public const int HttpStatusCodeGatewayTimeout = 504; | 242 | public const int HttpStatusCodeGatewayTimeout = 504; |
204 | public const int HttpStatusCodeHttpVersionError = 505; | 243 | public const int HttpStatusCodeHttpVersionError = 505; |
205 | 244 | ||
206 | // HTTP Status Descriptions (in status code order) | 245 | public static readonly int[] HttpStatusCodeArray = { |
246 | HttpStatusCodeContinue, | ||
247 | HttpStatusCodeSwitchingProtocols, | ||
248 | HttpStatusCodeOK, | ||
249 | HttpStatusCodeCreated, | ||
250 | HttpStatusCodeAccepted, | ||
251 | HttpStatusCodeNonAuthoritative, | ||
252 | HttpStatusCodeNoContent, | ||
253 | HttpStatusCodeResetContent, | ||
254 | HttpStatusCodePartialContent, | ||
255 | HttpStatusCodeMultipleChoices, | ||
256 | HttpStatusCodePermanentRedirect, | ||
257 | HttpStatusCodeFound, | ||
258 | HttpStatusCodeSeeOther, | ||
259 | HttpStatusCodeNotModified, | ||
260 | HttpStatusCodeUseProxy, | ||
261 | HttpStatusCodeReserved306, | ||
262 | HttpStatusCodeTemporaryRedirect, | ||
263 | HttpStatusCodeBadRequest, | ||
264 | HttpStatusCodeNotAuthorized, | ||
265 | HttpStatusCodePaymentRequired, | ||
266 | HttpStatusCodeForbidden, | ||
267 | HttpStatusCodeNotFound, | ||
268 | HttpStatusCodeMethodNotAllowed, | ||
269 | HttpStatusCodeNotAcceptable, | ||
270 | HttpStatusCodeProxyAuthenticate, | ||
271 | HttpStatusCodeTimeOut, | ||
272 | HttpStatusCodeConflict, | ||
273 | HttpStatusCodeGone, | ||
274 | HttpStatusCodeLengthRequired, | ||
275 | HttpStatusCodePreconditionFailed, | ||
276 | HttpStatusCodeEntityTooLarge, | ||
277 | HttpStatusCodeUriTooLarge, | ||
278 | HttpStatusCodeUnsupportedMedia, | ||
279 | HttpStatusCodeRangeNotSatsified, | ||
280 | HttpStatusCodeExpectationFailed, | ||
281 | HttpStatusCodeServerError, | ||
282 | HttpStatusCodeNotImplemented, | ||
283 | HttpStatusCodeBadGateway, | ||
284 | HttpStatusCodeServiceUnavailable, | ||
285 | HttpStatusCodeGatewayTimeout, | ||
286 | HttpStatusCodeHttpVersionError | ||
287 | }; | ||
207 | 288 | ||
208 | public const string HttpStatusDescContinue = "Continue Request"; // 100 | 289 | // HTTP Status Descriptions (in status code order) |
209 | public const string HttpStatusDescSwitchingProtocols = "Switching Protocols"; // 101 | 290 | // This array must be kept strictly consistent with respect |
210 | 291 | // to the status code array above. | |
211 | public const string HttpStatusDescOK = "OK"; | 292 | |
212 | public const string HttpStatusDescCreated = "CREATED"; | 293 | public static readonly string[] HttpStatusDescArray = { |
213 | public const string HttpStatusDescAccepted = "ACCEPTED"; | 294 | "Continue Request", |
214 | public const string HttpStatusDescNonAuthoritative = "NON-AUTHORITATIVE INFORMATION"; | 295 | "Switching Protocols", |
215 | public const string HttpStatusDescNoContent = "NO CONTENT"; | 296 | "OK", |
216 | public const string HttpStatusDescResetContent = "RESET CONTENT"; | 297 | "CREATED", |
217 | public const string HttpStatusDescPartialContent = "PARTIAL CONTENT"; | 298 | "ACCEPTED", |
218 | 299 | "NON-AUTHORITATIVE INFORMATION", | |
219 | public const string HttpStatusDescMultipleChoices = "MULTIPLE CHOICES"; | 300 | "NO CONTENT", |
220 | public const string HttpStatusDescPermanentRedirect = "PERMANENT REDIRECT"; | 301 | "RESET CONTENT", |
221 | public const string HttpStatusDescFound = "FOUND"; | 302 | "PARTIAL CONTENT", |
222 | public const string HttpStatusDescSeeOther = "SEE OTHER"; | 303 | "MULTIPLE CHOICES", |
223 | public const string HttpStatusDescNotModified = "NOT MODIFIED"; | 304 | "PERMANENT REDIRECT", |
224 | public const string HttpStatusDescUseProxy = "USE PROXY"; | 305 | "FOUND", |
225 | public const string HttpStatusDescReserved306 = "RESERVED CODE 306"; | 306 | "SEE OTHER", |
226 | public const string HttpStatusDescTemporaryRedirect = "TEMPORARY REDIRECT"; | 307 | "NOT MODIFIED", |
227 | 308 | "USE PROXY", | |
228 | public const string HttpStatusDescBadRequest = "BAD REQUEST"; | 309 | "RESERVED CODE 306", |
229 | public const string HttpStatusDescNotAuthorized = "NOT AUTHORIZED"; | 310 | "TEMPORARY REDIRECT", |
230 | public const string HttpStatusDescPaymentRequired = "PAYMENT REQUIRED"; | 311 | "BAD REQUEST", |
231 | public const string HttpStatusDescForbidden = "FORBIDDEN"; | 312 | "NOT AUTHORIZED", |
232 | public const string HttpStatusDescNotFound = "NOT FOUND"; | 313 | "PAYMENT REQUIRED", |
233 | public const string HttpStatusDescMethodNotAllowed = "METHOD NOT ALLOWED"; | 314 | "FORBIDDEN", |
234 | public const string HttpStatusDescNotAcceptable = "NOT ACCEPTABLE"; | 315 | "NOT FOUND", |
235 | public const string HttpStatusDescProxyAuthenticate = "PROXY AUTHENTICATION REQUIRED"; | 316 | "METHOD NOT ALLOWED", |
236 | public const string HttpStatusDescTimeOut = "TIMEOUT"; | 317 | "NOT ACCEPTABLE", |
237 | public const string HttpStatusDescConflict = "CONFLICT"; | 318 | "PROXY AUTHENTICATION REQUIRED", |
238 | public const string HttpStatusDescGone = "GONE"; | 319 | "TIMEOUT", |
239 | public const string HttpStatusDescLengthRequired = "LENGTH REQUIRED"; | 320 | "CONFLICT", |
240 | public const string HttpStatusDescPreconditionFailed = "PRECONDITION FAILED"; | 321 | "GONE", |
241 | public const string HttpStatusDescEntityTooLarge = "ENTITY TOO LARGE"; | 322 | "LENGTH REQUIRED", |
242 | public const string HttpStatusDescUriTooLarge = "URI TOO LARGE"; | 323 | "PRECONDITION FAILED", |
243 | public const string HttpStatusDescUnsupportedMedia = "UNSUPPORTED MEDIA"; | 324 | "ENTITY TOO LARGE", |
244 | public const string HttpStatusDescRangeNotSatisfied = "RANGE NOT SATISFIED"; | 325 | "URI TOO LARGE", |
245 | public const string HttpStatusDescExpectationFailed = "EXPECTATION FAILED"; | 326 | "UNSUPPORTED MEDIA", |
246 | 327 | "RANGE NOT SATISFIED", | |
247 | public const string HttpStatusDescServerError = "SERVER ERROR"; | 328 | "EXPECTATION FAILED", |
248 | public const string HttpStatusDescNotImplemented = "NOT IMPLEMENTED"; | 329 | "SERVER ERROR", |
249 | public const string HttpStatusDescBadGateway = "BAD GATEWAY"; | 330 | "NOT IMPLEMENTED", |
250 | public const string HttpStatusDescServiceUnavailable = "SERVICE UNAVAILABLE"; | 331 | "BAD GATEWAY", |
251 | public const string HttpStatusDescGatewayTimeout = "GATEWAY TIMEOUT"; | 332 | "SERVICE UNAVAILABLE", |
252 | public const string HttpStatusDescHttpVersionError = "HTTP VERSION NOT SUPPORTED"; | 333 | "GATEWAY TIMEOUT", |
334 | "HTTP VERSION NOT SUPPORTED" | ||
335 | }; | ||
253 | 336 | ||
254 | // HTTP Headers | 337 | // HTTP Headers |
255 | 338 | ||
@@ -309,7 +392,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
309 | public const string AS_DIGEST = "Digest"; | 392 | public const string AS_DIGEST = "Digest"; |
310 | 393 | ||
311 | /// Supported Digest algorithms | 394 | /// Supported Digest algorithms |
312 | 395 | ||
313 | public const string Digest_MD5 = "MD5"; // assumedd efault if omitted | 396 | public const string Digest_MD5 = "MD5"; // assumedd efault if omitted |
314 | public const string Digest_MD5Sess = "MD5-sess"; | 397 | public const string Digest_MD5Sess = "MD5-sess"; |
315 | 398 | ||
@@ -357,7 +440,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
357 | int val = 0; | 440 | int val = 0; |
358 | int sum = 0; | 441 | int sum = 0; |
359 | string tmp = null; | 442 | string tmp = null; |
360 | 443 | ||
361 | if (hex != null) | 444 | if (hex != null) |
362 | { | 445 | { |
363 | tmp = hex.ToLower(); | 446 | tmp = hex.ToLower(); |
@@ -372,40 +455,21 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
372 | } | 455 | } |
373 | 456 | ||
374 | return sum; | 457 | return sum; |
375 | } | ||
376 | |||
377 | public static string Int2Hex8(int val) | ||
378 | { | ||
379 | string res = String.Empty; | ||
380 | for (int i = 0; i < 8; i++) | ||
381 | { | ||
382 | res = (val % 16) + res; | ||
383 | val = val / 16; | ||
384 | } | ||
385 | return res; | ||
386 | } | ||
387 | |||
388 | public static string ToHex32(int val) | ||
389 | { | ||
390 | return String.Empty; | ||
391 | } | ||
392 | 458 | ||
393 | public static string ToHex32(string val) | ||
394 | { | ||
395 | return String.Empty; | ||
396 | } | 459 | } |
397 | 460 | ||
398 | // Nonce management | 461 | // Nonce management |
399 | 462 | ||
400 | public static string NonceGenerator() | 463 | public static string NonceGenerator() |
401 | { | 464 | { |
402 | return StringToBase64(Guid.NewGuid().ToString()); | 465 | return StringToBase64(CreationDate + Guid.NewGuid().ToString()); |
403 | } | 466 | } |
404 | 467 | ||
405 | // Dump he specified data stream; | 468 | // Dump he specified data stream; |
406 | 469 | ||
407 | public static void Dump(byte[] data) | 470 | public static void Dump(byte[] data) |
408 | { | 471 | { |
472 | |||
409 | char[] buffer = new char[Rest.DumpLineSize]; | 473 | char[] buffer = new char[Rest.DumpLineSize]; |
410 | int cc = 0; | 474 | int cc = 0; |
411 | 475 | ||
@@ -415,7 +479,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
415 | if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); | 479 | if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); |
416 | 480 | ||
417 | if (i % 4 == 0) Console.Write(" "); | 481 | if (i % 4 == 0) Console.Write(" "); |
418 | // if (i%16 == 0) Console.Write(" "); | ||
419 | 482 | ||
420 | Console.Write("{0}",data[i].ToString("x2")); | 483 | Console.Write("{0}",data[i].ToString("x2")); |
421 | 484 | ||
@@ -431,6 +494,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
431 | Console.Write(" |"+(new String(buffer))+"|"); | 494 | Console.Write(" |"+(new String(buffer))+"|"); |
432 | cc = 0; | 495 | cc = 0; |
433 | } | 496 | } |
497 | |||
434 | } | 498 | } |
435 | 499 | ||
436 | // Finish off any incomplete line | 500 | // Finish off any incomplete line |
@@ -440,30 +504,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
440 | for (int i = cc ; i < Rest.DumpLineSize; i++) | 504 | for (int i = cc ; i < Rest.DumpLineSize; i++) |
441 | { | 505 | { |
442 | if (i % 4 == 0) Console.Write(" "); | 506 | if (i % 4 == 0) Console.Write(" "); |
443 | // if (i%16 == 0) Console.Write(" "); | 507 | Console.Write(" "); |
444 | Console.Write(" "); | ||
445 | buffer[i % Rest.DumpLineSize] = ' '; | 508 | buffer[i % Rest.DumpLineSize] = ' '; |
446 | } | 509 | } |
447 | Console.WriteLine(" |"+(new String(buffer))+"|"); | 510 | Console.WriteLine(" |"+(new String(buffer))+"|"); |
448 | } | 511 | } |
449 | else | 512 | else |
450 | { | 513 | { |
451 | Console.Write("\n"); | 514 | Console.Write("\n"); |
452 | } | 515 | } |
516 | |||
453 | } | 517 | } |
454 | } | ||
455 | 518 | ||
519 | } | ||
520 | |||
456 | // Local exception type | 521 | // Local exception type |
457 | 522 | ||
458 | public class RestException : Exception | 523 | public class RestException : Exception |
459 | { | 524 | { |
525 | |||
460 | internal int statusCode; | 526 | internal int statusCode; |
461 | internal string statusDesc; | 527 | internal string statusDesc; |
462 | internal string httpmethod; | 528 | internal string httpmethod; |
463 | internal string httppath; | 529 | internal string httppath; |
464 | 530 | ||
465 | public RestException(string msg) : base(msg) | 531 | public RestException(string msg) : base(msg) |
466 | { | 532 | { |
467 | } | 533 | } |
468 | } | 534 | } |
535 | |||
469 | } | 536 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs index 85748fa..a40d197 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs | |||
@@ -23,6 +23,7 @@ | |||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | ||
26 | */ | 27 | */ |
27 | 28 | ||
28 | using libsecondlife; | 29 | using libsecondlife; |
@@ -39,8 +40,10 @@ using OpenSim.Framework.Communications.Cache; | |||
39 | 40 | ||
40 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 41 | namespace OpenSim.ApplicationPlugins.Rest.Inventory |
41 | { | 42 | { |
43 | |||
42 | public class RestAssetServices : IRest | 44 | public class RestAssetServices : IRest |
43 | { | 45 | { |
46 | |||
44 | private bool enabled = false; | 47 | private bool enabled = false; |
45 | private string qPrefix = "assets"; | 48 | private string qPrefix = "assets"; |
46 | 49 | ||
@@ -49,6 +52,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
49 | 52 | ||
50 | public RestAssetServices() | 53 | public RestAssetServices() |
51 | { | 54 | { |
55 | |||
52 | Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); | 56 | Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); |
53 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | 57 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); |
54 | 58 | ||
@@ -69,6 +73,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
69 | enabled = true; | 73 | enabled = true; |
70 | 74 | ||
71 | Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); | 75 | Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); |
76 | |||
72 | } | 77 | } |
73 | 78 | ||
74 | // Post-construction, pre-enabled initialization opportunity | 79 | // Post-construction, pre-enabled initialization opportunity |
@@ -79,7 +84,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
79 | } | 84 | } |
80 | 85 | ||
81 | // Called by the plug-in to halt REST processing. Local processing is | 86 | // Called by the plug-in to halt REST processing. Local processing is |
82 | // disabled, and control blocks until all current processing has | 87 | // disabled, and control blocks until all current processing has |
83 | // completed. No new processing will be started | 88 | // completed. No new processing will be started |
84 | 89 | ||
85 | public void Close() | 90 | public void Close() |
@@ -106,14 +111,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
106 | 111 | ||
107 | private void DoAsset(RequestData rparm) | 112 | private void DoAsset(RequestData rparm) |
108 | { | 113 | { |
109 | if (!enabled) | 114 | |
110 | return; | 115 | if (!enabled) return; |
111 | 116 | ||
112 | AssetRequestData rdata = (AssetRequestData) rparm; | 117 | AssetRequestData rdata = (AssetRequestData) rparm; |
113 | 118 | ||
114 | Rest.Log.DebugFormat("{0} REST Asset handler ENTRY", MsgId); | 119 | Rest.Log.DebugFormat("{0} REST Asset handler ENTRY", MsgId); |
115 | 120 | ||
116 | // Now that we know this is a serious attempt to | 121 | // Now that we know this is a serious attempt to |
117 | // access inventory data, we should find out who | 122 | // access inventory data, we should find out who |
118 | // is asking, and make sure they are authorized | 123 | // is asking, and make sure they are authorized |
119 | // to do so. We need to validate the caller's | 124 | // to do so. We need to validate the caller's |
@@ -124,14 +129,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
124 | // With the present HTTP server we can't use the | 129 | // With the present HTTP server we can't use the |
125 | // builtin authentication mechanisms because they | 130 | // builtin authentication mechanisms because they |
126 | // would be enforced for all in-bound requests. | 131 | // would be enforced for all in-bound requests. |
127 | // Instead we look at the headers ourselves and | 132 | // Instead we look at the headers ourselves and |
128 | // handle authentication directly. | 133 | // handle authentication directly. |
129 | 134 | ||
130 | try | 135 | try |
131 | { | 136 | { |
132 | if (!rdata.IsAuthenticated) | 137 | if (!rdata.IsAuthenticated) |
133 | { | 138 | { |
134 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); | 139 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); |
135 | } | 140 | } |
136 | } | 141 | } |
137 | catch (RestException e) | 142 | catch (RestException e) |
@@ -139,13 +144,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
139 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | 144 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) |
140 | { | 145 | { |
141 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | 146 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); |
142 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | 147 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, |
143 | rdata.request.Headers.Get("Authorization")); | 148 | rdata.request.Headers.Get("Authorization")); |
144 | } | 149 | } |
145 | else | 150 | else |
146 | { | 151 | { |
147 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | 152 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); |
148 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, | 153 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, |
149 | rdata.request.Headers.Get("Authorization")); | 154 | rdata.request.Headers.Get("Authorization")); |
150 | } | 155 | } |
151 | throw (e); | 156 | throw (e); |
@@ -155,7 +160,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
155 | // the parameters we need, fail the request. Parameters do NOT include | 160 | // the parameters we need, fail the request. Parameters do NOT include |
156 | // any supplied query values. | 161 | // any supplied query values. |
157 | 162 | ||
158 | if (rdata.parameters.Length > 0) | 163 | if (rdata.Parameters.Length > 0) |
159 | { | 164 | { |
160 | switch (rdata.method) | 165 | switch (rdata.method) |
161 | { | 166 | { |
@@ -168,26 +173,27 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
168 | case "post" : | 173 | case "post" : |
169 | case "delete" : | 174 | case "delete" : |
170 | default : | 175 | default : |
171 | Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", | 176 | Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", |
172 | MsgId, rdata.method); | 177 | MsgId, rdata.method); |
173 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 178 | rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); |
174 | Rest.HttpStatusDescBadRequest); | ||
175 | break; | 179 | break; |
176 | } | 180 | } |
177 | } | 181 | } |
178 | else | 182 | else |
179 | { | 183 | { |
180 | Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); | 184 | Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); |
181 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | 185 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); |
182 | } | 186 | } |
183 | 187 | ||
184 | Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); | 188 | Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); |
189 | |||
185 | } | 190 | } |
186 | 191 | ||
187 | #endregion Interface | 192 | #endregion Interface |
188 | 193 | ||
189 | private void DoGet(AssetRequestData rdata) | 194 | private void DoGet(AssetRequestData rdata) |
190 | { | 195 | { |
196 | |||
191 | bool istexture = false; | 197 | bool istexture = false; |
192 | 198 | ||
193 | Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); | 199 | Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); |
@@ -195,14 +201,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
195 | // The only parameter we accept is an LLUUID for | 201 | // The only parameter we accept is an LLUUID for |
196 | // the asset | 202 | // the asset |
197 | 203 | ||
198 | if (rdata.parameters.Length == 1) | 204 | if (rdata.Parameters.Length == 1) |
199 | { | 205 | { |
200 | LLUUID uuid = new LLUUID(rdata.parameters[0]); | 206 | |
207 | LLUUID uuid = new LLUUID(rdata.Parameters[0]); | ||
201 | AssetBase asset = Rest.AssetServices.GetAsset(uuid, istexture); | 208 | AssetBase asset = Rest.AssetServices.GetAsset(uuid, istexture); |
202 | 209 | ||
203 | if (asset != null) | 210 | if (asset != null) |
204 | { | 211 | { |
205 | Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.parameters[0]); | 212 | |
213 | Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]); | ||
206 | 214 | ||
207 | rdata.initXmlWriter(); | 215 | rdata.initXmlWriter(); |
208 | 216 | ||
@@ -218,17 +226,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
218 | rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); | 226 | rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); |
219 | 227 | ||
220 | rdata.writer.WriteFullEndElement(); | 228 | rdata.writer.WriteFullEndElement(); |
229 | |||
221 | } | 230 | } |
222 | else | 231 | else |
223 | { | 232 | { |
224 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | 233 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); |
225 | rdata.Fail(Rest.HttpStatusCodeNotFound, | 234 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); |
226 | Rest.HttpStatusDescNotFound); | ||
227 | } | 235 | } |
228 | } | 236 | } |
229 | 237 | ||
230 | rdata.Complete(); | 238 | rdata.Complete(); |
231 | rdata.Respond("Asset " + rdata.method + ": Normal completion"); | 239 | rdata.Respond("Asset " + rdata.method + ": Normal completion"); |
240 | |||
232 | } | 241 | } |
233 | 242 | ||
234 | private void DoPut(AssetRequestData rdata) | 243 | private void DoPut(AssetRequestData rdata) |
@@ -238,7 +247,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
238 | // The only parameter we accept is an LLUUID for | 247 | // The only parameter we accept is an LLUUID for |
239 | // the asset | 248 | // the asset |
240 | 249 | ||
241 | if (rdata.parameters.Length == 1) | 250 | if (rdata.Parameters.Length == 1) |
242 | { | 251 | { |
243 | rdata.initXmlReader(); | 252 | rdata.initXmlReader(); |
244 | XmlReader xml = rdata.reader; | 253 | XmlReader xml = rdata.reader; |
@@ -246,12 +255,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
246 | if (!xml.ReadToFollowing("Asset")) | 255 | if (!xml.ReadToFollowing("Asset")) |
247 | { | 256 | { |
248 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); | 257 | Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); |
249 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 258 | rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); |
250 | Rest.HttpStatusDescBadRequest); | ||
251 | } | 259 | } |
252 | 260 | ||
253 | AssetBase asset = new AssetBase(); | 261 | AssetBase asset = new AssetBase(); |
254 | asset.ID = rdata.parameters[0]; | 262 | asset.ID = rdata.Parameters[0]; |
255 | asset.Name = xml.GetAttribute("name"); | 263 | asset.Name = xml.GetAttribute("name"); |
256 | asset.Description = xml.GetAttribute("desc"); | 264 | asset.Description = xml.GetAttribute("desc"); |
257 | asset.Type = SByte.Parse(xml.GetAttribute("type")); | 265 | asset.Type = SByte.Parse(xml.GetAttribute("type")); |
@@ -264,12 +272,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
264 | else | 272 | else |
265 | { | 273 | { |
266 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); | 274 | Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); |
267 | rdata.Fail(Rest.HttpStatusCodeNotFound, | 275 | rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); |
268 | Rest.HttpStatusDescNotFound); | ||
269 | } | 276 | } |
270 | 277 | ||
271 | rdata.Complete(); | 278 | rdata.Complete(); |
272 | rdata.Respond("Asset " + rdata.method + ": Normal completion"); | 279 | rdata.Respond("Asset " + rdata.method + ": Normal completion"); |
280 | |||
273 | } | 281 | } |
274 | 282 | ||
275 | internal class AssetRequestData : RequestData | 283 | internal class AssetRequestData : RequestData |
@@ -279,5 +287,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
279 | { | 287 | { |
280 | } | 288 | } |
281 | } | 289 | } |
290 | |||
282 | } | 291 | } |
283 | } | 292 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs index 9853f16..cb80846 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs | |||
@@ -23,6 +23,7 @@ | |||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | ||
26 | */ | 27 | */ |
27 | 28 | ||
28 | using System; | 29 | using System; |
@@ -34,8 +35,27 @@ using OpenSim.ApplicationPlugins.Rest; | |||
34 | 35 | ||
35 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 36 | namespace OpenSim.ApplicationPlugins.Rest.Inventory |
36 | { | 37 | { |
37 | public class RestHandler : RestPlugin, IHttpAgentHandler | 38 | |
39 | /// <remarks> | ||
40 | /// The class signature reveals the roles that RestHandler plays. | ||
41 | /// | ||
42 | /// [1] It is a sub-class of RestPlugin. It inherits and extends | ||
43 | /// the functionality of this class, constraining it to the | ||
44 | /// specific needs of this REST implementation. This relates | ||
45 | /// to the plug-in mechanism supported by OpenSim, the specifics | ||
46 | /// of which are mostly hidden by RestPlugin. | ||
47 | /// [2] IRestHandler describes the interface that this class | ||
48 | /// exports to service implementations. This is the services | ||
49 | /// management interface. | ||
50 | /// [3] IHttpAgentHandler describes the interface that is exported | ||
51 | /// to the BaseHttpServer in support of this particular HTTP | ||
52 | /// processing model. This is the request interface of the | ||
53 | /// handler. | ||
54 | /// </remarks> | ||
55 | |||
56 | public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler | ||
38 | { | 57 | { |
58 | |||
39 | /// <remarks> | 59 | /// <remarks> |
40 | /// The handler delegates are not noteworthy. The allocator allows | 60 | /// The handler delegates are not noteworthy. The allocator allows |
41 | /// a given handler to optionally subclass the base RequestData | 61 | /// a given handler to optionally subclass the base RequestData |
@@ -43,8 +63,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
43 | /// needed. | 63 | /// needed. |
44 | /// </remarks> | 64 | /// </remarks> |
45 | 65 | ||
46 | internal delegate void RestMethodHandler(RequestData rdata); | 66 | // internal delegate void RestMethodHandler(RequestData rdata); |
47 | internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); | 67 | // internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); |
48 | 68 | ||
49 | // Handler tables: both stream and REST are supported. The path handlers and their | 69 | // Handler tables: both stream and REST are supported. The path handlers and their |
50 | // respective allocators are stored in separate tables. | 70 | // respective allocators are stored in separate tables. |
@@ -64,10 +84,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
64 | /// <summary> | 84 | /// <summary> |
65 | /// This static initializer scans the ASSEMBLY for classes that | 85 | /// This static initializer scans the ASSEMBLY for classes that |
66 | /// export the IRest interface and builds a list of them. These | 86 | /// export the IRest interface and builds a list of them. These |
67 | /// are later activated by the handler. To add a new handler it | 87 | /// are later activated by the handler. To add a new handler it |
68 | /// is only necessary to create a new services class that implements | 88 | /// is only necessary to create a new services class that implements |
69 | /// the IRest interface, and recompile the handler. This gives | 89 | /// the IRest interface, and recompile the handler. This gives |
70 | /// all of the build-time flexibility of a modular approach | 90 | /// all of the build-time flexibility of a modular approach |
71 | /// while not introducing yet-another module loader. Note that | 91 | /// while not introducing yet-another module loader. Note that |
72 | /// multiple assembles can still be built, each with its own set | 92 | /// multiple assembles can still be built, each with its own set |
73 | /// of handlers. Examples of services classes are RestInventoryServices | 93 | /// of handlers. Examples of services classes are RestInventoryServices |
@@ -76,12 +96,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
76 | 96 | ||
77 | static RestHandler() | 97 | static RestHandler() |
78 | { | 98 | { |
99 | |||
79 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); | 100 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); |
80 | 101 | ||
81 | foreach (Module m in mods) | 102 | foreach (Module m in mods) |
82 | { | 103 | { |
83 | Type[] types = m.GetTypes(); | 104 | Type[] types = m.GetTypes(); |
84 | foreach (Type t in types) | 105 | foreach (Type t in types) |
85 | { | 106 | { |
86 | try | 107 | try |
87 | { | 108 | { |
@@ -97,6 +118,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
97 | } | 118 | } |
98 | } | 119 | } |
99 | } | 120 | } |
121 | |||
100 | } | 122 | } |
101 | 123 | ||
102 | #endregion local static state | 124 | #endregion local static state |
@@ -105,13 +127,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
105 | 127 | ||
106 | /// <summary> | 128 | /// <summary> |
107 | /// This routine loads all of the handlers discovered during | 129 | /// This routine loads all of the handlers discovered during |
108 | /// instance initialization. | 130 | /// instance initialization. |
109 | /// A table of all loaded and successfully constructed handlers | 131 | /// A table of all loaded and successfully constructed handlers |
110 | /// is built, and this table is then used by the constructor to | 132 | /// is built, and this table is then used by the constructor to |
111 | /// initialize each of the handlers in turn. | 133 | /// initialize each of the handlers in turn. |
112 | /// NOTE: The loading process does not automatically imply that | 134 | /// NOTE: The loading process does not automatically imply that |
113 | /// the handler has registered any kind of an interface, that | 135 | /// the handler has registered any kind of an interface, that |
114 | /// may be (optionally) done by the handler either during | 136 | /// may be (optionally) done by the handler either during |
115 | /// construction, or during initialization. | 137 | /// construction, or during initialization. |
116 | /// | 138 | /// |
117 | /// I was not able to make this code work within a constructor | 139 | /// I was not able to make this code work within a constructor |
@@ -124,6 +146,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
124 | { | 146 | { |
125 | if (!handlersLoaded) | 147 | if (!handlersLoaded) |
126 | { | 148 | { |
149 | |||
127 | ConstructorInfo ci; | 150 | ConstructorInfo ci; |
128 | Object ht; | 151 | Object ht; |
129 | 152 | ||
@@ -154,8 +177,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
154 | 177 | ||
155 | // Name is used to differentiate the message header. | 178 | // Name is used to differentiate the message header. |
156 | 179 | ||
157 | public override string Name | 180 | public override string Name |
158 | { | 181 | { |
159 | get { return "HANDLER"; } | 182 | get { return "HANDLER"; } |
160 | } | 183 | } |
161 | 184 | ||
@@ -168,15 +191,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
168 | 191 | ||
169 | // We have to rename these because we want | 192 | // We have to rename these because we want |
170 | // to be able to share the values with other | 193 | // to be able to share the values with other |
171 | // classes in our assembly and the base | 194 | // classes in our assembly and the base |
172 | // names are protected. | 195 | // names are protected. |
173 | 196 | ||
174 | internal string MsgId | 197 | public string MsgId |
175 | { | 198 | { |
176 | get { return base.MsgID; } | 199 | get { return base.MsgID; } |
177 | } | 200 | } |
178 | 201 | ||
179 | internal string RequestId | 202 | public string RequestId |
180 | { | 203 | { |
181 | get { return base.RequestID; } | 204 | get { return base.RequestID; } |
182 | } | 205 | } |
@@ -198,6 +221,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
198 | { | 221 | { |
199 | try | 222 | try |
200 | { | 223 | { |
224 | |||
201 | // This plugin will only be enabled if the broader | 225 | // This plugin will only be enabled if the broader |
202 | // REST plugin mechanism is enabled. | 226 | // REST plugin mechanism is enabled. |
203 | 227 | ||
@@ -208,7 +232,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
208 | // IsEnabled is implemented by the base class and | 232 | // IsEnabled is implemented by the base class and |
209 | // reflects an overall RestPlugin status | 233 | // reflects an overall RestPlugin status |
210 | 234 | ||
211 | if (!IsEnabled) | 235 | if (!IsEnabled) |
212 | { | 236 | { |
213 | Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); | 237 | Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); |
214 | return; | 238 | return; |
@@ -221,7 +245,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
221 | 245 | ||
222 | Rest.main = openSim; | 246 | Rest.main = openSim; |
223 | Rest.Plugin = this; | 247 | Rest.Plugin = this; |
224 | Rest.Comms = App.CommunicationsManager; | 248 | Rest.Comms = Rest.main.CommunicationsManager; |
225 | Rest.UserServices = Rest.Comms.UserService; | 249 | Rest.UserServices = Rest.Comms.UserService; |
226 | Rest.InventoryServices = Rest.Comms.InventoryService; | 250 | Rest.InventoryServices = Rest.Comms.InventoryService; |
227 | Rest.AssetServices = Rest.Comms.AssetCache; | 251 | Rest.AssetServices = Rest.Comms.AssetCache; |
@@ -234,7 +258,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
234 | Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); | 258 | Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); |
235 | Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); | 259 | Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); |
236 | Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); | 260 | Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); |
261 | Rest.Fill = Rest.Config.GetBoolean("path-fill",true); | ||
237 | Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); | 262 | Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); |
263 | Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true); | ||
238 | 264 | ||
239 | Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, | 265 | Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, |
240 | (Rest.Authenticate ? "" : "not ")); | 266 | (Rest.Authenticate ? "" : "not ")); |
@@ -248,6 +274,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
248 | Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, | 274 | Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, |
249 | (Rest.DumpAsset ? "" : "not ")); | 275 | (Rest.DumpAsset ? "" : "not ")); |
250 | 276 | ||
277 | // The supplied prefix MUST be absolute | ||
278 | |||
279 | if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator) | ||
280 | Rest.Prefix = Rest.UrlPathSeparator+Rest.Prefix; | ||
281 | |||
251 | // If data dumping is requested, report on the chosen line | 282 | // If data dumping is requested, report on the chosen line |
252 | // length. | 283 | // length. |
253 | 284 | ||
@@ -257,15 +288,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
257 | Rest.DumpLineSize); | 288 | Rest.DumpLineSize); |
258 | } | 289 | } |
259 | 290 | ||
260 | // Load all of the handlers present in the | 291 | // Load all of the handlers present in the |
261 | // assembly | 292 | // assembly |
262 | 293 | ||
263 | // In principle, as we're an application plug-in, | 294 | // In principle, as we're an application plug-in, |
264 | // most of what needs to be done could be done using | 295 | // most of what needs to be done could be done using |
265 | // static resources, however the Open Sim plug-in | 296 | // static resources, however the Open Sim plug-in |
266 | // model makes this an instance, so that's what we | 297 | // model makes this an instance, so that's what we |
267 | // need to be. | 298 | // need to be. |
268 | // There is only one Communications manager per | 299 | // There is only one Communications manager per |
269 | // server, and by inference, only one each of the | 300 | // server, and by inference, only one each of the |
270 | // user, asset, and inventory servers. So we can cache | 301 | // user, asset, and inventory servers. So we can cache |
271 | // those using a static initializer. | 302 | // those using a static initializer. |
@@ -308,12 +339,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
308 | { | 339 | { |
309 | Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); | 340 | Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); |
310 | } | 341 | } |
342 | |||
311 | } | 343 | } |
312 | 344 | ||
313 | /// <summary> | 345 | /// <summary> |
314 | /// In the interests of efficiency, and because we cannot determine whether | 346 | /// In the interests of efficiency, and because we cannot determine whether |
315 | /// or not this instance will actually be harvested, we clobber the only | 347 | /// or not this instance will actually be harvested, we clobber the only |
316 | /// anchoring reference to the working state for this plug-in. What the | 348 | /// anchoring reference to the working state for this plug-in. What the |
317 | /// call to close does is irrelevant to this class beyond knowing that it | 349 | /// call to close does is irrelevant to this class beyond knowing that it |
318 | /// can nullify the reference when it returns. | 350 | /// can nullify the reference when it returns. |
319 | /// To make sure everything is copacetic we make sure the primary interface | 351 | /// To make sure everything is copacetic we make sure the primary interface |
@@ -322,6 +354,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
322 | 354 | ||
323 | public override void Close() | 355 | public override void Close() |
324 | { | 356 | { |
357 | |||
325 | Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); | 358 | Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); |
326 | 359 | ||
327 | try | 360 | try |
@@ -329,11 +362,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
329 | RemoveAgentHandler(Rest.Name, this); | 362 | RemoveAgentHandler(Rest.Name, this); |
330 | } | 363 | } |
331 | catch (KeyNotFoundException){} | 364 | catch (KeyNotFoundException){} |
332 | 365 | ||
333 | foreach (IRest handler in handlers) | 366 | foreach (IRest handler in handlers) |
334 | { | 367 | { |
335 | handler.Close(); | 368 | handler.Close(); |
336 | } | 369 | } |
370 | |||
337 | } | 371 | } |
338 | 372 | ||
339 | #endregion overriding methods | 373 | #endregion overriding methods |
@@ -352,25 +386,57 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
352 | { | 386 | { |
353 | string path = request.RawUrl; | 387 | string path = request.RawUrl; |
354 | 388 | ||
389 | Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); | ||
390 | |||
355 | try | 391 | try |
356 | { | 392 | { |
357 | foreach (string key in pathHandlers.Keys) | 393 | foreach (string key in pathHandlers.Keys) |
358 | { | 394 | { |
395 | Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key); | ||
396 | |||
397 | // Note that Match will not necessarily find the handler that will | ||
398 | // actually be used - it does no test for the "closest" fit. It | ||
399 | // simply reflects that at least one possible handler exists. | ||
400 | |||
359 | if (path.StartsWith(key)) | 401 | if (path.StartsWith(key)) |
360 | { | 402 | { |
361 | return (path.Length == key.Length || | 403 | Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); |
362 | path.Substring(key.Length, 1) == Rest.UrlPathSeparator); | 404 | |
405 | // This apparently odd evaluation is needed to prevent a match | ||
406 | // on anything other than a URI token boundary. Otherwise we | ||
407 | // may match on URL's that were not intended for this handler. | ||
408 | |||
409 | return ( path.Length == key.Length || | ||
410 | path.Substring(key.Length,1) == Rest.UrlPathSeparator); | ||
411 | |||
363 | } | 412 | } |
364 | } | 413 | } |
365 | 414 | ||
366 | path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); | 415 | path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); |
416 | |||
367 | foreach (string key in streamHandlers.Keys) | 417 | foreach (string key in streamHandlers.Keys) |
368 | { | 418 | { |
419 | |||
420 | Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); | ||
421 | |||
422 | // Note that Match will not necessarily find the handler that will | ||
423 | // actually be used - it does no test for the "closest" fit. It | ||
424 | // simply reflects that at least one possible handler exists. | ||
425 | |||
369 | if (path.StartsWith(key)) | 426 | if (path.StartsWith(key)) |
370 | { | 427 | { |
371 | return true; | 428 | Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); |
429 | |||
430 | // This apparently odd evaluation is needed to prevent a match | ||
431 | // on anything other than a URI token boundary. Otherwise we | ||
432 | // may match on URL's that were not intended for this handler. | ||
433 | |||
434 | return ( path.Length == key.Length || | ||
435 | path.Substring(key.Length,1) == Rest.UrlPathSeparator); | ||
436 | |||
372 | } | 437 | } |
373 | } | 438 | } |
439 | |||
374 | } | 440 | } |
375 | catch (Exception e) | 441 | catch (Exception e) |
376 | { | 442 | { |
@@ -404,7 +470,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
404 | 470 | ||
405 | for (int i = 0; i < request.Headers.Count; i++) | 471 | for (int i = 0; i < request.Headers.Count; i++) |
406 | { | 472 | { |
407 | Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", | 473 | Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", |
408 | MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); | 474 | MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); |
409 | } | 475 | } |
410 | Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); | 476 | Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); |
@@ -415,8 +481,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
415 | 481 | ||
416 | try | 482 | try |
417 | { | 483 | { |
418 | handled = FindPathHandler(request, response) || | 484 | handled = ( FindPathHandler(request, response) || |
419 | FindStreamHandler(request, response); | 485 | FindStreamHandler(request, response) ); |
420 | } | 486 | } |
421 | catch (Exception e) | 487 | catch (Exception e) |
422 | { | 488 | { |
@@ -430,6 +496,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
430 | Rest.Log.DebugFormat("{0} EXIT", MsgId); | 496 | Rest.Log.DebugFormat("{0} EXIT", MsgId); |
431 | 497 | ||
432 | return handled; | 498 | return handled; |
499 | |||
433 | } | 500 | } |
434 | 501 | ||
435 | #endregion interface methods | 502 | #endregion interface methods |
@@ -477,6 +544,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
477 | } | 544 | } |
478 | 545 | ||
479 | return rdata.handled; | 546 | return rdata.handled; |
547 | |||
480 | } | 548 | } |
481 | 549 | ||
482 | /// <summary> | 550 | /// <summary> |
@@ -489,12 +557,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
489 | 557 | ||
490 | public void AddStreamHandler(string httpMethod, string path, RestMethod method) | 558 | public void AddStreamHandler(string httpMethod, string path, RestMethod method) |
491 | { | 559 | { |
560 | |||
492 | if (!IsEnabled) | 561 | if (!IsEnabled) |
493 | { | 562 | { |
494 | return; | 563 | return; |
495 | } | 564 | } |
496 | 565 | ||
497 | if (!path.StartsWith(Rest.Prefix)) | 566 | if (!path.StartsWith(Rest.Prefix)) |
498 | { | 567 | { |
499 | path = String.Format("{0}{1}", Rest.Prefix, path); | 568 | path = String.Format("{0}{1}", Rest.Prefix, path); |
500 | } | 569 | } |
@@ -512,6 +581,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
512 | { | 581 | { |
513 | Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); | 582 | Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); |
514 | } | 583 | } |
584 | |||
515 | } | 585 | } |
516 | 586 | ||
517 | /// <summary> | 587 | /// <summary> |
@@ -526,9 +596,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
526 | 596 | ||
527 | internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) | 597 | internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) |
528 | { | 598 | { |
599 | |||
529 | RequestData rdata = null; | 600 | RequestData rdata = null; |
530 | string bestMatch = null; | 601 | string bestMatch = null; |
531 | 602 | ||
532 | if (!IsEnabled) | 603 | if (!IsEnabled) |
533 | { | 604 | { |
534 | return false; | 605 | return false; |
@@ -551,6 +622,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
551 | 622 | ||
552 | if (!String.IsNullOrEmpty(bestMatch)) | 623 | if (!String.IsNullOrEmpty(bestMatch)) |
553 | { | 624 | { |
625 | |||
554 | rdata = pathAllocators[bestMatch](request, response); | 626 | rdata = pathAllocators[bestMatch](request, response); |
555 | 627 | ||
556 | Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); | 628 | Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); |
@@ -559,7 +631,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
559 | { | 631 | { |
560 | pathHandlers[bestMatch](rdata); | 632 | pathHandlers[bestMatch](rdata); |
561 | } | 633 | } |
562 | 634 | ||
563 | // A plugin generated error indicates a request-related error | 635 | // A plugin generated error indicates a request-related error |
564 | // that has been handled by the plugin. | 636 | // that has been handled by the plugin. |
565 | 637 | ||
@@ -567,9 +639,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
567 | { | 639 | { |
568 | Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); | 640 | Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); |
569 | } | 641 | } |
642 | |||
570 | } | 643 | } |
571 | 644 | ||
572 | return (rdata == null) ? false : rdata.handled; | 645 | return (rdata == null) ? false : rdata.handled; |
646 | |||
573 | } | 647 | } |
574 | 648 | ||
575 | /// <summary> | 649 | /// <summary> |
@@ -577,8 +651,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
577 | /// path as a key. If an entry already exists, it is replaced by the new one. | 651 | /// path as a key. If an entry already exists, it is replaced by the new one. |
578 | /// </summary> | 652 | /// </summary> |
579 | 653 | ||
580 | internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) | 654 | public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) |
581 | { | 655 | { |
656 | |||
582 | if (!IsEnabled) | 657 | if (!IsEnabled) |
583 | { | 658 | { |
584 | return; | 659 | return; |
@@ -600,6 +675,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
600 | 675 | ||
601 | pathHandlers.Add(path, mh); | 676 | pathHandlers.Add(path, mh); |
602 | pathAllocators.Add(path, ra); | 677 | pathAllocators.Add(path, ra); |
678 | |||
603 | } | 679 | } |
604 | } | 680 | } |
681 | |||
605 | } | 682 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs index 8a0eba5..6a0fdf2 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | |||
@@ -23,6 +23,7 @@ | |||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | ||
26 | */ | 27 | */ |
27 | 28 | ||
28 | using System; | 29 | using System; |
@@ -41,22 +42,30 @@ using Nini.Config; | |||
41 | 42 | ||
42 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | 43 | namespace OpenSim.ApplicationPlugins.Rest.Inventory |
43 | { | 44 | { |
45 | |||
44 | public class RestInventoryServices : IRest | 46 | public class RestInventoryServices : IRest |
45 | { | 47 | { |
48 | |||
49 | private static readonly int PARM_USERID = 0; | ||
50 | private static readonly int PARM_PATH = 1; | ||
51 | |||
46 | private bool enabled = false; | 52 | private bool enabled = false; |
47 | private string qPrefix = "inventory"; | 53 | private string qPrefix = "inventory"; |
48 | 54 | ||
55 | private static readonly string PRIVATE_ROOT_NAME = "My Inventory"; | ||
56 | |||
49 | /// <summary> | 57 | /// <summary> |
50 | /// A simple constructor is used to handle any once-only | 58 | /// The constructor makes sure that the service prefix is absolute |
51 | /// initialization of working classes. | 59 | /// and the registers the service handler and the allocator. |
52 | /// </summary> | 60 | /// </summary> |
53 | 61 | ||
54 | public RestInventoryServices() | 62 | public RestInventoryServices() |
55 | { | 63 | { |
64 | |||
56 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); | 65 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); |
57 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | 66 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); |
58 | 67 | ||
59 | // If a relative path was specified for the handler's domain, | 68 | // If a relative path was specified for the handler's domain, |
60 | // add the standard prefix to make it absolute, e.g. /admin | 69 | // add the standard prefix to make it absolute, e.g. /admin |
61 | 70 | ||
62 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | 71 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) |
@@ -73,6 +82,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
73 | enabled = true; | 82 | enabled = true; |
74 | 83 | ||
75 | Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); | 84 | Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); |
85 | |||
76 | } | 86 | } |
77 | 87 | ||
78 | /// <summary> | 88 | /// <summary> |
@@ -85,9 +95,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
85 | } | 95 | } |
86 | 96 | ||
87 | /// <summary> | 97 | /// <summary> |
88 | /// Called by the plug-in to halt REST processing. Local processing is | 98 | /// Called by the plug-in to halt service processing. Local processing is |
89 | /// disabled, and control blocks until all current processing has | 99 | /// disabled. |
90 | /// completed. No new processing will be started | ||
91 | /// </summary> | 100 | /// </summary> |
92 | 101 | ||
93 | public void Close() | 102 | public void Close() |
@@ -114,6 +123,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
114 | /// completes. All request-instance specific state is kept here. This | 123 | /// completes. All request-instance specific state is kept here. This |
115 | /// is registered when this service provider is registered. | 124 | /// is registered when this service provider is registered. |
116 | /// </summary> | 125 | /// </summary> |
126 | /// <param name=request>Inbound HTTP request information</param> | ||
127 | /// <param name=response>Outbound HTTP request information</param> | ||
128 | /// <param name=qPrefix>REST service domain prefix</param> | ||
129 | /// <returns>A RequestData instance suitable for this service</returns> | ||
117 | 130 | ||
118 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) | 131 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) |
119 | { | 132 | { |
@@ -123,12 +136,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
123 | /// <summary> | 136 | /// <summary> |
124 | /// This method is registered with the handler when this service provider | 137 | /// This method is registered with the handler when this service provider |
125 | /// is initialized. It is called whenever the plug-in identifies this service | 138 | /// is initialized. It is called whenever the plug-in identifies this service |
126 | /// provider as the best match. | 139 | /// provider as the best match for a given request. |
127 | /// It handles all aspects of inventory REST processing. | 140 | /// It handles all aspects of inventory REST processing, i.e. /admin/inventory |
128 | /// </summary> | 141 | /// </summary> |
142 | /// <param name=hdata>A consolidated HTTP request work area</param> | ||
129 | 143 | ||
130 | private void DoInventory(RequestData hdata) | 144 | private void DoInventory(RequestData hdata) |
131 | { | 145 | { |
146 | |||
132 | InventoryRequestData rdata = (InventoryRequestData) hdata; | 147 | InventoryRequestData rdata = (InventoryRequestData) hdata; |
133 | 148 | ||
134 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); | 149 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); |
@@ -140,7 +155,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
140 | return; | 155 | return; |
141 | } | 156 | } |
142 | 157 | ||
143 | // Now that we know this is a serious attempt to | 158 | // Now that we know this is a serious attempt to |
144 | // access inventory data, we should find out who | 159 | // access inventory data, we should find out who |
145 | // is asking, and make sure they are authorized | 160 | // is asking, and make sure they are authorized |
146 | // to do so. We need to validate the caller's | 161 | // to do so. We need to validate the caller's |
@@ -151,14 +166,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
151 | // With the present HTTP server we can't use the | 166 | // With the present HTTP server we can't use the |
152 | // builtin authentication mechanisms because they | 167 | // builtin authentication mechanisms because they |
153 | // would be enforced for all in-bound requests. | 168 | // would be enforced for all in-bound requests. |
154 | // Instead we look at the headers ourselves and | 169 | // Instead we look at the headers ourselves and |
155 | // handle authentication directly. | 170 | // handle authentication directly. |
156 | 171 | ||
157 | try | 172 | try |
158 | { | 173 | { |
159 | if (!rdata.IsAuthenticated) | 174 | if (!rdata.IsAuthenticated) |
160 | { | 175 | { |
161 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); | 176 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); |
162 | } | 177 | } |
163 | } | 178 | } |
164 | catch (RestException e) | 179 | catch (RestException e) |
@@ -178,43 +193,43 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
178 | 193 | ||
179 | Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | 194 | Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); |
180 | 195 | ||
181 | /// <remarks> | 196 | // We can only get here if we are authorized |
182 | /// We can only get here if we are authorized | 197 | // |
183 | /// | 198 | // The requestor may have specified an LLUUID or |
184 | /// The requestor may have specified an LLUUID or | 199 | // a conjoined FirstName LastName string. We'll |
185 | /// a conjoined FirstName LastName string. We'll | 200 | // try both. If we fail with the first, UUID, |
186 | /// try both. If we fail with the first, UUID, | 201 | // attempt, we try the other. As an example, the |
187 | /// attempt, we try the other. As an example, the | 202 | // URI for a valid inventory request might be: |
188 | /// URI for a valid inventory request might be: | 203 | // |
189 | /// | 204 | // http://<host>:<port>/admin/inventory/Arthur Dent |
190 | /// http://<host>:<port>/admin/inventory/Arthur Dent | 205 | // |
191 | /// | 206 | // Indicating that this is an inventory request for |
192 | /// Indicating that this is an inventory request for | 207 | // an avatar named Arthur Dent. This is ALL that is |
193 | /// an avatar named Arthur Dent. This is ALl that is | 208 | // required to designate a GET for an entire |
194 | /// required to designate a GET for an entire | 209 | // inventory. |
195 | /// inventory. | 210 | // |
196 | /// </remarks> | ||
197 | |||
198 | // Do we have at least a user agent name? | 211 | // Do we have at least a user agent name? |
199 | 212 | ||
200 | if (rdata.parameters.Length < 1) | 213 | if (rdata.Parameters.Length < 1) |
201 | { | 214 | { |
202 | Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); | 215 | Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); |
203 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": No user identity specified"); | 216 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); |
204 | } | 217 | } |
205 | 218 | ||
206 | // The first parameter MUST be the agent identification, either an LLUUID | 219 | // The first parameter MUST be the agent identification, either an LLUUID |
207 | // or a space-separated First-name Last-Name specification. | 220 | // or a space-separated First-name Last-Name specification. We check for |
221 | // an LLUUID first, if anyone names their character using a valid LLUUID | ||
222 | // that identifies another existing avatar will cause this a problem... | ||
208 | 223 | ||
209 | try | 224 | try |
210 | { | 225 | { |
211 | rdata.uuid = new LLUUID(rdata.parameters[0]); | 226 | rdata.uuid = new LLUUID(rdata.Parameters[PARM_USERID]); |
212 | Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId); | 227 | Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId); |
213 | rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); | 228 | rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); |
214 | } | 229 | } |
215 | catch | 230 | catch |
216 | { | 231 | { |
217 | string[] names = rdata.parameters[0].Split(Rest.CA_SPACE); | 232 | string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); |
218 | if (names.Length == 2) | 233 | if (names.Length == 2) |
219 | { | 234 | { |
220 | Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); | 235 | Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); |
@@ -222,23 +237,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
222 | } | 237 | } |
223 | else | 238 | else |
224 | { | 239 | { |
225 | Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | 240 | Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); |
226 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": invalid user identity"); | 241 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); |
227 | } | 242 | } |
228 | } | 243 | } |
229 | 244 | ||
230 | // If the user rpofile is null then either the server is broken, or the | 245 | // If the user profile is null then either the server is broken, or the |
231 | // user is not known. We always assume the latter case. | 246 | // user is not known. We always assume the latter case. |
232 | 247 | ||
233 | if (rdata.userProfile != null) | 248 | if (rdata.userProfile != null) |
234 | { | 249 | { |
235 | Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", | 250 | Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", |
236 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 251 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
237 | } | 252 | } |
238 | else | 253 | else |
239 | { | 254 | { |
240 | Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); | 255 | Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path); |
241 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": unrecognized user identity"); | 256 | rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); |
242 | } | 257 | } |
243 | 258 | ||
244 | // If we get to here, then we have effectively validated the user's | 259 | // If we get to here, then we have effectively validated the user's |
@@ -254,17 +269,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
254 | // response is not recieved in a timely fashion. | 269 | // response is not recieved in a timely fashion. |
255 | 270 | ||
256 | rdata.uuid = rdata.userProfile.ID; | 271 | rdata.uuid = rdata.userProfile.ID; |
257 | 272 | ||
258 | if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) | 273 | if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) |
259 | { | 274 | { |
275 | |||
260 | rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid); | 276 | rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid); |
261 | 277 | ||
262 | Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", | 278 | Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", |
263 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 279 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
264 | 280 | ||
265 | Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory); | 281 | Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory); |
266 | 282 | ||
267 | Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", | 283 | Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", |
268 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 284 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
269 | 285 | ||
270 | lock (rdata) | 286 | lock (rdata) |
@@ -277,16 +293,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
277 | 293 | ||
278 | if (rdata.root == null) | 294 | if (rdata.root == null) |
279 | { | 295 | { |
280 | Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}", | 296 | Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}", |
281 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 297 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
282 | rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError+": inventory retrieval failed"); | 298 | rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed"); |
283 | } | 299 | } |
300 | |||
284 | } | 301 | } |
285 | else | 302 | else |
286 | { | 303 | { |
287 | Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}", | 304 | Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}", |
288 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 305 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
289 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no inventory for user"); | 306 | rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user"); |
290 | } | 307 | } |
291 | 308 | ||
292 | // If we get here, then we have successfully retrieved the user's information | 309 | // If we get here, then we have successfully retrieved the user's information |
@@ -294,34 +311,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
294 | 311 | ||
295 | switch (rdata.method) | 312 | switch (rdata.method) |
296 | { | 313 | { |
297 | case Rest.HEAD : // Do the processing, set the status code, suppress entity | ||
298 | DoGet(rdata); | ||
299 | rdata.buffer = null; | ||
300 | break; | ||
301 | 314 | ||
302 | case Rest.GET : // Do the processing, set the status code, return entity | 315 | case Rest.HEAD : // Do the processing, set the status code, suppress entity |
303 | DoGet(rdata); | 316 | DoGet(rdata); |
304 | break; | 317 | rdata.buffer = null; |
318 | break; | ||
305 | 319 | ||
306 | case Rest.PUT : // Add new information | 320 | case Rest.GET : // Do the processing, set the status code, return entity |
307 | DoPut(rdata); | 321 | DoGet(rdata); |
308 | break; | 322 | break; |
309 | 323 | ||
310 | case Rest.POST : // Update (replace) | 324 | case Rest.PUT : // Update named element |
311 | DoPost(rdata); | 325 | DoUpdate(rdata); |
312 | break; | 326 | break; |
313 | 327 | ||
314 | case Rest.DELETE : // Delete information | 328 | case Rest.POST : // Add new information to identified context. |
315 | DoDelete(rdata); | 329 | DoExtend(rdata); |
316 | break; | 330 | break; |
317 | 331 | ||
318 | default : | 332 | case Rest.DELETE : // Delete information |
319 | Rest.Log.DebugFormat("{0} Method {1} not supported for {2}", | 333 | DoDelete(rdata); |
320 | MsgId, rdata.method, rdata.path); | 334 | break; |
321 | rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | 335 | |
322 | Rest.HttpStatusDescMethodNotAllowed+": "+rdata.method+" not supported"); | 336 | default : |
323 | break; | 337 | Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", |
338 | MsgId, rdata.method, rdata.path); | ||
339 | rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, rdata.method+" not supported"); | ||
340 | break; | ||
324 | } | 341 | } |
342 | |||
325 | } | 343 | } |
326 | 344 | ||
327 | #endregion Interface | 345 | #endregion Interface |
@@ -333,84 +351,97 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
333 | /// Any remaining parameters are used to locate the | 351 | /// Any remaining parameters are used to locate the |
334 | /// corresponding subtree based upon node name. | 352 | /// corresponding subtree based upon node name. |
335 | /// </summary> | 353 | /// </summary> |
354 | /// <param name=rdata>HTTP service request work area</param> | ||
336 | 355 | ||
337 | private void DoGet(InventoryRequestData rdata) | 356 | private void DoGet(InventoryRequestData rdata) |
338 | { | 357 | { |
358 | |||
339 | rdata.initXmlWriter(); | 359 | rdata.initXmlWriter(); |
340 | 360 | ||
341 | rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); | 361 | rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); |
342 | 362 | ||
343 | // If there was only one parameter, then the entire | ||
344 | // inventory is being requested. | ||
345 | |||
346 | if (rdata.parameters.Length == 1) | ||
347 | { | ||
348 | formatInventory(rdata, rdata.root, String.Empty); | ||
349 | } | ||
350 | |||
351 | // If there are additional parameters, then these represent | 363 | // If there are additional parameters, then these represent |
352 | // a path relative to the root of the inventory. This path | 364 | // a path relative to the root of the inventory. This path |
353 | // must be traversed before we format the sub-tree thus | 365 | // must be traversed before we format the sub-tree thus |
354 | // identified. | 366 | // identified. |
355 | 367 | ||
356 | else | 368 | traverse(rdata, rdata.root, PARM_PATH); |
357 | { | 369 | |
358 | traverseInventory(rdata, rdata.root, 1); | 370 | // Close all open elements |
359 | } | ||
360 | 371 | ||
361 | rdata.writer.WriteFullEndElement(); | 372 | rdata.writer.WriteFullEndElement(); |
362 | 373 | ||
374 | // Indicate a successful request | ||
375 | |||
363 | rdata.Complete(); | 376 | rdata.Complete(); |
364 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
365 | } | ||
366 | 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 | |||
367 | /// <summary> | 385 | /// <summary> |
368 | /// In the case of the inventory, and probably in general, | 386 | /// In the case of the inventory, and probably in general, |
369 | /// the distinction between PUT and POST is not always | 387 | /// the distinction between PUT and POST is not always |
370 | /// easy to discern. Adding a directory can be viewed as | 388 | /// easy to discern. The standard is badly worded in places, |
389 | /// and adding a node to a hierarchy can be viewed as | ||
371 | /// an addition, or as a modification to the inventory as | 390 | /// an addition, or as a modification to the inventory as |
372 | /// a whole. This is exacerbated by a lack of consistency | 391 | /// a whole. This is exacerbated by an unjustified lack of |
373 | /// across different implementations. | 392 | /// consistency across different implementations. |
374 | /// | 393 | /// |
375 | /// For OpenSim POST is an update and PUT is an addition. | 394 | /// For OpenSim PUT is an update and POST is an addition. This |
395 | /// is the behavior required by the HTTP specification and | ||
396 | /// therefore as required by REST. | ||
376 | /// | 397 | /// |
377 | /// The best way to exaplain the distinction is to | 398 | /// The best way to explain the distinction is to |
378 | /// consider the relationship between the URI and the | 399 | /// consider the relationship between the URI and the |
379 | /// entity in question. For POST, the URI identifies the | 400 | /// enclosed entity. For PUT, the URI identifies the |
380 | /// entity to be modified or replaced. | 401 | /// actual entity to be modified or replaced, i.e. the |
381 | /// If the operation is PUT,then the URI describes the | 402 | /// enclosed entity. |
403 | /// | ||
404 | /// If the operation is POST,then the URI describes the | ||
382 | /// context into which the new entity will be added. | 405 | /// context into which the new entity will be added. |
383 | /// | 406 | /// |
384 | /// As an example, suppose the URI contains: | 407 | /// As an example, suppose the URI contains: |
385 | /// /admin/inventory/Clothing | 408 | /// /admin/inventory/Clothing |
386 | /// | 409 | /// |
387 | /// A POST request will result in some modification of | 410 | /// A PUT request will normally result in some modification of |
388 | /// the folder or item named "Clothing". Whereas a PUT | 411 | /// the folder or item named "Clothing". Whereas a POST |
389 | /// request will add some new information into the | 412 | /// request will normally add some new information into the |
390 | /// content identified by Clothing. It follows from this | 413 | /// content identified by Clothing. It follows from this |
391 | /// that for PUT, the element identified by the URI must | 414 | /// that for POST, the element identified by the URI MUST |
392 | /// be a folder. | 415 | /// be a folder. |
393 | /// </summary> | 416 | /// </summary> |
394 | 417 | ||
395 | /// <summary> | 418 | /// <summary> |
396 | /// PUT adds new information to the inventory in the | 419 | /// POST adds new information to the inventory in the |
397 | /// context identified by the URI. | 420 | /// context identified by the URI. |
398 | /// </summary> | 421 | /// </summary> |
422 | /// <param name=rdata>HTTP service request work area</param> | ||
399 | 423 | ||
400 | private void DoPut(InventoryRequestData rdata) | 424 | private void DoExtend(InventoryRequestData rdata) |
401 | { | 425 | { |
426 | |||
427 | bool created = false; | ||
428 | bool modified = false; | ||
429 | string newnode = String.Empty; | ||
430 | |||
402 | // Resolve the context node specified in the URI. Entity | 431 | // Resolve the context node specified in the URI. Entity |
403 | // data will be ADDED beneath this node. | 432 | // data will be ADDED beneath this node. rdata already contains |
433 | // information about the current content of the user's | ||
434 | // inventory. | ||
404 | 435 | ||
405 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | 436 | Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); |
406 | 437 | ||
407 | // Processing depends upon the type of inventory node | 438 | // Processing depends upon the type of inventory node |
408 | // identified in the URI. This is the CONTEXT for the | 439 | // identified in the URI. This is the CONTEXT for the |
409 | // change. We either got a context or we threw an | 440 | // change. We either got a context or we threw an |
410 | // exception. | 441 | // exception. |
411 | 442 | ||
412 | // It follows that we can only add information if the URI | 443 | // It follows that we can only add information if the URI |
413 | // has identified a folder. So only a type of folder is supported | 444 | // has identified a folder. So only a type of folder is supported |
414 | // in this case. | 445 | // in this case. |
415 | 446 | ||
416 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | 447 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || |
@@ -430,16 +461,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
430 | // [1] A (possibly empty) set of folders. | 461 | // [1] A (possibly empty) set of folders. |
431 | // [2] A (possibly empty) set of items. | 462 | // [2] A (possibly empty) set of items. |
432 | // [3] A (possibly empty) set of assets. | 463 | // [3] A (possibly empty) set of assets. |
433 | // If all of these are empty, then the PUT is a harmless no-operation. | 464 | // If all of these are empty, then the POST is a harmless no-operation. |
434 | 465 | ||
435 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | 466 | XmlInventoryCollection entity = ReconstituteEntity(rdata); |
436 | 467 | ||
437 | // Inlined assets can be included in entity. These must be incorporated into | 468 | // Inlined assets can be included in entity. These must be incorporated into |
438 | // the asset database before we attempt to update the inventory. If anything | 469 | // the asset database before we attempt to update the inventory. If anything |
439 | // fails, return a failure to requestor. | 470 | // fails, return a failure to requestor. |
440 | 471 | ||
441 | if (entity.Assets.Count > 0) | 472 | if (entity.Assets.Count > 0) |
442 | { | 473 | { |
474 | |||
443 | Rest.Log.DebugFormat("{0} Adding {1} assets to server", | 475 | Rest.Log.DebugFormat("{0} Adding {1} assets to server", |
444 | MsgId, entity.Assets.Count); | 476 | MsgId, entity.Assets.Count); |
445 | 477 | ||
@@ -449,11 +481,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
449 | MsgId, asset.ID, asset.Type, asset.Name); | 481 | MsgId, asset.ID, asset.Type, asset.Name); |
450 | Rest.AssetServices.AddAsset(asset); | 482 | Rest.AssetServices.AddAsset(asset); |
451 | 483 | ||
452 | if (Rest.DumpAsset) | 484 | created = true; |
485 | rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1}<p>", | ||
486 | asset.Name, asset.ID)); | ||
487 | |||
488 | if (Rest.DEBUG && Rest.DumpAsset) | ||
453 | { | 489 | { |
454 | Rest.Dump(asset.Data); | 490 | Rest.Dump(asset.Data); |
455 | } | 491 | } |
492 | |||
456 | } | 493 | } |
494 | |||
457 | } | 495 | } |
458 | 496 | ||
459 | // Modify the context using the collection of folders and items | 497 | // Modify the context using the collection of folders and items |
@@ -461,6 +499,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
461 | 499 | ||
462 | foreach (InventoryFolderBase folder in entity.Folders) | 500 | foreach (InventoryFolderBase folder in entity.Folders) |
463 | { | 501 | { |
502 | |||
464 | InventoryFolderBase found; | 503 | InventoryFolderBase found; |
465 | 504 | ||
466 | // If the parentID is zero, then this folder is going | 505 | // If the parentID is zero, then this folder is going |
@@ -468,9 +507,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
468 | // may have already set the parent ID explicitly, in which | 507 | // may have already set the parent ID explicitly, in which |
469 | // case we don't have to do it here. | 508 | // case we don't have to do it here. |
470 | 509 | ||
471 | if (folder.ParentID == LLUUID.Zero) | 510 | if (folder.ParentID == LLUUID.Zero || folder.ParentID == context.ID) |
472 | { | 511 | { |
512 | if (newnode != String.Empty) | ||
513 | { | ||
514 | Rest.Log.DebugFormat("{0} Too many resources", MsgId); | ||
515 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed"); | ||
516 | } | ||
473 | folder.ParentID = context.ID; | 517 | folder.ParentID = context.ID; |
518 | newnode = folder.Name; | ||
474 | } | 519 | } |
475 | 520 | ||
476 | // Search the existing inventory for an existing entry. If | 521 | // Search the existing inventory for an existing entry. If |
@@ -496,12 +541,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
496 | { | 541 | { |
497 | Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); | 542 | Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); |
498 | Rest.InventoryServices.MoveFolder(folder); | 543 | Rest.InventoryServices.MoveFolder(folder); |
544 | |||
545 | modified = true; | ||
546 | rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1}<p>", | ||
547 | folder.Name, folder.ID)); | ||
499 | } | 548 | } |
500 | else | 549 | else |
501 | { | 550 | { |
502 | Rest.Log.DebugFormat("{0} Adding new folder", MsgId); | 551 | Rest.Log.DebugFormat("{0} Adding new folder", MsgId); |
503 | Rest.InventoryServices.AddFolder(folder); | 552 | Rest.InventoryServices.AddFolder(folder); |
553 | |||
554 | created = true; | ||
555 | rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1}<p>", | ||
556 | folder.Name, folder.ID)); | ||
557 | |||
504 | } | 558 | } |
559 | |||
505 | } | 560 | } |
506 | 561 | ||
507 | // Now we repeat a similar process for the items included | 562 | // Now we repeat a similar process for the items included |
@@ -509,6 +564,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
509 | 564 | ||
510 | foreach (InventoryItemBase item in entity.Items) | 565 | foreach (InventoryItemBase item in entity.Items) |
511 | { | 566 | { |
567 | |||
512 | InventoryItemBase found = null; | 568 | InventoryItemBase found = null; |
513 | 569 | ||
514 | // If the parentID is zero, then this is going | 570 | // If the parentID is zero, then this is going |
@@ -519,7 +575,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
519 | item.Folder = context.ID; | 575 | item.Folder = context.ID; |
520 | } | 576 | } |
521 | 577 | ||
522 | // Determine whether this is a new item or a | 578 | // Determine whether this is a new item or a |
523 | // replacement definition. | 579 | // replacement definition. |
524 | 580 | ||
525 | foreach (InventoryItemBase xi in rdata.items) | 581 | foreach (InventoryItemBase xi in rdata.items) |
@@ -537,60 +593,90 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
537 | Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", | 593 | Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", |
538 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | 594 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); |
539 | Rest.InventoryServices.UpdateItem(item); | 595 | Rest.InventoryServices.UpdateItem(item); |
596 | modified = true; | ||
597 | rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1}<p>", item.Name, item.ID)); | ||
540 | } | 598 | } |
541 | else | 599 | else |
542 | { | 600 | { |
543 | Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", | 601 | Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", |
544 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | 602 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); |
545 | Rest.InventoryServices.AddItem(item); | 603 | Rest.InventoryServices.AddItem(item); |
604 | created = true; | ||
605 | rdata.appendStatus(String.Format("<p> Created item {2}, UUID {3}<p>", item.Name, item.ID)); | ||
546 | } | 606 | } |
607 | |||
547 | } | 608 | } |
609 | |||
610 | if (created) | ||
611 | { | ||
612 | // Must include a location header with a URI that identifies the new resource. | ||
613 | rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}/{2}", | ||
614 | rdata.hostname+":"+rdata.port,rdata.path,newnode)); | ||
615 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
616 | } | ||
617 | else | ||
618 | { | ||
619 | if (modified) | ||
620 | { | ||
621 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
622 | } | ||
623 | else | ||
624 | { | ||
625 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
626 | } | ||
627 | } | ||
628 | |||
629 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
630 | |||
548 | } | 631 | } |
549 | else | 632 | else |
550 | { | 633 | { |
551 | Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", | 634 | Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", |
552 | MsgId, rdata.method, rdata.path, InventoryNode.GetType()); | 635 | MsgId, rdata.method, rdata.path, InventoryNode.GetType()); |
553 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 636 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context"); |
554 | Rest.HttpStatusDescBadRequest+": invalid resource context"); | ||
555 | } | 637 | } |
556 | 638 | ||
557 | rdata.Complete(); | ||
558 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
559 | } | 639 | } |
560 | 640 | ||
561 | /// <summary> | 641 | /// <summary> |
562 | /// POST updates the URI-identified element in the inventory. This | 642 | /// PUT updates the URI-identified element in the inventory. This |
563 | /// is actually far more flexible than it might at first sound. For | 643 | /// is actually far more flexible than it might at first sound. For |
564 | /// POST the URI serves two purposes: | 644 | /// PUT the URI serves two purposes: |
565 | /// [1] It identifies the user whose inventory is to be | 645 | /// [1] It identifies the user whose inventory is to be |
566 | /// processed. | 646 | /// processed. |
567 | /// [2] It optionally specifies a subtree of the inventory | 647 | /// [2] It optionally specifies a subtree of the inventory |
568 | /// that is to be used to resolve any relative subtree | 648 | /// that is to be used to resolve any relative subtree |
569 | /// specifications in the entity. If nothing is specified | 649 | /// specifications in the entity. If nothing is specified |
570 | /// then the whole inventory is implied. | 650 | /// then the whole of the private inventory is implied. |
571 | /// Please note that the subtree specified by the URI is only relevant | 651 | /// Please note that the subtree specified by the URI is only relevant |
572 | /// to an entity containing a URI relative specification, i.e. one or | 652 | /// to an entity containing a URI relative specification, i.e. one or |
573 | /// more elements do not specify parent folder information. These | 653 | /// more elements do not specify parent folder information. These |
574 | /// elements will be implicitly referenced within the context identified | 654 | /// elements will be implicitly referenced within the context identified |
575 | /// by the URI. | 655 | /// by the URI. |
576 | /// If an element in the entity specifies an explicit parent folder, then | 656 | /// If an element in the entity specifies an explicit parent folder, then |
577 | /// that parent is effective, regardless of any value specified in the | 657 | /// that parent is effective, regardless of any value specified in the |
578 | /// URI. If the parent does not exist, then the element, and any dependent | 658 | /// URI. If the parent does not exist, then the element, and any dependent |
579 | /// elements, are ignored. This case is actually detected and handled | 659 | /// elements, are ignored. This case is actually detected and handled |
580 | /// during the reconstitution process. | 660 | /// during the reconstitution process. |
581 | /// </summary> | 661 | /// </summary> |
662 | /// <param name=rdata>HTTP service request work area</param> | ||
582 | 663 | ||
583 | private void DoPost(InventoryRequestData rdata) | 664 | private void DoUpdate(InventoryRequestData rdata) |
584 | { | 665 | { |
585 | int count = 0; | 666 | |
667 | int count = 0; | ||
668 | bool created = false; | ||
669 | bool modified = false; | ||
586 | 670 | ||
587 | // Resolve the inventory node that is to be modified. | 671 | // Resolve the inventory node that is to be modified. |
672 | // rdata already contains information about the current | ||
673 | // content of the user's inventory. | ||
588 | 674 | ||
589 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | 675 | Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); |
590 | 676 | ||
591 | // As long as we have a node, then we have something | 677 | // As long as we have a node, then we have something |
592 | // meaningful to do, unlike PUT. So we reconstitute the | 678 | // meaningful to do, unlike POST. So we reconstitute the |
593 | // subtree before doing anything else. Note that we | 679 | // subtree before doing anything else. Note that we |
594 | // etiher got a valid node or we threw an exception. | 680 | // etiher got a valid node or we threw an exception. |
595 | 681 | ||
596 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | 682 | XmlInventoryCollection entity = ReconstituteEntity(rdata); |
@@ -612,46 +698,77 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
612 | 698 | ||
613 | Rest.AssetServices.AddAsset(asset); | 699 | Rest.AssetServices.AddAsset(asset); |
614 | 700 | ||
615 | if (Rest.DumpAsset) | 701 | created = true; |
702 | rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1}<p>", asset.Name, asset.ID)); | ||
703 | |||
704 | if (Rest.DEBUG && Rest.DumpAsset) | ||
616 | { | 705 | { |
617 | Rest.Dump(asset.Data); | 706 | Rest.Dump(asset.Data); |
618 | } | 707 | } |
708 | |||
619 | } | 709 | } |
620 | } | 710 | } |
621 | 711 | ||
622 | /// <summary> | 712 | // The URI specifies either a folder or an item to be updated. |
623 | /// The URI specifies either a folder or an item to be updated. | 713 | // |
624 | /// </summary> | 714 | // The root node in the entity will replace the node identified |
625 | /// <remarks> | 715 | // by the URI. This means the parent will remain the same, but |
626 | /// The root node in the entity will replace the node identified | 716 | // any or all attributes associated with the named element |
627 | /// by the URI. This means the parent will remain the same, but | 717 | // will change. |
628 | /// any or all attributes associated with the named element | 718 | // |
629 | /// will change. | 719 | // If the inventory collection contains an element with a zero |
630 | /// | 720 | // parent ID, then this is taken to be the replacement for the |
631 | /// If the inventory collection contains an element with a zero | 721 | // named node. The collection MAY also specify an explicit |
632 | /// parent ID, then this is taken to be the replacement for the | 722 | // parent ID, in this case it MAY identify the same parent as |
633 | /// named node. The collection MAY also specify an explicit | 723 | // the current node, or it MAY specify a different parent, |
634 | /// parent ID, in this case it MAY identify the same parent as | 724 | // indicating that the folder is being moved in addition to any |
635 | /// the current node, or it MAY specify a different parent, | 725 | // other modifications being made. |
636 | /// indicating that the folder is being moved in addition to any | ||
637 | /// other modifications being made. | ||
638 | /// </remarks> | ||
639 | 726 | ||
640 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | 727 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || |
641 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) | 728 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) |
642 | { | 729 | { |
730 | |||
731 | bool rfound = false; | ||
643 | InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; | 732 | InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; |
644 | InventoryFolderBase xml = null; | 733 | InventoryFolderBase xml = null; |
734 | |||
735 | // If the entity to be replaced resolved to be the root | ||
736 | // directory itself (My Inventory), then make sure that | ||
737 | // the supplied data include as appropriately typed and | ||
738 | // named folder. Note that we can;t rule out the possibility | ||
739 | // of a sub-directory being called "My Inventory", so that | ||
740 | // is anticipated. | ||
741 | |||
742 | if (uri == rdata.root) | ||
743 | { | ||
744 | |||
745 | foreach (InventoryFolderBase folder in entity.Folders) | ||
746 | { | ||
747 | if ((rfound = (folder.Name == PRIVATE_ROOT_NAME))) | ||
748 | { | ||
749 | if ((rfound = (folder.ParentID == LLUUID.Zero))) | ||
750 | break; | ||
751 | } | ||
752 | } | ||
753 | |||
754 | if (!rfound) | ||
755 | { | ||
756 | Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory", | ||
757 | MsgId, rdata.method, rdata.path); | ||
758 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure"); | ||
759 | } | ||
760 | |||
761 | } | ||
645 | 762 | ||
646 | // Scan the set of folders in the entity collection for an | 763 | // Scan the set of folders in the entity collection for an |
647 | // entry that matches the context folder. It is assumed that | 764 | // entry that matches the context folder. It is assumed that |
648 | // the only reliable indicator of this is a zero UUID (using | 765 | // the only reliable indicator of this is a zero UUID ( using |
649 | // implicit context), or the parent's UUID matches that of the | 766 | // implicit context), or the parent's UUID matches that of the |
650 | // URI designated node (explicit context). We don't allow | 767 | // URI designated node (explicit context). We don't allow |
651 | // ambiguity in this case because this is POST and we are | 768 | // ambiguity in this case because this is POST and we are |
652 | // supposed to be modifying a specific node. | 769 | // supposed to be modifying a specific node. |
653 | // We assign any element IDs required as an economy; we don't | 770 | // We assign any element IDs required as an economy; we don't |
654 | // want to iterate over the fodler set again if it can be | 771 | // want to iterate over the fodler set again if it can be |
655 | // helped. | 772 | // helped. |
656 | 773 | ||
657 | foreach (InventoryFolderBase folder in entity.Folders) | 774 | foreach (InventoryFolderBase folder in entity.Folders) |
@@ -663,51 +780,60 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
663 | xml = folder; | 780 | xml = folder; |
664 | count++; | 781 | count++; |
665 | } | 782 | } |
666 | if (xml.ID == LLUUID.Zero) | ||
667 | { | ||
668 | xml.ID = LLUUID.Random(); | ||
669 | } | ||
670 | } | 783 | } |
671 | 784 | ||
672 | // More than one entry is ambiguous. Other folders should be | 785 | // More than one entry is ambiguous. Other folders should be |
673 | // added using the PUT verb. | 786 | // added using the POST verb. |
674 | 787 | ||
675 | if (count > 1) | 788 | if (count > 1) |
676 | { | 789 | { |
677 | Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", | 790 | Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", |
678 | MsgId, rdata.method, rdata.path); | 791 | MsgId, rdata.method, rdata.path); |
679 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 792 | rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous"); |
680 | Rest.HttpStatusDescBadRequest+": context is ambiguous"); | ||
681 | } | 793 | } |
682 | 794 | ||
683 | // Exactly one entry means we ARE replacing the node | 795 | // Exactly one entry means we ARE replacing the node |
684 | // identified by the URI. So we delete the old folder | 796 | // identified by the URI. So we delete the old folder |
685 | // by moving it to the trash and then purging it. | 797 | // by moving it to the trash and then purging it. |
686 | // We then add all of the folders and items we | 798 | // We then add all of the folders and items we |
687 | // included in the entity. The subtree has been | 799 | // included in the entity. The subtree has been |
688 | // modified. | 800 | // modified. |
689 | 801 | ||
690 | if (count == 1) | 802 | if (count == 1) |
691 | { | 803 | { |
804 | |||
692 | InventoryFolderBase TrashCan = GetTrashCan(rdata); | 805 | InventoryFolderBase TrashCan = GetTrashCan(rdata); |
693 | 806 | ||
807 | // All went well, so we generate a UUID is one is | ||
808 | // needed. | ||
809 | |||
810 | if (xml.ID == LLUUID.Zero) | ||
811 | { | ||
812 | xml.ID = LLUUID.Random(); | ||
813 | } | ||
814 | |||
694 | uri.ParentID = TrashCan.ID; | 815 | uri.ParentID = TrashCan.ID; |
695 | Rest.InventoryServices.MoveFolder(uri); | 816 | Rest.InventoryServices.MoveFolder(uri); |
696 | Rest.InventoryServices.PurgeFolder(TrashCan); | 817 | Rest.InventoryServices.PurgeFolder(TrashCan); |
818 | modified = true; | ||
819 | |||
697 | } | 820 | } |
698 | 821 | ||
699 | // Now, regardelss of what they represent, we | 822 | // Now, regardelss of what they represent, we |
700 | // integrate all of the elements in the entity. | 823 | // integrate all of the elements in the entity. |
701 | 824 | ||
702 | foreach (InventoryFolderBase f in entity.Folders) | 825 | foreach (InventoryFolderBase f in entity.Folders) |
703 | { | 826 | { |
827 | rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1}<p>", f.Name, f.ID)); | ||
704 | Rest.InventoryServices.MoveFolder(f); | 828 | Rest.InventoryServices.MoveFolder(f); |
705 | } | 829 | } |
706 | 830 | ||
707 | foreach (InventoryItemBase it in entity.Items) | 831 | foreach (InventoryItemBase it in entity.Items) |
708 | { | 832 | { |
833 | rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1}<p>", it.Name, it.ID)); | ||
709 | Rest.InventoryServices.AddItem(it); | 834 | Rest.InventoryServices.AddItem(it); |
710 | } | 835 | } |
836 | |||
711 | } | 837 | } |
712 | 838 | ||
713 | /// <summary> | 839 | /// <summary> |
@@ -720,6 +846,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
720 | 846 | ||
721 | else | 847 | else |
722 | { | 848 | { |
849 | |||
723 | InventoryItemBase uri = (InventoryItemBase) InventoryNode; | 850 | InventoryItemBase uri = (InventoryItemBase) InventoryNode; |
724 | InventoryItemBase xml = null; | 851 | InventoryItemBase xml = null; |
725 | 852 | ||
@@ -727,20 +854,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
727 | { | 854 | { |
728 | Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", | 855 | Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", |
729 | MsgId, rdata.method, rdata.path); | 856 | MsgId, rdata.method, rdata.path); |
730 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 857 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed"); |
731 | Rest.HttpStatusDescBadRequest+": folder is not allowed"); | ||
732 | } | 858 | } |
733 | 859 | ||
734 | if (entity.Items.Count > 1) | 860 | if (entity.Items.Count > 1) |
735 | { | 861 | { |
736 | Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", | 862 | Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", |
737 | MsgId, rdata.method, rdata.path); | 863 | MsgId, rdata.method, rdata.path); |
738 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 864 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items"); |
739 | Rest.HttpStatusDescBadRequest+": too may items"); | ||
740 | } | 865 | } |
741 | 866 | ||
742 | xml = entity.Items[0]; | 867 | xml = entity.Items[0]; |
743 | 868 | ||
744 | if (xml.ID == LLUUID.Zero) | 869 | if (xml.ID == LLUUID.Zero) |
745 | { | 870 | { |
746 | xml.ID = LLUUID.Random(); | 871 | xml.ID = LLUUID.Random(); |
@@ -757,10 +882,29 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
757 | // Add the new item to the inventory | 882 | // Add the new item to the inventory |
758 | 883 | ||
759 | Rest.InventoryServices.AddItem(xml); | 884 | Rest.InventoryServices.AddItem(xml); |
885 | |||
886 | rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1}<p>", xml.Name, xml.ID)); | ||
887 | |||
888 | } | ||
889 | |||
890 | if (created) | ||
891 | { | ||
892 | rdata.Complete(Rest.HttpStatusCodeCreated); | ||
893 | } | ||
894 | else | ||
895 | { | ||
896 | if (modified) | ||
897 | { | ||
898 | rdata.Complete(Rest.HttpStatusCodeOK); | ||
899 | } | ||
900 | else | ||
901 | { | ||
902 | rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
903 | } | ||
760 | } | 904 | } |
761 | 905 | ||
762 | rdata.Complete(); | ||
763 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | 906 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); |
907 | |||
764 | } | 908 | } |
765 | 909 | ||
766 | /// <summary> | 910 | /// <summary> |
@@ -773,7 +917,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
773 | /// | 917 | /// |
774 | /// Folders are deleted by moving them to another folder and then | 918 | /// Folders are deleted by moving them to another folder and then |
775 | /// purging that folder. We'll do that by creating a temporary | 919 | /// purging that folder. We'll do that by creating a temporary |
776 | /// sub-folder in the TrashCan and purging that folder's | 920 | /// sub-folder in the TrashCan and purging that folder's |
777 | /// contents. If we can't can it, we don't delete it... | 921 | /// contents. If we can't can it, we don't delete it... |
778 | /// So, if no trashcan is available, the request does nothing. | 922 | /// So, if no trashcan is available, the request does nothing. |
779 | /// Items are summarily deleted. | 923 | /// Items are summarily deleted. |
@@ -782,14 +926,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
782 | /// be performed using UUID, as a name might identify several | 926 | /// be performed using UUID, as a name might identify several |
783 | /// elements. | 927 | /// elements. |
784 | /// </summary> | 928 | /// </summary> |
929 | /// <param name=rdata>HTTP service request work area</param> | ||
785 | 930 | ||
786 | private void DoDelete(InventoryRequestData rdata) | 931 | private void DoDelete(InventoryRequestData rdata) |
787 | { | 932 | { |
788 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | 933 | |
934 | Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false); | ||
789 | 935 | ||
790 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | 936 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || |
791 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) | 937 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) |
792 | { | 938 | { |
939 | |||
793 | InventoryFolderBase TrashCan = GetTrashCan(rdata); | 940 | InventoryFolderBase TrashCan = GetTrashCan(rdata); |
794 | 941 | ||
795 | InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; | 942 | InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; |
@@ -798,6 +945,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
798 | folder.ParentID = TrashCan.ID; | 945 | folder.ParentID = TrashCan.ID; |
799 | Rest.InventoryServices.MoveFolder(folder); | 946 | Rest.InventoryServices.MoveFolder(folder); |
800 | Rest.InventoryServices.PurgeFolder(TrashCan); | 947 | Rest.InventoryServices.PurgeFolder(TrashCan); |
948 | |||
949 | rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1}<p>", folder.Name, folder.ID)); | ||
950 | |||
801 | } | 951 | } |
802 | 952 | ||
803 | // Deleting items is much more straight forward. | 953 | // Deleting items is much more straight forward. |
@@ -808,21 +958,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
808 | Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", | 958 | Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", |
809 | MsgId, rdata.method, rdata.path); | 959 | MsgId, rdata.method, rdata.path); |
810 | Rest.InventoryServices.DeleteItem(item); | 960 | Rest.InventoryServices.DeleteItem(item); |
961 | rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1}<p>", item.Name, item.ID)); | ||
811 | } | 962 | } |
812 | 963 | ||
813 | rdata.Complete(); | 964 | rdata.Complete(); |
814 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | 965 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); |
966 | |||
815 | } | 967 | } |
816 | 968 | ||
817 | #endregion method-specific processing | 969 | #endregion method-specific processing |
818 | 970 | ||
819 | /// <summary> | 971 | /// <summary> |
820 | /// This method is called to obtain the OpenSim inventory object identified | 972 | /// This method is called to obtain the OpenSim inventory object identified |
821 | /// by the supplied URI. This may be either an Item or a Folder, so a suitably | 973 | /// by the supplied URI. This may be either an Item or a Folder, so a suitably |
822 | /// ambiguous return type is employed (Object). This method recurses as | 974 | /// ambiguous return type is employed (Object). This method recurses as |
823 | /// necessary to process the designated hierarchy. | 975 | /// necessary to process the designated hierarchy. |
824 | /// | 976 | /// |
825 | /// If we reach the end of the URI then we return the contextural folder to | 977 | /// If we reach the end of the URI then we return the contextual folder to |
826 | /// our caller. | 978 | /// our caller. |
827 | /// | 979 | /// |
828 | /// If we are not yet at the end of the URI we attempt to find a child folder | 980 | /// If we are not yet at the end of the URI we attempt to find a child folder |
@@ -831,50 +983,79 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
831 | /// If this is the last node, then we look to see if this is an item. If it is, | 983 | /// If this is the last node, then we look to see if this is an item. If it is, |
832 | /// we return that item. | 984 | /// we return that item. |
833 | /// | 985 | /// |
986 | /// If we reach the end of an inventory path and the URI si not yet exhausted, | ||
987 | /// then if 'fill' is specified, we create the intermediate nodes. | ||
988 | /// | ||
834 | /// Otherwise we fail the request on the ground of an invalid URI. | 989 | /// Otherwise we fail the request on the ground of an invalid URI. |
835 | /// | 990 | /// |
836 | /// <note> | 991 | /// An ambiguous request causes the request to fail. |
837 | /// This mechanism cannot detect the case where duplicate subtrees satisfy a | ||
838 | /// request. In such a case the 1st element gets processed. If this is a | ||
839 | /// problem, then UUID should be used to identify the end-node. This is basic | ||
840 | /// premise of normal inventory processing. The name is an informational, and | ||
841 | /// not a defining, attribute. | ||
842 | /// </note> | ||
843 | /// | 992 | /// |
844 | /// </summary> | 993 | /// </summary> |
845 | 994 | /// <param name=rdata>HTTP service request work area</param> | |
846 | private Object getInventoryNode(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | 995 | /// <param name=folder>The folder to be searched (parent)</param> |
996 | /// <param name=pi>URI parameter index</param> | ||
997 | /// <param name=fill>Should missing path members be created?</param> | ||
998 | |||
999 | private Object getInventoryNode(InventoryRequestData rdata, | ||
1000 | InventoryFolderBase folder, | ||
1001 | int pi, bool fill) | ||
847 | { | 1002 | { |
1003 | |||
1004 | InventoryFolderBase foundf = null; | ||
1005 | int fk = 0; | ||
1006 | |||
848 | Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | 1007 | Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); |
849 | 1008 | ||
850 | // We have just run off the end of the parameter sequence | 1009 | // We have just run off the end of the parameter sequence |
851 | 1010 | ||
852 | if (pi >= rdata.parameters.Length) | 1011 | if (pi >= rdata.Parameters.Length) |
853 | { | 1012 | { |
854 | return folder; | 1013 | return folder; |
855 | } | 1014 | } |
856 | 1015 | ||
857 | // More names in the sequence, look for a folder that might | 1016 | // There are more names in the parameter sequence, |
858 | // get us there. | 1017 | // look for the folder named by param[pi] as a |
1018 | // child of the folder supplied as an argument. | ||
1019 | // Note that a UUID may have been supplied as the | ||
1020 | // identifier (it is the ONLY guaranteed unambiguous | ||
1021 | // option. | ||
859 | 1022 | ||
860 | if (rdata.folders != null) | 1023 | if (rdata.folders != null) |
861 | { | ||
862 | foreach (InventoryFolderBase f in rdata.folders) | 1024 | foreach (InventoryFolderBase f in rdata.folders) |
863 | { | 1025 | { |
864 | // Look for the present node in the directory list | 1026 | // Look for the present node in the directory list |
865 | if (f.ParentID == folder.ID && | 1027 | if (f.ParentID == folder.ID && |
866 | (f.Name == rdata.parameters[pi] || | 1028 | (f.Name == rdata.Parameters[pi] || |
867 | f.ID.ToString() == rdata.parameters[pi])) | 1029 | f.ID.ToString() == rdata.Parameters[pi])) |
868 | { | 1030 | { |
869 | return getInventoryNode(rdata, f, pi+1); | 1031 | foundf = f; |
1032 | fk++; | ||
870 | } | 1033 | } |
871 | } | 1034 | } |
1035 | |||
1036 | // If more than one node matched, then the path, as specified | ||
1037 | // is ambiguous. | ||
1038 | |||
1039 | if (fk > 1) | ||
1040 | { | ||
1041 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
1042 | MsgId, rdata.method, rdata.path); | ||
1043 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); | ||
1044 | } | ||
1045 | |||
1046 | // If we find a match, then the method | ||
1047 | // increment the parameter index, and calls itself | ||
1048 | // passing the found folder as the new context. | ||
1049 | |||
1050 | if (foundf != null) | ||
1051 | { | ||
1052 | return getInventoryNode(rdata, foundf, pi+1, fill); | ||
872 | } | 1053 | } |
873 | 1054 | ||
874 | // No folders that match. Perhaps this parameter identifies an item? If | 1055 | // No folders that match. Perhaps this parameter identifies an item? If |
875 | // it does, then it MUST also be the last name in the sequence. | 1056 | // it does, then it MUST also be the last name in the sequence. |
876 | 1057 | ||
877 | if (pi == rdata.parameters.Length-1) | 1058 | if (pi == rdata.Parameters.Length-1) |
878 | { | 1059 | { |
879 | if (rdata.items != null) | 1060 | if (rdata.items != null) |
880 | { | 1061 | { |
@@ -882,9 +1063,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
882 | InventoryItemBase li = null; | 1063 | InventoryItemBase li = null; |
883 | foreach (InventoryItemBase i in rdata.items) | 1064 | foreach (InventoryItemBase i in rdata.items) |
884 | { | 1065 | { |
885 | if (i.Folder == folder.ID && | 1066 | if (i.Folder == folder.ID && |
886 | (i.Name == rdata.parameters[pi] || | 1067 | (i.Name == rdata.Parameters[pi] || |
887 | i.ID.ToString() == rdata.parameters[pi])) | 1068 | i.ID.ToString() == rdata.Parameters[pi])) |
888 | { | 1069 | { |
889 | li = i; | 1070 | li = i; |
890 | k++; | 1071 | k++; |
@@ -894,26 +1075,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
894 | { | 1075 | { |
895 | return li; | 1076 | return li; |
896 | } | 1077 | } |
897 | else | 1078 | else if (k > 1) |
898 | { | 1079 | { |
899 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | 1080 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", |
900 | MsgId, rdata.method, rdata.path); | 1081 | MsgId, rdata.method, rdata.path); |
901 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": request is ambiguous"); | 1082 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); |
902 | } | 1083 | } |
903 | } | 1084 | } |
904 | } | 1085 | } |
905 | 1086 | ||
906 | // No, so abandon the request | 1087 | // If fill is enabled, then we must create the missing intermediate nodes. |
1088 | // And of course, even this is not straightforward. All intermediate nodes | ||
1089 | // are obviously folders, but the last node may be a folder or an item. | ||
1090 | |||
1091 | if (fill) | ||
1092 | { | ||
1093 | } | ||
1094 | |||
1095 | // No fill, so abandon the request | ||
907 | 1096 | ||
908 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", | 1097 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", |
909 | MsgId, rdata.method, rdata.path); | 1098 | MsgId, rdata.method, rdata.path); |
910 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": resource "+rdata.path+" not found"); | 1099 | rdata.Fail(Rest.HttpStatusCodeNotFound, "resource "+rdata.path+" not found"); |
911 | 1100 | ||
912 | return null; /* Never reached */ | 1101 | return null; /* Never reached */ |
1102 | |||
913 | } | 1103 | } |
914 | 1104 | ||
915 | /// <summary> | 1105 | /// <summary> |
916 | /// This routine traverse the inventory's structure until the end-point identified | 1106 | /// This routine traverse the inventory's structure until the end-point identified |
917 | /// in the URI is reached, the remainder of the inventory (if any) is then formatted | 1107 | /// in the URI is reached, the remainder of the inventory (if any) is then formatted |
918 | /// and returned to the requestor. | 1108 | /// and returned to the requestor. |
919 | /// | 1109 | /// |
@@ -923,57 +1113,142 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
923 | /// | 1113 | /// |
924 | /// Only the last element in the URI should identify an item. | 1114 | /// Only the last element in the URI should identify an item. |
925 | /// </summary> | 1115 | /// </summary> |
1116 | /// <param name=rdata>HTTP service request work area</param> | ||
1117 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1118 | /// <param name=pi>URI parameter index</param> | ||
926 | 1119 | ||
927 | private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | 1120 | private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi) |
928 | { | 1121 | { |
929 | Rest.Log.DebugFormat("{0} Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | 1122 | |
1123 | Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
930 | 1124 | ||
931 | if (rdata.folders != null) | 1125 | if (rdata.folders != null) |
932 | { | 1126 | { |
933 | foreach (InventoryFolderBase f in rdata.folders) | 1127 | |
1128 | // If there was only one parameter (avatar name), then the entire | ||
1129 | // inventory is being requested. | ||
1130 | |||
1131 | if (rdata.Parameters.Length == 1) | ||
934 | { | 1132 | { |
935 | if (f.ParentID == folder.ID && | 1133 | formatInventory(rdata, rdata.root, String.Empty); |
936 | (f.Name == rdata.parameters[pi] || | 1134 | } |
937 | f.ID.ToString() == rdata.parameters[pi])) | 1135 | |
1136 | // Has the client specified the root directory name explicitly? | ||
1137 | // if yes, then we just absorb the reference, because the folder | ||
1138 | // we start looking in for a match *is* the root directory. If there | ||
1139 | // are more parameters remaining we tarverse, otehrwise it's time | ||
1140 | // to format. Otherwise,we consider the "My Inventory" to be implied | ||
1141 | // and we just traverse normally. | ||
1142 | |||
1143 | else if (folder.ID.ToString() == rdata.Parameters[pi] || | ||
1144 | folder.Name == rdata.Parameters[pi]) | ||
1145 | { | ||
1146 | // Length is -1 because the avatar name is a parameter | ||
1147 | if (pi<(rdata.Parameters.Length-1)) | ||
938 | { | 1148 | { |
939 | if (pi < rdata.parameters.Length-1) | 1149 | traverseInventory(rdata, folder, pi+1); |
940 | { | ||
941 | traverseInventory(rdata, f, pi+1); | ||
942 | } | ||
943 | else | ||
944 | { | ||
945 | formatInventory(rdata, f, String.Empty); | ||
946 | } | ||
947 | return; | ||
948 | } | 1150 | } |
1151 | else | ||
1152 | { | ||
1153 | formatInventory(rdata, folder, String.Empty); | ||
1154 | } | ||
1155 | } | ||
1156 | else | ||
1157 | { | ||
1158 | traverseInventory(rdata, folder, pi); | ||
949 | } | 1159 | } |
1160 | |||
1161 | return; | ||
1162 | |||
950 | } | 1163 | } |
1164 | } | ||
1165 | |||
1166 | /// <summary> | ||
1167 | /// This is the recursive method. I've separated them in this way so that | ||
1168 | /// we do not have to waste cycles on any first-case-only processing. | ||
1169 | /// </summary> | ||
1170 | |||
1171 | private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
1172 | { | ||
1173 | |||
1174 | int fk = 0; | ||
1175 | InventoryFolderBase ffound = null; | ||
1176 | InventoryItemBase ifound = null; | ||
951 | 1177 | ||
952 | if (pi == rdata.parameters.Length-1) | 1178 | Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); |
1179 | |||
1180 | foreach (InventoryFolderBase f in rdata.folders) | ||
953 | { | 1181 | { |
1182 | if (f.ParentID == folder.ID && | ||
1183 | (f.Name == rdata.Parameters[pi] || | ||
1184 | f.ID.ToString() == rdata.Parameters[pi])) | ||
1185 | { | ||
1186 | fk++; | ||
1187 | ffound = f; | ||
1188 | } | ||
1189 | } | ||
1190 | |||
1191 | // If this is the last element in the parameter sequence, then | ||
1192 | // it is reasonable to check for an item. All intermediate nodes | ||
1193 | // MUST be folders. | ||
1194 | |||
1195 | if (pi == rdata.Parameters.Length-1) | ||
1196 | { | ||
1197 | |||
1198 | // Only if there are any items, and there pretty much always are. | ||
1199 | |||
954 | if (rdata.items != null) | 1200 | if (rdata.items != null) |
955 | { | 1201 | { |
956 | foreach (InventoryItemBase i in rdata.items) | 1202 | foreach (InventoryItemBase i in rdata.items) |
957 | { | 1203 | { |
958 | if (i.Folder == folder.ID && | 1204 | if (i.Folder == folder.ID && |
959 | (i.Name == rdata.parameters[pi] || | 1205 | (i.Name == rdata.Parameters[pi] || |
960 | i.ID.ToString() == rdata.parameters[pi])) | 1206 | i.ID.ToString() == rdata.Parameters[pi])) |
961 | { | 1207 | { |
962 | // Fetching an Item has a special significance. In this | 1208 | fk++; |
963 | // case we also want to fetch the associated asset. | 1209 | ifound = i; |
964 | // To make it interesting, we'll d this via redirection. | ||
965 | string asseturl = "http://" + rdata.hostname + ":" + rdata.port + | ||
966 | "/admin/assets" + Rest.UrlPathSeparator + i.AssetID.ToString(); | ||
967 | rdata.Redirect(asseturl,Rest.PERMANENT); | ||
968 | Rest.Log.DebugFormat("{0} Never Reached"); | ||
969 | } | 1210 | } |
970 | } | 1211 | } |
971 | } | 1212 | } |
972 | } | 1213 | } |
973 | 1214 | ||
974 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | 1215 | if (fk == 1) |
1216 | { | ||
1217 | if (ffound != null) | ||
1218 | { | ||
1219 | if (pi < rdata.Parameters.Length-1) | ||
1220 | { | ||
1221 | traverseInventory(rdata, ffound, pi+1); | ||
1222 | } | ||
1223 | else | ||
1224 | { | ||
1225 | formatInventory(rdata, ffound, String.Empty); | ||
1226 | } | ||
1227 | return; | ||
1228 | } | ||
1229 | else | ||
1230 | { | ||
1231 | // Fetching an Item has a special significance. In this | ||
1232 | // case we also want to fetch the associated asset. | ||
1233 | // To make it interesting, we'll d this via redirection. | ||
1234 | string asseturl = "http://" + rdata.hostname + ":" + rdata.port + | ||
1235 | "/admin/assets" + Rest.UrlPathSeparator + ifound.AssetID.ToString(); | ||
1236 | rdata.Redirect(asseturl,Rest.PERMANENT); | ||
1237 | Rest.Log.DebugFormat("{0} Never Reached", MsgId); | ||
1238 | } | ||
1239 | } | ||
1240 | else if (fk > 1) | ||
1241 | { | ||
1242 | rdata.Fail(Rest.HttpStatusCodeConflict, | ||
1243 | String.Format("ambiguous element ({0}) in path specified: <{1}>", | ||
1244 | pi, rdata.path)); | ||
1245 | } | ||
1246 | |||
1247 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | ||
975 | MsgId, rdata.path); | 1248 | MsgId, rdata.path); |
976 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no such item/folder"); | 1249 | rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}", |
1250 | rdata.Parameters[pi])); | ||
1251 | |||
977 | } | 1252 | } |
978 | 1253 | ||
979 | /// <summary> | 1254 | /// <summary> |
@@ -983,12 +1258,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
983 | /// The indentation parameter is solely for the benefit of trace record | 1258 | /// The indentation parameter is solely for the benefit of trace record |
984 | /// formatting. | 1259 | /// formatting. |
985 | /// </summary> | 1260 | /// </summary> |
1261 | /// <param name=rdata>HTTP service request work area</param> | ||
1262 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1263 | /// <param name=indent>pretty print indentation</param> | ||
986 | 1264 | ||
987 | private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) | 1265 | private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) |
988 | { | 1266 | { |
1267 | |||
989 | if (Rest.DEBUG) | 1268 | if (Rest.DEBUG) |
990 | { | 1269 | { |
991 | Rest.Log.DebugFormat("{0} Folder : {1} {2} {3}", MsgId, folder.ID, indent, folder.Name); | 1270 | Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}", |
1271 | MsgId, folder.ID, indent, folder.Name, folder.Type); | ||
992 | indent += "\t"; | 1272 | indent += "\t"; |
993 | } | 1273 | } |
994 | 1274 | ||
@@ -997,6 +1277,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
997 | rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); | 1277 | rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); |
998 | rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); | 1278 | rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); |
999 | rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); | 1279 | rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); |
1280 | rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString()); | ||
1000 | rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); | 1281 | rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); |
1001 | rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); | 1282 | rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); |
1002 | rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); | 1283 | rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); |
@@ -1026,21 +1307,28 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1026 | // End folder item | 1307 | // End folder item |
1027 | 1308 | ||
1028 | rdata.writer.WriteEndElement(); | 1309 | rdata.writer.WriteEndElement(); |
1310 | |||
1029 | } | 1311 | } |
1030 | 1312 | ||
1031 | /// <summary> | 1313 | /// <summary> |
1032 | /// This method generates XML that describes an instance of InventoryItemBase. | 1314 | /// This method generates XML that describes an instance of InventoryItemBase. |
1033 | /// </summary> | 1315 | /// </summary> |
1316 | /// <param name=rdata>HTTP service request work area</param> | ||
1317 | /// <param name=i>The item to be formatted</param> | ||
1318 | /// <param name=indent>Pretty print indentation</param> | ||
1034 | 1319 | ||
1035 | private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) | 1320 | private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) |
1036 | { | 1321 | { |
1037 | Rest.Log.DebugFormat("{0} Item : {1} {2} {3}", MsgId, i.ID, indent, i.Name); | 1322 | |
1323 | Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}", | ||
1324 | MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType); | ||
1038 | 1325 | ||
1039 | rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty); | 1326 | rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty); |
1040 | 1327 | ||
1041 | rdata.writer.WriteAttributeString("name",String.Empty,i.Name); | 1328 | rdata.writer.WriteAttributeString("name",String.Empty,i.Name); |
1042 | rdata.writer.WriteAttributeString("desc",String.Empty,i.Description); | 1329 | rdata.writer.WriteAttributeString("desc",String.Empty,i.Description); |
1043 | rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString()); | 1330 | rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString()); |
1331 | rdata.writer.WriteAttributeString("folder",String.Empty,i.Folder.ToString()); | ||
1044 | rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString()); | 1332 | rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString()); |
1045 | rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString()); | 1333 | rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString()); |
1046 | rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString()); | 1334 | rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString()); |
@@ -1062,10 +1350,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1062 | rdata.writer.WriteElementString("Asset",i.AssetID.ToString()); | 1350 | rdata.writer.WriteElementString("Asset",i.AssetID.ToString()); |
1063 | 1351 | ||
1064 | rdata.writer.WriteEndElement(); | 1352 | rdata.writer.WriteEndElement(); |
1353 | |||
1065 | } | 1354 | } |
1066 | 1355 | ||
1067 | /// <summary> | 1356 | /// <summary> |
1068 | /// This method creates a "trashcan" folder to support folder and item | 1357 | /// This method creates a "trashcan" folder to support folder and item |
1069 | /// deletions by this interface. The xisting trash folder is found and | 1358 | /// deletions by this interface. The xisting trash folder is found and |
1070 | /// this folder is created within it. It is called "tmp" to indicate to | 1359 | /// this folder is created within it. It is called "tmp" to indicate to |
1071 | /// the client that it is OK to delete this folder. The REST interface | 1360 | /// the client that it is OK to delete this folder. The REST interface |
@@ -1073,9 +1362,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1073 | /// If the trash can cannot be created, then by implication the request | 1362 | /// If the trash can cannot be created, then by implication the request |
1074 | /// that required it cannot be completed, and it fails accordingly. | 1363 | /// that required it cannot be completed, and it fails accordingly. |
1075 | /// </summary> | 1364 | /// </summary> |
1365 | /// <param name=rdata>HTTP service request work area</param> | ||
1076 | 1366 | ||
1077 | private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) | 1367 | private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) |
1078 | { | 1368 | { |
1369 | |||
1079 | InventoryFolderBase TrashCan = null; | 1370 | InventoryFolderBase TrashCan = null; |
1080 | 1371 | ||
1081 | foreach (InventoryFolderBase f in rdata.folders) | 1372 | foreach (InventoryFolderBase f in rdata.folders) |
@@ -1102,29 +1393,31 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1102 | } | 1393 | } |
1103 | } | 1394 | } |
1104 | } | 1395 | } |
1105 | 1396 | ||
1106 | if (TrashCan == null) | 1397 | if (TrashCan == null) |
1107 | { | 1398 | { |
1108 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); | 1399 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); |
1109 | rdata.Fail(Rest.HttpStatusCodeServerError, | 1400 | rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can"); |
1110 | Rest.HttpStatusDescServerError+": unable to create trash can"); | ||
1111 | } | 1401 | } |
1112 | 1402 | ||
1113 | return TrashCan; | 1403 | return TrashCan; |
1404 | |||
1114 | } | 1405 | } |
1115 | 1406 | ||
1116 | /// <summary> | 1407 | /// <summary> |
1117 | /// Make sure that an unchanged folder is not unnecessarily | 1408 | /// Make sure that an unchanged folder is not unnecessarily |
1118 | /// processed. | 1409 | /// processed. |
1119 | /// </summary> | 1410 | /// </summary> |
1411 | /// <param name=newf>Folder obtained from enclosed entity</param> | ||
1412 | /// <param name=oldf>Folder obtained from the user's inventory</param> | ||
1120 | 1413 | ||
1121 | private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) | 1414 | private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) |
1122 | { | 1415 | { |
1123 | return (newf.Name != oldf.Name | 1416 | return ( newf.Name != oldf.Name |
1124 | || newf.ParentID != oldf.ParentID | 1417 | || newf.ParentID != oldf.ParentID |
1125 | || newf.Owner != oldf.Owner | 1418 | || newf.Owner != oldf.Owner |
1126 | || newf.Type != oldf.Type | 1419 | || newf.Type != oldf.Type |
1127 | || newf.Version != oldf.Version | 1420 | || newf.Version != oldf.Version |
1128 | ); | 1421 | ); |
1129 | } | 1422 | } |
1130 | 1423 | ||
@@ -1132,27 +1425,29 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1132 | /// Make sure that an unchanged item is not unnecessarily | 1425 | /// Make sure that an unchanged item is not unnecessarily |
1133 | /// processed. | 1426 | /// processed. |
1134 | /// </summary> | 1427 | /// </summary> |
1428 | /// <param name=newf>Item obtained from enclosed entity</param> | ||
1429 | /// <param name=oldf>Item obtained from the user's inventory</param> | ||
1135 | 1430 | ||
1136 | private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) | 1431 | private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) |
1137 | { | 1432 | { |
1138 | return (newf.Name != oldf.Name | 1433 | return ( newf.Name != oldf.Name |
1139 | || newf.Folder != oldf.Description | 1434 | || newf.Folder != oldf.Description |
1140 | || newf.Description != oldf.Description | 1435 | || newf.Description != oldf.Description |
1141 | || newf.Owner != oldf.Owner | 1436 | || newf.Owner != oldf.Owner |
1142 | || newf.Creator != oldf.Creator | 1437 | || newf.Creator != oldf.Creator |
1143 | || newf.AssetID != oldf.AssetID | 1438 | || newf.AssetID != oldf.AssetID |
1144 | || newf.GroupID != oldf.GroupID | 1439 | || newf.GroupID != oldf.GroupID |
1145 | || newf.GroupOwned != oldf.GroupOwned | 1440 | || newf.GroupOwned != oldf.GroupOwned |
1146 | || newf.InvType != oldf.InvType | 1441 | || newf.InvType != oldf.InvType |
1147 | || newf.AssetType != oldf.AssetType | 1442 | || newf.AssetType != oldf.AssetType |
1148 | ); | 1443 | ); |
1149 | } | 1444 | } |
1150 | 1445 | ||
1151 | /// <summary> | 1446 | /// <summary> |
1152 | /// This method is called by PUT and POST to create an XmlInventoryCollection | 1447 | /// This method is called by PUT and POST to create an XmlInventoryCollection |
1153 | /// instance that reflects the content of the entity supplied on the request. | 1448 | /// instance that reflects the content of the entity supplied on the request. |
1154 | /// Any elements in the completed collection whose UUID is zero, are | 1449 | /// Any elements in the completed collection whose UUID is zero, are |
1155 | /// considered to be located relative to the end-point identified int he | 1450 | /// considered to be located relative to the end-point identified int he |
1156 | /// URI. In this way, an entire sub-tree can be conveyed in a single REST | 1451 | /// URI. In this way, an entire sub-tree can be conveyed in a single REST |
1157 | /// PUT or POST request. | 1452 | /// PUT or POST request. |
1158 | /// | 1453 | /// |
@@ -1160,24 +1455,27 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1160 | /// has an entity, it is more completely initialized. thus, if no entity was | 1455 | /// has an entity, it is more completely initialized. thus, if no entity was |
1161 | /// provided the collection is valid, but empty. | 1456 | /// provided the collection is valid, but empty. |
1162 | /// | 1457 | /// |
1163 | /// The entity is then scanned and each tag is processed to produce the | 1458 | /// The entity is then scanned and each tag is processed to produce the |
1164 | /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection | 1459 | /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection |
1165 | /// will reflect the subtree described by the entity. | 1460 | /// will reflect the subtree described by the entity. |
1166 | /// | 1461 | /// |
1167 | /// This is a very flexible mechanism, the entity may contain arbitrary, | 1462 | /// This is a very flexible mechanism, the entity may contain arbitrary, |
1168 | /// discontiguous tree fragments, or may contain single element. The caller is | 1463 | /// discontiguous tree fragments, or may contain single element. The caller is |
1169 | /// responsible for integrating this collection (and ensuring that any | 1464 | /// responsible for integrating this collection (and ensuring that any |
1170 | /// missing parent IDs are resolved). | 1465 | /// missing parent IDs are resolved). |
1171 | /// </summary> | 1466 | /// </summary> |
1467 | /// <param name=rdata>HTTP service request work area</param> | ||
1172 | 1468 | ||
1173 | internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) | 1469 | internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) |
1174 | { | 1470 | { |
1471 | |||
1175 | Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); | 1472 | Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); |
1176 | 1473 | ||
1177 | XmlInventoryCollection ic = new XmlInventoryCollection(); | 1474 | XmlInventoryCollection ic = new XmlInventoryCollection(); |
1178 | 1475 | ||
1179 | if (rdata.request.HasEntityBody) | 1476 | if (rdata.request.HasEntityBody) |
1180 | { | 1477 | { |
1478 | |||
1181 | Rest.Log.DebugFormat("{0} Entity present", MsgId); | 1479 | Rest.Log.DebugFormat("{0} Entity present", MsgId); |
1182 | 1480 | ||
1183 | ic.init(rdata); | 1481 | ic.init(rdata); |
@@ -1273,6 +1571,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1273 | Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); | 1571 | Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); |
1274 | throw e; | 1572 | throw e; |
1275 | } | 1573 | } |
1574 | |||
1276 | } | 1575 | } |
1277 | else | 1576 | else |
1278 | { | 1577 | { |
@@ -1288,13 +1587,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1288 | } | 1587 | } |
1289 | 1588 | ||
1290 | return ic; | 1589 | return ic; |
1590 | |||
1291 | } | 1591 | } |
1292 | 1592 | ||
1293 | /// <summary> | 1593 | /// <summary> |
1294 | /// This method creates an inventory Folder from the | 1594 | /// This method creates an inventory Folder from the |
1295 | /// information supplied in the request's entity. | 1595 | /// information supplied in the request's entity. |
1296 | /// A folder instance is created and initialized to reflect | 1596 | /// A folder instance is created and initialized to reflect |
1297 | /// default values. These values are then overridden | 1597 | /// default values. These values are then overridden |
1298 | /// by information supplied in the entity. | 1598 | /// by information supplied in the entity. |
1299 | /// If context was not explicitly provided, then the | 1599 | /// If context was not explicitly provided, then the |
1300 | /// appropriate ID values are determined. | 1600 | /// appropriate ID values are determined. |
@@ -1302,6 +1602,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1302 | 1602 | ||
1303 | private void CollectFolder(XmlInventoryCollection ic) | 1603 | private void CollectFolder(XmlInventoryCollection ic) |
1304 | { | 1604 | { |
1605 | |||
1305 | Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); | 1606 | Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); |
1306 | 1607 | ||
1307 | InventoryFolderBase result = new InventoryFolderBase(); | 1608 | InventoryFolderBase result = new InventoryFolderBase(); |
@@ -1341,10 +1642,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1341 | result.Version = UInt16.Parse(ic.xml.Value); | 1642 | result.Version = UInt16.Parse(ic.xml.Value); |
1342 | break; | 1643 | break; |
1343 | default : | 1644 | default : |
1344 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", | 1645 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", |
1345 | MsgId, ic.xml.Name, ic.xml.Value); | 1646 | MsgId, ic.xml.Name, ic.xml.Value); |
1346 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1647 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", |
1347 | Rest.HttpStatusDescBadRequest+": unrecognized attribute"); | 1648 | ic.xml.Name)); |
1348 | break; | 1649 | break; |
1349 | } | 1650 | } |
1350 | } | 1651 | } |
@@ -1363,11 +1664,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1363 | } | 1664 | } |
1364 | else | 1665 | else |
1365 | { | 1666 | { |
1667 | |||
1366 | bool found = false; | 1668 | bool found = false; |
1367 | 1669 | ||
1368 | foreach (InventoryFolderBase parent in ic.rdata.folders) | 1670 | foreach (InventoryFolderBase parent in ic.rdata.folders) |
1369 | { | 1671 | { |
1370 | if (parent.ID == result.ParentID) | 1672 | if ( parent.ID == result.ParentID ) |
1371 | { | 1673 | { |
1372 | found = true; | 1674 | found = true; |
1373 | break; | 1675 | break; |
@@ -1376,11 +1678,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1376 | 1678 | ||
1377 | if (!found) | 1679 | if (!found) |
1378 | { | 1680 | { |
1379 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", | 1681 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", |
1380 | MsgId, ic.Item.Folder, result.ID); | 1682 | MsgId, ic.Item.Folder, result.ID); |
1381 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1683 | ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); |
1382 | Rest.HttpStatusDescBadRequest+": invalid parent"); | ||
1383 | } | 1684 | } |
1685 | |||
1384 | } | 1686 | } |
1385 | 1687 | ||
1386 | // This is a new folder, so no existing UUID is available | 1688 | // This is a new folder, so no existing UUID is available |
@@ -1395,14 +1697,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1395 | // obsolete as a consequence. | 1697 | // obsolete as a consequence. |
1396 | 1698 | ||
1397 | ic.Push(result); | 1699 | ic.Push(result); |
1700 | |||
1398 | } | 1701 | } |
1399 | 1702 | ||
1400 | /// <summary> | 1703 | /// <summary> |
1401 | /// This method is called to handle the construction of an Item | 1704 | /// This method is called to handle the construction of an Item |
1402 | /// instance from the supplied request entity. It is called | 1705 | /// instance from the supplied request entity. It is called |
1403 | /// whenever an Item start tag is detected. | 1706 | /// whenever an Item start tag is detected. |
1404 | /// An instance of an Item is created and initialized to default | 1707 | /// An instance of an Item is created and initialized to default |
1405 | /// values. These values are then overridden from values supplied | 1708 | /// values. These values are then overridden from values supplied |
1406 | /// as attributes to the Item element. | 1709 | /// as attributes to the Item element. |
1407 | /// This item is then stored in the XmlInventoryCollection and | 1710 | /// This item is then stored in the XmlInventoryCollection and |
1408 | /// will be verified by Validate. | 1711 | /// will be verified by Validate. |
@@ -1412,6 +1715,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1412 | 1715 | ||
1413 | private void CollectItem(XmlInventoryCollection ic) | 1716 | private void CollectItem(XmlInventoryCollection ic) |
1414 | { | 1717 | { |
1718 | |||
1415 | Rest.Log.DebugFormat("{0} Interpret item element", MsgId); | 1719 | Rest.Log.DebugFormat("{0} Interpret item element", MsgId); |
1416 | 1720 | ||
1417 | InventoryItemBase result = new InventoryItemBase(); | 1721 | InventoryItemBase result = new InventoryItemBase(); |
@@ -1432,6 +1736,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1432 | { | 1736 | { |
1433 | for (int i = 0; i < ic.xml.AttributeCount; i++) | 1737 | for (int i = 0; i < ic.xml.AttributeCount; i++) |
1434 | { | 1738 | { |
1739 | |||
1435 | ic.xml.MoveToAttribute(i); | 1740 | ic.xml.MoveToAttribute(i); |
1436 | 1741 | ||
1437 | switch (ic.xml.Name) | 1742 | switch (ic.xml.Name) |
@@ -1480,36 +1785,37 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1480 | break; | 1785 | break; |
1481 | 1786 | ||
1482 | default : | 1787 | default : |
1483 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", | 1788 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", |
1484 | MsgId, ic.xml.Name, ic.xml.Value); | 1789 | MsgId, ic.xml.Name, ic.xml.Value); |
1485 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1790 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", |
1486 | Rest.HttpStatusDescBadRequest+": unrecognized attribute"); | 1791 | ic.xml.Name)); |
1487 | break; | 1792 | break; |
1488 | } | 1793 | } |
1489 | } | 1794 | } |
1490 | } | 1795 | } |
1491 | 1796 | ||
1492 | ic.xml.MoveToElement(); | 1797 | ic.xml.MoveToElement(); |
1493 | 1798 | ||
1494 | ic.Push(result); | 1799 | ic.Push(result); |
1800 | |||
1495 | } | 1801 | } |
1496 | 1802 | ||
1497 | /// <summary> | 1803 | /// <summary> |
1498 | /// This method assembles an asset instance from the | 1804 | /// This method assembles an asset instance from the |
1499 | /// information supplied in the request's entity. It is | 1805 | /// information supplied in the request's entity. It is |
1500 | /// called as a result of detecting a start tag for a | 1806 | /// called as a result of detecting a start tag for a |
1501 | /// type of Asset. | 1807 | /// type of Asset. |
1502 | /// The information is collected locally, and an asset | 1808 | /// The information is collected locally, and an asset |
1503 | /// instance is created only if the basic XML parsing | 1809 | /// instance is created only if the basic XML parsing |
1504 | /// completes successfully. | 1810 | /// completes successfully. |
1505 | /// Default values for all parts of the asset are | 1811 | /// Default values for all parts of the asset are |
1506 | /// established before overriding them from the supplied | 1812 | /// established before overriding them from the supplied |
1507 | /// XML. | 1813 | /// XML. |
1508 | /// If an asset has inline=true as an attribute, then | 1814 | /// If an asset has inline=true as an attribute, then |
1509 | /// the element contains the data representing the | 1815 | /// the element contains the data representing the |
1510 | /// asset. This is saved as the data component. | 1816 | /// asset. This is saved as the data component. |
1511 | /// inline=false means that the element's payload is | 1817 | /// inline=false means that the element's payload is |
1512 | /// simply the UUID of the asset referenced by the | 1818 | /// simply the UUID of the asset referenced by the |
1513 | /// item being constructed. | 1819 | /// item being constructed. |
1514 | /// An asset, if created is stored in the | 1820 | /// An asset, if created is stored in the |
1515 | /// XmlInventoryCollection | 1821 | /// XmlInventoryCollection |
@@ -1570,10 +1876,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1570 | break; | 1876 | break; |
1571 | 1877 | ||
1572 | default : | 1878 | default : |
1573 | Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", | 1879 | Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", |
1574 | MsgId, ic.xml.Name, ic.xml.Value); | 1880 | MsgId, ic.xml.Name, ic.xml.Value); |
1575 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1881 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1576 | Rest.HttpStatusDescBadRequest); | 1882 | String.Format("unrecognized attribute <{0}>", ic.xml.Name)); |
1577 | break; | 1883 | break; |
1578 | } | 1884 | } |
1579 | } | 1885 | } |
@@ -1583,7 +1889,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1583 | 1889 | ||
1584 | // If this is a reference to an existing asset, just store the | 1890 | // If this is a reference to an existing asset, just store the |
1585 | // asset ID into the item. | 1891 | // asset ID into the item. |
1586 | 1892 | ||
1587 | if (!inline) | 1893 | if (!inline) |
1588 | { | 1894 | { |
1589 | if (ic.Item != null) | 1895 | if (ic.Item != null) |
@@ -1594,17 +1900,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1594 | else | 1900 | else |
1595 | { | 1901 | { |
1596 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); | 1902 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); |
1597 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1903 | ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); |
1598 | Rest.HttpStatusDescBadRequest+": no context for asset"); | ||
1599 | } | 1904 | } |
1600 | } | 1905 | } |
1601 | 1906 | ||
1602 | // Otherwise, generate an asset ID, store that into the item, and | 1907 | // Otherwise, generate an asset ID, store that into the item, and |
1603 | // create an entry in the asset list for the inlined asset. But | 1908 | // create an entry in the asset list for the inlined asset. But |
1604 | // only if the size is non-zero. | 1909 | // only if the size is non-zero. |
1605 | 1910 | ||
1606 | else | 1911 | else |
1607 | { | 1912 | { |
1913 | |||
1608 | string b64string = null; | 1914 | string b64string = null; |
1609 | 1915 | ||
1610 | // Generate a UUID of none were given, and generally none should | 1916 | // Generate a UUID of none were given, and generally none should |
@@ -1617,17 +1923,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1617 | 1923 | ||
1618 | // Create AssetBase entity to hold the inlined asset | 1924 | // Create AssetBase entity to hold the inlined asset |
1619 | 1925 | ||
1620 | asset = new AssetBase(uuid, name); | 1926 | asset = new AssetBase(uuid, name); |
1621 | 1927 | ||
1622 | asset.Description = desc; | 1928 | asset.Description = desc; |
1623 | asset.Type = type; // type == 0 == texture | 1929 | asset.Type = type; // type == 0 == texture |
1624 | asset.Local = local; | 1930 | asset.Local = local; |
1625 | asset.Temporary = temp; | 1931 | asset.Temporary = temp; |
1626 | 1932 | ||
1627 | b64string = ic.xml.ReadElementContentAsString(); | 1933 | b64string = ic.xml.ReadElementContentAsString(); |
1628 | 1934 | ||
1629 | Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); | 1935 | Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); |
1630 | Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, | 1936 | Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, |
1631 | b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); | 1937 | b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); |
1632 | 1938 | ||
1633 | asset.Data = Convert.FromBase64String(b64string); | 1939 | asset.Data = Convert.FromBase64String(b64string); |
@@ -1646,19 +1952,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1646 | { | 1952 | { |
1647 | ic.Item.AssetID = uuid; | 1953 | ic.Item.AssetID = uuid; |
1648 | } | 1954 | } |
1955 | |||
1649 | } | 1956 | } |
1650 | 1957 | ||
1651 | ic.Push(asset); | 1958 | ic.Push(asset); |
1959 | |||
1652 | } | 1960 | } |
1653 | 1961 | ||
1654 | /// <summary> | 1962 | /// <summary> |
1655 | /// Store any permissions information provided by the request. | 1963 | /// Store any permissions information provided by the request. |
1656 | /// This overrides the default permissions set when the | 1964 | /// This overrides the default permissions set when the |
1657 | /// XmlInventoryCollection object was created. | 1965 | /// XmlInventoryCollection object was created. |
1658 | /// </summary> | 1966 | /// </summary> |
1659 | 1967 | ||
1660 | private void CollectPermissions(XmlInventoryCollection ic) | 1968 | private void CollectPermissions(XmlInventoryCollection ic) |
1661 | { | 1969 | { |
1970 | |||
1662 | if (ic.xml.HasAttributes) | 1971 | if (ic.xml.HasAttributes) |
1663 | { | 1972 | { |
1664 | for (int i = 0; i < ic.xml.AttributeCount; i++) | 1973 | for (int i = 0; i < ic.xml.AttributeCount; i++) |
@@ -1681,14 +1990,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1681 | default : | 1990 | default : |
1682 | Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", | 1991 | Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", |
1683 | MsgId,ic.xml.Name, ic.xml.Value); | 1992 | MsgId,ic.xml.Name, ic.xml.Value); |
1684 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1993 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1685 | Rest.HttpStatusDescBadRequest); | 1994 | String.Format("invalid attribute <{0}>", ic.xml.Name)); |
1686 | break; | 1995 | break; |
1687 | } | 1996 | } |
1688 | } | 1997 | } |
1689 | } | 1998 | } |
1690 | 1999 | ||
1691 | ic.xml.MoveToElement(); | 2000 | ic.xml.MoveToElement(); |
2001 | |||
1692 | } | 2002 | } |
1693 | 2003 | ||
1694 | /// <summary> | 2004 | /// <summary> |
@@ -1703,35 +2013,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1703 | 2013 | ||
1704 | private void Validate(XmlInventoryCollection ic) | 2014 | private void Validate(XmlInventoryCollection ic) |
1705 | { | 2015 | { |
2016 | |||
1706 | // There really should be an item present if we've | 2017 | // There really should be an item present if we've |
1707 | // called validate. So fail if there is not. | 2018 | // called validate. So fail if there is not. |
1708 | 2019 | ||
1709 | if (ic.Item == null) | 2020 | if (ic.Item == null) |
1710 | { | 2021 | { |
1711 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); | 2022 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); |
1712 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 2023 | ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); |
1713 | Rest.HttpStatusDescBadRequest+": request parse error"); | ||
1714 | } | 2024 | } |
1715 | 2025 | ||
1716 | // Every item is required to have a name (via REST anyway) | 2026 | // Every item is required to have a name (via REST anyway) |
1717 | 2027 | ||
1718 | if (ic.Item.Name == String.Empty) | 2028 | if (ic.Item.Name == String.Empty) |
1719 | { | 2029 | { |
1720 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); | 2030 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); |
1721 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 2031 | ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); |
1722 | Rest.HttpStatusDescBadRequest+": item name required"); | ||
1723 | } | 2032 | } |
1724 | 2033 | ||
1725 | // An item MUST have an asset ID. AssetID should never be zero | 2034 | // An item MUST have an asset ID. AssetID should never be zero |
1726 | // here. It should always get set from the information stored | 2035 | // here. It should always get set from the information stored |
1727 | // when the Asset element was processed. | 2036 | // when the Asset element was processed. |
1728 | 2037 | ||
1729 | if (ic.Item.AssetID == LLUUID.Zero) | 2038 | if (ic.Item.AssetID == LLUUID.Zero) |
1730 | { | 2039 | { |
2040 | |||
1731 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); | 2041 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); |
1732 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); | 2042 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); |
1733 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 2043 | ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); |
1734 | Rest.HttpStatusDescBadRequest+": asset information required"); | 2044 | |
1735 | } | 2045 | } |
1736 | 2046 | ||
1737 | // If the item is new, then assign it an ID | 2047 | // If the item is new, then assign it an ID |
@@ -1744,18 +2054,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1744 | // If the context is being implied, obtain the current | 2054 | // If the context is being implied, obtain the current |
1745 | // folder item's ID. If it was specified explicitly, make | 2055 | // folder item's ID. If it was specified explicitly, make |
1746 | // sure that theparent folder exists. | 2056 | // sure that theparent folder exists. |
1747 | 2057 | ||
1748 | if (ic.Item.Folder == LLUUID.Zero) | 2058 | if (ic.Item.Folder == LLUUID.Zero) |
1749 | { | 2059 | { |
1750 | ic.Item.Folder = ic.Parent(); | 2060 | ic.Item.Folder = ic.Parent(); |
1751 | } | 2061 | } |
1752 | else | 2062 | else |
1753 | { | 2063 | { |
2064 | |||
1754 | bool found = false; | 2065 | bool found = false; |
1755 | 2066 | ||
1756 | foreach (InventoryFolderBase parent in ic.rdata.folders) | 2067 | foreach (InventoryFolderBase parent in ic.rdata.folders) |
1757 | { | 2068 | { |
1758 | if (parent.ID == ic.Item.Folder) | 2069 | if ( parent.ID == ic.Item.Folder ) |
1759 | { | 2070 | { |
1760 | found = true; | 2071 | found = true; |
1761 | break; | 2072 | break; |
@@ -1764,11 +2075,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1764 | 2075 | ||
1765 | if (!found) | 2076 | if (!found) |
1766 | { | 2077 | { |
1767 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", | 2078 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", |
1768 | MsgId, ic.Item.Folder, ic.Item.ID); | 2079 | MsgId, ic.Item.Folder, ic.Item.ID); |
1769 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 2080 | ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); |
1770 | Rest.HttpStatusDescBadRequest+": parent information required"); | ||
1771 | } | 2081 | } |
2082 | |||
1772 | } | 2083 | } |
1773 | 2084 | ||
1774 | // If this is an inline asset being constructed in the context | 2085 | // If this is an inline asset being constructed in the context |
@@ -1790,12 +2101,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1790 | ic.Item.NextPermissions = ic.NextPermissions; | 2101 | ic.Item.NextPermissions = ic.NextPermissions; |
1791 | 2102 | ||
1792 | // If no type was specified for this item, we can attempt to | 2103 | // If no type was specified for this item, we can attempt to |
1793 | // infer something from the file type maybe. This is NOT as | 2104 | // infer something from the file type maybe. This is NOT as |
1794 | // good as having type be specified in the XML. | 2105 | // good as having type be specified in the XML. |
1795 | 2106 | ||
1796 | if (ic.Item.AssetType == (int) AssetType.Unknown || | 2107 | if (ic.Item.AssetType == (int) AssetType.Unknown || |
1797 | ic.Item.InvType == (int) AssetType.Unknown) | 2108 | ic.Item.InvType == (int) AssetType.Unknown) |
1798 | { | 2109 | { |
2110 | |||
1799 | Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); | 2111 | Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); |
1800 | 2112 | ||
1801 | string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); | 2113 | string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); |
@@ -1815,7 +2127,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1815 | 2127 | ||
1816 | if (parts.Length > 1) | 2128 | if (parts.Length > 1) |
1817 | { | 2129 | { |
1818 | Rest.Log.DebugFormat("{0} File type is {1}", | 2130 | Rest.Log.DebugFormat("{0} File type is {1}", |
1819 | MsgId, parts[parts.Length - 1]); | 2131 | MsgId, parts[parts.Length - 1]); |
1820 | switch (parts[parts.Length - 1]) | 2132 | switch (parts[parts.Length - 1]) |
1821 | { | 2133 | { |
@@ -1823,7 +2135,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1823 | case "jpeg-2000" : | 2135 | case "jpeg-2000" : |
1824 | case "jpg2000" : | 2136 | case "jpg2000" : |
1825 | case "jpg-2000" : | 2137 | case "jpg-2000" : |
1826 | Rest.Log.DebugFormat("{0} Type {1} inferred", | 2138 | Rest.Log.DebugFormat("{0} Type {1} inferred", |
1827 | MsgId, parts[parts.Length-1]); | 2139 | MsgId, parts[parts.Length-1]); |
1828 | if (ic.Item.AssetType == (int) AssetType.Unknown) | 2140 | if (ic.Item.AssetType == (int) AssetType.Unknown) |
1829 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | 2141 | ic.Item.AssetType = (int) AssetType.ImageJPEG; |
@@ -1832,7 +2144,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1832 | break; | 2144 | break; |
1833 | case "jpg" : | 2145 | case "jpg" : |
1834 | case "jpeg" : | 2146 | case "jpeg" : |
1835 | Rest.Log.DebugFormat("{0} Type {1} inferred", | 2147 | Rest.Log.DebugFormat("{0} Type {1} inferred", |
1836 | MsgId, parts[parts.Length - 1]); | 2148 | MsgId, parts[parts.Length - 1]); |
1837 | if (ic.Item.AssetType == (int) AssetType.Unknown) | 2149 | if (ic.Item.AssetType == (int) AssetType.Unknown) |
1838 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | 2150 | ic.Item.AssetType = (int) AssetType.ImageJPEG; |
@@ -1873,19 +2185,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1873 | temp = OpenJPEGNet.LoadTGAClass.LoadTGA(tgadata); | 2185 | temp = OpenJPEGNet.LoadTGAClass.LoadTGA(tgadata); |
1874 | ic.Asset.Data = OpenJPEGNet.OpenJPEG.EncodeFromImage(temp, true); | 2186 | ic.Asset.Data = OpenJPEGNet.OpenJPEG.EncodeFromImage(temp, true); |
1875 | } | 2187 | } |
1876 | 2188 | ||
1877 | ic.reset(); | 2189 | ic.reset(); |
2190 | |||
1878 | } | 2191 | } |
1879 | 2192 | ||
1880 | #region Inventory RequestData extension | 2193 | #region Inventory RequestData extension |
1881 | 2194 | ||
1882 | internal class InventoryRequestData : RequestData | 2195 | internal class InventoryRequestData : RequestData |
1883 | { | 2196 | { |
2197 | |||
1884 | /// <summary> | 2198 | /// <summary> |
1885 | /// These are the inventory specific request/response state | 2199 | /// These are the inventory specific request/response state |
1886 | /// extensions. | 2200 | /// extensions. |
1887 | /// </summary> | 2201 | /// </summary> |
1888 | 2202 | ||
2203 | internal LLUUID uuid = LLUUID.Zero; | ||
1889 | internal bool HaveInventory = false; | 2204 | internal bool HaveInventory = false; |
1890 | internal ICollection<InventoryFolderImpl> folders = null; | 2205 | internal ICollection<InventoryFolderImpl> folders = null; |
1891 | internal ICollection<InventoryItemBase> items = null; | 2206 | internal ICollection<InventoryItemBase> items = null; |
@@ -1898,7 +2213,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1898 | } | 2213 | } |
1899 | 2214 | ||
1900 | /// <summary> | 2215 | /// <summary> |
1901 | /// This is the callback method required by inventory services. The | 2216 | /// This is the callback method required by inventory services. The |
1902 | /// requestor issues an inventory request and then blocks until this | 2217 | /// requestor issues an inventory request and then blocks until this |
1903 | /// method signals the monitor. | 2218 | /// method signals the monitor. |
1904 | /// </summary> | 2219 | /// </summary> |
@@ -1914,6 +2229,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1914 | Monitor.Pulse(this); | 2229 | Monitor.Pulse(this); |
1915 | } | 2230 | } |
1916 | } | 2231 | } |
2232 | |||
1917 | } | 2233 | } |
1918 | 2234 | ||
1919 | #endregion Inventory RequestData extension | 2235 | #endregion Inventory RequestData extension |
@@ -1926,6 +2242,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1926 | 2242 | ||
1927 | internal class XmlInventoryCollection : InventoryCollection | 2243 | internal class XmlInventoryCollection : InventoryCollection |
1928 | { | 2244 | { |
2245 | |||
1929 | internal InventoryRequestData rdata; | 2246 | internal InventoryRequestData rdata; |
1930 | private Stack<InventoryFolderBase> stk; | 2247 | private Stack<InventoryFolderBase> stk; |
1931 | 2248 | ||
@@ -2014,10 +2331,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
2014 | initPermissions(); | 2331 | initPermissions(); |
2015 | } | 2332 | } |
2016 | 2333 | ||
2017 | internal void Fail(int code, string desc) | 2334 | internal void Fail(int code, string addendum) |
2018 | { | 2335 | { |
2019 | rdata.Fail(code, desc); | 2336 | rdata.Fail(code, addendum); |
2020 | } | 2337 | } |
2338 | |||
2021 | } | 2339 | } |
2022 | } | 2340 | } |
2023 | } | 2341 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs index a5165d9..984f6d3 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs | |||
@@ -140,7 +140,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
140 | { | 140 | { |
141 | if (!rdata.IsAuthenticated) | 141 | if (!rdata.IsAuthenticated) |
142 | { | 142 | { |
143 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); | 143 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, |
144 | String.Format("user \"{0}\" could not be authenticated", rdata.userName)); | ||
144 | } | 145 | } |
145 | } | 146 | } |
146 | catch (RestException e) | 147 | catch (RestException e) |
@@ -160,10 +161,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
160 | 161 | ||
161 | // Check that a test was specified | 162 | // Check that a test was specified |
162 | 163 | ||
163 | if (rdata.parameters.Length < 1) | 164 | if (rdata.Parameters.Length < 1) |
164 | { | 165 | { |
165 | Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); | 166 | Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); |
166 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | 167 | rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters"); |
167 | } | 168 | } |
168 | 169 | ||
169 | // Select the test | 170 | // Select the test |
@@ -180,8 +181,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
180 | private static bool testsLoaded = false; | 181 | private static bool testsLoaded = false; |
181 | private static List<Type> classes = new List<Type>(); | 182 | private static List<Type> classes = new List<Type>(); |
182 | private static List<ITest> tests = new List<ITest>(); | 183 | private static List<ITest> tests = new List<ITest>(); |
183 | private static Type[] parms = new Type[1]; | 184 | private static Type[] parms = new Type[0]; |
184 | private static Object[] args = new Object[1]; | 185 | private static Object[] args = new Object[0]; |
185 | 186 | ||
186 | static RestTestServices() | 187 | static RestTestServices() |
187 | { | 188 | { |
@@ -191,9 +192,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
191 | Type[] types = m.GetTypes(); | 192 | Type[] types = m.GetTypes(); |
192 | foreach (Type t in types) | 193 | foreach (Type t in types) |
193 | { | 194 | { |
194 | if (t.GetInterface("ITest") != null) | 195 | try |
195 | { | 196 | { |
196 | classes.Add(t); | 197 | if (t.GetInterface("ITest") != null) |
198 | { | ||
199 | classes.Add(t); | ||
200 | } | ||
201 | } | ||
202 | catch (Exception e) | ||
203 | { | ||
204 | Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message); | ||
197 | } | 205 | } |
198 | } | 206 | } |
199 | } | 207 | } |
@@ -205,27 +213,38 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
205 | /// registering itself with this handler. | 213 | /// registering itself with this handler. |
206 | /// I was not able to make this code work in a constructor. | 214 | /// I was not able to make this code work in a constructor. |
207 | /// </summary> | 215 | /// </summary> |
216 | |||
208 | private void loadTests() | 217 | private void loadTests() |
209 | { | 218 | { |
210 | lock (tests) | 219 | lock (tests) |
211 | { | 220 | { |
212 | if (!testsLoaded) | 221 | if (!testsLoaded) |
213 | { | 222 | { |
214 | parms[0] = this.GetType(); | ||
215 | args[0] = this; | ||
216 | 223 | ||
217 | ConstructorInfo ci; | 224 | ConstructorInfo ci; |
218 | Object ht; | 225 | Object ht; |
219 | 226 | ||
220 | foreach (Type t in classes) | 227 | foreach (Type t in classes) |
221 | { | 228 | { |
222 | ci = t.GetConstructor(parms); | 229 | try |
223 | ht = ci.Invoke(args); | 230 | { |
224 | tests.Add((ITest)ht); | 231 | if (t.GetInterface("ITest") != null) |
232 | { | ||
233 | ci = t.GetConstructor(parms); | ||
234 | ht = ci.Invoke(args); | ||
235 | tests.Add((ITest)ht); | ||
236 | Rest.Log.WarnFormat("{0} Test {1} added", MsgId, t); | ||
237 | } | ||
238 | } | ||
239 | catch (Exception e) | ||
240 | { | ||
241 | Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message); | ||
242 | } | ||
225 | } | 243 | } |
226 | testsLoaded = true; | 244 | testsLoaded = true; |
227 | } | 245 | } |
228 | } | 246 | } |
229 | } | 247 | } |
248 | |||
230 | } | 249 | } |
231 | } | 250 | } |