* RemotingServices.cs: In GetMethodBaseFromMethodMessage, look for
[mono.git] / mcs / class / corlib / System.Runtime.Remoting / RemotingServices.cs
1 //
2 // System.Runtime.Remoting.RemotingServices.cs
3 //
4 // Authors:
5 //   Dietmar Maurer (dietmar@ximian.com)
6 //   Lluis Sanchez Gual (lluis@ideary.com)
7 //   Patrik Torstensson
8 //
9 // (C) 2001 Ximian, Inc.  http://www.ximian.com
10 //
11
12 using System;
13 using System.Text;
14 using System.Reflection;
15 using System.Threading;
16 using System.Collections;
17 using System.Runtime.Remoting.Messaging;
18 using System.Runtime.Remoting.Proxies;
19 using System.Runtime.Remoting.Channels;
20 using System.Runtime.Remoting.Contexts;
21 using System.Runtime.Remoting.Activation;
22 using System.Runtime.Remoting.Lifetime;
23 using System.Runtime.CompilerServices;
24 using System.Runtime.Serialization;
25 using System.IO;
26
27 namespace System.Runtime.Remoting
28 {
29         public sealed class RemotingServices 
30         {
31                 // Holds the identities of the objects, using uri as index
32                 static Hashtable uri_hash = new Hashtable ();           
33
34                 internal static string app_id;
35                 static int next_id = 1;
36                 
37                 static RemotingServices ()
38                 {
39                         RegisterInternalChannels ();
40                         app_id = Guid.NewGuid().ToString().Replace('-', '_') + "/";
41                         CreateWellKnownServerIdentity (typeof(RemoteActivator), "RemoteActivationService.rem", WellKnownObjectMode.Singleton);
42                 }
43         
44                 private RemotingServices () {}
45
46                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
47                 internal extern static object InternalExecute (MonoMethod method, Object obj,
48                                                                Object[] parameters, out object [] out_args);
49
50                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
51                 public extern static bool IsTransparentProxy (object proxy);
52                 
53                 internal static IMethodReturnMessage InternalExecuteMessage (
54                         MarshalByRefObject target, IMethodCallMessage reqMsg)
55                 {
56                         ReturnMessage result;
57                         
58                         MonoMethod method = (MonoMethod) target.GetType().GetMethod(reqMsg.MethodName, BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance, null, (Type[]) reqMsg.MethodSignature, null);
59                         object oldContext = CallContext.SetCurrentCallContext (reqMsg.LogicalCallContext);
60                         
61                         try 
62                         {
63                                 object [] out_args;
64                                 object rval = InternalExecute (method, target, reqMsg.Args, out out_args);
65                         
66                                 // Collect parameters with Out flag from the request message
67                                 // FIXME: This can be done in the unmanaged side and will be
68                                 // more efficient
69                                 
70                                 ParameterInfo[] parameters = method.GetParameters();
71                                 object[] returnArgs = new object [parameters.Length];
72                                 
73                                 int n = 0;
74                                 int noa = 0;
75                                 foreach (ParameterInfo par in parameters)
76                                 {
77                                         if (par.IsOut && !par.ParameterType.IsByRef) 
78                                                 returnArgs [n++] = reqMsg.GetArg (par.Position);
79                                         else if (par.ParameterType.IsByRef)
80                                                 returnArgs [n++] = out_args [noa++]; 
81                                         else
82                                                 returnArgs [n++] = null; 
83                                 }
84                                 
85                                 result = new ReturnMessage (rval, returnArgs, n, CallContext.CreateLogicalCallContext(), reqMsg);
86                         } 
87                         catch (Exception e) 
88                         {
89                                 result = new ReturnMessage (e, reqMsg);
90                         }
91                         
92                         CallContext.RestoreCallContext (oldContext);
93                         return result;
94                 }
95
96                 public static IMethodReturnMessage ExecuteMessage (
97                         MarshalByRefObject target, IMethodCallMessage reqMsg)
98                 {
99                         if (IsTransparentProxy(target))
100                         {
101                                 // Message must go through all chain of sinks
102                                 RealProxy rp = GetRealProxy (target);
103                                 return (IMethodReturnMessage) rp.Invoke (reqMsg);
104                         }
105                         else    // Direct call
106                                 return InternalExecuteMessage (target, reqMsg);
107                 }
108
109                 public static object Connect (Type classToProxy, string url)
110                 {
111                         ObjRef objRef = new ObjRef (classToProxy, url, null);
112                         return GetRemoteObject (objRef, classToProxy);
113                 }
114
115                 public static object Connect (Type classToProxy, string url, object data)
116                 {
117                         ObjRef objRef = new ObjRef (classToProxy, url, data);
118                         return GetRemoteObject (objRef, classToProxy);
119                 }
120
121                 public static bool Disconnect (MarshalByRefObject obj)
122                 {
123                         if (obj == null) throw new ArgumentNullException ("obj");
124
125                         ServerIdentity identity;
126
127                         if (IsTransparentProxy (obj))
128                         {
129                                 // CBOs are always accessed through a proxy, even in the server, so
130                                 // for server CBOs it is ok to disconnect a proxy
131
132                                 RealProxy proxy = GetRealProxy(obj);
133                                 if (proxy.GetProxiedType().IsContextful && (proxy.ObjectIdentity is ServerIdentity))
134                                         identity = proxy.ObjectIdentity as ServerIdentity;
135                                 else
136                                         throw new ArgumentException ("The obj parameter is a proxy.");
137                         }
138                         else
139                                 identity = obj.ObjectIdentity;
140
141                         if (identity == null || !identity.IsConnected)
142                                 return false;
143                         else
144                         {
145                                 LifetimeServices.StopTrackingLifetime (identity);
146                                 DisposeIdentity (identity);
147                                 return true;
148                         }
149                 }
150
151                 public static Type GetServerTypeForUri (string uri)
152                 {
153                         ServerIdentity ident = GetIdentityForUri (uri) as ServerIdentity;
154                         if (ident == null) return null;
155                         return ident.ObjectType;
156                 }
157
158                 public static string GetObjectUri (MarshalByRefObject obj)
159                 {
160                         Identity ident = GetObjectIdentity(obj);
161                         if (ident is ClientIdentity) return ((ClientIdentity)ident).TargetUri;
162                         else if (ident != null) return ident.ObjectUri;
163                         else return null;
164                 }
165
166                 public static object Unmarshal (ObjRef objref)
167                 {
168                         return Unmarshal(objref, true);
169                 }
170
171                 public static object Unmarshal (ObjRef objref, bool fRefine)
172                 {
173                         Type classToProxy = fRefine ? objref.ServerType : typeof (MarshalByRefObject);
174                         if (classToProxy == null) classToProxy = typeof (MarshalByRefObject);
175
176                         if (objref.IsReferenceToWellKnow)
177                                 return GetRemoteObject(objref, classToProxy);
178                         else
179                         {
180                                 if (classToProxy.IsContextful)
181                                 {
182                                         // Look for a ProxyAttribute
183                                         ProxyAttribute att = (ProxyAttribute) Attribute.GetCustomAttribute (classToProxy, typeof(ProxyAttribute),true);
184                                         if (att != null)
185                                                 return att.CreateProxy (objref, classToProxy, null, null).GetTransparentProxy();
186                                 }
187                                 return GetProxyForRemoteObject (objref, classToProxy);
188                         }
189                 }
190
191                 public static ObjRef Marshal (MarshalByRefObject obj)
192                 {
193                         return Marshal (obj, null, null);
194                 }
195                 
196                 public static ObjRef Marshal (MarshalByRefObject obj, string uri)
197                 {
198                         return Marshal (obj, uri, null);
199                 }
200                 
201                 public static ObjRef Marshal (MarshalByRefObject obj, string uri, Type requested_type)
202                 {
203                         if (IsTransparentProxy (obj))
204                         {
205                                 RealProxy proxy = RemotingServices.GetRealProxy(obj);
206                                 Identity identity = proxy.ObjectIdentity;
207
208                                 if (identity != null)
209                                 {
210                                         if (proxy.GetProxiedType().IsContextful && !identity.IsConnected)
211                                         {
212                                                 // Unregistered local contextbound object. Register now.
213                                                 ClientActivatedIdentity cboundIdentity = (ClientActivatedIdentity)identity;
214                                                 if (uri == null) uri = NewUri();
215                                                 cboundIdentity.ObjectUri = uri;
216                                                 RegisterServerIdentity (cboundIdentity);
217                                                 cboundIdentity.StartTrackingLifetime ((ILease)obj.InitializeLifetimeService());
218                                                 return cboundIdentity.CreateObjRef(requested_type);
219                                         }
220                                         else if (uri != null)
221                                                 throw new RemotingException ("It is not possible marshal a proxy of a remote object.");
222
223                                         return proxy.ObjectIdentity.CreateObjRef(requested_type);
224                                 }
225                         }
226
227                         if (requested_type == null) requested_type = obj.GetType();
228
229                         if (uri == null) 
230                         {
231                                 if (obj.ObjectIdentity == null)
232                                 {
233                                         uri = NewUri();
234                                         CreateClientActivatedServerIdentity (obj, requested_type, uri);
235                                 }
236                         }
237                         else
238                         {
239                                 ClientActivatedIdentity identity = GetIdentityForUri ("/" + uri) as ClientActivatedIdentity;
240                                 if (identity == null || obj != identity.GetServerObject()) 
241                                         CreateClientActivatedServerIdentity (obj, requested_type, uri);
242                         }
243
244                         if (IsTransparentProxy (obj))
245                                 return RemotingServices.GetRealProxy(obj).ObjectIdentity.CreateObjRef (requested_type);
246                         else
247                                 return obj.CreateObjRef(requested_type);
248                 }
249
250                 static string NewUri ()
251                 {
252                         int n = Interlocked.Increment (ref next_id);
253                         return app_id + Environment.TickCount + "_" + n + ".rem";
254                 }
255
256                 public static RealProxy GetRealProxy (object proxy)
257                 {
258                         if (!IsTransparentProxy(proxy)) throw new RemotingException("Cannot get the real proxy from an object that is not a transparent proxy.");
259                         return (RealProxy)((TransparentProxy)proxy)._rp;
260                 }
261
262                 public static MethodBase GetMethodBaseFromMethodMessage(IMethodMessage msg)
263                 {
264                         Type type = Type.GetType (msg.TypeName);
265                         if (type == null)
266                                 throw new RemotingException ("Type '" + msg.TypeName + "' not found.");
267
268                         BindingFlags bflags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
269                         
270                         MethodBase method;
271                         if (msg.MethodSignature == null)
272                                 method = type.GetMethod (msg.MethodName, bflags);
273                         else
274                                 method = type.GetMethod (msg.MethodName, bflags, null, (Type[]) msg.MethodSignature, null);
275                         
276                         if (method != null) 
277                                 return method;
278                         
279                         if (msg.MethodSignature == null)
280                                 return type.GetConstructor (bflags, null, Type.EmptyTypes, null);
281                         else
282                                 return type.GetConstructor (bflags, null, (Type[]) msg.MethodSignature, null);
283                 }
284
285                 public static void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
286                 {
287                         if (obj == null) throw new ArgumentNullException ("obj");
288
289                         ObjRef oref = Marshal ((MarshalByRefObject)obj);
290                         oref.GetObjectData (info, context);
291                 }
292
293                 public static ObjRef GetObjRefForProxy(MarshalByRefObject obj)
294                 {
295                         Identity ident = GetObjectIdentity(obj);
296                         if (ident == null) return null;
297                         else return ident.CreateObjRef(null);
298                 }
299
300                 public static object GetLifetimeService (MarshalByRefObject obj)
301                 {
302                         if (obj == null) return null;
303                         return obj.GetLifetimeService ();
304                 }
305
306                 public static IMessageSink GetEnvoyChainForProxy (MarshalByRefObject obj)
307                 {
308                         if (IsTransparentProxy(obj))
309                                 return ((ClientIdentity)GetRealProxy (obj).ObjectIdentity).EnvoySink;
310                         else
311                                 throw new ArgumentException ("obj must be a proxy.","obj");                     
312                 }
313
314                 public static void LogRemotingStage (int stage)
315                 {
316                         throw new NotImplementedException ();
317                 }
318
319                 public static string GetSessionIdForMethodMessage(IMethodMessage msg)
320                 {
321                         // It seems that this it what MS returns.
322                         return msg.Uri;
323                 }
324
325                 public static bool IsMethodOverloaded(IMethodMessage msg)
326                 {
327                         // TODO: use internal call for better performance
328                         Type type = msg.MethodBase.DeclaringType;
329                         MemberInfo[] members = type.GetMember (msg.MethodName, MemberTypes.Method, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
330                         return members.Length > 1;
331                 }
332
333                 public static bool IsObjectOutOfAppDomain(object tp)
334                 {
335                         // TODO: use internal call for better performance
336                         Identity ident = GetObjectIdentity((MarshalByRefObject)tp);
337                         if (ident != null) return !ident.IsFromThisAppDomain;
338                         else return false;
339                 }
340
341                 public static bool IsObjectOutOfContext(object tp)
342                 {
343                         // TODO: use internal call for better performance
344                         ServerIdentity ident = GetObjectIdentity((MarshalByRefObject)tp) as ServerIdentity;
345                         if (ident != null) return ident.Context != System.Threading.Thread.CurrentContext;
346                         else return false;
347                 }
348
349                 public static bool IsOneWay(MethodBase method)
350                 {
351                         // TODO: use internal call for better performance
352                         object[] atts = method.GetCustomAttributes (typeof (OneWayAttribute), false);
353                         return atts.Length > 0;
354                 }
355
356                 internal static bool IsAsyncMessage(IMessage msg)
357                 {
358                         if (! (msg is MonoMethodMessage)) return false;
359                         else if (((MonoMethodMessage)msg).IsAsync) return true;
360                         else if (IsOneWay (((MonoMethodMessage)msg).MethodBase)) return true;
361                         else return false;
362                 }
363
364                 public static void SetObjectUriForMarshal(MarshalByRefObject obj, string uri)
365                 {
366                         if (IsTransparentProxy (obj)) throw new RemotingException ("SetObjectUriForMarshal method should only be called for MarshalByRefObjects that exist in the current AppDomain.");
367                         Marshal (obj, uri);
368                 }
369
370                 #region Internal Methods
371                 
372                 internal static object CreateClientProxy (ActivatedClientTypeEntry entry, object[] activationAttributes)
373                 {
374                         if (entry.ContextAttributes != null || activationAttributes != null)
375                         {
376                                 ArrayList props = new ArrayList ();
377                                 if (entry.ContextAttributes != null) props.AddRange (entry.ContextAttributes);
378                                 if (activationAttributes != null) props.AddRange (activationAttributes);
379                                 return CreateClientProxy (entry.ObjectType, entry.ApplicationUrl, props.ToArray ());
380                         }
381                         else
382                                 return CreateClientProxy (entry.ObjectType, entry.ApplicationUrl, null);
383                 }
384         
385                 internal static object CreateClientProxy (Type objectType, string url, object[] activationAttributes)
386                 {
387                         string activationUrl = url + "/RemoteActivationService.rem";
388
389                         string objectUri;
390                         IMessageSink sink = GetClientChannelSinkChain (activationUrl, null, out objectUri);
391
392                         RemotingProxy proxy = new RemotingProxy (objectType, activationUrl, activationAttributes);
393                         return proxy.GetTransparentProxy();
394                 }
395         
396                 internal static object CreateClientProxy (WellKnownClientTypeEntry entry)
397                 {
398                         return Connect (entry.ObjectType, entry.ObjectUrl, null);
399                 }
400         
401                 internal static object CreateClientProxyForContextBound (Type type, object[] activationAttributes)
402                 {
403                         if (type.IsContextful)
404                         {
405                                 // Look for a ProxyAttribute
406                                 ProxyAttribute att = (ProxyAttribute) Attribute.GetCustomAttribute (type, typeof(ProxyAttribute), true);
407                                 if (att != null)
408                                         return att.CreateInstance (type);
409                         }
410                         RemotingProxy proxy = new RemotingProxy (type, ChannelServices.CrossContextUrl, activationAttributes);
411                         return proxy.GetTransparentProxy();
412                 }
413         
414                 internal static Identity GetIdentityForUri (string uri)
415                 {
416                         lock (uri_hash)
417                         {
418                                 return (Identity)uri_hash [GetNormalizedUri(uri)];
419                         }
420                 }
421
422                 internal static Identity GetObjectIdentity (MarshalByRefObject obj)
423                 {
424                         if (IsTransparentProxy(obj))
425                                 return GetRealProxy (obj).ObjectIdentity;
426                         else
427                                 return obj.ObjectIdentity;
428                 }
429
430                 internal static ClientIdentity GetOrCreateClientIdentity(ObjRef objRef, Type proxyType, out object clientProxy)
431                 {
432                         // This method looks for an identity for the given url. 
433                         // If an identity is not found, it creates the identity and 
434                         // assigns it a proxy to the remote object.
435
436                         // Creates the client sink chain for the given url or channelData.
437                         // It will also get the object uri from the url.
438
439                         object channelData = objRef.ChannelInfo != null ? objRef.ChannelInfo.ChannelData : null;
440                         string url = (channelData == null) ? objRef.URI : null;
441
442                         string objectUri;
443                         IMessageSink sink = GetClientChannelSinkChain (url, channelData, out objectUri);
444
445                         if (objectUri == null) objectUri = objRef.URI;
446
447                         lock (uri_hash)
448                         {
449                                 clientProxy = null;
450                                 string uri = GetNormalizedUri (objRef.URI);
451                                 
452                                 ClientIdentity identity = uri_hash [uri] as ClientIdentity;
453                                 if (identity != null)
454                                 {
455                                         // Object already registered
456                                         clientProxy = identity.ClientProxy;
457                                         if (clientProxy != null) return identity;
458                                         
459                                         // The proxy has just been GCed, so its identity cannot
460                                         // be reused. Just dispose it.
461                                         DisposeIdentity (identity);
462                                 }
463
464                                 // Creates an identity and a proxy for the remote object
465
466                                 identity = new ClientIdentity (objectUri, objRef);
467                                 identity.ChannelSink = sink;
468
469                                 // Registers the identity
470                                 uri_hash [uri] = identity;
471                                 
472                                 if (proxyType != null)
473                                 {
474                                         RemotingProxy proxy = new RemotingProxy (proxyType, identity);
475                                         clientProxy = proxy.GetTransparentProxy();
476                                         identity.ClientProxy = (MarshalByRefObject) clientProxy;
477                                 }
478
479                                 return identity;
480                         }
481                 }
482
483                 static IMessageSink GetClientChannelSinkChain(string url, object channelData, out string objectUri)
484                 {
485                         IMessageSink sink = ChannelServices.CreateClientChannelSinkChain (url, channelData, out objectUri);
486                         if (sink == null) 
487                         {
488                                 if (url != null) 
489                                 {
490                                         string msg = String.Format ("Cannot create channel sink to connect to URL {0}. An appropriate channel has probably not been registered.", url); 
491                                         throw new RemotingException (msg);
492                                 }
493                                 else 
494                                 {
495                                         string msg = String.Format ("Cannot create channel sink to connect to the remote object. An appropriate channel has probably not been registered.", url); 
496                                         throw new RemotingException (msg);
497                                 }
498                         }
499                         return sink;
500                 }
501
502                 internal static ClientActivatedIdentity CreateContextBoundObjectIdentity(Type objectType)
503                 {
504                         ClientActivatedIdentity identity = new ClientActivatedIdentity (null, objectType);
505                         identity.ChannelSink = ChannelServices.CrossContextChannel;
506                         return identity;
507                 }
508
509                 internal static ClientActivatedIdentity CreateClientActivatedServerIdentity(MarshalByRefObject realObject, Type objectType, string objectUri)
510                 {
511                         ClientActivatedIdentity identity = new ClientActivatedIdentity (objectUri, objectType);
512                         identity.AttachServerObject (realObject, Context.DefaultContext);
513                         RegisterServerIdentity (identity);
514                         identity.StartTrackingLifetime ((ILease)realObject.InitializeLifetimeService ());
515                         return identity;
516                 }
517
518                 internal static ServerIdentity CreateWellKnownServerIdentity(Type objectType, string objectUri, WellKnownObjectMode mode)
519                 {
520                         ServerIdentity identity;
521
522                         if (mode == WellKnownObjectMode.SingleCall)
523                                 identity = new  SingleCallIdentity(objectUri, Context.DefaultContext, objectType);
524                         else
525                                 identity = new  SingletonIdentity(objectUri, Context.DefaultContext, objectType);
526
527                         RegisterServerIdentity (identity);
528                         return identity;
529                 }
530
531                 private static void RegisterServerIdentity(ServerIdentity identity)
532                 {
533                         lock (uri_hash)
534                         {
535                                 if (uri_hash.ContainsKey (identity.ObjectUri)) 
536                                         throw new RemotingException ("Uri already in use: " + identity.ObjectUri + ".");
537
538                                 uri_hash[identity.ObjectUri] = identity;
539                         }
540                 }
541
542                 internal static object GetProxyForRemoteObject (ObjRef objref, Type classToProxy)
543                 {
544                         ClientActivatedIdentity identity = GetIdentityForUri (objref.URI) as ClientActivatedIdentity;
545                         if (identity != null) return identity.GetServerObject ();
546                         else return GetRemoteObject (objref, classToProxy);
547                 }
548
549                 internal static object GetRemoteObject(ObjRef objRef, Type proxyType)
550                 {
551                         object proxy;
552                         GetOrCreateClientIdentity (objRef, proxyType, out proxy);
553                         return proxy;
554                 }
555
556                 internal static object GetDomainProxy(AppDomain domain) 
557                 {
558                         byte[] data = null;
559
560                         Context currentContext = Thread.CurrentContext;
561
562                         try
563                         {
564                                 data = (byte[])AppDomain.InvokeInDomain (domain, typeof (AppDomain).GetMethod ("GetMarshalledDomainObjRef", BindingFlags.Instance|BindingFlags.NonPublic), domain, null);
565                         }
566                         finally
567                         {
568                                 AppDomain.InternalSetContext (currentContext);
569                         }                               
570
571                         MemoryStream stream = new MemoryStream (data);
572                         ObjRef appref = (ObjRef) CADSerializer.DeserializeObject (stream);
573                         return (AppDomain) RemotingServices.Unmarshal(appref);
574                 }
575
576                 private static void RegisterInternalChannels() 
577                 {
578                         CrossAppDomainChannel.RegisterCrossAppDomainChannel();
579                 }
580                 
581                 internal static void DisposeIdentity (Identity ident)
582                 {
583                         lock (uri_hash)
584                         {
585                                 if (!ident.Disposed) {
586                                         ClientIdentity clientId = ident as ClientIdentity;
587                                         if (clientId != null)
588                                                 uri_hash.Remove (GetNormalizedUri (clientId.TargetUri));
589                                         else
590                                                 uri_hash.Remove (ident.ObjectUri);
591                                                 
592                                         ident.Disposed = true;
593                                 }
594                         }
595                 }
596
597                 internal static Identity GetMessageTargetIdentity (IMessage msg)
598                 {
599                         // Returns the identity where the message is sent
600
601                         if (msg is IInternalMessage) 
602                                 return ((IInternalMessage)msg).TargetIdentity;
603
604                         lock (uri_hash)
605                         {
606                                 string uri = GetNormalizedUri (((IMethodMessage)msg).Uri);
607                                 return uri_hash [uri] as ServerIdentity;
608                         }
609                 }
610
611                 internal static void SetMessageTargetIdentity (IMessage msg, Identity ident)
612                 {
613                         if (msg is IInternalMessage) 
614                                 ((IInternalMessage)msg).TargetIdentity = ident;
615                 }
616                 
617                 internal static bool UpdateOutArgObject (ParameterInfo pi, object local, object remote)
618                 {
619                         if (local is StringBuilder) 
620                         {
621                                 StringBuilder sb = local as StringBuilder;
622                                 sb.Remove (0, sb.Length);
623                                 sb.Append (remote.ToString());
624                                 return true;
625                         }
626                         else if (pi.ParameterType.IsArray && ((Array)local).Rank == 1)
627                         {
628                                 Array alocal = (Array) local;
629                                 if (alocal.Rank == 1)
630                                 {
631                                         Array.Copy ((Array) remote, alocal, alocal.Length);
632                                         return true;
633                                 }
634                                 else
635                                 {
636                                         // TODO
637                                 }
638                         }
639                         return false;
640                 }
641                 
642                 static string GetNormalizedUri (string uri)
643                 {
644                         if (uri.StartsWith ("/")) return uri.Substring (1);
645                         else return uri;
646                 }
647
648                 #endregion
649         }
650 }