sources/Google.Solutions.IapDesktop.Application/Data/IapRdpUrl.cs (111 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Apis.Diagnostics; using Google.Solutions.Apis.Locator; using Google.Solutions.Common.Linq; using Google.Solutions.IapDesktop.Application.Diagnostics; using System; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using System.Web; namespace Google.Solutions.IapDesktop.Application.Data { /// <summary> /// Represents an iap-rdp:/// URI. /// /// Rules: /// * The host part is empty, so a URL has to start with iap-rdp:/ or iap-rdp:/// /// * The query string may contain settings, but not all settings are supported /// (either for security reasons or because they are just not very relevant). /// </summary> public class IapRdpUrl { public const string Scheme = "iap-rdp"; private static readonly Regex ProjectPattern = new Regex(@"(?:(?:[-a-z0-9]{1,63}\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"); private static readonly Regex ZonePattern = new Regex(@"[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"); private static readonly Regex InstanceNamePattern = new Regex(@"[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19}"); /// <summary> /// Instance referenced by this URL. /// </summary> public InstanceLocator Instance { get; } /// <summary> /// Query string parameters that might contain connection settings. /// /// NB. The NameValueCollection is case-insensitive. /// </summary> public NameValueCollection Parameters { get; } public IapRdpUrl(InstanceLocator instance, NameValueCollection parameters) { this.Instance = instance; this.Parameters = parameters; } //--------------------------------------------------------------------- // Parsing. //--------------------------------------------------------------------- private static InstanceLocator CreateInstanceLocatorFromPath(string absolutePath) { if (string.IsNullOrEmpty(absolutePath)) { throw new IapRdpUrlFormatException($"Path is empty"); } if (!absolutePath.StartsWith("/")) { throw new IapRdpUrlFormatException($"Path must start with /"); } var pathComponents = absolutePath.Split('/'); if (pathComponents.Length != 4) { throw new IapRdpUrlFormatException($"Path not in format project/zone/instance-name: {absolutePath}"); } Debug.Assert(string.IsNullOrEmpty(pathComponents[0])); if (!ProjectPattern.IsMatch(pathComponents[1])) { throw new IapRdpUrlFormatException($"Invalid project ID"); } if (!ZonePattern.IsMatch(pathComponents[2])) { throw new IapRdpUrlFormatException($"Invalid zone ID"); } if (!InstanceNamePattern.IsMatch(pathComponents[3])) { throw new IapRdpUrlFormatException($"Invalid instance name"); } return new InstanceLocator(pathComponents[1], pathComponents[2], pathComponents[3]); } public static IapRdpUrl FromString(string uri) { return FromUri(new Uri(uri)); } public static IapRdpUrl FromUri(Uri uri) { if (uri.Scheme != Scheme) { throw new IapRdpUrlFormatException($"Invalid scheme: {uri.Scheme}"); } if (!string.IsNullOrEmpty(uri.Host)) { throw new IapRdpUrlFormatException($"Host part not empty: {uri.Host}"); } var instanceRef = CreateInstanceLocatorFromPath(uri.AbsolutePath); return new IapRdpUrl( instanceRef, HttpUtility.ParseQueryString(uri.Query)); } //--------------------------------------------------------------------- // Formatting. //--------------------------------------------------------------------- public string ToString(bool includeQuery) { var url = $"{Scheme}:///{this.Instance.ProjectId}/{this.Instance.Zone}/{this.Instance.Name}"; if (includeQuery) { var formattedParameters = this.Parameters .ToKeyValuePairs() .Select(p => p.Key + "=" + HttpUtility.UrlEncode(p.Value)); url += $"?{string.Join("&", formattedParameters)}"; } return url; } public override string ToString() => ToString(true); } public class IapRdpUrlFormatException : UriFormatException, IExceptionWithHelpTopic { public IHelpTopic Help => HelpTopics.BrowserIntegration; public IapRdpUrlFormatException(string message) : base(message) { } } }