Back to index

lightning-sunbird  0.9+nobinonly
nsXFormsUploadElement.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla XForms support.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * IBM Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2004
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsIXFormsUploadElement.h"
00040 #include "nsIXFormsUploadUIElement.h"
00041 #include "nsXFormsUtils.h"
00042 #include "nsIContent.h"
00043 #include "nsNetUtil.h"
00044 #include "nsXFormsDelegateStub.h"
00045 #include "nsIMIMEService.h"
00046 #include "nsCExternalHandlerService.h"
00047 #include "plbase64.h"
00048 #include "nsIFilePicker.h"
00049 #include "nsIDOMDocument.h"
00050 #include "nsIDOMDocumentView.h"
00051 #include "nsIDOMAbstractView.h"
00052 #include "nsIDOMWindowInternal.h"
00053 #include "nsIAttribute.h"
00054 #include "nsIStringBundle.h"
00055 #include "nsAutoBuffer.h"
00056 #include "nsIEventStateManager.h"
00057 #include "prmem.h"
00058 #include "nsISchema.h"
00059 
00060 #define NS_HTMLFORM_BUNDLE_URL \
00061           "chrome://global/locale/layout/HtmlForm.properties"
00062 
00066 class nsXFormsUploadElement : public nsXFormsDelegateStub,
00067                               public nsIXFormsUploadElement
00068 {
00069 public:
00070   NS_DECL_ISUPPORTS_INHERITED
00071   NS_DECL_NSIXFORMSUPLOADELEMENT
00072 
00073   NS_IMETHOD IsTypeAllowed(PRUint16 aType, PRBool *aIsAllowed,
00074                            nsRestrictionFlag *aRestriction,
00075                            nsAString &aAllowedTypes);
00076 
00077 private:
00082   nsresult SetFile(nsILocalFile *aFile);
00083 
00089   nsresult HandleChildElements(nsILocalFile *aFile, PRBool *aChanged);
00090 
00095   nsresult EncodeFileContents(nsIFile *aFile, PRUint16 aType,
00096                               PRUnichar **aResult);
00097 
00098   void BinaryToHex(const char *aBuffer, PRUint32 aCount,
00099                    PRUnichar **aHexString);
00100 };
00101 
00102 NS_IMPL_ISUPPORTS_INHERITED1(nsXFormsUploadElement,
00103                              nsXFormsDelegateStub,
00104                              nsIXFormsUploadElement)
00105 
00106 NS_IMETHODIMP
00107 nsXFormsUploadElement::IsTypeAllowed(PRUint16 aType, PRBool *aIsAllowed,
00108                                      nsRestrictionFlag *aRestriction,
00109                                      nsAString &aAllowedTypes)
00110 {
00111   NS_ENSURE_ARG_POINTER(aRestriction);
00112   NS_ENSURE_ARG_POINTER(aIsAllowed);
00113   *aRestriction = eTypes_Inclusive;
00114   *aIsAllowed = PR_FALSE;
00115 
00116   // If it is not bound to 'anyURI', 'base64Binary', 'hexBinary', or an
00117   // extension or derivation of one of these three types, then put an error in
00118   // the console.  CSS and XBL will make sure that the control won't appear in
00119   // the form.
00120 
00121   if (aType == nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY ||
00122       aType == nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY ||
00123       aType == nsISchemaBuiltinType::BUILTIN_TYPE_ANYURI) {
00124 
00125     *aIsAllowed = PR_TRUE;
00126     return NS_OK;
00127   }
00128 
00129   // build the string of types that upload can bind to
00130   aAllowedTypes.AssignLiteral("xsd:anyURI xsd:base64Binary xsd:hexBinary");
00131   return NS_OK;
00132 }
00133 
00134 static void
00135 ReleaseObject(void    *aObject,
00136               nsIAtom *aPropertyName,
00137               void    *aPropertyValue,
00138               void    *aData)
00139 {
00140   NS_STATIC_CAST(nsISupports *, aPropertyValue)->Release();
00141 }
00142 
00143 NS_IMETHODIMP
00144 nsXFormsUploadElement::PickFile()
00145 {
00146   if (!mElement)
00147     return NS_OK;
00148 
00149   nsresult rv;
00150 
00151   // get localized file picker title text
00152   nsCOMPtr<nsIStringBundleService> bundleService =
00153             do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
00154   NS_ENSURE_SUCCESS(rv, rv);
00155   nsCOMPtr<nsIStringBundle> bundle;
00156   rv = bundleService->CreateBundle(NS_HTMLFORM_BUNDLE_URL,
00157                                    getter_AddRefs(bundle));
00158   NS_ENSURE_SUCCESS(rv, rv);
00159   nsXPIDLString filepickerTitle;
00160   rv = bundle->GetStringFromName(NS_LITERAL_STRING("FileUpload").get(),
00161                                  getter_Copies(filepickerTitle));
00162   if (NS_FAILED(rv)) {
00163     // fall back to English text
00164     filepickerTitle.AssignASCII("File Upload");
00165   }
00166 
00167   // get nsIDOMWindowInternal
00168   nsCOMPtr<nsIDOMDocument> doc;
00169   nsCOMPtr<nsIDOMWindowInternal> internal;
00170   mElement->GetOwnerDocument(getter_AddRefs(doc));
00171   rv = nsXFormsUtils::GetWindowFromDocument(doc, getter_AddRefs(internal));
00172   NS_ENSURE_STATE(internal);
00173 
00174   // init filepicker
00175   nsCOMPtr<nsIFilePicker> filePicker =
00176             do_CreateInstance("@mozilla.org/filepicker;1");
00177   if (!filePicker)
00178     return NS_ERROR_FAILURE;
00179 
00180   rv = filePicker->Init(internal, filepickerTitle, nsIFilePicker::modeOpen);
00181   NS_ENSURE_SUCCESS(rv, rv);
00182 
00183   // Set the file picker filters based on the mediatype attribute.
00184   nsAutoString mediaType;
00185   mElement->GetAttribute(NS_LITERAL_STRING("mediatype"), mediaType);
00186 
00187   if (!mediaType.IsEmpty()) {
00188     // The mediatype attribute contains a space delimited list of mime types.
00189     nsresult rv;
00190     nsCOMPtr<nsIMIMEService> mimeService =
00191       do_GetService("@mozilla.org/mime;1", &rv);
00192     NS_ENSURE_SUCCESS(rv, rv);
00193 
00194     nsAString::const_iterator start, end, iter;
00195     mediaType.BeginReading(start);
00196     mediaType.BeginReading(iter);
00197     mediaType.EndReading(end);
00198 
00199     nsAutoString fileFilter;
00200     nsAutoString mimeType;
00201     while (iter != end) {
00202       if (FindCharInReadable(' ', iter, end)) {
00203          mimeType = Substring(start, iter);
00204          // Skip the space.
00205          ++iter;
00206          // Save the starting position for the next mime type (if any).
00207          start = iter;
00208       } else {
00209         // Only 1 media type or we've reached the end of the list.
00210         mimeType = Substring(start, end);
00211       }
00212 
00213       // Map the mime type to a file extension and add the extension to the file
00214       // type filter for the file dialog.
00215       nsCAutoString fileExtension;
00216       rv = mimeService->GetPrimaryExtension(NS_ConvertUTF16toUTF8(mimeType),
00217                                             EmptyCString(), fileExtension);
00218       if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) {
00219         fileFilter.AppendLiteral("*.");
00220         fileFilter.Append(NS_ConvertUTF8toUTF16(fileExtension));
00221         if (iter != end) {
00222           fileFilter.AppendLiteral(";");
00223         }
00224       }
00225     }
00226 
00227     // Append the file extension filter.
00228     filePicker->AppendFilter(fileFilter, fileFilter);
00229   }
00230 
00231   // Always add 'All Files'
00232   filePicker->AppendFilters(nsIFilePicker::filterAll);
00233 
00234   // open dialog
00235   PRInt16 mode;
00236   rv = filePicker->Show(&mode);
00237   NS_ENSURE_SUCCESS(rv, rv);
00238   if (mode == nsIFilePicker::returnCancel)
00239     return NS_OK;
00240 
00241   // file was selected
00242   nsCOMPtr<nsILocalFile> localFile;
00243   rv = filePicker->GetFile(getter_AddRefs(localFile));
00244   if (localFile) {
00245     // set path value in <upload>'s text field.
00246     nsCOMPtr<nsIXFormsUploadUIElement> uiUpload = do_QueryInterface(mElement);
00247     if (uiUpload) {
00248       nsCAutoString spec;
00249       NS_GetURLSpecFromFile(localFile, spec);
00250       uiUpload->SetFieldText(NS_ConvertUTF8toUTF16(spec));
00251     }
00252 
00253     // set file into instance data
00254     return SetFile(localFile);
00255   }
00256 
00257   return rv;
00258 }
00259 
00260 NS_IMETHODIMP
00261 nsXFormsUploadElement::ClearFile()
00262 {
00263   // clear path value in <upload>'s text field.
00264   nsCOMPtr<nsIXFormsUploadUIElement> uiUpload = do_QueryInterface(mElement);
00265   if (uiUpload) {
00266     uiUpload->SetFieldText(EmptyString());
00267   }
00268 
00269   // clear file from instance data
00270   return SetFile(nsnull);
00271 }
00272 
00273 nsresult
00274 nsXFormsUploadElement::SetFile(nsILocalFile *aFile)
00275 {
00276   if (!mBoundNode || !mModel)
00277     return NS_OK;
00278 
00279   nsresult rv;
00280 
00281   nsCOMPtr<nsIContent> content = do_QueryInterface(mBoundNode);
00282   nsCOMPtr<nsIAttribute> attr;
00283   if (!content) {
00284     attr = do_QueryInterface(mBoundNode);
00285     NS_ENSURE_STATE(attr);
00286   }
00287 
00288   PRBool dataChanged = PR_FALSE;
00289   if (!aFile) {
00290     // clear instance data
00291     if (content) {
00292       content->DeleteProperty(nsXFormsAtoms::uploadFileProperty);
00293     } else {
00294       attr->DeleteProperty(nsXFormsAtoms::uploadFileProperty);
00295     }
00296     rv = mModel->SetNodeValue(mBoundNode, EmptyString(), PR_FALSE,
00297                               &dataChanged);
00298   } else {
00299     // set file into instance data
00300 
00301     PRUint16 type = 0;
00302     rv = GetBoundBuiltinType(&type);
00303     NS_ENSURE_SUCCESS(rv, rv);
00304     if (type == nsISchemaBuiltinType::BUILTIN_TYPE_ANYURI) {
00305       // set fully qualified path as value in instance data node
00306       nsCAutoString spec;
00307       NS_GetURLSpecFromFile(aFile, spec);
00308       rv = mModel->SetNodeValue(mBoundNode, NS_ConvertUTF8toUTF16(spec),
00309                                 PR_FALSE, &dataChanged);
00310     } else if (type == nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY ||
00311                type == nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY) {
00312       // encode file contents in base64/hex and set into instance data node
00313       PRUnichar *fileData;
00314       rv = EncodeFileContents(aFile, type, &fileData);
00315       if (NS_SUCCEEDED(rv)) {
00316         rv = mModel->SetNodeValue(mBoundNode, nsDependentString(fileData),
00317                                   PR_FALSE, &dataChanged);
00318         nsMemory::Free(fileData);
00319       }
00320     } else {
00321       rv = NS_ERROR_FAILURE;
00322     }
00323 
00324     // XXX need to handle derived types
00325 
00326     // Set nsIFile object as property on instance data node, so submission
00327     // code can use it.
00328     if (NS_SUCCEEDED(rv)) {
00329       nsIFile *fileCopy = nsnull;
00330       rv = aFile->Clone(&fileCopy);
00331       NS_ENSURE_SUCCESS(rv, rv);
00332       if (content) {
00333         rv = content->SetProperty(nsXFormsAtoms::uploadFileProperty, fileCopy,
00334                                   ReleaseObject);
00335       } else {
00336         rv = attr->SetProperty(nsXFormsAtoms::uploadFileProperty, fileCopy,
00337                                ReleaseObject);
00338       }
00339     }
00340   }
00341   NS_ENSURE_SUCCESS(rv, rv);
00342 
00343   // Handle <filename> and <mediatype> children
00344   PRBool childrenChanged;
00345   rv = HandleChildElements(aFile, &childrenChanged);
00346   NS_ENSURE_SUCCESS(rv, rv);
00347 
00348   if (dataChanged || childrenChanged) {
00349     rv = mModel->RequestRecalculate();
00350     NS_ENSURE_SUCCESS(rv, rv);
00351     rv = mModel->RequestRevalidate();
00352     NS_ENSURE_SUCCESS(rv, rv);
00353     rv = mModel->RequestRefresh();
00354     NS_ENSURE_SUCCESS(rv, rv);
00355   }
00356 
00357   return NS_OK;
00358 }
00359 
00360 nsresult
00361 nsXFormsUploadElement::HandleChildElements(nsILocalFile *aFile,
00362                                            PRBool *aChanged)
00363 {
00364   NS_ENSURE_ARG_POINTER(aChanged);
00365   NS_ENSURE_STATE(mModel);
00366 
00367   *aChanged = PR_FALSE;
00368 
00369   // return immediately if we have no children
00370   PRBool hasNodes;
00371   mElement->HasChildNodes(&hasNodes);
00372   if (!hasNodes)
00373     return NS_OK;
00374 
00375   nsresult rv = NS_OK;
00376 
00377   // look for the <filename> & <mediatype> elements in the
00378   // <upload> children
00379   nsCOMPtr<nsIDOMNode> filenameNode, mediatypeNode;
00380   nsCOMPtr<nsIDOMNode> child, temp;
00381   mElement->GetFirstChild(getter_AddRefs(child));
00382   while (child && (!filenameNode || !mediatypeNode)) {
00383     if (!filenameNode &&
00384         nsXFormsUtils::IsXFormsElement(child,
00385                                        NS_LITERAL_STRING("filename")))
00386     {
00387       filenameNode = child;
00388     }
00389 
00390     if (!mediatypeNode &&
00391         nsXFormsUtils::IsXFormsElement(child,
00392                                        NS_LITERAL_STRING("mediatype")))
00393     {
00394       mediatypeNode = child;
00395     }
00396 
00397     temp.swap(child);
00398     temp->GetNextSibling(getter_AddRefs(child));
00399   }
00400 
00401   // handle "filename"
00402   PRBool filenameChanged = PR_FALSE;
00403   if (filenameNode) {
00404     nsCOMPtr<nsIDOMElement> filenameElem = do_QueryInterface(filenameNode);
00405     if (aFile) {
00406       nsAutoString filename;
00407       rv = aFile->GetLeafName(filename);
00408       if (!filename.IsEmpty()) {
00409         rv = nsXFormsUtils::SetSingleNodeBindingValue(filenameElem, filename,
00410                                                       &filenameChanged);
00411       }
00412     } else {
00413       rv = nsXFormsUtils::SetSingleNodeBindingValue(filenameElem, EmptyString(),
00414                                                     &filenameChanged);
00415     }
00416     NS_ENSURE_SUCCESS(rv, rv);
00417   }
00418 
00419   // handle "mediatype"
00420   PRBool mediatypechanged = PR_FALSE;
00421   if (mediatypeNode) {
00422     nsCOMPtr<nsIDOMElement> mediatypeElem = do_QueryInterface(mediatypeNode);
00423     if (aFile) {
00424       nsCOMPtr<nsIMIMEService> mimeService =
00425                 do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
00426       if (NS_SUCCEEDED(rv)) {
00427         nsCAutoString contentType;
00428         rv = mimeService->GetTypeFromFile(aFile, contentType);
00429         if (NS_FAILED(rv)) {
00430           contentType.AssignLiteral("application/octet-stream");
00431         }
00432         rv = nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem,
00433                         NS_ConvertUTF8toUTF16(contentType), &mediatypechanged);
00434       }
00435     } else {
00436       rv = nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem,
00437                         EmptyString(), &mediatypechanged);
00438     }
00439   }
00440 
00441   *aChanged = filenameChanged || mediatypechanged;
00442   return rv;
00443 }
00444 
00445 typedef nsAutoBuffer<char, 256> nsAutoCharBuffer;
00446 
00447 static void
00448 ReportEncodingMemoryError(nsIDOMElement* aElement, nsIFile *aFile,
00449                           PRUint32 aFailedSize)
00450 {
00451   nsAutoString filename;
00452   if (NS_FAILED(aFile->GetLeafName(filename))) {
00453     return;
00454   }
00455 
00456   nsAutoString size;
00457   size.AppendInt((PRInt64)aFailedSize);
00458   const PRUnichar *strings[] = { filename.get(), size.get() };
00459   nsXFormsUtils::ReportError(NS_LITERAL_STRING("encodingMemoryError"),
00460                              strings, 2, aElement, aElement);
00461 }
00462 
00463 nsresult
00464 nsXFormsUploadElement::EncodeFileContents(nsIFile *aFile, PRUint16 aType,
00465                                           PRUnichar **aResult)
00466 {
00467   nsresult rv;
00468 
00469   // get file contents
00470   nsCOMPtr<nsIInputStream> fileStream;
00471   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFile,
00472                                   PR_RDONLY, -1 /* no mode bits */,
00473                                   nsIFileInputStream::CLOSE_ON_EOF);
00474   NS_ENSURE_SUCCESS(rv, rv);
00475 
00476   PRUint32 size;
00477   rv = fileStream->Available(&size);
00478   NS_ENSURE_SUCCESS(rv, rv);
00479 
00480   nsAutoCharBuffer fileData;
00481   if (!fileData.EnsureElemCapacity(size + 1)) {
00482     ReportEncodingMemoryError(mElement, aFile, size + 1);
00483     return NS_ERROR_OUT_OF_MEMORY;
00484   }
00485 
00486   PRUint32 bytesRead;
00487   rv = fileStream->Read(fileData.get(), size, &bytesRead);
00488   NS_ASSERTION(NS_SUCCEEDED(rv) && bytesRead == size,
00489                "fileStream->Read failed");
00490 
00491   if (NS_SUCCEEDED(rv)) {
00492     if (aType == nsISchemaBuiltinType::BUILTIN_TYPE_BASE64BINARY) {
00493       // encode file contents
00494       *aResult = nsnull;
00495       char *buffer = PL_Base64Encode(fileData.get(), bytesRead, nsnull);
00496       if (buffer) {
00497         *aResult = ToNewUnicode(nsDependentCString(buffer));
00498         PR_Free(buffer);
00499       }
00500       if (!*aResult) {
00501         PRUint32 failedSize = buffer ? strlen(buffer) * sizeof(PRUnichar)
00502                                      : ((bytesRead + 2) / 3) * 4 + 1;
00503         ReportEncodingMemoryError(mElement, aFile, failedSize);
00504         rv = NS_ERROR_OUT_OF_MEMORY;
00505       }
00506     } else if (aType == nsISchemaBuiltinType::BUILTIN_TYPE_HEXBINARY) {
00507       // create buffer for hex encoded data
00508       PRUint32 length = bytesRead * 2 + 1;
00509       PRUnichar *fileDataHex =
00510         NS_STATIC_CAST(PRUnichar*, nsMemory::Alloc(length * sizeof(PRUnichar)));
00511       if (!fileDataHex) {
00512         ReportEncodingMemoryError(mElement, aFile, length * sizeof(PRUnichar));
00513         rv = NS_ERROR_OUT_OF_MEMORY;
00514       } else {
00515         // encode file contents
00516         BinaryToHex(fileData.get(), bytesRead, &fileDataHex);
00517         fileDataHex[bytesRead * 2] = 0;
00518         *aResult = fileDataHex;
00519       }
00520     } else {
00521       NS_ERROR("Unknown encoding type for <upload> element");
00522       rv = NS_ERROR_INVALID_ARG;
00523     }
00524   }
00525 
00526   return rv;
00527 }
00528 
00529 static inline PRUnichar
00530 ToHexChar(PRInt16 aValue)
00531 {
00532   if (aValue < 10)
00533     return (PRUnichar) aValue + '0';
00534   else
00535     return (PRUnichar) aValue - 10 + 'A';
00536 }
00537 
00538 void
00539 nsXFormsUploadElement::BinaryToHex(const char *aBuffer, PRUint32 aCount,
00540                                    PRUnichar **aHexString)
00541 {
00542   for (PRUint32 index = 0; index < aCount; index++) {
00543     (*aHexString)[index * 2] = ToHexChar((aBuffer[index] >> 4) & 0xf);
00544     (*aHexString)[index * 2 + 1] = ToHexChar(aBuffer[index] & 0xf);
00545   }
00546 }
00547 
00548 
00549 NS_HIDDEN_(nsresult)
00550 NS_NewXFormsUploadElement(nsIXTFElement **aResult)
00551 {
00552   *aResult = new nsXFormsUploadElement();
00553   if (!*aResult)
00554     return NS_ERROR_OUT_OF_MEMORY;
00555 
00556   NS_ADDREF(*aResult);
00557   return NS_OK;
00558 }