LCOV - code coverage report
Current view: top level - dom/base - BodyUtil.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 9 220 4.1 %
Date: 2017-07-14 16:53:18 Functions: 2 15 13.3 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13