LCOV - code coverage report
Current view: top level - dom/base - BodyUtil.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 11 187 5.9 %
Date: 2018-08-07 16:35:00 Functions: 0 0 -
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       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
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "BodyUtil.h"
       8             : 
       9             : #include "nsError.h"
      10             : #include "nsString.h"
      11             : #include "nsIGlobalObject.h"
      12             : #include "mozilla/Encoding.h"
      13             : 
      14             : #include "nsCharSeparatedTokenizer.h"
      15             : #include "nsDOMString.h"
      16             : #include "nsNetUtil.h"
      17             : #include "nsReadableUtils.h"
      18             : #include "nsStreamUtils.h"
      19             : #include "nsStringStream.h"
      20             : 
      21             : #include "mozilla/ErrorResult.h"
      22             : #include "mozilla/dom/Exceptions.h"
      23             : #include "mozilla/dom/FetchUtil.h"
      24             : #include "mozilla/dom/File.h"
      25             : #include "mozilla/dom/FormData.h"
      26             : #include "mozilla/dom/Headers.h"
      27             : #include "mozilla/dom/Promise.h"
      28             : #include "mozilla/dom/URLSearchParams.h"
      29             : 
      30             : namespace mozilla {
      31             : namespace dom {
      32             : 
      33             : namespace {
      34             : 
      35             : // Reads over a CRLF and positions start after it.
      36             : static bool
      37           0 : PushOverLine(nsACString::const_iterator& aStart,
      38             :              const nsACString::const_iterator& aEnd)
      39             : {
      40           0 :   if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
      41           0 :     ++aStart; // advance to after CRLF
      42           0 :     return true;
      43             :   }
      44             : 
      45             :   return false;
      46             : }
      47             : 
      48             : class MOZ_STACK_CLASS FillFormIterator final
      49             :   : public URLParams::ForEachIterator
      50             : {
      51             : public:
      52           0 :   explicit FillFormIterator(FormData* aFormData)
      53           0 :     : mFormData(aFormData)
      54             :   {
      55           0 :     MOZ_ASSERT(aFormData);
      56           0 :   }
      57             : 
      58           0 :   bool URLParamsIterator(const nsAString& aName,
      59             :                          const nsAString& aValue) override
      60             :   {
      61           0 :     ErrorResult rv;
      62           0 :     mFormData->Append(aName, aValue, rv);
      63           0 :     MOZ_ASSERT(!rv.Failed());
      64           0 :     return true;
      65             :   }
      66             : 
      67             : private:
      68             :   FormData* mFormData;
      69             : };
      70             : 
      71             : /**
      72             :  * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
      73             :  * This does not respect any encoding specified per entry, using UTF-8
      74             :  * throughout. This is as the Fetch spec states in the consume body algorithm.
      75             :  * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
      76             :  * unlike Necko we do not have to deal with receiving incomplete chunks of data.
      77             :  *
      78             :  * This parser will fail the entire parse on any invalid entry, so it will
      79             :  * never return a partially filled FormData.
      80             :  * The content-disposition header is used to figure out the name and filename
      81             :  * entries. The inclusion of the filename parameter decides if the entry is
      82             :  * inserted into the FormData as a string or a File.
      83             :  *
      84             :  * File blobs are copies of the underlying data string since we cannot adopt
      85             :  * char* chunks embedded within the larger body without significant effort.
      86             :  * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
      87             :  * friends to figure out if Fetch ends up copying big blobs to see if this is
      88             :  * worth optimizing.
      89             :  */
      90           0 : class MOZ_STACK_CLASS FormDataParser
      91             : {
      92             : private:
      93             :   RefPtr<FormData> mFormData;
      94             :   nsCString mMimeType;
      95             :   nsCString mData;
      96             : 
      97             :   // Entry state, reset in START_PART.
      98             :   nsCString mName;
      99             :   nsCString mFilename;
     100             :   nsCString mContentType;
     101             : 
     102             :   enum
     103             :   {
     104             :     START_PART,
     105             :     PARSE_HEADER,
     106             :     PARSE_BODY,
     107             :   } mState;
     108             : 
     109             :   nsIGlobalObject* mParentObject;
     110             : 
     111             :   // Reads over a boundary and sets start to the position after the end of the
     112             :   // boundary. Returns false if no boundary is found immediately.
     113             :   bool
     114           0 :   PushOverBoundary(const nsACString& aBoundaryString,
     115             :                    nsACString::const_iterator& aStart,
     116             :                    nsACString::const_iterator& aEnd)
     117             :   {
     118             :     // We copy the end iterator to keep the original pointing to the real end
     119             :     // of the string.
     120           0 :     nsACString::const_iterator end(aEnd);
     121           0 :     const char* beginning = aStart.get();
     122           0 :     if (FindInReadable(aBoundaryString, aStart, end)) {
     123             :       // We either should find the body immediately, or after 2 chars with the
     124             :       // 2 chars being '-', everything else is failure.
     125           0 :       if ((aStart.get() - beginning) == 0) {
     126           0 :         aStart.advance(aBoundaryString.Length());
     127             :         return true;
     128             :       }
     129             : 
     130           0 :       if ((aStart.get() - beginning) == 2) {
     131           0 :         if (*(--aStart) == '-' && *(--aStart) == '-') {
     132           0 :           aStart.advance(aBoundaryString.Length() + 2);
     133             :           return true;
     134             :         }
     135             :       }
     136             :     }
     137             : 
     138             :     return false;
     139             :   }
     140             : 
     141             :   bool
     142           0 :   ParseHeader(nsACString::const_iterator& aStart,
     143             :               nsACString::const_iterator& aEnd,
     144             :               bool* aWasEmptyHeader)
     145             :   {
     146           0 :     nsAutoCString headerName, headerValue;
     147           0 :     if (!FetchUtil::ExtractHeader(aStart, aEnd,
     148             :                                   headerName, headerValue,
     149             :                                   aWasEmptyHeader)) {
     150             :       return false;
     151             :     }
     152           0 :     if (*aWasEmptyHeader) {
     153             :       return true;
     154             :     }
     155             : 
     156           0 :     if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
     157           0 :       nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
     158           0 :       bool seenFormData = false;
     159           0 :       while (tokenizer.hasMoreTokens()) {
     160           0 :         const nsDependentCSubstring& token = tokenizer.nextToken();
     161           0 :         if (token.IsEmpty()) {
     162             :           continue;
     163             :         }
     164             : 
     165           0 :         if (token.EqualsLiteral("form-data")) {
     166             :           seenFormData = true;
     167             :           continue;
     168             :         }
     169             : 
     170           0 :         if (seenFormData &&
     171           0 :             StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
     172           0 :           mName = StringTail(token, token.Length() - 5);
     173           0 :           mName.Trim(" \"");
     174           0 :           continue;
     175             :         }
     176             : 
     177           0 :         if (seenFormData &&
     178           0 :             StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
     179           0 :           mFilename = StringTail(token, token.Length() - 9);
     180           0 :           mFilename.Trim(" \"");
     181           0 :           continue;
     182             :         }
     183             :       }
     184             : 
     185           0 :       if (mName.IsVoid()) {
     186             :         // Could not parse a valid entry name.
     187           0 :         return false;
     188             :       }
     189           0 :     } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
     190           0 :       mContentType = headerValue;
     191             :     }
     192             : 
     193             :     return true;
     194             :   }
     195             : 
     196             :   // The end of a body is marked by a CRLF followed by the boundary. So the
     197             :   // CRLF is part of the boundary and not the body, but any prior CRLFs are
     198             :   // part of the body. This will position the iterator at the beginning of the
     199             :   // boundary (after the CRLF).
     200             :   bool
     201           0 :   ParseBody(const nsACString& aBoundaryString,
     202             :             nsACString::const_iterator& aStart,
     203             :             nsACString::const_iterator& aEnd)
     204             :   {
     205           0 :     const char* beginning = aStart.get();
     206             : 
     207             :     // Find the boundary marking the end of the body.
     208           0 :     nsACString::const_iterator end(aEnd);
     209           0 :     if (!FindInReadable(aBoundaryString, aStart, end)) {
     210             :       return false;
     211             :     }
     212             : 
     213             :     // We found a boundary, strip the just prior CRLF, and consider
     214             :     // everything else the body section.
     215           0 :     if (aStart.get() - beginning < 2) {
     216             :       // Only the first entry can have a boundary right at the beginning. Even
     217             :       // an empty body will have a CRLF before the boundary. So this is
     218             :       // a failure.
     219             :       return false;
     220             :     }
     221             : 
     222             :     // Check that there is a CRLF right before the boundary.
     223           0 :     aStart.advance(-2);
     224             : 
     225             :     // Skip optional hyphens.
     226           0 :     if (*aStart == '-' && *(aStart.get()+1) == '-') {
     227           0 :       if (aStart.get() - beginning < 2) {
     228             :         return false;
     229             :       }
     230             : 
     231           0 :       aStart.advance(-2);
     232             :     }
     233             : 
     234           0 :     if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
     235             :       return false;
     236             :     }
     237             : 
     238           0 :     nsAutoCString body(beginning, aStart.get() - beginning);
     239             : 
     240             :     // Restore iterator to after the \r\n as we promised.
     241             :     // We do not need to handle the extra hyphens case since our boundary
     242             :     // parser in PushOverBoundary()
     243           0 :     aStart.advance(2);
     244             : 
     245           0 :     if (!mFormData) {
     246           0 :       mFormData = new FormData();
     247             :     }
     248             : 
     249           0 :     NS_ConvertUTF8toUTF16 name(mName);
     250             : 
     251           0 :     if (mFilename.IsVoid()) {
     252           0 :       ErrorResult rv;
     253           0 :       mFormData->Append(name, NS_ConvertUTF8toUTF16(body), rv);
     254           0 :       MOZ_ASSERT(!rv.Failed());
     255             :     } else {
     256             :       // Unfortunately we've to copy the data first since all our strings are
     257             :       // going to free it. We also need fallible alloc, so we can't just use
     258             :       // ToNewCString().
     259           0 :       char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
     260           0 :       if (!copy) {
     261           0 :         NS_WARNING("Failed to copy File entry body.");
     262           0 :         return false;
     263             :       }
     264           0 :       nsCString::const_iterator bodyIter, bodyEnd;
     265           0 :       body.BeginReading(bodyIter);
     266           0 :       body.EndReading(bodyEnd);
     267           0 :       char *p = copy;
     268           0 :       while (bodyIter != bodyEnd) {
     269           0 :         *p++ = *bodyIter++;
     270             :       }
     271           0 :       p = nullptr;
     272             : 
     273             :       RefPtr<Blob> file =
     274           0 :         File::CreateMemoryFile(mParentObject,
     275           0 :                                reinterpret_cast<void *>(copy), body.Length(),
     276           0 :                                NS_ConvertUTF8toUTF16(mFilename),
     277           0 :                                NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
     278           0 :       Optional<nsAString> dummy;
     279           0 :       ErrorResult rv;
     280           0 :       mFormData->Append(name, *file, dummy, rv);
     281           0 :       if (NS_WARN_IF(rv.Failed())) {
     282           0 :         rv.SuppressException();
     283           0 :         return false;
     284             :       }
     285             :     }
     286             : 
     287             :     return true;
     288             :   }
     289             : 
     290             : public:
     291           0 :   FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
     292           0 :     : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
     293             :   {
     294           0 :   }
     295             : 
     296             :   bool
     297           0 :   Parse()
     298             :   {
     299           0 :     if (mData.IsEmpty()) {
     300             :       return false;
     301             :     }
     302             : 
     303             :     // Determine boundary from mimetype.
     304           0 :     const char* boundaryId = nullptr;
     305           0 :     boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
     306           0 :     if (!boundaryId) {
     307             :       return false;
     308             :     }
     309             : 
     310           0 :     boundaryId = strchr(boundaryId, '=');
     311           0 :     if (!boundaryId) {
     312             :       return false;
     313             :     }
     314             : 
     315             :     // Skip over '='.
     316           0 :     boundaryId++;
     317             : 
     318           0 :     char *attrib = (char *) strchr(boundaryId, ';');
     319           0 :     if (attrib) *attrib = '\0';
     320             : 
     321           0 :     nsAutoCString boundaryString(boundaryId);
     322           0 :     if (attrib) *attrib = ';';
     323             : 
     324           0 :     boundaryString.Trim(" \"");
     325             : 
     326           0 :     if (boundaryString.Length() == 0) {
     327             :       return false;
     328             :     }
     329             : 
     330           0 :     nsACString::const_iterator start, end;
     331           0 :     mData.BeginReading(start);
     332             :     // This should ALWAYS point to the end of data.
     333             :     // Helpers make copies.
     334           0 :     mData.EndReading(end);
     335             : 
     336           0 :     while (start != end) {
     337           0 :       switch(mState) {
     338             :         case START_PART:
     339           0 :           mName.SetIsVoid(true);
     340           0 :           mFilename.SetIsVoid(true);
     341           0 :           mContentType = NS_LITERAL_CSTRING("text/plain");
     342             : 
     343             :           // MUST start with boundary.
     344           0 :           if (!PushOverBoundary(boundaryString, start, end)) {
     345           0 :             return false;
     346             :           }
     347             : 
     348           0 :           if (start != end && *start == '-') {
     349             :             // End of data.
     350           0 :             if (!mFormData) {
     351           0 :               mFormData = new FormData();
     352             :             }
     353             :             return true;
     354             :           }
     355             : 
     356           0 :           if (!PushOverLine(start, end)) {
     357             :             return false;
     358             :           }
     359           0 :           mState = PARSE_HEADER;
     360           0 :           break;
     361             : 
     362             :         case PARSE_HEADER:
     363             :           bool emptyHeader;
     364           0 :           if (!ParseHeader(start, end, &emptyHeader)) {
     365             :             return false;
     366             :           }
     367             : 
     368           0 :           if (emptyHeader && !PushOverLine(start, end)) {
     369             :             return false;
     370             :           }
     371             : 
     372           0 :           mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
     373           0 :           break;
     374             : 
     375             :         case PARSE_BODY:
     376           0 :           if (mName.IsVoid()) {
     377             :             NS_WARNING("No content-disposition header with a valid name was "
     378           0 :                        "found. Failing at body parse.");
     379           0 :             return false;
     380             :           }
     381             : 
     382           0 :           if (!ParseBody(boundaryString, start, end)) {
     383             :             return false;
     384             :           }
     385             : 
     386           0 :           mState = START_PART;
     387           0 :           break;
     388             : 
     389             :         default:
     390           0 :           MOZ_CRASH("Invalid case");
     391             :       }
     392             :     }
     393             : 
     394           0 :     MOZ_ASSERT_UNREACHABLE("Should never reach here.");
     395             :     return false;
     396             :   }
     397             : 
     398             :   already_AddRefed<FormData> GetFormData()
     399             :   {
     400           0 :     return mFormData.forget();
     401             :   }
     402             : };
     403             : }
     404             : 
     405             : // static
     406             : void
     407          10 : BodyUtil::ConsumeArrayBuffer(JSContext* aCx,
     408             :                               JS::MutableHandle<JSObject*> aValue,
     409             :                               uint32_t aInputLength, uint8_t* aInput,
     410             :                               ErrorResult& aRv)
     411             : {
     412           1 :   JS::Rooted<JSObject*> arrayBuffer(aCx);
     413           1 :   arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
     414           1 :     reinterpret_cast<void *>(aInput));
     415          10 :   if (!arrayBuffer) {
     416           0 :     JS_ClearPendingException(aCx);
     417           0 :     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     418           0 :     return;
     419             :   }
     420          10 :   aValue.set(arrayBuffer);
     421             : }
     422             : 
     423             : // static
     424             : already_AddRefed<Blob>
     425           0 : BodyUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
     426             :                        uint32_t aInputLength, uint8_t* aInput,
     427             :                        ErrorResult& aRv)
     428             : {
     429             :   RefPtr<Blob> blob =
     430           0 :     Blob::CreateMemoryBlob(aParent,
     431             :                            reinterpret_cast<void *>(aInput), aInputLength,
     432           0 :                            aMimeType);
     433             : 
     434           0 :   if (!blob) {
     435           0 :     aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
     436             :     return nullptr;
     437             :   }
     438             :   return blob.forget();
     439             : }
     440             : 
     441             : // static
     442             : already_AddRefed<FormData>
     443           0 : BodyUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
     444             :                            const nsCString& aStr, ErrorResult& aRv)
     445             : {
     446           0 :   NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
     447             : 
     448             :   // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
     449             :   // but disallow multipart/form-datafoobar.
     450           0 :   bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
     451             : 
     452           0 :   if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
     453           0 :     isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
     454             :   }
     455             : 
     456           0 :   if (isValidFormDataMimeType) {
     457           0 :     FormDataParser parser(aMimeType, aStr, aParent);
     458           0 :     if (!parser.Parse()) {
     459           0 :       aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
     460             :       return nullptr;
     461             :     }
     462             : 
     463           0 :     RefPtr<FormData> fd = parser.GetFormData();
     464           0 :     MOZ_ASSERT(fd);
     465           0 :     return fd.forget();
     466             :   }
     467             : 
     468           0 :   NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
     469           0 :   bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
     470             : 
     471           0 :   if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
     472           0 :     isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
     473             :   }
     474             : 
     475           0 :   if (isValidUrlEncodedMimeType) {
     476           0 :     RefPtr<FormData> fd = new FormData(aParent);
     477           0 :     FillFormIterator iterator(fd);
     478           0 :     DebugOnly<bool> status = URLParams::Parse(aStr, iterator);
     479           0 :     MOZ_ASSERT(status);
     480             : 
     481           0 :     return fd.forget();
     482             :   }
     483             : 
     484           0 :   aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
     485             :   return nullptr;
     486             : }
     487             : 
     488             : // static
     489             : nsresult
     490           0 : BodyUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
     491             :                        nsString& aText)
     492             : {
     493             :   nsresult rv =
     494           0 :     UTF_8_ENCODING->DecodeWithBOMRemoval(MakeSpan(aInput, aInputLength), aText);
     495           9 :   if (NS_FAILED(rv)) {
     496             :     return rv;
     497             :   }
     498           9 :   return NS_OK;
     499             : }
     500             : 
     501             : // static
     502             : void
     503           2 : BodyUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
     504             :                        const nsString& aStr, ErrorResult& aRv)
     505             : {
     506           0 :   aRv.MightThrowJSException();
     507             : 
     508           1 :   JS::Rooted<JS::Value> json(aCx);
     509           2 :   if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
     510           0 :     if (!JS_IsExceptionPending(aCx)) {
     511           0 :       aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
     512           0 :       return;
     513             :     }
     514             : 
     515           0 :     JS::Rooted<JS::Value> exn(aCx);
     516           0 :     DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
     517           0 :     MOZ_ASSERT(gotException);
     518             : 
     519           0 :     JS_ClearPendingException(aCx);
     520           0 :     aRv.ThrowJSException(aCx, exn);
     521             :     return;
     522             :   }
     523             : 
     524             :   aValue.set(json);
     525             : }
     526             : 
     527             : } // namespace dom
     528             : } // namespace mozilla

Generated by: LCOV version 1.13-14-ga5dd952