gecko-dev/dom/html/HTMLFormSubmission.cpp

981 lines
29 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "HTMLFormSubmission.h"
#include "nsCOMPtr.h"
#include "nsIForm.h"
#include "nsILinkHandler.h"
#include "nsIDocument.h"
#include "nsGkAtoms.h"
#include "nsIFormControl.h"
#include "nsIDOMHTMLFormElement.h"
#include "nsError.h"
#include "nsGenericHTMLElement.h"
#include "nsAttrValueInlines.h"
#include "nsISaveAsCharset.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "nsStringStream.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "nsLinebreakConverter.h"
#include "nsEscape.h"
#include "nsUnicharUtils.h"
#include "nsIMultiplexInputStream.h"
#include "nsIMIMEInputStream.h"
#include "nsIMIMEService.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
#include "nsIStringBundle.h"
#include "nsCExternalHandlerService.h"
#include "nsIFileStreams.h"
#include "nsContentUtils.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/File.h"
namespace mozilla {
namespace dom {
namespace {
void
SendJSWarning(nsIDocument* aDocument,
const char* aWarningName,
const char16_t** aWarningArgs, uint32_t aWarningArgsLen)
{
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("HTML"), aDocument,
nsContentUtils::eFORMS_PROPERTIES,
aWarningName,
aWarningArgs, aWarningArgsLen);
}
void
RetrieveFileName(Blob* aBlob, nsAString& aFilename)
{
if (!aBlob) {
return;
}
RefPtr<File> file = aBlob->ToFile();
if (file) {
file->GetName(aFilename);
}
}
void
RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname)
{
MOZ_ASSERT(aDirectory);
ErrorResult rv;
aDirectory->GetName(aDirname, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
aDirname.Truncate();
}
}
// --------------------------------------------------------------------------
class FSURLEncoded : public EncodingFormSubmission
{
public:
/**
* @param aCharset the charset of the form as a string
* @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
* NS_FORM_METHOD_POST).
*/
FSURLEncoded(const nsACString& aCharset,
int32_t aMethod,
nsIDocument* aDocument,
nsIContent* aOriginatingElement)
: EncodingFormSubmission(aCharset, aOriginatingElement),
mMethod(aMethod),
mDocument(aDocument),
mWarnedFileControl(false)
{
}
virtual nsresult
AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
virtual nsresult
AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
virtual bool SupportsIsindexSubmission() override
{
return true;
}
virtual nsresult AddIsindex(const nsAString& aValue) override;
protected:
/**
* 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, nsACString& aEncoded);
private:
/**
* The method of the submit (either NS_FORM_METHOD_GET or
* NS_FORM_METHOD_POST).
*/
int32_t mMethod;
/** The query string so far (the part after the ?) */
nsCString mQueryString;
/** The document whose URI to use when reporting errors */
nsCOMPtr<nsIDocument> mDocument;
/** Whether or not we have warned about a file control not being submitted */
bool mWarnedFileControl;
};
nsresult
FSURLEncoded::AddNameValuePair(const nsAString& aName,
const nsAString& aValue)
{
// Encode value
nsCString convValue;
nsresult rv = URLEncode(aValue, convValue);
NS_ENSURE_SUCCESS(rv, rv);
// Encode name
nsAutoCString convName;
rv = URLEncode(aName, convName);
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;
}
return NS_OK;
}
nsresult
FSURLEncoded::AddIsindex(const nsAString& aValue)
{
// Encode value
nsCString convValue;
nsresult rv = URLEncode(aValue, convValue);
NS_ENSURE_SUCCESS(rv, rv);
// Append data to string
if (mQueryString.IsEmpty()) {
mQueryString.Assign(convValue);
} else {
mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
}
return NS_OK;
}
nsresult
FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
Blob* aBlob)
{
if (!mWarnedFileControl) {
SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
mWarnedFileControl = true;
}
nsAutoString filename;
RetrieveFileName(aBlob, filename);
return AddNameValuePair(aName, filename);
}
nsresult
FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
Directory* aDirectory)
{
// No warning about because Directory objects are never sent via form.
nsAutoString dirname;
RetrieveDirectoryName(aDirectory, dirname);
return AddNameValuePair(aName, dirname);
}
void
HandleMailtoSubject(nsCString& aPath)
{
// Walk through the string and see if we have a subject already.
bool hasSubject = false;
bool hasParams = false;
int32_t paramSep = aPath.FindChar('?');
while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
hasParams = 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
int32_t nameEnd = aPath.FindChar('=', paramSep+1);
int32_t 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)).
LowerCaseEqualsLiteral("subject")) {
hasSubject = 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('?');
}
// Get the default subject
nsXPIDLString brandName;
nsresult rv =
nsContentUtils::GetLocalizedString(nsContentUtils::eBRAND_PROPERTIES,
"brandShortName", brandName);
if (NS_FAILED(rv))
return;
const char16_t *formatStrings[] = { brandName.get() };
nsXPIDLString subjectStr;
rv = nsContentUtils::FormatLocalizedString(
nsContentUtils::eFORMS_PROPERTIES,
"DefaultFormSubject",
formatStrings,
subjectStr);
if (NS_FAILED(rv))
return;
aPath.AppendLiteral("subject=");
nsCString subjectStrEscaped;
rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
subjectStrEscaped, mozilla::fallible);
if (NS_FAILED(rv))
return;
aPath.Append(subjectStrEscaped);
}
}
nsresult
FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
nsIInputStream** aPostDataStream)
{
nsresult rv = NS_OK;
*aPostDataStream = nullptr;
if (mMethod == NS_FORM_METHOD_POST) {
bool isMailto = false;
aURI->SchemeIs("mailto", &isMailto);
if (isMailto) {
nsAutoCString 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
nsAutoCString escapedBody;
if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) {
return NS_ERROR_OUT_OF_MEMORY;
}
path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
rv = aURI->SetPath(path);
} else {
nsCOMPtr<nsIInputStream> 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<nsIMIMEInputStream> 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(true);
mimeStream->SetData(dataStream);
*aPostDataStream = mimeStream;
NS_ADDREF(*aPostDataStream);
}
} else {
// Get the full query string
bool schemeIsJavaScript;
rv = aURI->SchemeIs("javascript", &schemeIsJavaScript);
NS_ENSURE_SUCCESS(rv, rv);
if (schemeIsJavaScript) {
return NS_OK;
}
nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
if (url) {
url->SetQuery(mQueryString);
}
else {
nsAutoCString path;
rv = aURI->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
// Bug 42616: Trim off named anchor and save it to add later
int32_t namedAnchorPos = path.FindChar('#');
nsAutoCString 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)
int32_t 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
FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded)
{
// convert to CRLF breaks
int32_t convertedBufLength = 0;
char16_t* convertedBuf =
nsLinebreakConverter::ConvertUnicharLineBreaks(aStr.BeginReading(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet,
aStr.Length(),
&convertedBufLength);
NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
nsAutoString convertedString;
convertedString.Adopt(convertedBuf, convertedBufLength);
nsAutoCString encodedBuf;
nsresult rv = EncodeVal(convertedString, encodedBuf, false);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
} // anonymous namespace
// --------------------------------------------------------------------------
FSMultipartFormData::FSMultipartFormData(const nsACString& aCharset,
nsIContent* aOriginatingElement)
: EncodingFormSubmission(aCharset, aOriginatingElement)
{
mPostDataStream =
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
mTotalLength = 0;
mBoundary.AssignLiteral("---------------------------");
mBoundary.AppendInt(rand());
mBoundary.AppendInt(rand());
mBoundary.AppendInt(rand());
}
FSMultipartFormData::~FSMultipartFormData()
{
NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
}
nsIInputStream*
FSMultipartFormData::GetSubmissionBody(uint64_t* aContentLength)
{
// Finish data
mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ NS_LITERAL_CSTRING("--" CRLF);
// Add final data input stream
AddPostDataStream();
*aContentLength = mTotalLength;
return mPostDataStream;
}
nsresult
FSMultipartFormData::AddNameValuePair(const nsAString& aName,
const nsAString& aValue)
{
nsCString valueStr;
nsAutoCString encodedVal;
nsresult rv = EncodeVal(aValue, encodedVal, false);
NS_ENSURE_SUCCESS(rv, rv);
valueStr.Adopt(nsLinebreakConverter::
ConvertLineBreaks(encodedVal.get(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet));
nsAutoCString nameStr;
rv = EncodeVal(aName, nameStr, true);
NS_ENSURE_SUCCESS(rv, rv);
// Make MIME block for name/value pair
// XXX: name parameter should be encoded per RFC 2231
// RFC 2388 specifies that RFC 2047 be used, but I think it's not
// consistent with MIME standard.
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
FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
{
// Encode the control name
nsAutoCString nameStr;
nsresult rv = EncodeVal(aName, nameStr, true);
NS_ENSURE_SUCCESS(rv, rv);
ErrorResult error;
uint64_t size = 0;
nsAutoCString filename;
nsAutoCString contentType;
nsCOMPtr<nsIInputStream> fileStream;
if (aBlob) {
nsAutoString filename16;
RefPtr<File> file = aBlob->ToFile();
if (file) {
nsAutoString relativePath;
file->GetRelativePath(relativePath);
if (Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr) &&
!relativePath.IsEmpty()) {
filename16 = relativePath;
}
if (filename16.IsEmpty()) {
RetrieveFileName(aBlob, filename16);
}
}
rv = EncodeVal(filename16, filename, true);
NS_ENSURE_SUCCESS(rv, rv);
// Get content type
nsAutoString contentType16;
aBlob->GetType(contentType16);
if (contentType16.IsEmpty()) {
contentType16.AssignLiteral("application/octet-stream");
}
contentType.Adopt(nsLinebreakConverter::
ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakSpace));
// Get input stream
aBlob->GetInternalStream(getter_AddRefs(fileStream), error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
// Get size
size = aBlob->GetSize(error);
if (error.Failed()) {
error.SuppressException();
fileStream = nullptr;
}
if (fileStream) {
// Create buffered stream (for efficiency)
nsCOMPtr<nsIInputStream> bufferedStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
fileStream, 8192);
NS_ENSURE_SUCCESS(rv, rv);
fileStream = bufferedStream;
}
} else {
contentType.AssignLiteral("application/octet-stream");
}
AddDataChunk(nameStr, filename, contentType, fileStream, size);
return NS_OK;
}
nsresult
FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
Directory* aDirectory)
{
if (!Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr)) {
return NS_OK;
}
// Encode the control name
nsAutoCString nameStr;
nsresult rv = EncodeVal(aName, nameStr, true);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString dirname;
nsAutoString dirname16;
ErrorResult error;
nsAutoString path;
aDirectory->GetPath(path, error);
if (NS_WARN_IF(error.Failed())) {
error.SuppressException();
} else {
dirname16 = path;
}
if (dirname16.IsEmpty()) {
RetrieveDirectoryName(aDirectory, dirname16);
}
rv = EncodeVal(dirname16, dirname, true);
NS_ENSURE_SUCCESS(rv, rv);
AddDataChunk(nameStr, dirname,
NS_LITERAL_CSTRING("application/octet-stream"),
nullptr, 0);
return NS_OK;
}
void
FSMultipartFormData::AddDataChunk(const nsACString& aName,
const nsACString& aFilename,
const nsACString& aContentType,
nsIInputStream* aInputStream,
uint64_t aInputStreamSize)
{
//
// Make MIME block for name/value pair
//
// more appropriate than always using binary?
mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ NS_LITERAL_CSTRING(CRLF);
// XXX: name/filename parameter should be encoded per RFC 2231
// RFC 2388 specifies that RFC 2047 be used, but I think it's not
// consistent with the MIME standard.
mPostDataChunk +=
NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
+ aName + NS_LITERAL_CSTRING("\"; filename=\"")
+ aFilename + NS_LITERAL_CSTRING("\"" CRLF)
+ NS_LITERAL_CSTRING("Content-Type: ")
+ aContentType + NS_LITERAL_CSTRING(CRLF CRLF);
// We should not try to append an invalid stream. That will happen for example
// if we try to update a file that actually do not exist.
if (aInputStream) {
// 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(aInputStream);
mTotalLength += aInputStreamSize;
}
// CRLF after file
mPostDataChunk.AppendLiteral(CRLF);
}
nsresult
FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
nsIInputStream** aPostDataStream)
{
nsresult rv;
// Make header
nsCOMPtr<nsIMIMEInputStream> mimeStream
= do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString contentType;
GetContentType(contentType);
mimeStream->AddHeader("Content-Type", contentType.get());
mimeStream->SetAddContentLength(true);
uint64_t unused;
mimeStream->SetData(GetSubmissionBody(&unused));
mimeStream.forget(aPostDataStream);
return NS_OK;
}
nsresult
FSMultipartFormData::AddPostDataStream()
{
nsresult rv = NS_OK;
nsCOMPtr<nsIInputStream> postDataChunkStream;
rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
mPostDataChunk);
NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
if (postDataChunkStream) {
mPostDataStream->AppendStream(postDataChunkStream);
mTotalLength += mPostDataChunk.Length();
}
mPostDataChunk.Truncate();
return rv;
}
// --------------------------------------------------------------------------
namespace {
class FSTextPlain : public EncodingFormSubmission
{
public:
FSTextPlain(const nsACString& aCharset, nsIContent* aOriginatingElement)
: EncodingFormSubmission(aCharset, aOriginatingElement)
{
}
virtual nsresult
AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
virtual nsresult
AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
private:
nsString mBody;
};
nsresult
FSTextPlain::AddNameValuePair(const nsAString& aName, const nsAString& 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.
mBody.Append(aName + NS_LITERAL_STRING("=") + aValue +
NS_LITERAL_STRING(CRLF));
return NS_OK;
}
nsresult
FSTextPlain::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
{
nsAutoString filename;
RetrieveFileName(aBlob, filename);
AddNameValuePair(aName, filename);
return NS_OK;
}
nsresult
FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
Directory* aDirectory)
{
nsAutoString dirname;
RetrieveDirectoryName(aDirectory, dirname);
AddNameValuePair(aName, dirname);
return NS_OK;
}
nsresult
FSTextPlain::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.
bool isMailto = false;
aURI->SchemeIs("mailto", &isMailto);
if (isMailto) {
nsAutoCString 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
nsAutoCString escapedBody;
if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody,
url_XAlphas))) {
return NS_ERROR_OUT_OF_MEMORY;
}
path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
rv = aURI->SetPath(path);
} else {
// Create data stream.
// We do want to send the data through the charset encoder and we want to
// normalize linebreaks to use the "standard net" format (\r\n), but we
// don't want to perform any other encoding. This means that names and
// values which contains '=' or newlines are potentially ambigiously
// encoded, but that how text/plain is specced.
nsCString cbody;
EncodeVal(mBody, cbody, false);
cbody.Adopt(nsLinebreakConverter::
ConvertLineBreaks(cbody.get(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet));
nsCOMPtr<nsIInputStream> bodyStream;
rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), cbody);
if (!bodyStream) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Create mime stream with headers and such
nsCOMPtr<nsIMIMEInputStream> mimeStream
= do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
mimeStream->AddHeader("Content-Type", "text/plain");
mimeStream->SetAddContentLength(true);
mimeStream->SetData(bodyStream);
CallQueryInterface(mimeStream, aPostDataStream);
}
return rv;
}
} // anonymous namespace
// --------------------------------------------------------------------------
EncodingFormSubmission::EncodingFormSubmission(const nsACString& aCharset,
nsIContent* aOriginatingElement)
: HTMLFormSubmission(aCharset, aOriginatingElement)
, mEncoder(aCharset)
{
if (!(aCharset.EqualsLiteral("UTF-8") || aCharset.EqualsLiteral("gb18030"))) {
NS_ConvertUTF8toUTF16 charsetUtf16(aCharset);
const char16_t* charsetPtr = charsetUtf16.get();
SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument()
: nullptr,
"CannotEncodeAllUnicode",
&charsetPtr,
1);
}
}
EncodingFormSubmission::~EncodingFormSubmission()
{
}
// i18n helper routines
nsresult
EncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut,
bool aHeaderEncode)
{
if (!mEncoder.Encode(aStr, aOut)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (aHeaderEncode) {
aOut.Adopt(nsLinebreakConverter::
ConvertLineBreaks(aOut.get(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakSpace));
aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""),
NS_LITERAL_CSTRING("\\\""));
}
return NS_OK;
}
// --------------------------------------------------------------------------
namespace {
void
GetSubmitCharset(nsGenericHTMLElement* aForm,
nsACString& oCharset)
{
oCharset.AssignLiteral("UTF-8"); // default to utf-8
nsAutoString acceptCharsetValue;
aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset,
acceptCharsetValue);
int32_t charsetLen = acceptCharsetValue.Length();
if (charsetLen > 0) {
int32_t offset=0;
int32_t spPos=0;
// get charset from charsets one by one
do {
spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
int32_t cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset));
if (cnt > 0) {
nsAutoString uCharset;
acceptCharsetValue.Mid(uCharset, offset, cnt);
if (EncodingUtils::FindEncodingForLabelNoReplacement(uCharset, 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->GetComposedDoc();
if (doc) {
oCharset = doc->GetDocumentCharacterSet();
}
}
void
GetEnumAttr(nsGenericHTMLElement* aContent,
nsIAtom* atom, int32_t* aValue)
{
const nsAttrValue* value = aContent->GetParsedAttr(atom);
if (value && value->Type() == nsAttrValue::eEnum) {
*aValue = value->GetEnumValue();
}
}
} // anonymous namespace
/* static */ nsresult
HTMLFormSubmission::GetFromForm(nsGenericHTMLElement* aForm,
nsGenericHTMLElement* aOriginatingElement,
HTMLFormSubmission** aFormSubmission)
{
// Get all the information necessary to encode the form data
NS_ASSERTION(aForm->GetComposedDoc(),
"Should have doc if we're building submission!");
// Get encoding type (default: urlencoded)
int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
if (aOriginatingElement &&
aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype);
} else {
GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
}
// Get method (default: GET)
int32_t method = NS_FORM_METHOD_GET;
if (aOriginatingElement &&
aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formmethod)) {
GetEnumAttr(aOriginatingElement, nsGkAtoms::formmethod, &method);
} else {
GetEnumAttr(aForm, nsGkAtoms::method, &method);
}
// Get charset
nsAutoCString charset;
GetSubmitCharset(aForm, charset);
// We now have a canonical charset name, so we only have to check it
// against canonical names.
// use UTF-8 for UTF-16* (per WHATWG and existing practice of
// MS IE/Opera).
if (StringBeginsWith(charset, NS_LITERAL_CSTRING("UTF-16"))) {
charset.AssignLiteral("UTF-8");
}
// Choose encoder
if (method == NS_FORM_METHOD_POST &&
enctype == NS_FORM_ENCTYPE_MULTIPART) {
*aFormSubmission = new FSMultipartFormData(charset, aOriginatingElement);
} else if (method == NS_FORM_METHOD_POST &&
enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
*aFormSubmission = new FSTextPlain(charset, aOriginatingElement);
} else {
nsIDocument* doc = aForm->OwnerDoc();
if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
nsAutoString enctypeStr;
if (aOriginatingElement &&
aOriginatingElement->HasAttr(kNameSpaceID_None,
nsGkAtoms::formenctype)) {
aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype,
enctypeStr);
} else {
aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr);
}
const char16_t* enctypeStrPtr = enctypeStr.get();
SendJSWarning(doc, "ForgotPostWarning",
&enctypeStrPtr, 1);
}
*aFormSubmission = new FSURLEncoded(charset, method, doc,
aOriginatingElement);
}
return NS_OK;
}
} // dom namespace
} // mozilla namespace