1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla XForms support.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2004
20 * the Initial Developer. All Rights Reserved.
23 * Darin Fisher <darin@meer.net>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsIXFormsUploadElement.h"
40 #include "nsIXFormsUploadUIElement.h"
41 #include "nsXFormsUtils.h"
42 #include "nsIContent.h"
43 #include "nsNetUtil.h"
44 #include "nsXFormsDelegateStub.h"
45 #include "nsIMIMEService.h"
46 #include "nsCExternalHandlerService.h"
48 #include "nsIFilePicker.h"
49 #include "nsIDOMDocument.h"
50 #include "nsIDOMDocumentView.h"
51 #include "nsIDOMAbstractView.h"
52 #include "nsIDOMWindowInternal.h"
53 #include "nsIAttribute.h"
54 #include "nsIStringBundle.h"
56 #include "nsIEventStateManager.h"
58 #include "nsISchema.h"
60 #define NS_HTMLFORM_BUNDLE_URL \
61 "chrome://global/locale/layout/HtmlForm.properties"
64 * Implementation of the \<upload\> element.
66 class nsXFormsUploadElement
: public nsXFormsDelegateStub
,
67 public nsIXFormsUploadElement
70 NS_DECL_ISUPPORTS_INHERITED
71 NS_DECL_NSIXFORMSUPLOADELEMENT
73 NS_IMETHOD
IsTypeAllowed(PRUint16 aType
, PRBool
*aIsAllowed
,
74 nsRestrictionFlag
*aRestriction
,
75 nsAString
&aAllowedTypes
);
79 * Sets file path/contents into instance data. If aFile is nsnull,
80 * this clears the data.
82 nsresult
SetFile(nsILocalFile
*aFile
);
85 * Sets "filename" & "mediatype" in the instance data, using the given file.
86 * If aFile == nsnull, then this function clears the values in the
89 nsresult
HandleChildElements(nsILocalFile
*aFile
, PRBool
*aChanged
);
92 * Read the contents of the file and encode in Base64 or Hex. |aResult| must
93 * be freed by nsMemory::Free().
95 nsresult
EncodeFileContents(nsIFile
*aFile
, PRUint16 aType
,
98 void BinaryToHex(const char *aBuffer
, PRUint32 aCount
,
99 PRUnichar
**aHexString
);
102 NS_IMPL_ISUPPORTS_INHERITED1(nsXFormsUploadElement
,
103 nsXFormsDelegateStub
,
104 nsIXFormsUploadElement
)
107 nsXFormsUploadElement::IsTypeAllowed(PRUint16 aType
, PRBool
*aIsAllowed
,
108 nsRestrictionFlag
*aRestriction
,
109 nsAString
&aAllowedTypes
)
111 NS_ENSURE_ARG_POINTER(aRestriction
);
112 NS_ENSURE_ARG_POINTER(aIsAllowed
);
113 *aRestriction
= eTypes_Inclusive
;
114 *aIsAllowed
= PR_FALSE
;
116 // If it is not bound to 'anyURI', 'base64Binary', 'hexBinary', or an
117 // extension or derivation of one of these three types, then put an error in
118 // the console. CSS and XBL will make sure that the control won't appear in
121 if (aType
== nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY
||
122 aType
== nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY
||
123 aType
== nsISchemaBuiltinType::BUILTIN_TYPE_ANYURI
) {
125 *aIsAllowed
= PR_TRUE
;
129 // build the string of types that upload can bind to
130 aAllowedTypes
.AssignLiteral("xsd:anyURI xsd:base64Binary xsd:hexBinary");
135 ReleaseObject(void *aObject
,
136 nsIAtom
*aPropertyName
,
137 void *aPropertyValue
,
140 static_cast<nsISupports
*>(aPropertyValue
)->Release();
144 nsXFormsUploadElement::PickFile()
151 // get localized file picker title text
152 nsCOMPtr
<nsIStringBundleService
> bundleService
=
153 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
154 NS_ENSURE_SUCCESS(rv
, rv
);
155 nsCOMPtr
<nsIStringBundle
> bundle
;
156 rv
= bundleService
->CreateBundle(NS_HTMLFORM_BUNDLE_URL
,
157 getter_AddRefs(bundle
));
158 NS_ENSURE_SUCCESS(rv
, rv
);
159 nsString filepickerTitle
;
160 rv
= bundle
->GetStringFromName(NS_LITERAL_STRING("FileUpload").get(),
161 getter_Copies(filepickerTitle
));
163 // fall back to English text
164 filepickerTitle
.AssignLiteral("File Upload");
167 // get nsIDOMWindowInternal
168 nsCOMPtr
<nsIDOMDocument
> doc
;
169 nsCOMPtr
<nsIDOMWindowInternal
> internal
;
170 mElement
->GetOwnerDocument(getter_AddRefs(doc
));
171 rv
= nsXFormsUtils::GetWindowFromDocument(doc
, getter_AddRefs(internal
));
172 NS_ENSURE_STATE(internal
);
175 nsCOMPtr
<nsIFilePicker
> filePicker
=
176 do_CreateInstance("@mozilla.org/filepicker;1");
178 return NS_ERROR_FAILURE
;
180 rv
= filePicker
->Init(internal
, filepickerTitle
, nsIFilePicker::modeOpen
);
181 NS_ENSURE_SUCCESS(rv
, rv
);
183 // Set the file picker filters based on the mediatype attribute.
184 nsAutoString mediaType
;
185 mElement
->GetAttribute(NS_LITERAL_STRING("mediatype"), mediaType
);
187 if (!mediaType
.IsEmpty()) {
188 // The mediatype attribute contains a space delimited list of mime types.
190 nsCOMPtr
<nsIMIMEService
> mimeService
=
191 do_GetService("@mozilla.org/mime;1", &rv
);
192 NS_ENSURE_SUCCESS(rv
, rv
);
194 const PRUnichar
*start
= nsnull
, *end
= nsnull
, *iter
= nsnull
;
195 mediaType
.BeginReading(&start
, &end
);
196 mediaType
.BeginReading(&iter
);
198 nsAutoString fileFilter
;
199 nsAutoString mimeType
;
200 while (iter
!= end
) {
201 while (iter
< end
&& *iter
!= ' ')
204 mimeType
= Substring(start
, iter
);
207 // Save the starting position for the next mime type (if any).
210 // Only 1 media type or we've reached the end of the list.
211 mimeType
= Substring(start
, end
);
214 // Map the mime type to a file extension and add the extension to the file
215 // type filter for the file dialog.
216 nsCAutoString fileExtension
;
217 rv
= mimeService
->GetPrimaryExtension(NS_ConvertUTF16toUTF8(mimeType
),
218 EmptyCString(), fileExtension
);
219 if (NS_SUCCEEDED(rv
) && !fileExtension
.IsEmpty()) {
220 fileFilter
.AppendLiteral("*.");
221 fileFilter
.Append(NS_ConvertUTF8toUTF16(fileExtension
));
223 fileFilter
.AppendLiteral(";");
228 // Append the file extension filter.
229 filePicker
->AppendFilter(fileFilter
, fileFilter
);
232 // Always add 'All Files'
233 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
237 rv
= filePicker
->Show(&mode
);
238 NS_ENSURE_SUCCESS(rv
, rv
);
239 if (mode
== nsIFilePicker::returnCancel
)
243 nsCOMPtr
<nsILocalFile
> localFile
;
244 rv
= filePicker
->GetFile(getter_AddRefs(localFile
));
246 // set path value in \<upload\>'s text field.
247 nsCOMPtr
<nsIXFormsUploadUIElement
> uiUpload
= do_QueryInterface(mElement
);
250 NS_GetURLSpecFromFile(localFile
, spec
);
251 uiUpload
->SetFieldText(NS_ConvertUTF8toUTF16(spec
));
254 // set file into instance data
255 return SetFile(localFile
);
262 nsXFormsUploadElement::ClearFile()
264 // clear path value in \<upload\>'s text field.
265 nsCOMPtr
<nsIXFormsUploadUIElement
> uiUpload
= do_QueryInterface(mElement
);
267 uiUpload
->SetFieldText(EmptyString());
270 // clear file from instance data
271 return SetFile(nsnull
);
275 nsXFormsUploadElement::SetFile(nsILocalFile
*aFile
)
277 if (!mBoundNode
|| !mModel
)
282 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mBoundNode
);
283 nsCOMPtr
<nsIAttribute
> attr
;
285 attr
= do_QueryInterface(mBoundNode
);
286 NS_ENSURE_STATE(attr
);
289 PRBool dataChanged
= PR_FALSE
;
291 // clear instance data
293 content
->DeleteProperty(nsXFormsAtoms::uploadFileProperty
);
295 attr
->DeleteProperty(nsXFormsAtoms::uploadFileProperty
);
297 rv
= mModel
->SetNodeValue(mBoundNode
, EmptyString(), PR_FALSE
,
300 // set file into instance data
303 rv
= GetBoundBuiltinType(&type
);
304 NS_ENSURE_SUCCESS(rv
, rv
);
305 if (type
== nsISchemaBuiltinType::BUILTIN_TYPE_ANYURI
) {
306 // set fully qualified path as value in instance data node
308 NS_GetURLSpecFromFile(aFile
, spec
);
309 rv
= mModel
->SetNodeValue(mBoundNode
, NS_ConvertUTF8toUTF16(spec
),
310 PR_FALSE
, &dataChanged
);
311 } else if (type
== nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY
||
312 type
== nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY
) {
313 // encode file contents in base64/hex and set into instance data node
315 rv
= EncodeFileContents(aFile
, type
, &fileData
);
316 if (NS_SUCCEEDED(rv
)) {
317 rv
= mModel
->SetNodeValue(mBoundNode
, nsDependentString(fileData
),
318 PR_FALSE
, &dataChanged
);
319 nsMemory::Free(fileData
);
322 rv
= NS_ERROR_FAILURE
;
325 // XXX need to handle derived types
327 // Set nsIFile object as property on instance data node, so submission
329 if (NS_SUCCEEDED(rv
)) {
330 nsIFile
*fileCopy
= nsnull
;
331 rv
= aFile
->Clone(&fileCopy
);
332 NS_ENSURE_SUCCESS(rv
, rv
);
334 rv
= content
->SetProperty(nsXFormsAtoms::uploadFileProperty
, fileCopy
,
337 rv
= attr
->SetProperty(nsXFormsAtoms::uploadFileProperty
, fileCopy
,
342 NS_ENSURE_SUCCESS(rv
, rv
);
344 // Handle <filename> and <mediatype> children
345 PRBool childrenChanged
;
346 rv
= HandleChildElements(aFile
, &childrenChanged
);
347 NS_ENSURE_SUCCESS(rv
, rv
);
349 if (dataChanged
|| childrenChanged
) {
350 rv
= mModel
->RequestRecalculate();
351 NS_ENSURE_SUCCESS(rv
, rv
);
352 rv
= mModel
->RequestRevalidate();
353 NS_ENSURE_SUCCESS(rv
, rv
);
354 rv
= mModel
->RequestRefresh();
355 NS_ENSURE_SUCCESS(rv
, rv
);
362 nsXFormsUploadElement::HandleChildElements(nsILocalFile
*aFile
,
365 NS_ENSURE_ARG_POINTER(aChanged
);
366 NS_ENSURE_STATE(mModel
);
368 *aChanged
= PR_FALSE
;
370 // return immediately if we have no children
372 mElement
->HasChildNodes(&hasNodes
);
378 // look for the \<filename\> & \<mediatype\> elements in the
379 // \<upload\> children
380 nsCOMPtr
<nsIDOMNode
> filenameNode
, mediatypeNode
;
381 nsCOMPtr
<nsIDOMNode
> child
, temp
;
382 mElement
->GetFirstChild(getter_AddRefs(child
));
383 while (child
&& (!filenameNode
|| !mediatypeNode
)) {
385 nsXFormsUtils::IsXFormsElement(child
,
386 NS_LITERAL_STRING("filename")))
388 filenameNode
= child
;
391 if (!mediatypeNode
&&
392 nsXFormsUtils::IsXFormsElement(child
,
393 NS_LITERAL_STRING("mediatype")))
395 mediatypeNode
= child
;
399 temp
->GetNextSibling(getter_AddRefs(child
));
403 PRBool filenameChanged
= PR_FALSE
;
405 nsCOMPtr
<nsIDOMElement
> filenameElem
= do_QueryInterface(filenameNode
);
407 nsAutoString filename
;
408 rv
= aFile
->GetLeafName(filename
);
409 if (!filename
.IsEmpty()) {
410 rv
= nsXFormsUtils::SetSingleNodeBindingValue(filenameElem
, filename
,
414 rv
= nsXFormsUtils::SetSingleNodeBindingValue(filenameElem
, EmptyString(),
417 NS_ENSURE_SUCCESS(rv
, rv
);
420 // handle "mediatype"
421 PRBool mediatypechanged
= PR_FALSE
;
423 nsCOMPtr
<nsIDOMElement
> mediatypeElem
= do_QueryInterface(mediatypeNode
);
425 nsCOMPtr
<nsIMIMEService
> mimeService
=
426 do_GetService(NS_MIMESERVICE_CONTRACTID
, &rv
);
427 if (NS_SUCCEEDED(rv
)) {
428 nsCAutoString contentType
;
429 rv
= mimeService
->GetTypeFromFile(aFile
, contentType
);
431 contentType
.AssignLiteral("application/octet-stream");
433 rv
= nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem
,
434 NS_ConvertUTF8toUTF16(contentType
), &mediatypechanged
);
437 rv
= nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem
,
438 EmptyString(), &mediatypechanged
);
442 *aChanged
= filenameChanged
|| mediatypechanged
;
446 typedef nsAutoTArray
<char, 256> nsAutoCharBuffer
;
449 ReportEncodingMemoryError(nsIDOMElement
* aElement
, nsIFile
*aFile
,
450 PRUint32 aFailedSize
)
452 nsAutoString filename
;
453 if (NS_FAILED(aFile
->GetLeafName(filename
))) {
458 size
.AppendInt(aFailedSize
);
459 const PRUnichar
*strings
[] = { filename
.get(), size
.get() };
460 nsXFormsUtils::ReportError(NS_LITERAL_STRING("encodingMemoryError"),
461 strings
, 2, aElement
, aElement
);
465 nsXFormsUploadElement::EncodeFileContents(nsIFile
*aFile
, PRUint16 aType
,
471 nsCOMPtr
<nsIInputStream
> fileStream
;
472 rv
= NS_NewLocalFileInputStream(getter_AddRefs(fileStream
), aFile
,
473 PR_RDONLY
, -1 /* no mode bits */,
474 nsIFileInputStream::CLOSE_ON_EOF
);
475 NS_ENSURE_SUCCESS(rv
, rv
);
478 rv
= fileStream
->Available(&size
);
479 NS_ENSURE_SUCCESS(rv
, rv
);
481 nsAutoCharBuffer fileData
;
482 if (!fileData
.SetLength(size
+ 1)) {
483 ReportEncodingMemoryError(mElement
, aFile
, size
+ 1);
484 return NS_ERROR_OUT_OF_MEMORY
;
488 rv
= fileStream
->Read(fileData
.Elements(), size
, &bytesRead
);
489 NS_ASSERTION(NS_SUCCEEDED(rv
) && bytesRead
== size
,
490 "fileStream->Read failed");
492 if (NS_SUCCEEDED(rv
)) {
493 if (aType
== nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY
) {
494 // encode file contents
496 char *buffer
= PL_Base64Encode(fileData
.Elements(), bytesRead
, nsnull
);
498 *aResult
= ToNewUnicode(NS_ConvertASCIItoUTF16(buffer
));
502 PRUint32 failedSize
= buffer
? strlen(buffer
) * sizeof(PRUnichar
)
503 : ((bytesRead
+ 2) / 3) * 4 + 1;
504 ReportEncodingMemoryError(mElement
, aFile
, failedSize
);
505 rv
= NS_ERROR_OUT_OF_MEMORY
;
507 } else if (aType
== nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY
) {
508 // create buffer for hex encoded data
509 PRUint32 length
= bytesRead
* 2 + 1;
510 PRUnichar
*fileDataHex
=
511 static_cast<PRUnichar
*>(nsMemory::Alloc(length
* sizeof(PRUnichar
)));
513 ReportEncodingMemoryError(mElement
, aFile
, length
* sizeof(PRUnichar
));
514 rv
= NS_ERROR_OUT_OF_MEMORY
;
516 // encode file contents
517 BinaryToHex(fileData
.Elements(), bytesRead
, &fileDataHex
);
518 fileDataHex
[bytesRead
* 2] = 0;
519 *aResult
= fileDataHex
;
522 NS_ERROR("Unknown encoding type for <upload> element");
523 rv
= NS_ERROR_INVALID_ARG
;
530 static inline PRUnichar
531 ToHexChar(PRInt16 aValue
)
534 return (PRUnichar
) aValue
+ '0';
536 return (PRUnichar
) aValue
- 10 + 'A';
540 nsXFormsUploadElement::BinaryToHex(const char *aBuffer
, PRUint32 aCount
,
541 PRUnichar
**aHexString
)
543 for (PRUint32 index
= 0; index
< aCount
; index
++) {
544 (*aHexString
)[index
* 2] = ToHexChar((aBuffer
[index
] >> 4) & 0xf);
545 (*aHexString
)[index
* 2 + 1] = ToHexChar(aBuffer
[index
] & 0xf);
551 NS_NewXFormsUploadElement(nsIXTFElement
**aResult
)
553 *aResult
= new nsXFormsUploadElement();
555 return NS_ERROR_OUT_OF_MEMORY
;