I had a need to automatically fill the value of an HTML file upload box in Firefox. I thought Greasemonkey would just do the job. But, it was not that simple because to prevent a malicious website to upload any file in your local system without your knowledge, Firefox prevents the value of a file upload box to be set programmatically. Googling for information to do what I wanted did not return a good result other than simple information stating that it would not be possible due to the aforementioned security reason. So, I digged the source code to figure out either the way myself or better googling keywords. Finally, I came up with the right way to do what I wanted.
First, I downloaded the source code of 3.0.19-real-real that matched my system Firefox from ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/ as per https://developer.mozilla.org/en/Download_Mozilla_Source_Code. After that, the next step was to find out the code that managed the HTML file upload box. The simple way was of course to ask someone familiar with Firefox internals via IRC as per http://irc.mozilla.org/ or mailing list as per https://lists.mozilla.org/listinfo. But, I took the hard way to get some feeling on Firefox internals. So, I built a debuggable Firefox as per https://developer.mozilla.org/en/build_documentation and specifically as per https://developer.mozilla.org/en/Configuring_Build_Options.
After obtaining the debuggable version, I fired Firefox inside GDB as per https://developer.mozilla.org/en/Setting_up_extension_development_enviro... and https://developer.mozilla.org/En/Debugging, specifically https://developer.mozilla.org/en/Debugging_Mozilla_with_gdb. Setting a break point at main() and running the program informed me that the entry point is in browser/app/nsBrowserApp.cpp. So, I opened the file and read the source code in main() (downward arrow signifies function call while [*] signifies my comment):
[*] Bunch of code to load information necessary for XULRunner from application.ini XRE_main(argc, argv, appData); | V toolkit/xre/nsAppRunner.cpp (the XULRunner entry point)
Since the code beyond XRE_main is XULRunner's, now I have the strong assurance that Firefox is simply another XULRunner application that is platform independent.
So, the file picker dialog box that is presented everytime an HTML file upload box is clicked should be what XULRunner is doing since the dialog is platform dependent. Since the file picker dialog box will eventually read a local file, the XULRunner mechanism to read a local file should be the same as that that is used to read application.ini to create appData. So, I looked closer at how appData is created in main(): XRE_CreateAppData(appini, &appData). Since appData is created from appini, I looked at how appini is created in main():
XRE_GetBinaryPath(argv[0], getter_AddRefs(appini));
|
V
toolkit/xre/nsAppRunner.cpp:
NS_NewNativeLocalFile(nsDependentCString(exePath), PR_TRUE,
| getter_AddRefs(lf));
|
V
xpcom/io/nsLocalFileUnix.cpp:
nsLocalFile *file = new nsLocalFile();
file->InitWithNativePath(path);
[*] NS_NewNativeLocalFile is used to instantiate a file object
that points to the real file through the given path.
appini->SetNativeLeafName(NS_LITERAL_CSTRING("application.ini"));
[*] This means that application.ini is expected to be
in the same directory as the executable.
// Allow firefox.exe to launch XULRunner apps via -app
// Note that -app must be the *first* argument.
char *appEnv = nsnull;
const char *appDataFile = PR_GetEnv("XUL_APP_FILE");
if (appDataFile && *appDataFile) {
rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
|
V
toolkit/profile/src/nsToolkitProfileService.cpp:
return NS_NewNativeLocalFile(nsDependentCString(fullPath),
PR_TRUE, aResult);
[*] This means that XULRunner apps can be launched by setting
environment variable 'XUL_APP_FILE' instead of using '-app' switch.
}
else if (argc > 1 && IsArg(argv[1], "app")) {
rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
|
V
toolkit/profile/src/nsToolkitProfileService.cpp:
return NS_NewNativeLocalFile(nsDependentCString(fullPath),
PR_TRUE, aResult);
appEnv = PR_smprintf("XUL_APP_FILE=%s", argv[2]);
PR_SetEnv(appEnv);
[*] This means that in all cases, the environment variable
'XUL_APP_FILE' is always set to the name of the XULRunner
application.
}
[*] Both 'if' branches call XRE_GetFileFromPath() to point
to the file specified through either the environment
variable or the command line switch, respectively, to
run a XULRunner application other than Firefox.
If nothing specifed, appini will still point to
'application.ini' in the same directory as the executable.
And, as a result, XULRunner will run Firefox.
Okay, now I have a feeling as to where everything will boil down to when it comes to picking a local file in my GNU/Linux system.
After that, I tried to trace Firefox execution when I clicked the browse button of a file upload box. This won't easily work if the Firefox window is covered by another window because whenever CTRL+C is pressed in GDB console to see the backtrace, the Firefox window will stop refreshing the drawable components including the browse button that have been covered by the console window, resulting in a blank area in the Firefox window. To allow me to click the browse button, I arranged Firefox and GDB console windows such that they didn't overlap with each other. After this, I pressed CTRL+C first in GDB console before clicking the browse button in Firefox. Returning to the GDB console after clicking the browse button and stepping over the code while observing the backtrace gave me the following interesting backtrace snippet that told me that nsFileControlFrame.cpp is the one that launches a file picker as well as handling the selected file:
#7 0xb52649ca in nsFilePicker::Show (this=0xaf414bc0, aReturn=0xbfc63cb8)
at /media/bond/zzz_ff/mozilla/widget/src/gtk2/nsFilePicker.cpp:661
#8 0xb3a87acc in nsFileControlFrame::MouseListener::MouseClick
(this=0xaf4fbfc0, aMouseEvent=0xaf414b70)
at /media/bond/zzz_ff/mozilla/layout/forms/nsFileControlFrame.cpp:347
So, I looked into the code after line 347 in nsFileControlFrame::MouseListener::MouseClick(nsIDOMEvent* aMouseEvent) in nsFileControlFrame.cpp below:
// Set property
nsCOMPtr localFile;
result = filePicker->GetFile(getter_AddRefs(localFile));
[*] the file selected by the user is stored in variable localFile.
if (localFile) {
nsAutoString unicodePath;
result = localFile->GetPath(unicodePath);
[*] the path of the selected file is stored in variable unicodePath.
if (!unicodePath.IsEmpty()) {
// Tell mTextFrame that this update of the value is a user initiated
// change. Otherwise it'll think that the value is being set by a script
// and not fire onchange when it should.
[*] An interesting comment indeed! We are zooming in on the target.
PRBool oldState = mFrame->mTextFrame->GetFireChangeEventState();
mFrame->mTextFrame->SetFireChangeEventState(PR_TRUE);
[*] But, those trick above are not what I wanted to play this clean
(i.e., using ordinary JavaScript code in Firefox instead of creating my
own version of Firefox)
nsCOMPtr fileControl = do_QueryInterface(content);
if (fileControl) {
fileControl->SetFileName(unicodePath);
[*] This method is the thing that I needed to look at to find a clean way to
programmatically set the value of a file upload box.
}
mFrame->mTextFrame->SetFireChangeEventState(oldState);
// May need to fire an onchange here
mFrame->mTextFrame->CheckFireOnChange();
return NS_OK;
}
}
To find out the code of SetFileName() method used in "fileControl->SetFileName(unicodePath)", I needed to look at the class of fileControl. Since fileControl is an object implementing nsIFileControlElement, I grep-ed all C/C++ source files that includes nsIFileControlElement. "find -type f \( -iname '*.c' -o -iname '*.cpp' \) | xargs grep nsIFileControlElement\\.h" with the following result:
./content/html/content/src/nsHTMLInputElement.cpp:#include "nsIFileControlElement.h" ./layout/forms/nsFileControlFrame.cpp:#include "nsIFileControlElement.h"
So, to ./content/html/content/src/nsHTMLInputElement.cpp I went to find the implementation of SetFileName():
void
nsHTMLInputElement::SetFileName(const nsAString& aValue)
{
// No big deal if |new| fails, we simply won't submit the file
mFileName = aValue.IsEmpty() ? nsnull : new nsString(aValue);
[*] mFileName should be the field that holds the file name.
Since the code that follows seems to be a view update,
I looked at mFileName.
// No need to flush here, if there's no frame at this point we
// don't need to force creation of one just to tell it about this
// new value. We just want the display to update as needed.
nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
if (formControlFrame) {
formControlFrame->SetFormProperty(nsGkAtoms::value, aValue);
}
UpdateFileList();
SetValueChanged(PR_TRUE);
}
In the beginning of the file, mFileName is defined in as a protected field as follows:
/** * The value of the input if it is a file input. This is the filename used * when uploading a file. It is vital that this is kept separate from mValue * so that it won't be possible to 'leak' the value from a text-input to a * file-input. Additionally, the logic for this value is kept as simple as * possible to avoid accidental errors where the wrong filename is used. * Therefor the filename is always owned by this member, never by the frame. * Whenever the frame wants to change the filename it has to call * SetFileName to update this member. */ nsAutoPtr mFileName;
From the comment, I was sure that I already found out where the value of a file input box is stored. But, I had only seen the internal method to set it. Therefore, I guessed that if there were really a way to set the value of a file input box from a JavaScript using ".value = ", such a way would at the end invoke SetFileName. So, I went to find methods that invoke SetFileName. There is one method having an interesting name in the same file: SetValue.
NS_IMETHODIMP
nsHTMLInputElement::SetValue(const nsAString& aValue)
{
// check security. Note that setting the value to the empty string is always
// OK and gives pages a way to clear a file input if necessary.
[*] That's a very interesting comment indeed!
if (mType == NS_FORM_INPUT_FILE) {
if (!aValue.IsEmpty()) {
if (!nsContentUtils::IsCallerTrustedForCapability("UniversalFileRead")) {
// setting the value of a "FILE" input widget requires the
// UniversalFileRead privilege
return NS_ERROR_DOM_SECURITY_ERR;
[*] Here I figured out a clean way to programmatically set the value of
a file upload box. If I managed to pass IsCallerTrustedForCapability
check for UniversalFileRead, my JavaScript code to set the value of
a file input box with ".value = " would work.
}
}
SetFileName(aValue);
}
else {
SetValueInternal(aValue, nsnull, PR_FALSE);
}
return NS_OK;
}
So, I needed to figure out how nsContentUtils::IsCallerTrustedForCapability works. "find -type f \( -iname '*.c' -o -iname '*.cpp' \) | xargs grep 'nsContentUtils::IsCallerTrustedForCapability'" showed that the method is located in ./content/base/src/nsContentUtils.cpp:
// static
PRBool
nsContentUtils::IsCallerTrustedForCapability(const char* aCapability)
{
// The secman really should handle UniversalXPConnect case, since that
// should include UniversalBrowserRead... doesn't right now, though.
PRBool hasCap;
if (NS_FAILED(sSecurityManager->IsCapabilityEnabled(aCapability, &hasCap)))
return PR_FALSE;
if (hasCap)
return PR_TRUE;
if (NS_FAILED(sSecurityManager->IsCapabilityEnabled("UniversalXPConnect",
&hasCap)))
return PR_FALSE;
return hasCap;
}
Okay, my next step was to figure out how sSecurityManager works so that I might know how to convince sSecurityManager that I had the capability. So, I needed to see the instance of sSecurityManager in the same file: nsIScriptSecurityManager *nsContentUtils::sSecurityManager. It didn't help me since it is only an interface. So, I looked at how sSecurityManager is initialized.
// static
nsresult
nsContentUtils::Init()
{
if (sInitialized) {
NS_WARNING("Init() called twice");
return NS_OK;
}
nsresult rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
&sSecurityManager);
...
}
At this point, I figured out that I needed to know how XPCOM works since CallGetService is implemented in ./xpcom/glue/nsServiceManagerUtils.h. Before I continued that way, I tried to google for "firefox security giving UniversalFileRead capability" that landed me on http://cakebaker.42dh.com/2006/03/29/file-upload-with-selenium/. The article told me that I could obtain UniversalFileRead capability by putting the following JavaScript code before setting the value of a file upload box using ".value = ".
netscape.security.PrivilegeManager.enablePrivilege('UniversalFileRead');
I also googled for "firefox file upload UniversalFileRead privilege" that returned this vital information: http://forums.mozillazine.org/viewtopic.php?t=550045. In addition to "UniversalFileRead", I also need "UniversalXPConnect" privilege.
Both articles told me that the code will only work if I set "signed.applets.codebase_principal_support" to true in "about:config" or in "prefs.js". That made me curios to find out what codebase_principal_support is. Googling for it landed me on https://developer.mozilla.org/en/Bypassing_Security_Restrictions_and_Sig....
Now I figured out that setting codebase_principal_support to true will make Firefox grant additional privilege requests from any site. That's surely not what I want. So, I looked for a cleaner way than setting codebase_principal_support to true. Based on the article on "Bypassing Security Restrictions and Signing Code", the cleaner way will be to sign my JavaScript code: http://www.mozilla.org/projects/security/components/signed-scripts.html.
To conclude, the best way one can programmatically sets the value of a file input/upload box in Firefox using JavaScript ".value = " is using a signed script. For the real thing, see the next article.
QtWkwprvoV
ZgaEJK aznfonmmwoeu, [url=http://kjfdvlgbjphj.com/]kjfdvlgbjphj[/url], [link=http://qpqadazikrdp.com/]qpqadazikrdp[/link], http://knxmijlcgnzh.com/