content/media/plugins/MediaResourceServer.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/Assertions.h"
     7 #include "mozilla/Base64.h"
     8 #include "nsThreadUtils.h"
     9 #include "nsIServiceManager.h"
    10 #include "nsISocketTransport.h"
    11 #include "nsIOutputStream.h"
    12 #include "nsIInputStream.h"
    13 #include "nsIRandomGenerator.h"
    14 #include "nsReadLine.h"
    15 #include "nsNetCID.h"
    16 #include "VideoUtils.h"
    17 #include "MediaResource.h"
    18 #include "MediaResourceServer.h"
    20 #if defined(_MSC_VER)
    21 #define strtoll _strtoi64
    22 #define snprintf _snprintf_s
    23 #endif
    25 using namespace mozilla;
    27 /*
    28   ReadCRLF is a variant of NS_ReadLine from nsReadLine.h that deals
    29   with the carriage return/line feed requirements of HTTP requests.
    30 */
    31 template<typename CharT, class StreamType, class StringType>
    32 nsresult
    33 ReadCRLF (StreamType* aStream, nsLineBuffer<CharT> * aBuffer,
    34           StringType & aLine, bool *aMore)
    35 {
    36   // eollast is true if the last character in the buffer is a '\r',
    37   // signaling a potential '\r\n' sequence split between reads.
    38   bool eollast = false;
    40   aLine.Truncate();
    42   while (1) { // will be returning out of this loop on eol or eof
    43     if (aBuffer->start == aBuffer->end) { // buffer is empty.  Read into it.
    44       uint32_t bytesRead;
    45       nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
    46       if (NS_FAILED(rv) || bytesRead == 0) {
    47         *aMore = false;
    48         return rv;
    49       }
    50       aBuffer->start = aBuffer->buf;
    51       aBuffer->end = aBuffer->buf + bytesRead;
    52       *(aBuffer->end) = '\0';
    53     }
    55     /*
    56      * Walk the buffer looking for an end-of-line.
    57      * There are 4 cases to consider:
    58      *  1. the CR char is the last char in the buffer
    59      *  2. the CRLF sequence are the last characters in the buffer
    60      *  3. the CRLF sequence + one or more chars at the end of the buffer
    61      *      we need at least one char after the first CRLF sequence to
    62      *      set |aMore| correctly.
    63      *  4. The LF character is the first char in the buffer when eollast is
    64      *      true.
    65      */
    66     CharT* current = aBuffer->start;
    67     if (eollast) { // Case 4
    68       if (*current == '\n') {
    69         aBuffer->start = ++current;
    70         *aMore = true;
    71         return NS_OK;
    72       }
    73       else {
    74         eollast = false;
    75         aLine.Append('\r');
    76       }
    77     }
    78     // Cases 2 and 3
    79     for ( ; current < aBuffer->end-1; ++current) {
    80       if (*current == '\r' && *(current+1) == '\n') {
    81         *current++ = '\0';
    82         *current++ = '\0';
    83         aLine.Append(aBuffer->start);
    84         aBuffer->start = current;
    85         *aMore = true;
    86         return NS_OK;
    87       }
    88     }
    89     // Case 1
    90     if (*current == '\r') {
    91       eollast = true;
    92       *current++ = '\0';
    93     }
    95     aLine.Append(aBuffer->start);
    96     aBuffer->start = aBuffer->end; // mark the buffer empty
    97   }
    98 }
   100 // Each client HTTP request results in a thread being spawned to process it.
   101 // That thread has a single event dispatched to it which handles the HTTP
   102 // protocol. It parses the headers and forwards data from the MediaResource
   103 // associated with the URL back to client. When the request is complete it will
   104 // shutdown the thread.
   105 class ServeResourceEvent : public nsRunnable {
   106 private:
   107   // Reading from this reads the data sent from the client.
   108   nsCOMPtr<nsIInputStream> mInput;
   110   // Writing to this sends data to the client.
   111   nsCOMPtr<nsIOutputStream> mOutput;
   113   // The MediaResourceServer that owns the MediaResource instances
   114   // served. This is used to lookup the MediaResource from the URL.
   115   nsRefPtr<MediaResourceServer> mServer;
   117   // Write 'aBufferLength' bytes from 'aBuffer' to 'mOutput'. This
   118   // method ensures all the data is written by checking the number
   119   // of bytes returned from the output streams 'Write' method and
   120   // looping until done.
   121   nsresult WriteAll(char const* aBuffer, int32_t aBufferLength);
   123 public:
   124   ServeResourceEvent(nsIInputStream* aInput, nsIOutputStream* aOutput,
   125                      MediaResourceServer* aServer)
   126     : mInput(aInput), mOutput(aOutput), mServer(aServer) {}
   128   // This method runs on the thread and exits when it has completed the
   129   // HTTP request.
   130   NS_IMETHOD Run();
   132   // Given the first line of an HTTP request, parse the URL requested and
   133   // return the MediaResource for that URL.
   134   already_AddRefed<MediaResource> GetMediaResource(nsCString const& aHTTPRequest);
   136   // Gracefully shutdown the thread and cleanup resources
   137   void Shutdown();
   138 };
   140 nsresult
   141 ServeResourceEvent::WriteAll(char const* aBuffer, int32_t aBufferLength)
   142 {
   143   while (aBufferLength > 0) {
   144     uint32_t written = 0;
   145     nsresult rv = mOutput->Write(aBuffer, aBufferLength, &written);
   146     if (NS_FAILED (rv)) return rv;
   148     aBufferLength -= written;
   149     aBuffer += written;
   150   }
   152   return NS_OK;
   153 }
   155 already_AddRefed<MediaResource>
   156 ServeResourceEvent::GetMediaResource(nsCString const& aHTTPRequest)
   157 {
   158   // Check that the HTTP method is GET
   159   const char* HTTP_METHOD = "GET ";
   160   if (strncmp(aHTTPRequest.get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) {
   161     return nullptr;
   162   }
   164   const char* url_start = strchr(aHTTPRequest.get(), ' ');
   165   if (!url_start) {
   166     return nullptr;
   167   }
   169   const char* url_end = strrchr(++url_start, ' ');
   170   if (!url_end) {
   171     return nullptr;
   172   }
   174   // The path extracted from the HTTP request is used as a key in hash
   175   // table. It is not related to retrieving data from the filesystem so
   176   // we don't need to do any sanity checking on ".." paths and similar
   177   // exploits.
   178   nsCString relative(url_start, url_end - url_start);
   179   nsRefPtr<MediaResource> resource =
   180     mServer->GetResource(mServer->GetURLPrefix() + relative);
   181   return resource.forget();
   182 }
   184 NS_IMETHODIMP
   185 ServeResourceEvent::Run() {
   186   bool more = false; // Are there HTTP headers to read after the first line
   187   nsCString line;    // Contains the current line read from input stream
   188   nsLineBuffer<char>* buffer = new nsLineBuffer<char>();
   189   nsresult rv = ReadCRLF(mInput.get(), buffer, line, &more);
   190   if (NS_FAILED(rv)) { Shutdown(); return rv; }
   192   // First line contains the HTTP GET request. Extract the URL and obtain
   193   // the MediaResource for it.
   194   nsRefPtr<MediaResource> resource = GetMediaResource(line);
   195   if (!resource) {
   196     const char* response_404 = "HTTP/1.1 404 Not Found\r\n"
   197                                "Content-Length: 0\r\n\r\n";
   198     rv = WriteAll(response_404, strlen(response_404));
   199     Shutdown();
   200     return rv;
   201   }
   203   // Offset in bytes to start reading from resource.
   204   // This is zero by default but can be set to another starting value if
   205   // this HTTP request includes a byte range request header.
   206   int64_t start = 0;
   208   // Keep reading lines until we get a zero length line, which is the HTTP
   209   // protocol's way of signifying the end of headers and start of body, or
   210   // until we have no more data to read.
   211   while (more && line.Length() > 0) {
   212     rv = ReadCRLF(mInput.get(), buffer, line, &more);
   213     if (NS_FAILED(rv)) { Shutdown(); return rv; }
   215     // Look for a byte range request header. If there is one, set the
   216     // media resource offset to start from to that requested. Here we
   217     // only check for the range request format used by Android rather
   218     // than implementing all possibilities in the HTTP specification.
   219     // That is, the range request is of the form:
   220     //   Range: bytes=nnnn-
   221     // Were 'nnnn' is an integer number.
   222     // The end of the range is not checked, instead we return up to
   223     // the end of the resource and the client is informed of this via
   224     // the content-range header.
   225     NS_NAMED_LITERAL_CSTRING(byteRange, "Range: bytes=");
   226     const char* s = strstr(line.get(), byteRange.get());
   227     if (s) {
   228       start = strtoll(s+byteRange.Length(), nullptr, 10);
   230       // Clamp 'start' to be between 0 and the resource length.
   231       start = std::max(0ll, std::min(resource->GetLength(), start));
   232     }
   233   }
   235   // HTTP response to use if this is a non byte range request
   236   const char* response_normal = "HTTP/1.1 200 OK\r\n";
   238   // HTTP response to use if this is a byte range request
   239   const char* response_range = "HTTP/1.1 206 Partial Content\r\n";
   241   // End of HTTP reponse headers is indicated by an empty line.
   242   const char* response_end = "\r\n";
   244   // If the request was a byte range request, we need to read from the
   245   // requested offset. If the resource is non-seekable, or the seek
   246   // fails, then the start offset is set back to zero. This results in all
   247   // HTTP response data being as if the byte range request was not made.
   248   if (start > 0 && !resource->IsTransportSeekable()) {
   249     start = 0;
   250   }
   252   const char* response_line = start > 0 ?
   253                                 response_range :
   254                                 response_normal;
   255   rv = WriteAll(response_line, strlen(response_line));
   256   if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
   258   // Buffer used for reading from the input stream and writing to
   259   // the output stream. The buffer size should be big enough for the
   260   // HTTP response headers sent below. A static_assert ensures
   261   // this where the buffer is used.
   262   const int buffer_size = 32768;
   263   nsAutoArrayPtr<char> b(new char[buffer_size]);
   265   // If we know the length of the resource, send a Content-Length header.
   266   int64_t contentlength = resource->GetLength() - start;
   267   if (contentlength > 0) {
   268     static_assert (buffer_size > 1024,
   269                    "buffer_size must be large enough "
   270                    "to hold response headers");
   271     snprintf(b, buffer_size, "Content-Length: %lld\r\n", contentlength);
   272     rv = WriteAll(b, strlen(b));
   273     if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
   274   }
   276   // If the request was a byte range request, respond with a Content-Range
   277   // header which details the extent of the data returned.
   278   if (start > 0) {
   279     static_assert (buffer_size > 1024,
   280                    "buffer_size must be large enough "
   281                    "to hold response headers");
   282     snprintf(b, buffer_size, "Content-Range: bytes %lld-%lld/%lld\r\n",
   283              start, resource->GetLength() - 1, resource->GetLength());
   284     rv = WriteAll(b, strlen(b));
   285     if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
   286   }
   288   rv = WriteAll(response_end, strlen(response_end));
   289   if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
   291   rv = mOutput->Flush();
   292   if (NS_FAILED(rv)) { Shutdown(); return NS_OK; }
   294   // Read data from media resource
   295   uint32_t bytesRead = 0; // Number of bytes read/written to streams
   296   rv = resource->ReadAt(start, b, buffer_size, &bytesRead);
   297   while (NS_SUCCEEDED(rv) && bytesRead != 0) {
   298     // Keep track of what we think the starting position for the next read
   299     // is. This is used in subsequent ReadAt calls to ensure we are reading
   300     // from the correct offset in the case where another thread is reading
   301     // from th same MediaResource.
   302     start += bytesRead;
   304     // Write data obtained from media resource to output stream
   305     rv = WriteAll(b, bytesRead);
   306     if (NS_FAILED (rv)) break;
   308     rv = resource->ReadAt(start, b, 32768, &bytesRead);
   309   }
   311   Shutdown();
   312   return NS_OK;
   313 }
   315 void
   316 ServeResourceEvent::Shutdown()
   317 {
   318   // Cleanup resources and exit.
   319   mInput->Close();
   320   mOutput->Close();
   322   // To shutdown the current thread we need to first exit this event.
   323   // The Shutdown event below is posted to the main thread to do this.
   324   nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(NS_GetCurrentThread());
   325   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   326 }
   328 /*
   329   This is the listener attached to the server socket. When an HTTP
   330   request is made by the client the OnSocketAccepted method is
   331   called. This method will spawn a thread to process the request.
   332   The thread receives a single event which does the parsing of
   333   the HTTP request and forwarding the data from the MediaResource
   334   to the output stream of the request.
   336   The MediaResource used for providing the request data is obtained
   337   from the MediaResourceServer that created this listener, using the
   338   URL the client requested.
   339 */
   340 class ResourceSocketListener : public nsIServerSocketListener
   341 {
   342 public:
   343   // The MediaResourceServer used to look up the MediaResource
   344   // on requests.
   345   nsRefPtr<MediaResourceServer> mServer;
   347 public:
   348   NS_DECL_THREADSAFE_ISUPPORTS
   349   NS_DECL_NSISERVERSOCKETLISTENER
   351   ResourceSocketListener(MediaResourceServer* aServer) :
   352     mServer(aServer)
   353   {
   354   }
   356   virtual ~ResourceSocketListener() { }
   357 };
   359 NS_IMPL_ISUPPORTS(ResourceSocketListener, nsIServerSocketListener)
   361 NS_IMETHODIMP
   362 ResourceSocketListener::OnSocketAccepted(nsIServerSocket* aServ,
   363                                          nsISocketTransport* aTrans)
   364 {
   365   nsCOMPtr<nsIInputStream> input;
   366   nsCOMPtr<nsIOutputStream> output;
   367   nsresult rv;
   369   rv = aTrans->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(input));
   370   if (NS_FAILED(rv)) return rv;
   372   rv = aTrans->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(output));
   373   if (NS_FAILED(rv)) return rv;
   375   nsCOMPtr<nsIThread> thread;
   376   rv = NS_NewThread(getter_AddRefs(thread));
   377   if (NS_FAILED(rv)) return rv;
   379   nsCOMPtr<nsIRunnable> event = new ServeResourceEvent(input.get(), output.get(), mServer);
   380   return thread->Dispatch(event, NS_DISPATCH_NORMAL);
   381 }
   383 NS_IMETHODIMP
   384 ResourceSocketListener::OnStopListening(nsIServerSocket* aServ, nsresult aStatus)
   385 {
   386   return NS_OK;
   387 }
   389 MediaResourceServer::MediaResourceServer() :
   390   mMutex("MediaResourceServer")
   391 {
   392 }
   394 NS_IMETHODIMP
   395 MediaResourceServer::Run()
   396 {
   397   MutexAutoLock lock(mMutex);
   399   nsresult rv;
   400   mSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID, &rv);
   401   if (NS_FAILED(rv)) return rv;
   403   rv = mSocket->InitSpecialConnection(-1,
   404                                       nsIServerSocket::LoopbackOnly
   405                                       | nsIServerSocket::KeepWhenOffline,
   406                                       -1);
   407   if (NS_FAILED(rv)) return rv;
   409   rv = mSocket->AsyncListen(new ResourceSocketListener(this));
   410   if (NS_FAILED(rv)) return rv;
   412   return NS_OK;
   413 }
   415 /* static */
   416 already_AddRefed<MediaResourceServer>
   417 MediaResourceServer::Start()
   418 {
   419   nsRefPtr<MediaResourceServer> server = new MediaResourceServer();
   420   NS_DispatchToMainThread(server, NS_DISPATCH_SYNC);
   421   return server.forget();
   422 }
   424 void
   425 MediaResourceServer::Stop()
   426 {
   427   MutexAutoLock lock(mMutex);
   428   mSocket->Close();
   429   mSocket = nullptr;
   430 }
   432 nsresult
   433 MediaResourceServer::AppendRandomPath(nsCString& aUrl)
   434 {
   435   // Use a cryptographic quality PRNG to generate raw random bytes
   436   // and convert that to a base64 string for use as an URL path. This
   437   // is based on code from nsExternalAppHandler::SetUpTempFile.
   438   nsresult rv;
   439   nsCOMPtr<nsIRandomGenerator> rg =
   440     do_GetService("@mozilla.org/security/random-generator;1", &rv);
   441   if (NS_FAILED(rv)) return rv;
   443   // For each three bytes of random data we will get four bytes of
   444   // ASCII. Request a bit more to be safe and truncate to the length
   445   // we want at the end.
   446   const uint32_t wantedFileNameLength = 16;
   447   const uint32_t requiredBytesLength =
   448     static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
   450   uint8_t* buffer;
   451   rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
   452   if (NS_FAILED(rv)) return rv;
   454   nsAutoCString tempLeafName;
   455   nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
   456                                    requiredBytesLength);
   457   rv = Base64Encode(randomData, tempLeafName);
   458   NS_Free(buffer);
   459   buffer = nullptr;
   460   if (NS_FAILED (rv)) return rv;
   462   tempLeafName.Truncate(wantedFileNameLength);
   464   // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
   465   // to replace illegal characters -- notably '/'
   466   tempLeafName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
   468   aUrl += "/";
   469   aUrl += tempLeafName;
   471   return NS_OK;
   472 }
   474 nsresult
   475 MediaResourceServer::AddResource(mozilla::MediaResource* aResource, nsCString& aUrl)
   476 {
   477   nsCString url = GetURLPrefix();
   478   nsresult rv = AppendRandomPath(url);
   479   if (NS_FAILED (rv)) return rv;
   481   {
   482     MutexAutoLock lock(mMutex);
   484     // Adding a resource URL that already exists is considered an error.
   485     if (mResources.find(aUrl) != mResources.end()) return NS_ERROR_FAILURE;
   486     mResources[url] = aResource;
   487   }
   489   aUrl = url;
   491   return NS_OK;
   492 }
   494 void
   495 MediaResourceServer::RemoveResource(nsCString const& aUrl)
   496 {
   497   MutexAutoLock lock(mMutex);
   498   mResources.erase(aUrl);
   499 }
   501 nsCString
   502 MediaResourceServer::GetURLPrefix()
   503 {
   504   MutexAutoLock lock(mMutex);
   506   int32_t port = 0;
   507   nsresult rv = mSocket->GetPort(&port);
   508   if (NS_FAILED (rv) || port < 0) {
   509     return nsCString("");
   510   }
   512   char buffer[256];
   513   snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%d", port >= 0 ? port : 0);
   514   return nsCString(buffer);
   515 }
   517 already_AddRefed<MediaResource>
   518 MediaResourceServer::GetResource(nsCString const& aUrl)
   519 {
   520   MutexAutoLock lock(mMutex);
   521   ResourceMap::const_iterator it = mResources.find(aUrl);
   522   if (it == mResources.end()) return nullptr;
   524   nsRefPtr<MediaResource> resource = it->second;
   525   return resource.forget();
   526 }

mercurial