summaryrefslogtreecommitdiffstats
path: root/dom/media/android/AndroidMediaResourceServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/android/AndroidMediaResourceServer.cpp')
-rw-r--r--dom/media/android/AndroidMediaResourceServer.cpp503
1 files changed, 0 insertions, 503 deletions
diff --git a/dom/media/android/AndroidMediaResourceServer.cpp b/dom/media/android/AndroidMediaResourceServer.cpp
deleted file mode 100644
index bd76a8c68..000000000
--- a/dom/media/android/AndroidMediaResourceServer.cpp
+++ /dev/null
@@ -1,503 +0,0 @@
-/* -*- 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 "mozilla/IntegerPrintfMacros.h"
-#include "mozilla/UniquePtr.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 "AndroidMediaResourceServer.h"
-
-#if defined(_MSC_VER)
-#define strtoll _strtoi64
-#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<typename CharT, class StreamType, class StringType>
-nsresult
-ReadCRLF (StreamType* aStream, nsLineBuffer<CharT> * 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 Runnable {
-private:
- // Reading from this reads the data sent from the client.
- nsCOMPtr<nsIInputStream> mInput;
-
- // Writing to this sends data to the client.
- nsCOMPtr<nsIOutputStream> mOutput;
-
- // The AndroidMediaResourceServer that owns the MediaResource instances
- // served. This is used to lookup the MediaResource from the URL.
- RefPtr<AndroidMediaResourceServer> 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,
- AndroidMediaResourceServer* 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<MediaResource> 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<MediaResource>
-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);
- RefPtr<MediaResource> 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<char>* buffer = new nsLineBuffer<char>();
- 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.
- RefPtr<MediaResource> 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(), nullptr, 10);
-
- // Clamp 'start' to be between 0 and the resource length.
- start = std::max(int64_t(0), 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;
- auto b = MakeUnique<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.get(), buffer_size, "Content-Length: %" PRId64 "\r\n", contentlength);
- rv = WriteAll(b.get(), strlen(b.get()));
- 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.get(), buffer_size, "Content-Range: "
- "bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n",
- start, resource->GetLength() - 1, resource->GetLength());
- rv = WriteAll(b.get(), strlen(b.get()));
- 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.get(), 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.get(), bytesRead);
- if (NS_FAILED (rv)) break;
-
- rv = resource->ReadAt(start, b.get(), 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<nsIRunnable> event = new ShutdownThreadEvent(NS_GetCurrentThread());
- NS_DispatchToMainThread(event);
-}
-
-/*
- 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 AndroidMediaResourceServer that created this listener, using the
- URL the client requested.
-*/
-class ResourceSocketListener : public nsIServerSocketListener
-{
-public:
- // The AndroidMediaResourceServer used to look up the MediaResource
- // on requests.
- RefPtr<AndroidMediaResourceServer> mServer;
-
- NS_DECL_THREADSAFE_ISUPPORTS
- NS_DECL_NSISERVERSOCKETLISTENER
-
- ResourceSocketListener(AndroidMediaResourceServer* aServer) :
- mServer(aServer)
- {
- }
-
-private:
- virtual ~ResourceSocketListener() { }
-};
-
-NS_IMPL_ISUPPORTS(ResourceSocketListener, nsIServerSocketListener)
-
-NS_IMETHODIMP
-ResourceSocketListener::OnSocketAccepted(nsIServerSocket* aServ,
- nsISocketTransport* aTrans)
-{
- nsCOMPtr<nsIInputStream> input;
- nsCOMPtr<nsIOutputStream> 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<nsIThread> thread;
- rv = NS_NewThread(getter_AddRefs(thread));
- if (NS_FAILED(rv)) return rv;
-
- nsCOMPtr<nsIRunnable> 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;
-}
-
-AndroidMediaResourceServer::AndroidMediaResourceServer() :
- mMutex("AndroidMediaResourceServer")
-{
-}
-
-NS_IMETHODIMP
-AndroidMediaResourceServer::Run()
-{
- MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
- 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<AndroidMediaResourceServer>
-AndroidMediaResourceServer::Start()
-{
- MOZ_ASSERT(NS_IsMainThread());
- RefPtr<AndroidMediaResourceServer> server = new AndroidMediaResourceServer();
- server->Run();
- return server.forget();
-}
-
-void
-AndroidMediaResourceServer::Stop()
-{
- MutexAutoLock lock(mMutex);
- mSocket->Close();
- mSocket = nullptr;
-}
-
-nsresult
-AndroidMediaResourceServer::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;
- nsAutoCString salt;
- rv = GenerateRandomPathName(salt, 16);
- if (NS_FAILED(rv)) return rv;
- aUrl += "/";
- aUrl += salt;
- return NS_OK;
-}
-
-nsresult
-AndroidMediaResourceServer::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(url) != mResources.end()) return NS_ERROR_FAILURE;
- mResources[url] = aResource;
- }
-
- aUrl = url;
-
- return NS_OK;
-}
-
-void
-AndroidMediaResourceServer::RemoveResource(nsCString const& aUrl)
-{
- MutexAutoLock lock(mMutex);
- mResources.erase(aUrl);
-}
-
-nsCString
-AndroidMediaResourceServer::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<MediaResource>
-AndroidMediaResourceServer::GetResource(nsCString const& aUrl)
-{
- MutexAutoLock lock(mMutex);
- ResourceMap::const_iterator it = mResources.find(aUrl);
- if (it == mResources.end()) return nullptr;
-
- RefPtr<MediaResource> resource = it->second;
- return resource.forget();
-}