public static bool TryParse(string versionStr, out HTTPVersion result) { result = null; int dotIndex = versionStr.IndexOf('.'); if (!versionStr.StartsWith(VERSION_PREFIX) || dotIndex < VERSION_PREFIX.Length + 1) return false; int verMin; if (!int.TryParse(versionStr.Substring(VERSION_PREFIX.Length, dotIndex - VERSION_PREFIX.Length), out verMin)) return false; int verMax; if (!int.TryParse(versionStr.Substring(dotIndex + 1), out verMax)) return false; result = new HTTPVersion(verMax, verMin); return true; }
public static bool TryParse(string versionStr, out HTTPVersion result) { result = null; int dotIndex = versionStr.IndexOf('.'); if (!versionStr.StartsWith(VERSION_PREFIX) || dotIndex < VERSION_PREFIX.Length + 1) { return(false); } int verMin; if (!int.TryParse(versionStr.Substring(VERSION_PREFIX.Length, dotIndex - VERSION_PREFIX.Length), out verMin)) { return(false); } int verMax; if (!int.TryParse(versionStr.Substring(dotIndex + 1), out verMax)) { return(false); } result = new HTTPVersion(verMax, verMin); return(true); }
protected void HandleNotifyPacket(EndpointConfiguration config, IPEndPoint remoteEndPoint, HTTPVersion httpVersion, string date, string cacheControl, string location, string server, string nts, string usn, string bi, string ci, string sp) { uint bootID = 0; if (bi != null && !uint.TryParse(bi, out bootID)) // Invalid message return; uint configID = 0; if (ci != null && !uint.TryParse(ci, out configID)) // Invalid message return; if (!usn.StartsWith("uuid:")) // Invalid usn return; string deviceUUID; string messageType; if (!ParserHelper.TryParseUSN(usn, out deviceUUID, out messageType)) // We only use messages of the type "uuid:device-UUID::..." and discard the "uuid:device-UUID" message return; if (nts == "ssdp:alive") { if (server == null) // Invalid message return; int maxAge; if (!TryParseMaxAge(cacheControl, out maxAge)) // Invalid message return; DateTime d; if (!DateTime.TryParse(date, out d)) d = DateTime.Now; DateTime expirationTime = d.AddSeconds(maxAge); // The specification says the SERVER header should contain three entries, separated by space, like // "SERVER: OS/version UPnP/1.1 product/version". // Unfortunately, some clients send entries separated by ", ", like "Linux/2.x.x, UPnP/1.0, pvConnect UPnP SDK/1.0". // We try to handle all situations correctly here, that's the reason for this ugly code. // What we've seen until now: // SERVER: Linux/2.x.x, UPnP/1.0, pvConnect UPnP SDK/1.0 => tokens separated by ',' // SERVER: Windows 2003, UPnP/1.0 DLNADOC/1.50, Serviio/0.5.2 => tokens separated by ',' and additional info in UPnP version token // SERVER: 3Com-ADSL-11g/1.0 UPnP/1.0 => only two tokens string[] versionInfos = server.Contains(", ") ? server.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries) : server.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string upnpVersionInfo = versionInfos.FirstOrDefault(v => v.StartsWith(UPnPVersion.VERSION_PREFIX)); if (upnpVersionInfo == null) // Invalid message return; // upnpVersionInfo = 'UPnP/1.0', 'UPnP/1.1', 'UPnP/1.0 DLNADOC/1.50', ..., the UPnP version is always the first token string[] upnpVersionInfoTokens = upnpVersionInfo.Split(' '); string upnpVersionInfoToken = upnpVersionInfoTokens[0]; UPnPVersion upnpVersion; if (!UPnPVersion.TryParse(upnpVersionInfoToken, out upnpVersion)) // Invalid message return; if (upnpVersion.VerMax != 1) // Incompatible UPnP version return; int searchPort = 1900; if (upnpVersion.VerMin >= 1) { if (bi == null || ci == null) // Invalid message return; if (sp != null && (!int.TryParse(sp, out searchPort) || searchPort < 49152 || searchPort > 65535)) // Invalid message return; } RootEntry rootEntry; DeviceEntry deviceEntry = null; string serviceType = null; bool fireDeviceRebooted = false; bool fireConfigurationChanged = false; bool fireRootDeviceAdded = false; bool fireDeviceAdded = false; bool fireServiceAdded = false; lock (_cpData.SyncObj) { bool rootEntryAdded; // Use fail-safe code, see comment above about the different SERVER headers string osVersion = versionInfos.Length < 1 ? string.Empty : versionInfos[0]; string productVersion = versionInfos.Length < 3 ? string.Empty : versionInfos[2]; rootEntry = GetOrCreateRootEntry(deviceUUID, location, upnpVersion, osVersion, productVersion, expirationTime, config, httpVersion, searchPort, out rootEntryAdded); if (bi != null && rootEntry.BootID > bootID) // Invalid message return; uint currentConfigId = rootEntry.GetConfigID(remoteEndPoint); if (currentConfigId != 0 && currentConfigId != configID) fireConfigurationChanged = true; rootEntry.SetConfigID(remoteEndPoint, configID); if (!rootEntryAdded && bi != null && rootEntry.BootID < bootID) { // Device reboot // A device, which has rebooted, has lost all its links, so we must forget about the old link registrations and wait for new registrations in alive messages rootEntry.ClearLinks(); fireDeviceRebooted = true; } // Don't add the link before a reboot was detected and thus, rootEntry.ClearLinks() was called rootEntry.AddOrUpdateLink(config, location, httpVersion, searchPort); rootEntry.BootID = bootID; if (messageType == "upnp:rootdevice") { rootEntry.GetOrCreateDeviceEntry(deviceUUID); object value; if (!rootEntry.ClientProperties.TryGetValue("RootDeviceSetUp", out value)) { rootEntry = MergeOrMoveRootEntry(rootEntry, deviceUUID); fireRootDeviceAdded = true; rootEntry.ClientProperties["RootDeviceSetUp"] = true; } } else if (messageType.StartsWith("urn:")) { if (messageType.IndexOf(":device:") > -1) { string deviceType; int deviceTypeVersion; if (!ParserHelper.TryParseTypeVersion_URN(messageType, out deviceType, out deviceTypeVersion)) // Invalid message return; deviceEntry = rootEntry.GetOrCreateDeviceEntry(deviceUUID); fireDeviceAdded = string.IsNullOrEmpty(deviceEntry.DeviceType); deviceEntry.DeviceType = deviceType; deviceEntry.DeviceTypeVersion = deviceTypeVersion; } else if (messageType.IndexOf(":service:") > -1) { deviceEntry = rootEntry.GetOrCreateDeviceEntry(deviceUUID); serviceType = messageType; if (deviceEntry.Services.Contains(serviceType)) return; deviceEntry.Services.Add(serviceType); fireServiceAdded = true; } } else // Invalid message return; } // Raise events after returning the lock if (fireDeviceRebooted) InvokeDeviceRebooted(rootEntry, fireConfigurationChanged); else if (fireConfigurationChanged) InvokeDeviceConfigurationChanged(rootEntry); if (fireRootDeviceAdded) InvokeRootDeviceAdded(rootEntry); if (fireDeviceAdded) InvokeDeviceAdded(rootEntry, deviceEntry); if (fireServiceAdded) InvokeServiceAdded(rootEntry, deviceEntry, serviceType); } else if (nts == "ssdp:byebye") { RootEntry rootEntry = GetRootEntryByContainedDeviceUUID(deviceUUID); if (rootEntry != null) { if (bi != null && rootEntry.BootID > bootID) // Invalid message return; RemoveRootEntry(rootEntry); InvokeRootDeviceRemoved(rootEntry); } } }
protected RootEntry GetOrCreateRootEntry(string deviceUUID, string descriptionLocation, UPnPVersion upnpVersion, string osVersion, string productVersion, DateTime expirationTime, EndpointConfiguration endpoint, HTTPVersion httpVersion, int searchPort, out bool wasAdded) { // Because the order of the UDP advertisement packets isn't guaranteed (and even not really specified by the UPnP specification), // in the general case it is not possible to find the correct root entry for each advertisement message. // - We cannot search by root device UUID because the upnp:rootdevice message might not be the first message, so before that message, we don't know the root device ID and // thus we cannot use the root device id as unique key to find the root entry // - We cannot use the device description because for multi-homed devices, more than one device description can belong to the same root device // // Assume the message arrive in an order so that device A over network interface N1 is announced first. Second, device B over network interface N2 is announced. // In that case, we cannot judge if those two devices belong to the same root device or not. // // To face that situation, we first add all advertised devices to _pendingDeviceEntries. When a upnp:rootdevice message is received, // we either simply move the root entry from _pendingDeviceEntries into _cpData.DeviceEntries or we merge the pending entry with an already existing // entry in _cpData.DeviceEntries. At that time the merge is possible because we then have the root device id for both root entries. lock (_cpData.SyncObj) { RootEntry result = GetRootEntryByContainedDeviceUUID(deviceUUID) ?? GetRootEntryByDescriptionLocation(descriptionLocation); if (result != null) { result.ExpirationTime = expirationTime; wasAdded = false; } else { result = new RootEntry(_cpData.SyncObj, upnpVersion, osVersion, productVersion, expirationTime); _pendingDeviceEntries.Add(result); wasAdded = true; } return result; } }