/// <summary> /// Method executes when a control point invokes the ContentDirectory.ExportResource action. /// This method effectively causes the MediaServer to send a local binary to /// an URI that expects an HTTP-POST method of transfer. The method will report /// error messages if the specified local URI does not map to a URI that is /// automapped using <see cref="MediaResource.AUTOMAPFILE"/>. /// </summary> /// <param name="SourceURI"></param> /// <param name="DestinationURI"></param> /// <param name="TransferID"></param> private void SinkCd_ExportResource(System.Uri SourceURI, System.Uri DestinationURI, out System.UInt32 TransferID) { TransferID = 0; Uri dest = DestinationURI; string resourceID, objectID; // find media and resource IDs given a URI that should map to something // served on this server this.GetObjectResourceIDS(SourceURI, out objectID, out resourceID); if ((objectID == "") || (resourceID == "")) { throw new Error_NoSuchResource(SourceURI.ToString()); } IDvResource res = this.GetResource(objectID, resourceID); if (res != null) { //TODO: Might actually want to throw an exception here if the IP address //of the requested resource doesn't match an IP address of the server. //This may be more trouble than its worth though,a s it means //we have to figure out all of the IP addresses that apply to this //object and do a match, etc. What happens if the IP address //of the server changes spontaneously? Ignore the problem for now. // // create a socket that will connect to remote host // Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint remoteIPE = null; if (dest.HostNameType == System.UriHostNameType.Dns) { remoteIPE = new IPEndPoint(Dns.GetHostByName(dest.Host).AddressList[0], dest.Port); } else { remoteIPE = new IPEndPoint(IPAddress.Parse(dest.Host), dest.Port); } if (remoteIPE != null) { // check if the file actually exists string fileName = res.ContentUri.Substring(MediaResource.AUTOMAPFILE.Length); if (Directory.Exists(fileName)) { throw new Error_NoSuchResource("The binary could not be found on the system."); } FileNotMapped mapping = new FileNotMapped(); StringBuilder li = new StringBuilder(); li.AppendFormat("{0}:{1}", SourceURI.Host, SourceURI.Port); mapping.LocalInterface = li.ToString(); mapping.RequestedResource = res; mapping.RedirectedStream = null; if (File.Exists(fileName)) { // the file exists, so go ahead and send it mapping.RedirectedStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } else { try { if (this.OnFileNotMapped != null) { this.OnFileNotMapped(this, mapping); } } catch (Exception ofnme) { mapping.RedirectedStream = null; } } if (mapping.RedirectedStream == null) { throw new Error_NoSuchResource("The binary could not be found on the system."); } else { try { s.Connect(remoteIPE); } catch { throw new UPnPCustomException(800, "Could not connect to the remote address of " +remoteIPE.ToString()+":"+remoteIPE.Port.ToString()); } // Create a session that will post the file HTTPSession session = null; SessionData sd; this.SetupSessionForTransfer(session); sd = (SessionData) session.StateObject; sd.HttpVer1_1 = true; HttpTransfer transferInfo = new HttpTransfer(false, true, session, res, mapping.RedirectedStream, mapping.RedirectedStream.Length); this.AddTransfer(session, transferInfo); session.PostStreamObject(mapping.RedirectedStream, dest.PathAndQuery, res.ProtocolInfo.MimeType); TransferID = transferInfo.m_TransferId; } } else { throw new UPnPCustomException(800, "Could not connect to the socket."); } } else { throw new Error_NoSuchResource(""); } this.m_Stats.ExportResource++; this.FireStatsChange(); }
/// <summary> /// This method is called by <see cref="MediaServerDevice.WebServer_OnPacketReceiveSink"/> /// and handles the HTTP-GET or HTTP-GET message requests and provides /// the appropriate response. In either case, the handler is intended /// to handle requests for resource URIs that have been mapped to local /// files using the <see cref="MediaResource.AUTOMAPFILE"/> convention. /// <para> /// If a resource cannot be mapped to a local file, /// then the method will ask upper-layer application logic /// for a stream object to send in response to the request. /// This is used primarily in scenarios where the application intentionally /// specified a contentUri value for a resource that uses the /// <see cref="MediaResource.AUTOMAPFILE"/> convention but doesn't /// actually map to a local file. This gives the upper application layer /// to provide transcoded stream objects or some specialized file stream /// that is not stored on the local file system. /// </para> /// <para> /// Upper application layers can always opt to leave the stream object /// blank, effectively indicating that the HTTP-GET or head request /// cannot be handled because such a file does not exist. /// </para> /// </summary> /// <param name="msg"></param> /// <param name="session"></param> private void HandleGetOrHeadRequest(HTTPMessage msg, HTTPSession session) { // Format of DirectiveObj will be // "/[res.m_ResourceID]/[item.ID]/[item.Title].[ext]" // We want the bool is404 = true; Exception problem = null; string resourceID = null; string objectID = null; try { DText DirectiveParser = new DText(); DirectiveParser.ATTRMARK = "/"; DirectiveParser[0] = msg.DirectiveObj; resourceID = DirectiveParser[2]; objectID = DirectiveParser[3]; IDvResource res = this.GetResource(objectID, resourceID); if (res == null) { throw new Error_GetRequestError(msg.DirectiveObj, null); } else { // attempt to figure otu the local file path and the mime type string f = MediaResource.AUTOMAPFILE; string fileName = res.ContentUri.Substring(f.Length); string type = res.ProtocolInfo.MimeType; if ((type == null) || (type == "") || (type == "*")) { //content-type not known, programmer //that built content-hierarchy didn't provide one throw new Error_GetRequestError(msg.DirectiveObj, res); } else { // must be a get or head request // check if the file actually exists if (Directory.Exists(fileName)) { throw new Error_GetRequestError(msg.DirectiveObj, res); } FileNotMapped mapping = new FileNotMapped(); mapping.RequestedResource = res; mapping.LocalInterface = session.Source.ToString(); mapping.RedirectedStream = null; if (File.Exists(fileName)) { // the file exists, so go ahead and send it mapping.RedirectedStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } else { try { // the file doesn't exist but the owner of this // server specified some kind of locally mapped file // so perhaps they may want to route a stream object themselves if (this.OnFileNotMapped != null) { this.OnFileNotMapped (this, mapping); } } catch (Exception ofnm) { mapping.RedirectedStream = null; } } // if the RedirectedStream is blank, then it means // no stream can be sent in response to the request if (mapping.RedirectedStream != null) { lock (session) { // get the intended length, if known long expectedLength = -1; if (mapping.OverrideRedirectedStreamLength) { expectedLength = mapping.ExpectedStreamLength; } else { expectedLength = mapping.RedirectedStream.Length; } if (String.Compare(msg.Directive, "HEAD", true) == 0) { // must be a head request - reply with 200/OK, content type, content length HTTPMessage head = new HTTPMessage(); head.StatusCode = 200; head.StatusData = "OK"; head.ContentType = type; if (expectedLength >= 0) { // if we can calculate the length, // then we provide a content-length and // also indicate that range requests can be // handled. head.OverrideContentLength = true; string rangeStr = msg.GetTag("RANGE"); if ((rangeStr == null) || (rangeStr == "")) { head.AddTag("CONTENT-LENGTH", expectedLength.ToString()); head.AddTag("ACCEPT-RANGES", "bytes"); } else { ArrayList rangeSets = new ArrayList(); head.StatusCode = 206; AddRangeSets(rangeSets, rangeStr.Trim().ToLower(), expectedLength); if (rangeSets.Count == 1) { head.AddTag("Content-Range", "bytes " + ((HTTPSession.Range)(rangeSets[0])).Position.ToString() + "-" + ((int)(((HTTPSession.Range)(rangeSets[0])).Position+((HTTPSession.Range)(rangeSets[0])).Length-1)).ToString() + "/" + expectedLength.ToString()); head.AddTag("Content-Length", ((HTTPSession.Range)(rangeSets[0])).Length.ToString()); } } } else { // can't calculate length => can't do range head.AddTag("ACCEPT-RANGES", "none"); } session.Send(head); is404 = false; } else { ArrayList rangeSets = new ArrayList(); string rangeStr = msg.GetTag("RANGE"); // Only allow range requests for content where we have the // entire length and also only for requests that have // also provided an allowed range. if ((rangeStr == null) || (rangeStr != "")) { if (expectedLength >= 0) { // validate the requested ranges; if invalid range // found, send the entire document... AddRangeSets(rangeSets, rangeStr.Trim().ToLower(), expectedLength); } } // must be a get request // create an outgoing transfer that is not visible to UPNP // GetTransferProgress method, and add the transfer HttpTransfer transferInfo = new HttpTransfer(false, false, session, res, mapping.RedirectedStream, expectedLength); this.AddTransfer(session, transferInfo); if (rangeSets.Count > 0) { session.SendStreamObject(mapping.RedirectedStream, (HTTPSession.Range[])rangeSets.ToArray(typeof(HTTPSession.Range)), type); } else { //start from the beginning mapping.RedirectedStream.Seek(0, SeekOrigin.Begin); if (expectedLength >= 0) { session.SendStreamObject(mapping.RedirectedStream, expectedLength, type); } else { session.SendStreamObject(mapping.RedirectedStream, type); } } is404 = false; } } } } } } catch (Exception error) { problem = error; } if (is404) { StringBuilder sb = new StringBuilder(); sb.Append("File not found."); sb.AppendFormat("\r\n\tRequested: \"{0}\"", msg.DirectiveObj); if (objectID != null) { sb.AppendFormat("\r\n\tObjectID=\"{0}\"", objectID); } if (resourceID != null) { sb.AppendFormat("\r\n\tResourceID=\"{0}\"", resourceID); } Error_GetRequestError getHeadError = problem as Error_GetRequestError; if (getHeadError != null) { sb.Append("\r\n"); IUPnPMedia mobj = this._GetEntry(objectID); if (mobj == null) { sb.AppendFormat("\r\n\tCould not find object with ID=\"{0}\"", objectID); } else { sb.AppendFormat("\r\n\tFound object with ID=\"{0}\"", objectID); sb.Append("\r\n---Metadata---\r\n"); sb.Append(mobj.ToDidl()); } sb.Append("\r\n"); if (getHeadError.Resource == null) { sb.Append("\r\n\tResource is null."); } else { sb.Append("\r\n\tResource is not null."); string uri = getHeadError.Resource.ContentUri; if (uri== null) { sb.Append("\r\n\t\tContentUri of resource is null."); } else if (uri == "") { sb.Append("\r\n\t\tContentUri of resource is empty."); } else { sb.AppendFormat("\r\n\t\tContentUri of resource is \"{0}\"", uri); } } } if (problem != null) { sb.Append("\r\n"); Exception e = problem; sb.Append("\r\n!!! Exception information !!!"); while (e != null) { sb.AppendFormat("\r\nMessage=\"{0}\".\r\nStackTrace=\"{1}\"", e.Message, e.StackTrace); e = e.InnerException; if (e != null) { sb.Append("\r\n---InnerException---"); } } } // file has not been found so return a valid HTTP 404 error message HTTPMessage error = new HTTPMessage(); error.StatusCode = 404; error.StatusData = "File not found"; error.StringBuffer = sb.ToString(); session.Send(error); } }