/// <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);
            }
        }