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 #ifndef mozilla_dom_HTMLFormSubmission_h
8 #define mozilla_dom_HTMLFormSubmission_h
9 
10 #include "mozilla/Attributes.h"
11 #include "mozilla/dom/UserActivation.h"
12 #include "mozilla/dom/HTMLDialogElement.h"
13 #include "nsCOMPtr.h"
14 #include "mozilla/Encoding.h"
15 #include "nsString.h"
16 
17 class nsIURI;
18 class nsIInputStream;
19 class nsGenericHTMLElement;
20 class nsIMultiplexInputStream;
21 
22 namespace mozilla {
23 namespace dom {
24 
25 class Blob;
26 class DialogFormSubmission;
27 class Directory;
28 class Element;
29 class HTMLFormElement;
30 
31 /**
32  * Class for form submissions; encompasses the function to call to submit as
33  * well as the form submission name/value pairs
34  */
35 class HTMLFormSubmission {
36  public:
37   /**
38    * Get a submission object based on attributes in the form (ENCTYPE and
39    * METHOD)
40    *
41    * @param aForm the form to get a submission object based on
42    * @param aSubmitter the submitter element (can be null)
43    * @param aFormSubmission the form submission object (out param)
44    */
45   static nsresult GetFromForm(HTMLFormElement* aForm,
46                               nsGenericHTMLElement* aSubmitter,
47                               NotNull<const Encoding*>& aEncoding,
48                               HTMLFormSubmission** aFormSubmission);
49 
50   MOZ_COUNTED_DTOR_VIRTUAL(HTMLFormSubmission)
51 
52   /**
53    * Submit a name/value pair
54    *
55    * @param aName the name of the parameter
56    * @param aValue the value of the parameter
57    */
58   virtual nsresult AddNameValuePair(const nsAString& aName,
59                                     const nsAString& aValue) = 0;
60 
61   /**
62    * Submit a name/blob pair
63    *
64    * @param aName the name of the parameter
65    * @param aBlob the blob to submit. The file's name will be used if the Blob
66    * is actually a File, otherwise 'blob' string is used instead. Must not be
67    * null.
68    */
69   virtual nsresult AddNameBlobPair(const nsAString& aName, Blob* aBlob) = 0;
70 
71   /**
72    * Submit a name/directory pair
73    *
74    * @param aName the name of the parameter
75    * @param aBlob the directory to submit.
76    */
77   virtual nsresult AddNameDirectoryPair(const nsAString& aName,
78                                         Directory* aDirectory) = 0;
79 
80   /**
81    * Given a URI and the current submission, create the final URI and data
82    * stream that will be submitted.  Subclasses *must* implement this.
83    *
84    * @param aURI the URI being submitted to [IN]
85    * @param aPostDataStream a data stream for POST data [OUT]
86    * @param aOutURI the resulting URI. May be the same as aURI [OUT]
87    */
88   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
89                                         nsIInputStream** aPostDataStream,
90                                         nsCOMPtr<nsIURI>& aOutURI) = 0;
91 
92   /**
93    * Get the charset that will be used for submission.
94    */
GetCharset(nsACString & aCharset)95   void GetCharset(nsACString& aCharset) { mEncoding->Name(aCharset); }
96 
97   /**
98    * Get the action URI that will be used for submission.
99    */
GetActionURL()100   nsIURI* GetActionURL() const { return mActionURL; }
101 
102   /**
103    * Get the target that will be used for submission.
104    */
GetTarget(nsAString & aTarget)105   void GetTarget(nsAString& aTarget) { aTarget = mTarget; }
106 
107   /**
108    * Return true if this form submission was user-initiated.
109    */
IsInitiatedFromUserInput()110   bool IsInitiatedFromUserInput() const { return mInitiatedFromUserInput; }
111 
GetAsDialogSubmission()112   virtual DialogFormSubmission* GetAsDialogSubmission() { return nullptr; }
113 
114  protected:
115   /**
116    * Can only be constructed by subclasses.
117    *
118    * @param aEncoding the character encoding of the form
119    */
120   HTMLFormSubmission(nsIURI* aActionURL, const nsAString& aTarget,
121                      mozilla::NotNull<const mozilla::Encoding*> aEncoding);
122 
123   // The action url.
124   nsCOMPtr<nsIURI> mActionURL;
125 
126   // The target.
127   nsString mTarget;
128 
129   // The character encoding of this form submission
130   mozilla::NotNull<const mozilla::Encoding*> mEncoding;
131 
132   // Keep track of whether this form submission was user-initiated or not
133   bool mInitiatedFromUserInput;
134 };
135 
136 class EncodingFormSubmission : public HTMLFormSubmission {
137  public:
138   EncodingFormSubmission(nsIURI* aActionURL, const nsAString& aTarget,
139                          mozilla::NotNull<const mozilla::Encoding*> aEncoding,
140                          Element* aSubmitter);
141 
142   virtual ~EncodingFormSubmission();
143 
144   // Indicates the type of newline normalization and escaping to perform in
145   // `EncodeVal`, in addition to encoding the string into bytes.
146   enum EncodeType {
147     // Normalizes newlines to CRLF and then escapes for use in
148     // `Content-Disposition`. (Useful for `multipart/form-data` entry names.)
149     eNameEncode,
150     // Escapes for use in `Content-Disposition`. (Useful for
151     // `multipart/form-data` filenames.)
152     eFilenameEncode,
153     // Normalizes newlines to CRLF.
154     eValueEncode,
155   };
156 
157   /**
158    * Encode a Unicode string to bytes, additionally performing escapes or
159    * normalizations.
160    * @param aStr the string to encode
161    * @param aOut the encoded string [OUT]
162    * @param aEncodeType The type of escapes or normalizations to perform on the
163    *                    encoded string.
164    * @throws an error if UnicodeToNewBytes fails
165    */
166   nsresult EncodeVal(const nsAString& aStr, nsCString& aOut,
167                      EncodeType aEncodeType);
168 };
169 
170 class DialogFormSubmission final : public HTMLFormSubmission {
171  public:
DialogFormSubmission(nsAString & aResult,nsIURI * aActionURL,const nsAString & aTarget,NotNull<const Encoding * > aEncoding,HTMLDialogElement * aDialogElement)172   DialogFormSubmission(nsAString& aResult, nsIURI* aActionURL,
173                        const nsAString& aTarget,
174                        NotNull<const Encoding*> aEncoding,
175                        HTMLDialogElement* aDialogElement)
176       : HTMLFormSubmission(aActionURL, aTarget, aEncoding),
177         mDialogElement(aDialogElement),
178         mReturnValue(aResult) {}
AddNameValuePair(const nsAString & aName,const nsAString & aValue)179   nsresult AddNameValuePair(const nsAString& aName,
180                             const nsAString& aValue) override {
181     MOZ_CRASH("This method should not be called");
182     return NS_OK;
183   }
184 
AddNameBlobPair(const nsAString & aName,Blob * aBlob)185   nsresult AddNameBlobPair(const nsAString& aName, Blob* aBlob) override {
186     MOZ_CRASH("This method should not be called");
187     return NS_OK;
188   }
189 
AddNameDirectoryPair(const nsAString & aName,Directory * aDirectory)190   nsresult AddNameDirectoryPair(const nsAString& aName,
191                                 Directory* aDirectory) override {
192     MOZ_CRASH("This method should not be called");
193     return NS_OK;
194   }
195 
GetEncodedSubmission(nsIURI * aURI,nsIInputStream ** aPostDataStream,nsCOMPtr<nsIURI> & aOutURI)196   nsresult GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream,
197                                 nsCOMPtr<nsIURI>& aOutURI) override {
198     MOZ_CRASH("This method should not be called");
199     return NS_OK;
200   }
201 
GetAsDialogSubmission()202   DialogFormSubmission* GetAsDialogSubmission() override { return this; }
203 
DialogElement()204   HTMLDialogElement* DialogElement() { return mDialogElement; }
205 
ReturnValue()206   nsString& ReturnValue() { return mReturnValue; }
207 
208  private:
209   const RefPtr<HTMLDialogElement> mDialogElement;
210   nsString mReturnValue;
211 };
212 
213 /**
214  * Handle multipart/form-data encoding, which does files as well as normal
215  * inputs.  This always does POST.
216  */
217 class FSMultipartFormData : public EncodingFormSubmission {
218  public:
219   /**
220    * @param aEncoding the character encoding of the form
221    */
222   FSMultipartFormData(nsIURI* aActionURL, const nsAString& aTarget,
223                       mozilla::NotNull<const mozilla::Encoding*> aEncoding,
224                       Element* aSubmitter);
225   ~FSMultipartFormData();
226 
227   virtual nsresult AddNameValuePair(const nsAString& aName,
228                                     const nsAString& aValue) override;
229 
230   virtual nsresult AddNameBlobPair(const nsAString& aName,
231                                    Blob* aBlob) override;
232 
233   virtual nsresult AddNameDirectoryPair(const nsAString& aName,
234                                         Directory* aDirectory) override;
235 
236   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
237                                         nsIInputStream** aPostDataStream,
238                                         nsCOMPtr<nsIURI>& aOutURI) override;
239 
GetContentType(nsACString & aContentType)240   void GetContentType(nsACString& aContentType) {
241     aContentType = "multipart/form-data; boundary="_ns + mBoundary;
242   }
243 
244   nsIInputStream* GetSubmissionBody(uint64_t* aContentLength);
245 
246  protected:
247   /**
248    * Roll up the data we have so far and add it to the multiplexed data stream.
249    */
250   nsresult AddPostDataStream();
251 
252  private:
253   void AddDataChunk(const nsACString& aName, const nsACString& aFilename,
254                     const nsACString& aContentType,
255                     nsIInputStream* aInputStream, uint64_t aInputStreamSize);
256   /**
257    * The post data stream as it is so far.  This is a collection of smaller
258    * chunks--string streams and file streams interleaved to make one big POST
259    * stream.
260    */
261   nsCOMPtr<nsIMultiplexInputStream> mPostData;
262 
263   /**
264    * The same stream, but as an nsIInputStream.
265    * Raw pointers because it is just QI of mInputStream.
266    */
267   nsIInputStream* mPostDataStream;
268 
269   /**
270    * The current string chunk.  When a file is hit, the string chunk gets
271    * wrapped up into an input stream and put into mPostDataStream so that the
272    * file input stream can then be appended and everything is in the right
273    * order.  Then the string chunk gets appended to again as we process more
274    * name/value pairs.
275    */
276   nsCString mPostDataChunk;
277 
278   /**
279    * The boundary string to use after each "part" (the boundary that marks the
280    * end of a value).  This is computed randomly and is different for each
281    * submission.
282    */
283   nsCString mBoundary;
284 
285   /**
286    * The total length in bytes of the streams that make up mPostDataStream
287    */
288   uint64_t mTotalLength;
289 };
290 
291 }  // namespace dom
292 }  // namespace mozilla
293 
294 #endif /* mozilla_dom_HTMLFormSubmission_h */
295