RequestData.cs 52 KB

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