diff --git a/content/media/MediaResource.h b/content/media/MediaResource.h index 579fdde87128..0cc072b7fff1 100644 --- a/content/media/MediaResource.h +++ b/content/media/MediaResource.h @@ -193,7 +193,8 @@ inline MediaByteRange::MediaByteRange(TimestampedMediaByteRange& aByteRange) * Decoder they are called on the Decode thread for example. You must * ensure that no threads are calling these methods once Close is called. * - * Instances of this class are explicitly managed. 'delete' it when done. + * Instances of this class are reference counted. Use nsRefPtr for + * managing the lifetime of instances of this class. * * The generic implementation of this class is ChannelMediaResource, which can * handle any URI for which Necko supports AsyncOpen. diff --git a/content/media/plugins/MediaPluginHost.cpp b/content/media/plugins/MediaPluginHost.cpp index c7b46df4d9cc..8bbc1f613ff2 100644 --- a/content/media/plugins/MediaPluginHost.cpp +++ b/content/media/plugins/MediaPluginHost.cpp @@ -14,6 +14,7 @@ #include "nsIGfxInfo.h" #include "gfxCrashReporterUtils.h" #include "prmem.h" +#include "MediaResourceServer.h" #include "MPAPI.h" @@ -33,40 +34,9 @@ Decoder::Decoder() : namespace mozilla { -static MediaResource *GetResource(Decoder *aDecoder) +static char* GetResource(Decoder *aDecoder) { - return reinterpret_cast(aDecoder->mResource); -} - -static bool Read(Decoder *aDecoder, char *aBuffer, int64_t aOffset, uint32_t aCount, uint32_t* aBytes) -{ - MediaResource *resource = GetResource(aDecoder); - if (aOffset != resource->Tell()) { - nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); - if (NS_FAILED(rv)) { - return false; - } - } - nsresult rv = resource->Read(aBuffer, aCount, aBytes); - if (NS_FAILED(rv)) { - return false; - } - return true; -} - -static uint64_t GetLength(Decoder *aDecoder) -{ - return GetResource(aDecoder)->GetLength(); -} - -static void SetMetaDataReadMode(Decoder *aDecoder) -{ - GetResource(aDecoder)->SetReadMode(MediaCacheStream::MODE_METADATA); -} - -static void SetPlaybackReadMode(Decoder *aDecoder) -{ - GetResource(aDecoder)->SetReadMode(MediaCacheStream::MODE_PLAYBACK); + return static_cast(aDecoder->mResource); } class GetIntPrefEvent : public nsRunnable { @@ -92,10 +62,10 @@ static bool GetIntPref(const char* aPref, int32_t* aResult) } static PluginHost sPluginHost = { - Read, - GetLength, - SetMetaDataReadMode, - SetPlaybackReadMode, + nullptr, + nullptr, + nullptr, + nullptr, GetIntPref }; @@ -221,6 +191,8 @@ static const char* GetOmxLibraryName() MediaPluginHost::MediaPluginHost() { MOZ_COUNT_CTOR(MediaPluginHost); + mResourceServer = MediaResourceServer::Start(); + const char* name = GetOmxLibraryName(); ALOG("Loading OMX Plugin: %s", name ? name : "nullptr"); if (name) { @@ -250,6 +222,7 @@ MediaPluginHost::MediaPluginHost() { } MediaPluginHost::~MediaPluginHost() { + mResourceServer->Stop(); MOZ_COUNT_DTOR(MediaPluginHost); } @@ -277,7 +250,6 @@ MPAPI::Decoder *MediaPluginHost::CreateDecoder(MediaResource *aResource, const n if (!decoder) { return nullptr; } - decoder->mResource = aResource; const char *chars; size_t len = NS_CStringGetData(aMimeType, &chars, nullptr); @@ -287,6 +259,12 @@ MPAPI::Decoder *MediaPluginHost::CreateDecoder(MediaResource *aResource, const n if (!plugin->CanDecode(chars, len, &codecs)) { continue; } + + nsCString url; + nsresult rv = mResourceServer->AddResource(aResource, url); + if (NS_FAILED (rv)) continue; + + decoder->mResource = strdup(url.get()); if (plugin->CreateDecoder(&sPluginHost, decoder, chars, len)) { aResource->AddRef(); return decoder.forget(); @@ -299,11 +277,12 @@ MPAPI::Decoder *MediaPluginHost::CreateDecoder(MediaResource *aResource, const n void MediaPluginHost::DestroyDecoder(Decoder *aDecoder) { aDecoder->DestroyDecoder(aDecoder); - MediaResource* resource = GetResource(aDecoder); + char* resource = GetResource(aDecoder); if (resource) { // resource *shouldn't* be null, but check anyway just in case the plugin // decoder does something stupid. - resource->Release(); + mResourceServer->RemoveResource(nsCString(resource)); + free(resource); } delete aDecoder; } diff --git a/content/media/plugins/MediaPluginHost.h b/content/media/plugins/MediaPluginHost.h index 8a17bc8be814..a4c7ada93254 100644 --- a/content/media/plugins/MediaPluginHost.h +++ b/content/media/plugins/MediaPluginHost.h @@ -9,13 +9,16 @@ #include "nsTArray.h" #include "MediaResource.h" #include "MPAPI.h" +#include "MediaResourceServer.h" namespace mozilla { class MediaPluginReader; class MediaPluginHost { + nsCOMPtr mResourceServer; nsTArray mPlugins; + MPAPI::Manifest *FindPlugin(const nsACString& aMimeType); public: MediaPluginHost(); diff --git a/content/media/plugins/MediaResourceServer.cpp b/content/media/plugins/MediaResourceServer.cpp new file mode 100644 index 000000000000..2cc6b3342e53 --- /dev/null +++ b/content/media/plugins/MediaResourceServer.cpp @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/Assertions.h" +#include "mozilla/Base64.h" +#include "nsThreadUtils.h" +#include "nsIServiceManager.h" +#include "nsISocketTransport.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIRandomGenerator.h" +#include "nsReadLine.h" +#include "nsNetCID.h" +#include "VideoUtils.h" +#include "MediaResource.h" +#include "MediaResourceServer.h" + +#if defined(_MSC_VER) +#define strtoll _strtoi64 +#define snprintf _snprintf_s +#endif + +using namespace mozilla; + +/* + ReadCRLF is a variant of NS_ReadLine from nsReadLine.h that deals + with the carriage return/line feed requirements of HTTP requests. +*/ +template +nsresult +ReadCRLF (StreamType* aStream, nsLineBuffer * aBuffer, + StringType & aLine, bool *aMore) +{ + // eollast is true if the last character in the buffer is a '\r', + // signaling a potential '\r\n' sequence split between reads. + bool eollast = false; + + aLine.Truncate(); + + while (1) { // will be returning out of this loop on eol or eof + if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it. + uint32_t bytesRead; + nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead); + if (NS_FAILED(rv) || bytesRead == 0) { + *aMore = false; + return rv; + } + aBuffer->start = aBuffer->buf; + aBuffer->end = aBuffer->buf + bytesRead; + *(aBuffer->end) = '\0'; + } + + /* + * Walk the buffer looking for an end-of-line. + * There are 4 cases to consider: + * 1. the CR char is the last char in the buffer + * 2. the CRLF sequence are the last characters in the buffer + * 3. the CRLF sequence + one or more chars at the end of the buffer + * we need at least one char after the first CRLF sequence to + * set |aMore| correctly. + * 4. The LF character is the first char in the buffer when eollast is + * true. + */ + CharT* current = aBuffer->start; + if (eollast) { // Case 4 + if (*current == '\n') { + aBuffer->start = ++current; + *aMore = true; + return NS_OK; + } + else { + eollast = false; + aLine.Append('\r'); + } + } + // Cases 2 and 3 + for ( ; current < aBuffer->end-1; ++current) { + if (*current == '\r' && *(current+1) == '\n') { + *current++ = '\0'; + *current++ = '\0'; + aLine.Append(aBuffer->start); + aBuffer->start = current; + *aMore = true; + return NS_OK; + } + } + // Case 1 + if (*current == '\r') { + eollast = true; + *current++ = '\0'; + } + + aLine.Append(aBuffer->start); + aBuffer->start = aBuffer->end; // mark the buffer empty + } +} + +// Each client HTTP request results in a thread being spawned to process it. +// That thread has a single event dispatched to it which handles the HTTP +// protocol. It parses the headers and forwards data from the MediaResource +// associated with the URL back to client. When the request is complete it will +// shutdown the thread. +class ServeResourceEvent : public nsRunnable { +private: + // Reading from this reads the data sent from the client. + nsCOMPtr mInput; + + // Writing to this sends data to the client. + nsCOMPtr mOutput; + + // The MediaResourceServer that owns the MediaResource instances + // served. This is used to lookup the MediaResource from the URL. + nsCOMPtr mServer; + + // Write 'aBufferLength' bytes from 'aBuffer' to 'mOutput'. This + // method ensures all the data is written by checking the number + // of bytes returned from the output streams 'Write' method and + // looping until done. + nsresult WriteAll(char const* aBuffer, int32_t aBufferLength); + +public: + ServeResourceEvent(nsIInputStream* aInput, nsIOutputStream* aOutput, + MediaResourceServer* aServer) + : mInput(aInput), mOutput(aOutput), mServer(aServer) {} + + // This method runs on the thread and exits when it has completed the + // HTTP request. + NS_IMETHOD Run(); + + // Given the first line of an HTTP request, parse the URL requested and + // return the MediaResource for that URL. + already_AddRefed GetMediaResource(nsCString const& aHTTPRequest); + + // Gracefully shutdown the thread and cleanup resources + void Shutdown(); +}; + +nsresult +ServeResourceEvent::WriteAll(char const* aBuffer, int32_t aBufferLength) +{ + while (aBufferLength > 0) { + uint32_t written = 0; + nsresult rv = mOutput->Write(aBuffer, aBufferLength, &written); + if (NS_FAILED (rv)) return rv; + + aBufferLength -= written; + aBuffer += written; + } + + return NS_OK; +} + +already_AddRefed +ServeResourceEvent::GetMediaResource(nsCString const& aHTTPRequest) +{ + // Check that the HTTP method is GET + const char* HTTP_METHOD = "GET "; + if (strncmp(aHTTPRequest.get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) { + return nullptr; + } + + const char* url_start = strchr(aHTTPRequest.get(), ' '); + if (!url_start) { + return nullptr; + } + + const char* url_end = strrchr(++url_start, ' '); + if (!url_end) { + return nullptr; + } + + // The path extracted from the HTTP request is used as a key in hash + // table. It is not related to retrieving data from the filesystem so + // we don't need to do any sanity checking on ".." paths and similar + // exploits. + nsCString relative(url_start, url_end - url_start); + nsRefPtr resource = + mServer->GetResource(mServer->GetURLPrefix() + relative); + return resource.forget(); +} + +NS_IMETHODIMP +ServeResourceEvent::Run() { + bool more = false; // Are there HTTP headers to read after the first line + nsCString line; // Contains the current line read from input stream + nsLineBuffer* buffer = new nsLineBuffer(); + nsresult rv = ReadCRLF(mInput.get(), buffer, line, &more); + if (NS_FAILED(rv)) { Shutdown(); return rv; } + + // First line contains the HTTP GET request. Extract the URL and obtain + // the MediaResource for it. + nsRefPtr resource = GetMediaResource(line); + if (!resource) { + const char* response_404 = "HTTP/1.1 404 Not Found\r\n" + "Content-Length: 0\r\n\r\n"; + rv = WriteAll(response_404, strlen(response_404)); + Shutdown(); + return rv; + } + + // Offset in bytes to start reading from resource. + // This is zero by default but can be set to another starting value if + // this HTTP request includes a byte range request header. + int64_t start = 0; + + // Keep reading lines until we get a zero length line, which is the HTTP + // protocol's way of signifying the end of headers and start of body, or + // until we have no more data to read. + while (more && line.Length() > 0) { + rv = ReadCRLF(mInput.get(), buffer, line, &more); + if (NS_FAILED(rv)) { Shutdown(); return rv; } + + // Look for a byte range request header. If there is one, set the + // media resource offset to start from to that requested. Here we + // only check for the range request format used by Android rather + // than implementing all possibilities in the HTTP specification. + // That is, the range request is of the form: + // Range: bytes=nnnn- + // Were 'nnnn' is an integer number. + // The end of the range is not checked, instead we return up to + // the end of the resource and the client is informed of this via + // the content-range header. + NS_NAMED_LITERAL_CSTRING(byteRange, "Range: bytes="); + const char* s = strstr(line.get(), byteRange.get()); + if (s) { + start = strtoll(s+byteRange.Length(), NULL, 10); + + // Clamp 'start' to be between 0 and the resource length. + start = std::max(0ll, std::min(resource->GetLength(), start)); + } + } + + // HTTP response to use if this is a non byte range request + const char* response_normal = "HTTP/1.1 200 OK\r\n"; + + // HTTP response to use if this is a byte range request + const char* response_range = "HTTP/1.1 206 Partial Content\r\n"; + + // End of HTTP reponse headers is indicated by an empty line. + const char* response_end = "\r\n"; + + // If the request was a byte range request, we need to read from the + // requested offset. If the resource is non-seekable, or the seek + // fails, then the start offset is set back to zero. This results in all + // HTTP response data being as if the byte range request was not made. + if (start > 0 && !resource->IsTransportSeekable()) { + start = 0; + } + + const char* response_line = start > 0 ? + response_range : + response_normal; + rv = WriteAll(response_line, strlen(response_line)); + if (NS_FAILED(rv)) { Shutdown(); return NS_OK; } + + // Buffer used for reading from the input stream and writing to + // the output stream. The buffer size should be big enough for the + // HTTP response headers sent below. A static_assert ensures + // this where the buffer is used. + const int buffer_size = 32768; + nsAutoArrayPtr b(new char[buffer_size]); + + // If we know the length of the resource, send a Content-Length header. + int64_t contentlength = resource->GetLength() - start; + if (contentlength > 0) { + static_assert (buffer_size > 1024, + "buffer_size must be large enough " + "to hold response headers"); + snprintf(b, buffer_size, "Content-Length: %lld\r\n", contentlength); + rv = WriteAll(b, strlen(b)); + if (NS_FAILED(rv)) { Shutdown(); return NS_OK; } + } + + // If the request was a byte range request, respond with a Content-Range + // header which details the extent of the data returned. + if (start > 0) { + static_assert (buffer_size > 1024, + "buffer_size must be large enough " + "to hold response headers"); + snprintf(b, buffer_size, "Content-Range: bytes %lld-%lld/%lld\r\n", + start, resource->GetLength() - 1, resource->GetLength()); + rv = WriteAll(b, strlen(b)); + if (NS_FAILED(rv)) { Shutdown(); return NS_OK; } + } + + rv = WriteAll(response_end, strlen(response_end)); + if (NS_FAILED(rv)) { Shutdown(); return NS_OK; } + + rv = mOutput->Flush(); + if (NS_FAILED(rv)) { Shutdown(); return NS_OK; } + + // Read data from media resource + uint32_t bytesRead = 0; // Number of bytes read/written to streams + rv = resource->ReadAt(start, b, buffer_size, &bytesRead); + while (NS_SUCCEEDED(rv) && bytesRead != 0) { + // Keep track of what we think the starting position for the next read + // is. This is used in subsequent ReadAt calls to ensure we are reading + // from the correct offset in the case where another thread is reading + // from th same MediaResource. + start += bytesRead; + + // Write data obtained from media resource to output stream + rv = WriteAll(b, bytesRead); + if (NS_FAILED (rv)) break; + + rv = resource->ReadAt(start, b, 32768, &bytesRead); + } + + Shutdown(); + return NS_OK; +} + +void +ServeResourceEvent::Shutdown() +{ + // Cleanup resources and exit. + mInput->Close(); + mOutput->Close(); + + // To shutdown the current thread we need to first exit this event. + // The Shutdown event below is posted to the main thread to do this. + nsCOMPtr event = new ShutdownThreadEvent(NS_GetCurrentThread()); + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); +} + +/* + This is the listener attached to the server socket. When an HTTP + request is made by the client the OnSocketAccepted method is + called. This method will spawn a thread to process the request. + The thread receives a single event which does the parsing of + the HTTP request and forwarding the data from the MediaResource + to the output stream of the request. + + The MediaResource used for providing the request data is obtained + from the MediaResourceServer that created this listener, using the + URL the client requested. +*/ +class ResourceSocketListener : public nsIServerSocketListener +{ +public: + // The MediaResourceServer used to look up the MediaResource + // on requests. + nsCOMPtr mServer; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISERVERSOCKETLISTENER + + ResourceSocketListener(MediaResourceServer* aServer) : + mServer(aServer) + { + } + + virtual ~ResourceSocketListener() { } +}; + +NS_IMPL_ISUPPORTS1(ResourceSocketListener, nsIServerSocketListener) + +NS_IMETHODIMP +ResourceSocketListener::OnSocketAccepted(nsIServerSocket* aServ, + nsISocketTransport* aTrans) +{ + nsCOMPtr input; + nsCOMPtr output; + nsresult rv; + + rv = aTrans->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(input)); + if (NS_FAILED(rv)) return rv; + + rv = aTrans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(output)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr thread; + rv = NS_NewThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr event = new ServeResourceEvent(input.get(), output.get(), mServer); + return thread->Dispatch(event, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ResourceSocketListener::OnStopListening(nsIServerSocket* aServ, nsresult aStatus) +{ + return NS_OK; +} + +MediaResourceServer::MediaResourceServer() : + mMutex("MediaResourceServer") +{ +} + +NS_IMETHODIMP +MediaResourceServer::Run() +{ + MutexAutoLock lock(mMutex); + + nsresult rv; + mSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = mSocket->InitSpecialConnection(-1, + nsIServerSocket::LoopbackOnly + | nsIServerSocket::KeepWhenOffline, + -1); + if (NS_FAILED(rv)) return rv; + + rv = mSocket->AsyncListen(new ResourceSocketListener(this)); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +/* static */ +already_AddRefed +MediaResourceServer::Start() +{ + nsCOMPtr server = new MediaResourceServer(); + NS_DispatchToMainThread(server, NS_DISPATCH_SYNC); + return server.forget(); +} + +void +MediaResourceServer::Stop() +{ + MutexAutoLock lock(mMutex); + mSocket->Close(); + mSocket = nullptr; +} + +nsresult +MediaResourceServer::AppendRandomPath(nsCString& aUrl) +{ + // Use a cryptographic quality PRNG to generate raw random bytes + // and convert that to a base64 string for use as an URL path. This + // is based on code from nsExternalAppHandler::SetUpTempFile. + nsresult rv; + nsCOMPtr rg = + do_GetService("@mozilla.org/security/random-generator;1", &rv); + if (NS_FAILED(rv)) return rv; + + // For each three bytes of random data we will get four bytes of + // ASCII. Request a bit more to be safe and truncate to the length + // we want at the end. + const uint32_t wantedFileNameLength = 16; + const uint32_t requiredBytesLength = + static_cast((wantedFileNameLength + 1) / 4 * 3); + + uint8_t* buffer; + rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer); + if (NS_FAILED(rv)) return rv; + + nsAutoCString tempLeafName; + nsDependentCSubstring randomData(reinterpret_cast(buffer), + requiredBytesLength); + rv = Base64Encode(randomData, tempLeafName); + NS_Free(buffer); + buffer = nullptr; + if (NS_FAILED (rv)) return rv; + + tempLeafName.Truncate(wantedFileNameLength); + + // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need + // to replace illegal characters -- notably '/' + tempLeafName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_'); + + aUrl += "/"; + aUrl += tempLeafName; + + return NS_OK; +} + +nsresult +MediaResourceServer::AddResource(mozilla::MediaResource* aResource, nsCString& aUrl) +{ + nsCString url = GetURLPrefix(); + nsresult rv = AppendRandomPath(url); + if (NS_FAILED (rv)) return rv; + + { + MutexAutoLock lock(mMutex); + + // Adding a resource URL that already exists is considered an error. + if (mResources.find(aUrl) != mResources.end()) return NS_ERROR_FAILURE; + mResources[url] = aResource; + } + + aUrl = url; + + return NS_OK; +} + +void +MediaResourceServer::RemoveResource(nsCString const& aUrl) +{ + MutexAutoLock lock(mMutex); + mResources.erase(aUrl); +} + +nsCString +MediaResourceServer::GetURLPrefix() +{ + MutexAutoLock lock(mMutex); + + int32_t port = 0; + nsresult rv = mSocket->GetPort(&port); + if (NS_FAILED (rv) || port < 0) { + return nsCString(""); + } + + char buffer[256]; + snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%d", port >= 0 ? port : 0); + return nsCString(buffer); +} + +already_AddRefed +MediaResourceServer::GetResource(nsCString const& aUrl) +{ + MutexAutoLock lock(mMutex); + ResourceMap::const_iterator it = mResources.find(aUrl); + if (it == mResources.end()) return nullptr; + + nsRefPtr resource = it->second; + return resource.forget(); +} diff --git a/content/media/plugins/MediaResourceServer.h b/content/media/plugins/MediaResourceServer.h new file mode 100644 index 000000000000..6bfa196739ce --- /dev/null +++ b/content/media/plugins/MediaResourceServer.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(MediaResourceServer_h_) +#define MediaResourceServer_h_ + +#include +#include "nsIServerSocket.h" +#include "MediaResource.h" + +namespace mozilla { + +class MediaResource; + +/* + MediaResourceServer instantiates a socket server that understands + HTTP requests for MediaResource instances. The server runs on an + automatically selected port and MediaResource instances are registered. + The registration returns a string URL than can be used to fetch the + resource. That URL contains a randomly generated path to make it + difficult for other local applications on the device to guess it. + + The HTTP protocol is limited in that it supports only what the + Android DataSource implementation uses to fetch media. It + understands HTTP GET and byte range requests. + + The intent of this class is to be used in Media backends that + have a system component that does its own network requests. These + requests are made against this server which then uses standard + Gecko network requests and media cache usage. + + The MediaResourceServer can be instantiated on any thread and + its methods are threadsafe - they can be called on any thread. + The server socket itself is always run on the main thread and + this is done by the Start() static method by synchronously + dispatching to the main thread. +*/ +class MediaResourceServer : public nsRunnable +{ +private: + // Mutex protecting private members of MediaResourceServer. + // All member variables below this point in the class definition + // must acquire the mutex before access. + mozilla::Mutex mMutex; + + // Server socket used to listen for incoming connections + nsCOMPtr mSocket; + + // Mapping between MediaResource URL's to the MediaResource + // object served at that URL. + typedef std::map > ResourceMap; + ResourceMap mResources; + + // Create a MediaResourceServer that will listen on an automatically + // selected port when started. This is private as it should only be + // called internally from the public 'Start' method. + MediaResourceServer(); + NS_IMETHOD Run(); + + // Append a random URL path to a string. This is used for creating a + // unique URl for a resource which helps prevent malicious software + // running on the same machine as the server from guessing the URL + // and accessing video data. + nsresult AppendRandomPath(nsCString& aURL); + +public: + // Create a MediaResourceServer and start it listening. This call will + // perform a synchronous request on the main thread. + static already_AddRefed Start(); + + // Stops the server from listening and accepting further connections. + void Stop(); + + // Add a MediaResource to be served by this server. Stores the + // absolute URL that can be used to access the resource in 'aUrl'. + nsresult AddResource(mozilla::MediaResource* aResource, nsCString& aUrl); + + // Remove a MediaResource so it is no longer served by this server. + // The URL provided must match exactly that provided by a previous + // call to "AddResource". + void RemoveResource(nsCString const& aUrl); + + // Returns the prefix for HTTP requests to the server. This plus + // the result of AddResource results in an Absolute URL. + nsCString GetURLPrefix(); + + // Returns the resource asociated with a given URL + already_AddRefed GetResource(nsCString const& aUrl); +}; + +} // namespace mozilla + +#endif diff --git a/content/media/plugins/moz.build b/content/media/plugins/moz.build index c2845273bc8e..729551df3173 100644 --- a/content/media/plugins/moz.build +++ b/content/media/plugins/moz.build @@ -11,12 +11,14 @@ EXPORTS += [ 'MediaPluginDecoder.h', 'MediaPluginHost.h', 'MediaPluginReader.h', + 'MediaResourceServer.h', ] CPP_SOURCES += [ 'MediaPluginDecoder.cpp', 'MediaPluginHost.cpp', 'MediaPluginReader.cpp', + 'MediaResourceServer.cpp', ] LIBRARY_NAME = 'gkconmediaplugins_s' diff --git a/media/omx-plugin/OmxPlugin.cpp b/media/omx-plugin/OmxPlugin.cpp index 3b45bef724d5..50dba16925bb 100644 --- a/media/omx-plugin/OmxPlugin.cpp +++ b/media/omx-plugin/OmxPlugin.cpp @@ -38,91 +38,10 @@ using namespace MPAPI; -namespace android { - #if !defined(MOZ_STAGEFRIGHT_OFF_T) #define MOZ_STAGEFRIGHT_OFF_T off64_t #endif -// MediaStreamSource is a DataSource that reads from a MPAPI media stream. -class MediaStreamSource : public DataSource { - PluginHost *mPluginHost; -public: - MediaStreamSource(PluginHost *aPluginHost, Decoder *aDecoder); - - virtual status_t initCheck() const; - virtual ssize_t readAt(MOZ_STAGEFRIGHT_OFF_T offset, void *data, size_t size); - virtual status_t getSize(MOZ_STAGEFRIGHT_OFF_T *size); - virtual uint32_t flags() { - return kWantsPrefetching; - } - - virtual ~MediaStreamSource(); - -private: - Decoder *mDecoder; - - MediaStreamSource(const MediaStreamSource &); - MediaStreamSource &operator=(const MediaStreamSource &); - -#ifdef MOZ_ANDROID_HTC_WORKAROUND - // libstagefright on some Jellybean HTC devices (at least the Tegra 3 One X) - // calls this function and expects this magic number to be returned when - // sniffing audio stream formats. - // It is unclear what this is for or what it does. - virtual uint32_t MagicalHTCIncantation() { return 0x3f0; } -#endif -}; - -MediaStreamSource::MediaStreamSource(PluginHost *aPluginHost, Decoder *aDecoder) : - mPluginHost(aPluginHost) -{ - mDecoder = aDecoder; -} - -MediaStreamSource::~MediaStreamSource() -{ -} - -status_t MediaStreamSource::initCheck() const -{ - return OK; -} - -ssize_t MediaStreamSource::readAt(MOZ_STAGEFRIGHT_OFF_T offset, void *data, size_t size) -{ - char *ptr = reinterpret_cast(data); - size_t todo = size; - while (todo > 0) { - uint32_t bytesRead; - if (!mPluginHost->Read(mDecoder, ptr, offset, todo, &bytesRead)) { - return ERROR_IO; - } - - if (bytesRead == 0) { - return size - todo; - } - - offset += bytesRead; - todo -= bytesRead; - ptr += bytesRead; - } - return size; -} - -status_t MediaStreamSource::getSize(MOZ_STAGEFRIGHT_OFF_T *size) -{ - uint64_t length = mPluginHost->GetLength(mDecoder); - if (length == static_cast(-1)) - return ERROR_UNSUPPORTED; - - *size = length; - - return OK; -} - -} // namespace android - using namespace android; namespace OmxPlugin { @@ -369,13 +288,12 @@ bool OmxDecoder::Init() { //register sniffers, if they are not registered in this process. DataSource::RegisterDefaultSniffers(); - sp dataSource = new MediaStreamSource(mPluginHost, mDecoder); - if (dataSource->initCheck()) { + sp dataSource = + DataSource::CreateFromURI(static_cast(mDecoder->mResource)); + if (!dataSource.get() || dataSource->initCheck()) { return false; } - mPluginHost->SetMetaDataReadMode(mDecoder); - sp extractor = MediaExtractor::Create(dataSource); if (extractor == NULL) { return false; @@ -407,8 +325,6 @@ bool OmxDecoder::Init() { return false; } - mPluginHost->SetPlaybackReadMode(mDecoder); - int64_t totalDurationUs = 0; #ifdef MOZ_WIDGET_GONK diff --git a/media/omx-plugin/lib/gb/libstagefright/libstagefright.cpp b/media/omx-plugin/lib/gb/libstagefright/libstagefright.cpp index b66f0c1cdbef..76df694bf5bb 100644 --- a/media/omx-plugin/lib/gb/libstagefright/libstagefright.cpp +++ b/media/omx-plugin/lib/gb/libstagefright/libstagefright.cpp @@ -143,18 +143,4 @@ MOZ_EXPORT void OMXClient::disconnect() { } -class __attribute__ ((visibility ("default"))) UnknownDataSource : public DataSource { -public: -UnknownDataSource(); - -virtual status_t initCheck() const { return 0; } -virtual ssize_t readAt(MOZ_STAGEFRIGHT_OFF_T offset, void *data, size_t size) { return 0; } -virtual status_t getSize(MOZ_STAGEFRIGHT_OFF_T *size) { return 0; } - -virtual ~UnknownDataSource() { } -}; - -UnknownDataSource foo; - -MOZ_EXPORT UnknownDataSource::UnknownDataSource() { } } diff --git a/media/omx-plugin/lib/ics/libstagefright/libstagefright.cpp b/media/omx-plugin/lib/ics/libstagefright/libstagefright.cpp index 06e86ab8e773..43bee539fd8d 100644 --- a/media/omx-plugin/lib/ics/libstagefright/libstagefright.cpp +++ b/media/omx-plugin/lib/ics/libstagefright/libstagefright.cpp @@ -95,6 +95,12 @@ MediaSource::ReadOptions::setSeekTo(int64_t time_us, SeekMode mode) { } +MOZ_EXPORT sp DataSource::CreateFromURI( + const char *uri, + const KeyedVector *headers) { + return 0; +} + MOZ_EXPORT bool DataSource::getUInt16(off64_t offset, uint16_t *x) { @@ -149,21 +155,6 @@ MOZ_EXPORT void OMXClient::disconnect() { } -class __attribute__ ((visibility ("default"))) UnknownDataSource : public DataSource { -public: -UnknownDataSource(); - -virtual status_t initCheck() const { return 0; } -virtual ssize_t readAt(off64_t offset, void *data, size_t size) { return 0; } -virtual status_t getSize(off64_t *size) { return 0; } - -virtual ~UnknownDataSource() { } -}; - -UnknownDataSource foo; - -MOZ_EXPORT UnknownDataSource::UnknownDataSource() { } - MOZ_EXPORT ColorConverter::ColorConverter(OMX_COLOR_FORMATTYPE, OMX_COLOR_FORMATTYPE) { }