RequestData.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Security.Cryptography;
  31. using System.Text;
  32. using System.Text.RegularExpressions;
  33. using System.Xml;
  34. using OpenSim.Framework;
  35. using OpenSim.Framework.Servers;
  36. using OpenSim.Framework.Servers.HttpServer;
  37. namespace OpenSim.ApplicationPlugins.Rest.Inventory
  38. {
  39. /// <summary>
  40. /// This class represents the current REST request. It
  41. /// encapsulates the request/response state and takes care
  42. /// of response generation without exposing the REST handler
  43. /// to the actual mechanisms involved.
  44. ///
  45. /// This structure is created on entry to the Handler
  46. /// method and is disposed of upon return. It is part of
  47. /// the plug-in infrastructure, rather than the functionally
  48. /// specific REST handler, and fundamental changes to
  49. /// this should be reflected in the Rest HandlerVersion. The
  50. /// object is instantiated, and may be extended by, any
  51. /// given handler. See the inventory handler for an example
  52. /// of this.
  53. ///
  54. /// If possible, the underlying request/response state is not
  55. /// changed until the handler explicitly issues a Respond call.
  56. /// This ensures that the request/response pair can be safely
  57. /// processed by subsequent, unrelated, handlers even id the
  58. /// agent handler had completed much of its processing. Think
  59. /// of it as a transactional req/resp capability.
  60. /// </summary>
  61. public class RequestData
  62. {
  63. // HTTP Server interface data (Received values)
  64. internal OSHttpRequest request = null;
  65. internal OSHttpResponse response = null;
  66. internal string qprefix = null;
  67. // Request lifetime values
  68. // buffer is global because it is referenced by the handler
  69. // in supported of streamed requests.
  70. // If a service provider wants to construct the message
  71. // body explicitly it can use body to do this. The value
  72. // in body is used if the buffer is still null when a response
  73. // is generated.
  74. // Storing information in body will suppress the return of
  75. // statusBody which is only intended to report status on
  76. // requests which do not themselves ordinarily generate
  77. // an informational response. All of this is handled in
  78. // Respond().
  79. internal byte[] buffer = null;
  80. internal string body = null;
  81. internal string bodyType = "text/html";
  82. // The encoding in effect is set to a server default. It may
  83. // subsequently be overridden by a Content header. This
  84. // value is established during construction and is used
  85. // wherever encoding services are needed.
  86. internal Encoding encoding = Rest.Encoding;
  87. // These values are derived from the supplied URL. They
  88. // are initialized during construction.
  89. internal string path = null;
  90. internal string method = null;
  91. internal Uri uri = null;
  92. internal string query = null;
  93. internal string hostname = "localhost";
  94. internal int port = 80;
  95. // The path part of the URI is decomposed. pathNodes
  96. // is an array of every element in the URI. Parameters
  97. // is an array that contains only those nodes that
  98. // are not a part of the authority prefix
  99. private string[] pathNodes = null;
  100. private string[] parameters = null;
  101. private static readonly string[] EmptyPath = { String.Empty };
  102. // The status code gets set during the course of processing
  103. // and is the HTTP completion code. The status body is
  104. // initialized during construction, is appended to during the
  105. // course of execution, and is finalized during Respond
  106. // processing.
  107. //
  108. // Fail processing marks the request as failed and this is
  109. // then used to inhibit processing during Response processing.
  110. internal int statusCode = 0;
  111. internal string statusBody = String.Empty;
  112. internal bool fail = false;
  113. // This carries the URL to which the client should be redirected.
  114. // It is set by the service provider using the Redirect call.
  115. internal string redirectLocation = null;
  116. // These values influence response processing. They can be set by
  117. // service providers according to need. The defaults are generally
  118. // good.
  119. internal bool keepAlive = false;
  120. internal bool chunked = false;
  121. // XML related state
  122. internal XmlWriter writer = null;
  123. internal XmlReader reader = null;
  124. // Internal working state
  125. private StringBuilder sbuilder = new StringBuilder(1024);
  126. private MemoryStream xmldata = null;
  127. // This is used to make the response mechanism idempotent.
  128. internal bool handled = false;
  129. // Authentication related state
  130. //
  131. // Two supported authentication mechanisms are:
  132. // scheme = Rest.AS_BASIC;
  133. // scheme = Rest.AS_DIGEST;
  134. // Presented in that order (as required by spec)
  135. // A service provider can set the scheme variable to
  136. // force selection of a particular authentication model
  137. // (choosing from amongst those supported of course)
  138. //
  139. internal bool authenticated = false;
  140. internal string scheme = Rest.Scheme;
  141. internal string realm = Rest.Realm;
  142. internal string domain = null;
  143. internal string nonce = null;
  144. internal string cnonce = null;
  145. internal string qop = Rest.Qop_Auth;
  146. internal string opaque = null;
  147. internal string stale = null;
  148. internal string algorithm = Rest.Digest_MD5;
  149. internal string authParms = null;
  150. internal string authPrefix = null;
  151. internal string userName = String.Empty;
  152. internal string userPass = String.Empty;
  153. // Session related tables. These are only needed if QOP is set to "auth-sess"
  154. // and for now at least, it is not. Session related authentication is of
  155. // questionable merit in the context of REST anyway, but it is, arguably, more
  156. // secure.
  157. private static Dictionary<string,string> cntable = new Dictionary<string,string>();
  158. private static Dictionary<string,string> sktable = new Dictionary<string,string>();
  159. // This dictionary is used to keep track fo all of the parameters discovered
  160. // when the authorisation header is anaylsed.
  161. private Dictionary<string,string> authparms = new Dictionary<string,string>();
  162. // These regular expressions are used to decipher the various header entries.
  163. private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
  164. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  165. private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
  166. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  167. private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
  168. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  169. private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
  170. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  171. private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
  172. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  173. // For efficiency, we create static instances of these objects
  174. private static MD5 md5hash = MD5.Create();
  175. private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
  176. #region properties
  177. // Just for convenience...
  178. internal string MsgId
  179. {
  180. get { return Rest.MsgId; }
  181. }
  182. /// <summary>
  183. /// Return a boolean indication of whether or no an authenticated user is
  184. /// associated with this request. This could be wholly integrated, but
  185. /// that would make authentication mandatory.
  186. /// </summary>
  187. internal bool IsAuthenticated
  188. {
  189. get
  190. {
  191. if (Rest.Authenticate)
  192. {
  193. if (!authenticated)
  194. {
  195. authenticate();
  196. }
  197. return authenticated;
  198. }
  199. else return true;
  200. }
  201. }
  202. /// <summary>
  203. /// Access to all 'nodes' in the supplied URI as an
  204. /// array of strings.
  205. /// </summary>
  206. internal string[] PathNodes
  207. {
  208. get
  209. {
  210. return pathNodes;
  211. }
  212. }
  213. /// <summary>
  214. /// Access to all non-prefix 'nodes' in the supplied URI as an
  215. /// array of strings. These identify a specific resource that
  216. /// is managed by the authority (the prefix).
  217. /// </summary>
  218. internal string[] Parameters
  219. {
  220. get
  221. {
  222. return parameters;
  223. }
  224. }
  225. #endregion properties
  226. #region constructors
  227. // Constructor
  228. internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
  229. {
  230. request = p_request;
  231. response = p_response;
  232. qprefix = p_qprefix;
  233. sbuilder.Length = 0;
  234. encoding = request.ContentEncoding;
  235. if (encoding == null)
  236. {
  237. encoding = Rest.Encoding;
  238. }
  239. method = request.HttpMethod.ToLower();
  240. initUrl();
  241. initParameters(p_qprefix.Length);
  242. }
  243. #endregion constructors
  244. #region authentication_common
  245. /// <summary>
  246. /// The REST handler has requested authentication. Authentication
  247. /// is considered to be with respect to the current values for
  248. /// Realm, domain, etc.
  249. ///
  250. /// This method checks to see if the current request is already
  251. /// authenticated for this domain. If it is, then it returns
  252. /// true. If it is not, then it issues a challenge to the client
  253. /// and responds negatively to the request.
  254. ///
  255. /// As soon as authentication failure is detected the method calls
  256. /// DoChallenge() which terminates the request with REST exception
  257. /// for unauthroized access.
  258. /// </summary>
  259. private void authenticate()
  260. {
  261. string authdata = request.Headers.Get("Authorization");
  262. string reqscheme = String.Empty;
  263. // If we don't have an authorization header, then this
  264. // user is certainly not authorized. This is the typical
  265. // pivot for the 1st request by a client.
  266. if (authdata == null)
  267. {
  268. Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
  269. DoChallenge();
  270. }
  271. // So, we have authentication data, now we have to check to
  272. // see what we got and whether or not it is valid for the
  273. // current domain. To do this we need to interpret the data
  274. // provided in the Authorization header. First we need to
  275. // identify the scheme being used and route accordingly.
  276. MatchCollection matches = schema.Matches(authdata);
  277. foreach (Match m in matches)
  278. {
  279. Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
  280. reqscheme = m.Groups["scheme"].Value.ToLower();
  281. }
  282. // If we want a specific authentication mechanism, make sure
  283. // we get it. null indicates we don't care. non-null indicates
  284. // a specific scheme requirement.
  285. if (scheme != null && scheme.ToLower() != reqscheme)
  286. {
  287. Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId);
  288. DoChallenge();
  289. }
  290. // In the future, these could be made into plug-ins...
  291. // But for now at least we have no reason to use anything other
  292. // then MD5. TLS/SSL are taken care of elsewhere.
  293. switch (reqscheme)
  294. {
  295. case "digest" :
  296. Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
  297. DoDigest(authdata);
  298. break;
  299. case "basic" :
  300. Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
  301. DoBasic(authdata);
  302. break;
  303. }
  304. // If the current header is invalid, then a challenge is still needed.
  305. if (!authenticated)
  306. {
  307. Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
  308. DoChallenge();
  309. }
  310. }
  311. /// <summary>
  312. /// Construct the necessary WWW-Authenticate headers and fail the request
  313. /// with a NOT AUTHORIZED response. The parameters are the union of values
  314. /// required by the supported schemes.
  315. /// </summary>
  316. private void DoChallenge()
  317. {
  318. Flush();
  319. nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
  320. Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
  321. Fail(Rest.HttpStatusCodeNotAuthorized);
  322. }
  323. /// <summary>
  324. /// The Flush() call is here to support a problem encountered with the
  325. /// client where an authentication rejection was lost because the rejection
  326. /// may flow before the clienthas finished sending us the inbound data stream,
  327. /// in which case the client responds to the socket error on out put, and
  328. /// never sees the authentication challenge. The client should be fixed,
  329. /// because this solution leaves the server prone to DOS attacks. A message
  330. /// will be issued whenever flushing occurs. It can be enabled/disabled from
  331. /// the configuration file.
  332. /// </summary>
  333. private void Flush()
  334. {
  335. if (Rest.FlushEnabled)
  336. {
  337. byte[] dbuffer = new byte[8192];
  338. Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
  339. while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
  340. }
  341. return;
  342. }
  343. // Indicate that authentication is required
  344. private void Challenge(string scheme, string realm, string domain, string nonce,
  345. string opaque, string stale, string alg,
  346. string qop, string auth)
  347. {
  348. sbuilder.Length = 0;
  349. // The service provider can force a particular scheme by
  350. // assigning a value to scheme.
  351. // Basic authentication is pretty simple.
  352. // Just specify the realm in question.
  353. if (scheme == null || scheme == Rest.AS_BASIC)
  354. {
  355. sbuilder.Append(Rest.AS_BASIC);
  356. if (realm != null)
  357. {
  358. sbuilder.Append(" realm=");
  359. sbuilder.Append(Rest.CS_DQUOTE);
  360. sbuilder.Append(realm);
  361. sbuilder.Append(Rest.CS_DQUOTE);
  362. }
  363. AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
  364. }
  365. sbuilder.Length = 0;
  366. // Digest authentication takes somewhat more
  367. // to express.
  368. if (scheme == null || scheme == Rest.AS_DIGEST)
  369. {
  370. sbuilder.Append(Rest.AS_DIGEST);
  371. sbuilder.Append(" ");
  372. // Specify the effective realm. This should
  373. // never be null if we are uthenticating, as it is required for all
  374. // authentication schemes. It defines, in conjunction with the
  375. // absolute URI information, the domain to which the authentication
  376. // applies. It is an arbitrary string. I *believe* this allows an
  377. // authentication to apply to disjoint resources within the same
  378. // server.
  379. if (realm != null)
  380. {
  381. sbuilder.Append("realm=");
  382. sbuilder.Append(Rest.CS_DQUOTE);
  383. sbuilder.Append(realm);
  384. sbuilder.Append(Rest.CS_DQUOTE);
  385. sbuilder.Append(Rest.CS_COMMA);
  386. }
  387. // Share our nonce. This is *uniquely* generated each time a 401 is
  388. // returned. We do not generate a very sophisticated nonce at the
  389. // moment (it's simply a base64 encoded UUID).
  390. if (nonce != null)
  391. {
  392. sbuilder.Append("nonce=");
  393. sbuilder.Append(Rest.CS_DQUOTE);
  394. sbuilder.Append(nonce);
  395. sbuilder.Append(Rest.CS_DQUOTE);
  396. sbuilder.Append(Rest.CS_COMMA);
  397. }
  398. // The opaque string should be returned by the client unchanged in all
  399. // subsequent requests.
  400. if (opaque != null)
  401. {
  402. sbuilder.Append("opaque=");
  403. sbuilder.Append(Rest.CS_DQUOTE);
  404. sbuilder.Append(opaque);
  405. sbuilder.Append(Rest.CS_DQUOTE);
  406. sbuilder.Append(Rest.CS_COMMA);
  407. }
  408. // This flag indicates that the authentication was rejected because the
  409. // included nonce was stale. The server might use timestamp information
  410. // in the nonce to determine this. We do not.
  411. if (stale != null)
  412. {
  413. sbuilder.Append("stale=");
  414. sbuilder.Append(Rest.CS_DQUOTE);
  415. sbuilder.Append(stale);
  416. sbuilder.Append(Rest.CS_DQUOTE);
  417. sbuilder.Append(Rest.CS_COMMA);
  418. }
  419. // Identifies the algorithm used to produce the digest and checksum.
  420. // The default is MD5.
  421. if (alg != null)
  422. {
  423. sbuilder.Append("algorithm=");
  424. sbuilder.Append(alg);
  425. sbuilder.Append(Rest.CS_COMMA);
  426. }
  427. // Theoretically QOP is optional, but it is required by a compliant
  428. // with current versions of the scheme. In fact IE requires that QOP
  429. // be specified and will refuse to authenticate otherwise.
  430. if (qop != String.Empty)
  431. {
  432. sbuilder.Append("qop=");
  433. sbuilder.Append(Rest.CS_DQUOTE);
  434. sbuilder.Append(qop);
  435. sbuilder.Append(Rest.CS_DQUOTE);
  436. sbuilder.Append(Rest.CS_COMMA);
  437. }
  438. // This parameter allows for arbitrary extensions to the protocol.
  439. // Unrecognized values should be simply ignored.
  440. if (auth != null)
  441. {
  442. sbuilder.Append(auth);
  443. sbuilder.Append(Rest.CS_COMMA);
  444. }
  445. // We don't know the userid that will be used
  446. // so we cannot make any authentication domain
  447. // assumptions. So the prefix will determine
  448. // this.
  449. sbuilder.Append("domain=");
  450. sbuilder.Append(Rest.CS_DQUOTE);
  451. sbuilder.Append(qprefix);
  452. sbuilder.Append(Rest.CS_DQUOTE);
  453. // Generate the authenticate header and we're basically
  454. // done.
  455. AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
  456. }
  457. }
  458. #endregion authentication_common
  459. #region authentication_basic
  460. /// <summary>
  461. /// Interpret a BASIC authorization claim. Some clients can only
  462. /// understand this and also expect it to be the first one
  463. /// offered. So we do.
  464. /// OpenSim also needs this, as it is the only scheme that allows
  465. /// authentication using the hashed passwords stored in the
  466. /// user database.
  467. /// </summary>
  468. private void DoBasic(string authdata)
  469. {
  470. string response = null;
  471. MatchCollection matches = basicParms.Matches(authdata);
  472. // In the case of basic authentication there is
  473. // only expected to be a single argument.
  474. foreach (Match m in matches)
  475. {
  476. authparms.Add("response",m.Groups["pval"].Value);
  477. Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
  478. MsgId, "response", m.Groups["pval"].Value);
  479. }
  480. // Did we get a valid response?
  481. if (authparms.TryGetValue("response", out response))
  482. {
  483. // Decode
  484. response = Rest.Base64ToString(response);
  485. Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
  486. // Extract user & password
  487. Match m = reuserPass.Match(response);
  488. userName = m.Groups["user"].Value;
  489. userPass = m.Groups["pass"].Value;
  490. // Validate against user database
  491. authenticated = Validate(userName,userPass);
  492. }
  493. }
  494. /// <summary>
  495. /// This method provides validation in support of the BASIC
  496. /// authentication method. This is not normaly expected to be
  497. /// used, but is included for completeness (and because I tried
  498. /// it first).
  499. /// </summary>
  500. private bool Validate(string user, string pass)
  501. {
  502. Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
  503. // Both values are required
  504. if (user == null || pass == null)
  505. return false;
  506. // Eliminate any leading or trailing spaces
  507. user = user.Trim();
  508. return vetPassword(user, pass);
  509. }
  510. /// <summary>
  511. /// This is used by the BASIC authentication scheme to calculate
  512. /// the double hash used by OpenSim to encode user's passwords.
  513. /// It returns true, if the supplied password is actually correct.
  514. /// If the specified user-id is not recognized, but the password
  515. /// matches the God password, then this is accepted as an admin
  516. /// session.
  517. /// </summary>
  518. private bool vetPassword(string user, string pass)
  519. {
  520. int x;
  521. string HA1;
  522. string first;
  523. string last;
  524. // Distinguish the parts, if necessary
  525. if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
  526. {
  527. first = user.Substring(0,x);
  528. last = user.Substring(x+1);
  529. }
  530. else
  531. {
  532. first = user;
  533. last = String.Empty;
  534. }
  535. UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
  536. // If we don't recognize the user id, perhaps it is god?
  537. if (udata == null)
  538. return pass == Rest.GodKey;
  539. HA1 = HashToString(pass);
  540. HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
  541. return (0 == sc.Compare(HA1, udata.PasswordHash));
  542. }
  543. #endregion authentication_basic
  544. #region authentication_digest
  545. /// <summary>
  546. /// This is an RFC2617 compliant HTTP MD5 Digest authentication
  547. /// implementation. It has been tested with Firefox, Java HTTP client,
  548. /// and Microsoft's Internet Explorer V7.
  549. /// </summary>
  550. private void DoDigest(string authdata)
  551. {
  552. string response = null;
  553. // Find all of the values of the for x = "y"
  554. MatchCollection matches = digestParm1.Matches(authdata);
  555. foreach (Match m in matches)
  556. {
  557. authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
  558. Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
  559. MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
  560. }
  561. // Find all of the values of the for x = y
  562. matches = digestParm2.Matches(authdata);
  563. foreach (Match m in matches)
  564. {
  565. authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
  566. Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
  567. MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
  568. }
  569. // A response string MUST be returned, otherwise we are
  570. // NOT authenticated.
  571. Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
  572. if (authparms.TryGetValue("response", out response))
  573. {
  574. string temp = null;
  575. do
  576. {
  577. string nck = null;
  578. string ncl = null;
  579. // The userid is sent in clear text. Needed for the
  580. // verification.
  581. authparms.TryGetValue("username", out userName);
  582. // All URI's of which this is a prefix are
  583. // optimistically considered to be authenticated by the
  584. // client. This is also needed to verify the response.
  585. authparms.TryGetValue("uri", out authPrefix);
  586. // There MUST be a nonce string present. We're not preserving any server
  587. // side state and we can't validate the MD5 unless the client returns it
  588. // to us, as it should.
  589. if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
  590. {
  591. Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
  592. break;
  593. }
  594. // If there is an opaque string present, it had better
  595. // match what we sent.
  596. if (authparms.TryGetValue("opaque", out temp))
  597. {
  598. if (temp != opaque)
  599. {
  600. Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
  601. break;
  602. }
  603. }
  604. // If an algorithm string is present, it had better
  605. // match what we sent.
  606. if (authparms.TryGetValue("algorithm", out temp))
  607. {
  608. if (temp != algorithm)
  609. {
  610. Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
  611. break;
  612. }
  613. }
  614. // Quality of protection considerations...
  615. if (authparms.TryGetValue("qop", out temp))
  616. {
  617. qop = temp.ToLower(); // replace with actual value used
  618. // if QOP was specified then
  619. // these MUST be present.
  620. if (!authparms.ContainsKey("cnonce"))
  621. {
  622. Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
  623. Fail(Rest.HttpStatusCodeBadRequest);
  624. break;
  625. }
  626. cnonce = authparms["cnonce"];
  627. if (!authparms.TryGetValue("nc", out nck) || nck == null)
  628. {
  629. Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
  630. Fail(Rest.HttpStatusCodeBadRequest);
  631. break;
  632. }
  633. Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId);
  634. if (cntable.TryGetValue(nonce, out ncl))
  635. {
  636. Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl);
  637. if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
  638. {
  639. Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
  640. Fail(Rest.HttpStatusCodeBadRequest);
  641. break;
  642. }
  643. cntable[nonce] = nck;
  644. }
  645. else
  646. {
  647. lock (cntable) cntable.Add(nonce, nck);
  648. }
  649. }
  650. else
  651. {
  652. qop = String.Empty;
  653. // if QOP was not specified then
  654. // these MUST NOT be present.
  655. if (authparms.ContainsKey("cnonce"))
  656. {
  657. Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
  658. Fail(Rest.HttpStatusCodeBadRequest);
  659. break;
  660. }
  661. if (authparms.ContainsKey("nc"))
  662. {
  663. Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
  664. Fail(Rest.HttpStatusCodeBadRequest);
  665. break;
  666. }
  667. }
  668. // Validate the supplied userid/password info
  669. authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
  670. }
  671. while (false);
  672. }
  673. else
  674. Fail(Rest.HttpStatusCodeBadRequest);
  675. }
  676. /// <summary>
  677. /// This mechanism is used by the digest authentication mechanism
  678. /// to return the user's password. In fact, because the OpenSim
  679. /// user's passwords are already hashed, and the HTTP mechanism
  680. /// does not supply an open password, the hashed passwords cannot
  681. /// be used unless the client has used the same salting mechanism
  682. /// to has the password before using it in the authentication
  683. /// algorithn. This is not inconceivable...
  684. /// </summary>
  685. private string getPassword(string user)
  686. {
  687. int x;
  688. string first;
  689. string last;
  690. // Distinguish the parts, if necessary
  691. if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
  692. {
  693. first = user.Substring(0,x);
  694. last = user.Substring(x+1);
  695. }
  696. else
  697. {
  698. first = user;
  699. last = String.Empty;
  700. }
  701. UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
  702. // If we don;t recognize the user id, perhaps it is god?
  703. if (udata == null)
  704. {
  705. Rest.Log.DebugFormat("{0} Administrator", MsgId);
  706. return Rest.GodKey;
  707. }
  708. else
  709. {
  710. Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
  711. return udata.PasswordHash;
  712. }
  713. }
  714. // Validate the request-digest
  715. private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
  716. {
  717. string patt = null;
  718. string payl = String.Empty;
  719. string KDS = null;
  720. string HA1 = null;
  721. string HA2 = null;
  722. string pass = getPassword(user);
  723. // Generate H(A1)
  724. if (algorithm == Rest.Digest_MD5Sess)
  725. {
  726. if (!sktable.ContainsKey(cnonce))
  727. {
  728. patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
  729. HA1 = HashToString(patt);
  730. sktable.Add(cnonce, HA1);
  731. }
  732. else
  733. {
  734. HA1 = sktable[cnonce];
  735. }
  736. }
  737. else
  738. {
  739. patt = String.Format("{0}:{1}:{2}", user, realm, pass);
  740. HA1 = HashToString(patt);
  741. }
  742. // Generate H(A2)
  743. if (qop == "auth-int")
  744. {
  745. patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
  746. }
  747. else
  748. {
  749. patt = String.Format("{0}:{1}", request.HttpMethod, uri);
  750. }
  751. HA2 = HashToString(patt);
  752. // Generate Digest
  753. if (qop != String.Empty)
  754. {
  755. patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
  756. }
  757. else
  758. {
  759. patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
  760. }
  761. KDS = HashToString(patt);
  762. // Compare the generated sequence with the original
  763. return (0 == sc.Compare(KDS, response));
  764. }
  765. private string HashToString(string pattern)
  766. {
  767. Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
  768. byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
  769. sbuilder.Length = 0;
  770. for (int i = 0; i < hash.Length; i++)
  771. {
  772. sbuilder.Append(hash[i].ToString("x2"));
  773. }
  774. Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
  775. return sbuilder.ToString();
  776. }
  777. #endregion authentication_digest
  778. #region service_interface
  779. /// <summary>
  780. /// Conditionally set a normal completion code. This allows a normal
  781. /// execution path to default.
  782. /// </summary>
  783. internal void Complete()
  784. {
  785. if (statusCode == 0)
  786. {
  787. statusCode = Rest.HttpStatusCodeOK;
  788. }
  789. }
  790. /// <summary>
  791. /// Indicate a functionally-dependent conclusion to the
  792. /// request. See Rest.cs for a list of possible values.
  793. /// </summary>
  794. internal void Complete(int code)
  795. {
  796. statusCode = code;
  797. }
  798. /// <summary>
  799. /// Indicate that a request should be redirected, using
  800. /// the HTTP completion codes. Permanent and temporary
  801. /// redirections may be indicated. The supplied URL is
  802. /// the new location of the resource.
  803. /// </summary>
  804. internal void Redirect(string Url, bool temp)
  805. {
  806. redirectLocation = Url;
  807. if (temp)
  808. {
  809. statusCode = Rest.HttpStatusCodeTemporaryRedirect;
  810. }
  811. else
  812. {
  813. statusCode = Rest.HttpStatusCodePermanentRedirect;
  814. }
  815. Fail(statusCode, String.Empty, true);
  816. }
  817. /// <summary>
  818. /// Fail for an arbitrary reason. Just a failure with
  819. /// headers. The supplied message will be returned in the
  820. /// message body.
  821. /// </summary>
  822. internal void Fail(int code)
  823. {
  824. Fail(code, String.Empty, false);
  825. }
  826. /// <summary>
  827. /// For the more adventurous. This failure also includes a
  828. /// specified entity to be appended to the code-related
  829. /// status string.
  830. /// </summary>
  831. internal void Fail(int code, string addendum)
  832. {
  833. Fail(code, addendum, false);
  834. }
  835. internal void Fail(int code, string addendum, bool reset)
  836. {
  837. statusCode = code;
  838. appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));
  839. // Add any final addendum to the status information
  840. if (addendum != String.Empty)
  841. {
  842. appendStatus(String.Format(addendum));
  843. }
  844. // Help us understand why the request is being rejected
  845. if (Rest.DEBUG)
  846. {
  847. Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
  848. Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
  849. Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
  850. Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
  851. Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
  852. Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
  853. Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
  854. Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
  855. Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
  856. Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
  857. Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
  858. Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
  859. Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
  860. }
  861. fail = true;
  862. // Respond to the client's request, tag the response (for the
  863. // benefit of trace) to indicate the reason.
  864. Respond(String.Format("Failure response: ({0}) : {1} ",
  865. code, Rest.HttpStatusDesc[code]));
  866. // Finally initialize and the throw a RestException. All of the
  867. // handler's infrastructure knows that this is a "normal"
  868. // completion from a code point-of-view.
  869. RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");
  870. re.statusCode = code;
  871. re.statusDesc = Rest.HttpStatusDesc[code];
  872. re.httpmethod = method;
  873. re.httppath = path;
  874. throw re;
  875. }
  876. // Reject this request
  877. internal void Reject()
  878. {
  879. Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
  880. }
  881. // This MUST be called by an agent handler before it returns
  882. // control to Handle, otherwise the request will be ignored.
  883. // This is called implciitly for the REST stream handlers and
  884. // is harmless if it is called twice.
  885. internal virtual bool Respond(string reason)
  886. {
  887. Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
  888. // We do this to try and make multiple Respond requests harmless,
  889. // as it is sometimes convenient to isse a response without
  890. // certain knowledge that it has not previously been done.
  891. if (!handled)
  892. {
  893. Rest.Log.DebugFormat("{0} Generating Response", MsgId);
  894. Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
  895. // A Head request can NOT have a body! So don't waste time on
  896. // formatting if we're going to reject it anyway!
  897. if (method != Rest.HEAD)
  898. {
  899. Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
  900. // If the writer is non-null then we know that an XML
  901. // data component exists. Flush and close the writer and
  902. // then convert the result to the expected buffer format
  903. // unless the request has already been failed for some
  904. // reason.
  905. if (writer != null)
  906. {
  907. Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
  908. Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
  909. writer.Flush();
  910. writer.Close();
  911. if (!fail)
  912. {
  913. buffer = xmldata.ToArray();
  914. AddHeader("Content-Type","application/xml");
  915. }
  916. xmldata.Close();
  917. Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
  918. Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
  919. }
  920. if (buffer == null && body != null)
  921. {
  922. buffer = encoding.GetBytes(body);
  923. AddHeader("Content-Type",bodyType);
  924. }
  925. // OK, if the buffer contains something, regardless of how
  926. // it got there, set various response headers accordingly.
  927. if (buffer != null)
  928. {
  929. Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
  930. }
  931. else
  932. {
  933. if (statusBody != String.Empty)
  934. {
  935. statusBody += Rest.statusTail;
  936. buffer = encoding.GetBytes(statusBody);
  937. AddHeader("Content-Type","text/html");
  938. }
  939. else
  940. {
  941. statusBody = Rest.statusHead;
  942. appendStatus(String.Format(": ({0}) {1}",
  943. statusCode, Rest.HttpStatusDesc[statusCode]));
  944. statusBody += Rest.statusTail;
  945. buffer = encoding.GetBytes(statusBody);
  946. AddHeader("Content-Type","text/html");
  947. }
  948. }
  949. response.ContentLength64 = buffer.Length;
  950. if (response.ContentEncoding == null)
  951. response.ContentEncoding = encoding;
  952. response.SendChunked = chunked;
  953. response.KeepAlive = keepAlive;
  954. }
  955. // Set the status code & description. If nothing has been stored,
  956. // we consider that a success.
  957. if (statusCode == 0)
  958. {
  959. Complete();
  960. }
  961. // Set the response code in the actual carrier
  962. response.StatusCode = statusCode;
  963. // For a redirect we need to set the relocation header accordingly
  964. if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
  965. response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
  966. {
  967. Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
  968. response.RedirectLocation = redirectLocation;
  969. }
  970. // And include the status description if provided.
  971. response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];
  972. // Finally we send back our response.
  973. // We've left the setting of handled' until the
  974. // last minute because the header settings included
  975. // above are pretty harmless. But everything from
  976. // here on down probably leaves the response
  977. // element unusable by anyone else.
  978. handled = true;
  979. // DumpHeaders();
  980. // if (request.InputStream != null)
  981. // {
  982. // Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
  983. // request.InputStream.Close();
  984. // }
  985. if (buffer != null && buffer.Length != 0)
  986. {
  987. Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length);
  988. // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
  989. // MsgId, buffer.Length, encoding.GetString(buffer));
  990. response.OutputStream.Write(buffer, 0, buffer.Length);
  991. }
  992. // Closing the outputstream should complete the transmission process
  993. Rest.Log.DebugFormat("{0} Sending response", MsgId);
  994. // response.OutputStream.Close();
  995. response.Send();
  996. }
  997. Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
  998. return handled;
  999. }
  1000. /// <summary>
  1001. /// These methods allow a service provider to manipulate the
  1002. /// request/response headers. The DumpHeaders method is intended
  1003. /// for problem diagnosis.
  1004. /// </summary>
  1005. internal void AddHeader(string hdr, string data)
  1006. {
  1007. if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data);
  1008. response.AddHeader(hdr, data);
  1009. }
  1010. // internal void RemoveHeader(string hdr)
  1011. // {
  1012. // if (Rest.DEBUG)
  1013. // {
  1014. // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
  1015. // if (response.Headers.Get(hdr) == null)
  1016. // {
  1017. // Rest.Log.DebugFormat("{0} No such header existed",
  1018. // MsgId, hdr);
  1019. // }
  1020. // }
  1021. // response.Headers.Remove(hdr);
  1022. // }
  1023. // internal void DumpHeaders()
  1024. // {
  1025. // if (Rest.DEBUG)
  1026. // {
  1027. // for (int i=0;i<response.Headers.Count;i++)
  1028. // {
  1029. // Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
  1030. // response.Headers.Get(i));
  1031. // }
  1032. // }
  1033. // }
  1034. // Setup the XML writer for output
  1035. internal void initXmlWriter()
  1036. {
  1037. XmlWriterSettings settings = new XmlWriterSettings();
  1038. xmldata = new MemoryStream();
  1039. settings.Indent = true;
  1040. settings.IndentChars = " ";
  1041. settings.Encoding = encoding;
  1042. settings.CloseOutput = false;
  1043. settings.OmitXmlDeclaration = true;
  1044. settings.ConformanceLevel = ConformanceLevel.Fragment;
  1045. writer = XmlWriter.Create(xmldata, settings);
  1046. }
  1047. internal void initXmlReader()
  1048. {
  1049. XmlReaderSettings settings = new XmlReaderSettings();
  1050. settings.ConformanceLevel = ConformanceLevel.Fragment;
  1051. settings.IgnoreComments = true;
  1052. settings.IgnoreWhitespace = true;
  1053. settings.IgnoreProcessingInstructions = true;
  1054. settings.ValidationType = ValidationType.None;
  1055. reader = XmlReader.Create(request.InputStream,settings);
  1056. }
  1057. internal void appendStatus(string msg)
  1058. {
  1059. if (statusBody == String.Empty)
  1060. {
  1061. statusBody = String.Format(Rest.statusHead, request.HttpMethod);
  1062. }
  1063. statusBody = String.Format("{0} {1}", statusBody, msg);
  1064. }
  1065. #endregion service_interface
  1066. #region internal_methods
  1067. /// <summary>
  1068. /// Helper methods for deconstructing and reconstructing
  1069. /// URI path data.
  1070. /// </summary>
  1071. private void initUrl()
  1072. {
  1073. uri = request.Url;
  1074. if (query == null)
  1075. {
  1076. query = uri.Query;
  1077. }
  1078. // If the path has not been previously initialized,
  1079. // do so now.
  1080. if (path == null)
  1081. {
  1082. path = uri.AbsolutePath;
  1083. if (path.EndsWith(Rest.UrlPathSeparator))
  1084. path = path.Substring(0,path.Length-1);
  1085. }
  1086. // If we succeeded in getting a path, perform any
  1087. // additional pre-processing required.
  1088. if (path != null)
  1089. {
  1090. if (Rest.ExtendedEscape)
  1091. {
  1092. // Handle "+". Not a standard substitution, but
  1093. // common enough...
  1094. path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
  1095. }
  1096. pathNodes = path.Split(Rest.CA_PATHSEP);
  1097. }
  1098. else
  1099. {
  1100. pathNodes = EmptyPath;
  1101. }
  1102. // Elimiate any %-escaped values. This is left until here
  1103. // so that escaped "+' are not mistakenly replaced.
  1104. path = Uri.UnescapeDataString(path);
  1105. // Request server context info
  1106. hostname = uri.Host;
  1107. port = uri.Port;
  1108. }
  1109. private int initParameters(int prfxlen)
  1110. {
  1111. if (prfxlen < path.Length-1)
  1112. {
  1113. parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
  1114. }
  1115. else
  1116. {
  1117. parameters = new string[0];
  1118. }
  1119. // Generate a debug list of the decoded parameters
  1120. if (Rest.DEBUG && prfxlen < path.Length-1)
  1121. {
  1122. Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
  1123. for (int i = 0; i < parameters.Length; i++)
  1124. {
  1125. Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
  1126. }
  1127. }
  1128. return parameters.Length;
  1129. }
  1130. #endregion internal_methods
  1131. }
  1132. }