/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsIFormSubmission.h" #include "nsIPresContext.h" #include "nsCOMPtr.h" #include "nsIForm.h" #include "nsILinkHandler.h" #include "nsIDocument.h" #include "nsHTMLAtoms.h" #include "nsIHTMLDocument.h" #include "nsIFormControl.h" #include "nsIScriptGlobalObject.h" #include "nsIDOMHTMLFormElement.h" #include "nsDOMError.h" #include "nsHTMLValue.h" #include "nsGenericElement.h" #include "nsISaveAsCharset.h" // JBK added for submit move from content frame #include "nsIFile.h" #include "nsDirectoryServiceDefs.h" #include "nsIFormProcessor.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsLinebreakConverter.h" #include "nsICharsetConverterManager.h" #include "nsICharsetAlias.h" #include "nsEscape.h" #include "nsUnicharUtils.h" #include "nsIMultiplexInputStream.h" #include "nsIMIMEInputStream.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" #include "nsIStringBundle.h" //BIDI #include "nsBidiUtils.h" //end static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID); static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); /** * Helper superclass implementation of nsIFormSubmission, providing common * methods that most of the specific implementations need and use. */ class nsFormSubmission : public nsIFormSubmission { public: /** * @param aCharset the charset of the form as a string * @param aEncoder an encoder that will encode Unicode names and values into * bytes to be sent over the wire (usually a charset transformation) * @param aFormProcessor a form processor who can listen to * @param aBidiOptions the BIDI options flags for the current pres context */ nsFormSubmission(const nsACString& aCharset, nsISaveAsCharset* aEncoder, nsIFormProcessor* aFormProcessor, PRInt32 aBidiOptions) : mCharset(aCharset), mEncoder(aEncoder), mFormProcessor(aFormProcessor), mBidiOptions(aBidiOptions) { }; virtual ~nsFormSubmission() { }; NS_DECL_ISUPPORTS // // nsIFormSubmission // virtual nsresult SubmitTo(nsIURI* aActionURI, const nsAString& aTarget, nsIContent* aSource, nsIPresContext* aPresContext, nsIDocShell** aDocShell, nsIRequest** aRequest); /** * Called to initialize the submission. Perform any initialization that may * fail here. Subclasses *must* implement this. */ NS_IMETHOD Init() = 0; protected: // this is essentially the nsFormSubmission interface (to be overridden) /** * Given a URI and the current submission, create the final URI and data * stream that will be submitted. Subclasses *must* implement this. * * @param aURI the URI being submitted to [INOUT] * @param aPostDataStream a data stream for POST data [OUT] */ NS_IMETHOD GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) = 0; // Helpers /** * Call to have the form processor listen in when a name/value pair is found * to be submitted. * * @param aSource the HTML element the name/value is associated with * @param aName the name that will be submitted * @param aValue the value that will be submitted * @return the new value (or null if the value was not changed) */ nsString* ProcessValue(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue); // Encoding Helpers /** * Encode a Unicode string to bytes using the encoder (or just do ToNewCString * if there is no encoder). * @param aStr the string to encode * @return a char* pointing to the encoded bytes (use nsMemory::Free to free) */ char* EncodeVal(const nsAString& aStr); /** * Encode a Unicode string to bytes using an encoder. (Used by EncodeVal) * @param aStr the string to encode * @param aLen the length of aStr * @param aEncoder the encoder to encode the bytes with (cannot be null) * @return a char* pointing to the encoded bytes (use nsMemory::Free to free) */ char* UnicodeToNewBytes(const PRUnichar* aStr, PRUint32 aLen, nsISaveAsCharset* aEncoder); /** The name of the encoder charset */ nsCString mCharset; /** The encoder that will encode Unicode names and values into * bytes to be sent over the wire (usually a charset transformation) */ nsCOMPtr mEncoder; /** A form processor who can listen to values */ nsCOMPtr mFormProcessor; /** The BIDI options flags for the current pres context */ PRInt32 mBidiOptions; public: // Static helpers /** * Get the submit charset for a form (suitable to pass in to the constructor). * @param aForm the form in question * @param aCtrlsModAtSubmit BIDI controls text mode. Unused in non-BIDI * builds. * @param aCharset the returned charset [OUT] */ static void GetSubmitCharset(nsIHTMLContent* aForm, PRUint8 aCtrlsModAtSubmit, nsACString& aCharset); /** * Get the encoder for a form (suitable to pass in to the constructor). * @param aForm the form in question * @param aPresContext the pres context in which we are submitting * @param aCharset the charset of the form * @param aEncoder the returned encoder [OUT] */ static nsresult GetEncoder(nsIHTMLContent* aForm, nsIPresContext* aPresContext, const nsACString& aCharset, nsISaveAsCharset** aEncoder); /** * Get an attribute of a form as int, provided that it is an enumerated value. * @param aForm the form in question * @param aAtom the attribute (for example, nsHTMLAtoms::enctype) to get * @param aValue the result (will not be set at all if the attribute does not * exist on the form, so *make sure you provide a default value*.) * [OUT] */ static void GetEnumAttr(nsIHTMLContent* aForm, nsIAtom* aAtom, PRInt32* aValue); }; // // Static helper methods that don't really have nothing to do with nsFormSub // /** * Send a warning to the JS console * @param aContent the content the warning is about * @param aWarningName the internationalized name of the warning within * layout/html/forms/src/HtmlProperties.js */ static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName); /** * Send a warning to the JS console * @param aContent the content the warning is about * @param aWarningName the internationalized name of the warning within * layout/html/forms/src/HtmlProperties.js * @param aWarningArg1 an argument to replace a %S in the warning */ static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName, const nsAFlatString& aWarningArg1); /** * Send a warning to the JS console * @param aContent the content the warning is about * @param aWarningName the internationalized name of the warning within * layout/html/forms/src/HtmlProperties.js * @param aWarningArgs an array of strings to replace %S's in the warning * @param aWarningArgsLen the number of strings in the array */ static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName, const PRUnichar** aWarningArgs, PRUint32 aWarningArgsLen); class nsFSURLEncoded : public nsFormSubmission { public: /** * @param aCharset the charset of the form as a string * @param aEncoder an encoder that will encode Unicode names and values into * bytes to be sent over the wire (usually a charset transformation) * @param aFormProcessor a form processor who can listen to * @param aBidiOptions the BIDI options flags for the current pres context * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or * NS_FORM_METHOD_POST). */ nsFSURLEncoded(const nsACString& aCharset, nsISaveAsCharset* aEncoder, nsIFormProcessor* aFormProcessor, PRInt32 aBidiOptions, PRInt32 aMethod) : nsFormSubmission(aCharset, aEncoder, aFormProcessor, aBidiOptions), mMethod(aMethod) { } virtual ~nsFSURLEncoded() { } NS_DECL_ISUPPORTS_INHERITED // nsIFormSubmission virtual nsresult AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue); virtual nsresult AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome); virtual PRBool AcceptsFiles() const { return PR_FALSE; } NS_IMETHOD Init(); protected: // nsFormSubmission NS_IMETHOD GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream); // Helpers /** * URL encode a Unicode string by encoding it to bytes, converting linebreaks * properly, and then escaping many bytes as %xx. * * @param aStr the string to encode * @param aEncoded the encoded string [OUT] * @throws NS_ERROR_OUT_OF_MEMORY if we run out of memory */ nsresult URLEncode(const nsAString& aStr, nsCString& aEncoded); private: /** * The method of the submit (either NS_FORM_METHOD_GET or * NS_FORM_METHOD_POST). */ PRInt32 mMethod; /** The query string so far (the part after the ?) */ nsCString mQueryString; /** Whether or not we have warned about a file control not being submitted */ PRBool mWarnedFileControl; }; NS_IMPL_RELEASE_INHERITED(nsFSURLEncoded, nsFormSubmission) NS_IMPL_ADDREF_INHERITED(nsFSURLEncoded, nsFormSubmission) NS_IMPL_QUERY_INTERFACE_INHERITED0(nsFSURLEncoded, nsFormSubmission) nsresult nsFSURLEncoded::AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue) { // // Check if there is an input type=file so that we can warn // if (!mWarnedFileControl) { nsCOMPtr formControl = do_QueryInterface(aSource); if (formControl->GetType() == NS_FORM_INPUT_FILE) { nsCOMPtr content = do_QueryInterface(aSource); SendJSWarning(content, NS_LITERAL_STRING("ForgotFileEnctypeWarning")); mWarnedFileControl = PR_TRUE; } } // // Let external code process (and possibly change) value // nsString* processedValue = ProcessValue(aSource, aName, aValue); // // Encode name // nsCString convName; nsresult rv = URLEncode(aName, convName); NS_ENSURE_SUCCESS(rv, rv); // // Encode value // nsCString convValue; if (processedValue) { rv = URLEncode(*processedValue, convValue); } else { rv = URLEncode(aValue, convValue); } NS_ENSURE_SUCCESS(rv, rv); // // Append data to string // if (mQueryString.IsEmpty()) { mQueryString += convName + NS_LITERAL_CSTRING("=") + convValue; } else { mQueryString += NS_LITERAL_CSTRING("&") + convName + NS_LITERAL_CSTRING("=") + convValue; } delete processedValue; return NS_OK; } nsresult nsFSURLEncoded::AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome) { return AddNameValuePair(aSource, aName, aFilename); } // // nsFormSubmission // NS_IMETHODIMP nsFSURLEncoded::Init() { mQueryString.Truncate(); mWarnedFileControl = PR_FALSE; return NS_OK; } static void HandleMailtoSubject(nsCString& aPath) { // Walk through the string and see if we have a subject already. PRBool hasSubject = PR_FALSE; PRBool hasParams = PR_FALSE; PRInt32 paramSep = aPath.FindChar('?'); while (paramSep != kNotFound && paramSep < (PRInt32)aPath.Length()) { hasParams = PR_TRUE; // Get the end of the name at the = op. If it is *after* the next &, // assume that someone made a parameter without an = in it PRInt32 nameEnd = aPath.FindChar('=', paramSep+1); PRInt32 nextParamSep = aPath.FindChar('&', paramSep+1); if (nextParamSep == kNotFound) { nextParamSep = aPath.Length(); } // If the = op is after the &, this parameter is a name without value. // If there is no = op, same thing. if (nameEnd == kNotFound || nextParamSep < nameEnd) { nameEnd = nextParamSep; } if (nameEnd != kNotFound) { if (Substring(aPath, paramSep+1, nameEnd-(paramSep+1)) == NS_LITERAL_CSTRING("subject")) { hasSubject = PR_TRUE; break; } } paramSep = nextParamSep; } // If there is no subject, append a preformed subject to the mailto line if (!hasSubject) { if (hasParams) { aPath.Append('&'); } else { aPath.Append('?'); } aPath += NS_LITERAL_CSTRING("subject=Form%20Post%20From%20Mozilla&"); } } NS_IMETHODIMP nsFSURLEncoded::GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) { nsresult rv = NS_OK; *aPostDataStream = nsnull; if (mMethod == NS_FORM_METHOD_POST) { PRBool isMailto = PR_FALSE; aURI->SchemeIs("mailto", &isMailto); if (isMailto) { nsCAutoString path; rv = aURI->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); HandleMailtoSubject(path); // Append the body to and force-plain-text args to the mailto line nsCString escapedBody; escapedBody.Adopt(nsEscape(mQueryString.get(), url_XAlphas)); path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody; rv = aURI->SetPath(path); } else { nsCOMPtr dataStream; // XXX We *really* need to either get the string to disown its data (and // not destroy it), or make a string input stream that owns the CString // that is passed to it. Right now this operation does a copy. rv = NS_NewCStringInputStream(getter_AddRefs(dataStream), mQueryString); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr mimeStream( do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); #ifdef SPECIFY_CHARSET_IN_CONTENT_TYPE mimeStream->AddHeader("Content-Type", PromiseFlatString( "application/x-www-form-urlencoded; charset=" + mCharset ).get()); #else mimeStream->AddHeader("Content-Type", "application/x-www-form-urlencoded"); #endif mimeStream->SetAddContentLength(PR_TRUE); mimeStream->SetData(dataStream); *aPostDataStream = mimeStream; NS_ADDREF(*aPostDataStream); } } else { // // Get the full query string // PRBool schemeIsJavaScript; rv = aURI->SchemeIs("javascript", &schemeIsJavaScript); NS_ENSURE_SUCCESS(rv, rv); if (schemeIsJavaScript) { return NS_OK; } nsCAutoString path; rv = aURI->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); // Bug 42616: Trim off named anchor and save it to add later PRInt32 namedAnchorPos = path.FindChar('#'); nsCAutoString namedAnchor; if (kNotFound != namedAnchorPos) { path.Right(namedAnchor, (path.Length() - namedAnchorPos)); path.Truncate(namedAnchorPos); } // Chop off old query string (bug 25330, 57333) // Only do this for GET not POST (bug 41585) PRInt32 queryStart = path.FindChar('?'); if (kNotFound != queryStart) { path.Truncate(queryStart); } path.Append('?'); // Bug 42616: Add named anchor to end after query string path.Append(mQueryString + namedAnchor); aURI->SetPath(path); } return rv; } // i18n helper routines nsresult nsFSURLEncoded::URLEncode(const nsAString& aStr, nsCString& aEncoded) { char* inBuf = EncodeVal(aStr); if (!inBuf) inBuf = ToNewCString(aStr); NS_ENSURE_TRUE(inBuf, NS_ERROR_OUT_OF_MEMORY); // convert to CRLF breaks char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(inBuf, nsLinebreakConverter::eLinebreakAny, nsLinebreakConverter::eLinebreakNet); nsMemory::Free(inBuf); char* escapedBuf = nsEscape(convertedBuf, url_XPAlphas); nsMemory::Free(convertedBuf); aEncoded.Adopt(escapedBuf); return NS_OK; } /** * Handle multipart/form-data encoding, which does files as well as normal * inputs. This always does POST. */ class nsFSMultipartFormData : public nsFormSubmission { public: /** * @param aCharset the charset of the form as a string * @param aEncoder an encoder that will encode Unicode names and values into * bytes to be sent over the wire (usually a charset transformation) * @param aFormProcessor a form processor who can listen to * @param aBidiOptions the BIDI options flags for the current pres context */ nsFSMultipartFormData(const nsACString& aCharset, nsISaveAsCharset* aEncoder, nsIFormProcessor* aFormProcessor, PRInt32 aBidiOptions); virtual ~nsFSMultipartFormData() { } NS_DECL_ISUPPORTS_INHERITED // nsIFormSubmission virtual nsresult AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue); virtual nsresult AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome); virtual PRBool AcceptsFiles() const { return PR_TRUE; } NS_IMETHOD Init(); protected: // nsFormSubmission NS_IMETHOD GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream); // Helpers /** * Roll up the data we have so far and add it to the multiplexed data stream. */ nsresult AddPostDataStream(); /** * Call ProcessValue() and EncodeVal() on name and value. * * @param aSource the source of the name/value pair * @param aName the name to be sent * @param aValue the value to be sent * @param aProcessedName the name, after being encoded [OUT] * @param aProcessedValue the value, after being processed / encoded [OUT] * @throws NS_ERROR_OUT_OF_MEMORY if out of memory */ nsresult ProcessAndEncode(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue, nsCString& aProcessedName, nsCString& aProcessedValue); private: /** * Get whether we are supposed to be doing backwards compatible submit, which * causes us to leave off the mandatory Content-Transfer-Encoding header. * This used to cause Bad Things, including server crashes. * * It is hoped that we can get rid of this at some point, but that will take * a lot of testing or some other browsers that send the header and have not * had problems. */ PRBool mBackwardsCompatibleSubmit; /** * The post data stream as it is so far. This is a collection of smaller * chunks--string streams and file streams interleaved to make one big POST * stream. */ nsCOMPtr mPostDataStream; /** * The current string chunk. When a file is hit, the string chunk gets * wrapped up into an input stream and put into mPostDataStream so that the * file input stream can then be appended and everything is in the right * order. Then the string chunk gets appended to again as we process more * name/value pairs. */ nsCString mPostDataChunk; /** * The boundary string to use after each "part" (the boundary that marks the * end of a value). This is computed randomly and is different for each * submission. */ nsCString mBoundary; }; NS_IMPL_RELEASE_INHERITED(nsFSMultipartFormData, nsFormSubmission) NS_IMPL_ADDREF_INHERITED(nsFSMultipartFormData, nsFormSubmission) NS_IMPL_QUERY_INTERFACE_INHERITED0(nsFSMultipartFormData, nsFormSubmission) // // Constructor // nsFSMultipartFormData::nsFSMultipartFormData(const nsACString& aCharset, nsISaveAsCharset* aEncoder, nsIFormProcessor* aFormProcessor, PRInt32 aBidiOptions) : nsFormSubmission(aCharset, aEncoder, aFormProcessor, aBidiOptions) { // XXX I can't *believe* we have a pref for this. ifdef, anyone? mBackwardsCompatibleSubmit = PR_FALSE; nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) { prefBranch->GetBoolPref("browser.forms.submit.backwards_compatible", &mBackwardsCompatibleSubmit); } } nsresult nsFSMultipartFormData::ProcessAndEncode(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue, nsCString& aProcessedName, nsCString& aProcessedValue) { // // Let external code process (and possibly change) value // nsString* processedValue = ProcessValue(aSource, aName, aValue); // // Get name // char * encodedVal = EncodeVal(aName); if (!encodedVal) { delete processedValue; return NS_ERROR_OUT_OF_MEMORY; } aProcessedName.Adopt(encodedVal); // // Get value // if (processedValue) { encodedVal = EncodeVal(*processedValue); delete processedValue; } else { encodedVal = EncodeVal(aValue); } if (!encodedVal) { return NS_ERROR_OUT_OF_MEMORY; } aProcessedValue.Adopt(encodedVal); // // Convert linebreaks in value // aProcessedValue.Adopt(nsLinebreakConverter::ConvertLineBreaks(aProcessedValue.get(), nsLinebreakConverter::eLinebreakAny, nsLinebreakConverter::eLinebreakNet)); return NS_OK; } // // nsIFormSubmission // nsresult nsFSMultipartFormData::AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue) { nsCString nameStr; nsCString valueStr; nsresult rv = ProcessAndEncode(aSource, aName, aValue, nameStr, valueStr); NS_ENSURE_SUCCESS(rv, rv); // // Make MIME block for name/value pair // mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF) + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF) + valueStr + NS_LITERAL_CSTRING(CRLF); return NS_OK; } nsresult nsFSMultipartFormData::AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome) { nsCString nameStr; nsCString filenameStr; nsresult rv = ProcessAndEncode(aSource, aName, aFilename, nameStr, filenameStr); NS_ENSURE_SUCCESS(rv, rv); // // Make MIME block for name/value pair // // more appropriate than always using binary? mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF); if (!mBackwardsCompatibleSubmit) { // XXX Is there any way to tell when "8bit" or "7bit" etc may be mPostDataChunk += NS_LITERAL_CSTRING("Content-Transfer-Encoding: binary" CRLF); } mPostDataChunk += NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + nameStr + NS_LITERAL_CSTRING("\"; filename=\"") + filenameStr + NS_LITERAL_CSTRING("\"" CRLF) + NS_LITERAL_CSTRING("Content-Type: ") + aContentType + NS_LITERAL_CSTRING(CRLF CRLF); // // Add the file to the stream // if (aStream) { // We need to dump the data up to this point into the POST data stream here, // since we're about to add the file input stream AddPostDataStream(); mPostDataStream->AppendStream(aStream); } // // CRLF after file // mPostDataChunk += NS_LITERAL_CSTRING(CRLF); return NS_OK; } // // nsFormSubmission // NS_IMETHODIMP nsFSMultipartFormData::Init() { nsresult rv; // // Create the POST stream // mPostDataStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv); NS_ENSURE_SUCCESS(rv, rv); if (!mPostDataStream) { return NS_ERROR_OUT_OF_MEMORY; } // // Build boundary // mBoundary = NS_LITERAL_CSTRING("---------------------------"); mBoundary.AppendInt(rand()); mBoundary.AppendInt(rand()); mBoundary.AppendInt(rand()); return NS_OK; } NS_IMETHODIMP nsFSMultipartFormData::GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) { nsresult rv; // // Finish data // mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING("--" CRLF); // // Add final data input stream // AddPostDataStream(); // // Make header // nsCOMPtr mimeStream = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCAutoString boundaryHeaderValue( NS_LITERAL_CSTRING("multipart/form-data; boundary=") + mBoundary); mimeStream->AddHeader("Content-Type", boundaryHeaderValue.get()); mimeStream->SetAddContentLength(PR_TRUE); mimeStream->SetData(mPostDataStream); *aPostDataStream = mimeStream; NS_ADDREF(*aPostDataStream); return NS_OK; } nsresult nsFSMultipartFormData::AddPostDataStream() { nsresult rv = NS_OK; nsCOMPtr postDataChunkStream; rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream), mPostDataChunk); NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!"); if (postDataChunkStream) { mPostDataStream->AppendStream(postDataChunkStream); } mPostDataChunk.Truncate(); return rv; } // // CLASS nsFSTextPlain // class nsFSTextPlain : public nsFormSubmission { public: nsFSTextPlain(const nsACString& aCharset, nsISaveAsCharset* aEncoder, nsIFormProcessor* aFormProcessor, PRInt32 aBidiOptions) : nsFormSubmission(aCharset, aEncoder, aFormProcessor, aBidiOptions) { } virtual ~nsFSTextPlain() { } NS_DECL_ISUPPORTS_INHERITED // nsIFormSubmission virtual nsresult AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue); virtual nsresult AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome); NS_IMETHOD Init(); protected: // nsFormSubmission NS_IMETHOD GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream); virtual PRBool AcceptsFiles() const { return PR_FALSE; } private: nsString mBody; }; NS_IMPL_RELEASE_INHERITED(nsFSTextPlain, nsFormSubmission) NS_IMPL_ADDREF_INHERITED(nsFSTextPlain, nsFormSubmission) NS_IMPL_QUERY_INTERFACE_INHERITED0(nsFSTextPlain, nsFormSubmission) nsresult nsFSTextPlain::AddNameValuePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue) { // // Let external code process (and possibly change) value // nsString* processedValue = ProcessValue(aSource, aName, aValue); // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose // text/plain doesn't care about that. Parsers aren't built for escaped // values so we'll have to live with it. if (processedValue) { mBody.Append(aName + NS_LITERAL_STRING("=") + *processedValue + NS_LITERAL_STRING(CRLF)); delete processedValue; } else { mBody.Append(aName + NS_LITERAL_STRING("=") + aValue + NS_LITERAL_STRING(CRLF)); } return NS_OK; } nsresult nsFSTextPlain::AddNameFilePair(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aFilename, nsIInputStream* aStream, const nsACString& aContentType, PRBool aMoreFilesToCome) { AddNameValuePair(aSource,aName,aFilename); return NS_OK; } // // nsFormSubmission // NS_IMETHODIMP nsFSTextPlain::Init() { mBody.Truncate(); return NS_OK; } NS_IMETHODIMP nsFSTextPlain::GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) { nsresult rv = NS_OK; // XXX HACK We are using the standard URL mechanism to give the body to the // mailer instead of passing the post data stream to it, since that sounds // hard. PRBool isMailto = PR_FALSE; aURI->SchemeIs("mailto", &isMailto); if (isMailto) { nsCAutoString path; rv = aURI->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); HandleMailtoSubject(path); // Append the body to and force-plain-text args to the mailto line nsCString escapedBody; escapedBody.Adopt(nsEscape(NS_ConvertUTF16toUTF8(mBody).get(), url_XAlphas)); path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody; rv = aURI->SetPath(path); } else { // Create data stream nsCOMPtr bodyStream; rv = NS_NewStringInputStream(getter_AddRefs(bodyStream), mBody); if (!bodyStream) { return NS_ERROR_OUT_OF_MEMORY; } // Create mime stream with headers and such nsCOMPtr mimeStream = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv); NS_ENSURE_SUCCESS(rv, rv); mimeStream->AddHeader("Content-Type", "text/plain"); mimeStream->SetAddContentLength(PR_TRUE); mimeStream->SetData(bodyStream); CallQueryInterface(mimeStream, aPostDataStream); NS_ADDREF(*aPostDataStream); } return rv; } // // CLASS nsFormSubmission // // // nsISupports stuff // NS_IMPL_ADDREF(nsFormSubmission) NS_IMPL_RELEASE(nsFormSubmission) NS_INTERFACE_MAP_BEGIN(nsFormSubmission) NS_INTERFACE_MAP_ENTRY(nsIFormSubmission) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END // JBK moved from nsFormFrame - bug 34297 // submission static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName) { return SendJSWarning(aContent, aWarningName, nsnull, 0); } static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName, const nsAFlatString& aWarningArg1) { const PRUnichar* formatStrings[1] = { aWarningArg1.get() }; return SendJSWarning(aContent, aWarningName, formatStrings, 1); } static nsresult SendJSWarning(nsIHTMLContent* aContent, const nsAFlatString& aWarningName, const PRUnichar** aWarningArgs, PRUint32 aWarningArgsLen) { nsresult rv = NS_OK; // // Get the document URL to use as the filename // nsCAutoString documentURISpec; nsIDocument* document = aContent->GetDocument(); if (document) { nsIURI *documentURI = document->GetDocumentURI(); NS_ENSURE_TRUE(documentURI, NS_ERROR_UNEXPECTED); documentURI->GetPath(documentURISpec); } // // Get the error string // nsCOMPtr bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bundle; rv = bundleService->CreateBundle( "chrome://global/locale/layout/HtmlForm.properties", getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsXPIDLString warningStr; if (aWarningArgsLen > 0) { bundle->FormatStringFromName(aWarningName.get(), aWarningArgs, aWarningArgsLen, getter_Copies(warningStr)); } else { bundle->GetStringFromName(aWarningName.get(), getter_Copies(warningStr)); } // // Create the error // nsCOMPtr scriptError(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); NS_ENSURE_TRUE(scriptError, NS_ERROR_UNEXPECTED); rv = scriptError->Init(warningStr.get(), NS_ConvertUTF8toUTF16(documentURISpec).get(), nsnull, (uintN)0, 0, nsIScriptError::warningFlag, "HTML"); NS_ENSURE_SUCCESS(rv,rv); // // Send the error to the console // nsCOMPtr consoleService(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); NS_ENSURE_TRUE(consoleService, NS_ERROR_UNEXPECTED); return consoleService->LogMessage(scriptError); } nsresult GetSubmissionFromForm(nsIHTMLContent* aForm, nsIPresContext* aPresContext, nsIFormSubmission** aFormSubmission) { nsresult rv = NS_OK; // // Get all the information necessary to encode the form data // // Get BIDI options PRUint32 bidiOptions = 0; PRUint8 ctrlsModAtSubmit = 0; aPresContext->GetBidi(&bidiOptions); ctrlsModAtSubmit = GET_BIDI_OPTION_CONTROLSTEXTMODE(bidiOptions); // Get encoding type (default: urlencoded) PRInt32 enctype = NS_FORM_ENCTYPE_URLENCODED; nsFormSubmission::GetEnumAttr(aForm, nsHTMLAtoms::enctype, &enctype); // Get method (default: GET) PRInt32 method = NS_FORM_METHOD_GET; nsFormSubmission::GetEnumAttr(aForm, nsHTMLAtoms::method, &method); // Get charset nsCAutoString charset; nsFormSubmission::GetSubmitCharset(aForm, ctrlsModAtSubmit, charset); // Get unicode encoder nsCOMPtr encoder; nsFormSubmission::GetEncoder(aForm, aPresContext, charset, getter_AddRefs(encoder)); // Get form processor nsCOMPtr formProcessor = do_GetService(kFormProcessorCID, &rv); // // Choose encoder // // If enctype=multipart/form-data and method=post, do multipart // Else do URL encoded // NOTE: // The rule used to be, if enctype=multipart/form-data, do multipart // Else do URL encoded if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_MULTIPART) { *aFormSubmission = new nsFSMultipartFormData(charset, encoder, formProcessor, bidiOptions); } else if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { *aFormSubmission = new nsFSTextPlain(charset, encoder, formProcessor, bidiOptions); } else { if (enctype == NS_FORM_ENCTYPE_MULTIPART || enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { nsAutoString enctypeStr; aForm->GetAttr(kNameSpaceID_None, nsHTMLAtoms::enctype, enctypeStr); SendJSWarning(aForm, NS_LITERAL_STRING("ForgotPostWarning"), PromiseFlatString(enctypeStr)); } *aFormSubmission = new nsFSURLEncoded(charset, encoder, formProcessor, bidiOptions, method); } NS_ENSURE_TRUE(*aFormSubmission, NS_ERROR_OUT_OF_MEMORY); NS_ADDREF(*aFormSubmission); // This ASSUMES that all encodings above inherit from nsFormSubmission, which // they currently do. If that changes, change this too. NS_STATIC_CAST(nsFormSubmission*, *aFormSubmission)->Init(); return NS_OK; } nsresult nsFormSubmission::SubmitTo(nsIURI* aActionURI, const nsAString& aTarget, nsIContent* aSource, nsIPresContext* aPresContext, nsIDocShell** aDocShell, nsIRequest** aRequest) { nsresult rv; // // Finish encoding (get post data stream and URI) // nsCOMPtr postDataStream; rv = GetEncodedSubmission(aActionURI, getter_AddRefs(postDataStream)); NS_ENSURE_SUCCESS(rv, rv); // // Actually submit the data // nsILinkHandler *handler = aPresContext->GetLinkHandler(); NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE); return handler->OnLinkClickSync(aSource, eLinkVerb_Replace, aActionURI, PromiseFlatString(aTarget).get(), postDataStream, nsnull, aDocShell, aRequest); } // JBK moved from nsFormFrame - bug 34297 // static void nsFormSubmission::GetSubmitCharset(nsIHTMLContent* aForm, PRUint8 aCtrlsModAtSubmit, nsACString& oCharset) { oCharset = NS_LITERAL_CSTRING("UTF-8"); // default to utf-8 nsresult rv = NS_OK; nsAutoString acceptCharsetValue; nsHTMLValue value; rv = aForm->GetHTMLAttribute(nsHTMLAtoms::acceptcharset, value); if (rv == NS_CONTENT_ATTR_HAS_VALUE && value.GetUnit() == eHTMLUnit_String) { value.GetStringValue(acceptCharsetValue); } PRInt32 charsetLen = acceptCharsetValue.Length(); if (charsetLen > 0) { PRInt32 offset=0; PRInt32 spPos=0; // get charset from charsets one by one nsCOMPtr calias(do_GetService(kCharsetAliasCID, &rv)); if (NS_FAILED(rv)) { return; } if (calias) { do { spPos = acceptCharsetValue.FindChar(PRUnichar(' '), offset); PRInt32 cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset)); if (cnt > 0) { nsAutoString uCharset; acceptCharsetValue.Mid(uCharset, offset, cnt); nsCAutoString charset; charset.AssignWithConversion(uCharset); if (NS_SUCCEEDED(calias->GetPreferred(charset, oCharset))) return; } offset = spPos + 1; } while (spPos != -1); } } // if there are no accept-charset or all the charset are not supported // Get the charset from document nsIDocument* doc = aForm->GetDocument(); if (doc) { oCharset = doc->GetDocumentCharacterSet(); } if (aCtrlsModAtSubmit==IBMBIDI_CONTROLSTEXTMODE_VISUAL && oCharset.Equals(NS_LITERAL_CSTRING("windows-1256"), nsCaseInsensitiveCStringComparator())) { //Mohamed oCharset = NS_LITERAL_CSTRING("IBM864"); } else if (aCtrlsModAtSubmit==IBMBIDI_CONTROLSTEXTMODE_LOGICAL && oCharset.Equals(NS_LITERAL_CSTRING("IBM864"), nsCaseInsensitiveCStringComparator())) { oCharset = NS_LITERAL_CSTRING("IBM864i"); } else if (aCtrlsModAtSubmit==IBMBIDI_CONTROLSTEXTMODE_VISUAL && oCharset.Equals(NS_LITERAL_CSTRING("ISO-8859-6"), nsCaseInsensitiveCStringComparator())) { oCharset = NS_LITERAL_CSTRING("IBM864"); } else if (aCtrlsModAtSubmit==IBMBIDI_CONTROLSTEXTMODE_VISUAL && oCharset.Equals(NS_LITERAL_CSTRING("UTF-8"), nsCaseInsensitiveCStringComparator())) { oCharset = NS_LITERAL_CSTRING("IBM864"); } } // JBK moved from nsFormFrame - bug 34297 // static nsresult nsFormSubmission::GetEncoder(nsIHTMLContent* aForm, nsIPresContext* aPresContext, const nsACString& aCharset, nsISaveAsCharset** aEncoder) { *aEncoder = nsnull; nsresult rv = NS_OK; nsCAutoString charset(aCharset); if(charset.Equals(NS_LITERAL_CSTRING("ISO-8859-1"))) charset.Assign(NS_LITERAL_CSTRING("windows-1252")); rv = CallCreateInstance( NS_SAVEASCHARSET_CONTRACTID, aEncoder); NS_ASSERTION(NS_SUCCEEDED(rv), "create nsISaveAsCharset failed"); NS_ENSURE_SUCCESS(rv, rv); rv = (*aEncoder)->Init(charset.get(), (nsISaveAsCharset::attr_EntityAfterCharsetConv + nsISaveAsCharset::attr_FallbackDecimalNCR), 0); NS_ASSERTION(NS_SUCCEEDED(rv), "initialize nsISaveAsCharset failed"); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // i18n helper routines char* nsFormSubmission::UnicodeToNewBytes(const PRUnichar* aStr, PRUint32 aLen, nsISaveAsCharset* aEncoder) { nsresult rv = NS_OK; PRUint8 ctrlsModAtSubmit = GET_BIDI_OPTION_CONTROLSTEXTMODE(mBidiOptions); PRUint8 textDirAtSubmit = GET_BIDI_OPTION_DIRECTION(mBidiOptions); //ahmed 15-1 nsAutoString temp; nsAutoString newBuffer; //This condition handle the RTL,LTR for a logical file if (ctrlsModAtSubmit == IBMBIDI_CONTROLSTEXTMODE_VISUAL && mCharset.Equals(NS_LITERAL_CSTRING("windows-1256"), nsCaseInsensitiveCStringComparator())) { Conv_06_FE_WithReverse(nsString(aStr), newBuffer, textDirAtSubmit); aStr = newBuffer.get(); aLen=newBuffer.Length(); } else if (ctrlsModAtSubmit == IBMBIDI_CONTROLSTEXTMODE_LOGICAL && mCharset.Equals(NS_LITERAL_CSTRING("IBM864"), nsCaseInsensitiveCStringComparator())) { //For 864 file, When it is logical, if LTR then only convert //If RTL will mak a reverse for the buffer Conv_FE_06(nsString(aStr), newBuffer); aStr = newBuffer.get(); temp = newBuffer; aLen=newBuffer.Length(); if (textDirAtSubmit == 2) { //RTL //Now we need to reverse the Buffer, it is by searching the buffer PRUint32 loop = aLen; PRUint32 z; for (z=0; z<=aLen; z++) { temp.SetCharAt((PRUnichar)aStr[loop], z); loop--; } } aStr = temp.get(); } else if (ctrlsModAtSubmit == IBMBIDI_CONTROLSTEXTMODE_VISUAL && mCharset.Equals(NS_LITERAL_CSTRING("IBM864"), nsCaseInsensitiveCStringComparator()) && textDirAtSubmit == IBMBIDI_TEXTDIRECTION_RTL) { Conv_FE_06(nsString(aStr), newBuffer); aStr = newBuffer.get(); temp = newBuffer; aLen=newBuffer.Length(); //Now we need to reverse the Buffer, it is by searching the buffer PRUint32 loop = aLen; PRUint32 z; for (z=0; z<=aLen; z++) { temp.SetCharAt((PRUnichar)aStr[loop], z); loop--; } aStr = temp.get(); } char* res = nsnull; if(aStr && aStr[0]) { rv = aEncoder->Convert(aStr, &res); NS_ASSERTION(NS_SUCCEEDED(rv), "conversion failed"); } if(! res) res = nsCRT::strdup(""); return res; } // static void nsFormSubmission::GetEnumAttr(nsIHTMLContent* aContent, nsIAtom* atom, PRInt32* aValue) { nsHTMLValue value; if (aContent->GetHTMLAttribute(atom, value) == NS_CONTENT_ATTR_HAS_VALUE) { if (eHTMLUnit_Enumerated == value.GetUnit()) { *aValue = value.GetIntValue(); } } } char* nsFormSubmission::EncodeVal(const nsAString& aStr) { char* retval; if (mEncoder) { retval = UnicodeToNewBytes(PromiseFlatString(aStr).get(), aStr.Length(), mEncoder); } else { retval = ToNewCString(aStr); } return retval; } nsString* nsFormSubmission::ProcessValue(nsIDOMHTMLElement* aSource, const nsAString& aName, const nsAString& aValue) { // Hijack _charset_ (hidden inputs only) for internationalization (bug 18643) if (aName == NS_LITERAL_STRING("_charset_")) { nsCOMPtr formControl = do_QueryInterface(aSource); if (formControl) { if (formControl->GetType() == NS_FORM_INPUT_HIDDEN) { return new NS_ConvertASCIItoUTF16(mCharset); } } } nsString* retval = nsnull; if (mFormProcessor) { // XXX We need to change the ProcessValue interface to take nsAString nsString tmpNameStr(aName); retval = new nsString(aValue); if (!retval) { return nsnull; } #ifdef DEBUG nsresult rv = #endif mFormProcessor->ProcessValue(aSource, tmpNameStr, *retval); NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to Notify form process observer"); } return retval; }